<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dl_01312.log</title>
        <link>https://velog.io/</link>
        <description>느리게 갱신되는 개발실력 - &gt;_0</description>
        <lastBuildDate>Sun, 15 Mar 2026 15:41:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dl_01312.log</title>
            <url>https://velog.velcdn.com/images/dl_01312/profile/08101e89-2a48-4e16-abb4-49ce3a92f495/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dl_01312.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dl_01312" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Vibe Coding에서 Agentic Development까지
| 주니어 개발자의 AI 개발 적응기]]></title>
            <link>https://velog.io/@dl_01312/Vibe-Coding%EC%97%90%EC%84%9C-Agentic-Development%EA%B9%8C%EC%A7%80-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-AI-%EA%B0%9C%EB%B0%9C-%EC%A0%81%EC%9D%91%EA%B8%B0</link>
            <guid>https://velog.io/@dl_01312/Vibe-Coding%EC%97%90%EC%84%9C-Agentic-Development%EA%B9%8C%EC%A7%80-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-AI-%EA%B0%9C%EB%B0%9C-%EC%A0%81%EC%9D%91%EA%B8%B0</guid>
            <pubDate>Sun, 15 Mar 2026 15:41:22 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가면서">들어가면서</h1>
<blockquote>
<p>Agentic Development를 회사와 개인프로젝트에서 적용 후 인사이트를 정리한 글입니다.</p>
</blockquote>
<p>주니어 개발자로 근무한 지 어느덧 6개월이 되었습니다. 
그동안 개발자에 대한 가치관, 개발방식에 대한 여러 인사이트를 배울 수 있어서 내적으로 많은 변화가 생겼음을 체감할 수 있었습니다.</p>
<p>특히 개발 패러다임에 큰 변화가 생겨서 이를 정리하는 시간을 가지려고 합니다.</p>
<p>26년도 부터 회사에서는 KPI 평가 항목에 Vibe-Coding 즉, <strong>Agent를 이용한 (Claude, Gemini) 개발 활용</strong>이 추가되었고 덕분에 <strong>Claude, Gemini</strong>를 개인돈을 들이지 않고 강제로(?) Vibe-Coding을 할 수 있게 되었습니다!!(감사합니다 ㅠㅠ)</p>
<p>다가올 미래를 위해, 현재까지 느낀점을 정리해보고자 합니다.</p>
<h1 id="1-vibe-coding과-agentic-development란-무엇인가">1. Vibe-Coding과 Agentic Development란 무엇인가?</h1>
<h2 id="vibe-coding">Vibe-Coding</h2>
<p>결과물을 생성하기 위해, 말하듯이 자연어로 개발하는 과정을 뜻합니다.
당장의 결과물을 생성해 내는데 중점이 맞춰져 있습니다.</p>
<h2 id="agentic-development">Agentic Development</h2>
<p>기존 인간 중심의 <strong>요구사항 파악 &gt; 설계 &gt; 구현 &gt; 테스트 &gt; 유지보수</strong>의 일련의 과정을, AI-Agent가 자율적으로 판단하고 실행하는 방식으로 함께 관리 및 개발하는 과정입니다.</p>
<hr>
<h1 id="2-회사가-원하는-것은-agentic-development">2. 회사가 원하는 것은 Agentic Development</h1>
<p>회사의 니즈는 생성(Vibe-Coding)뿐만 아니라, 계획, 설계, 테스트, 향후 유지보수 비용까지이다.
즉, 개발의 패러다임은 변화해도 기존 효율적인 SDLC를 유지해야한다.</p>
<p>실제로 회사에 이미 Agentic Development를 적용하여 개발중인 시니어 개발자분에게 여쭤보았고
새로운 인사이트를 얻을 수 있었다.</p>
<h2 id="생산성의-폭발에-양면성">생산성의 폭발에 양면성</h2>
<p>대부분의 SW 기업은 SaaS, 온프레미스 형태의 구독료 및 라이선스비용을 받는다. 
이때 무시될 수 없는 비용은 <strong>유지보수 비용</strong> 이다. 이러한 환경에서는, 생산성 증가와 그에 따른 유지보수성 역시 함께 고려되어야 한다.</p>
<p>그렇기 때문에 인간이 Agent를 통제하고 도구로서 이용해야지, Agent가 인간을 도구로 이용하게 두면 안된다고 하셨다.
단기적으로는 기능이 돌아가니, 이상은 없지만 실제 사용환경에서 유지보수는 전혀 다른 이야기가 되기 때문이다.</p>
<p>즉 <strong>폭발하는 생산성을 뒷받침하는 유지보수 효율성 개선도 고민되어야 한다.</strong></p>
<h2 id="실무에는-책임의-영역이-존재한다">실무에는 책임의 영역이 존재한다</h2>
<p>실무는 내가 개발한 내용에 대한 향후 관리를 넘어서 비즈니스 협업적으로 주어진 책임이 존재한다.
그렇기 때문에, 개발만 하고 멈추는 게 아닌, 향후 이어 개발할 사람과 이에 대해 충분히 설명하고 비즈니스 요구사항을 함께 해결해 나갈 수 있어야 한다.</p>
<p>이러한 특수성 때문에 단순히 AI를 활용한 폭발적인 생산성 향상에만 초점을 두는 것은 니즈에 어긋난다고 생각했다
그렇기 때문에 회사는 단순 Vibe-Coding을 사용하는 <strong>개발자</strong> 보다는 <strong>Agentic Development 개발자</strong>에 수요가 있다.</p>
<hr>
<h1 id="3-프로젝트-spec-문서에-시간을-투자하자">3. 프로젝트 Spec 문서에 시간을 투자하자</h1>
<p>개인적으로는 Agentic Development에서 코드치는 시간보다 Spec 문서를 작성하는 시간이 더 많아져야한다고 생각한다. 물론 이러한 과정을 <strong>번거롭게</strong> 생각 할 수도 있다.</p>
<p>하지만 <strong>Trade-off 관점</strong>에서 보면, 프로젝트가 장기화 되거나, 다수와 협업을 할수록 실보다는 득이 <strong>압도적으로 크다</strong></p>
<h2 id="vibe-coding은-동일한-퀄리티를-보장하기-힘들다">Vibe-Coding은 동일한 퀄리티를 보장하기 힘들다.</h2>
<p>다수와 작업을 하게 될 경우, Agent가 작동하는 세션의 숫자가 많아질 수록 퀄리티가 천차만별일 것이다.
하지만 프로젝트 Spec 문서를 작성해 놓고 버전관리를 한다면, 세션이 달라도 Agent는 동일한 프로젝트 마크다운 파일을 읽고 작업을 시작 할 것이다. </p>
<p>물론 100% 동일한 퀄리티를 보장하긴 어렵다(이건 인간도 ㅎㅎ..) 
하지만 Spec 마크다운 문서가 정확/명확하게 작성이 되어있는 경우라면 퀄리티를 비약적으로 향상시킬 수 있다.</p>
<p>결국은 설계, 개발, 테스팅 등 <strong>Agentic Development</strong>를 지향해야한다고 생각한다.</p>
<hr>
<h1 id="4-프로젝트-spec-md-파일은-인간ai-공용이다">4. 프로젝트 spec .md 파일은 인간/AI 공용이다.</h1>
<p>.md 파일을 한글로 작성하면, 영어로 작성했을때 보다 40~50% 정도 손해가 있는건 사실이다,
그럼에도 불구하고 개인적으로는 한글로, 정확히는 팀이 주로 사용하는 언어로 적는 것을 권장하고 있다.</p>
<h2 id="ai뿐만-아니라-인간도-참조해야한다">AI뿐만 아니라 인간도 참조해야한다.</h2>
<p>한글(팀 언어기반)을 이용해서 작성해야하는 가장 큰 이유중 하나이다.</p>
<p>이미 올바르게 프로젝트 spec이 작성 되었다면, 그 자체 하나로 좋은 문서가 될 수 있기 때문이다.
또한 문서가 항상 올바르게 작성이 된다는 보장이 없기 때문에 코드를 리팩토링하는 것처럼 
.md 파일 또한 주기적으로 리팩토링해줘야한다, 이때 팀이 익숙한 언어로 적혔을 때 유리하다.</p>
<p><strong>개발자도 개발 전 문서를 읽고 시작한다면,
Agent가 잘못된 방향으로 개발하는 것을 바로잡을 수 있다.</strong></p>
<hr>
<h1 id="5-context를-최대한-가볍게-유지해야한다">5. Context를 최대한 가볍게 유지해야한다.</h1>
<blockquote>
<p>Context : Agent가 사용자와 대화, 프롬프트, 파일, 도구 등을 참조할 수 있는 용량</p>
</blockquote>
<p>작성된 Skills, 프로젝트 spec .md 파일들이 Context의 대부분을 점유 한다면, Agent의 작업 결과물의 퀄리티는 매우 떨어진다.
즉, CLAUDE.md, spec .md 파일을 적을때도 무작정 적는것이 아닌, 개발자가 예상가능한 범위에서 작성되어야 한다.</p>
<h2 id="사이드-프로젝트-spec-md-파일">사이드 프로젝트 Spec .md 파일</h2>
<pre><code> Side-Project/
  │
  ├── CLAUDE.md ( 200 줄 이하 )
  │   └── 진입점 &amp; Context-Switcher
  │
  └── agent_docs/
      ├── architecture.md (필수)
      │   └── 프로젝트 전체적인 아키텍처, 3-Layer, MVC, Design Pattern, Plugin 정의
      │
      ├── conventions.md (필수)
      │   └── 코딩 규칙 &amp; 패턴 (로깅, 메서드 등) &amp; 주석 등
      │
      ├── database.md (필수)
      │   └── 도메인별 DB Table 스키마 작성
      │
      ├── Service.md (필수)
      │   └── 도메인, 서비스 구현 정보 등
      │
      └── etc...  (옵션) (확장 기능 및 새로운 rule)
          └── test.md, rule.md, decisions.md</code></pre><h3 id="claudemd">CLAUDE.md</h3>
