<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>푯대를 향하여</title>
    <link>https://to-the-goal.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 25 Jun 2026 19:26:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hhyun</managingEditor>
    <item>
      <title>밴드 플랫폼 서버 개발 일지 - 협업 툴로서 Postman 도입 및 작업 환경</title>
      <link>https://to-the-goal.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드와 협업을 위해 보통 스웨거가 많이 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스웨거는 컨트롤러 코드에 침투해 코드가 오염되는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 포스트맨 워크스페이스를 활용해 스웨거를 대체해보려 한다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;협업 문서로서 포스트맨 도입 배경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스웨거 코드를 인터페이스로 분리할 수도 있고, 어노테이션으로도 공통 관심사를 뺄 수 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 코드에 남겨야하는 부담이 있다. 즉, 스웨거가 잘못되었을 때 서버 재배포가 이루어져야 스웨거 수정이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 활용해서 작성하면 귀찮음도 떨어지고 실수할 일도 없겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스웨거 코드 고쳐서 배포하다가 서버 떨어질 아주 낮은 확률을 방지해보고자 하는 의도가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 사실 개인적으로 포스트맨을 잘 써보고 싶었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(보통 스웨거가 편하기 때문에 협업 시작하면 다시 돌아갈 수도 있다. 프론트엔드 개발자를 설득할 자신은 없다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그렇다면 왜 포스트맨을 써보려 했나?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 문서로도 활용이 가능하지만 궁극적으로 테스트 자동화까지 시도하고싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코덱스에서 충분히 가능하지만, 컨트롤러 통합테스트를 언젠가 일일이 시도하기에는 시간도, 토큰도 많이 소모될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'포스트맨을 잘 작성해놓고 MCP를 세팅한다면 로컬에서 테스트 편하게 가능하지 않을까?'라는 생각에서 출발했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 테스트 자동화는 AI없이 Github action CI로 세팅하는게 더 편리하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트에서는 Local 방식으로 MCP를 연동해 포스트맨 테스트 자동화가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 협업시에는 모두 동일한 환경에서 진행하지 않기에 깃헙 액션을 활용함이 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MCP 연동&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/postmanlabs/postman-mcp-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/postmanlabs/postman-mcp-server&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learning.postman.com/docs/reference/postman-api/postman-mcp-server/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learning.postman.com/docs/reference/postman-api/postman-mcp-server/overview&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 문서가 친절하게 잘 작성되어있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP를 연결할 때 2가지 방식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Remote, Local 방식이 각각 존재한데 간단히 각각 알아보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Local MCP&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 방식은 내 맥북에서 mcp 서버를 띄워 더 여러 조작을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 서버를 띄워 동작하다보니 내가 띄워놓은 스프링 서버에 직접 접근이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접적으로 codex가 포스트맨으로 로컬 서버에 여러 요청을 찔러보고 실제 에러/응답 코드를 확인하여 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 변수나 로컬 파일을 외부에 유출하지 않기 때문에 보안이 강화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Remote MCP&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Remote MCP는 포스트맨 쪽 클라우드 mcp 서버에 붙는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공홈에 있듯 &lt;a href=&quot;https://mcp.postman.com/minimal&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mcp.postman.com/minimal&lt;/a&gt; 이 Postman Cloud 쪽에서 작업이 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local MCP가 직접적으로 포스트맨을 로컬 서버에 쏘면서 세팅이 가능하다는 장점이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세팅도 조금 더 복잡하고 크게 와닿지 않아 간편한 Remote MCP를 사용해 세팅했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. MCP 연동&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하다.&lt;/p&gt;