<ul>
<li>Agent는 개발시 CLAUDE.md 파일을 한번 읽는다.</li>
<li>CLAUDE.md 는 간단한 프로젝트 개요와 함께 추가 정보를 제공하고 어떤 문서를 사용할지 Agent에게 가이드를 하는 역할을 한다</li>
</ul>
<p>CLAUDE.md 파일을 200줄 이하로 작성하는 것이 좋다는 후기들이 많다.
무엇보다도 CLAUDE.md 파일에 모든 .md 파일을 다 작성한다면, 필요 없는 Context까지 읽게 되어 효율이 떨어진다.</p>
<p>그렇기 때문에 CLAUDE.md는 최대한 가볍게 작성을 하되, Context Switcher 역할을 수행하도록 명확히 작성해야한다.</p>
<h3 id="agent_docs">agent_docs/</h3>
<ul>
<li>CLAUDE.md에서 Context Switcher가 지정해주는 명세 파일을 모아두는 폴더</li>
<li>자유 양식, 팀에서 필요한 섹션별로 정리하는게 좋아 보임</li>
</ul>
<h2 id="실제-작성된-claudemd-agent_docsmd-리뷰-결과">실제 작성된 CLAUDE.md, agent_docs/*.md 리뷰 결과</h2>
<ul>
<li>Claude Sonnet 4.5 (200K Context) 사용</li>
<li>명세 파일 토큰 효율성 검사 요청했습니다.</li>
</ul>
<table>
<thead>
<tr>
<th>파일</th>
<th>줄 수</th>
<th>예상 토큰</th>
</tr>
</thead>
<tbody><tr>
<td>CLAUDE.md</td>
<td>70줄</td>
<td>~1,000-1,500</td>
</tr>
<tr>
<td>Architecture.md</td>
<td>247줄</td>
<td>~4,000-5,000</td>
</tr>
<tr>
<td>Database.md</td>
<td>357줄</td>
<td>~5,000-6,000</td>
</tr>
<tr>
<td>Service.md</td>
<td>361줄</td>
<td>~5,000-6,000</td>
</tr>
<tr>
<td>Conventions.md</td>
<td>243줄</td>
<td>~4,000-4,500</td>
</tr>
<tr>
<td>Decisions.md</td>
<td>155줄</td>
<td>~2,000-2,500</td>
</tr>
<tr>
<td><strong>전체</strong></td>
<td><strong>1,433줄</strong></td>
<td><strong>~21,000-25,000</strong></td>
</tr>
</tbody></table>
<pre><code>효율성 분석

200,000 토큰 중 ~25,000 토큰 = 12.5% (최악인 경우)

이 정도면 매우 효율적입니다:

✅ 한 번 읽으면 세션 내 캐싱 - Service.md를 다시 참조 시 캐싱된 데이터는 비용이 적음
✅ 선택적 읽기 가능 - 필요한 1-2개 파일만 읽으면 5,000-10,000 토큰
✅ 175,000 토큰 여유 - 코드 읽기/작성에 충분한 공간


결론

현재 문서 세트는 **전체 컨텍스트의 12.5%**로 매우 적절합니다.
실제 개발 시 코드 읽기/작성에 87.5%를 사용할 수 있어 이상적입니다.</code></pre><h3 id="claudemd에-모든것을-다-넣지-말자">CLAUDE.md에 모든것을 다 넣지 말자.</h3>
<p>Agent는 작업시 필요한 .md 파일을 읽게된다, 이때 필요 없는 파일은 읽지 않는다 이렇게되면
Context를 절약 할 수 있게 되며, 그만큼 Agent는 일을 할 수 있는 공간이 확보되어 효율적이다.</p>
<p>물론 최악인 경우 (모든 명세가 필요할때)를 대비하여 미리 중복되거나, .md파일내 코드 블럭 등
불필요한 요소를 리팩터링하는 활동도 필요하다</p>
<p>개발자의 역할은 점점 <strong>코드 작성자</strong>에서
<strong>&#39;명세를 설계하는 사람&#39;</strong>으로 변화하고 있는 것 같습니다.</p>
<hr>
<h1 id="6-agent는-마법사가-아니다">6. Agent는 마법사가 아니다</h1>
<p>소위 말하는 딸깍(?)으로 개발되지 않는다. 내가 원하는 방향으로 개발을 진행하기 위해서는 많은 정보를 입력해야 한다. 코드 컨벤션, 개발 순서 및 아키텍처 구조, 참조 코드 등 여러가지 정보를 세밀하게 조절하여 프롬프트를 작성해야 더 나은 결과물이 생성된다.</p>
<p>심지어 명세를 잘 작성해놔도, Agent가 이전 context를 잊어버리던가, 할루시네이션으로 인한 문제도 존재한다. 이 때문에 인간이 결과물을 <strong>검증</strong>하는 단계가 꼭 필요하다.</p>
<p>Agent를 사용하는 개발자 본인도 프로젝트와 WorkFlow에 대한 높은 수준의 이해가 필요하다.</p>
<hr>
<h1 id="마무리하면서">마무리하면서</h1>
<p>AI를 이용한 개발은 이제 피한다고 해서 피해지는 게 아니라, 어떻게 잘 다룰 수 있는지를 고민하는 게
이 시대의 과제인 것 같습니다 ㅎㅎ..</p>
<p>특히 기존 개발자의 역할에서 새로운 역할로 변경하는 과도기에 있는 것 같아서 흥미롭네요
좋은 환경에서 배울 수 있음에 감사하면서 대체당하지 않도록 노력해보겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[합리적인 애플리케이션이 Error를 해결하지 않는 이유]]></title>
            <link>https://velog.io/@dl_01312/%ED%95%A9%EB%A6%AC%EC%A0%81%EC%9D%B8-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%B4-Error%EB%A5%BC-%ED%95%B4%EA%B2%B0%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dl_01312/%ED%95%A9%EB%A6%AC%EC%A0%81%EC%9D%B8-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%B4-Error%EB%A5%BC-%ED%95%B4%EA%B2%B0%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Mon, 24 Nov 2025 15:46:06 GMT</pubDate>
            <description><![CDATA[<h1 id="🚨-error">🚨 Error</h1>
<blockquote>
<p>Error의 하위 클래스로, Throwable 합리적인 애플리케이션에서는 포착하려고 시도해서는 안 되는 심각한 문제를 나타냅니다. 이러한 오류는 대부분 비정상적인 상태입니다.</p>
</blockquote>
<ul>
<li><a href="https://docs.oracle.com/javase/6/docs/api/java/lang/Error.html">https://docs.oracle.com/javase/6/docs/api/java/lang/Error.html</a></li>
</ul>
<h1 id="🛠️-exception">🛠️ Exception</h1>
<blockquote>
<p>클래스 와 하위 클래스는 합리적인 애플리케이션이 포착하고자 할 수 있는 조건을 나타내는 Exception형식입니다.</p>
</blockquote>
<ul>
<li><a href="https://docs.oracle.com/javase/6/docs/api/java/lang/Exception.html">https://docs.oracle.com/javase/6/docs/api/java/lang/Exception.html</a></li>
</ul>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/c610559d-09c5-4619-805a-6e8f0f7f56cb/image.png" alt=""></p>
<h1 id="error-exception">Error Exception</h1>
<p>Java는 애플리케이션 실행 중 발생할 수 있는 이상 상태를 두 가지 클래스로 구분합니다.</p>
<ul>
<li><code>Exception</code> : 애플리케이션이 예측하고 복구할 수 있는 문제</li>
<li><code>Error</code> : 시스템 수준 문제로, 애플리케이션이 복구할 수 없는 심각한 문제</li>
</ul>
<h2 id="💻-reasonable-application">💻 Reasonable Application</h2>
<p>위 JavaDocs 원문을 보면 <strong>합리적인 애플리케이션</strong>이라는 문장이 공통적으로 강조되어 설명하고 있습니다.</p>
<p><strong>Error</strong>는 애플리케이션이 예측하거나, 포착하려고 시도조차해서는 안되고,
반대로 <strong>Exception</strong>은 예상범위 내에서 예측하고, 포착하는 것을 <strong>합리적인 애플리케이션</strong>이라고 표현하고 있습니다.</p>
<p>이러한 가이드라인을 준수하는 애플리케이션을 JavaDoc에선 합리적인 애플리케이션(Reasonable Application)이라고 표현합니다.</p>
<h2 id="🙌-error를-잡지-않는-합리적인-이유">🙌 Error를 잡지 않는 합리적인 이유</h2>
<p>만약 여러분이 만든 애플리케이션에 <strong>OutOfMemoryError</strong>가 발생한 상황이고
애플리케이션 이것을 예측하고 포착하여 복구(Recovery)를 시도 중인 상황을 예를 들어 봅시다.</p>
<p>애플리케이션이 어떤 방식으로 스스로 복구 할 수 있을까요?</p>
<h3 id="error-message를-작성하여-사용자에게-알린다">Error Message를 작성하여 사용자에게 알린다.</h3>
<ul>
<li><p>Memory가 없는 상태를 사용자에게 알려서 어떻게 <strong>복구</strong>하나요?
  즉, 사용자는 Error Message를 받아도 애플리케이션이 스스로 복구되도록 유도 할 수 없습니다.</p>
</li>
<li><p>Memory가 부족한 상태에서 복구를 하기위해 <strong>Error Response 객체</strong>를 만들어 복구한다?
  해당 객체를 만들 메모리 조차 없을 수도 있고 무엇보다 상태를 더욱 악화시키는 방식입니다.</p>
</li>
</ul>
<h3 id="error를-포착하여서-로직적으로-해결한다">Error를 포착하여서 로직적으로 해결한다.</h3>
<ul>
<li>서버 Memory가 부족하여 생긴 Error를 포착하고 자바코드를 통해 해결 할 수 있나요?
  물론, GC 튜닝을 하거나 Memory leak를 개선하는 작업을 <strong>누군가</strong>가 할 수 있지만
  <strong>실행중인 애플리케이션</strong>이 스스로 해결할 수는 없습니다.</li>
</ul>
<h1 id="🧐-결론">🧐 결론</h1>
<p>따라서 Error를 예측하여도, 포착하여도, 이를 스스로 복구하려고 해도, 근본적인 문제를 해결하지 못합니다.
결국 <strong>개발자가 애플리케이션을 종료하고, Memory Leak 분석, GC튜닝, 수평/수직확장을 통해 해결해야하는
문제이지, 실행중인 애플리케이션이 스스로 복구할만한 문제가 아닙니다.</strong></p>
<p>이러한 이유때문에 Java에서는 Error를 예측, 포착하려고 시도하지않고 더나아가 복구조차 하지 않는 
애플리케이션을 <strong>합리적</strong>이라고 표현합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring에서의 Proxy 패턴 적용해보기]]></title>
            <link>https://velog.io/@dl_01312/Spring%EC%97%90%EC%84%9C%EC%9D%98-Proxy-%ED%8C%A8%ED%84%B4-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@dl_01312/Spring%EC%97%90%EC%84%9C%EC%9D%98-Proxy-%ED%8C%A8%ED%84%B4-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 03 Oct 2025 16:46:08 GMT</pubDate>
            <description><![CDATA[<h1 id="💻-proxy-pattern-이란">💻 Proxy Pattern 이란?</h1>
<blockquote>
<p>한국어로는 &quot;대리인&quot;이라는 뜻</p>
</blockquote>
<p>대상 객체로의 접근을 중간에서 대신 중개하여 로직에 흐름을 제어하거나 
특정 작업을 하도록 설계하는 개발 방법이다.</p>
<p>여기에서 <strong>어떤 오브젝트</strong>라 함은, 매우 넓은 의미인데 
말 그대로 Object, Server, Network 등 다양하다. </p>
<p>이 포스팅에서는 <strong>Spring에서의 Proxy 적용 방법과 쓰임</strong>을 정리해보고자 한다. </p>
<h2 id="자주-사용해봤을지도">자주 사용해봤을지도??</h2>
<p>직접 Proxy 객체를 구현하기보다는,
Library나 Framework에서 제공하는 <strong>Proxy 기능</strong>을 사용해 본 경험이 더 많을 것이다.</p>
<p>예를 들어 <strong>@Transactional 어노테이션</strong>이 있습니다.
만약 이를 사용하지 않는다면, 개발자는 모든 비즈니스 로직마다 직접 트랜잭션 코드를 작성해야 한다.</p>
<p>하지만 @Transactional을 적용하면, Spring 컨테이너가 Proxy 객체를 생성하여 트랜잭션을 관리한다. 
덕분에 기존 코드를 수정하지 않고도 <strong>트랜잭션 기능</strong>을 손쉽게 추가할 수 있다.</p>
<h2 id="proxy를-사용해야-하는-이유">Proxy를 사용해야 하는 이유</h2>
<h3 id="기존-비즈니스-로직을-변경하지-않은-채로-기능을-추가해야-하는-경우">기존 비즈니스 로직을 변경하지 않은 채로 기능을 추가해야 하는 경우</h3>
<p>이는 SOLID원칙 중 <strong><em>개방-폐쇄 원칙</em></strong> 과 관련이 있다. </p>
<p>만약 Proxy를 사용하지 않는다면, 
기존 비즈니스 로직에 기능을 추가할 때 <strong>새로운 기능이 주는 이점</strong>과  <strong>기존 코드를 수정함으로써 생기는 리스크</strong> 
를 모두 고려햐야하는 상황이 발생한다.</p>
<p>그러나 Proxy를 사용하여 기존 비즈니스 로직을 감싸는 Proxy 객체에 <strong>기능</strong>을 추가한다면 
기존 객체를 수정해야 하는 리스크를 피하면서도, 기능을 확장할 수 있다. </p>
<p>따라서 <strong>확장에는 열려(Open) 있고, 수정에는 닫혀(Close) 있는 구조</strong>를 구현할 수 있게된다.</p>
<h2 id="proxy-패턴-대표적인-종류-3가지">Proxy 패턴 대표적인 종류 3가지</h2>
<h4 id="paymentservicejava">PaymentService.java</h4>
<pre><code class="language-java">public class PaymentService implements Payment {

    @Override
    public void confirm() {
        System.out.println(&quot;[PaymentService.java - confirm()] : 결제완료!&quot;);
    }

    @Override
    public void cancel() {
        System.out.println(&quot;[PaymentService.java - cancel()] : 결제취소!&quot;);
    }
}</code></pre>
<h4 id="paymentjava">Payment.java</h4>
<pre><code class="language-java">package org.practice.proxy;

public interface Payment {

    void confirm();

    void cancel();
}
</code></pre>
<h2 id="✅-base-proxy-패턴">✅ Base Proxy 패턴</h2>
<pre><code class="language-java">public class PaymentBaseProxy implements Payment {

    private final PaymentService paymentService;

    PaymentBaseProxy(PaymentService paymentService) {
        System.out.println(&quot;[PaymentBaseProxy.java - 생성자] : PaymentService 초기화 실행!&quot;);
        this.paymentService = paymentService;
    }

    @Override
    public void confirm() {
        System.out.println(&quot;[PaymentBaseProxy.java - confirm()] : 프록시 실행!&quot;);
        paymentService.confirm();
    }

    @Override
    public void cancel() {
        System.out.println(&quot;[PaymentBaseProxy.java - cancel()] : 프록시 실행!&quot;);
        paymentService.cancel();
    }
}</code></pre>
<p><strong>Base Proxy 패턴</strong>입니다. 호출자와 본 객체 사이에서 요청의 흐름을 조율합니다.
이를 이용해 다양한 기능을 추가 할 수 있습니다.</p>
<p>위 코드에서는 PaymentService를 생성자 주입하고 이를 실행하는 메서드를 설정합니다. 
이때 Proxy 객체가 PaymentService가 Implement했던 인터페이스를 동일하게 Implement하여
동일한 메서드를 재정의하여 사용하게 됩니다.</p>
<h2 id="✅-protection-proxy-패턴">✅ Protection Proxy 패턴</h2>
<pre><code class="language-java">public class PaymentProtectionProxy implements Payment {

    private PaymentService paymentService;
    private User user;

    PaymentProtectionProxy(User user) {
        System.out.println(&quot;[PaymentProtectionProxy.java - 생성자] : PaymentService 초기화 실행!&quot;);
        paymentService = new PaymentService();
        this.user = user;
    }

    @Override
    public void confirm() {
        System.out.println(&quot;[PaymentProtectionProxy.java - confirm()] : 프록시 실행!&quot;);

        if (user.hasUserRole(&quot;Admin&quot;)) { // 프록시에서 권한을 체크한다.
            System.out.println(&quot;[PaymentProtectionProxy.java - confirm()] : 어드민 인증 성공!&quot;);
            paymentService.confirm();
        } else {
            System.out.println(&quot;[PaymentProtectionProxy.java - confirm()] : 어드민 인증 실패!&quot;);
        }
    }

    @Override
    public void cancel() {
        System.out.println(&quot;[PaymentProtectionProxy.java - cancel()] : 프록시 실행!&quot;);
        if (user.hasUserRole(&quot;Manager&quot;)) { // 프록시에서 권한을 체크한다.
            System.out.println(&quot;[PaymentProtectionProxy.java - cancel()] : 매니저 인증 성공!&quot;);
            paymentService.cancel();
        } else {
            System.out.println(&quot;[PaymentProtectionProxy.java - cancel()] : 매니저 인증 실패!&quot;);
        }
    }
}</code></pre>
<p>검증 기능을 수행하는 <strong>Protection Proxy 패턴</strong>입니다.
본 객체에 메서드를 호출하기전 Proxy 객체에서 권한(검증)을 진행하여 접근을 검증하거나 실행 흐름을 관리합니다.</p>
<p>Proxy 객체에 검증을 위임했기 때문에 본 객체는 변경없이 기능이 추가된 효과를 볼 수 있습니다.</p>
<h2 id="✅-virtual-proxy-패턴">✅ Virtual Proxy 패턴</h2>
<pre><code class="language-java">public class PaymentVirtualProxy implements Payment {

    // PaymentService가 무겁기 때문에 사용될 시점에 초기화 하고 싶을때 - Lazy-Loading
    private PaymentService paymentService;

    PaymentVirtualProxy() {
    }

    @Override
    public void confirm() {
        lazyInitialized();
        System.out.println(&quot;[PaymentVirtualProxy.java - confirm()] : 프록시 실행!&quot;);
        paymentService.confirm(); // 본 객체에 위임
    }

    @Override
    public void cancel() {
        lazyInitialized();
        System.out.println(&quot;[PaymentVirtualProxy.java - confirm()] : 프록시 실행!&quot;);
        paymentService.cancel(); // 본 객체에 위임
    }


    private void lazyInitialized() {
        if (paymentService == null) {
            System.out.println(
                &quot;[PaymentVirtualProxy.java - ensureInitialized()] : PaymentService 지연 초기화 실행!&quot;);
            paymentService = new PaymentService();
        }
    }
}</code></pre>
<p>대상 서비스나 객체의 생성 비용이 큰 경우, 프록시를 사용하여 <strong>지연 초기화(Lazy Initialization)</strong> 를 구현할 수 있습니다. 이를 통해 불필요한 리소스 낭비를 줄이고, 실제 사용 시점에만 객체를 생성하여 시스템 효율성을 높일 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSAFY 12th 후기 + 궁시렁궁시렁]]></title>
            <link>https://velog.io/@dl_01312/SSAFY-12th-%ED%9B%84%EA%B8%B0-%EA%B6%81%EC%8B%9C%EB%A0%81%EA%B6%81%EC%8B%9C%EB%A0%81</link>
            <guid>https://velog.io/@dl_01312/SSAFY-12th-%ED%9B%84%EA%B8%B0-%EA%B6%81%EC%8B%9C%EB%A0%81%EA%B6%81%EC%8B%9C%EB%A0%81</guid>
            <pubDate>Fri, 29 Aug 2025 18:00:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>삼성 청년 SW-AI 아카데미 - SSAFY</strong> 란?
삼성의 교육 경험과 고용노동부의 취업지원 노하우를 바탕으로,
취업 준비생에게 SW·AI 역량 향상 교육 및 다양한 취업지원 서비스를 제공하여
취업에 성공하도록 돕는 프로그램입니다.</p>
</blockquote>
<ul>
<li><a href="https://www.ssafy.com/ksp/jsp/swp/swpMain.jsp">https://www.ssafy.com/ksp/jsp/swp/swpMain.jsp</a></li>
</ul>
<p><em><strong>개인적 기록을 목적으로 작성한 게시글입니다!!</strong></em></p>
<h1 id="ssafy-지원한-이유는">SSAFY 지원한 이유는?</h1>
<h3 id="가장-주된-이유는-개발자로서-역량이-부족했기-때문이다">가장 주된 이유는 개발자로서 역량이 부족했기 때문이다.</h3>
<p><em><del>혹시 컨트롤러가 CRUD까지 담당하는 코드를 보셨습니까?</del></em> 
<em><del>Git이 머임?</del></em> </p>
<p>대학교에서 객체지향설계, 네트워크, 개발방법론, 프로그래밍 언어까지..
잘 생각해 보면 개발에 필요한 요소들은 다 배웠던 것 같다. </p>
<p>다만, 조화롭게 적용하는 노하우가 부족했던 것 같다. </p>
<p>그러다 보니, 캡스톤디자인(IoT, Web) 프로젝트를 진행하면서 오직 <strong>구현</strong>에만 집중했고 
주로 혼자 작업했다, 외롭기도 했지만 그게 당연하다고 느꼈던 것 같다. </p>
<p>그러면서 내가 지향하는 <strong>개발자</strong>와는 점점 멀어지는 것 같았다.</p>
<h3 id="그래서-너무-궁금했다">그래서 너무 궁금했다.</h3>
<p><strong>개발자</strong>라는 직업을 지향하는 나와 같은 나이대 사람들은 어떻게 학습하고 개발을 할까? 
그리고 나와 어떤 점이 다른지 알고 싶어졌다. </p>
<p>그렇기 때문에 부트캠프를 가고 싶었고, 수많은 후보들 중 커리큘럼 + 경쟁률 + 규모 + (130만 원 감사합니다) 
만족하는 조건을 가진 <strong>SSAFY</strong>를 지원하게 되었다! 지금 생각해 보면 잘 선택한 듯?</p>
<h1 id="실은-11기-떨어짐-_">실은.... 11기 떨어짐 &gt;_&lt;</h1>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/f33134eb-908a-496d-bdc0-48315436f494/image.png" 
     width="700" height="500" 
     alt="이미지"></p>
<p>어떻게 첫술에 배가 부를까요? <del>부르고 싶었다.</del> </p>
<p>부랴부랴 준비했었다, 에세이 적고, 코딩테스트 준비하고, 면접까지 정말 바쁘게 준비했고 
간절했었다. 하지만 아쉽게도 마지막 면접에서 떨어졌고 정말 아팠던 경험이었다. </p>
<h3 id="뭐가-부족했을까">뭐가 부족했을까?</h3>
<p><strong>코딩테스트, 면접</strong> 둘 다 부족했다고 생각했다.. </p>
<p>코딩테스트는 간단한 문제였지만, 생각을 코드로 구현하는 게 너무 어려웠고 
면접 때는 대화보다는 <strong>외우는 것을 말하는것</strong>이 위주였고, 그러다 보니 
물어본 것 이상으로 와다다다 말하게 되었다. </p>
<p>되돌아보니 부족하게 너무 많았다, 그렇다고 포기하고 싶진 않았다 
여태까지 너무 많은 것을 포기해 왔고, 그 결과가 지금 내 모습이라 생각했어서
후회도 많이 해왔던 것 같았다, 그래서 어떻게든 이겨내고 싶었다.</p>
<h3 id="알고리즘에-빠져보아요">알고리즘에 빠져보아요~</h3>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/3add1bc3-ca90-4158-af04-373c1212f862/image.png" 
     width="700" height="500" 
     alt="이미지"></p>
<p>먼저 기초적인 코딩 실력을 쌓으려고 노력했다, 알고리즘에서 한 문제를 더 맞았다면? 
혹시 모른다는 생각에 더욱 열심히 했는데.. </p>
<p>이게 또 재미있다 ㅋㅋㅋ 그래서 집에서 쉬면서 한 문제, 밥 먹고 한 문제 
이렇게 풀던 게 대략 6개월 만에 400문제 가까이를 풀었다... </p>
<p>지금 와서 생각해 보면, 이런 과정이 없었다면 Java를 이용한 로직 구현 능력이나, 이해 능력이 
매우 떨어졌을 것이라고 생각하고 있어서, 최고의 선택이라고 생각한다 ^_^ </p>
<p>이와 동시에 면접스터디도 틈틈이 진행했다 ㅎㅎ..</p>
<h1 id="ssafy-12th-합격-취업아님">SSAFY 12th 합격!! (취업아님)</h1>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/2c21af35-3426-4d9c-9626-3e4f8f64ea8b/image.png" 
     width="450" height="300" 
     alt="이미지"></p>
<p>위 제목처럼 취업은 아니지만, 취업한 것 이상으로 너무 기뻤다 ~~ </p>
<p>노력은 배신하지 않는다 라는 말을 매번 들어왔으나, 
노력을 해보지도 않고서 배신당할까 봐 무서워서 피해왔었다. </p>
<p>하지만 이번엔 간절하게 노력했고 원하는 결과도 얻어냈어서 인생의 터닝포인트라고 생각할 정도로 
내 사고방식을 크게 변화시키는 사건이었다고 생각한다.</p>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/92ce3166-9efc-4b8c-bf18-95ac4499c3f6/image.png" alt=""></p>
<p>입과 꾹~</p>
<h1 id="ssafy-12th-1학기">SSAFY 12th 1학기</h1>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/baf38cf2-5544-430a-890e-e4f6e9976f26/image.jpeg" 
     width="450" height="300" 
     alt="이미지"></p>
<h2 id="algorithm-java">Algorithm, Java</h2>
<p>정말 끝도 없는 학습량 + 테스트가 쏟아졌다... </p>
<p>개인적으로는 Java, 알고리즘 수업은 원활했다! 
알고리즘은 많이 풀어왔고, 주력언어는 Java였기 때문에 편했다 
덕분에 2학기 가기 위해선 (전공반 기준) 소프트웨어 역량평가 A이상 맞아야 하는데 
A+을 달성할 수 있었다. </p>
<p>무엇보다도 Java에 관해 강사님, 동기들과 함께 이야기하면서 <strong>다양한 시각</strong>을 
경험해 볼 수 있다는 점이 가장 매력적이었다. </p>
<h3 id="이맘때쯤-웰컴키트도-받는다">이맘때쯤 웰컴키트도 받는다!</h3>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/9eb74c67-359a-47c0-a729-cea165926491/image.jpeg" 
width="450" height="300" 
alt="이미지"> </p>
<p>후드티, 티셔츠, 수첩, 마우스패드, 보조배터리, 텀블러 이렇게 제공받았습니다 
<strong>텀블러 너무 쓸만합니다 GOAT 감사해요 샘숭</strong> </p>
<h3 id="db-spring">DB, Spring</h3>
<p>많이 어려웠다, 좀 더 깊게 공부할 수 있는 계기가 되었고 DB는 기초를 쌓는 느낌으로 했다. </p>
<p>Spring은 처음 보는 개념(AOP, Interceptor, etc)을 매번 학습해야 했고, 월말, 주간평가를 
진행했어야 했기 때문에 어려웠다. </p>
<p>마지막으로 관통 프로젝트(1학기 때 배운 것을 통합해서 써먹는?, 싸피 용어임)를 진행해서 
개발세팅부터 개발, 발표까지 두루 경험할 수 있었다. </p>
<p>개발 컨밴션을 설정하고, Git을 이용해서 형상관리까지 하는 협업을 처음 경험했던 
프로젝트였고 나중 프로젝트를 진행할 때 크게 도움이 되었다.</p>
<h1 id="ssafy-12th-2학기">SSAFY 12th 2학기</h1>
<h2 id="프로젝트-jippy-nokbank-apighost-진행하면서">프로젝트 Jippy, NokBank, APIGhost 진행하면서</h2>
<p><strong>구현은 누구나 잘한다.</strong> -&gt; 이 말을 입에 달고 살았던 것 같다. </p>
<p>비즈니스로직을 개발하는 능력 이외의 능력도 함께 키우고 싶었다, 그런 의미에서 공통, 특화, 자율 프로젝트는 
내가 얻어가고 싶었던 역량들을 배울 수 있는 좋은 기회였다 </p>
<h3 id="jippy---자영업-카페-통합관리-서비스">Jippy - 자영업 카페 통합관리 서비스</h3>
<p>기본적인 개발실력을 쌓을 수 있는 정석(?)적인 프로젝트였다. 코드리뷰를 통해 다른 사람의 코드를 분석하면서 
어떤 방법이 더 나은 방법인지, 어떤 식으로 개발을 진행해 왔는지 를 알 수 있었다. </p>
<p>개인적으로 지원동기의 절반을 해결해 준 프로젝트라 기억이 남는다. </p>
<p>Junit으로 테스트 커버리지를 올리기 위해 테스트 코드 작성과 부하테스트(K6 + InfluxDB + Grafana)를 진행하면서 
구현만 하는 프로젝트가 아닌 구현한 것을 검증하는 단계도 함께했다. 그 덕분에 개발을 바라보는 시선이 더 넓어졌다. </p>
<h3 id="nokbank---디지털-취약계층을-위한-폰뱅킹">NokBank - 디지털 취약계층을 위한 폰뱅킹</h3>
<p><strong>개인적으로 난이도가 매우 어려웠던 프로젝트, 그 덕분에 많이 배웠다...</strong> </p>
<p>개발, 아키텍처(MSA), 인프라, CI-CD분야를 깊게 배울 수 있었던 프로젝트였다. 
MSA 아키텍처 설계하기 위해서 알아야 할 기술스택과 지식들이 너무 많았다. </p>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/cffd8407-0a6c-474a-957b-2587389b4e38/image.png" 
width="450" height="300" 
alt="이미지"> </p>
<p><strong><del><em>휴먼에러 아.</em></del></strong> </p>
<p>프로젝트를 진행하면서 기술 선택 근거와 관련 레퍼런스 자료들을 찾는데 시간을 많이 투자했고 
<strong>분산 트랜잭션, 비동기메시징, Docker, Jenkins, SpringCloud, Service Discovery etc..</strong>
다양한 기술을 활용하기 위해 매번 정리하면서 프로젝트를 진행했다. 정말... 정말 어려웠고</p>
<p>이 모든 요소가 잘 맞물려서 작동하기위해 수많은 시행착오를 거쳤었다, 덕분에 새벽 3-4시에 자는건 기본 ㅎㅎ..</p>
<img width="800" src="https://github.com/user-attachments/assets/3b00b429-c810-4bdd-9c13-b89e1f9000eb">

<p>MSA 아키텍처 기반 서비스를 완성했고 부하테스트를 돌려봤다.</p>
<ul>
<li>K6, InfluxDB, Grafana로 10분간 500명, 약 9만건 요청</li>
<li>TPS: 140, 평균 응답: 1.26초</li>
</ul>
<p>확실히 모놀리식보단 그래프가 덜 튄다. 수평, 수직확장을 한다면 더 좋은 퍼포먼스를 보여줄것이라 생각된다
이를 통해서 사용자 규모, 서비스 종류에 따라 아키텍처의 선택이 실제로 매우 큰 효과를 줄 수 있다는것을 체험했다.</p>
<h3 id="apighost---api-시나리오-테스트-라이브러리--tool">APIGhost - API 시나리오 테스트 라이브러리 + Tool</h3>
<p><strong>동시성 환경을 고려한 프로그래밍</strong></p>
<p>라이브러리 프로젝트를 진행해보고 싶어졌다, Web 프로젝트는 3번 했으니까~ 
Java, Spring의 작동 구조를 다시 한번 공부해 볼 수 있는 기회가 되었다. </p>
<p>특히 나는 라이브러리팀을 맞게 되었는데, 
SpringBoot 이식성 vs 경량화 라이브러리, 라이브러리가 Bean 등록 허용범위, Javax vs Jakarta 
Web 개발에서 해보지 않은 고민들을 맞닥트리게 되었고, 새로운 자극이 되었다.. </p>
<p>라이브러리 개발을 진행하면서 <strong>호환성</strong>에 대해 트러블이 많았고 이를 해결하기 위해 의존 라이브러리를 교체하는 
일도 생겼다, <em>라이브러리 개발자 존경합니두...</em> </p>
<p>ConcurrentHashMap, volatile, Thread Safety에 대해서 공부하거나 직접 적용해 보면서 
Thread마다 자원을 관리하도록 설계했다. </p>
<p>이를 통해 정체되었던 Java관련 지식을 발전시키는 Next Level(에스파 아님)으로 향하는 길이였지 않나 싶다.</p>
<h1 id="공부만-했나">공부만 했나?</h1>
<p>그건 아니다 입과 후 친해졌던 동기들과 여행도 가고, 볼링도 하고 술도 마시고 이것저것 막 했다<del>~ 
1학기때 다들 새로운 환경이다 보니 쉽게 친해졌던 것 같다, 다들 열정이 있었고 도움이 필요할 때 
매번 도와줬던 사람들이라 더 정감이 갔던 것 같다 감사합니두</del></p>
<p>정말 많은 일이 있었고, 힘들었던 일도 많았지만 그럼에도 완주할 수 있었던 이유는 결국 사람 때문이지 않나 싶네요.. </p>
<h3 id="mt도-가고-대학교때가-마지막일줄-ㅋㅋ">MT도 가고 <em><del>대학교때가 마지막일줄 ㅋㅋ</del></em></h3>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/bfad5a65-e66b-4af5-b77c-c50c73fc1e28/image.jpeg" 
     width="450" height="300" 
     alt="이미지">
승우형, 의현이형 깜짝등장~ 다 늙어서 눈이 침침하더라고요~</p>
<h3 id="야구장도-가고">야구장도 가고~</h3>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/81f2af08-5b1b-4002-8a23-875dd3a1eef9/image.jpeg" 
     width="450" height="300" 
     alt="이미지"></p>
<h3 id="일본여행도-가고">일본여행도 가고~</h3>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/5e650c73-e800-46b4-9826-5f215a50c117/image.jpeg" 
     width="450" height="300" 
     alt="이미지"></p>
<p>승우형은 오시다가 쓰러졌다고 하네요~</p>
<h3 id="싸피-수료후에도-또만나는-ㅋㅋ">싸피 수료후에도 또만나는 ㅋㅋ</h3>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/dl_01312/post/ba5a4ab7-b89c-496a-9946-a0eca9224c01/image.jpeg" width="490px"></th>
<th><img src="https://velog.velcdn.com/images/dl_01312/post/1db530cc-ebbe-4972-8de4-f6b6b8a63a8d/image.jpeg" width="410px"></th>
</tr>
</thead>
</table>
<p><em><del>계곡물 맛있더라</del></em></p>
<h1 id="ssafy를-마무리-하며">SSAFY를 마무리 하며</h1>
<hr>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/dl_01312/post/ae24ecc5-62ec-4287-aa98-8dc1ba09c810/image.png" width="490px"></th>
<th><img src="https://velog.velcdn.com/images/dl_01312/post/b1dceb1e-dc9a-4afb-b51b-de8cdba02185/image.jpeg" width="600px"></th>
</tr>
</thead>
</table>
<p>적다 보니 너무 길어질 것 같아서 추가하지 못한 이야기들도 많다는 것을 느꼈습니다. 
열심히 달려왔다 생각했지만, 시간이 지나고 보니 못 챙겼던 부분도 생각나더라고요 ㅎㅎ </p>
<p>그래도 원하는 목표 그 이상을 달성한 것 같아서 기분이 좋습니다 하하~ </p>
<p>그리고 9/8부터 새로운 도전을 시작하게 되었는데요, 취업했습니다!! </p>
<p>여의도에 있는 보안솔루션 개발회사에 감사하게도 취직하게 되어서 지방 -&gt; 서울로 이사준비 중에 있습니다 
다행히도 기술스택은 제가 메인 도메인으로 가져가는 것들이라 적응하는데 도움이 될 것 같지만 
보안은 아직 부족하기 때문에 또 한 번 도전하게 된 기분이네요 ㅎㅎ </p>
<p>원래 관심 있던 분야였는데 서브도메인으로 가져간다면 미래에 큰 도움이 될 것이라 생각합니다 ㅎㅎ</p>
<p>그럼 20000!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 테스트에서 EntityManager를 이용해보자.]]></title>
            <link>https://velog.io/@dl_01312/JUnit-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%97%90%EC%84%9C-EntityManager%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dl_01312/JUnit-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%97%90%EC%84%9C-EntityManager%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sat, 16 Aug 2025 07:58:12 GMT</pubDate>
            <description><![CDATA[<h1 id="🧐-junit-테스트코드를-작성하면서">🧐 JUnit 테스트코드를 작성하면서</h1>
<blockquote>
<p>한정판매 플랫폼 <strong>Sunpick</strong>을 개발과 함께 테스트코드도 작성하려고 한다.. </p>
</blockquote>
<p><strong>개발 + 테스트코드작성</strong> 과정을 하나로 묶어서 작업하는 습관을 들이려고 한다.. 
프로젝트하면서 시간에 쫓기다 보니 신경을 못썼던 점 반성하고 고쳐보자 </p>
<p><strong>SunPick</strong>은 현재 JPA로만 DB에 접근하도록 설계하고 있습니다. 
JPA 매핑을 위해 테이블 별 Entity를 작성했고, 이를 테스트하는 절차가 필요하다 생각했다. </p>
<p><strong>실제 DB에서 잘 작동이 되는지를 판단하는 테스트코드를 작성해 보자</strong></p>
<h1 id="💻-jpa---persistence-context">💻 JPA - Persistence Context</h1>
<p>먼저 영속성 컨텍스트가 어떤 역할을 하는지 알아야 제대로 된 테스트 코드를 작성할 수 있다. 
다양한 역할을 하지만 현재 포스팅에선 두 가지만 알아두고 넘어가 보자. </p>
<h3 id="✅-1차-캐시">✅ 1차 캐시</h3>
<p>영속성 컨택스트 내부에 존재하는 엔티티를 잠시 저장해 두는 메모리 공간입니다. 
같은 트랜잭션 내에서 엔티티를 캐싱하여 DB 접근을 최소화할 수 있습니다. </p>
<h3 id="✅-쓰기-지연-write---behind">✅ 쓰기 지연 (Write - Behind)</h3>
<p><strong>Insert, Update, Delete</strong> 같은 작업들을 바로 DB에 보내지 않고 Query을 모아두었다가 
트랜잭션이 커밋되는 시점에 저장된 모든 Query를 보내는 기능입니다. </p>
<p>네트워크 통신을 최소화하여 성능 최적화 가능합니다. 
Query를 한 번에 전송한다면, 문제가 생겼을 때 전송되지 않기 때문에, 원자성을 지원합니다.</p>
<h1 id="🚨-문제발생">🚨 문제발생</h1>
<p>Entity Mapping이 잘되었는지 판단하려면 어떻게 테스트를 해야 할까? 
<strong>_당연하게도 실제 DB에 넣어보고, 가져와서 값을 검증하면 될 것 같다. _</strong> </p>
<p>하지만 Persistence Context를 이해해지 못한 채로 테스트 코드를 작성하면 
전혀 다른 테스트가 될 수 있다.</p>
<h3 id="처음-의도한-테스트-설계">처음 의도한 테스트 설계</h3>
<blockquote>
<p>Member 엔티티 생성 -&gt; DB 저장 -&gt; DB 조회 -&gt; 데이터 검증</p>
</blockquote>
<h3 id="jpa를-이용하여-db-crud-테스트-entitymanager-미사용">JPA를 이용하여 DB CRUD 테스트 (EntityManager 미사용)</h3>
<blockquote>
<p>Member 엔티티 생성 -&gt; save 호출 -&gt; 1차 캐시 적재 -&gt; findById() -&gt; 캐시 적중 후 조회 -&gt; 데이터 검증 -&gt; Commit</p>
</blockquote>
<p>처음 설계한 테스트 목적은 DB에 저장 후 조회 했을때 데이터 검증이다.
하지만 1차 캐시가 테스트에 포함되어버린다면</p>
<h3 id="1차-캐시를-검증하는-테스트가-아님에도-처음-의도와-전혀-다른-테스트가-된다"><strong>1차 캐시를 검증하는 테스트가 아님에도, 처음 의도와 전혀 다른 테스트가 된다</strong></h3>
<p>즉 초기 테스트 설계를 지키려면, JPA 1차 캐시를 테스트코드에서 제외해야 했다.</p>
<h1 id="💻-검증">💻 검증</h1>
<h3 id="🚨-entitymanager를-사용하지-않고-1차-캐시가-개입된-경우">🚨 EntityManager를 사용하지 않고 1차 캐시가 개입된 경우</h3>
<ul>
<li>캐싱이 된 객체를 반환하기 때문에 생성된 Member 엔티티와, findById()를 통해 조회한 Member 엔티티의 참조주소가 같을 것이다.
<img src="https://velog.velcdn.com/images/dl_01312/post/a00ea421-7af0-4bb5-a130-baa3c55e19bc/image.png" alt=""></li>
</ul>
<p>로그를 찍어본 결과 예상대로 참조주소가 같다, 그리고 
캐싱된 객체을 가져왔기 때문에 Select 관련 Query는 DB로 전송되지 않았다.</p>
<h3 id="✅-entitymanager를-사용하여-1차-캐시를-제외한-경우">✅ EntityManager를 사용하여 1차 캐시를 제외한 경우</h3>
<ul>
<li>캐시사용 X, 즉 생성된 Member 엔티티와, findById()를 통해 조회한 Member 엔티티와 전혀 다른 객체 일 것이다.
<img src="https://velog.velcdn.com/images/dl_01312/post/471f3e8c-f171-4580-8720-1d72d5d60af5/image.png" alt=""></li>
</ul>
<p>캐시를 이용하지 않았기 때문에 각각 다른 객체로 생성되어 참조주소가 다르다는 것을 알 수 있다.
추가로 캐시 적중 실패로, Select Query가 DB로 전송된 것도 확인가능하다.</p>
<h1 id="☑️-해결">☑️ 해결</h1>
<pre><code class="language-java">@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ActiveProfiles(&quot;test&quot;)
@Import(JpaConfig.class)
public class MemberJpaMappingTest {

    @PersistenceContext
    private EntityManager em;

    @Autowired
    private MemberRepository memberRepository;

    @Test
    void 멤버_엔티티_필드_매핑_정상작동_테스트() {

        //given
        Member member = TestEntityFactory.testMember();
        Member savedMember = memberRepository.save(member);
        em.flush(); // 모든 Query문 DB에 전송
        em.clear(); // 1차 캐시 정리

        //when
        Member selectedMember = memberRepository.findById(savedMember.getId()).orElseThrow();

        //then
        assertSoftly(as -&gt; {
            as.assertThat(selectedMember.getName()).isEqualTo(&quot;이영석&quot;);
            as.assertThat(selectedMember.getEmail()).isEqualTo(&quot;ssafy@naver.com&quot;);
            as.assertThat(selectedMember.getPassword()).isEqualTo(&quot;password123&quot;);
            as.assertThat(selectedMember.getBirthDate()).isEqualTo(LocalDate.of(1999, 1, 15));
            as.assertThat(selectedMember.getWithdrawnAt()).isNull();
            as.assertThat(selectedMember.getCreatedAt()).isNotNull();
            as.assertThat(selectedMember.getModifiedAt()).isNotNull();
        });
    }
}</code></pre>
<p><strong>em.flush()를 통해 즉시 DB에 Query를 전송하고, em.clear()를 통해 1차 캐시를 정리를 해주자.</strong></p>
<p>이렇게 되면 <em><strong>em.flush()</strong></em> 시점에 DB에 실제로 저장되고,
_<strong>em.clear()</strong>_을 통해 when절의 findById()에서 1차 캐시가 아닌 DB에서 데이터를 조회한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[낙관적 락, 비관적 락도 Rock이다.]]></title>
            <link>https://velog.io/@dl_01312/%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD%EB%8F%84-Rock%EC%9D%B4%EB%8B%A4</link>
            <guid>https://velog.io/@dl_01312/%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD%EB%8F%84-Rock%EC%9D%B4%EB%8B%A4</guid>
            <pubDate>Wed, 09 Jul 2025 19:51:22 GMT</pubDate>
            <description><![CDATA[<h1 id="💡-낙관적-락-비관적-락이-필요한-이유">💡 낙관적 락, 비관적 락이 필요한 이유</h1>
<blockquote>
<p>상품 재고가 한개 남았는데 동시에 구매요청이 들어온다면?</p>
</blockquote>
<p>당연하게도 단 하나의 구매 요청만 처리가 되어야 하지만,
동시성 문제에 대한 대처가 되어있지 않다면 재고가 음수가 될 수도 있다. </p>
<p>이런 문제는 서비스 장애로 이어지거나, 데이터 정합성 마저 깨지게 된다. 
그때마다 로깅하면서 에러를 추적할 것인가? 아니다 미리 방지해야 한다. </p>
<p><em>이제는 비즈니스 로직과 서비스 특성에 맞는 Lock 전략을 선택하고 적용해야 할 때이다.</em></p>
<h1 id="🔒-낙관적-락---optimistic-lock">🔒 낙관적 락 - Optimistic Lock</h1>
<blockquote>
<p>DB 업데이트의 횟수가 적으며, 충돌이 자주 발생하지 않을 것이라 가정하고
데이터 버전을 비교하여 충돌을 감지하는 Lock 메커니즘</p>
</blockquote>
<h2 id="낙관적-락-프로세스">낙관적 락 프로세스</h2>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/da6ed001-12d6-4e58-8e47-78cae9130ff5/image.png" alt=""></p>
<blockquote>
<p>낙관적 락은 충돌을 탐지하여 동시성 문제를 회피하거나 재시도하도록 한다.</p>
</blockquote>
<p>낙관적 락은 기본적으로 충돌을 허용한다. 대신에 버전정보를 토대로 충돌을 <strong>탐지</strong>하는데</p>
<p>테이블에 <strong>version 정보를 담는 필드를 추가하고 매번 업데이트를 진행할때
version정보를 검증하는 식으로 충돌을 탐지한다.</strong></p>
<p>즉, 처음 조회했을때 <strong>version</strong>정보가 도중에 변경되었다면 이는 충돌이라고 간주한다.</p>
<p>이후 재 조회를 하고 다시 UPDATE하는 로직과 별도의 검증로직을 통해
트랜잭션을 재실행 한다.</p>
<h3 id="✅-장점">✅ 장점</h3>
<h4 id="1-비관적-락-보다-성능상-이점있음">1. 비관적 락 보다 성능상 이점있음</h4>
<ul>
<li>Lock을 선점하지 않기 때문에 병렬처리에 유리함<h4 id="2-데드락-발생-가능성-낮음">2. 데드락 발생 가능성 낮음</h4>
</li>
<li>DB Layer에서 락을 선점하지 않기 때문에 트랜잭션간 교착상태 발생하지 않음</li>
</ul>
<h3 id="❌-단점">❌ 단점</h3>
<h4 id="1-충돌-발생시-트랜잭션을-다시-시도하는-비용-발생">1. 충돌 발생시 트랜잭션을 다시 시도하는 비용 발생</h4>
<ul>
<li>충돌감지 + 데이터 재조회 + 재시도 로직필요함<h4 id="2-엔티티에-version을-관리하는-필드가-존재해야-함">2. 엔티티에 version을 관리하는 필드가 존재해야 함</h4>
</li>
<li>버전비교를 통한 충돌감지를 하려면 별도의 필드가 존재해야함</li>
</ul>
<h1 id="🔒-비관적-락---pessimistic-lock">🔒 비관적 락 - Pessimistic Lock</h1>
<blockquote>
<p>DB 업데이트의 횟수가 많으며, 충돌이 자주 발생할 것이라 가정하고
데이터를 조회하는 순간부터 락을 걸어 접근을 방지하는 Lock 메커니즘</p>
</blockquote>
<h2 id="비관적-락-프로세스">비관적 락 프로세스</h2>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/e0cccd61-062b-4f33-a0a3-36f8047d080d/image.png" alt=""></p>
<blockquote>
<p>비관적 락은 데이터에 동시 접근을 제한하여 충돌 가능성을 사전에 차단한다.</p>
</blockquote>
<p>비관적 락은 DB 수준에서 Lock을 수행한다.
<strong>SELECT ... FOR UPDATE</strong> 같은 쿼리를 사용하여 Lock을 획득한 후
다른 트랜잭션의 데이터 접근을 방지하여 충돌 가능성을 <strong>차단</strong>한다.</p>
<p>이후 변경사항이 <strong>Commit</strong> 되었다면 <strong>Lock</strong>을 해제하여 대기 중인 다른 트랜잭션이 Lock을 획득하고 Update가 수행된다.</p>
<h3 id="✅-장점-1">✅ 장점</h3>
<h4 id="1-데이터정합성을-강하게-보장함">1. 데이터정합성을 강하게 보장함</h4>
<ul>
<li>Lock을 걸어 다른 트랜잭션의 접근을 원천차단함<h4 id="2-race-condition-방지에-효과적임">2. Race Condition 방지에 효과적임</h4>
</li>
<li>동시 접근하는 상황 자체를 제거하여 경쟁상태 방지함<h4 id="3-동시성이-높은-상황에서도-안정적">3. 동시성이 높은 상황에서도 안정적</h4>
</li>
<li>충돌 가능성을 배제하여, 별도의 예외상황없이 안정적인 흐름 유지</li>
</ul>
<h3 id="❌-단점-1">❌ 단점</h3>
<h4 id="1-트랜잭션-대기상태-발생으로-인한-성능-감소">1. 트랜잭션 대기상태 발생으로 인한 성능 감소</h4>
<ul>
<li>Lock이 해제될때까지 다른 트랜잭션은 대기상태 -&gt; 처리량 저하<h4 id="2-락으로-인한-데드락-발생-가능성-증가">2. 락으로 인한 데드락 발생 가능성 증가</h4>
</li>
<li>무분별한 순서로 다른 자원에 Lock을 걸면 교착상태 발생 가능함<h4 id="3-db-부하-증가">3. DB 부하 증가</h4>
</li>
<li>Lock 관리, 트랜잭션 대기처리에 대한 DB 리소스 사용함</li>
</ul>
<h1 id="🥑-결론">🥑 결론</h1>
<h3 id="🚨-lock-메커니즘-서비스의-특징-상황을-정확히-파악하고-타당한-근거하에-선택하자">🚨 Lock 메커니즘, 서비스의 특징, 상황을 정확히 파악하고 타당한 근거하에 선택하자!</h3>
<p>DB 업데이트가 적거나, 충돌이 발생할 가능성이 낮다고 생각하는 기능 
(게시물 수정, 마이페이지 수정 등)은 <strong>낙관적 락</strong>이 좋은 선택지 일 수 있다 </p>
<p>반대로 DB 업데이트가 많거나, 충돌이 발생할 가능성이 크다고 생각하는 기능 
(물건 판매에 따른 재고감소, 티켓팅 등)은 <strong>비관적 락</strong>이 좋은 선택지이다. </p>
<p>따라서 단순히 <strong>성능, 구현 편의성</strong>으로 판단하기보다는 기능에 대한 
<strong>트랜잭션의 특성과 정합성의 중요도를 종합적으로 판단하여 Lock 메커니즘을 선택해야 한다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래밍에서 멱등성과 멱등키]]></title>
            <link>https://velog.io/@dl_01312/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%97%90%EC%84%9C-%EB%A9%B1%EB%93%B1%EC%84%B1%EA%B3%BC-%EB%A9%B1%EB%93%B1%ED%82%A4</link>
            <guid>https://velog.io/@dl_01312/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%97%90%EC%84%9C-%EB%A9%B1%EB%93%B1%EC%84%B1%EA%B3%BC-%EB%A9%B1%EB%93%B1%ED%82%A4</guid>
            <pubDate>Sun, 29 Jun 2025 06:35:13 GMT</pubDate>
            <description><![CDATA[<h1 id="🥑-멱등성---idempotent">🥑 멱등성 - Idempotent</h1>
<blockquote>
<p><em>어떤 연산이나 요청을 여러 번 반복수행해도 최초 수행 결과 이외의 변화가 발생하지 않는 성질</em></p>
</blockquote>
<p>프로그래밍에서 <strong>멱등성</strong>이란 개념이 자주 사용되기도 한다. 실제로 <strong>멱등성</strong>에 대해 잘 알지 못해도 알게 모르게 자주 사용해보았고, 멱등성을 이용한 개발을 해보았을 것이다.</p>
<p>이를 쉽게 풀어서 정리해보려고 한다.</p>
<h1 id="코드로-보는-멱등성">코드로 보는 멱등성</h1>
<pre><code class="language-java">
// 멱등 함수: 항상 같은 입력 -&gt; 같은 출력 (상태 변화 없음)
public class Idempotent {
    public int giveNumber(int number) {
        return number + 2025;
    }
}

// 비멱등 함수: 같은 입력이라도 호출할 때마다 결과가 바뀜
public class NonIdempotent {
    private int counter = 0;

    public int giveNumber(int number) {
        counter++; // 상태 변화 발생
        return number + counter;
    }
}</code></pre>
<p><strong>Idempotent 클래스의 giveNumber함수는 멱등하게 설계되었습니다. **
동일한 요청에 대해 2025를 더해서 **항상 같은</strong> 값을 리턴하고 
이를 printNumber 함수는 멱등하다고 표현합니다.</p>
<p><strong>반대로 NonIdempotent의 giveNumber는 비멱등하게 설계되었습니다.</strong>
요청마다 내부 counter 값이 증가하게 되고 
이는 동일한 요청에 대해 <strong>항상 다른</strong>값을 리턴하고 
이를 printNumber 함수는 비멱등하다고 표현합니다.</p>
<h1 id="📑-http-method와-멱등성">📑 HTTP Method와 멱등성</h1>
<h4 id="rfc-7231-http11-semantics-and-content">RFC 7231 (HTTP/1.1 Semantics and Content)</h4>
<table>
<thead>
<tr>
<th>HTTP Method</th>
<th>멱등성 여부</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>GET</strong></td>
<td>✅ 멱등함</td>
<td>리소스를 조회만 하며 서버 상태를 변경하지 않음. 동일 요청 반복해도 결과 동일.</td>
</tr>
<tr>
<td><strong>HEAD</strong></td>
<td>✅ 멱등함</td>
<td>GET과 유사하되, 응답 본문 없이 헤더만 반환. 서버 상태 변경 없음.</td>
</tr>
<tr>
<td><strong>PUT</strong></td>
<td>✅ 멱등함</td>
<td>리소스를 &quot;지정된 값으로 덮어쓰기&quot; 때문에 여러 번 호출해도 결과는 동일.</td>
</tr>
<tr>
<td><strong>DELETE</strong></td>
<td>✅ 멱등함</td>
<td>리소스를 삭제. 이미 삭제된 리소스를 다시 삭제해도 상태 변화 없음.</td>
</tr>
<tr>
<td><strong>POST</strong></td>
<td>❌ 비멱등함</td>
<td>서버에 데이터를 생성하거나 처리하는 요청. 반복 시 중복 처리 가능성 존재.</td>
</tr>
<tr>
<td><strong>PATCH</strong></td>
<td>❌ 비멱등함</td>
<td>리소스 일부를 수정. 수정 대상이 누적될 수 있어 멱등하지 않음.</td>
</tr>
</tbody></table>
<p>RFC 7231 명세에 따르면 <strong>GET, PUT, HEAD, DELETE</strong>는 멱등하게 설계를 해야한다고 명시되어있습니다.</p>
<p>HTTP Method를 이용한 API 설계에서는 <strong>GET,HEAD</strong>는 <strong>안정성과 멱등성</strong>을요구하고 <strong>PUT, DELETE</strong>는 <strong>멱등성</strong>만 요구합니다.</p>
<h3 id="🧐-서버-리소스가-변경되는데-왜-put-delete가-멱등성을-가지나요">🧐 서버 리소스가 변경되는데 왜 PUT, DELETE가 멱등성을 가지나요?</h3>
<p>먼저 멱등성의 핵심은 <strong>같은 요청을 여러 번 보내도 결과는 같음</strong>이다. 
물론 서버 리소스가 변경이 되지 않는 것도 부분적으로 맞지만 완전한 정의는 아닙니다. </p>
<blockquote>
</blockquote>
<ul>
<li>DELETE Method를 통해 user 1 삭제하는 API를 다건 요청한 경우 </li>
</ul>
<ol>
<li>첫 번째 요청 -&gt; user 1 삭제 완료 (서버에서 user 1 존재 x) </li>
<li>두 번째 요청 -&gt; user 1 찾지 못해 실패 (서버에서 user 1 존재 x) </li>
<li>세 번째 요청 -&gt; user 1 찾지 못해 실패 (서버에서 user 1 존재 x) </li>
<li>... </li>
</ol>
<p>이 과정에서 서버의 리소스가 제거된 후 서버의 상태나 응답은 더 이상 변경되지 않으므로 결과적으로 멱등합니다. </p>
<p>이러한 이유 때문에 <strong>DELETE, PUT</strong>은 같은 요청에 대해 항상 같은 결과를 제공하므로 멱등성을 가진다라고 이야기할 수 있게 됩니다.</p>
<h3 id="🧐-patch-put-둘다-리소스-수정에-쓰이는데-왜-멱등성-여부가-다른가요">🧐 PATCH, PUT 둘다 리소스 수정에 쓰이는데 왜 멱등성 여부가 다른가요?</h3>
<ul>
<li>PUT : 전체 업데이트</li>
<li>PATCH : 부분 업데이트</li>
</ul>
<p>PUT은 리소스를 전체 업데이트합니다. 즉 요청에 들어있는 데이터가 최종 상태임을 의미하고 개발됩니다. 즉 같은 PUT 요청을 여러 번 보내면 결과는 항상 같게  되어 멱등성을 보장합니다.</p>
<p>반대로 PATCH는 리소스의 일부분을 업데이트합니다</p>
<pre><code class="language-json">PATCH /account/1
{
  &quot;balance&quot;: &quot;+100&quot;
}</code></pre>
<p>같은 PATCH 요청을 여러 번 보내게 된다면 balance는 100 * N 만큼 늘어나게 되어
서버 리소스의 결과가 항상 달라지게 되어 멱등성을 보장하지 못하게 됩니다.</p>
<p>물론 PATCH 요청은 개발의도에 따라 멱등 or 비멱등한 요청으로 변경될 수 있습니다.
다음과 같이 상태 설정형 요청이라면 멱등성을 가질 수 있습니다.</p>
<pre><code class="language-json">PATCH /account/1
{
  &quot;isActive&quot;: &quot;Y&quot;
}</code></pre>
<p>이렇게 의도가 되었다면 여러 번 요청해도 결과는 같기 때문에 멱등하게됩니다.</p>
<h1 id="✅-post-patch-에서-멱등성이-필요하다면">✅ POST, PATCH 에서 멱등성이 필요하다면?</h1>
<h3 id="멱등키---idempotent-key">멱등키 - Idempotent key</h3>
<blockquote>
<p>같은 요청이 여러번 서버에 전송되더라도 단 한번만 처리되도록 보장하기 위한 고유한 값</p>
</blockquote>
<p>예를 들어, 네트워크 오류로 인한 retry, 더블 클릭 등 이슈로 인한 상품 결제 요청이 여러번 서버에 전송되는 상황을 상상해보자.</p>
<blockquote>
</blockquote>
<ul>
<li>POST Method를 이용한 상품결제 API 중복 전송</li>
</ul>
<ol>
<li>첫 번째 요청 -&gt; 상품 결제 완료(서버 리소스 증가)</li>
<li>두 번째 요청 -&gt; 상품 결제 완료(서버 리소스 증가)</li>
<li>세 번째 요청 -&gt; 상품 결제 완료(서버 리소스 증가)</li>
<li>...</li>
</ol>
<p>이처럼 <strong>POST는 멱등하지 않은 메서드</strong>이기 때문에, 동일 요청이 여러 번 전달되면 <strong>서버의 상태가 매번 달라지고</strong>, 결과 또한 달라지게 된다.<br>결과적으로 <strong>상품이 여러 번 결제되는 상황</strong>이 발생할 수 있으며, 사용자 입장에서는 <strong>금전적인 손해</strong>로 이어질 수 있다.</p>
<p>즉 POST요청이지만 <strong>멱등성</strong>이 필요한 상황이다.</p>
<p>이처럼 POST나 PATCH 처럼 비멱등한 HTTP Method에 멱등성을 부여하기 위해 <strong>멱등키(Idempotent-Key)</strong>를 사용한다.</p>
<h1 id="📚-결론">📚 결론</h1>
<ul>
<li>멱등성을 고려한 API설계는 Retry, 더블클릭요청, 네트워크 오류 등 다양한 예외사항에 서버가 안정적으로 대응가능 하게 한다.</li>
<li>멱등성에서 <strong>결과의 같음</strong>은 응답뿐만 아니라, 서버 리소스의 최종상태 또한 같음을 의미한다.</li>
<li>RFC 7231 명세에 의거하여 개발을 하되 서비스에 맞는 유연한 개발도 필요하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring IoC Container이란?]]></title>
            <link>https://velog.io/@dl_01312/Spring-IoC-Container%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@dl_01312/Spring-IoC-Container%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Tue, 10 Jun 2025 09:20:58 GMT</pubDate>
            <description><![CDATA[<h1 id="💡-inversion-of-control">💡 Inversion of Control</h1>
<blockquote>
<p>개발자가 제어하는 권한들을 프레임워크에 위임하는 설계방식</p>
</blockquote>
<p>객체의 생성, 의존성 주입, 생명주기까지 통틀어서 개발자가 아닌 프레임워크가 관리하는 방식입니다.</p>
<p>Spring에서는 위와 같은 작업을 <strong>IoC Container</strong>가 담당합니다.</p>
<h2 id="spring-ioc-container의-구성요소">Spring IoC Container의 구성요소</h2>
<ul>
<li>Bean 정의</li>
<li>BeanFactory / ApplicationContext</li>
<li>LifeCycle 담당</li>
<li>의존성 관리 (DI/DL)</li>
</ul>
<p>IoC Container는 여러 구성요소로 이루어져 있습니다.
이를 통해 취할수 있는 장점은 무엇이 있을까요?</p>
<h2 id="🥑-장점">🥑 장점</h2>
<h4 id="✅-개발-생산성이-향상된다">✅ 개발 생산성이 향상된다.</h4>
<ul>
<li>객체의 생성과 주입, 생명주기관리를 프레임워크가 담당하게 되면
개발자는 비즈니스 로직에 집중할 수 있게 됩니다.</li>
</ul>
<h4 id="✅-테스트에-용이하다">✅ 테스트에 용이하다.</h4>
<ul>
<li>의존성을 외부에서 주입받기 때문에 테스트시 필요한 Mock, Stub을 외부에서 코드변경없이 주입할수 있습니다.</li>
</ul>
<h4 id="✅-구현체-변경에-의한-코드-수정이-없다">✅ 구현체 변경에 의한 코드 수정이 없다.</h4>
<ul>
<li>내부에서 객체를 생성하지 않고 외부에서 주입받아 사용하기 때문에
기존 비즈니스 로직의 변경이 없습니다.</li>
</ul>
<h4 id="✅-객체의-생명주기-및-의존성-주입이-표준화된다">✅ 객체의 생명주기 및 의존성 주입이 표준화된다.</h4>
<ul>
<li>프레임워크에서 관리되기 때문에 표준화되어, 코딩방식이 일관적이게 유지됩니다.</li>
</ul>
<p>이러한 장점에 큰 영향을 주는 <strong>의존성관리 (DI, DL)</strong>의 개념을 알아보겠습니다.</p>
<h1 id="📚-di---dependency-injection">📚 DI - Dependency Injection</h1>
<blockquote>
<p>IoC Container가 필요한 의존성을 자동으로 주입해주는 방식
이를통해 객체내부에서 필요한 객체를 생성하지 않고 외부에서 주입받게 됩니다.
DI방식을 이용함으로서 테스트용이성, 컴포넌트간 결합도를 낮춰주는 장점을 가지게 됩니다.</p>
</blockquote>
<pre><code class="language-java">public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;  // IoC 컨테이너가 외부에서 주입해줌
    }
}</code></pre>
<h1 id="📚-dl---dependency-lookup">📚 DL - Dependency Lookup</h1>
<blockquote>
<p>IoC Container에 등록된 의존성을 조회해서 주입하는 방식
객체 내부에서 필요한 객체를 개발자가 직접 조회하여 등록된 객체를 가져와서 사용하는 방식임</p>
</blockquote>
<ul>
<li>하지만 DL방식은 유연성 및 테스트가 DI방식보다 불리합니다 이 때문에 주로 DI 방식이 사용됩니다.</li>
</ul>
<pre><code class="language-java">ApplicationContext context = ...;
OrderRepository repository = context.getBean(OrderRepository.class);  // 직접 조회</code></pre>
<h1 id="마무리">마무리</h1>
<p>개발중 느끼지 못했지만 편했던것들이 IoC 컨테이너가 자체적으로 관리해주고 있다는 것을 다시한번 알게 되었습니다, 다음 포스트에는 <strong>생성자 주입</strong>이 권장되는 이유를 포스팅 하겠습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA와 데이터 정합성]]></title>
            <link>https://velog.io/@dl_01312/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%95%ED%95%A9%EC%84%B1%EA%B3%BC-MSA</link>
            <guid>https://velog.io/@dl_01312/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%95%ED%95%A9%EC%84%B1%EA%B3%BC-MSA</guid>
            <pubDate>Thu, 29 May 2025 04:20:32 GMT</pubDate>
            <description><![CDATA[<h1 id="🤖-데이터-정합성이란">🤖 데이터 정합성이란?</h1>
<blockquote>
<p>데이터 정합성이란, 시스템이 다루는 데이터가 항상 일관성 있고 신뢰할 수 있는 상태를 유지하는 것을 말합니다.</p>
</blockquote>
<p>즉, 데이터의 값과 구조가 서로 일치하거나, 논리적으로 이상함이 없어야 합니다. 대표적인 두 가지 정합성 모델을 알아보겠습니다.</p>
<h1 id="acid">ACID</h1>
<p>전통적인 관계형 DB나 단일 트랜잭션 환경에서 중요하게 여기는 강력한 트랜잭션관리 모델입니다.
각각 <em>원자성, 일관성, 고립성, 영속성</em> 을 뜻한다.</p>
<ul>
<li><strong>Atomicity</strong> : 전부다 성공하거나, 실패하거나.</li>
<li><strong>Consistency</strong> : 트랜잭션완료 후에도 데이터 무결성 보장</li>
<li><strong>Isolation</strong> : 동시에 수행되는 트랜잭션은 서로 간섭할수 없음.</li>
<li><strong>Durability</strong> : 트랜잭션이 완료되어 커밋된 데이터는 영구적 저장</li>
</ul>
<h1 id="base">BASE</h1>
<p>NoSQL, MSA, 분산 시스템 환경에서 자주 언급되고 사용되는 최종적 일관성을 보장하는 모델입니다.</p>
<ul>
<li><p><strong>Basically Available</strong> : 시스템이 항상 정확성보단 응답을 우선으로 제공</p>
</li>
<li><p><strong>Soft state</strong> : 일시적 정합성 훼손</p>
</li>
<li><p><strong>Eventual consistency</strong> : 최종적으로 일관성을 보장함.</p>
</li>
</ul>
<h1 id="🧐-msa를-공부하면서">🧐 MSA를 공부하면서</h1>
<p>MSA기반 서비스를 설계하면서 어려웠던 부분은 어떤 단위로 서비스를 분리하는 것이었다.</p>
<p>명확한 기준 없이 크거나, 작게 나누게 된다면 성능뿐만 아니라
MSA 아키텍처를 선택한 이유까지 손해를 볼 것 같았다.</p>
<p>정합성을 공부하면서 나름대로 서비스를 분리하는 기준을 가지게 되었는데 바로 보장되어야하는 데이터 정합성 모델에 따라 분리하면 되는 것이였다.</p>
<p>크게 두 가지 경우가 있다.</p>
<h2 id="💡트랜잭션의-과정까지-정합성이-지켜져야-하는-경우">💡트랜잭션의 과정까지 정합성이 지켜져야 하는 경우</h2>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/df597635-8faf-4cce-9162-adf1a66a4dba/image.png" alt=""></p>
<p>계좌송금을 그림으로 표현해봤습니다.</p>
<p>내부적으로 송금을 처리할 때, 정석대로라면 반드시 <strong>계좌 A</strong>에서 <strong>5,000원</strong>을 출금하고, 이어서 <strong>계좌 B</strong>에 <strong>5,000원</strong>이 입금 처리되어야 합니다.</p>
<p>그 이유는 만약 <strong>계좌 A</strong>에서 출금하지 않고 <strong>계좌 B</strong>로 송금한다면, 그 사이에 다른 <strong>계좌 C</strong>로의 <strong>3,000원</strong> 송금 트랜잭션이 끼어들어 <strong>계좌 A</strong>의 잔고가 <strong>-3,000</strong>원이 되어버리는 문제가 발생할 수 있기 때문입니다.</p>
<p>즉 이러한 시나리오는 <strong>작업의 순서가 명확하게 보장</strong>되어야 하며, 이는 곧 <strong>트랜잭션의 과정까지 정합성이 유지</strong>되어야 한다는 것을 의미합니다.</p>
<h2 id="💡트랜잭션의-결과만-정합성이-필요한-경우">💡트랜잭션의 결과만 정합성이 필요한 경우</h2>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/09dc39cc-081a-4821-86b6-0dc0a61b8a73/image.png" alt=""></p>
<p>상품을 결제하고 이벤트 쿠폰을 받는 시나리오입니다.
언뜻 보기에 상품 결제가 성공한 후 쿠폰 증정까지 하나의 트랜잭션으로 엄격하게 보장해야 할 것처럼 느껴집니다.</p>
<p>그러나 실제로 쿠폰 증정이 0.5초나 1초 정도 늦어진다고 해도 사용자에게 
<strong>심각한 문제나 불편이 발생할 가능성은 매우 낮습니다.</strong></p>
<p>이러한 경우 <strong>최종적 일관성</strong> 을 보장하는 방식으로도 충분합니다.
사용자는 쿠폰 증정 지연을 기다릴 필요가 없어 오히려 사용자 경험이 향상되며, 쿠폰도 정상적으로 발급됩니다.</p>
<p>따라서 사용자 경험을 향상하고, <strong>최종적 일관성</strong>만 보장하는 전략을 충분히 선택할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Singleton 패턴과 Thread Safety]]></title>
            <link>https://velog.io/@dl_01312/singleton-threadsafety</link>
            <guid>https://velog.io/@dl_01312/singleton-threadsafety</guid>
            <pubDate>Tue, 13 May 2025 04:54:31 GMT</pubDate>
            <description><![CDATA[<h1 id="🧮thread-safety">🧮Thread Safety</h1>
<blockquote>
<p><strong>쓰레드 세이프티(Thread Safety),</strong> 여러 개의 쓰레드가 동시에 어떤 <strong>공유 자원(shared resource)</strong>에 접근하더라도 프로그램이 잘못된 동작을 하지 않도록 보장되는 상태를 의미합니다.</p>
</blockquote>
<h1 id="싱글톤-패턴을-적용하며">싱글톤 패턴을 적용하며</h1>
<pre><code class="language-java">public class ObjectMapperHolder {
    private static class SingletonHolder {
        private static final ObjectMapper objectMapper = new ObjectMapper();
    }

    public static ObjectMapper getInstance() {
        return SingletonHolder.objectMapper;
    }
}</code></pre>
<p>라이브러리 프로젝트를 진행하면서 본프로젝트와 독립적으로 사용해야하는 기능(ObjectMapper, etc)들이 생겨나서 따로 싱글톤으로 관리하기로 했습니다.</p>
<p>그렇다면 모든 클래스를 싱글톤패턴으로 관리해도 되는걸까?</p>
<h1 id="🚨싱글톤-패턴을-적용안되는-경우">🚨싱글톤 패턴을 적용안되는 경우!</h1>
<ul>
<li>클래스가 상태(status)를 가지는경우</li>
<li>내부 값이 자주 변경될 가능성이 있는 경우</li>
</ul>
<p>둘다 비슷한 말이다, 결국은 내부값이 변경으로 인해 쓰레드 세이프티가 깨진다는 것이다. 예를 들어보자</p>
<h3 id="unsafesingletonjava">UnsafeSingleton.java</h3>
<pre><code class="language-java">public class UnsafeSingleton {
    private static final UnsafeSingleton INSTANCE = new UnsafeSingleton();

    private String status; // 상태값!

    private UnsafeSingleton() {}

    public static UnsafeSingleton getInstance() {
        return INSTANCE;
    }

    // getter, setter
}</code></pre>
<h3 id="executorjava">Executor.java</h3>
<pre><code class="language-java">UnsafeSingleton singleton = UnsafeSingleton.getInstance();

Thread t1 = new Thread(() -&gt; singleton.setStatus(&quot;고기 먹자&quot;));
Thread t2 = new Thread(() -&gt; singleton.setStatus(&quot;햄부기 먹자&quot;));

t1.start();
t2.start();

// 결과값은???
System.out.println(singleton.getStatus());</code></pre>
<p>이와 같은 경우, <em><strong>.getStatus()</strong></em> 가 어떤 값을 반환할지는 예측할 수 없습니다.</p>
<p>그 이유는_<strong>.getStatus()</strong>_의 반환값은 <strong>Thread t1,t2</strong> 실행이 완료되는 시점에 따라 달라지기 때문입니다.</p>
<p>Singleton은 모든 스레드가 동일한 객체 인스턴스를 공유하기 때문에, 의도하지 않게 상태값이 변경될 여지가 있습니다.</p>
<p> 즉 <em>Singleton보다는 각기 다른 객체로 관리하는게 적합해 보입니다!</em></p>
<h1 id="결론">결론</h1>
<blockquote>
<p>Singleton 패턴이 적용된 클래스는 내부 상태가 안정적으로 유지되어야 하며, 값이 자주 변경되어서는 안 됩니다. 이러한 원칙은 스레드 세이프티(Thread Safety) 를 보장하는 데 중요합니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[github에 100MB 이상 push 하기.]]></title>
            <link>https://velog.io/@dl_01312/github%EC%97%90-100MB-%EC%9D%B4%EC%83%81-push-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dl_01312/github%EC%97%90-100MB-%EC%9D%B4%EC%83%81-push-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 21 Apr 2025 14:51:11 GMT</pubDate>
            <description><![CDATA[<h1 id="🚨-발생-원인">🚨 발생 원인</h1>
<p><strong>gitlab</strong>에서 했던 프로젝트를 <strong>github</strong>로 옮기려고 했다.
<strong>프로젝트 코드 + commit 메시지 내역</strong> 등을 한 번에 옮기려고 했는데 문제가 생겼습니다.</p>
<pre><code class="language-bash">remote: Resolving deltas: 100% (4812/4812), done.
remote: error: Trace: c8115ed37ee052ccbf0cf957bece41a44fba9432fe67faaec0d40e60acd29827
remote: error: See https://gh.io/lfs for more information.
remote: error: File python/kcelectra_sentiment/model.safetensors is 416.14 MB; this exceeds GitHub&#39;s file size limit of 100.00 MB
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
To https://github.com/kobenlys/Jippy.git
 ! [remote rejected] main -&gt; main (pre-receive hook declined)
error: 레퍼런스를 &#39;https://github.com/kobenlys/Jippy.git&#39;에 푸시하는데 실패했습니다</code></pre>
<p>파이썬 모델파일 용량이 416MB라서 github에 푸시하는데 실패했습니다.
이유는 github에서 push시 <strong>100mb</strong> 제한을 두고 있었기 때문입니다.</p>
<p>이를 해결하는 과정을 남겨보겠습니다.</p>
<h1 id="🛠️-해결과정">🛠️ 해결과정</h1>
<h3 id="1-homebrew로-lfs-filter-repo-설치">1. HomeBrew로 lfs, filter-repo 설치</h3>
<pre><code class="language-bash">brew install git-filter-repo
brew install git-lfs</code></pre>
<ul>
<li><strong>filter-repo*</strong>는 로컬 저장소의 히스토리를 완전히 재작성 합니다.</li>
<li><strong>lfs</strong>는 대용량데이터를 git에 push할때 사용합니다.</li>
</ul>
<h3 id="2-100mb가-넘는-파일-git-히스토리에서-제거">2. 100mb가 넘는 파일 git 히스토리에서 제거</h3>
<pre><code class="language-bash">git filter-repo --path python/kcelectra_sentiment/model.safetensors --invert-paths</code></pre>
<ul>
<li>commit 내역까지 push하기 때문에 히스토리에서 <strong>100mb</strong>가 넘어가는 파일을 제거 해줘야 합니다.</li>
</ul>
<h3 id="3-가장-마지막-커밋에-반영된-것만-추가하려면-선택임">3. 가장 마지막 커밋에 반영된 것만 추가하려면 (선택임)</h3>
<pre><code class="language-bash">
# lfs 초기화
git lfs install

# LFS 설정 트래킹 할 파일 지정
git lfs track &quot;python/sentiment/model.safetensors&quot;

# .gitattributes 추가 후 커밋
git add .gitattributes
git add python/kcelectra_sentiment/model.safetensors
git commit -m &quot;chore: using lfs&quot;

# push 하기
git push origin main
</code></pre>
<blockquote>
<p>commit 히스토리에는 100mb되는 파일을 제거하고 가장 마지막 커밋의 파일만 LFS로 관리하면 된다. 😋</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[K6Weaver 라이브러리 제작기]]></title>
            <link>https://velog.io/@dl_01312/K6Weaver-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%A0%9C%EC%9E%91%EA%B8%B0</link>
            <guid>https://velog.io/@dl_01312/K6Weaver-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%A0%9C%EC%9E%91%EA%B8%B0</guid>
            <pubDate>Sat, 12 Apr 2025 15:45:59 GMT</pubDate>
            <description><![CDATA[<h1 id="🧐-왜-만들었나요">🧐 왜 만들었나요?</h1>
<blockquote>
<p>SSAFY에서 관통 프로젝트를 할때였다..</p>
</blockquote>
<p>한창 Jippy(플젝이름 맞음)를 개발하고 프로젝트 발표시간이 다가오기
일주일전, 갑자기 부하테스트를 진행하고 싶은 생각이 들어버렸다 ㅎㅎ</p>
<p>백엔드 개발자로서 내가 개발한 API서버가 얼마나 버틸지 궁금했고.
발표에서 시각화 자료와 함께 짠~ 하고 보여주고싶었다.</p>
<h1 id="🧐-어떤-부하테스트-툴을-사용했는지">🧐 어떤 부하테스트 툴을 사용했는지?</h1>
<blockquote>
<p>K6 vs JMeter 고민이다..</p>
</blockquote>
<p>한참 고민하다가 K6를 사용하기로 했다.
JMeter는 GUI를 지원하지만 한번에 설정하는게 어려웠고
심지어 XML기반이라 작성하기도 어려웠다</p>
<p>하지만 K6는 javascript기반이여서 일단 스크립트가 되게 친숙했다(ㅋㅋ)
그래서 빠른시간안에 배워서 적용하기엔 K6가 유리해 보여서 사용했습니다.</p>
<p>이럴땐 자바스크립트가 또 반갑네 ㅎㅎ;;</p>
<h1 id="🧐-그래서-뭐가-불편했는데">🧐 그래서 뭐가 불편했는데?</h1>
<pre><code class="language-javascript">// 엔드포인트들
  const urls = [
    &quot;/api/calendar/1/select&quot;,
    &quot;/api/calendar/1/select/1&quot;,
    &quot;/api/qr/1/select&quot;,
    &quot;/api/dashboard/staff/1/totalStoreSalary&quot;,
    &quot;/api/dashboard/staff/1/storeSalary?date=2025-01&quot;,
    &quot;/api/feedback/1/select&quot;,
    &quot;/api/cash/1/select&quot;,
    &quot;/api/attendance/1/tempChange/list&quot;,
    &quot;/api/stock/1/select&quot;,
    &quot;/api/chat/1/select/1&quot;,
    &quot;/api/todo/1/select&quot;,
    &quot;/api/storeStaff/1/select&quot;,
    &quot;/api/storeStaff/staff/earn?storeId=1&amp;yearMonth=2024-10&quot;,
    &quot;/api/store/select/1&quot;,
    &quot;/api/store/select/list&quot;,
    &quot;/api/product/1/select&quot;,
    &quot;/api/product/1/select/1&quot;,
    &quot;/api/product/1/fetch/month?targetYear=2024&quot;,
    &quot;/api/product/1/fetch/all?startDate=2024-01&amp;endDate=2025-01&quot;,
    &quot;/api/payment-history/list?storeId=1&quot;,
    &quot;/api/payment-history/sales/day?storeId=1&amp;startDate=2024-01-01&amp;endDate=2024-07-01&quot;,
    &quot;/api/payment-history/sales/month?storeId=1&amp;startDate=2024-01&amp;endDate=2024-12&quot;,
    &quot;/api/payment-history/sales/week?storeId=1&amp;startDate=2024-01&amp;endDate=2024-20&quot;,
    &quot;/api/category/1/select&quot;,
  ];</code></pre>
<p>엔드포인트를 일일히 적는게 매우 불편했다. 할일도 많고..
그러다가 갑자기 Swagger API 문서가 떠올랐다.</p>
<blockquote>
<p>코드분석해서 API 문서 만들어주는거면, 코드 분석해서 스크립트 만들 수 있겠는데?</p>
</blockquote>
<p>아무튼 프로젝트가 끝나고나서 바로 라이브러리 제작에 돌입했다.</p>
<h1 id="🙃-어떻게-구현했나">🙃 어떻게 구현했나?</h1>
<h4 id="1-스프링-컨테이너에-등록된-restcontroller만-가져오기">1. 스프링 컨테이너에 등록된 RestController만 가져오기.</h4>
<ul>
<li><strong>ControllerScan</strong> 이라고 우리는 이야기했다. </li>
</ul>
<p>정확히는 <strong>@RestController</strong> 어노테이션이 붙은 class 파일을 탐색했다.</p>
<h4 id="2-엔드포인트-추출하기">2. 엔드포인트 추출하기.</h4>
<ul>
<li><strong>@RequestMapping, @XXXMapping</strong> 에 있는 value를 읽어와서 추출했다.</li>
</ul>
<p>만약 <strong>@RequestMapping(&quot;/api/user&quot;), @PostMapping(&quot;/create&quot;)</strong>
있다면 http 메서드와, 엔드포인트, 패키지경로를 담는 객체에 저장했다.</p>
<p><em>=&gt; /api/user/create, POST, com.kiki.org</em>
그후 정렬했다.</p>
<h4 id="3-스크립트-생성하기">3. 스크립트 생성하기</h4>
<p>우선순위에따라 정렬하여 사용자가 편하게 커스텀 할 수 있도록 집합화 하여
스크립트를 생성했다.</p>
<pre><code class="language-javascript">/* ========== com.nok.authenticationserver.domain.web.controller ========== */
    res = http.post(`${baseUrl}/api/auth/login/memberInfo`,authLoginMemberInfoPayload, params);
    check(res, { &#39;status was 2xx&#39;: (r) =&gt; r.status &gt;= 200 &amp;&amp; r.status &lt; 300 });

    res = http.post(`${baseUrl}/api/auth/login/password`,authLoginPasswordPayload, params);
    check(res, { &#39;status was 2xx&#39;: (r) =&gt; r.status &gt;= 200 &amp;&amp; r.status &lt; 300 });

    res = http.post(`${baseUrl}/api/auth/login/pattern`,authLoginPatternPayload, params);
    check(res, { &#39;status was 2xx&#39;: (r) =&gt; r.status &gt;= 200 &amp;&amp; r.status &lt; 300 });

    res = http.get(`${baseUrl}/api/auth/verify/member-id/ssafy1`);
    check(res, { &#39;status was 2xx&#39;: (r) =&gt; r.status &gt;= 200 &amp;&amp; r.status &lt; 300 });

    res = http.get(`${baseUrl}/api/auth/voice/fetch/1`);
    check(res, { &#39;status was 2xx&#39;: (r) =&gt; r.status &gt;= 200 &amp;&amp; r.status &lt; 300 });


    /* ========== com.nok.bank.web.controller ========== */
    res = http.put(`${baseUrl}/api/bank`,bankPayload, params);
    check(res, { &#39;status was 2xx&#39;: (r) =&gt; r.status &gt;= 200 &amp;&amp; r.status &lt; 300 });

    res = http.post(`${baseUrl}/api/bank/auth`,bankAuthPayload, params);
    check(res, { &#39;status was 2xx&#39;: (r) =&gt; r.status &gt;= 200 &amp;&amp; r.status &lt; 300 });

    res = http.post(`${baseUrl}/api/bank/check-deposit`,bankCheckdepositPayload, para</code></pre>
<p>이렇게 패키지별로 정리후 http메서드를 기준으로 한번더 깔끔하게 정렬하여
생성했다.</p>
<h1 id="✅-프로젝트-후기">✅ 프로젝트 후기.</h1>
<p>Java코드를 작성하기만 해도 K6부하테스트 스크립트의 초안을 만들어 주는 게 
매리트는 있는 것 같다. </p>
<p>사용해 본 사람들이 후기 남겨주는 것도 감사하고, 뿌듯했다. </p>
<p>지인들도 프로젝트 진행하면서 각자 프로젝트에서 적용해봤다고 한다. 
덕분에 버그, 피드백을 많이 수집할 수 있었다. 이번 특화프로젝트가 끝나면 </p>
<p>버그픽스 + 사용자경험을 개선한 정식 릴리즈버전을 배포해야겠다.</p>
<h2 id="✅-etc">✅ etc.</h2>
<p>✅ <a href="https://mvnrepository.com/artifact/io.github.kobenlys/K6Weaver">MVN 배포완료!</a></p>
<p>Maven Central Repository에 직접 배포까지 진행해서. jar파일을 
항상 가지고 다닐 필요가 없게 됐다 ㅎㅎ 
작게나마 스프링개발자 생태계에 기여한 것 같아서 좋았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSAFY - 공통 프로젝트 회고 ]]></title>
            <link>https://velog.io/@dl_01312/SSAFY-%EA%B3%B5%ED%86%B5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@dl_01312/SSAFY-%EA%B3%B5%ED%86%B5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 03 Apr 2025 14:13:03 GMT</pubDate>
            <description><![CDATA[<h1 id="프로젝트-기간">프로젝트 기간</h1>
<ul>
<li>2025-01-03 ~ 2025-02-27</li>
<li>인원은 6명</li>
</ul>
<h1 id="무엇을-배웠나요">무엇을 배웠나요?</h1>
<h3 id="1-jira를-사용한-프로젝트-협업스킬">1. Jira를 사용한 프로젝트 협업스킬</h3>
<blockquote>
<p>JIRA란? 
지라는 아틀라시안이 개발한 사유 이슈 추적 제품이다. 버그 추적, 이슈 추적, 프로젝트 관리 기능을 제공하는 소프트웨어이다.</p>
</blockquote>
<p>지금까지 프로젝트를 해오면서 불편했던 점은 팀원들의 작업진행 정도를 파악하기 힘들었고 매번 어디까지 작업했는지
종합하는 시간을 가져야만 파악할 수 있다는 점이 불편했었다.</p>
<p>이번 프로젝트에서 JIRA를 도입하면서 팀원들의 업무량, 속도 등을 파악하기 쉬워졌고
프로젝트를 더 넓은 시야에서 볼 수 있다는 점이 좋았다</p>
<p>팀원들과 백로그를 작성하고 스프린트진행하며 애자일 하게 개발하는 연습을 주로 했다</p>
<h3 id="2-mongodb-redis-적용방법">2. MongoDB, Redis 적용방법</h3>
<blockquote>
<p>MongoDB : NoSQL 데이터베이스이며 데이터를 JSON 형태로 저장한다 특히 대용량데이터 처리에 강점을 가진다.
Redis : 인메모리 데이터베이스이고 Key-Value 구조로 저장된다.</p>
</blockquote>
<p>이번 프로젝트에서는 성능을 위해 데이터베이스를 Mysql제외하고 MongoDB, Redis를 프로젝트에 적용했다. </p>
<p>매출로그, 판매내역 등 데이터를 이용해서 대시보드를 만들거나 데이터 분석하여 매출 예측하는 기능을 만들기 위해서 
대규모 데이터를 다루기엔 MySql만으론 성능상 불리하다고 생각했다. </p>
<p>그래서 대규모 데이터를 읽기/추가할 때 좋은 성능을 내는 MongoDB를 사용하는 게 적절한 선택지인 것 같아서 적용했다. 레디스 또한 JWT 토큰 등 휘발성이 강한 데이터를 다룰 때 주로 사용했고. 자주 요청되는 데이터를 캐싱하는 
용도로도 사용했다. </p>
<p>레디스 캐싱전략을 공부를 해봐야겠다고 느꼈다.</p>
<h3 id="3-docker를-이용한-배포-및-개발환경-세팅">3. Docker를 이용한 배포 및 개발환경 세팅</h3>
<p>항상 프로젝트를 하면 개발환경 세팅하는 것에 시간을 많이 사용하곤 했다. 이번에 Docker로 EC2에 
DB 컨테이너를 띄우는 김에 로컬환경에서도 개발환경 세팅을 Docker로 시도했다 </p>
<p>결과는 매우 만족했다. 버전변경에 자유롭고 설치 후 삭제를 할 필요 없이 컨테이너 종료만 하면 된다는 점이 
아주 편했다. 이제 도커를 배운 것이 아쉽게 느껴질 정도로 매우 만족했다. </p>
<p>다음 프로젝트엔 인프라를 담당해서 도커와 더욱 가까워져야겠다.</p>
<h3 id="4-virtual-thread-적용방법">4. Virtual Thread 적용방법</h3>
<blockquote>
<p>JDK 21 버전부터 정식지원 하는 기능이며, 기존 플렛폼 스레드와 달리 OS스레드와 1대1 매핑되지 않는 가벼운 스레드이다.</p>
</blockquote>
<p>항상 동기처리 방식으로 개발을 해왔었던 나에게 신선한 경험을 하게 해 준 기능이다.</p>
<p>다건의 DB에 데이터 쓰기 작업을 할 때 주로 사용했다 쓰기작업을 병렬처리 함으로써
Blocking I/O 문제를 해결하여 성능상 이점을 보려고 했다</p>
<p>하지만 조회에서 일부 응답이 먼저 나가는 일이 벌어졌다 비동기 방식의 특징을 간과해서였다.</p>
<p>위 문제를 해결하려고 노력한 덕분에
비동기/동기 처리의 특징을 개발로서 익힐 수 있어서 좋았다.</p>
<h3 id="5-부하테스트-및-시각화작업">5. 부하테스트 및 시각화작업</h3>
<blockquote>
<p>부하테스트는 K6, 시각화는 InfluxDB, Grafana를 사용했다.</p>
</blockquote>
<p>주변 개발자 친구들에게 항상 하는 말 이 있다
이젠 기능구현은 누구나 잘한다, 경쟁력을 가지려면 다른 무언가 필요하다.</p>
<p>이번엔 그중에서 <strong>테스트</strong>라는 영역을 선택해서 실제 프로젝트에 적용하고 싶었다.</p>
<p>계획은 K6로 부하테스트를 하고 시계열 데이터를 InfluxDB에 저장 후
Grafana를 통해 테스트 결과를 실시간으로 시각화하고 싶었다.</p>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/f96736cb-fac1-4aef-8463-430d349ff254/image.png" alt=""></p>
<p>결과적으로 성공했고 앞으로 프로젝트마다 부하테스트 진행하면서 서비스의 capacity를 체크하고 성능을 최적화하는 방법을 공부해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[절대 JDK 21 + SpringBoot 3.4.*에 Swagger 2.6.0을 사용하지마]]></title>
            <link>https://velog.io/@dl_01312/%EC%A0%88%EB%8C%80-JDK-21-SpringBoot-3.4.%EC%97%90-Swagger-2.6.0%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80%EB%A7%88</link>
            <guid>https://velog.io/@dl_01312/%EC%A0%88%EB%8C%80-JDK-21-SpringBoot-3.4.%EC%97%90-Swagger-2.6.0%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80%EB%A7%88</guid>
            <pubDate>Sun, 23 Feb 2025 16:36:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>SSAFY에서 프로젝트를 진행하다 생긴 일입니다.</p>
</blockquote>
<h1 id="🧐-왜-사용하면-안되나요">🧐 왜 사용하면 안되나요?</h1>
<p>Swagger-ui/index.html로 접속 후 API 문서조회를 한다면.
Swagger <strong>500, 403 Error</strong> 가 발생합니다.</p>
<h2 id="exceptionhandler-때문">@ExceptionHandler 때문..?</h2>
<p>사실 @ExceptionHandler 충돌 문제라고 치부하기보다는 버전 호환성 문제가 더 큽니다.
저희 팀에서는 @ExceptionHandler와 충돌이 난 것뿐이구요..</p>
<p>먼저 Spring Boot 3.X 버전에서는 @ExceptionHandler, @ControllerAdvice 등의
MVC 예외 처리 관련 코드가 충돌할 가능성이 큽니다.</p>
<blockquote>
<p>SpringBoot 3.X 버전부터 @ExceptionHandler는 HandlerMethodArgumentResolver를 활용하는데
이전 Swagger 버전에서는 이를 제대로 지원하지 못해 예외 발생할 수 있기 때문입니다.</p>
</blockquote>
<p><strong>SpringBoot 3.x는 OpenAPI(Swagger 3.0)을 사용 할 것을 권장합니다.</strong></p>
<h2 id="🛠️-해결방안은">🛠️ 해결방안은??</h2>
<ol>
<li>springdoc-openapi를 사용하는 것을 권장합니다</li>
<li>2.7.0 버전으로 변경하시면 됩니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JDK 21 Virtual Thread 사용해보기]]></title>
            <link>https://velog.io/@dl_01312/JDK-21-Virtual-Thread-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@dl_01312/JDK-21-Virtual-Thread-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 19 Jan 2025 08:41:28 GMT</pubDate>
            <description><![CDATA[<h1 id="🧐-virtual-thread-란">🧐 Virtual Thread 란?</h1>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/a30240d4-5284-43bd-a1b5-6db32cc8ca6e/image.png" alt=""></p>
<ul>
<li><em>가상 스레드는 고처리량 동시 애플리케이션을 작성, 유지 관리, 디버깅하는 데 드는 노력을 줄여 주는 가벼운 스레드입니다. - 오라클 공식문서 -</em></li>
<li><strong>경량스레드</strong>라고도 불린다</li>
</ul>
<h2 id="🧐-기존-jdk-버전에서는">🧐 기존 JDK 버전에서는..?</h2>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/77c0cee9-8af0-4a81-80cb-b4334a72d2c1/image.png" alt=""></p>
<ul>
<li>기존 JDK에서는 플랫폼 스레드와, OS스레드가 1대1 Wrapping 된 구조이다.
이러한 구조는 OS thread에 종속적이며 이는 곧 CPU 스팩에 종속적이다는 말과 같다</li>
</ul>
<h3 id="️-한계점">‼️ 한계점</h3>
<ul>
<li>메모리 소비 - OS thread는 1~2MB 메모리 할당받음</li>
<li>운영체제 부하 - 스레드가 많아질 경우 운영체제에서 스케줄링에 부담</li>
<li>blocking I/O - 입출력/요청시 스레드는 리소스를 점유하는 동시에 대기 상태</li>
</ul>
<p>이러한 문제점을 해결하기 위해 <strong>가상스레드(Virtual Thread)</strong>가 등장한다</p>
<h2 id="📚-jdk-21-버전-부터는">📚 JDK 21 버전 부터는..?</h2>
<p><img src="https://velog.velcdn.com/images/vlfxhd69/post/8e86eade-a6cf-4904-8988-51723f673048/image.png" alt=""></p>
<ul>
<li>가상스레드를 도입!</li>
</ul>
<p>가상스레드는 JVM레벨에서 관리되는 스레드이며 수백만 개의 가상스레드 생성해도
메모리와 CPU리소스를 적게 소모한다.</p>
<p>먼저 생각해보면 가상스레드를 사용한다면 기존 OS스레드에 의존적이지 않게 되며
JVM 자체 스케줄러를 사용하기 때문에 스위칭 비용이 작아짐.
또한..</p>
<h3 id="blocking-io-완화">Blocking I/O 완화</h3>
<p>가상 스레드는 1대 1 Wrapping 되어 있지 않기 때문에 언제든지 마운트/언마운트로 플랫폼 스레드에 작업을 할당할 수 있음</p>
<p>즉 <strong>가상스레드 1번</strong>이 대기상태에 돌입하면, 작업 끝날때까지 플랫폼 스레드를 점유하지 않고 <strong>가상스레드 2번</strong>이 작업을 시작하고, <strong>가상스레드 1번</strong>이 대기상태에서 풀려나면 다시 플랫폼 스레드를 마운트하여 사용한다</p>
<h1 id="🥑-그래서-언제-사용하지">🥑 그래서 언제 사용하지?</h1>
<ul>
<li>시나리오 1
  매일 아침 6시에 만 명 이상이 존재하는 서비스에 모든 유저의 포인트를 +30 할 예정이다.</li>
</ul>
<pre><code class="language-java">    @Scheduled(cron = &quot;0 0 6 * * *&quot;) // 매일 아침 6시 실행 스캐줄
    public void updateAllUserPoints() {
        List&lt;User&gt; users = getListAllUsers(); // 모든 유저 리스트 가져오기

        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (User user : users) {
                executor.submit(() -&gt; { // 작업 제출
                    updateUserPoints(user); // 유저 포인트 업데이트
                });
            }
        }
    }</code></pre>
<ul>
<li>Executors 
  자바의 스레드 풀 관리 유틸리티 클래스임.</li>
<li>newVirtualThreadPerTaskExecutor
  JDK 21에 추가된 가상스레드기반의 스레드 풀 생성하는 메서드</li>
</ul>
<h2 id="🌱-기대효과근사치">🌱 기대효과(근사치)</h2>
<p>만약 10000명의 유저의 업데이트 작업을 한다면?</p>
<ul>
<li>1명당 작업 시간 200ms 소요 (가정), DB 연결 풀 (100)</li>
</ul>
<h3 id="🌱-기존-방식">🌱 기존 방식</h3>
<p>(10000/100) * 200ms =&gt; 20000ms =&gt; 20초</p>
<h3 id="🌱-가상스레드-방식">🌱 가상스레드 방식</h3>
<ul>
<li>Blocking I/O의 성능 최적화 (적은 데이터(10~20%) 대용량데이터일때 (50% 이상) 가정) 20% 성능 개선 가정</li>
</ul>
<p>(10000 (200ms * 80%))/100 =&gt; 16초</p>
<h1 id="👍-결론">👍 결론</h1>
<ul>
<li>DB 등 다른 부분의 성능을 향상해 주는 기술은 아님 Blocking I/O의 시간을 효율적으로 처리하여 <strong>애플리케이션 차원</strong>에서 대기시간을 감소하여 작업시간을 단축하는 효과가 크다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Controller에 단일책임원칙(SRP) 보장하기 (Exception 편)]]></title>
            <link>https://velog.io/@dl_01312/Controller%EC%97%90-%EB%8B%A8%EC%9D%BC%EC%B1%85%EC%9E%84%EC%9B%90%EC%B9%99SRP-%EB%B3%B4%EC%9E%A5%ED%95%98%EA%B8%B0-Exception-%ED%8E%B8</link>
            <guid>https://velog.io/@dl_01312/Controller%EC%97%90-%EB%8B%A8%EC%9D%BC%EC%B1%85%EC%9E%84%EC%9B%90%EC%B9%99SRP-%EB%B3%B4%EC%9E%A5%ED%95%98%EA%B8%B0-Exception-%ED%8E%B8</guid>
            <pubDate>Mon, 06 Jan 2025 16:28:08 GMT</pubDate>
            <description><![CDATA[<h1 id="🧐-단일책임원칙srp란">🧐 단일책임원칙(SRP)란?</h1>
<blockquote>
<p>SOLID 원칙 중 가장 첫번째 단일책임원칙</p>
</blockquote>
<ul>
<li>하나의 클래스는 하나의 역할만 수행해야 한다.</li>
<li>변경 사유는 하나여야 한다.</li>
</ul>
<p>클래스가 너무 많은 책임을 가지면 코드가 복잡해지고 수정하기 어려워질뿐더러 
하나의 책임을 변경할 때 다른 책임이 변경될 수 있다.</p>
<p>즉 Controller 자신의 본 책임, 요청과 응답을 처리하는 역할만 가지는 게 가장 좋은 구조이다. 
가끔 공부를 하다 보면 Controller 내부에 예외처리코드를 넣은 코드를 보게 된다. </p>
<p>물론 작동은 잘 될 것이다, 하지만 Controller가 많아지고 내부 함수 또한 많아진다면 
중복코드발생 및 수정할 때도 많은 시간이 걸릴 것이다...</p>
<h1 id="📚-controller에서-exception코드-분리하기">📚 Controller에서 Exception코드 분리하기</h1>
<ul>
<li>@RestControllerAdvice + @ExceptionHandler 사용하기</li>
</ul>
<h2 id="restcontrolleradvice">@RestControllerAdvice</h2>
<ul>
<li>컨트롤러 전역에서 발생하는 예외를 처리하기 위한 어노테이션이다. </li>
<li>AOP를 이용해서 존재하는 모든 Controller에 발생하는 에러를 캐치하여 공통으로 처리할 수 있게 도와준다. </li>
<li>객체를 반환할 때 사용하는 특징이 있다.<h2 id="exceptionhandler-란">@ExceptionHandler 란?</h2>
</li>
<li>@Controller 어노테이션이 들어간 Bean에서 발생하는 예외를 캐치하여 Exception처리하는 역할을 한다.</li>
<li>클래스 범위에서 작동하는 예외처리 메커니즘을 가진다, 즉 다른 컨트롤러에서 발생하는 에러는 처리하지 않습니다.</li>
<li>AOP를 사용하지 않고, HandlerExceptionResolver를 이용하여 예외처리 한다.</li>
</ul>
<p>위 두가지 어노테이션을 같은 클래스에서 사용한다면 모든 Controller에서 발생하는 에러를 공통 로직에서 처리가능하다.</p>
<h1 id="📚-예제코드">📚 예제코드</h1>
<ul>
<li>먼저 UserController.java에 있는 예외처리 코드를 제거하고, GlobalExceptionHandler.java
클래스를 만들어 UserController 뿐만 아닌 다른 Controller의 에러도 처리하려고 한다.</li>
</ul>
<h3 id="🌱-globalexceptionhandlerjava">🌱 GlobalExceptionHandler.java</h3>
<ul>
<li>저번 관통프로젝트에서 리펙토링을 진행하면서 만든 ExceptionHandler 코드임.</li>
</ul>
<pre><code class="language-java">@RestControllerAdvice // 전역 Controller 에러 캐치
public class GlobalExceptionHandler {

        // MethodArgumentNotValidException에 대한 에러 처리
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity&lt;ErrorResponse&gt; handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity&lt;&gt;(response, HttpStatus.BAD_REQUEST);
    }

        // BindException에 대한 에러 처리
    @ExceptionHandler(BindException.class)
    protected ResponseEntity&lt;ErrorResponse&gt; handleBindException(BindException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity&lt;&gt;(response, HttpStatus.BAD_REQUEST);
    }

        // ConstraintViolationException에 대한 에러 처리
    @ExceptionHandler(ConstraintViolationException.class)
    protected ResponseEntity&lt;ErrorResponse&gt; handleConstraintViolationException(ConstraintViolationException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE, e.getConstraintViolations());
        return new ResponseEntity&lt;&gt;(response, HttpStatus.BAD_REQUEST);
    }

        // MissingServletRequestParameterException에 대한 에러 처리
    @ExceptionHandler(MissingServletRequestParameterException.class)
    protected ResponseEntity&lt;ErrorResponse&gt; handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.MISSING_REQUEST_PARAMS, e);
        return new ResponseEntity&lt;&gt;(response, HttpStatus.BAD_REQUEST);
    }

        // NullPointerException에 대한 에러 처리
    @ExceptionHandler(NullPointerException.class)
    protected ResponseEntity&lt;ErrorResponse&gt; handleNullPointerException(NullPointerException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.NULL_POINT, e.getMessage());
        return new ResponseEntity&lt;&gt;(response, HttpStatus.NOT_FOUND);
    }

        // 나머지 에러처리
    @ExceptionHandler(Exception.class)
    protected ResponseEntity&lt;ErrorResponse&gt; handleException(Exception e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
        return new ResponseEntity&lt;&gt;(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
</code></pre>
<ul>
<li>에러 응답에 일관성을 부여하기 위해서 ErrorResponse나 CustomErrorCode 등 사용하는 것을 권장한다.</li>
</ul>
<h1 id="🥑-기대효과">🥑 기대효과</h1>
<ul>
<li>Controller와 예외처리코드를 분리하여 Controller의 본연의 책임에 집중할 수 있게 한다.</li>
<li>Controller에 대한 예외처리를 전역에서 공통코드로 처리하기 때문에 유지보수성이 향상되며 중복코드 감소한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[@Enumerated 보다 @Convert 사용이 권장되는 이유]]></title>
            <link>https://velog.io/@dl_01312/Enumerate-%EB%B3%B4%EB%8B%A4-Convert-%EC%82%AC%EC%9A%A9%EC%9D%B4-%EA%B6%8C%EC%9E%A5%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dl_01312/Enumerate-%EB%B3%B4%EB%8B%A4-Convert-%EC%82%AC%EC%9A%A9%EC%9D%B4-%EA%B6%8C%EC%9E%A5%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Wed, 18 Dec 2024 01:23:21 GMT</pubDate>
            <description><![CDATA[<h1 id="📚-annotation">📚 Annotation</h1>
<ul>
<li><p><em>@Enumerated</em>
  Entity필드의 Enum타입을 DB에 매핑할때 사용하는 어노테이션</p>
</li>
<li><p><em>@Convert</em>
  Entity의 필드타입을 변환하여 DB에 저장하거나 DB에서 읽어올때 변환작업
  이 필요할때 사용하는 어노테이션</p>
</li>
</ul>
<h1 id="📚-convert-사용이-권장되는-이유">📚 @Convert 사용이 권장되는 이유?</h1>
<p>먼저 <em>@Enumerated</em> 의 속성을 알아야한다.</p>
<blockquote>
<ol>
<li>EnumType.ORDINAL
 Enum타입을 순서에 맞는 숫자로 변환해서 DB에 저장</li>
<li>EnumType.STRING
 Enum타입을 그대로 문자열로 DB에 저장</li>
</ol>
</blockquote>
<p><strong>EnumType.STRING</strong>을 사용한다면 <strong>@Convert</strong>를 사용 할 필요는 없다
하지만 DB에 동일한 문자열을 여러번 저장하는 것은 용량을 많이 필요로 하기때문에 권장하지 않는다.</p>
<p>즉 <strong>EnumType.ORDINAL</strong>을 사용하여 <strong>문자열 -&gt; 숫자</strong>로 저장하는것이 효율적일 것이다.
하지만 <strong>EnumType.ORDINAL</strong>은 프로젝트가 복잡해질때 유지보수관련 문제점이 생긴다</p>
<hr>
<h2 id="🚨-enumtypeordinal-문제점">🚨 EnumType.ORDINAL 문제점</h2>
<pre><code class="language-java">public Enum Stat{
    GOOD, FINE, BAD
}</code></pre>
<p>만약 <strong>EnumType.ORDINA</strong>L으로 변환을 한다면
<em>GOOD = 1, FINE = 2, BAD = 3</em> 으로 DB에 저장될것이다.</p>
<p>하지만 모종의 이유로 위 코드를 수정할때 Enum 필드의 순서가 변경이 된다면 잘못된 값으로 매핑이 될것이다. 
이는 큰 규모의 프로젝트에서 그리 효율적이지 못한 방향이다.</p>
<p><em>Convert 사용을 권장하는 가장 큰 이유다.</em></p>
<h1 id="📚-convert를-사용하자">📚 @Convert를 사용하자!</h1>
<ul>
<li><p>예제코드</p>
<h3 id="🌱-statjava">🌱 Stat.java</h3>
<pre><code class="language-java">public Enum Stat{
  GOOD(1),
  FINE(2),
  BAD(3);

  private Integer code;

  Enum(Integer code){
      this.code = code;
  }

  public Integer getCode(){
      return code;
  }

  public static Stat ofLegacyCode(Integer code){
      for(Stat stat : Stat.values()){
          if(code == stat.getCode()){
              return stat;
          }
      }
      throw new IllegalArgumentException(&quot;Error!&quot;)
  }
}</code></pre>
<p>먼저 Enum Class이다. Enum필드 뒤에 고유 숫자를 부여하고
DB에 저장할때는 getCode()를 사용하여 문자 -&gt; 숫자로 변환하고
DB에서 읽어올때는 ofLegacyCode()를 사용해서 숫자 -&gt; 문자로 변환한다.</p>
</li>
</ul>
<h3 id="🌱-statconverterjava">🌱 StatConverter.java</h3>
<pre><code class="language-java">@Converter(autoApply = true)
public class StatConverter implements AttributeConverter&lt;Stat, Integer&gt;{
    @Override
    public Integer convertToDatabaseColumn(Stat stat){
        if(!Objects.isNull(stat)){
            return stat.getCode();
        }
        throw new IllgalArgumentException(&quot;Error!&quot;);
    }

    @Override
    public Stat convertToEntityAttribute(Integer code){
        if(!Objects.isNull(code)){
            return Stat.ofLegacyCode(code);
        }
        throw new IllegalArgumentException(&quot;Erorr!!!&quot;);
    }
}</code></pre>
<p>AttributeConverter&lt;K,V&gt;의 interface를 상속하여 각 interface에 맞게 재정의 해주면된다.</p>
<ul>
<li><p>convertToDatabaseColumn
  Enum타입을 숫자로 변환하여 DB에 저장하는 메서드</p>
</li>
<li><p>convertToEntityAttribute
  DB에서 읽어온 숫자를 Enum 필드와 매핑하여 반환하는 메서드</p>
</li>
</ul>
<h3 id="🌱-statentityjava">🌱 StatEntity.java</h3>
<pre><code class="language-java">    @Column
    @Convert(converter = StatConverter.class)
    private Stat stat;</code></pre>
<p>Entity의 필드에 @Convert를 위 처럼 적용해줘야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DTO, DAO가 무엇일까? with 사용하는 이유]]></title>
            <link>https://velog.io/@dl_01312/DTO-DAO%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-with-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dl_01312/DTO-DAO%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-with-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Fri, 13 Dec 2024 20:24:42 GMT</pubDate>
            <description><![CDATA[<h1 id="📚-dto---data-transfer-object">📚 DTO - Data Transfer Object</h1>
<p>DB의 데이터를 담고 전송하는 데 사용되는 객체이며 로직을 포함하지 않는 순수한 데이터만 담는 객체임</p>
<h2 id="🌱-dto-장점">🌱 DTO 장점</h2>
<ul>
<li>어떤 데이터를 전송하고, 데이터를 담는지 명확하기 때문에 복잡하고 큰 프로젝트에서 식별하는데 용이함.</li>
<li>Client의 요구사항이 변경되어도 DTO만 수정하면 되므로 서비스 로직, DB계층(Entity)에 변경 없이 대응가능 함</li>
</ul>
<h1 id="📚-dao---data-access-object">📚 DAO - Data Access Object</h1>
<p>DB에 직접적으로 접근하는 객체이며 주로 CRUD 작업을 처리한다.
DAO는 DB와 상호작용하며 비즈니스로직과 DB사이를 이어주는 징검다리 역할을 한다.</p>
<h2 id="🌱-dao-장점">🌱 DAO 장점</h2>
<ul>
<li>일관된 interface를 제공하여 재사용이 용이하고, 확장이나 변경에 유리함.</li>
<li>서비스 로직과 DB접근 로직을 분리하여 결합도를 감소시킨다.</li>
<li>서비스 로직의 대한 단위테스트시 오직 서비스로직에만 테스트를 가능하게 한다.</li>
</ul>
<h1 id="📚-dao를-왜-사용하는-걸까">📚 DAO를 왜 사용하는 걸까?</h1>
<blockquote>
<p><img src="https://velog.velcdn.com/images/dl_01312/post/2d745e5d-f230-4470-926c-c0459fce2c64/image.png" alt=""></p>
</blockquote>
<p><em>관통 프로젝트를 진행하면서 가장 큰 의문점을 가졌던 부분이다</em>
위 코드는 서비스 로직에서 DAO를 통해 쿼리문으로 DB에서 데이터를 가져오고 가져온 데이터를 DTO에 담는 예제코드이다.
처음 든 생각은 그냥 서비스 로직 구현체에서 DAO의 역할을 부여하면 되지 않을까? 였다.</p>
<p>하지만 DAO가 없다면 이러한 문제점이 발생할 수 있다.</p>
<blockquote>
<ol>
<li>서비스 로직이 원래의 역할이 아닌 DB상호작용까지 책임을 지게 된다면 DB에 의존적으로 구현한다.</li>
<li>단위 테스트시 서비스로직테스트 시 DB까지 함께 테스트하게 되어 DB상태가 테스트 결과에 영향을 미친다.</li>
</ol>
</blockquote>
<p><em><strong>결국 결합도의 증가로 이어져 확장에 유연하지 못하고 복잡한 구조를 가지게 된다.</strong></em></p>
<h1 id="🚨-결론">🚨 결론</h1>
<blockquote>
<p>미래를 위해서 DTO, DAO를 사용하자!</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>