&lt;pre id=&quot;code_1782381465549&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;codex mcp add postman --remote-url https://mcp.postman.com/minimal&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 codex 경로가 달라서 몇가지 명령어 더 입력했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 입력하면 웹에서 로그인 진행하고 연동이 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 작업 환경 세팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 스레드(채팅)을 2개로 분리해서 각각 다른 워크트리에서 병렬로 작업을 돌렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 lazycodex ulw-plan 명령어로 계속 계획을 뽑아내며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 병렬로 돌릴 작업 분배하고 하다보니 스레드 분리가 필요하다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 4가지 스레드를 워크트리를 각각 나누어 작업을 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계획, 작업1, 작업2, 포스트맨 작성 이렇게 4가지로 스레드를 분리했고 워크트리도 각각 분리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업은 이러한 순서로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 계획 스레드에서 ulw-plan을 사용해 병렬로 돌릴 작업을 선정하고 프롬프트까지 뽑는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 작업1, 작업2 스레드에서 1에서 세운 계획을 붙여넣고 ulw-plan을 사용해 구체적 작업 계획을 세운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. ulw-start로 작업을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 작업 완료 이후에 포스트맨 스레드에서 API 명세를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 나쁘지 않은 것 같아서 당분간은 이렇게 할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실제 작업 진행&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;763&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm52cn/dJMcabraTi9/bwgVkSi3mkXrtKKMkBlanK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm52cn/dJMcabraTi9/bwgVkSi3mkXrtKKMkBlanK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm52cn/dJMcabraTi9/bwgVkSi3mkXrtKKMkBlanK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm52cn%2FdJMcabraTi9%2FbwgVkSi3mkXrtKKMkBlanK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1470&quot; height=&quot;763&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;763&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 연동하고 codex한테 만들어달라하니 Collections를 잘 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 하단 Pull from Cloud 버튼 클릭하면 내 로컬 워크스페이스와 연동 잘 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpXFlE/dJMcahLFMC0/s2Dn1QamyxMD9S7mTyM0u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpXFlE/dJMcahLFMC0/s2Dn1QamyxMD9S7mTyM0u1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpXFlE/dJMcahLFMC0/s2Dn1QamyxMD9S7mTyM0u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpXFlE%2FdJMcahLFMC0%2Fs2Dn1QamyxMD9S7mTyM0u1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;887&quot; height=&quot;261&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PEUvL/dJMb99NAthZ/ZqzkbmMTkrV9IEOOjwItxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PEUvL/dJMb99NAthZ/ZqzkbmMTkrV9IEOOjwItxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PEUvL/dJMb99NAthZ/ZqzkbmMTkrV9IEOOjwItxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPEUvL%2FdJMb99NAthZ%2FZqzkbmMTkrV9IEOOjwItxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1317&quot; height=&quot;500&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 API를 테스트로 만들어 봤는데 나쁘지 않았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;애플리케이션 코드를 codex가 직접 확인해서 성공/실패 케이스 모두 요청해볼 수 있도록 만들어져있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PEUvL/dJMb99NAthZ/ZqzkbmMTkrV9IEOOjwItxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PEUvL/dJMb99NAthZ/ZqzkbmMTkrV9IEOOjwItxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PEUvL/dJMb99NAthZ/ZqzkbmMTkrV9IEOOjwItxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPEUvL%2FdJMb99NAthZ%2FZqzkbmMTkrV9IEOOjwItxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1317&quot; height=&quot;500&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 아직 스웨거처럼 간편하게 api 연동에 필요한 정보들을 모두 얻기는 어렵다 판단된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1407&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG7IxL/dJMcaiDPpEJ/GLtBRQcTEa2E755sXnzYI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG7IxL/dJMcaiDPpEJ/GLtBRQcTEa2E755sXnzYI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG7IxL/dJMcaiDPpEJ/GLtBRQcTEa2E755sXnzYI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG7IxL%2FdJMcaiDPpEJ%2FGLtBRQcTEa2E755sXnzYI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1407&quot; height=&quot;800&quot; data-origin-width=&quot;1407&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프팅해서 넣어보니 많이 괜찮아졌다. 요청 필드도 추가되었고 하단에 응답도 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아직 백엔드 기준으로 DTO 명이나 응답들이 써있어서 아쉽다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5ZRF/dJMcajvX1Uz/KUUBhMZkdH93KCskhdUQu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5ZRF/dJMcajvX1Uz/KUUBhMZkdH93KCskhdUQu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5ZRF/dJMcajvX1Uz/KUUBhMZkdH93KCskhdUQu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5ZRF%2FdJMcajvX1Uz%2FKUUBhMZkdH93KCskhdUQu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1914&quot; height=&quot;782&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;769&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/upNwI/dJMcafNVKJ6/VQMUYSVKYHlAAMqlXQKuhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/upNwI/dJMcafNVKJ6/VQMUYSVKYHlAAMqlXQKuhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/upNwI/dJMcafNVKJ6/VQMUYSVKYHlAAMqlXQKuhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FupNwI%2FdJMcafNVKJ6%2FVQMUYSVKYHlAAMqlXQKuhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;769&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;769&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이정도면 협업할 때 불편함 없이 사용해볼 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔드포인트, 요청, 성공/에러 응답 모두 한 눈에 편하게 체크해 활용 가능해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 이후부터는 스크립트 활용해서 access token 자동으로 들어가서 api 요청될 수 있도록 세팅해보려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 불편함이 생길때마다 디벨롭하며 이 글에 추가해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 많아지면 스웨거 쓰는게 좋을 것 같은데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적은 인원의 협업 환경(4명으로 알고있음)은 무료니까 한번 쯤 생각해보아도 나쁘지 않을 것 같다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 일기</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/25</guid>
      <comments>https://to-the-goal.tistory.com/25#entry25comment</comments>
      <pubDate>Thu, 25 Jun 2026 19:14:22 +0900</pubDate>
    </item>
    <item>
      <title>밴드 플랫폼 서버 개발 일지 - LazyCodex 도입</title>
      <link>https://to-the-goal.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 스레드에서 핫한 LazyCodex를 작업에 도입해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 계획을 세우고, 실제 작업을 진행하니 서브에이전트끼리 구현하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 서로 검토하는 과정을 보고있자니 굉장히 신기하고 놀라웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 LazyCodex와 그 이전에 OmO, 에이전트 하네스를 간단히 알아보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;하네스란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Harness의 의미는 말에 장착해서 기수가 타서 말을 컨트롤할 수 있도록 하는 마구다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들이 번지점프나 레펠 등 몸에 장착하는 안전장치도 하네스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI에서도 인간이 AI 에이전트를 안전하고 예측 가능한 방식으로 작동하도록 설계한 구조 전체를 하네스라고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI의 작업 수행력이 나날이 늘어감에 따라, 인간의 작업 방식도 변화하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT가 처음 나왔을때는 문답이나 코드를 붙여넣으며 수정을 요청했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트/컨텍스트 엔지니어링 등의 방법론이 유행했다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘에는 &lt;b&gt;에이전트&lt;/b&gt;까지 도달했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 직접 파일을 만들고, 코드 작성, 테스트 PR후 리뷰까지 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 전체 과정을 수행하다보니 인간이 생각했던 컨벤션, 아키텍처 등을 뒤집기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙을 세부적으로 정해놓지 않았기 때문에 당연한 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하는 개념이 &lt;b&gt;하네스 엔지니어링&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에이전트가 올바른 방향으로 일하도록 만드는 능력이 중요해진 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하네스는 에이전트를 동작시키는 데 그치지 않고, 제어/감시/개선을 꾸준히 진행하며 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;동작이나 오류를 구조적으로 억제한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하네스 엔지니어링을 '잘' 사용하는 방법은 꾸준히 발전하고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들이 공통적으로 말하는 것들은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 명확한 가드레일&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하네스를 사용하면 루트 디렉토리에 AGENTS.md부터 시작해서 여러 제약 문서들이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트가 작업을 시작하기 전에 읽는 매뉴얼이고, 명확한 &lt;b&gt;가드레일 설치&lt;/b&gt;하여 컨벤션이나 코딩 스타일을 제어해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 피드백&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트의 동작과 출력을 실시간으로 교정하여 결과가 올바른지 즉시 알려주는 장치가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하네스는 지속적으로 변화하는 동적인 구조이기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 행동에 대한 원인을 분석하고 이를 문서로 남겨 꾸준히 개선해가야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 크게 보면 이 두 가지가 핵심 필요 작업이라 생각된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트/파일/폴더 별로 md파일을 만드는 전략, 수정하며 보안을 위한 거버넌스 구축이라든지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분들이 추가적으로 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OmO란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Oh my OpenAgent, Oh My OpenCode&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 용어를 말한다. OmO는 하네스 개념을 실제 개발 환경에 적용한 도구로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 사람의 프롬프팅 역량이 중요했는데, 에이전트의 작업 사이클이 커짐에 따라 단순한 프롬프트만으로 부족해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 어떤 순서로, 어떻게, 검증 방법, 어떤 기준으로 에이전트를 분배할 것인지 등등을 개발자가 다 정해주기란 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 에이전트를 실행하는 런타임에 더 가깝다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 코드는 아래 블로그에서 잘 정리되어있는 것 같아서 읽어보면 좋을 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/3676/&quot;&gt;https://yozm.wishket.com/magazine/detail/3676/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;LazyCodex를 도입하면 무엇이 달라질까?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;개념적으로 하네스가 에이전트를 제어하는 구조라면, 실제 프로젝트에서는 그 구조가 몇 가지 파일과 디렉터리로 드러난다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;LazyCodex를 프로젝트에 설치하면 루트 경로에 /codex, AGENTS.md 등 파일이 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;LazyCodex는 &lt;/span&gt;&lt;span&gt;$init-deep&lt;/span&gt;&lt;span&gt; 명령을 통해 프로젝트를 분석하고, 루트와 하위 디렉터리에 필요한 &lt;/span&gt;&lt;span&gt;AGENTS.md&lt;/span&gt;&lt;span&gt;를 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; 이를 통해 에이전트는 단순히 현재 파일 하나만 보는 것이 아니라, 프로젝트 전체의 맥락 안에서 작업할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&amp;nbsp;명령어&lt;/b&gt;&lt;br /&gt;-&amp;nbsp;$init-deep&lt;br /&gt;-&amp;nbsp;$ulw-plan&lt;br /&gt;-&amp;nbsp;$start-work&lt;br /&gt;-&amp;nbsp;$ulw-loop&lt;br /&gt;&lt;br /&gt;&lt;b&gt;주요&amp;nbsp;스킬&lt;/b&gt;&lt;br /&gt;-&amp;nbsp;review-work&lt;br /&gt;-&amp;nbsp;remove-ai-slops&lt;br /&gt;-&amp;nbsp;frontend&lt;br /&gt;-&amp;nbsp;programming&lt;br /&gt;-&amp;nbsp;git-master&lt;br /&gt;-&amp;nbsp;visual-qa&lt;br /&gt;-&amp;nbsp;debugging&lt;br /&gt;-&amp;nbsp;refactor&lt;br /&gt;-&amp;nbsp;ulw-research&lt;br /&gt;-&amp;nbsp;LSP&lt;br /&gt;-&amp;nbsp;lsp-setup&lt;br /&gt;-&amp;nbsp;AST-grep&lt;br /&gt;-&amp;nbsp;rules&lt;br /&gt;-&amp;nbsp;comment-checker&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 스킬들에 대해서는 차차 추가해나가도록 하겠다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NhhN4/dJMcaiqgaPe/nSn1avulreTIKGhpkvCRwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NhhN4/dJMcaiqgaPe/nSn1avulreTIKGhpkvCRwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NhhN4/dJMcaiqgaPe/nSn1avulreTIKGhpkvCRwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNhhN4%2FdJMcaiqgaPe%2FnSn1avulreTIKGhpkvCRwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;288&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용해보니 토큰 사용량이 확실히 늘어나기는 했지만, 굉장히 간편해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ulw-plan을 사용해 계획을 세우고 바로 $start-work를 사용하여 명령을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 원하는 방향만 제시해줘도 생각 이상의 결과물을 뽑아내고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 아직 매우 미흡하여 사용하는데 불과하지만 이 문서에서 계속 추가해나가도록 하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/code-yeongyu/oh-my-openagent&quot;&gt;https://github.com/code-yeongyu/oh-my-openagent&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782318660086&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - code-yeongyu/oh-my-openagent: omo/lazycodex: The coding agent for tokenmaxxers;the one and only agent harness for compl&quot; data-og-description=&quot;omo/lazycodex: The coding agent for tokenmaxxers;the one and only agent harness for complex codebases. For your Codex, for your OpenCode - code-yeongyu/oh-my-openagent&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/code-yeongyu/oh-my-openagent&quot; data-og-url=&quot;https://github.com/code-yeongyu/oh-my-openagent&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cvJVNm/dJMb81f2BL6/2XA3vUVtNTWH1kP4JVREX1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bWNY9E/dJMb83kCXTz/aLpxxr8M3pE6kDKoxl0Lc1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/co8IkS/dJMb8T99IAb/RWmIzPwNNsVR6MxOntz2ZK/img.png?width=3248&amp;amp;height=2122&amp;amp;face=0_0_3248_2122&quot;&gt;&lt;a href=&quot;https://github.com/code-yeongyu/oh-my-openagent&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/code-yeongyu/oh-my-openagent&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cvJVNm/dJMb81f2BL6/2XA3vUVtNTWH1kP4JVREX1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bWNY9E/dJMb83kCXTz/aLpxxr8M3pE6kDKoxl0Lc1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/co8IkS/dJMb8T99IAb/RWmIzPwNNsVR6MxOntz2ZK/img.png?width=3248&amp;amp;height=2122&amp;amp;face=0_0_3248_2122');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - code-yeongyu/oh-my-openagent: omo/lazycodex: The coding agent for tokenmaxxers;the one and only agent harness for compl&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;omo/lazycodex: The coding agent for tokenmaxxers;the one and only agent harness for complex codebases. For your Codex, for your OpenCode - code-yeongyu/oh-my-openagent&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://channel.io/ko/blog/articles/what-is-harness-2611ddf1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://channel.io/ko/blog/articles/what-is-harness-2611ddf1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782289624925&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;하네스 엔지니어링이란? 2026년 AI 에이전트 개발의 핵심으로 떠오른 이유&quot; data-og-description=&quot;하네스(Harness)란 AI 에이전트를 안전하게 제어&amp;middot;운용하기 위한 설계 구조입니다. 2026년 하네스 엔지니어링이 주목받는 이유와 3가지 핵심 구성 요소를 알아보세요.&quot; data-og-host=&quot;channel.io&quot; data-og-source-url=&quot;https://channel.io/ko/blog/articles/what-is-harness-2611ddf1&quot; data-og-url=&quot;https://channel.io/ko/blog/articles/what-is-harness-2611ddf1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blncBX/dJMb9fZFdcv/DCI2kUpbWiYf6Itp95VKUk/img.jpg?width=700&amp;amp;height=394&amp;amp;face=0_0_700_394,https://scrap.kakaocdn.net/dn/pLClv/dJMb9cBRTQI/phxQii7hCKIzwAV1fv33bk/img.png?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840,https://scrap.kakaocdn.net/dn/ZT41y/dJMb9kmmsyE/KFcSztAGX2tqSkah6BG5XK/img.png?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840&quot;&gt;&lt;a href=&quot;https://channel.io/ko/blog/articles/what-is-harness-2611ddf1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://channel.io/ko/blog/articles/what-is-harness-2611ddf1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blncBX/dJMb9fZFdcv/DCI2kUpbWiYf6Itp95VKUk/img.jpg?width=700&amp;amp;height=394&amp;amp;face=0_0_700_394,https://scrap.kakaocdn.net/dn/pLClv/dJMb9cBRTQI/phxQii7hCKIzwAV1fv33bk/img.png?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840,https://scrap.kakaocdn.net/dn/ZT41y/dJMb9kmmsyE/KFcSztAGX2tqSkah6BG5XK/img.png?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;하네스 엔지니어링이란? 2026년 AI 에이전트 개발의 핵심으로 떠오른 이유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;하네스(Harness)란 AI 에이전트를 안전하게 제어&amp;middot;운용하기 위한 설계 구조입니다. 2026년 하네스 엔지니어링이 주목받는 이유와 3가지 핵심 구성 요소를 알아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;channel.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://litmers.com/blog/open-code%EA%B0%80-%EC%9A%94%EC%A6%98-%EB%9C%A8%EB%8A%94-%EC%9D%B4%EC%9C%A0-oh-my-opencode%EA%B0%80-%EC%9C%A0%EB%82%9C%ED%9E%88-%EC%82%AC%EB%9E%91%EB%B0%9B%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://litmers.com/blog/open-code%EA%B0%80-%EC%9A%94%EC%A6%98-%EB%9C%A8%EB%8A%94-%EC%9D%B4%EC%9C%A0-oh-my-opencode%EA%B0%80-%EC%9C%A0%EB%82%9C%ED%9E%88-%EC%82%AC%EB%9E%91%EB%B0%9B%EB%8A%94-%EC%9D%B4%EC%9C%A0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782289631209&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;OpenCode가 요즘 뜨는 이유, 그리고 'Oh My OpenCode'가 유난히 사랑받는 이유&quot; data-og-description=&quot;AI 코딩 도구가 많은데, 왜 OpenCode일까요? 그리고 왜 사람들은 Oh My OpenCode까지 쓰게 될까요? 요즘 뜨는 이유와 실전 활용 포인트를 쉽게 정리했습니다.&quot; data-og-host=&quot;litmers.com&quot; data-og-source-url=&quot;https://litmers.com/blog/open-code%EA%B0%80-%EC%9A%94%EC%A6%98-%EB%9C%A8%EB%8A%94-%EC%9D%B4%EC%9C%A0-oh-my-opencode%EA%B0%80-%EC%9C%A0%EB%82%9C%ED%9E%88-%EC%82%AC%EB%9E%91%EB%B0%9B%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; data-og-url=&quot;https://litmers.com/blog/open-code%EA%B0%80-%EC%9A%94%EC%A6%98-%EB%9C%A8%EB%8A%94-%EC%9D%B4%EC%9C%A0-oh-my-opencode%EA%B0%80-%EC%9C%A0%EB%82%9C%ED%9E%88-%EC%82%AC%EB%9E%91%EB%B0%9B%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dMo6rn/dJMb82eWYCa/0x0lYQrl2kSQ0e34OrklkK/img.png?width=1200&amp;amp;height=720&amp;amp;face=108_345_633_598,https://scrap.kakaocdn.net/dn/iRrDP/dJMb83SsYVD/LuaokmNXGeYCCym8lFKAMk/img.png?width=1200&amp;amp;height=720&amp;amp;face=108_345_633_598,https://scrap.kakaocdn.net/dn/5Per9/dJMb88Ge1ik/nAE18YeaNYqrRZhHHz2b8K/img.png?width=4064&amp;amp;height=2586&amp;amp;face=0_0_4064_2586&quot;&gt;&lt;a href=&quot;https://litmers.com/blog/open-code%EA%B0%80-%EC%9A%94%EC%A6%98-%EB%9C%A8%EB%8A%94-%EC%9D%B4%EC%9C%A0-oh-my-opencode%EA%B0%80-%EC%9C%A0%EB%82%9C%ED%9E%88-%EC%82%AC%EB%9E%91%EB%B0%9B%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://litmers.com/blog/open-code%EA%B0%80-%EC%9A%94%EC%A6%98-%EB%9C%A8%EB%8A%94-%EC%9D%B4%EC%9C%A0-oh-my-opencode%EA%B0%80-%EC%9C%A0%EB%82%9C%ED%9E%88-%EC%82%AC%EB%9E%91%EB%B0%9B%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dMo6rn/dJMb82eWYCa/0x0lYQrl2kSQ0e34OrklkK/img.png?width=1200&amp;amp;height=720&amp;amp;face=108_345_633_598,https://scrap.kakaocdn.net/dn/iRrDP/dJMb83SsYVD/LuaokmNXGeYCCym8lFKAMk/img.png?width=1200&amp;amp;height=720&amp;amp;face=108_345_633_598,https://scrap.kakaocdn.net/dn/5Per9/dJMb88Ge1ik/nAE18YeaNYqrRZhHHz2b8K/img.png?width=4064&amp;amp;height=2586&amp;amp;face=0_0_4064_2586');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OpenCode가 요즘 뜨는 이유, 그리고 'Oh My OpenCode'가 유난히 사랑받는 이유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구가 많은데, 왜 OpenCode일까요? 그리고 왜 사람들은 Oh My OpenCode까지 쓰게 될까요? 요즘 뜨는 이유와 실전 활용 포인트를 쉽게 정리했습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;litmers.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wikidocs.net/340864&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wikidocs.net/340864&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 일기</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/24</guid>
      <comments>https://to-the-goal.tistory.com/24#entry24comment</comments>
      <pubDate>Thu, 25 Jun 2026 01:35:52 +0900</pubDate>
    </item>
    <item>
      <title>밴드 플랫폼 서버 개발 일지 - 스프링 시큐리티 세팅</title>
      <link>https://to-the-goal.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;PR링크:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/l-lyun/band-platform/pull/11/changes&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/l-lyun/band-platform/pull/11/changes&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782219682573&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;[FEAT] Spring Security 기본 설정 by l-lyun &amp;middot; Pull Request #11 &amp;middot; l-lyun/band-platform&quot; data-og-description=&quot;#️⃣ 연관된 이슈 resolves: #4   작업 내용 Spring Security 의존성 추가 및 중복 validation 의존성 정리 공개 API와 인증 필요 API 경로 분리 BCrypt 기반 PasswordEncoder Bean 추가 인증 실패 시 A01 공통 에러 응&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/l-lyun/band-platform/pull/11/changes&quot; data-og-url=&quot;https://github.com/l-lyun/band-platform/pull/11&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNCeEE/dJMb86PbCpc/bFp5VgYr8SFIZkHlQb8xb1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/hoAHc/dJMb88GeTU2/ldAIgYiLbND5VskvvId1e0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/l-lyun/band-platform/pull/11/changes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/l-lyun/band-platform/pull/11/changes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNCeEE/dJMb86PbCpc/bFp5VgYr8SFIZkHlQb8xb1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/hoAHc/dJMb88GeTU2/ldAIgYiLbND5VskvvId1e0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[FEAT] Spring Security 기본 설정 by l-lyun &amp;middot; Pull Request #11 &amp;middot; l-lyun/band-platform&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;#️⃣ 연관된 이슈 resolves: #4   작업 내용 Spring Security 의존성 추가 및 중복 validation 의존성 정리 공개 API와 인증 필요 API 경로 분리 BCrypt 기반 PasswordEncoder Bean 추가 인증 실패 시 A01 공통 에러 응&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티에 대해 간단히나마 알아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무나 방대한 내용이기 때문에 codex가 어떻게 구현했고, 그게 어떻게 적용되는지 우선적으로 알아보려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티는 따로 문서 작성을 하면 좋을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 스프링 시큐리티가 무엇인지, 어떤 역할을 하는지 간단히 살펴보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이번 PR에서 어떤 작업을 했는지 살펴보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스프링 시큐리티의 역할&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;authentication, authorization, and protection against common attacks&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티는 인증, 인가, 일반적인 공격에 대한 보호 기능을 제공하는 프레임워크이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티는 &lt;b&gt;Servlet Filter&lt;/b&gt;라는 기술을 기반으로 동작하는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 요청을 보내면 서블릿 컨테이너는 요청을 처리하기 위한 여러 &lt;b&gt;필터&lt;/b&gt;가 동작하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 &lt;b&gt;DispatcherServlet&lt;/b&gt;이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터는 클라이언트 요청이 서블릿에 도달하기 전까지 여러 부가 로직을 수행할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 서블릿에 도달하지 못하도록 차단할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oLuAV/dJMcabR9ZtM/nVyovKKvx6Io55eQD39s2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oLuAV/dJMcabR9ZtM/nVyovKKvx6Io55eQD39s2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oLuAV/dJMcabR9ZtM/nVyovKKvx6Io55eQD39s2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoLuAV%2FdJMcabR9ZtM%2FnVyovKKvx6Io55eQD39s2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;189&quot; height=&quot;273&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DelegatingFilterProxy&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿 컨테이너는 필터를 관리하지만, 스프링 빈은 직접 알지 못하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기위해 스프링은 DelegatingFilterProxy라는 다리 역할의 필터를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿 컨테이너에 DelegatingFilterProxy를 등록해주면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 처리는 필터 빈에서 처리하여 스프링 DI 등 기술을 자유롭게 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfwX1D/dJMcagMNTM3/NlvHTPPLHph730zDJ3GX9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfwX1D/dJMcagMNTM3/NlvHTPPLHph730zDJ3GX9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfwX1D/dJMcagMNTM3/NlvHTPPLHph730zDJ3GX9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfwX1D%2FdJMcagMNTM3%2FNlvHTPPLHph730zDJ3GX9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;192&quot; height=&quot;305&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 서블릿 컨테이너(스프링은 톰캣)에서는 Filter 인터페이스를 구현한 객체뿐인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티 실제 보안 필터 체인은 스프링 빈으로 등록되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 서블린 컨테이너에서 직접 스프링 빈을 관리하지 않고 있기 때문에!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DelegatingFilterProxy에서 doFilter()를 실행하면 스프링 AC에서 실제 필터 빈을 찾아 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1782222472381&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
	Filter delegate = getFilterBean(someBeanName); 
	delegate.doFilter(request, response, chain); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 필터 처리를 디스패처 서블릿 도달 이전에 진행하지 않는다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자가 원하는 인증처리를 하기가 어려울 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 원리는 점차... 알아보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html?utm_source&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782222794366&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;DelegatingFilterProxy (Spring Framework 7.0.8 API)&quot; data-og-description=&quot;Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that implements the Filter interface. Supports a &amp;quot;targetBeanName&amp;quot; filter init-param in web.xml, specifying the name of the target bean in the Spring application context. web.xml will &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html?utm_source=chatgpt.com&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html?utm_source=chatgpt.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DelegatingFilterProxy (Spring Framework 7.0.8 API)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that implements the Filter interface. Supports a &quot;targetBeanName&quot; filter init-param in web.xml, specifying the name of the target bean in the Spring application context. web.xml will&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;FilterChainProxy&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티의 핵심 엔진 필터다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 여러 SecurityFilterChain을 가지고 있으며, 요청에 알맞게 보안 처리를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 나중에 구현이 어떻게 되어있는지 보면서 자세히 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 알고 있듯 URL에 따라 보안 정책 구분이 가능하고, 어떤 필터 체인을 실행할 지 설정이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Security Filters&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SecurityFilterChain&amp;nbsp;내부에 실제로 들어있는, 빈으로 등록되어있는 필터들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증, 인가, CSRF 보호 등 실제 보안 기능을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트에서 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티는 3가지 역할을 담당할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 인증(Authentication)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JwtAuthentication에서 Access Token 검증&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 인가(Authorization)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SecurityConfig의 permitAll, authenticated 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Filter Chain&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Config를 통한 보안 설정과 Jwt 필터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트에서는 세션 로그인을 사용하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT를 활용해 인증할 예정이기 때문에 기본 로그인 필터를 그래도 사용하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JwtAuthenticationFilter를 통해 토큰을 검증하는 구조로 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앞서 살펴본 SecurityFilterChain를 구현한 SecurityConfig 클래스를 먼저 살펴보자.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1782223171009&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(
		HttpSecurity http,
		JsonAuthenticationEntryPoint authenticationEntryPoint,
		JsonAccessDeniedHandler accessDeniedHandler
	) throws Exception {
		return http
        		// 세션 기반 인증 시 보호 기능, 세션 미사용
			.csrf(AbstractHttpConfigurer::disable)
        	   	// 스프링 시큐리티 기본 폼 로그인 미사용
			.formLogin(AbstractHttpConfigurer::disable)
          	 	// 요청마다 로그인 정보를 Authorization에 담아 보내는 방식 미사용
			.httpBasic(AbstractHttpConfigurer::disable)
          		.logout(AbstractHttpConfigurer::disable)
          	 	// 스프링 시큐리티 서버 세션 미사용
			.sessionManagement(session -&amp;gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
			.cors(Customizer.withDefaults())
			.exceptionHandling(exception -&amp;gt; exception
            		// 필터 체인 안에서 발생한 예외 처리 클래스
				.authenticationEntryPoint(authenticationEntryPoint)
              		// 인증은 되었으나 인가가 없는 유저 접근 처리 클래스
				.accessDeniedHandler(accessDeniedHandler)
			)
          	 	// 접근 가능 API 엔드포인트 목록, 현재 리팩토링되었음
			.authorizeHttpRequests(authorization -&amp;gt; authorization
				.requestMatchers(HttpMethod.POST, &quot;/api/users&quot;, &quot;/api/auth/signup&quot;, &quot;/api/auth/login&quot;).permitAll()
				.requestMatchers(HttpMethod.OPTIONS, &quot;/**&quot;).permitAll()
				.requestMatchers(&quot;/error&quot;).permitAll()
				.anyRequest().authenticated()
			)
			.build();
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 체이닝이 잘 되어있는데 이해를 위해 블로그에서는 주석으로 설명을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FilterChainProxy 내부에서 SecurityFilterChain을 사용해 어떤 보안 필터를 적용할지 결정한다고 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 SecurityFilterChain을 빈으로 등록하면, 스프링 시큐리티는 이 설정을 기반으로 체인을 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 요청이 들어오면 DelegatingFilterProxy는 완성된 FilterChainProxy 빈에게 요청을 위임한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 FilterChainProxy 빈은&amp;nbsp; 요청에 알맞은 SecurityFilterChain을 선택하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체인에 포함된 필터들이 순서대로 작동하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782224242606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 필터 체인 안에서 발생한 예외 처리 클래스
.authenticationEntryPoint(authenticationEntryPoint)
// 인증은 되었으나 인가가 없는 유저 접근 처리 클래스
.accessDeniedHandler(accessDeniedHandler)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 라인들에 주목해보자. 각각 예외를 처리하는 클래스들 또한 잘 만들어줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부트캠프 하면서 인증/인가 공통 에러처리를 빼먹었어서 연동 과정이 어려웠던 기억이 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 AI가 처음부터 잘 잡아주었다. 실제 어떻게 동작하는지 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1782224343489&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint {

	private final SecurityErrorResponseWriter errorResponseWriter;
    
	@Override
	public void commence(
		HttpServletRequest request,
		HttpServletResponse response,
		AuthenticationException authException
	) throws IOException {
		errorResponseWriter.write(response, ErrorCode.AUTH_REQUIRED);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1782224351464&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class JsonAccessDeniedHandler implements AccessDeniedHandler {

	private final SecurityErrorResponseWriter errorResponseWriter;

	@Override
	public void handle(
		HttpServletRequest request,
		HttpServletResponse response,
		AccessDeniedException accessDeniedException
	) throws IOException {
		errorResponseWriter.write(response, ErrorCode.AUTH_FORBIDDEN);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1782224365200&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class SecurityErrorResponseWriter {

	private final ObjectMapper objectMapper;

	public void write(HttpServletResponse response, ErrorCode errorCode) throws IOException {
		response.setStatus(errorCode.status().value());
		response.setContentType(MediaType.APPLICATION_JSON_VALUE);
		response.setCharacterEncoding(StandardCharsets.UTF_8.name());
		objectMapper.writeValue(response.getWriter(), ErrorResponse.from(errorCode));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 실패시 JsonAuthenticationEntryPoint, 인가 실패시 AccessDeniedHandler가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작동하도록 SecurityConfig에 등록해두었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 클래스에서는 SecurityErrorResponseWriter에 우리 프로젝트 공통 응답대로 에러를 내려주도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JsonAuthenticationEntryPoint &amp;rarr; 인증 실패 상황 감지 &amp;rarr; AUTH_REQUIRED 전달 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JsonAccessDeniedHandler &amp;rarr; 권한 없음 상황 감지 &amp;rarr; AUTH_FORBIDDEN 전달&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 등록하지 않으면 스프링 시큐리티의 기본 처리 방식이 작동하기 때문에 예상치 못한 에러 응답이 나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API 환경에서는 일관된 JSON 응답이 필요하기 때문에 이 2개의 클래스가 참으로 중요하다고 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 JWT 토큰 인증 방식을 적용하기 위한 기반이 잘 만들어졌다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 세팅과 공통 포멧의 영역이기 때문에 AI가 정말 잘 짜준다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(마지막 3개 클래스 롬복 어노테이션은 이후에 추가되었는데 내가 달았다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티의 내용은 정말 방대하기 때문에 오늘 글을 작성하면서도 꾸준한 학습이 필요하다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 내용은 JWT, RTR 방식으로 글을 써보려 한다.&lt;/p&gt;</description>
      <category>spring</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/23</guid>
      <comments>https://to-the-goal.tistory.com/23#entry23comment</comments>
      <pubDate>Tue, 23 Jun 2026 23:30:51 +0900</pubDate>
    </item>
    <item>
      <title>DDD 그거 그렇게 하는거 아닌데 - 2024 우아콘</title>
      <link>https://to-the-goal.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=sLG5n_pXWK0&amp;amp;t=39s&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=sLG5n_pXWK0&amp;amp;t=39s&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;DDD 그렇게 하는 거 아닌데 &amp;mdash; 완벽한 설계보다 중요한 것&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 우아콘의 &lt;b&gt;「DDD 그렇게 하는 거 아닌데」&lt;/b&gt; 영상을 보면서 DDD에 대해 다시 생각하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD라는 단어를 처음 들으면 흔히 이런 생각을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;'처음부터 바운디드 컨텍스트를 잘 나눠야 하나?'&lt;br /&gt;'도메인 모델을 완벽하게 설계해야 하나?'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시 DDD를 처음 접했을 때는 객체지향 설계, 풍부한 도메인 모델, 엔티티 책임 분리 같은 코드 구조에 더 관심이 갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번 영상을 보면서 느낀 것은, DDD는 단순히 코드를 예쁘게 짜는 방법이 아니라는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD는 결국 &lt;b&gt;비즈니스 문제를 더 잘 이해하고, 그 복잡성을 소프트웨어 안에서 다루기 위한 방법&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;완벽한 설계는 존재하지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연사는 완벽한 설계를 인정하지 않는 것부터 완벽한 설계의 시작이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 처음부터 완벽하고 풍부한 도메인 모델을 만들어낼 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 구현을 통해 충분한 도메인 지식을 얻으려 노력하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter/setter도 사용하면서 점진적으로 리팩토링해서 더 나은 모델로 개선을 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점차 도메인 자체가 빈약한 모델에서 풍부한 모델로 나아가야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD와 OOP가 얼추 비슷하고, OOP는 실제 객체 즉 도메인 기반이긴 하지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개념은 분명 다르다. DDD는 조금 더 넓은 범위에서 문제를 이해하고 풀어나가는 과정이라고 생각한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유비쿼터스 언어는 이름 짓기 문제가 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로젝트를 진행하며 각자 다른 파트 간 서로 비슷한 의미로 사용하고 있다고 생각하는 단어를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다르게 말하고 있을 수도 있다.&amp;nbsp;이때 필요한 것이 &lt;b&gt;유비쿼터스 언어&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유비쿼터스 언어는 단순히 개발자가 변수 네이밍을 편하게 하기 위한 도구가 아니고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에 참여하는 모든 직군이 같은 언어로 대화가 이뤄지게 하는 도구이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 용어사전을 만드는 것이 중요하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;바운디드 컨텍스트 안에서 꼭 유지해야하는 것은 아니며 명사/동사/ 모두 기록하고, 한/영 함께 기록한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;용어사전을 살아있는 문서로 만들기 위해 프로젝트 README.md 같은 곳에서 관리하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;용어 또한 코드 리뷰의 대상이라고 한다! 더 의미 있는 용어를 지속적으로 찾기 위해 노력해야한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인수 테스트는 고객의 언어로 시스템을 설명한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD에서 인상 깊었던 부분 중 하나는 인수 테스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인수 테스트는 개발자만을 위한 테스트가 아니다.&lt;br /&gt;고객, 개발자, 테스터가 함께 소프트웨어가 어떻게 동작해야 하는지 정의하는 테스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 테스트가 내부 구현이 아니라 &lt;b&gt;고객의 관점&lt;/b&gt;에서 작성된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 자동 채점 기능이 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;Feature: 자동 채점

Scenario: 과제 제출물을 제출할 수 있는 특정 과제에 대한 과제 제출물이 있는 경우
Given 특정 과제 제출물이 있는 경우
When 해당 과제 제출물의 예제 테스트를 실행하면
Then 마지막 커밋에 대한 자동 채점 기록을 확인할 수 있고 자동 채점이 저장된다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 작성하면 개발자는 단순히 &amp;ldquo;채점 API를 만든다&amp;rdquo;가 아니라, 사용자가 어떤 상황에서 어떤 결과를 기대하는지 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 예외 상황도 고객의 관점에서 정의할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Scenario: 특정 과제 제출물에 대한 마지막 커밋이 없어 예외가 발생하는 경우
Given 특정 과제 제출물이 있는 경우
When 해당 과제 제출물의 예제 테스트를 실행하면
Then 예외가 발생하고 자동 채점이 저장되지 않는다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성하면 테스트는 단순 검증 도구가 아니라, 도메인 지식을 공유하는 문서가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 인수 테스트는 개발자에게 '무엇을 구현해야 하는지'뿐만 아니라 '왜 그렇게 동작해야 하는지'까지 알려준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인 지식은 개발자의 책임이기도 하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상에서 가장 기억에 남았던 말 중 하나는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 월급은 비즈니스에서 나온다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 코드를 작성하는 사람이지만, 결국 그 코드가 가치를 가지는 이유는 비즈니스 문제를 해결하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어로 해결하고자 하는 영역을 비즈니스 도메인이라고 한다.&lt;br /&gt;쇼핑몰을 만든다면 도메인은 전자상거래이고, 그 안에는 상품, 전시, 주문, 결제, 정산 같은 하위 도메인이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 개발자가 전자상거래에 대한 이해 없이 단순히 API만 구현한다면, 사용자가 무엇을 원하고 어떤 문제가 중요한지 알기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 지식을 쌓기 위해서는 이런 질문을 계속 던져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 어떤 종류의 비즈니스를 하고 있는가?&lt;br /&gt;사용자는 어떤 문제를 해결하고 싶어 하는가?&lt;br /&gt;선두 업체와 경쟁 업체는 어떤 기능을 제공하는가?&lt;br /&gt;우리 서비스가 다른 서비스와 차별화되는 지점은 무엇인가?&lt;br /&gt;한정된 자원을 어디에 집중해야 하는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문들은 단순히 기획자나 PO만의 질문이 아니다.&lt;br /&gt;개발자 역시 이 질문을 이해해야 부족하거나 지나치지 않은 솔루션을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로 멋진 구조를 만드는 것도 중요하지만, 그 구조가 비즈니스 요구에 비해 과하거나 부족하다면 좋은 설계라고 보기 어렵다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하위 도메인과 집중해야 할 영역&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 비즈니스 도메인 안에는 여러 하위 도메인이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자상거래를 예로 들면 상품, 전시, 주문, 결제, 정산, 배송 등이 각각 하위 도메인이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모든 하위 도메인이 똑같이 중요한 것은 아니다.&lt;br /&gt;어떤 영역은 우리 서비스의 핵심 경쟁력이고, 어떤 영역은 외부 시스템을 활용해도 충분할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 결제가 서비스의 차별점이 아니라면 외부 PG사를 사용하는 것이 더 나을 수 있다.&lt;br /&gt;반대로 추천, 검색, 정산 구조가 서비스의 핵심 경쟁력이라면 그 부분에는 더 많은 자원을 투자해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 하위 도메인은 고정된 것이 아니다.&lt;br /&gt;비즈니스 상황, 시장 환경, 사용자의 요구에 따라 계속 바뀔 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 개발자는 &amp;ldquo;어떻게 구현할까?&amp;rdquo;만 고민하는 것이 아니라, &amp;ldquo;어디에 집중해야 할까?&amp;rdquo;도 함께 고민해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;바운디드 컨텍스트는 문제를 나누는 방식이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD에서 자주 등장하는 개념이 바운디드 컨텍스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바운디드 컨텍스트는 단순히 패키지를 나누거나 서비스를 분리하는 개념이 아니다.&lt;br /&gt;같은 용어가 같은 의미로 사용되는 경계를 정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 &amp;ldquo;주문&amp;rdquo;이라는 단어도 결제 컨텍스트에서는 결제 대상일 수 있고, 배송 컨텍스트에서는 배송 준비의 기준이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정산 컨텍스트에서는 매출 계산의 근거가 될 수도 있다. 같은 이름을 사용한다고 해서 항상 같은 의미는 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 경계를 나누는 기준은 기술 구조만이 아니라 도메인 언어와 비즈니스 의미가 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;팀이 이미 분리되어 있다면 시스템 구조도 그 조직 구조의 영향을 받는다. 이것이 콘웨이의 법칙이다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;반대로 원하는 시스템 구조가 있다면 조직 구조를 그에 맞게 설계할 수도 있다. 이것이 역콘웨이의 법칙이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 바운디드 컨텍스트는 &amp;ldquo;마이크로서비스로 나눌 것인가?&amp;rdquo;의 문제가 아니라, &lt;b&gt;문제를 어떤 단위로 이해하고 해결할 것인가&lt;/b&gt;의 문제다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;조직 간 문제 해결은 기술만으로 되지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트를 나누면 자연스럽게 조직 간 협업 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 어떻게 설계할지 함께 논의할 수도 있고, 한 조직이 일방적으로 결정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 강력한 중앙 통제가 발생하기도 한다. 이 과정은 기술적인 문제이면서 동시에 정치적인 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 흔히 기술적으로 올바른 구조를 만들면 문제가 해결될 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 프로젝트에서는 각 팀의 책임, 일정, 우선순위, 이해관계가 모두 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 좋은 설계는 코드 안에서만 만들어지지 않는다. 회의, 문서, API 논의, 용어 정의, 책임 범위 조율 속에서 함께 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD가 개발자만의 방법론이 아닌 이유도 여기에 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;은총알은 없다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라이언 커니핸은 복잡성을 제어하는 것이 컴퓨터 프로그래밍의 본질이라고 했다.&lt;br /&gt;프레드 브룩스 역시 소프트웨어 개발에는 은총알이 없다고 말했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD도 마찬가지다. DDD를 적용한다고 해서 모든 문제가 해결되지는 않는다.&lt;br /&gt;바운디드 컨텍스트를 나눈다고 해서 복잡성이 사라지는 것도 아니고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풍부한 도메인 모델을 만든다고 해서 비즈니스 이해가 자동으로 생기는 것도 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 본질적 복잡성과 우발적 복잡성을 구별하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 자체가 가진 복잡성은 피할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 용어가 정리되지 않아 생기는 혼란, 외부 시스템 모델이 내부 도메인을 오염시키는 문제,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 책임이 하나의 객체나 하나의 서비스에 몰리는 문제는 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD는 이런 우발적 복잡성을 줄이고, 본질적 복잡성을 더 잘 다루기 위한 도구라고 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋은 DDD는 &amp;ldquo;DDD에서는 이렇게 해야 한다&amp;rdquo;로 시작하지 않는다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;좋은 DDD는 프로젝트가 실제로 겪고 있는 문제에서 시작한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로젝트 진행하면서 도메인 주도 개발, DDD 등 개념을 맛이라도 보기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티에서 자기 자신의 책임을 지는 로직을 많이 구현해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 40분짜리 영상에도 내가 모르는 부분들이 너무나 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 주로 개발자끼리, 적어도 어느정도 베이스 지식이 있는 이들과 소통했기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어사전이라는 개념이 항상 필요가 없었는데 상상해보니 정말 소통에 필수적이라는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD는 개발 방법론을 넘어 모두가 같은 문제를 바라보는 과정의 조율 도구라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 과정에서 변화에 맞서 싸우기보다 변화에 대응할 수 있는 구조를 만들고 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD는 정답지가 아니라, 이런 질문을 계속 던지게 해주는 도구라고 느껴지게 된 계기이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 DDD 관련한 공부도 꾸준히 필요할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>기술 아티클, 유튜브</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/22</guid>
      <comments>https://to-the-goal.tistory.com/22#entry22comment</comments>
      <pubDate>Fri, 19 Jun 2026 23:12:40 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 토비의 스프링 Vol.1 2장 - 테스트 (2)</title>
      <link>https://to-the-goal.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://to-the-goal.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://to-the-goal.tistory.com/20&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1781788688954&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring] 토비의 스프링 Vol.1 2장 - 테스트 (1)&quot; data-og-description=&quot;2장을 시작하며 필자는 스프링의 가장 중요한 가치를 객체지향과 테스트라고 말하고 있다.앞서 1장에서 IoC/DI는 오브젝트의 설계, 생성, 관계, 사용에 관한 기술이며이를 쉽게 사용할 수 있게 해&quot; data-og-host=&quot;to-the-goal.tistory.com&quot; data-og-source-url=&quot;https://to-the-goal.tistory.com/20&quot; data-og-url=&quot;https://to-the-goal.tistory.com/20&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZAxIe/dJMb9efnao2/Jp2LRseEK4vgQmmleYu20K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bbxNwo/dJMb9jOv3le/ROhQykT4J3Wiw1P1oN7b3K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://to-the-goal.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://to-the-goal.tistory.com/20&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZAxIe/dJMb9efnao2/Jp2LRseEK4vgQmmleYu20K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bbxNwo/dJMb9jOv3le/ROhQykT4J3Wiw1P1oN7b3K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring] 토비의 스프링 Vol.1 2장 - 테스트 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2장을 시작하며 필자는 스프링의 가장 중요한 가치를 객체지향과 테스트라고 말하고 있다.앞서 1장에서 IoC/DI는 오브젝트의 설계, 생성, 관계, 사용에 관한 기술이며이를 쉽게 사용할 수 있게 해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;to-the-goal.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.3.5 테스트 코드 개선&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 코드만이 리팩토링 대상이 아니고, &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;테스트 코드 또한 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;언제든지 내부 구조와 설계를 개선해 더 깔끔하고 이해하기 쉽고, 변경이 용이한 코드를 만들 필요가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;@Before&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(JUnit5에서 @BeforeEach로 변경되었다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781788779453&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BeforeEach
public void setup() {
	ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
	userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 메소드의 로컬변수였던 AC를 테스트 메소드에서 접근할 수 있도록 인스턴스 변수로 변경하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setUp()에서 getBean()이 돌려주는 오브젝트를 userDao에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 테스트는 잘 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 프레임워크에 대해서 다시 한 번 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크는 스스로 제어권을 가지며, 주도적으로 동작하고, 개발자가 만든 코드는 프레임워크에 의해 수동적으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드만으로는 실행 흐름이 잘 보이지 않기 때문에 프레임워크가 어떻게 사용하는지 잘 이해하고 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit이 하나의 테스트 클래스를 가져와 테스트를 수행하는 방식은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.&lt;/li&gt;
&lt;li&gt;테스트 클래스의 오브젝트를 하나 만든다.&lt;/li&gt;
&lt;li&gt;@BeforeEach가 붙은 메소드가 있으면 실행한다.&lt;/li&gt;
&lt;li&gt;@Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.&lt;/li&gt;
&lt;li&gt;@AfterEach가 붙은 메소드가 있으면 실행한다.&lt;/li&gt;
&lt;li&gt;나머지 테스트 메소드에 대해 2~5번을 반복한다.&lt;/li&gt;
&lt;li&gt;모든 테스트 결과를 종합해서 돌려준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 정말 복잡하지만 이러한 7단계를 거쳐 테스트가 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 기억해야할 것은 테스트 메소드를 실행할 때마다 테스트 클래스의 오브젝트를 새로 만든다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 만들어진 테스트 클래스 오브젝트는 하나의 테스트 메소드를 사용하고 나면 버려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 새로 만들면 비효율적인 것이 아닌가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 비효율로 인해 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 보장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 인스턴스 변수도 부담 없이 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 테스트 메소드 일부에서 공통적으로 사용되는 코드는 어떻게 분리해야할까?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일반적인 메소드 추출 방법을 사용해 메소드를 분리, 테스트 메소드에서 직접 호출&lt;/li&gt;
&lt;li&gt;공통적인 특징을 지닌 테스트 메소드를 모아 별도 클래스로 분리&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;픽스처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처라고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 픽스처는 여러 테스트에서 반복적으로 사용되기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@BeforeEach 메소드를 이용해 생성해두면 편리하다.&lt;/p&gt;
&lt;pre id=&quot;code_1781789150310&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private IndependentUserDao userDao;

private User user1;
private User user2;
private User user3;

@BeforeEach
public void setup() {
	ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
	userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);
		
	this.user1 = new User(&quot;aa&quot;, &quot;김김&quot;, &quot;spring1&quot;);
	this.user2 = new User(&quot;bb&quot;, &quot;이이&quot;, &quot;spring2&quot;);
	this.user3 = new User(&quot;cc&quot;, &quot;박박&quot;, &quot;spring3&quot;);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 테스트 코드에서는 User user1 = new User.. 메소드 로컬 변수를 반복적으로 선언했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 활용하면 로컬 변수를 따로 선언하지 않고 따로 깔끔하게 테스트 코드 작성이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.4 스프링 테스트 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 코드는 테스트 클래스 안에서 테스트 메소드가 n개 존재하면 ApplicationContext가 n번 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금이야 단순히 학습을 위해 Bean의 가짓수가 몇 없지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean이 많아지고 복잡해진다면 AC 생성에 적지않은 시간이 걸릴 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC가 만들어질 때는 모든 싱글톤 빈 오브젝트를 초기화한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC가 초기화될 때 어떤 빈 오브젝트는 자체적인 초기화 작업,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 빈은 독자적으로 많은 리소스 할당, 독립적 스레드를 띄우기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 가능한 독립적으로 새로운 오브젝트를 만들어 사용하는 것이 원칙이나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC처럼 생성에 많은 시간과 자원이 소모되는 경우 테스트 전체가 공유하는 오브젝트를 만들기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC는 초기화 이후 내부 상태가 바뀌는 일은 거의 없기 때문에 여러 테스트 공유가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 문제는 JUnit이 테스트 클래스 오브젝트를 매번 새로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 오브젝트 레벨에서 AC를 유지한다면 의미가 없어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 스태틱 필드에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메소드에서 AC를 만들어 스태틱 변수에 저장해두고 테스트 메소드에서 사용할 수 있으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 직접 제공하는 AC 테스트 지원 기능을 사용하는 것이 더 편리하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.4.1 테스트를 위한 애플리케이션 컨텍스트 관리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링 테스트 컨텍스트 프레임워크 적용&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781789484034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 책 예제와 어노테이션은 다르다.
@SpringJUnitConfig(DaoFactory.class)
public class UserDaoTest {

	@Autowired
	private ApplicationContext context;

	private IndependentUserDao userDao;

	private User user1;
	private User user2;
	private User user3;

	@BeforeEach
	public void setup() {

	this.userDao = this.context.getBean(&quot;userDao&quot;, IndependentUserDao.class);

	...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC를 @Autowired를 이용해 아주 단순하게 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이렇게 리팩토링한 상태에서, 아래 코드를 실행하면&lt;/p&gt;
&lt;pre id=&quot;code_1781789556499&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BeforeEach
public void setup() {

	System.out.println(this.context);
	System.out.println(this);
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qr2vH/dJMcahrkU9b/1BVcz3SP3hRlZvz0tiPFaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qr2vH/dJMcahrkU9b/1BVcz3SP3hRlZvz0tiPFaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qr2vH/dJMcahrkU9b/1BVcz3SP3hRlZvz0tiPFaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQr2vH%2FdJMcahrkU9b%2F1BVcz3SP3hRlZvz0tiPFaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;380&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콘솔을 확인하면 context의 참조값은 매번 동일하고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;this, 즉 테스트 오브젝트의 참조값은 달라 매 실행마다 새롭게 생성된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링의 JUnit 확장 기능은 테스트가 실행되기 전 한 번만 애플리케이션 컨텍스트를 만들어두고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 오브젝트가 만들어질 때마다 특별한 방법을 이용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 컨텍스트 자신을 테스트 오브젝트의 특정 필드에 주입해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 클래스의 컨텍스트 공유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 테스트 클래스가 모두 같은 설정 파일을 가진 애플리케이션 컨텍스트를 사용한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유하게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 수백 개의 테스트 클래스가 있어도 같은 설정 파일을 사용한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 전체에 걸쳐 한 개의 애플리케이션 컨텍스트만 만들어 사용ㅎ나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 테스트 성능 대폭 향상 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Autowired&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 DI에 사용되는 특별한 애노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Autowired가 붙은 인스턴스 변수가 있으면 테스트 컨텍스트 프레임워크는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다. 타입과 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도 DI 설정 필드 타입 정보를 이용해 빈을 자동으로 가져올 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방법을 타입에 의한 자동 와이어링이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 본 코드에서는 빈 오브젝트가 아니라 ApplicationContext에 DI를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 ApplicationContext는 초기화할 때 자기 자신도 빈으로 등록하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 컨텍스트 내에 AC 타입의 빈이 존재하고, 그러므로 DI도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1781791325784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringJUnitConfig(DaoFactory.class)
public class UserDaoTest {
	
	@Autowired
	private IndependentUserDao userDao;

	private User user1;
	private User user2;
	private User user3;

	@BeforeEach
	public void setup() {
		this.user1 = new User(&quot;aa&quot;, &quot;김김&quot;, &quot;spring1&quot;);
		this.user2 = new User(&quot;bb&quot;, &quot;이이&quot;, &quot;spring2&quot;);
		this.user3 = new User(&quot;cc&quot;, &quot;박박&quot;, &quot;spring3&quot;);
	}
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 AC를 DI 받아서 DL 방식으로 UserDao를 가져올 때보다 테스트 코드를 더 깔끔하게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Autowired&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 같은 타입의 빈이 두 개 이상 있는 경우 타입만으로는 어떤 빈을 가져오는지 결정할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;타입으로 가져올 빈 하나를 선택할 수 없는 경우에는 변수의 이름과 같은 이름의 빈을 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 안되면 예외가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.4.2 DI와 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'구체적인 구현 방식이 절대 바뀌지 않을 것인데 DI를 활용해야 하나?'&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다&lt;/li&gt;
&lt;li&gt;클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하면 다른 차원의 서비스 기능을 도입 가능&lt;/li&gt;
&lt;li&gt;효율적인 테스트를 손쉽게 만들기 위해서 DI 적용 필요 -&amp;gt; 테스트 코드는 가능한 작은 단위로 독립적으로 실행 필요&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 코드에 의한 DI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 서버에서 작동하고 있는 DB에 deleteAll()을 실행한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 테스트 클래스에서 스프링 테스트 컨텍스트 프레임워크를 적용했다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 컨텍스트는 테스트 과정에서 딱 1개만 만들어지고 모든 테스트에서 공유해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 어떤 테스트에서는 빈 의존관계 변경이 필요할 수도 있는데, 나머지 테스트에서도 오염이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; @DirtiesContext 애노테이션으로 해결 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링의 테스트 컨텍스트 프레임워크에게 해당 클래스 테스트에서 애플리케이션 컨텍스트의 상태를 변경함을 알리고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 메소드(클래스)를 실행한 이후에는 새로운 AC를 만들어 다음 테스트에서 활용할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트를 위한 별도 DI 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2장 초반부에서 살펴보았듯 테스트 코드에서 직접 수동 DI를 하면 단점이 많아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI를 잘 적용해놓았기 때문에 테스트 환경에 적합한 구성을 가진 설정 파일을 활용해 테스트를 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI를 이용한 테스트 방법 선택&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI를 테스트에 이용하는 세 가지 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 스프링 컨테이너 없이 테스트할 수 있는 방법을 가장 우선적으로 고려하자. 속도가 빠르고 가장 간결하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때때로 복잡한 의존관계를 갖고 있는 오브젝트를 테스트해야한다면 스프링 DI를 사용하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에 따른 차이가 있기 때문에 다른 설정 파일을 활용하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외적으로 의존관계를 강제로 구성해서 테스트해야할 경우가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 컨텍스트에서 DI 받은 오브젝트에 테스트 코드에서 수동 DI를 진행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@DirtiesContext 애노테이션을 통해 새로 컨텍스트를 생성해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.5 학습 테스트로 배우는 스프링&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어 제공한 라이브러리 등에 대해서도 테스트를 작성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;학습 테스트&lt;/b&gt;라고 한다. 학습 테스트의 목표는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용할 API나 프레임워크의 기능을 테스트로 보면서 사용 방법을 익힌다.&lt;/li&gt;
&lt;li&gt;기능의 검증보다 자신이 사용할 기술이나 기능에 대해 이해도를 검증한다.&lt;/li&gt;
&lt;li&gt;정확하고 빠르게 사용법을 익힌다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 학습 테스트는 테스트 대상보다 테스트 코드 자체에 관심을 갖고 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.5.1 학습 테스트의 장점&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.&lt;/li&gt;
&lt;li&gt;학습 테스트 코드를 개발 중에 참고할 수 있다.&lt;/li&gt;
&lt;li&gt;프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.&lt;/li&gt;
&lt;li&gt;테스트 작성에 대한 좋은 훈련이 된다.&lt;/li&gt;
&lt;li&gt;새로운 기술을 공부하는 과정이 즐거워진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 테스트를 직접 살펴본다면 래퍼런스 문서에는 미처 설명되어있지 않았던 정보가 많이 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 작성 방법에 대한 좋은 팁을 얻을 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.5.2 학습 테스트 예제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. JUnit 테스트 오브젝트 예제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit은 테스트 메소드를 수행할 때마다 새로운 오브젝트를 만든다고 했다. 이를 테스트해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781792105758&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static JUnitTest testObject;

@Test
void test1() {
	Assertions.assertThat(this).isNotSameAs(testObject);
	testObject = this;
}

@Test
void test2() {
	Assertions.assertThat(this).isNotSameAs(testObject);
	testObject = this;
}

@Test
void test3() {
	Assertions.assertThat(this).isNotSameAs(testObject);
	testObject = this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 필드에 만들어진 오브젝트와 자신을 비교해서 같지 않다는 사실을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 여기서 1 -&amp;gt; 2 -&amp;gt; 3번 순서로 실행되었다고 할때, 1번 3번이 같을 수도 있음을 테스트해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781792201112&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static Set&amp;lt;JUnitTest2&amp;gt; testObjects = new HashSet&amp;lt;&amp;gt;();

@Test
void test1() {
	Assertions.assertThat(testObjects).doesNotContain(this);
	testObjects.add(this);
}

@Test
void test2() {
	Assertions.assertThat(testObjects).doesNotContain(this);
	testObjects.add(this);
}

@Test
void test3() {
	Assertions.assertThat(testObjects).doesNotContain(this);
	testObjects.add(this);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 변수로 테스트 오브젝트를 저장할 수 있는 컬렉션을 만들어 아이템이 셋에 들어있는지 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 스프링 테스트 컨텍스트 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit과 반대로 테스트용 애플리케이션 컨텍스트는 1개만 만들어지며 모든 테스트에서 공유된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781792282540&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
ApplicationContext context;

static Set&amp;lt;ApplicationContextTest&amp;gt; testObjects = new HashSet&amp;lt;&amp;gt;();
static ApplicationContext contextObject = null;

@Test
public void test1() {
	Assertions.assertThat(testObjects).doesNotContain(this);
	testObjects.add(this);
	Assertions.assertThat(contextObject == null || this.context == contextObject).isTrue();
	contextObject = this.context;
}

@Test
public void test2() {
	Assertions.assertThat(testObjects).doesNotContain(this);
	testObjects.add(this);
	Assertions.assertThat(this.context == contextObject).isTrue();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(책 예제와는 조금 다르다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 코드에서는 test1()이 먼저 실행되면 두 개의 테스트가 모두 성공하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test2() 메소드가 먼저 실행되면 실패가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test1()이 먼저 실행되면, contextObject가 null이기 때문에 테스트가 통과한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 통과 이후, contextObject에 현재 만들어져있는 ApplicationContext 참조값이 대입된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후에 test2()가 실행되면 같다는게 판별이 나서 성공한다. 하지만 반대 순서면 null이라서 테스트가 실패한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 순서에 종속되지 않고 일관되어야 하지만 이해를 위해 코드를 변경했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.5.3 버그 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그 테스트란 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그 테스트는 일단 실패하도록 만들어야하며 버그 테스트가 성공할 수 있도록 애플리케이션 코드를 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버그 테스트의 필요성&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트의 완성도를 높여준다&lt;/li&gt;
&lt;li&gt;버그의 내용을 명확하게 분석하게 해준다&lt;/li&gt;
&lt;li&gt;테스트코드를 만들며 오류를 발생시키는 값의 범위가 어떤 것인지 분석해 기회가 생긴다.&lt;/li&gt;
&lt;li&gt;기술적인 문제를 해결하는 데 도움이 된다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드 PR&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/l-lyun/study/pull/9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/l-lyun/study/pull/9&lt;/a&gt;&lt;/p&gt;</description>
      <category>spring</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/21</guid>
      <comments>https://to-the-goal.tistory.com/21#entry21comment</comments>
      <pubDate>Thu, 18 Jun 2026 23:22:32 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 토비의 스프링 Vol.1 2장 - 테스트 (1)</title>
      <link>https://to-the-goal.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2장을 시작하며 필자는 스프링의 가장 중요한 가치를 객체지향과 테스트라고 말하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 1장에서 IoC/DI는 오브젝트의 설계, 생성, 관계, 사용에 관한 기술이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 쉽게 사용할 수 있게 해주는 것이 스프링이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링을 활용함에 있어 복잡한 서버 애플리케이션을 개발하는데 필요한 도구가 두 가지가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하나는 객체지향이며 하나는 테스트이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션은 계속 변하고 발전하고 복잡해진다. 개발자는 이에 유연하게 대응할 수 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 방법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 확장과 변화를 고려한 객체지향적 설계, 그에 대해 효과적으로 효과적으로 담아낼 수 있는 IoC/DI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 테스트를 통해 코드에 확신을 가져 변화에 유연하게 대처할 수 있도록&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 테스트를 통해 다양한 기술을 활용하고, 이해하고, 검증하며 실전에 적용하는 방법을 익힐 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.1 UserDaoTest 다시보기&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1.1 테스트의 유용성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 지속적으로 유지보수하고 리팩토링하며 온전하게 동작할 수 있도록 보장하는 방법?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 코드를 지속적으로 들여다보며 시뮬레이션? 확신 가능한가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트란 내가 예상하고 의도했던 코드가 정확히 동작하는지를 확인해서 만든 코드를 확신할 수 있게 해주는 작업이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1.2 UserDaoTest의 특징&lt;/b&gt;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781697060155&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {

		// 아직까지는 기존에 DaoFactory가 더 깔끔한 것 같음
		// 기능적으로도 차이가 없다.
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
		IndependentUserDao userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);

		User user = new User();
		user.setId(&quot;saddip&quot;);
		user.setName(&quot;김도현&quot;);
		user.setPassword(&quot;123456&quot;);

		userDao.add(user);

		System.out.println(user.getId() + &quot; 등록 성공&quot;);

		User user2 = userDao.get(user.getId());
		System.out.println(&quot;user2 = &quot; + user2.getName());

		CountingConnectionMaker ccm = context.getBean(
			&quot;connectionMaker&quot;,
			CountingConnectionMaker.class
		);


		User user3 = userDao.get(user.getId());
		System.out.println(&quot;user2 = &quot; + user3.getName());

		System.out.println(&quot;ccm.getCount() = &quot; + ccm.getCount());

	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드의 기능을 정리해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 손쉽게 실행 가능한 main() 메소드 이용&lt;/li&gt;
&lt;li&gt;테스트 대상 UserDao의 오브젝트를 가져와 메소드 호출&lt;/li&gt;
&lt;li&gt;테스트에 사용할 입력값을 직접 코드에서 만들어 넣어줌&lt;/li&gt;
&lt;li&gt;테스트 결과를 콘솔에 출력&lt;/li&gt;
&lt;li&gt;각 단계 작업이 에러 없이 끝나면 콘솔에 성공 메시지 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹을 통한 DAO 테스트 방법의 문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹을 통해 실제 DAO를 테스트하려면 선행되어야하는 서비스, 컨트롤러, 뷰 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 레이어의 기능을 다 만들어야 테스트가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 설령 진행하더라도 어떤 부분에서 에러가 나는지 찾아야할 수고도 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작은 단위의 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드의 단위는 작을수록 좋다. 다른 코드를 신경쓰지 않고, 참여하지도 않으며 테스트가 동작할 수 있으면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 작은 단위 코드에 대해 테스트를 수행한 것을 단위테스트라고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 과정에서 DB가 사용된다면 단위테스트가 아니라고 하는 사람들도 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 DB를 테스트가 관장하고 있다면 단위테스트가 맞다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 보다 큰 과정을 하나로 묶어 테스트하는 것도 필요하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 테스트만 진행한다면 에러 발생시 문제 원인 파악이 매우 어려워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위별로 테스트를 진행했다면 디버깅이 훨씬 수월할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 설계하고 만든 코드가 의도대로 동작하는지 개발자가 확인하기 위한 테스트라는 뜻으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자, 프로그래머 테스트라고 부르기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자동 수행 테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 보았던 UserDaoTest는 테스트할 데이터가 코드에 존재하고, 테스트 작업 역시 코드를 통해 자동으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화된 테스트는 매우 빠르기 때문에 자주 수행해도 부담이없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아직 main 메소드를 활용해 애플리케이션과 같이 혼재하고 있어 위치가 애매하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 서비스에서 테스트코드 한 줄 고치면 어떠한 여파가 미칠지 아무도 모른다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1.3 UserDaoTest의 문제점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 테스트의 문제점은 두 가지 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수동 확인 작업의 번거로움 - 콘솔 출력을 사람이 일일이 확인해야함&lt;/li&gt;
&lt;li&gt;실행 작업의 번거로움 - main 메소드를 수백개 만들어 수백번 실행하고 정리해야함&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.2 UserDaoTest 개선&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.1 테스트 검증의 자동화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트의 결과에는 두가지 종류가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트 성공&lt;/li&gt;
&lt;li&gt;테스트 실패&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 실패에서도 두가지 종류가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트 진행되는 동안 에러 발생 -&amp;gt; &lt;b&gt;테스트 에러&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;결과값이 기대값과 다름 -&amp;gt; &lt;b&gt;테스트 실패&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 에러는 콘솔로 체크가 가능하지만 테스트 실패는 별도의 확인 작업과 그 결과가 있어야한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정 전 테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781697783387&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;System.out.println(&quot;user2 = &quot; + user2.getName());
System.out.println(&quot;user2.password = &quot; + user2.getPassword());
System.out.println(&quot;user2.id = &quot; + user2.getId());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정 후 테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781697792928&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(!user.getName().equals(user2.getName())) {
	System.out.println(&quot;테스트 실패 (name)&quot;);
}
else if (!user.getPassword().equals(user2.getPassword())) {
	System.out.println(&quot;테스트 (password)&quot;);
}
else {
	System.out.println(&quot;조회 테스트 성공&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 콘솔 출력을 확인해야했는데 수정 후에는 '조회 테스트 성공'이라는 키워드를 기다리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패시에도 어떤 부분에서 실패가 났는지 로그가 확인이 가능해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름 형식을 갖춘 테스트가 되었고, 이제 코드의 기능을 모두 점검할 수 있는 포괄적인 테스트를 만들어냈다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이후에는 과감한 수정을 하고 나서라도 테스트를 모두 돌리면 안심할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.2 테스트의 효율적인 수행과 결과 관리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 효율적으로 수행하기에는 main() 메소드로는 분명한 한계가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 간단히 실행시키고, 결과를 종합하여 볼 수 있으며 테스트가 실패한 곳을 빠르게 찾을 수 있는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 갖춘 테스트 도구와 그에 맞는 테스트 작성 방법이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JUnit 테스트로 전환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit을 활용함으로써 프레임워크의 동작 원리인 &lt;b&gt;제어의 역전&lt;/b&gt;을 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1장에서 살펴보았듯, 프레임워크는 개발자가 만든 클래스에 대한 제어 권한을 넘겨받아 주도적으로 애플리케이션 흐름을 제어한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 만든 클래스의 오브젝트를 생성하고 실행하는 일은 프레임워크에 의해 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 메소드 전환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 만들었던 main() 메소드는 제어권을 직접 갖고 있으니, 이 테스트 코드를 일반 메소드로 옮겨야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 만들 테스트 코드는 JUnit 프레임워크가 요구하는 조건 두 가지를 따라야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메소드가 public으로 선언되어야함&lt;/li&gt;
&lt;li&gt;메소드에 @Test라는 애노테이션을 붙여줘야함&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 1번은 JUnit5가 도입되며 따로 접근제어자를 선언하지 않는 default를 사용해도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;검증 코드 전환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 if/else 문장을 JUnit이 제공하는 방법을 이용해서 전환해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781698730124&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {
	@Test
	public void addAndGet() throws SQLException, ClassNotFoundException {
		ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
		IndependentUserDao userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);

		User user = new User();
		user.setId(&quot;asdf&quot;);
		user.setName(&quot;김도현&quot;);
		user.setPassword(&quot;asdf&quot;);

		userDao.add(user);
		User user2 = userDao.get(user.getId());

		Assertions.assertThat(user.getId()).isEqualTo(user2.getId());
		Assertions.assertThat(user.getName()).isEqualTo(user2.getName());
		Assertions.assertThat(user.getPassword()).isEqualTo(user2.getPassword());
        
        // 책 예제 코드 JUnit4 환경에서는 아래와 같이 사용한다.
        // assertThat(user.getName(), is(user.getName()));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.3 개발자를 위한 테스팅 프레임워크 JUnit&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 없는 스프링은 의미가 없기 때문에 JUnit 테스트 작성 방법과 실행 방법은 알고있자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.3.2 테스트 결과의 일관성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 JUnit을 적용해 깔끔한 테스트 코드를 만들었고 편리하게 실행할 수 있는 IDE 환경을 체감했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아직 테스트 결과가 외부에 의존적이라는 단점이 존재한다. 이로 인해 테스트를 수행하기 이전 상태로 만들어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점차적으로 도입할 예정인데, 지금은 UserDao에서 &lt;b&gt;deleteAll()&lt;/b&gt;, &lt;b&gt;getCount()&lt;/b&gt; 두 가지 메소드를 추가해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 메소드는 독립적인 자동 실행 테스트를 만들기가 애매하기 때문에 기존 &lt;b&gt;addAndGet() &lt;/b&gt;테스트를 확장해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동일한 결과를 보장하는 테스트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781698971455&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void addAndGet() throws SQLException, ClassNotFoundException {
	ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
	IndependentUserDao userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);

	userDao.deleteAll();
	assertThat(userDao.getCount()).isEqualTo(0);

	User user = new User();
	user.setId(&quot;asdf&quot;);
	user.setName(&quot;김도현&quot;);
	user.setPassword(&quot;asdf&quot;);

	userDao.add(user);
	assertThat(userDao.getCount()).isEqualTo(1);
	User user2 = userDao.get(user.getId());

	assertThat(user.getId()).isEqualTo(user2.getId());
	assertThat(user.getName()).isEqualTo(user2.getName());
	assertThat(user.getPassword()).isEqualTo(user2.getPassword());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 id 값을 삭제하지 않고 반복적으로 실행해도 동일한 결과를 보장하는 테스트 코드가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단위 테스트는 코드가 바뀌지 않는다면 실행할 때마다 동일한 테스트 결과를 보장할 수 있어야한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 테스트 실행 순서, 데이터 외부 환경 등에 의존적이지 않아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.3.3 포괄적인 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 레코드가 1개만 저장될 때 상황을 테스트 해보았는데, 2개 이상일 때는 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 검증을 하고 넘어가야한다. getCount()라는 테스트 메소드를 하나 더 만들어 UserDaoTest 전체 코드를 살펴보고 지나가자.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1781699153339&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {
	@Test
	public void addAndGet() throws SQLException, ClassNotFoundException {
		ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
		IndependentUserDao userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);

		userDao.deleteAll();
		assertThat(userDao.getCount()).isEqualTo(0);

		User user = new User();
		user.setId(&quot;asdf&quot;);
		user.setName(&quot;김도현&quot;);
		user.setPassword(&quot;asdf&quot;);

		userDao.add(user);
		assertThat(userDao.getCount()).isEqualTo(1);
		User user2 = userDao.get(user.getId());

		assertThat(user.getId()).isEqualTo(user2.getId());
		assertThat(user.getName()).isEqualTo(user2.getName());
		assertThat(user.getPassword()).isEqualTo(user2.getPassword());
	}

	@Test
	public void count() throws SQLException, ClassNotFoundException {
		ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
		IndependentUserDao userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);
		User user1 = new User(&quot;aa&quot;, &quot;김김&quot;, &quot;spring1&quot;);
		User user2 = new User(&quot;bb&quot;, &quot;이이&quot;, &quot;spring2&quot;);
		User user3 = new User(&quot;cc&quot;, &quot;박박&quot;, &quot;spring3&quot;);

		userDao.deleteAll();
		assertThat(userDao.getCount()).isEqualTo(0);

		userDao.add(user1);
		assertThat(userDao.getCount()).isEqualTo(1);

		userDao.add(user2);
		assertThat(userDao.getCount()).isEqualTo(2);

		userDao.add(user3);
		assertThat(userDao.getCount()).isEqualTo(3);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점은 테스트 두가지가 어떠한 순서로 실행되는지 보장할 수는 없으니, 순서에 영향을 받지 않아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;addAndGet() 테스트 보완&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781699191804&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void addAndGet() throws SQLException, ClassNotFoundException {
	ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
	IndependentUserDao userDao = context.getBean(&quot;userDao&quot;, IndependentUserDao.class);

	User user1 = new User(&quot;aa&quot;, &quot;김김&quot;, &quot;spring1&quot;);
	User user2 = new User(&quot;bb&quot;, &quot;이이&quot;, &quot;spring2&quot;);

	userDao.deleteAll();
	assertThat(userDao.getCount()).isEqualTo(0);

	userDao.add(user1);
	userDao.add(user2);

	assertThat(userDao.getCount()).isEqualTo(2);

	// id를 활용해 user를 가져오는 테스트 보강
	User userGet1 = userDao.get(user1.getId());
	assertThat(userGet1.getId()).isEqualTo(user1.getId());
	assertThat(userGet1.getName()).isEqualTo(user1.getName());

	User userGet2 = userDao.get(user2.getId());
	assertThat(userGet2.getId()).isEqualTo(user2.getId());
	assertThat(userGet2.getName()).isEqualTo(user2.getName());

	// assertThat(userDao.getCount()).isEqualTo(1);
	// User user2 = userDao.get(user.getId());
	//
	// assertThat(user.getId()).isEqualTo(user2.getId());
	// assertThat(user.getName()).isEqualTo(user2.getName());
	// assertThat(user.getPassword()).isEqualTo(user2.getPassword());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석 부분에서 테스트를 보강했다. 아직 완벽한 테스트라 할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 get() 메소드에 전달된 id값에 해당하는 유저가 없다면?&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;null과 같은 특별한 값을 리턴&lt;/li&gt;
&lt;li&gt;해당하는 정보를 찾을 수 없다고 예외를 던지는 것&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 2번 방법을 사용해서 테스트를 진행해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 id에 해당하는 정보가 없다는 의미를 가진&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링의 EmptyResultDataAccessException 예외를 이용해 볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면 예외처리를 어떻게 테스트 코드로?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit은 예외 조건 테스트를 위한 특별한 방법을 제공하고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781699530456&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;assertThatThrownBy(() -&amp;gt; userDao.get(&quot;unknown&quot;))
	.isInstanceOf(EmptyResultDataAccessException.class);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit4에서는 @Test(expected = Exception.class)로 캐치가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit5에서는 불가한 것 같아서 위와 같은 코드로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아직 애플리케이션 단의 코드는 수정을 하지 않아서 실패하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트를 성공시키기 위한 코드 수정&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781699619432&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(rs.next()) {
	user = new User();
	user.setId(rs.getString(&quot;id&quot;));
	user.setName(rs.getString(&quot;name&quot;));
	user.setPassword(rs.getString(&quot;password&quot;));
}

rs.close();
ps.close();
c.close();

if (user == null)
	throw new EmptyResultDataAccessException(1);

return user;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User의 get() 메소드에서 id 값으로 찾아내지 못한다면 예외를 던지도록 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 테스트 메소드가 성공하고 있다. -&amp;gt; 기존 기능에도 영향을 주지 않음이 확실하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포괄적인 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC를 자주 접한 개발자라면 코드만 살펴보아도 문제가 되지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 DAO 메소드에 대한 포괄적인 테스트를 만들어두는 편이 훨씬 안전하고 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 특별한 상황이 되면 엉뚱하게 동작하는 코드를 작성했는데도 불구하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 안해보았다면 문제 원인 파악을 위해 고생하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 많은 개발자는 성공하는 테스트를 골라서 작성하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머릿속으로 이 코드가 잘 돌아가는 케이스를 상상하며 코드를 작성하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개발자도 조금만 신경쓰면 자신의 코드에서 다양한 상황과 입력 값을 고려하는 포괄적인 테스트를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 테스트를 작성할 때 부정적인 케이스를 먼저 만드는 습관을 들이는 것이 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.3.4 테스트가 이끄는 개발&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.3.3에서 테스트를 어떻게 실행할지 결정하고, 테스트 코드를 작성하고 UserDao 코드에 손을 대기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트할 대상도 만들지 않고 테스트 코드부터 만드는 것이 이상할 수도 있으나 이런 순서로 개발하는 방법론이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기능 설계를 위한 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 테스트 코드의 개선을 '존재하지 않는 id로 get() 메소드를 실행했을 때 어떻게 될까?'라는 고민에서 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 고민의 시작으로 테스트에서 특정한 예외를 캐치해야함을 생각할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 '어떻게 테스트할까?'라는 생각을 통해 &lt;b&gt;getUserFailure()&lt;/b&gt;를 만든 것이 아니라 추가하고 싶은 기능을 코드로 표현했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPFsEe/dJMcabR5wSW/yWd6DPYd8KCOfBk7NUd9Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPFsEe/dJMcabR5wSW/yWd6DPYd8KCOfBk7NUd9Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPFsEe/dJMcabR5wSW/yWd6DPYd8KCOfBk7NUd9Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPFsEe%2FdJMcabR5wSW%2FyWd6DPYd8KCOfBk7NUd9Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;188&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getUserFailure() 테스트에는 만들고 싶은 기능에 대한 조건과 행위, 결과에 대한 내용이 잘 표현되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 기능정의서 같아 보이지 않는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기능설계, 구현, 테스트라는 일반적인 개발 흐름의 기능설계에 해당하는 부분을 테스트 코드가 일부 담당한다고 볼 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 주도 개발&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TDD, TFD&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법론&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD는 아예 테스트를 먼저 만들고 테스트가 성공하도록 하는 코드만 만들기 때문에 테스트를 빼먹지 않고 꼼꼼히 만들어낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 TDD의 구체적인 장점은 무엇인가?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;미리 테스트가 작성되어있기 때문에 코드를 만들고 테스트까지 시간이 걸리지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;매번 테스트가 성공함에 따라 코드에 대한 강한 확신을 갖는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD에서는 테스트를 작성하고 성공시키는 코드를 만드는 작업의 주기를 가능한 짧게 가져가도록 권장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 반나절 만들고 오후 내내 테스트를 통과시키는 코드를 만드는 식의 개발은 좋지 못하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 어떻게 보면 모든 개발자는 TDD를 몰라도 테스트가 개발을 이끌어가는 방식으로 개발을 하고 있다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 새로운 기능을 가진 코드를 만든다고 가정해보자. 그렇다면 개발자의 머릿속에서는 다음과 같은 생각을 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'이런 조건 하에 이런 작업을 하면 이런 결과가 나올 것이다'라는 기능을 먼저 정리할 것이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성하면서도 코드를 보며 '이런 조건의 값이 들어오면 코드의 흐름과 조건에 따라 이렇게 진행되어 이런 결과값이 나온다'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머릿속에서 이미 TDD를 하고 있다고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법은 제약이 심하고, 오류가 많고, 반복이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD를 하면 개발 지연의 우려를 표하는 사람들이 종종 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 애플리케이션 코드보다 작성이 쉽고 독립적이다. 따라서 코드 양에 비해 작성 시간이 짧다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 디버깅 시간이 줄어들기 때문에 개발 속도는 빨라진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 웹을 통해 테스트하기까지 비효율을 생각한다면 단위 테스트를 미리 만들어 코드를 검증하는 편이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;책 소스코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/l-lyun/study/tree/book/toby-spring-chapter-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/l-lyun/study/tree/book/toby-spring-chapter-2&lt;/a&gt;&lt;/p&gt;</description>
      <category>spring</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/20</guid>
      <comments>https://to-the-goal.tistory.com/20#entry20comment</comments>
      <pubDate>Wed, 17 Jun 2026 21:46:54 +0900</pubDate>
    </item>
    <item>
      <title>밴드 플랫폼 서버 개발 일지 - 공통 기반 세팅</title>
      <link>https://to-the-goal.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;PR 링크:&amp;nbsp;&lt;a href=&quot;https://github.com/l-lyun/band-platform/pull/10/changes#diff-ccb8ca74f50cfb13410ca2409faceef7fb57e0e6aab8ba5f4d529a05d80182bf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/l-lyun/band-platform/pull/10/changes#diff-ccb8ca74f50cfb13410ca2409faceef7fb57e0e6aab8ba5f4d529a05d80182bf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 기능 구현하기에 앞서 공통 응답, 에러 처리를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 에러 응답 형식은 &lt;b&gt;{status, code, message}&lt;/b&gt;로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여태 협업 하며 코드별로 명확하게 에러 분기처리를 프론트에서 진행할 수 있었기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서화만 잘 된다면 좋은 방법이라고 생각한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781246607440&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public record ErrorResponse(
	int status,
	String code,
	String message
) {
}

public enum Errorcode {
	private final String code;
	private final HttpStatus status;
	private final String message;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불변 객체인 레코드를 활용해 ErrorResponse를 만들었고, 각각 들어가는 값들은 enum을 활용해 관리하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이후 애플리케이션 단으로 에러가 필요할 때는 다음과 같이 던질 수 있게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781246701157&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;throw new BusinessException(ErrorCode.INVALID_USER);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 AOP라는 기술을 통해 공통 관심사를 분리하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패한 기술이라고도 말하기는 하지만, 비즈니스 로직에서 반복되는 공통 로직(에러, 트랜잭션)등을 분리해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직은 로직답게 유지하고 중복 코드를 줄인다는 점에 충분한 의의가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리하여 우리는 @RestControllerAdvice라는 어노테이션이 전역적으로 동작함에 있어 굉장히 편리하게 에러처리를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html?utm_source&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781248204991&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Controller Advice :: Spring Framework&quot; data-og-description=&quot;@ExceptionHandler, @InitBinder, and @ModelAttribute methods apply only to the @Controller class, or class hierarchy, in which they are declared. If, instead, they are declared in an @ControllerAdvice or @RestControllerAdvice class, then they apply to any c&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html?utm_source=chatgpt.com&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html?utm_source=chatgpt.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Controller Advice :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;@ExceptionHandler, @InitBinder, and @ModelAttribute methods apply only to the @Controller class, or class hierarchy, in which they are declared. If, instead, they are declared in an @ControllerAdvice or @RestControllerAdvice class, then they apply to any c&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 스프링 MVC 패턴에 의해 http 요청이 들어오면 다음과 같은 순서로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client -&amp;gt; Tomcat -&amp;gt; Servlet filter chain -&amp;gt; DispatcherServlet -&amp;gt; HandlerMapping -&amp;gt; HandlerAdapter&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Service -&amp;gt; 로직 수행 -&amp;gt; Model and view 응답 생성 -&amp;gt; 응답 리턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이때 Tomcat 같은 WAS는 HTTP 요청을 받아 &lt;/span&gt;&lt;span&gt;HttpServletRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;HttpServletResponse&lt;/span&gt;&lt;span&gt;로 변환하고, 등록된 Servlet인 &lt;/span&gt;&lt;span&gt;DispatcherServlet&lt;/span&gt;&lt;span&gt;을 호출한다. &lt;/span&gt;&lt;span&gt;디스패처 서블릿부터 실질적인 Spring MVC 요청 처리가 시작된다고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 Spring Security 필터나 일반 Servlet Filter Chain 단에서 에러가 터지면 &lt;/span&gt;&lt;span&gt;@RestControllerAdvice&lt;/span&gt;&lt;span&gt;에서 에러를 잡지 못하는 경우가 있기 때문에 주의할 필요가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, &lt;/span&gt;&lt;span&gt;@RestControllerAdvice&lt;/span&gt;&lt;span&gt;는 보통 &lt;/span&gt;&lt;span&gt;DispatcherServlet&lt;/span&gt;&lt;span&gt; 내부에서 발생한 MVC 예외를 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;정상적으로 요청이 &lt;/span&gt;&lt;span&gt;DispatcherServlet&lt;/span&gt;&lt;span&gt;까지 들어오면, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;DispatcherServlet&lt;/span&gt;&lt;span&gt;은 &lt;/span&gt;&lt;span&gt;HandlerMapping&lt;/span&gt;&lt;span&gt;을 통해 어떤 컨트롤러가 요청을 처리할지 찾는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그 후 &lt;/span&gt;&lt;span&gt;HandlerAdapter&lt;/span&gt;&lt;span&gt;를 통해 해당 컨트롤러 메서드를 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;대부분 &lt;/span&gt;&lt;span&gt;@RestController&lt;/span&gt;&lt;span&gt;가 붙은 메서드를 사용하기 때문에 내부적으로는 &lt;/span&gt;&lt;span&gt;RequestMappingHandlerAdapter&lt;/span&gt;&lt;span&gt;가 실행한다고 보면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 과정에서 HTTP 요청을 필요에 맞게 자바 객체로 파싱하는 과정이 일어난다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 &lt;/span&gt;&lt;span&gt;@RequestBody&lt;/span&gt;&lt;span&gt;를 통해 JSON 요청 body를 DTO로 변환할 때 &lt;/span&gt;&lt;span&gt;HttpMessageConverter&lt;/span&gt;&lt;span&gt;가 사용되고, Spring Boot 환경에서는 보통 Jackson이 JSON 변환을 담당한다. &lt;/span&gt;&lt;span&gt;또한 &lt;/span&gt;&lt;span&gt;@Valid&lt;/span&gt;&lt;span&gt;가 붙어 있다면 DTO 필드에 선언된 Bean Validation annotation을 기반으로 검증도 수행된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면 실제적으로 예외가 발생했을 때 어떻게 처리될까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러나 서비스 로직 수행 중 예외가 발생했다면, 역순으로 DispatcherServlet 방향으로 예외가 전파된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 직접 예외를 처리하는 것이 아니라 스프링 빈으로 등록되어있는 HandlerExceptionResolver에 처리를 위임한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전략 패턴이 여기서 사용되어있으며,&amp;nbsp; 내부적으로 실제 사용되는 Resolver는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ExceptionHandlerExceptionResolver&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ResponseStatusExceptionResolver&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DefaultHandlerExceptionResolver&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 많이 사용하는 @ExceptionHanler는 ExceptionHandlerExceptionResolver가 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 GlobalExceptionHandler는 예외가 발생했을 때 갑자기 실행되는 것이 아니고 순차적으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherServlet &amp;rarr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HandlerExceptionResolver &amp;rarr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExceptionHandlerExceptionResolver &amp;rarr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RestControllerAdvice의 @ExceptionHandler&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로직에서 throw new BusinessException()을 했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 방향으로 전파되고, 별도로 try-catch 수행 로직이 없다면 서블릿단까지 다시 올라간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 예외를 발생했음을 감지하여 HandlerExceptionResolver들에게 예외 처리가 가능한지 확인 후,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExceptionHandlerExceptionResolver가 @RestControllerAdvice가 붙은 클래스를 확인하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 내부에서 BusinessException을 처리하는 메서드를 찾아 수행해 우리가 알고 있는 에러 로직이 작동하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서도 책임이 잘 분리되어 있음을 볼 수 있다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스는 ErrorCode에 Enum으로 등록된 비즈니스 상황만 던지면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 응답으로 변환하는 책임은 GlobalExceptionHandler가 수행하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 서비스 로직에서 던지는 예외 이외에도 Bean Validaiton 기반 어노테이션 검증 메소드까지 추가해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로직 이전 단의 예외까지 처리하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 개발하면서 서버 차원에서 인지하지 못해 캐치하지 못한 예외가 때때로 500으로 덮어쓰일 때가 많은데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기반은 차차 잡아가도록 노력하겠다.&lt;/p&gt;</description>
      <category>개발 일기</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/19</guid>
      <comments>https://to-the-goal.tistory.com/19#entry19comment</comments>
      <pubDate>Fri, 12 Jun 2026 16:37:34 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 토비의 스프링 Vol.1 1장 - 오브젝트와 의존관계 (2)</title>
      <link>https://to-the-goal.tistory.com/17</link>
      <description>&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.4 제어의 역전(IoC)&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.4.1 오브젝트 팩토리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 클라이언트였던 테스트 main() 메소드는 테스트 용도였지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 역할까지 맡아버려 오브젝트를 생성하고 관계를 맺어주는 역할까지 맡아버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 우리는 구현 클래스 오브젝트를 만들고 두 오브젝트를 연결해주는 역할을 분리해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;팩토리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려주는 것&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1780987285974&quot; class=&quot;haxe&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class DaoFactory {
	public IndependentUserDao userDao() {
		ConnectionMaker connectionMaker = new SimpleConnectionMaker();
		IndependentUserDao userDao = new IndependentUserDao(connectionMaker);
		return userDao;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 팩토리를 통해 UserDao 오브젝트를 받아 테스트에 활용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 오브젝트들의 역할과 관계에 따라 나누어볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 애플리케이션의 핵심적인 데이터 로직과 기술 로직을 담당하는 컴포넌트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 애플리케이션의 오브젝트를 구성하고 관계를 정의하는 설계도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DaoFactory를 분리함으로써 구조를 결정하는 오브젝트가 분리됨에 의미가 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.4.2 오브젝트 팩토리의 활용&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drzu0d/dJMcahksNPx/kvdmcxBzaa4A5cOr5pN0X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drzu0d/dJMcahksNPx/kvdmcxBzaa4A5cOr5pN0X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drzu0d/dJMcahksNPx/kvdmcxBzaa4A5cOr5pN0X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdrzu0d%2FdJMcahksNPx%2FkvdmcxBzaa4A5cOr5pN0X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;202&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConnectionMaker 구현 클래스의 오브젝트를 생성하는 코드마다 메소드가 반복되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new SimpleConnectionMaker()라는 구현 클래스의 인스턴스를 만드는 부분이 반복되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황이라면 구현 클래스를 바꿀 때마다 모든 메소드를 수정해야하기 때문에 수정이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqMbQS/dJMcacwEtDz/4LVFclkYuavcD1q487Yv4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqMbQS/dJMcacwEtDz/4LVFclkYuavcD1q487Yv4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqMbQS/dJMcacwEtDz/4LVFclkYuavcD1q487Yv4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqMbQS%2FdJMcacwEtDz%2F4LVFclkYuavcD1q487Yv4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;457&quot; height=&quot;253&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분리된다면 DAO의 팩토리 메소드가 많아져도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connectionMaker 메소드 내부의 ConnectionMaker 구현 클래스만 변경하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.4.3 제어권의 이전을 통한 제어관계 역전&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로그램 실행 흐름을 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;main() -&amp;gt; 다음 오브젝트 결정 후 생성 -&amp;gt; 오브젝트 내 메소드 호출 -&amp;gt; 다음 사용할 오브젝트/메소드 결정 및 호출 반복&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 오브젝트 자신이 사용할 오브젝트를 결정하고 언제, 어떻게 만들지 선택하는 능동적인 참여 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제어의 역전이란 이 제어의 흐름 개념을 거꾸로 뒤집는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제어의 역전이 이루어지면 자신이 사용할 오브젝트를 스스로 결정하지도 않고, 생성하지도 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 제어 권한을 다른 대상에 위임하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿과 앞서 배운 추상 클래스 구조에서도 제어의 역전 개념이 여실히 드러나고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿을 개발해서 서버에 배포할 수는 있지만 개발자가 제어할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 서블릿 제어 권한을 가진 컨테이너가 적절히 서블릿 클래스의 오브젝트를 만들고 내부 메소드를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상 클래스에서도 서브 클래스에서는 추상 메소드를 구현하기는 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈퍼 클래스에서 언제, 어떻게 사용될 지는 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제어권을 상위 템플릿 메소드에 넘기고 자신은 필요할 때 호출되어 사용되는 제어의 역전 개념을 발견할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라이브러리와 프레임워크의 대표적인 차이도 이 제어의 역전이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리를 사용하는 애플리케이션 코드는 애플리케이션의 흐름을 직접 제어하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앞서 자연스럽게 관심을 분리하고 책임을 나누고 유연하게 확장 가능한 구조를 만들기 위해&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리를 도입한것이 IoC를 적용한 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제어의 역전에서는 프레임워크 또는 컨테이너와 같이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 생성과 관계 설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 미니멀하게 DaoFactory를 오브젝트 수준에서 가장 단순한 IoC 컨테이너로 활용해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 전반에 걸쳐 본격적으로 활용하기 위해서는 스프링과 같은 IoC 프레임워크의 도움을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;스프링은 IoC를 기반 기술, 극한까지 적용하는 프레임워크다!!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.5 스프링의 IoC&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링의 핵심은 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.5.1 오브젝트 팩토리를 이용한 스프링 IoC&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;애플리케이션 컨텍스트와 설정 정보&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 제어권을 갖고 직접 만들고 관계를 부여하는 오브젝트들을 Bean이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 컨테이너가 관계설정, 사용 등을 제어하는 제어의 역전이 적용되는 오브젝트를 가리킨다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bean Factory&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Bean 생성, 관계 설정 등 제어를 담당하는 IoC 오브젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보통 빈팩토리를 확장한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Application Context(AC)&lt;/b&gt;를 주로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 빈 팩토리라고 부를 때는 빈 생성, 관계 설정에 입각해 IoC 기본 기능에 초점을 두는 것이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC로 부를 때는 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미를 부각시키는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC는 별도의 정보를 참고해 빈 생성, 관계설정 등 제어 작업을 총괄한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 DaoFactory를 참고해 설정정보를 담고있는 무언가를 가져와 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 팩토리의 역할이 설정정보까지 담고있던 엔진이었는데, 이제는 활용되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건물이 설계도면을 따라 만들어지듯이 애플리케이션도 AC와 그 설정정보를 따라 만들고 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Configuration&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Bean&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 오브젝트를 만들어주는 메소드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 애노테이션으로 스프링 프레임워크의 AC가 IoC를 제공할 때 완벽한 설정정보를 가질 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QG8LV/dJMcagMCtJ2/0x6MDxG9XYsQn8kFKqLc0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QG8LV/dJMcagMCtJ2/0x6MDxG9XYsQn8kFKqLc0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QG8LV/dJMcagMCtJ2/0x6MDxG9XYsQn8kFKqLc0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQG8LV%2FdJMcagMCtJ2%2F0x6MDxG9XYsQn8kFKqLc0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2044&quot; height=&quot;276&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 보면 그냥 daoFactory를 불러와서 생성하면 더 깔끔하지 않나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 얻을 수 있는 장점이 뭘까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.5.2 애플리케이션 컨텍스트의 동작 방식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 컨텍스트는 ApplicationContext 인터페이스를 구현하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApplicationContext는 BeanFactory가 구현하는 BeanFactory를 상속했기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC는 일종의 빈 팩토리로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC는 애플리케이션에서 IoC를 적용해 관리할 모든 오브젝트 생성과 관계설정을 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 없고 생성/연관관계 정보를 별도 설정정보를 통해 얻어낸다. 때로는 외부 오브젝트 팩토리로 위임하는 경우도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Configuration이 붙은 DaoFactory는 AC가 활용하는 IoC 설정정보다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Bean이 붙은 메소드 이름을 가져와 빈 목록을 만들어내고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트가 getBean()으로 일일이 요청해야 오브젝트가 전달되는데 장점은 무엇일까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트 팩토리 내에 계속 코드가 추가되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 종합 IoC 서비스를 제공한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 관계 설정만이 아닌 오브젝트 생성 방식, 시점, 전략을 다르게 가져갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오브젝트 후처리, 정보 조합, 설정 방식 다변화, 인터셉팅 등 다양한 기능이 사용가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 빈이 사용할 수 있는 기반 기술, 외부 시스템 연동 등 컨테이너 차원에서 제공된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 빈을 검색하는 다양한 방법을 제공한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.5.3 스프링 IoC 용어 정리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Bean&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링이 IoC 방식으로 관리하는 오브젝트, 관리되는 오브젝트&lt;/li&gt;
&lt;li&gt;애플리케이션에서 사용되는 모든 오브젝트가 다 빈은 아니고 스프링이 직접 생명주기를 담당하는 오브젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bean Factory&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 IoC 담당 핵심 컨테이너&lt;/li&gt;
&lt;li&gt;빈 등록, 생성, 조회하고 리턴, 부가적 빈 관리&lt;/li&gt;
&lt;li&gt;보통 AC 사용, 빈 팩토리가 구현하는 가장 기본적인 인터페이스 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Application Context&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈 팩토리를 확장한 IoC 컨테이너&lt;/li&gt;
&lt;li&gt;빈을 등록하고 관리하는 기본적인 기능은 빈 팩토리와 동일&lt;/li&gt;
&lt;li&gt;빈팩토리는 빈 생성/제어 관점, AC는 스프링 애플리케이션 지원 기능 모두 포함&lt;/li&gt;
&lt;li&gt;ApplicationContext는 BeanFactory 상속&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Configuration Metadata&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AC 또는 Bean Factory가 IoC를 적용하기 위해 사용하는 메타정보&lt;/li&gt;
&lt;li&gt;컨테이너에 의해 관리되는 오브젝트를 생성하고 구성할 때 사용&lt;/li&gt;
&lt;li&gt;형상정보, 청사진 ..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IoC Container&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IoC 방식으로 빈을 관리한다는 의미&lt;/li&gt;
&lt;li&gt;애플리케이션 컨텍스트보다 추상적인 의미이기도 하다&lt;/li&gt;
&lt;li&gt;애플리케이션 컨텍스트 오브젝트는 하나의 애플리케이션에서 여러개 만들어서 사용되기 때문에 통틀어서 스프링 컨테이너라고 부를 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링 프레임워크&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IoC 컨테인, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능 통틀어 부름&lt;/li&gt;
&lt;li&gt;주로 스프링으로 줄여 부름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.6 싱글톤 레지스트리와 오브젝트 스코프&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까지는 직접 만든 Factory와 @Configuration이 적용된 컨테이너가 동일하게 작동하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점을 알아보기 이전에 동일성과 동등성의 차이를 간단하게 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오브젝트의 동일성과 동등성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일성(identity)의 비교는 ==, 동등성(equality)의 비교는 equals() 메소드를 통해 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동등한 경우는 두 개의 각기 다른 오브젝트가 메모리상 각각 존재하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 경우는 오브젝트는 하나만 존재하고 래퍼런스 변수가 다른 것 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 만든 팩토리로는 동일성이 지켜지지 않는다. 하지만 AC의 getBean은 동일성이 지켜진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.6.1 싱글톤 레지스트리로서 애플리케이션 컨텍스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AC는 우리가 만들었던 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC 컨테이너를 활용해 오브젝트를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여타 설정을 하지 않는다면 모두 싱글톤으로 빈 오브젝트를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 애플리케이션과 싱글톤&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜 스프링은 빈을 싱글톤으로 만들어낼까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 주로 서버환경에서 활용되고, 그래야만 가치가 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 엔터프라이즈 환경이라고 가정해보면 서버 하나당 최대 초당 수십, 수백번의 브라우저 요청을 받아 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 과정에서 다양한 기능을 담당하는 오브젝트가 모두 새롭게 생성된다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿 또한 기본적인 서비스 오브젝트이며, 여러 스레드에서 하나의 오브젝트를 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 제한된 수, 대개 한 개의 오브젝트만 만들어 사용하는 것이 싱글톤 패턴의 원리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 디자인 패턴에서 싱글톤 패턴은 사용하기도 까다롭고 문제점도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 싱글톤 패턴을 알아보자.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;싱글톤 패턴 구현 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- private 접근 제어자로 외부 생성 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 스태틱 필드 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스태틱 팩토리 메소드인 getInstance()를 만들고 호출 시점에 오브젝트가 생성돼 스태틱 필드에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생성된 이후에는 getInstance() 메소드를 통해 스태틱 필드에 저장된 오브젝트 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;싱글톤 패턴의 한계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- private으로 생성되어 상속 불가 -&amp;gt; 다형성 이용 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- static으로 필드가 만들어져 목으로 대체하거나 주입 불가 -&amp;gt; 테스트 난이도 상승&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 JVM으로 분산되는 경우 서버환경에서 싱글톤 보장 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전역 상태, 즉 아무 객체나 자유롭게 접근하고 수정하고 공유하는 모델은 객체지향적이지 못함&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;싱글톤 레지스트리&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 환경에서 싱글톤으로 만들어져 서비스 오브젝트 방식으로 사용되는 것은 적극 지지하나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 싱글톤 구현 방식은 앞서 본 여러 단점이 있어 스프링은 직접 싱글톤 형태의 오브젝트를 관리하고 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이를 싱글톤 레지스트리라고 명명한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POJO 평범한 자바 클래스를 제어권을 컨테이너로 넘긴다면 싱글톤으로 활용할 수 있도록 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 생성에 관한 모든 권한은 AC에 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 public으로 생성하여도 싱글톤 방식이 유지되고, 앞서 살펴본 한계가 해결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 중요한 것은 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴을 적용할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.6.2 싱글톤과 오브젝트의 상태&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 오브젝트로 유지되기 때문에 여러 스레드가 동시에 접근하여 사용할 수 있도록 무상태로 만들어져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 전용이면 인스턴스 번수로 공유해도 되지만, 저장공간이 하나이기 때문에 서로 덮어쓸 수 있기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 파라미터, 로컬 변수, 리턴 값을 이용해 값을 주고받는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 파라미터, 로컬변수는 스레드 스택 영역에 저장되기 때문에 독립적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.6.3 스프링 빈의 스코프&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 빈의 기본 스코프는 컨테이너 내에 한 개의 오브젝트만 만들어져 스프링 컨테이너가 존재하는 동안 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 스코프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스프링 빈의 기본 설정, 컨테이너와 생명주기 동일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토타입 스코프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 스코프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 웹을 통해 HTTP 요청이 생길 때마다 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 스코프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 웹의 세션과 스코프가 유사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.7 의존관계 주입(DI)&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.7.1 제어의 역전(IoC)과 의존관계 주입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC는 전통적인 소프트웨어 방법론에서 살펴보던 일반적인 개념이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향적인 설계나 디자인 패턴, 컨테이너에서 동작하는 서버 기술을 사용하다보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연스럽게 IoC를 적용하거나 그 원리로 동작하는 기술을 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 스프링의 의도를 더욱 명확히 나타내기 위해 &lt;b&gt;의존관계 주입(DI)&lt;/b&gt;라는 용어를 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링이 여타 프레임워크와 차별화되어 제공해주는 기능은 의존관계 주입이라는 새로운 용어를 사용할 때 분명히 드러난다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오브젝트 레퍼런스를 외부로부터 제공받고, 여타 오브젝트와 다이나믹하게 의존관계가 만들어지는 것이 핵심이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.7.2 런타임 의존관계 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존관계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존한다는 것은 의존 대상이 변하면 의존하는 대상에 영향을 미친다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A -&amp;gt; B, A가 B를 의존하고 있을 때 B의 기능이 추가/변경 시 그 영향이 A로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserDao의 의존관계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 UserDao가 ConnectionMaker에 의존하고 있다. 따라서 ConnectionMaker의 변경에 UserDao가 영향을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인터페이스를 통해 설계 시점에 느슨한 의존관계를 만들어주면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDao의 오브젝트가 런타임 시에&amp;nbsp; 사용할 오브젝트가 어떤 클래스로 만든 것인지 미리 알 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 변경에서 자유로워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자, 운영자가 사전에 어떤 클래스의 오브젝트를 사용할 것인지 정해놓기는 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 설계와 코드 속에서는 드러나지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 프로그램이 시작되고 런타임 시에 의존관계를 맺는 대상을 &lt;b&gt;의존 오브젝트&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존관계 주입은 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체를 런타임 시에 연결해주는 작업을 말한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이 주입해주는 제 3의 존재가 있다는 것이 의존관계 주입의 핵심이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존관계 주입의 3가지 조건&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스만 의존해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 런타임 시점의 의존관계는 컨테이너나 팩토리같은 제 3의 존재가 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI 컨테이너는 오브젝트를 만드는 시점에서 생성자 파라미터로 오브젝트의 레퍼런스가 전달되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 스프링 부트에서는 보통 생성자 주입을 통해 오브젝트의 레퍼런스가 제공된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781180387987&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class IndependentUserDao {
	private final ConnectionMaker connectionMaker;
	public IndependentUserDao(ConnectionMaker connectionMaker) {
		this.connectionMaker = connectionMaker;
	}
...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 두 개의 오브젝트 간 런타임 의존관계가 만들어졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI는 자신이 사용할 오브젝트에 대한 선택과 생성 제어권을 외부로 넘기고 자신은 수동적으로 주입받는 오브젝트를 사용한다!!&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.7.3 의존관계 검색과 주입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파트는 아직 지식이 부족해서 학습을 더 진행한 후 작성하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.7.4 의존관계 주입의 응용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 인터페이스를 통한 결합도가 낮은 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코드에서 런타임 클래스에 대한 의존관계가 나타나지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 다른 책임을 가진 사용 의존관계가 있는 대상이 바뀌거나 변경되어도 자신은 영향받지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 변경을 통한 다양한 확장 방법에는 자유롭다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이러한 장점으로 인해 스프링이 제공하는 많은 기능이 DI의 혜택을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디테일하게 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기능 구현의 교환&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781180595969&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public ConnectionMaker connectionMaker() {
	// 둘중에 필요에 따라 교체만 하면 됨
	// return new ProductionDBConnectionMaker();
	return new LocalDBConnectioMaker();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 로컬에서 테스트하려고 DAO에서 매번 클래스 오브젝트를 생성해서 사용했다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new LocalDBConnectionMaker()라는 코드로 모든 DAO에서 변경해주어야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부가기능 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781180668516&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CountingConnectionMaker implements ConnectionMaker{
		int counter = 0;
		private ConnectionMaker realConnectionMaker;

		public CountingConnectionMaker(ConnectionMaker realConnectionMaker) {
			this.realConnectionMaker = realConnectionMaker;
		}
		public Connection makeConnection throws ClassNotFoundException, SQLException{
			this.counter++;
			return realConnectioMaker.makerConnection();
		}
			public int getCounter() {
				return this.counter;
			}	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAO와 DB 커넥션을 만드는 오브젝트 사이의 연결 횟수를 카운팅하는 기능이 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CountingConnectionMaker 클래스는 내부에서 직접 DB 커넥션을 만들지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count를 증가하는 기능이 추가되었고, 실제 DBConnector를 호출해 DAO에 돌려줄 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAO가 계속 증가하여도 DI를 통해 관심사가 분리되었기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAO가 직접 의존해서 사용할 ConnectionMaker 타입의 오브젝트는 @Configuration 안에서 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 관심사의 분리를 통해 얻어지는 높은 응집도&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링을 공부하는 것은 DI를 어떻게 활용해야할지 공부하는 것이기도 하다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>spring</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/17</guid>
      <comments>https://to-the-goal.tistory.com/17#entry17comment</comments>
      <pubDate>Thu, 11 Jun 2026 21:27:09 +0900</pubDate>
    </item>
    <item>
      <title>밴드 플랫폼 서버 개발 일지 6/10 - 프로젝트 세팅</title>
      <link>https://to-the-goal.tistory.com/18</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 프로젝트 세팅 - Codex와 친해지기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex를 사용하는 이유는 가장 크게 카카오 요금제로 저렴하게 많은 토큰을 이용할 수 있어서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 프로젝트 계속 진행하면서 여러 AI도 찾아보며 채워나가보려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex를 사용하며 구현하기에 앞서,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=474wZZHoWN4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=474wZZHoWN4&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=474wZZHoWN4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/on9WH/dJMb9iaZnba/kFEXzW1PvtHV9eGrx5QyAk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=798_106_1188_532&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Learn 95% of Codex in 30 minutes&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/474wZZHoWN4&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 영상을 보고 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 사용함에 있어 학습하는 입장에서 거리낌이 있었으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브 영상을 보고 실제 사용해보니 AI를 원활히 사용하는 능력이 필요함을 깨닫게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상에서 가장 크게 와닿은 것은 영구 저장, 커스텀 스킬과 플러그인을 통한 자동화였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'./~.codex/memories folder에서 너가 알고있는 것을 내가 이해하고싶어 파일도 보여줄래'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 명령을 제시하면 여태 어떻게 내가 명령했었고, 어떻게 진행되는지 코덱스가 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 클릭 몇번으로 MCP연동과 인증이 가능했고, 대화를 통해 내가 어느정도 원하는 결과값이 도출되었을 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'작업 과정을 스킬로 만들어줘'라는 프롬프팅을 통해 커스텀 스킬이 완성되고, 이를 언제든 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 코딩도 코딩이지만 취업준비 관련해서 여러 업무의 자동화를 충분히 해볼 수 있을 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDdeZi/dJMcagsh4VK/FOHCPI4v8IbIEp1VpZPKi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDdeZi/dJMcagsh4VK/FOHCPI4v8IbIEp1VpZPKi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDdeZi/dJMcagsh4VK/FOHCPI4v8IbIEp1VpZPKi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDdeZi%2FdJMcagsh4VK%2FFOHCPI4v8IbIEp1VpZPKi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1700&quot; height=&quot;388&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 우선 내 정보를 노션에 업로드해놓고 기업에 맞게 자기소개서를 작성하는 스킬을 만들어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 수정이 필요해보이긴 하지만 초안으로 사용하기에 충분하다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 실제 프로젝트 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developers.openai.com/codex&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.openai.com/codex&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781162816444&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Codex | OpenAI Developers&quot; data-og-description=&quot;One agent for everywhere you code&quot; data-og-host=&quot;developers.openai.com&quot; data-og-source-url=&quot;https://developers.openai.com/codex&quot; data-og-url=&quot;https://developers.openai.com/codex&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d59nYZ/dJMb8TCh68b/NCSuo0tKnV7YegPmPXfUb0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/lm9qx/dJMb9llfmZu/u9I6zubVz5KCVTt4n23Hxk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://developers.openai.com/codex&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.openai.com/codex&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d59nYZ/dJMb8TCh68b/NCSuo0tKnV7YegPmPXfUb0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/lm9qx/dJMb9llfmZu/u9I6zubVz5KCVTt4n23Hxk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Codex | OpenAI Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;One agent for everywhere you code&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.openai.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공홈을 참고하고 codex한테 계속 물어보고 진행하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 디렉토리에 AGENTS.md을 생성해 프로젝트 별 스킬?을 코덱스에게 지시할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 깃허브 PR에서 코덱스가 리뷰할 때 이 파일을 참고한다고 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 나는 리뷰어 관련 세팅만 진행했고, 내가 작업할 때 필요한 규칙들은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;/agents 디렉토리 내에서 관리해보려 한다. 계속 학습하면서 언제든 개선하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 AI를 활용하여 구현할 때 목표했던 사항들은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. AI 툴과 협업하는 방법 탐구&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 구현하며 검토를 꾸준히 하는 능력 체화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 2번을 통한 자바/스프링 생태계 학습 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2번은 git worktree를 사용해 채팅 스레드를 2개 유지하며 다른 폴더에서 작업하듯 진행하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텔리제이도 두개를 띄워서 왔다갔다 보아야하는 불편함이 있는데, 브랜치 바꾸면서 하는 것도 불편한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개까지 유지해볼까 고민해보았는데 뇌빼고 프로젝트할 것 같아서 2개로 생각하는 시간을 더 길러보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번은 구현만 계속 한다면 분명 학습하기가 쉽지 않으리라 예상되어서 노션을 통해 정리하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/l-lyun/band-platform/blob/dev/docs/notion-pr-workflow.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/l-lyun/band-platform/blob/dev/docs/notion-pr-workflow.md&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781163413321&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;band-platform/docs/notion-pr-workflow.md at dev &amp;middot; l-lyun/band-platform&quot; data-og-description=&quot;Contribute to l-lyun/band-platform development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/l-lyun/band-platform/blob/dev/docs/notion-pr-workflow.md&quot; data-og-url=&quot;https://github.com/l-lyun/band-platform/blob/dev/docs/notion-pr-workflow.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HaEoH/dJMb8XknMsT/4BLjKkcFNmTFtzsEeXhYpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bEEloM/dJMb8YXT0nz/Mxi8ILCwmGo1bAUGMFnLKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/l-lyun/band-platform/blob/dev/docs/notion-pr-workflow.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/l-lyun/band-platform/blob/dev/docs/notion-pr-workflow.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HaEoH/dJMb8XknMsT/4BLjKkcFNmTFtzsEeXhYpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bEEloM/dJMb8YXT0nz/Mxi8ILCwmGo1bAUGMFnLKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;band-platform/docs/notion-pr-workflow.md at dev &amp;middot; l-lyun/band-platform&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to l-lyun/band-platform development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프롬프팅을 통해 이렇게 작성해놓았고 피알 작성 시 관련 개념을 간단히 정리해서 노션 문서로 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서를 보고 어떤걸 구현했고, 이를 찾아볼 수 있도록 가이드 정도는 충분히 제시되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;codex 내부에서 세팅 이후 PR 댓글을 통해 @codex review 라고 요청하면 리뷰가 예쁘게 달린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 리뷰는 생각지도 못한 부분이었어서 사소하지만 남겨보려 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JS26F/dJMcaccl7Ze/1F9kX2iejhdK4lQYV71531/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JS26F/dJMcaccl7Ze/1F9kX2iejhdK4lQYV71531/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JS26F/dJMcaccl7Ze/1F9kX2iejhdK4lQYV71531/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJS26F%2FdJMcaccl7Ze%2F1F9kX2iejhdK4lQYV71531%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;911&quot; height=&quot;604&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 어느정도 세팅이 되어서 데일리하게, 혹은 기능 단위로 묶어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 진행했는지, 배운 것들에 대해 글을 작성해보려한다.&lt;/p&gt;</description>
      <category>개발 일기</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/18</guid>
      <comments>https://to-the-goal.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 11 Jun 2026 16:40:38 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 토비의 스프링 Vol.1 1장 - 오브젝트와 의존관계 (1)</title>
      <link>https://to-the-goal.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 자바 환경에서 객체지향 프로그래밍을 가장 중요한 가치로 두고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 오브젝트에 집중해서 스프링을 바라보도록 하자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정한 모델과 기법을 억지로 강요하지는 않지만 오브젝트를 효과적으로 사용할 기준을 마련해주어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 설계, 구현에 관한 실용적인 전략과 베스트 프랙티스를 자연스레 적용할 수 있도록 하는 프레임워크가 스프링이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.1 초난감 DAO&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.1.1 User&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DAO란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Data Access Object로 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하는 오브젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;자바빈 규약&lt;/b&gt;을 따르는 오브젝트를 이용하면 편리&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;자바 빈&quot; data-text-less=&quot;자바 빈&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바 빈&lt;br /&gt;&lt;/b&gt;비주얼 툴에서 조작 가능한 컴포넌트이지만 이제는 두 가지 관례를 따라 만들어진 오브젝트를 가리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 디폴트 생성자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 리플렉션: 런타임에 클래스 정보를 읽어 객체를 만들고, 메서드나 필드에 접근할 수 있게 해주는 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프로퍼티&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 자바빈이 노출하는 이름을 가진 속성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로퍼티는 setter와 getter를 활용해 수정/조회 가능&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.1.2 UserDao&lt;/b&gt;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;UserDao&quot; data-text-less=&quot;UserDao&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1780983450659&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDao {

    public void add(User user) throws ClassNotFoundException, SQLException {
        Class.forName(&quot;com.mysql.jdbc.Driver&quot;);

        Connection c = DriverManager.getConnection(
            &quot;jdbc:mysql://localhost/springbook&quot;,
            &quot;spring&quot;,
            &quot;book&quot;
        );

        PreparedStatement ps = c.prepareStatement(
            &quot;insert into users(id, name, password) values(?, ?, ?)&quot;
        );

        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());

        ps.executeUpdate();

        ps.close();
        c.close();
    }

    public User get(String id) throws ClassNotFoundException, SQLException {
        Class.forName(&quot;com.mysql.jdbc.Driver&quot;);

        Connection c = DriverManager.getConnection(
            &quot;jdbc:mysql://localhost/springbook&quot;,
            &quot;spring&quot;,
            &quot;book&quot;
        );

        PreparedStatement ps = c.prepareStatement(
            &quot;select * from users where id = ?&quot;
        );

        ps.setString(1, id);

        ResultSet rs = ps.executeQuery();
        rs.next();

        User user = new User();
        user.setId(rs.getString(&quot;id&quot;));
        user.setName(rs.getString(&quot;name&quot;));
        user.setPassword(rs.getString(&quot;password&quot;));

        rs.close();
        ps.close();
        c.close();

        return user;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.1.3 main()을 이용한 테스트 코드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신을 엔트리포인트로 설정해 직접 실행 가능한 스테틱 main 메소드 내에서 sysout으로 테스트할 수 있지만, 코드가 복잡하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 생각할 포인트는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 기능은 충실하게 동작하는데 어떠한 문제점이 있을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 개선했을 때 장점은 무엇인가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 미래에 주는 유익은?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링을 공부하는 것은 이러한 문제 제기와 의문에 대해 답을 찾아나가는 과정이라고 필자는 말하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 결론을 내릴 수 있도록 객체지향 기술과 자바 개발 선구자들의 방법은 힌트일 뿐,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종 결론은 개발자가 만들어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.2 DAO의 분리&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.2.1 관심사의 분리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세상에서는 변하는 것과 변하지 않는 것이 있지만, 객체지향 세계에서는 모든 것이 변한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수, 오브젝트 필드뿐 아닌 설계, 구현, 사용자 비즈니스 프로세스, 요구사항은 변화하고 발전하며 기반 기술, 운영 환경 또한 변한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 객체는 미래에 대비할 수 있어야한다. 그렇다면 어떻게?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실세계를 모델링한다기보다 가상의 추상 세계를 효과적으로 구성하고, 자유롭고 편리하게 변경/발전/확장할 수 있다는 것에 초점을 두어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분리와 확장을 고려한 설계&lt;/b&gt;가 이루어져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 한 가지 관심이 한 군데에 집중되게 코드를 작성하는 &lt;b&gt;관심사의 분리&lt;/b&gt;가 이루어져야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관심이 같은 것끼리는 하나의 객체 또는 친한 객체로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관심이 다른 것은 가능한 따로 떨어져 서로 영향을 주지 않도록.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.2.2 커넥션 만들기의 추출&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 UserDao 클래스에서 add() 메서드에는 세 가지 관심사항이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. DB 커넥션 가져오는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자 등록을 위해 DB에 보낼 SQL 쿼리를 만들고 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 작업이 끝나면 오브젝트를 닫고 공유 리소스를 시스템에 돌려줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 중복 코드 메서드 추출을 실행해보자.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;메소드 추출 UserDao&quot; data-text-less=&quot;메소드 추출 UserDao&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1780983914437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	public User get(String id) throws ClassNotFoundException, SQLException {

		Connection c = getConnection();

		PreparedStatement ps = c.prepareStatement(&quot;select * from users where id = ?&quot;);
		ps.setString(1, id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString(&quot;id&quot;));
		user.setName(rs.getString(&quot;name&quot;));
		user.setPassword(rs.getString(&quot;password&quot;));

		rs.close();
		ps.close();
		c.close();

		return user;
	}

	private Connection getConnection() throws ClassNotFoundException, SQLException {
		Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);
		Connection c = DriverManager.getConnection(&quot;jdbc:mysql://localhost:3306/tobyspring&quot;, &quot;toby&quot;, &quot;toby&quot;);
		return c;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 DB 종류나 접속 방법이 바뀌었을 때 getConnection()이라는 메서드만 수정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 관심이 집중된 코드만 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.2.3 DB 커넥션 만들기의 독립&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDao 소스코드를 외부에서 사용하고 싶지만 컴파일된 클래스 바이너리 파일만 제공하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트들의 사용 DB가 다르기 때문에 DB 커넥션을 독자적으로 적용하고 싶은 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 상속을 통한 확장&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;추상 클래스 적용 UserDao&quot; data-text-less=&quot;추상 클래스 적용 UserDao&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1780984293125&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package tobyspring.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import tobyspring.user.domain.User;

public abstract class AbstractUserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {

		Connection c = getConnection();

		PreparedStatement ps = c.prepareStatement(
			&quot;insert into users(id, name, password) values (?, ?, ?)&quot;
		);
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());

		ps.executeUpdate();

		ps.close();
		c.close();
	}

	public User get(String id) throws ClassNotFoundException, SQLException {

		Connection c = getConnection();

		PreparedStatement ps = c.prepareStatement(&quot;select * from users where id = ?&quot;);
		ps.setString(1, id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString(&quot;id&quot;));
		user.setName(rs.getString(&quot;name&quot;));
		user.setPassword(rs.getString(&quot;password&quot;));

		rs.close();
		ps.close();
		c.close();

		return user;
	}

	public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트의 UserDao가 클래스 레벨로 구분이 가능, 계층구조를 통해 관심사가 분리되어 변경에 용이하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 DB 연결은 UserDao를 상속하여 getConnection을 구현해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈퍼 클래스에 기본적인 로직 흐름은 두고 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드로 만들어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브클래스에서 필요에 맞게 구현하도록 할 수 있다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;템플릿 메소드 패턴&quot; data-text-less=&quot;템플릿 메소드 패턴&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속을 통해 슈퍼 클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변하지 않는 기능은 슈퍼 클래스에 만들어두고 자주 변경되며 확장할 기능은 서브 클래스에서 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈퍼 클래스에서는 미리 추상 메소드 또는 오버라이드 가능한 메소드를 정의하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 활용해 코드의 기본 알고리즘을 담고 있는 템플릿 메소드를 만든다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;추상 팩토리 메소드 패턴&quot; data-text-less=&quot;추상 팩토리 메소드 패턴&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속을 통해 기능을 확장하게 하는 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈퍼 클래스 코드에서는 서브 클래스에서 구현할 메소드를 호출해서 필요한 타입의 오브젝트를 가져와 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브 클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할지는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈퍼 클래스에서 알지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 통해 오브젝트 생성 방법을 나머지 로직, 슈퍼 클래스의 기본 코드에서 독립시키는 방법&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 상속을 통한 확장은 몇몇 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. JAVA는 단일 상속을 채택해 다른 목적으로 상속이 필요하다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 상위 클래스와 하위 클래스의 관계가 생각보다 밀접해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.3 DAO의 확장&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 오브젝트는 그에 맞는 성격에 따라 변한다. 그 성격은 이유, 시기, 주기가 모두 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 DB 연결 방식에 따라 서브 클래스의 코드만 변화하지만 상속이라는 방법을 사용한 사실이 불편하게 느껴진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.3.1 클래스의 분리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 코드였던 메소드를 추출했고, 상/하위 클래스로 분리까지 했다. 이제는 독립적인 클래스를 만들어볼 차례이다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;getConnection 클래스 분리&quot; data-text-less=&quot;getConnection 클래스 분리&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1780984818605&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package tobyspring.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import tobyspring.user.domain.User;

public class IndependentUserDao {

	private SimpleConnectionMaker connectionMaker = new SimpleConnectionMaker();

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = connectionMaker.makeNewConnection();
		PreparedStatement ps = c.prepareStatement(
			&quot;insert into users(id, name, password) values (?, ?, ?)&quot;
		);
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());

		ps.executeUpdate();

		ps.close();
		c.close();
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = connectionMaker.makeNewConnection();

		PreparedStatement ps = c.prepareStatement(&quot;select * from users where id = ?&quot;);
		ps.setString(1, id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString(&quot;id&quot;));
		user.setName(rs.getString(&quot;name&quot;));
		user.setPassword(rs.getString(&quot;password&quot;));

		rs.close();
		ps.close();
		c.close();

		return user;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분리하게 된다면 상속을 통해 DB 커넥션 기능을 확장해서 사용할 수 있었던 것이 불가능해진다.&lt;/p&gt;
&lt;pre id=&quot;code_1780984854936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SimpleConnectionMaker maker = new SimpleConneconectionMaker();
Conncection c = maker.makeNewConnection();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두줄에 UserDao가 종속적이게 된다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.3.2 인터페이스의 도입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스라는 중간 다리로 추상적이고 느슨한 연결고리를 만들어줌으로써 해결이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추상화란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통적인 성격을 뽑아내 따로 분리하는 작업으로, 자바의 추상화 도구는 인터페이스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스는 자신이 구현한 클래스에 대한 구체적인 정보는 모두 감춘다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 오브젝트가 생성되기 위해 클래스 하나를 선택해야하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스로 추상화한다면 오브젝트를 만들 때 사용할 클래스는 몰라도 상관이 없다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;인터페이스 도입&quot; data-text-less=&quot;인터페이스 도입&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1780985055576&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ConnectionMaker {

	public Connection makeNewConnection() throws ClassNotFoundException, SQLException;
}

package tobyspring.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import tobyspring.user.domain.User;

public class IndependentUserDao {

	private ConnectionMaker connectionMaker = new SimpleConnectionMaker();

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = connectionMaker.makeNewConnection();
		PreparedStatement ps = c.prepareStatement(
			&quot;insert into users(id, name, password) values (?, ?, ?)&quot;
		);
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());

		ps.executeUpdate();

		ps.close();
		c.close();
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = connectionMaker.makeNewConnection();

		PreparedStatement ps = c.prepareStatement(&quot;select * from users where id = ?&quot;);
		ps.setString(1, id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString(&quot;id&quot;));
		user.setName(rs.getString(&quot;name&quot;));
		user.setPassword(rs.getString(&quot;password&quot;));

		rs.close();
		ps.close();
		c.close();

		return user;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아직도 오브젝트 생성시 new 연산자를 통해 클래스 내에서 주입되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.3.3. 관계설정 책임의 분리&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1780985193896&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private ConnectionMaker connectionMaker = new SimpleConnectionMaker();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserDao가 아직 구현 클래스를 알아야 사용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 오브젝트를 자신이 알고 있는 관심사를 분리해야할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 줄로 짧은 코드이지만 매우 충분한 독립적인 관심사이며 이는 클라이언트 측에서 알아야할 관심사이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 클라이언트 오브젝트가 제3의 관심사항인 UserDao와 ConnectionMaker 구현 클래스 관계를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정해주는 기능을 분리하기 적합한 곳이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임 시점에서 오브젝트와 오브젝트의 관계를 설정해줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 런타임 시에 한 쪽이 다른 오브젝트의 레퍼런스를 갖고 있는 방식으로 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 생성자를 호출해 만들 수도 있지만 외부에서 만들어준 것을 사용해도 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 외부에서 주입된 ConnectionMaker 인터페이스를 구현한 오브젝트와 관계만 맺도록 만든다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스 사이의 관계가 아닌 오브젝트 사이의 다이나믹한 관계가 만들어진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 타입을 통해 받아 사용한다면 코드에 다른 클래스 이름이 나타나지 않게 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다형성을 충분히 만족시키는 코드가 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780985454800&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IndependentUserDao userDao = new IndependentUserDao(new SimpleConnectionMaker());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서 이렇게 UserDao를 사용한다면 런타임 오브젝트 의존 관계 설정 책임이 분리되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 고객이 자신을 위한 DB 접속 클래스를 만들어 UserDao가 사용할 수 있도록 분리되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.3.4 원칙과 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개방 폐쇄 원칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클래스나 모듈은 확장에 열려있어야 하고 변경은 닫혀있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;높은 응집도&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 응집도가 높다는 것은 하나의 모듈/클래스가 하나의 책임만 집중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변화 시 변하는 부분이 크기 때문에 모듈에서 어떤 부분이 바뀌었을 때 다른 영향이 있을 지 파악할 요소가 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;낮은 결합도&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 책임과 관심사가 다른 오브젝트 또는 모듈과는 느슨하게 연결된 형태를 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전략 패턴&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔 사용할 수 있도록 하는 디자인패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>spring</category>
      <author>hhyun</author>
      <guid isPermaLink="true">https://to-the-goal.tistory.com/16</guid>
      <comments>https://to-the-goal.tistory.com/16#entry16comment</comments>
      <pubDate>Tue, 9 Jun 2026 15:41:44 +0900</pubDate>
    </item>
  </channel>
</rss>