<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Back world</title>
    <link>https://pgmjun.tistory.com/</link>
    <description>백엔드 엔지니어 최승준입니다.
학습 또는 경험한 내용을 공유 및 메타인지를 목적으로 기록하고 있습니다.</description>
    <language>ko</language>
    <pubDate>Sat, 11 Jul 2026 18:06:00 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>PgmJUN</managingEditor>
    <image>
      <title>Back world</title>
      <url>https://tistory1.daumcdn.net/tistory/5063858/attach/1d5cdf6c7f9a4d62a9767cd45705355a</url>
      <link>https://pgmjun.tistory.com</link>
    </image>
    <item>
      <title>로깅(Logging)은 왜 해야할까?</title>
      <link>https://pgmjun.tistory.com/190</link>
      <description>&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;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhrIFb/btsLASPQJzR/uBs8zyMWF3NvIFqkoEx6x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhrIFb/btsLASPQJzR/uBs8zyMWF3NvIFqkoEx6x0/img.png&quot; data-alt=&quot;해당 게시글은 SOPT 35기 8차 세미나에서 후배 기수 대상으로 로깅에 대해 발표한 내용입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhrIFb/btsLASPQJzR/uBs8zyMWF3NvIFqkoEx6x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhrIFb%2FbtsLASPQJzR%2FuBs8zyMWF3NvIFqkoEx6x0%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;685&quot; height=&quot;385&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해당 게시글은 SOPT 35기 8차 세미나에서 후배 기수 대상으로 로깅에 대해 발표한 내용입니다.&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로깅이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;시스템이 동작할 때 &lt;b&gt;시스템의 상태 및 동작 정보&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;로그 레벨 (Log Level)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LdD4b/btsLAv1z9hT/tt1Ka7XptiF73RVuMqa621/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LdD4b/btsLAv1z9hT/tt1Ka7XptiF73RVuMqa621/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LdD4b/btsLAv1z9hT/tt1Ka7XptiF73RVuMqa621/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLdD4b%2FbtsLAv1z9hT%2Ftt1Ka7XptiF73RVuMqa621%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;476&quot; height=&quot;268&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;로그의 레벨은 총 5가지로 구분된다.&lt;/span&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;span&gt;Error : 예상하지 못한 심각한 문제가 발생하는 경우&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 연결 실패&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Warn : 애플리케이션이 정상적으로 작동하지만, 주의가 필요한 경우&lt;/span&gt;&lt;span&gt;&lt;/span&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;/li&gt;
&lt;li&gt;설정 파일이 누락되었으나 기본값 사용하여 동작은 가능&lt;/li&gt;
&lt;/ul&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;span&gt;Info : 운영에 참고할만한 사항&lt;/span&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;/li&gt;
&lt;li&gt;주요 작업의 완료 여부 (ex. 스케줄러)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Debug : 개발 단계에서 디버깅 용도&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 쿼리 로깅&lt;/li&gt;
&lt;li&gt;외부 API 호출 요청/응답 로깅&lt;/li&gt;
&lt;/ul&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;span&gt;Trace : Debug 레벨보다 더 세부적인 실행 흐름을 추적하는 경우&lt;/span&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;/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;&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;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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;로깅을 안하면 어떤 문제가 발생할까?&lt;/span&gt;&lt;/blockquote&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;h3 data-ke-size=&quot;size23&quot;&gt;예제1. 결제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;고객에게 이런 CS 문의가 들어왔습니다.&lt;br /&gt;&lt;b&gt;&amp;ldquo;제가 방금 결제를 했는데 돈은 나갔는데 상품 주문은 안됐어요!&amp;rdquo;&lt;/b&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;size18&quot;&gt;&lt;span&gt;&lt;span&gt;이 상황에서 로깅이 제대로 되어 있지 않다면, 다음과 같은 부분을 빠르게 인지할 수 없다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;로그가 남아있다면 아래처럼 빠르게 대처할 수 있었을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;rdquo;고객님께서 실제로 결제를 진행하셨지만, 결제 정보를 처리하는 시스템에서 오류가 발생해서 돈이 저희 서비스에 묶여있는 상태네요! 제가 처리해서 상품 주문 처리 해드리겠습니다.&amp;rdquo;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제2. 디버깅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;운영중인 서비스에서 에러가 발생했다.&lt;br /&gt;그런데 문제는, &lt;b&gt;에러가 발생한 메서드가 여러 API에서 공용으로 사용되고 있다.&lt;/b&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;size18&quot;&gt;&lt;span&gt;이 상황에서 로깅이 제대로 되어 있지 않다면, 다음과 같은 부분을 빠르게 인지할 수 없다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제가 발생한 API는 무엇인가?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;결국 이러한 문제가 발생한다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제가 발생한 메서드를 사용하는 모든 API를 일일히 점검&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;'유저 이탈'&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;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;로깅은 시스템의 &amp;lsquo;블랙박스&amp;rsquo; 다.&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;자동차의 블랙박스가 사고 원인을 파악하는데에 효과적인 도움을 주는 것처럼,&lt;br /&gt;로깅 또한 시스템에서 발생한 문제의 원인을 파악하는데에 효과적인 도움을 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇듯 로그는 운영에 있어서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;현재 상황에서 필요한 것에 대해서만 로깅하라&lt;/span&gt;&lt;span&gt;&lt;/span&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;span&gt;이 질문에 대한 답은 &quot;현재 필요한 것에 대해서만 로깅하라&quot;이다.&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;size18&quot;&gt;&lt;span&gt;불필요한 로그를 모두 남기게 되면 아래와 같은 문제로 이어질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;데이터 저장공간 절약&lt;/li&gt;
&lt;li&gt;필요한 로그만 빠르게 확인할 수 있어 분석에 용이&lt;/li&gt;
&lt;/ul&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;span&gt;필요한 로그는 어떻게 구분하는데요?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&gt;처음부터 필요한 로그를 구분하긴 어렵다. 몸으로 느껴라&lt;/span&gt;&lt;/span&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;&amp;nbsp;&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;&lt;span&gt;때문에 아래 방법을 통한 직접 몸으로 느끼는 방식을 추천한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 순서대로 직접 부딪히면서 개발해나가면 좋겠다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;API 요청/응답 로깅은 해두면 굉장히 편리해서 추천하는 편이다.&lt;br /&gt;관심이 있다면 &lt;a href=&quot;https://pgmjun.tistory.com/188&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&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;필요한 부분에 대해서 근거있게 로깅하며 개발해나갔으면 좋겠다!&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Infra</category>
      <category>logback</category>
      <category>logging</category>
      <category>SLF4j</category>
      <category>로그 레벨</category>
      <category>로깅</category>
      <category>로깅 왜 하나</category>
      <category>로깅하는 이유</category>
      <category>스프링부트 로깅</category>
      <category>스프링부트 로깅하는 이유</category>
      <category>자바 로깅</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/190</guid>
      <comments>https://pgmjun.tistory.com/190#entry190comment</comments>
      <pubDate>Mon, 30 Dec 2024 01:51:20 +0900</pubDate>
    </item>
    <item>
      <title>AWS Parameter Store로 SpringBoot 프로퍼티 값 암호화해서 관리하기</title>
      <link>https://pgmjun.tistory.com/189</link>
      <description>&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;516&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYXIR0/btsLrNgsvV4/DVs71U917V6o9IVdSf3AB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYXIR0/btsLrNgsvV4/DVs71U917V6o9IVdSf3AB1/img.png&quot; data-alt=&quot;Spring Cloud &amp;amp;amp; Parameter Store&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYXIR0/btsLrNgsvV4/DVs71U917V6o9IVdSf3AB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYXIR0%2FbtsLrNgsvV4%2FDVs71U917V6o9IVdSf3AB1%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;284&quot; height=&quot;288&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Spring Cloud &amp;amp; Parameter Store&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB password 등을 관리하는 application.yml 파일을 Github에 그대로 올리게 된다면 보안 상 문제가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하고 application.yml 값을 안전하게 다루기 위해 사람들은 다양한 방식을 적용한다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bohiGf/btsLqbiuuMt/jrR136WhPTzNs6lP4qxlw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bohiGf/btsLqbiuuMt/jrR136WhPTzNs6lP4qxlw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bohiGf/btsLqbiuuMt/jrR136WhPTzNs6lP4qxlw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbohiGf%2FbtsLqbiuuMt%2FjrR136WhPTzNs6lP4qxlw0%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;214&quot; height=&quot;52&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;현재 왓캠퍼스 프로젝트는 빠른 배포를 위해서 단순히&amp;nbsp;&lt;b&gt;application-prod.yml&lt;/b&gt;를 Git Actions Secrets을 통해 관리하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 CD 과정에서 artifact 파일을 생성하기 전, resources 패키지에 해당 파일을 저장해주는 방식을 사용했다.&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;562&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ULVeg/btsLrI7hO6q/JFqhyiYjqMFlKnkFX4yQb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ULVeg/btsLrI7hO6q/JFqhyiYjqMFlKnkFX4yQb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ULVeg/btsLrI7hO6q/JFqhyiYjqMFlKnkFX4yQb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FULVeg%2FbtsLrI7hO6q%2FJFqhyiYjqMFlKnkFX4yQb1%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;562&quot; height=&quot;192&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 그렇게 관리했을 때 특정 프로퍼티 값은 위와 같이 &lt;u&gt;읽을 수 없는 문제&lt;/u&gt;가 발생했다.&lt;br /&gt;&lt;b&gt;application-prod.yml&lt;/b&gt; 을 통해 &lt;u&gt;다른 로컬에서 작업하려면 직접 해당 파일을 전달해야 한다는 문제&lt;/u&gt;도 존재했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아무튼 이러한 설정으로 인해 구축해놓은 CI/CD 배포 자동 파이프라인이 정상적으로 동작하지 않았고&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이로 인해 로컬에서 bootJar를 통해 artifact 파일을 만들어 FTP 프로토콜을 통해 EC2에 파일을 전달 후 수동 배포하는 상황이었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; 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-filename=&quot;스크린샷 2024-12-21 15.11.52.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cke0YP/btsLsfjDCwZ/GFpg1Z8ZwKSihQbpYW39Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cke0YP/btsLsfjDCwZ/GFpg1Z8ZwKSihQbpYW39Hk/img.png&quot; data-alt=&quot;무료 이용 가능한 ParameterStore&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cke0YP/btsLsfjDCwZ/GFpg1Z8ZwKSihQbpYW39Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcke0YP%2FbtsLsfjDCwZ%2FGFpg1Z8ZwKSihQbpYW39Hk%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;614&quot; height=&quot;254&quot; data-filename=&quot;스크린샷 2024-12-21 15.11.52.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;무료 이용 가능한 ParameterStore&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;과거에는 이러한 문제를 해결하기 위해 과거에는 AWS Cloud에 private 값을 저장해서 사용할 수 있는 AWS SecretsManager를 사용했었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SecretsManager는 유료 서비스이기 때문에 &lt;u&gt;무료로 이용할 수 있는&lt;/u&gt; &lt;b&gt;AWS Parameter Store&lt;/b&gt;를 사용하여 문제에 해결책을 제시고자 하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;설정 난이도또한 Secrets Manager보다 쉽다고 느껴서 사용해보면 좋을 것 같다고 느꼈다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Secrets Manager를 사용하는 방법도 궁금하다면 &lt;a href=&quot;https://pgmjun.tistory.com/49&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;적용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. AWS Parameter Store에 프로퍼티 값 추가&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-20 21.09.38.png&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHJ2ht/btsLtsvJZsJ/EmOepCRwD0lp4ktYm17kB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHJ2ht/btsLtsvJZsJ/EmOepCRwD0lp4ktYm17kB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHJ2ht/btsLtsvJZsJ/EmOepCRwD0lp4ktYm17kB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHJ2ht%2FbtsLtsvJZsJ%2FEmOepCRwD0lp4ktYm17kB1%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;644&quot; height=&quot;289&quot; data-filename=&quot;스크린샷 2024-12-20 21.09.38.png&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS ParameterStore 접속&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-filename=&quot;스크린샷 2024-12-21 14.37.23.png&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;1462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMDxzP/btsLqz4tg0Y/rX3sHXPaGuKguWNuO2K331/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMDxzP/btsLqz4tg0Y/rX3sHXPaGuKguWNuO2K331/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMDxzP/btsLqz4tg0Y/rX3sHXPaGuKguWNuO2K331/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMDxzP%2FbtsLqz4tg0Y%2FrX3sHXPaGuKguWNuO2K331%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;623&quot; height=&quot;455&quot; data-filename=&quot;스크린샷 2024-12-21 14.37.23.png&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;1462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터 생성 시에 가장 중요한 것 중 하나는 이름이다. 이름은 &lt;u&gt;계층 구조&lt;/u&gt;로 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수십 또는 수백 개의 매개변수를 Flat 목록으로 관리하는 것은 전체 값을 다룰 때 시간이 많이 걸릴 수도 있고&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;때문에 ParameterStore는 파라미터를 &lt;u&gt;계층으로 관리&lt;/u&gt;하여 매개변수를 구성할 수 있도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734759912734&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/{APP_NAME}/secrets_{PROFILE}/{KEY_NAME}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 &lt;b&gt;15개&lt;/b&gt;의 레벨 계층을 만들 수 있으며 본인은 위와 같은 형태로 계층을 나눠보았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;APP_NAME&lt;/b&gt; : 서비스하는 애플리케이션 이름&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PROFILE&lt;/b&gt; : 사용할 환경 (ex. prod, dev, test)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;KEY_NAME&lt;/b&gt; : 파라미터 값의 구분자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;더 자세한 내용은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-hierarchies.html&quot;&gt;공식문서&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 14.37.46.png&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;1692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boK59R/btsLsAA3yTG/ZGnejS77yRTR86LIRwynEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boK59R/btsLsAA3yTG/ZGnejS77yRTR86LIRwynEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boK59R/btsLsAA3yTG/ZGnejS77yRTR86LIRwynEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboK59R%2FbtsLsAA3yTG%2FZGnejS77yRTR86LIRwynEK%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;601&quot; height=&quot;482&quot; data-filename=&quot;스크린샷 2024-12-21 14.37.46.png&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;1692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 값을 입력했다면 &lt;b&gt;'유형'&lt;/b&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;'유형'은 3가지 값이 있는데 보안이 필요한 값이라면 &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;h4 data-ke-size=&quot;size20&quot;&gt;KMS (AWS Key Management Service)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 암호화에 사용되는 암호화 키를 쉽게 생성하고 제어할 수 있는 AWS 서비스이다.&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;613&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xLgAj/btsLs9wi8U9/202Ma24qnKxkUADVKa1F20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xLgAj/btsLs9wi8U9/202Ma24qnKxkUADVKa1F20/img.png&quot; data-alt=&quot;Key 암호화 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xLgAj/btsLs9wi8U9/202Ma24qnKxkUADVKa1F20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxLgAj%2FbtsLs9wi8U9%2F202Ma24qnKxkUADVKa1F20%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;493&quot; height=&quot;161&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Key 암호화 구조&lt;/figcaption&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;키를 암호화하는 경우 암호화 키도 보호해야 하고, 데이터 보호 계층에서 가장 높은 레벨에 있는 암호화 키(Root Key)를 보호해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 AWS KMS가 사용된다.&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;613&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LaM4g/btsLqzQYwuL/ncXLX2QljCGGClzdwgXyc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LaM4g/btsLqzQYwuL/ncXLX2QljCGGClzdwgXyc1/img.png&quot; data-alt=&quot;KMS의 Root Key 암호화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LaM4g/btsLqzQYwuL/ncXLX2QljCGGClzdwgXyc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLaM4g%2FbtsLqzQYwuL%2FncXLX2QljCGGClzdwgXyc1%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;498&quot; height=&quot;178&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;KMS의 Root Key 암호화&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 암호화 구조에서 KMS는 Root Key를 보호한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMS는 AWS 내에서 완전히 생성, 관리, 사용 및 삭제된다.&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://docs.aws.amazon.com/kms/latest/developerguide/overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&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;h4 data-ke-size=&quot;size20&quot;&gt;SSM (AWS Systems Manager)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서 제공하는 리소스 및 애플리케이션 관리를 자동화하고 운영 작업을 간소화하기 위한 서비스 중 하나로 &lt;b&gt;Parameter Store&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;2. AWS IAM 역할 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으론 AWS에서 역할을 생성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할은 AWS 서비스나 애플리케이션이 특정 리소스에 접근할 수 있는 권한을 부여하는 IAM 엔티티이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터 SSM에 접근 가능한 IAM 역할을 생성하여 서비스의 EC2에 적용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[필수 참고 내용]&lt;br /&gt;&lt;/b&gt;현재 개발중인 왓캠퍼스의 CI/CD는 Git Actions에서 동작하고 있으며,&lt;br /&gt;특히 artifact 파일을 EC2에서 실행시키는 CD는 Self Hosted Runner를 통해 EC2에서 직접 동작하고 있다.&lt;br /&gt;때문에 본인은 AWS에 IAM 역할을 적용해주는 것만으로 설정을 마칠 수 있지만, 나와 같은 환경이 아니라면 CD 스크립트에 추가적인 설정이 필요할 수 있다.&lt;br /&gt;&lt;br /&gt;Self Hosted Runner에 대한 내용은 &lt;a href=&quot;https://pgmjun.tistory.com/172&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IAM 역할 생성&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 15.13.13.png&quot; data-origin-width=&quot;2310&quot; data-origin-height=&quot;1450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EPBir/btsLtc7Div9/PkpUWrH0EYiJKbE9ryb93K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EPBir/btsLtc7Div9/PkpUWrH0EYiJKbE9ryb93K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EPBir/btsLtc7Div9/PkpUWrH0EYiJKbE9ryb93K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEPBir%2FbtsLtc7Div9%2FPkpUWrH0EYiJKbE9ryb93K%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;720&quot; height=&quot;452&quot; data-filename=&quot;스크린샷 2024-12-21 15.13.13.png&quot; data-origin-width=&quot;2310&quot; data-origin-height=&quot;1450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 역할 생성 클릭 후 &lt;b&gt;AWS 서비스 엔터티 유형&lt;/b&gt; 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 사례는 &lt;b&gt;EC2&lt;/b&gt;를 선택해주면 된다.&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-filename=&quot;스크린샷 2024-12-21 15.13.31.png&quot; data-origin-width=&quot;2372&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba4Wjz/btsLrGu2GG5/esRT66HxpHtU3o6WKcrctK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba4Wjz/btsLrGu2GG5/esRT66HxpHtU3o6WKcrctK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba4Wjz/btsLrGu2GG5/esRT66HxpHtU3o6WKcrctK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba4Wjz%2FbtsLrGu2GG5%2FesRT66HxpHtU3o6WKcrctK%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;2372&quot; height=&quot;906&quot; data-filename=&quot;스크린샷 2024-12-21 15.13.31.png&quot; data-origin-width=&quot;2372&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSM(AWS Systems Manager)에 대한 ReadOnlyAccess 권한 정책을 선택&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;만약 더 많은 권한이 필요하다면 &lt;b&gt;AmazonSSMFullAccess&lt;/b&gt; 권한 정책을 선택하면 된다.&lt;/blockquote&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-filename=&quot;스크린샷 2024-12-21 15.14.51.png&quot; data-origin-width=&quot;2408&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yVUkq/btsLrd0Xukx/quFBNpAR6ma52BlAyKgKaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yVUkq/btsLrd0Xukx/quFBNpAR6ma52BlAyKgKaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yVUkq/btsLrd0Xukx/quFBNpAR6ma52BlAyKgKaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyVUkq%2FbtsLrd0Xukx%2FquFBNpAR6ma52BlAyKgKaK%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;2408&quot; height=&quot;800&quot; data-filename=&quot;스크린샷 2024-12-21 15.14.51.png&quot; data-origin-width=&quot;2408&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;&amp;nbsp;&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;EC2 역할 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 EC2에 방금 생성한 역할을 적용한다.&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-filename=&quot;스크린샷 2024-12-21 15.15.43.png&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5qRP9/btsLstB0Jsr/CCHLJUhpcNBe3VKaJCabkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5qRP9/btsLstB0Jsr/CCHLJUhpcNBe3VKaJCabkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5qRP9/btsLstB0Jsr/CCHLJUhpcNBe3VKaJCabkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5qRP9%2FbtsLstB0Jsr%2FCCHLJUhpcNBe3VKaJCabkk%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;305&quot; height=&quot;272&quot; data-filename=&quot;스크린샷 2024-12-21 15.15.43.png&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용할 EC2를 선택 - 작업 - 보안 - IAM 역할 수정&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-filename=&quot;스크린샷 2024-12-21 15.15.58.png&quot; data-origin-width=&quot;2928&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbmjMr/btsLrfqVKNb/rikUbFwKU6bR42jWHQ8HU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbmjMr/btsLrfqVKNb/rikUbFwKU6bR42jWHQ8HU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbmjMr/btsLrfqVKNb/rikUbFwKU6bR42jWHQ8HU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbmjMr%2FbtsLrfqVKNb%2FrikUbFwKU6bR42jWHQ8HU1%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;706&quot; height=&quot;203&quot; data-filename=&quot;스크린샷 2024-12-21 15.15.58.png&quot; data-origin-width=&quot;2928&quot; data-origin-height=&quot;842&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;&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;h3 data-ke-size=&quot;size23&quot;&gt;(Optional) 로컬 환경 세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 ParameterStore 설정 값을 로컬에서도 이용하려면 IAM을 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSM 관련 권한을 가진 IAM 사용자를 생성하고, 로컬에 AWS Configuration 작업을 통해 IAM의 권한을 등록해주어야 한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;AWS IAM 사용자 생성&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 15.17.41.png&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSm3RB/btsLqXRB97R/BIVFS99giQ6cIL2B9PEgyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSm3RB/btsLqXRB97R/BIVFS99giQ6cIL2B9PEgyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSm3RB/btsLqXRB97R/BIVFS99giQ6cIL2B9PEgyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSm3RB%2FbtsLqXRB97R%2FBIVFS99giQ6cIL2B9PEgyk%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;281&quot; height=&quot;163&quot; data-filename=&quot;스크린샷 2024-12-21 15.17.41.png&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;356&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 15.18.04.png&quot; data-origin-width=&quot;2372&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNAntp/btsLsfYedtp/KAne4fTRdrL8K6C1d53r3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNAntp/btsLsfYedtp/KAne4fTRdrL8K6C1d53r3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNAntp/btsLsfYedtp/KAne4fTRdrL8K6C1d53r3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNAntp%2FbtsLsfYedtp%2FKAne4fTRdrL8K6C1d53r3k%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;2372&quot; height=&quot;916&quot; data-filename=&quot;스크린샷 2024-12-21 15.18.04.png&quot; data-origin-width=&quot;2372&quot; data-origin-height=&quot;916&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 15.18.20.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U31mR/btsLrLb49VR/kPDItu7kHkHMqOvMttGDgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U31mR/btsLrLb49VR/kPDItu7kHkHMqOvMttGDgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U31mR/btsLrLb49VR/kPDItu7kHkHMqOvMttGDgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU31mR%2FbtsLrLb49VR%2FkPDItu7kHkHMqOvMttGDgk%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;711&quot; height=&quot;380&quot; data-filename=&quot;스크린샷 2024-12-21 15.18.20.png&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할과 똑같이 용도에 맞게 &lt;b&gt;AmazonSSMReadOnlyAccess&lt;/b&gt; 또는 &lt;b&gt;AmazonSSMFullAccess&lt;/b&gt; 중 하나를 선택하면 된다.&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-filename=&quot;스크린샷 2024-12-21 15.18.29.png&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;1304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yXuZR/btsLrcHLp6z/k3fphW8EthSY9f80HRXwqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yXuZR/btsLrcHLp6z/k3fphW8EthSY9f80HRXwqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yXuZR/btsLrcHLp6z/k3fphW8EthSY9f80HRXwqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyXuZR%2FbtsLrcHLp6z%2Fk3fphW8EthSY9f80HRXwqk%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;771&quot; height=&quot;426&quot; data-filename=&quot;스크린샷 2024-12-21 15.18.29.png&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;1304&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 15.18.57.png&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfGnik/btsLrXQYS5a/BWw2ZALbKxuNWkd5003Ggk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfGnik/btsLrXQYS5a/BWw2ZALbKxuNWkd5003Ggk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfGnik/btsLrXQYS5a/BWw2ZALbKxuNWkd5003Ggk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfGnik%2FbtsLrXQYS5a%2FBWw2ZALbKxuNWkd5003Ggk%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;510&quot; height=&quot;96&quot; data-filename=&quot;스크린샷 2024-12-21 15.18.57.png&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;186&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 15.19.04.png&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JtHCG/btsLqNVSxVE/rZvyem6T5Kjv7xRSbEZCn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JtHCG/btsLqNVSxVE/rZvyem6T5Kjv7xRSbEZCn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JtHCG/btsLqNVSxVE/rZvyem6T5Kjv7xRSbEZCn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJtHCG%2FbtsLqNVSxVE%2FrZvyem6T5Kjv7xRSbEZCn0%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;625&quot; height=&quot;56&quot; data-filename=&quot;스크린샷 2024-12-21 15.19.04.png&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 csv 파일인 엑세스 키를 발급받으면 된다.&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;h4 data-ke-size=&quot;size20&quot;&gt;AWS Configuration&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 흭득한 IAM 사용자 엑세스 키를 활용해서 로컬 환경에 configuration 을 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련된 내용은 &lt;a href=&quot;https://pgmjun.tistory.com/30&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전에 작성해둔 포스팅&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;h3 data-ke-size=&quot;size23&quot;&gt;3. SpringBoot 환경 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 SpringBoot 환경 설정을 통해서 Parameter Store에 등록해놓은 파라미터 값을 사용하기만 하면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;해당 내용은 코프링 프로젝트이기 때문에 &lt;b&gt;build.gradle.kts&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Spring Cloud 의존성 추가&lt;/h4&gt;
&lt;pre id=&quot;code_1734713628849&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation(platform(&quot;io.awspring.cloud:spring-cloud-aws-dependencies:3.2.0&quot;))
    implementation(&quot;io.awspring.cloud:spring-cloud-aws-starter-parameter-store&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle.kts 에 &lt;b&gt;'Spring Cloud AWS BOM'&lt;/b&gt;과 &lt;b&gt;'Spring Cloud AWS'&lt;/b&gt;에서 제공하는 Parameter Store의 의존성을 추가한다.&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;Spring Cloud AWS BOM 의존성은 그냥 사용해서는 안되고 버전과 함께 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;implementation(platform())&lt;/b&gt;은 'BOM(Bill of Meterials)'를 나타내며 BOM은 '자재 명세서' 라는 뜻을 가진다.&lt;br /&gt;BOM에 관련 버전을 명시해줌으로써 spring-cloud-aws에 포함된 의존성을 사용할 때 어떤 버전에 호환되는 의존성을 가져올 지 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 Parameter Store 의존성의 버전이 자동으로 맞춰질 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Could not find org.springframework.cloud:spring-cloud-starter-aws-parameter-store-config: Required by: project&lt;/blockquote&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;자신의 스프링부트 버전에 맞는 Spring Cloud AWS의 버전은 &lt;a href=&quot;https://github.com/awspring/spring-cloud-aws?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github 문서&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-21 22.21.33.png&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FTXPo/btsLtwLKPyO/Tr6vK1zMQCI3AlIhaKaH81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FTXPo/btsLtwLKPyO/Tr6vK1zMQCI3AlIhaKaH81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FTXPo/btsLtwLKPyO/Tr6vK1zMQCI3AlIhaKaH81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFTXPo%2FbtsLtwLKPyO%2FTr6vK1zMQCI3AlIhaKaH81%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;624&quot; height=&quot;347&quot; data-filename=&quot;스크린샷 2024-12-21 22.21.33.png&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 사용하는 Spring Boot 버전은 3.3.x 버전이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 내 프로젝트에 호환되는 Spring Cloud AWS 버전은 3.2.x 버전이며 때문에 &lt;b&gt;3.2.0&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;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;application.yml 파일 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1734713645487&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  config:
    activate:
      on-profile: prod
    import: aws-parameterstore:/whatcampus/secrets_prod/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 application.yml 파일에서 Parameter Store의 환경변수를 끌어다가 사용하기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 위와 같이 application-prod.yml 에 &lt;b&gt;spring:config:import&lt;/b&gt; 를 통해서 파라미터 값을 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'aws-parameterstore:{파라미터_계층}'&lt;/b&gt;&amp;nbsp;을 입력해주면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;나는 굉장히 가볍게 설정했지만 좀 더 자세한 설정이 알고 싶다면 &lt;a href=&quot;https://docs.spring.io/spring-cloud-config/reference/server/environment-repository/aws-parameter-store-backend.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 확인하자&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734713692375&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}&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;&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. 정상 동작 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0EltM/btsLp8lJOOC/3fYNWC136XiCk0cKBoDLJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0EltM/btsLp8lJOOC/3fYNWC136XiCk0cKBoDLJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0EltM/btsLp8lJOOC/3fYNWC136XiCk0cKBoDLJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0EltM%2FbtsLp8lJOOC%2F3fYNWC136XiCk0cKBoDLJK%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;572&quot; height=&quot;209&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;466&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;build되고 artifact 파일이 실행되어 배포가 수행된 것을 확인할 수 있었다.&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;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 private한 값을 AWS의 서비스 중 하나인 ParameterStore를 통해 무료로 암호화하여 관리하는 방법에 대해서 알아보았다.&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;-&amp;nbsp;&lt;a href=&quot;https://gong-story.tistory.com/45&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://gong-story.tistory.com/45&lt;/a&gt;&lt;br /&gt;-&amp;nbsp;&lt;a href=&quot;https://treecode.tistory.com/122&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://treecode.tistory.com/122&lt;/a&gt;&lt;br /&gt;-&amp;nbsp;&lt;a href=&quot;https://jojoldu.tistory.com/509&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jojoldu.tistory.com/509&lt;/a&gt;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>aws parameter store</category>
      <category>aws parameter store springboot</category>
      <category>aws parameter store 스프링부트</category>
      <category>AWS Secrets Manager</category>
      <category>parameter store 스프링에서</category>
      <category>springboot parameter store</category>
      <category>스프링부트 aws parameter store</category>
      <category>스프링부트 parameter store</category>
      <category>스프링부트 환경변수 aws</category>
      <category>스프링부트 환경변수 관리</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/189</guid>
      <comments>https://pgmjun.tistory.com/189#entry189comment</comments>
      <pubDate>Sat, 21 Dec 2024 22:55:04 +0900</pubDate>
    </item>
    <item>
      <title>스프링부트 Filter로 요청/응답 정보 로깅 시스템 구축하기</title>
      <link>https://pgmjun.tistory.com/188</link>
      <description>&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;300&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPFlkR/btsLoh3pl3z/U7ATUGljmknlXTxYl1uRzk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPFlkR/btsLoh3pl3z/U7ATUGljmknlXTxYl1uRzk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPFlkR/btsLoh3pl3z/U7ATUGljmknlXTxYl1uRzk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPFlkR%2FbtsLoh3pl3z%2FU7ATUGljmknlXTxYl1uRzk%2Fimg.jpg&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;300&quot; height=&quot;300&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;300&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;h2 data-ke-size=&quot;size26&quot;&gt;작업 배경&lt;/h2&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;b&gt;로깅&lt;/b&gt;을 통해&amp;nbsp;&lt;/span&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;/b&gt;은 실제로 서비스를 운영하면서 버그가 발생했을 때 해당 버그가 어떤 요청에서 비롯되어 어떻게 처리되다가 버그가 발생된 것인지를 쉽게 확인할 수 있도록 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;'어떤 요청을 받고 있는지', '적절한 응답을 보내고 있는지', '수상한 요청(Dos, DDos 등)은 없는지', '요청에 소요된 시간이 길진 않은지'&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 오늘은 스프링부트에서 Request/Response 정보를 로깅하는 방법에 대해서 알아볼 것이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;AOP 로깅 vs Filter 로깅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 먼저 요청/응답 정보를 무엇을 통해 로깅해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 떠올랐던 것은 Filter와 AOP가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;AOP 로깅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP는 공통관심사를 별도의 모듈로 분리해서 비즈니스 로직에서 반복되는 코드를 제거하여 유지보수성을 높여주는 스프링의 장점 중 하나이다. AOP를 통해 Controller의 RequestMapping 애노테이션을 Pointuct으로 Controller 메서드를 호출했을 때 메서드를 호출한 요청 정보, 그리고 메서드 호출 이후에 응답 정보를 로깅할 수 있다.&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;Filter 로깅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터는 DispatcherServlet이 '동작하기 전'과 '동작한 후' 에서 작동한다.&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AOP&lt;/b&gt;로 요청 정보에 대한 로깅을 수행한다면 &lt;u&gt;'@RestController 애노테이션이 붙은 클래스의 @RequestMapping 애노테이션이 붙은 메서드'&lt;/u&gt; 로 Pointcut을 잡아서, Request 및 Response에 대한 로그를 찍어주면 되고&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;Filter&lt;/b&gt;로 로깅을 수행한다면 Filter 인터페이스를 implements 하여, Request 및 Response에 대한 로그를 찍어주도록 구현하면 된다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VR6I1/btsLmT76i7f/dzxnKnDqml4gGAvBd8GSW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VR6I1/btsLmT76i7f/dzxnKnDqml4gGAvBd8GSW0/img.png&quot; data-alt=&quot;서버 정보를 빼가기 위한 공격성 요청&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VR6I1/btsLmT76i7f/dzxnKnDqml4gGAvBd8GSW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVR6I1%2FbtsLmT76i7f%2FdzxnKnDqml4gGAvBd8GSW0%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;400&quot; height=&quot;276&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버 정보를 빼가기 위한 공격성 요청&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다만 AOP로 구현하는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;존재하지 않는 Endpoint로 들어오는 요청은 로깅할 수 없는 문제&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;b&gt;존재하지 않는 정적 리소스를 조회하려고 시도한 요청의 에러 로그&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 중요한 정보가 담겨있는 파일을 조회하여 탈취하기 위해 무작위 Endpoint로 요청을 마구 보내는 일종의 공격성 요청 기록인데 존재하지 않는 Endpoint이기 때문에 HandlerMapping이 수행되지 않고, 그렇기 때문에 컨트롤러의 메서드가 실행되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;'누가' '어떤 정보'&lt;/b&gt;를 담아서 요청을 보낸 것인지 정확한 모니터링이 되지 않았다.&lt;/u&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;DispatcherServlet&lt;/b&gt;에 요청이 도달하기 전 요청 정보, 그리고 요청이 처리된 후 응답 정보를 모두 로깅할 수 있는 '&lt;b&gt;Filter 방식'&lt;/b&gt;을 통해 서버로 들어오는 모든 요청에 대해서 로깅 시스템을 구축해보고자 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;물론 Filter로 로깅하는 것뿐만 아니라, 존재하는 API 이외의 요청은 서버에서 받지 않도록 별도의 처리를 해주어야 한다고도 생각한다. 이에 대한 자세한 내용은 여기에서 확인할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Filter란?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-17 16.24.26.png&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TSug0/btsLmoOcRLk/wIDReJeGCazy0M4cz3vtcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TSug0/btsLmoOcRLk/wIDReJeGCazy0M4cz3vtcK/img.png&quot; data-alt=&quot;jakarta.servlet.Filter&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TSug0/btsLmoOcRLk/wIDReJeGCazy0M4cz3vtcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTSug0%2FbtsLmoOcRLk%2FwIDReJeGCazy0M4cz3vtcK%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;694&quot; height=&quot;350&quot; data-filename=&quot;스크린샷 2024-12-17 16.24.26.png&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;jakarta.servlet.Filter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Filter는 스프링 컨테이너의 &lt;u&gt;DispatcherServlet에 요청이 도달하기 전&lt;/u&gt;&amp;nbsp;그리고 &lt;u&gt;DispatcherServlet에서 요청이 처리된 이후&lt;/u&gt; 시점에 존재하는 요청 &lt;b&gt;사후/사전 처리 작업 객체&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;servlet 패키지에 포함되어 있는 필터는 Filter 인터페이스를 implements 하여 직접 구현할 수 있다.&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;Filter 인터페이스&lt;/b&gt;에 들어가보면 위와 같이 설명이 되어있는데 &lt;u&gt;요청/응답에 대한 로깅 필터&lt;/u&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;Filter의 메서드&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;init()&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;default void init(FilterConfig filterConfig) throws ServletException&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;Filter가 정상적으로 서비스에 배치되고 있음을 나타내기 위해 웹 컨테이너에 의해 호출되는 메서드&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿 컨테이너는 Filter를 인스턴스화한 후 init() 메소드를 정확히 한 번 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;init 메소드가 성공적으로 완료되어야 Filter가 Filtering 작업을 수행하도록 요청할 수 있다.&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;br /&gt;destroy()&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;haskell&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;default void destroy()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;doFilter() 메소드 내의 모든 스레드가 종료되거나 제한 시간이 경과한 후에 로직이 종료되었음을 알리기 위해 호출되는 메서드&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;doFilter&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;doFIlter()&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;유저가 서버 리소스를 흭득하기 위해 보낸 요청으로 인해 요청/응답 정보 쌍이 FilterChain을 통해 전달될 때마다 컨테이너에 의해 호출되는 메서드&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;ServletRequest, ServletResposne 를 통해 응답 요청 정보를 활용할 수 있으며,&amp;nbsp;&lt;/span&gt;FilterChain 매개변수를 통해 체인의 다음 엔티티로 요청/응답 정보를 전달할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;적용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Filter를 통해 적용해보는 과정에서 몇 가지 시행착오를 겪고 이에 대한 과정과 결론을 정리해보고자 한다.&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;1. Request Body 정보 캐싱 처리&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Throws(IOException::class, ServletException::class)
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드는 Filter에서 로깅에 사용할 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;doFilter()&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ServletRequest를 통해 요청 정보를 로깅할 수 있는데 이때 요청 정보는 InputStream 형태로 들어오며 Stream은 단 한 번만 읽을 수 있다.&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;때문에 InputStream을 로깅 시점, 요청 정보 사용 시점에서 모두 사용하려면 캐싱 처리를 수행해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;캐싱으로 인해 추가적인 메모리를 사용하게 되지만 요청이 거의 없는 현재 프로젝트 구조에서는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;나쁘지 않은 선택이라고 생각한다.&lt;/span&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;고민1 - ContentCachingRequestWrapper, ContentCachingResponseWrapper&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 방법으로 Spring에서 제공하는 &lt;b&gt;ContentCachingWrapper&lt;/b&gt;를 고민해보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Filter 로깅과 관련해서 찾아보면 주로 나오는 것이&lt;span&gt;&amp;nbsp;&lt;/span&gt;ContentCachingRequestWrapper이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 객체는 HttpServletRequest 정보를 Wrapper 패턴으로 감싸고, InputStream 값을 캐싱하여 여러 번 사용할 수 있도록 만들어주는 유용한 도구이다.&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;size18&quot;&gt;ContentCachingWrapper의 문제점&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;요청 정보에 대한 InputStream을&amp;nbsp;&lt;b&gt;ContentCachingWrapper&lt;/b&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wWvdM/btsLnnuBJ8h/87minWxVYOslmAHWxS2pSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wWvdM/btsLnnuBJ8h/87minWxVYOslmAHWxS2pSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wWvdM/btsLnnuBJ8h/87minWxVYOslmAHWxS2pSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwWvdM%2FbtsLnnuBJ8h%2F87minWxVYOslmAHWxS2pSk%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;735&quot; height=&quot;88&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ContentCachingWrapper를 사용하면 Stream의 값이 캐싱되어 맘껏 값을 사용해도 된다고 생각했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그게 아니라 해당 InputStream을 읽으면 해당 내용이 클래스 내부 필드인 cachedContent에 캐싱되는 구조였다.&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-filename=&quot;스크린샷 2024-12-18 00.35.50.png&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y6BuF/btsLmswGMd7/XBIJbxzciNhbptGxsAuNrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y6BuF/btsLmswGMd7/XBIJbxzciNhbptGxsAuNrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y6BuF/btsLmswGMd7/XBIJbxzciNhbptGxsAuNrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY6BuF%2FbtsLmswGMd7%2FXBIJbxzciNhbptGxsAuNrK%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;424&quot; height=&quot;83&quot; data-filename=&quot;스크린샷 2024-12-18 00.35.50.png&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이후부터는 &lt;b&gt;getContentAsByteArray()&lt;/b&gt; 메서드를 통해 캐시된 Stream 값을 사용할 수 있는 구조이다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3106&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebNTjF/btsLmqr4nVH/k2t99mSU0r6F6XEkT8YWiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebNTjF/btsLmqr4nVH/k2t99mSU0r6F6XEkT8YWiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebNTjF/btsLmqr4nVH/k2t99mSU0r6F6XEkT8YWiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebNTjF%2FbtsLmqr4nVH%2Fk2t99mSU0r6F6XEkT8YWiK%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;3106&quot; height=&quot;724&quot; data-origin-width=&quot;3106&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream은 1회성인데 Filter에서 읽어버리면 위와 같이 로깅은 성공하지만 Controller에서는 HttpMessageNotReadableException 예외가 발생하면서 사용할 수 없게 된다. (Controller에서 getContentAsByteArray() 메서드를 사용하진 않으니까)&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-filename=&quot;스크린샷 2024-12-18 00.52.33.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQwwC2/btsLmlYKxdM/pr2kk2RUMZaekkWJvwszK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQwwC2/btsLmlYKxdM/pr2kk2RUMZaekkWJvwszK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQwwC2/btsLmlYKxdM/pr2kk2RUMZaekkWJvwszK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQwwC2%2FbtsLmlYKxdM%2Fpr2kk2RUMZaekkWJvwszK0%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;211&quot; height=&quot;300&quot; data-filename=&quot;스크린샷 2024-12-18 00.52.33.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;860&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;고민2 - 커스텀 Request Body 캐싱 객체 구현하기 ✅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 대안으로 찾은 방식은 커스텀으로 Request Body를 캐싱하는 객체를 구현하는 방법이다.&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;size18&quot;&gt;CachedBodyHttpServletRequest 구현&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import jakarta.servlet.ReadListener
import jakarta.servlet.ServletInputStream
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletRequestWrapper
import java.io.ByteArrayInputStream
import java.io.IOException

class CachedBodyHttpServletRequest(request: HttpServletRequest) : HttpServletRequestWrapper(request) {

    private val cachedBody: ByteArray

    init {
        cachedBody = request.inputStream.readAllBytes()
    }

    @Throws(IOException::class)
    override fun getInputStream(): ServletInputStream {
        return object : ServletInputStream() {
            private val byteArrayInputStream = ByteArrayInputStream(cachedBody)

            override fun read(): Int {
                return byteArrayInputStream.read()
            }

            override fun isFinished(): Boolean {
                return byteArrayInputStream.available() &amp;lt;= 0
            }

            override fun isReady(): Boolean {
                return true
            }

            override fun setReadListener(listener: ReadListener?) {
                // 비동기 처리가 필요하다면 구현을 추가
            }
        }
    }

    fun getBody(): String {
        return String(cachedBody, Charsets.UTF_8)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 &lt;b&gt;ContentCachingWrapper &lt;/b&gt;의 문제를 해결하기 위해 직접 구현한 객체이다.&lt;span style=&quot;color: #333333; 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;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;ContentCachingWrapper&lt;/b&gt;와 동일하게 HttpServletRequestWrapper를 상속받아 구현하였기에 대체로 사용할 수 있다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 인스턴스가 생성되는 시점에 'init 블럭'을 통해서 cachedBody에 InputStream의 byte 값을 모두 캐싱해버린다.&lt;/li&gt;
&lt;li&gt;getInputStream() 메서드를 override 하여, 해당 메서드 호출 시 &lt;u&gt;항상&lt;/u&gt; 캐싱해둔 byte 값을 통해 ServletInputStream 객체를 만들어 return 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 InputStream을 담아서 인스턴스를 생성하면 그 시점부터 항상 캐싱된 cachedBody 값을 사용할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 컨트롤러에서 getInputStream() 메서드를 한 번 호출 하고, 그 이후에 getContentAsByteArray() 를 통해 캐싱된 body값을 얻을 수 있는 &lt;b&gt;ContentCachingWrapper&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;h3 data-ke-size=&quot;size23&quot;&gt;2. 요청정보 로깅 Filter 구현하기&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import com.whatcampus.whatcampus.logger
import jakarta.servlet.*
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.slf4j.MDC
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import java.io.IOException
import java.util.*

private val X_FORWARDED_FOR_HEADER = &quot;X-Forwarded-For&quot;
private val TRACE_ID = &quot;traceId&quot;

@Component
@Order(1)
class RequestLoggingFilter : Filter {

    val log = logger()

    @Throws(IOException::class, ServletException::class)
    override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain) {
        val request = CachedBodyHttpServletRequest(servletRequest as HttpServletRequest)
        val response = servletResponse as HttpServletResponse
        MDC.put(TRACE_ID, UUID.randomUUID().toString())

        val requestLine = &quot;${request.method} ${request.requestURI}${getRequestParams(request)}&quot;
        log.info(&quot;Request (${getRequestAddr(request)}) : $requestLine ${request.getBody()}&quot;)
        filterChain.doFilter(request, response)
        log.info(&quot;Response : $requestLine : ${response.status}&quot;)
    }

    fun getRequestAddr(request: HttpServletRequest): String {
        val requestAddr = request.getHeader(X_FORWARDED_FOR_HEADER)
        return requestAddr ?: request.remoteAddr
    }

    fun getRequestParams(request: HttpServletRequest): String {
        var queryParameters = request.parameterMap
            .entries
            .joinToString(&quot;&amp;amp;&quot;) { (key, values) -&amp;gt; &quot;$key=${values[0]}&quot; }

        if (queryParameters.isNotEmpty()) {
            queryParameters = &quot;?$queryParameters&quot;
        }
        return queryParameters
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 구현한 CachedBodyHttpServletRequest 를 바탕으로 구현된 요청 응답 정보 로깅 Filter이다.&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;FilterChain에 Filter 등록&lt;/h4&gt;
&lt;pre id=&quot;code_1734596666184&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@Order(1)
class RequestLoggingFilter : Filter {&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Component&lt;/b&gt; 애노테이션을 선언해주면 컴포넌트 스캔에 의해 Bean으로 등록된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bean으로 등록된 Filter 타입 객체는 &lt;b&gt;Spring Boot AutoConfiguration&lt;/b&gt;에 의해 &lt;u&gt;자동으로 FilterChain에 추가된다.&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Order&lt;/b&gt; 애노테이션과 &lt;b&gt;order 매개변수 값&lt;/b&gt;을를 입력해줌으로써 FilterChain에서 몇 번째 순서로 동작할 지를 정의할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;order를 1로 설정해주었기 때문에 가장 앞단에서 RequestLoggingFilter는 동작하는 필터가 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;h4 data-ke-size=&quot;size20&quot;&gt;doFilter()&lt;/h4&gt;
&lt;pre id=&quot;code_1734594555463&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Throws(IOException::class, ServletException::class)
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain) {
    val request = CachedBodyHttpServletRequest(servletRequest as HttpServletRequest)
    val response = servletResponse as HttpServletResponse
    MDC.put(TRACE_ID, UUID.randomUUID().toString())

    val requestLine = &quot;${request.method} ${request.requestURI}${getRequestParams(request)}&quot;
    log.info(&quot;Request (${getRequestAddr(request)}) : $requestLine ${request.getBody()}&quot;)
    filterChain.doFilter(request, response)
    log.info(&quot;Response : $requestLine : ${response.status}&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청 정보를 HttpServletRequest로 캐스팅하여 CachedBodyHttpServletRequest에 담는다.&lt;/li&gt;
&lt;li&gt;응답 정보를 HttpServletResponse로 캐스팅한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 캐스팅하는 이유는 ServletRequest/ServletResponse 에서는 로깅에 필요한 정보를 얻을 수 없기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MDC에 요청 추적을 위한 TraceId를 등록한다. (자세히 알고 싶으면 &lt;a href=&quot;https://pgmjun.tistory.com/176&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;&amp;nbsp;클릭)&lt;/li&gt;
&lt;li&gt;요청 정보 로깅 -&amp;gt; 다음 필터 호출 및 요청 처리 -&amp;gt; 응답 정보 로깅
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;[Request 로그 형태 예시]&lt;/b&gt; Request (IP주소) : GET /api/v1/test?key=value { &quot;key&quot;:&quot;value&quot;}&lt;/li&gt;
&lt;li&gt;&lt;b&gt;[Response 로그 형태 예시]&lt;/b&gt; Response : GET /api/v1/test?key=value : 200&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;getRequestAddr()&lt;/h4&gt;
&lt;pre id=&quot;code_1734594714855&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getRequestAddr(request: HttpServletRequest): String {
    val requestAddr = request.getHeader(X_FORWARDED_FOR_HEADER)
    return requestAddr ?: request.remoteAddr
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로그에 남길 요청을 보낸 주소를 흭득한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WAS 앞단에 구현해둔 Nginx 리버스 프록시 서버에서 요청을 먼저 받고 &lt;b&gt;'X_FORWARDED_FOR'&lt;/b&gt; 헤더로 WAS에 실제 요청 주소를 전달하도록 설정해놓은 상태이기 때문에, X_FORWARED_FOR 헤더가 존재한다면 그 값으로 로깅한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;getRequestParams()&lt;/h4&gt;
&lt;pre id=&quot;code_1734594743724&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getRequestParams(request: HttpServletRequest): String {
    var queryParameters = request.parameterMap
        .entries
        .joinToString(&quot;&amp;amp;&quot;) { (key, values) -&amp;gt; &quot;$key=${values[0]}&quot; }

    if (queryParameters.isNotEmpty()) {
        queryParameters = &quot;?$queryParameters&quot;
    }
    return queryParameters
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청에 포함된 쿼리 파라미터 값을 파싱하여 &lt;b&gt;&quot;?key1=value1&amp;amp;key2=value2&quot;&lt;/b&gt; 형태로 문자열을 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 결과 확인&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Request 로그&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/el40zX/btsLpJYVxwl/UfVKcV4Nk8GgC3FojzZBYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/el40zX/btsLpJYVxwl/UfVKcV4Nk8GgC3FojzZBYk/img.png&quot; data-alt=&quot;Request 로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/el40zX/btsLpJYVxwl/UfVKcV4Nk8GgC3FojzZBYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fel40zX%2FbtsLpJYVxwl%2FUfVKcV4Nk8GgC3FojzZBYk%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;572&quot; height=&quot;40&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Request 로그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Request 로그 형태 예시]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Request (IP주소) : GET /api/v1/test?key=value { &quot;key&quot;:&quot;value&quot;}&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Response 로그&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/muBiP/btsLpjfmSjs/AqQnib2GklTmmrrFPTu8k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/muBiP/btsLpjfmSjs/AqQnib2GklTmmrrFPTu8k1/img.png&quot; data-alt=&quot;Response 로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/muBiP/btsLpjfmSjs/AqQnib2GklTmmrrFPTu8k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmuBiP%2FbtsLpjfmSjs%2FAqQnib2GklTmmrrFPTu8k1%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;561&quot; height=&quot;37&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Response 로그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Response 로그 형태 예시]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Response : GET /api/v1/test?key=value : 200&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;h4 data-ke-size=&quot;size20&quot;&gt;커스텀 Logger를 통해 최종적으로 출력된 로그&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2806&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8Es3x/btsLnFjF5Nr/IwyYnoVv38kj9AWrzHKo41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8Es3x/btsLnFjF5Nr/IwyYnoVv38kj9AWrzHKo41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8Es3x/btsLnFjF5Nr/IwyYnoVv38kj9AWrzHKo41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8Es3x%2FbtsLnFjF5Nr%2FIwyYnoVv38kj9AWrzHKo41%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;2806&quot; height=&quot;76&quot; data-origin-width=&quot;2806&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Filter에서 함께 설정했던 TraceId 값과 함께 요청/응답 로그가 잘 출력되는 것을 확인할 수 있다.&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;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Filter를 통한 스프링부트 요청/응답 정보 로깅 시스템 구축하기에 대해서 알아보았다.&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;위에서 말했듯이 AOP를 통해 로깅 시스템을 구축하는 것과 난이도에 큰 차이는 없었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;존재하는 약간의 차이는 '존재하지 않는 HTTP API 메서드에 대한 요청이더라도 로그를 남기느냐 아니냐' 인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP로도 구현하려면 할 수 있겠지만 그렇게 할 거면 그냥 Filter를 쓰면 될 것 같다.&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;u&gt; AOP를 통한 요청/응답 정보 로깅 방식&lt;/u&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;</description>
      <category>BackEnd/Spring</category>
      <category>logback</category>
      <category>스프링 요청응답 로깅</category>
      <category>스프링부트 mdc</category>
      <category>스프링부트 로그 남기기</category>
      <category>스프링부트 로그 남기는 방법</category>
      <category>스프링부트 로깅</category>
      <category>스프링부트 로깅 구현</category>
      <category>스프링부트 요청 로그</category>
      <category>스프링부트 요청 로깅</category>
      <category>스프링부트 요청 정보 로깅</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/188</guid>
      <comments>https://pgmjun.tistory.com/188#entry188comment</comments>
      <pubDate>Thu, 19 Dec 2024 17:16:50 +0900</pubDate>
    </item>
    <item>
      <title>나도 모르게 책 출판해버린 이야기 : 일단 나부터 리팩터링 합니다2</title>
      <link>https://pgmjun.tistory.com/187</link>
      <description>&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;1030&quot; data-origin-height=&quot;1480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7kw6F/btsLn94K9HT/9uMqjqDhwmqZwN8KrqG9E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7kw6F/btsLn94K9HT/9uMqjqDhwmqZwN8KrqG9E1/img.png&quot; data-alt=&quot;일단 나부터 리팩터링 합니다2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7kw6F/btsLn94K9HT/9uMqjqDhwmqZwN8KrqG9E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7kw6F%2FbtsLn94K9HT%2F9uMqjqDhwmqZwN8KrqG9E1%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;354&quot; height=&quot;509&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;1480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;일단 나부터 리팩터링 합니다2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;수료 후 오랜만에 울린 슬랙 알림&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-19 01.45.46.png&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8zAPh/btsLoK4sX9t/4gWZGTbnwxROvErazZgAcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8zAPh/btsLoK4sX9t/4gWZGTbnwxROvErazZgAcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8zAPh/btsLoK4sX9t/4gWZGTbnwxROvErazZgAcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8zAPh%2FbtsLoK4sX9t%2F4gWZGTbnwxROvErazZgAcK%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;675&quot; height=&quot;289&quot; data-filename=&quot;스크린샷 2024-12-19 01.45.46.png&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 땅콩 팀원들과 연말파티를 보내고 있었는데 수료 후 잠잠하던 우테코 6기 슬랙에서 한 알림이 도착했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어보니 워니가 우테코 6기의 주요 활동 중 하나인 &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;h3 data-ke-size=&quot;size23&quot;&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;유연성 강화 훈련은 &amp;lsquo;고통스럽다고 느끼던 특정 상황을 유연하게 대처할 수 있게 만들어주는 스킬&amp;rsquo; 훈련이다. 쉽게 말해, 유연해져야 하는 부분에 대해 목표를 정하고 달성을 위해 노력하는 훈련이다.&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: #333333; text-align: start;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책 소개&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;일단 나부터 리팩터링 합니다.&quot;&lt;br /&gt;&lt;br /&gt;개발자라면 누구나 한 번쯤 코드 리팩터링의 필요성을 느껴봤을 거예요. 그럴 때 우리는 더 나은 구조와 논리를 찾아 리팩터링을 시작합니다. 그런데 가끔은 이런 생각이 들 때가 있습니다. &amp;ldquo;이 과정, 나에게도 필요하지 않을까?&amp;rdquo; 우리의 일상과 마음도 종종 코드처럼 복잡해지곤 합니다. 이럴 때 필요한 건 새로운 지식이나 도구를 더하는 것이 아니라, 나 자신을 들여다보고 불필요한 것들을 덜어내는 일일지도 모릅니다. 이 책은 우아한테크코스 6기 크루들이 각자 자신의 강점과 약점을 마주하고, 더 나은 방향으로 나아가기 위한 과정을 고민하며 써 내려간 이야기입니다. 자신을 리팩터링하며 더 유연하고 단단한 사람이 되어가는 여정을 담고 있습니다. 변화는 거창한 데서 시작하지 않고 나 자신을 이해하고 조금 더 나아지려는 작은 실천에서 비롯됩니다. 이 책이 여러분에게 그런 작은 출발점이 되길 바랍니다.&lt;br /&gt;&lt;br /&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;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;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;궁금한 사람들을 위한 링크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1734540360291&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;일단 나부터 리팩터링 합니다 2 - 우아한테크코스 6기 크루 / 조경찬, 나영서, 이상진, 양경호, 남&quot; data-og-description=&quot;개발자라면 누구나 한 번쯤 코드 리팩터링의 필요성을 느껴봤을 거예요. 그럴 때 우리는 더 나은 구조와 논리를 찾아 리팩터링을 시작합니다. 그런데...&quot; data-og-host=&quot;bookk.co.kr&quot; data-og-source-url=&quot;https://bookk.co.kr/bookStore/6757d7d1a04a426ad7820efa&quot; data-og-url=&quot;https://bookk.co.kr/bookStore/6757d7d1a04a426ad7820efa&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cFT7PU/hyXOdNu7Rc/ALaRX91KFjexkfv7bXik61/img.jpg?width=517&amp;amp;height=740&amp;amp;face=0_0_517_740&quot;&gt;&lt;a href=&quot;https://bookk.co.kr/bookStore/6757d7d1a04a426ad7820efa&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bookk.co.kr/bookStore/6757d7d1a04a426ad7820efa&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cFT7PU/hyXOdNu7Rc/ALaRX91KFjexkfv7bXik61/img.jpg?width=517&amp;amp;height=740&amp;amp;face=0_0_517_740');&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;일단 나부터 리팩터링 합니다 2 - 우아한테크코스 6기 크루 / 조경찬, 나영서, 이상진, 양경호, 남&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발자라면 누구나 한 번쯤 코드 리팩터링의 필요성을 느껴봤을 거예요. 그럴 때 우리는 더 나은 구조와 논리를 찾아 리팩터링을 시작합니다. 그런데...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bookk.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 작성한 유강스 내용은 '일단 나부터 리팩터링 합니다 2' 에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금한 사람들은 해당 서적의 목차 중 &lt;b&gt;이든(최승준) &lt;/b&gt;을 찾거나 &lt;b&gt;'523Page'&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1734540629613&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;일단 나부터 리팩터링 합니다1 - 우아한테크코스 6기 크루 / 김주하, 김민수, 고상현, 이낙헌, 송&quot; data-og-description=&quot;개발자라면 누구나 한 번쯤 코드 리팩터링의 필요성을 느껴봤을 거예요. 그럴 때 우리는 더 나은 구조와 논리를 찾아 리팩터링을 시작합니다. 그런데...&quot; data-og-host=&quot;bookk.co.kr&quot; data-og-source-url=&quot;https://bookk.co.kr/bookStore/6757d76aa04a426ad781f95d&quot; data-og-url=&quot;https://bookk.co.kr/bookStore/6757d76aa04a426ad781f95d&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ch4yGU/hyXOf5B6Ou/6KpgKLAkBDBdCp5r2KgkQk/img.jpg?width=517&amp;amp;height=742&amp;amp;face=0_0_517_742&quot;&gt;&lt;a href=&quot;https://bookk.co.kr/bookStore/6757d76aa04a426ad781f95d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bookk.co.kr/bookStore/6757d76aa04a426ad781f95d&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ch4yGU/hyXOf5B6Ou/6KpgKLAkBDBdCp5r2KgkQk/img.jpg?width=517&amp;amp;height=742&amp;amp;face=0_0_517_742');&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;일단 나부터 리팩터링 합니다1 - 우아한테크코스 6기 크루 / 김주하, 김민수, 고상현, 이낙헌, 송&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발자라면 누구나 한 번쯤 코드 리팩터링의 필요성을 느껴봤을 거예요. 그럴 때 우리는 더 나은 구조와 논리를 찾아 리팩터링을 시작합니다. 그런데...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bookk.co.kr&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;figure id=&quot;og_1734540634071&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;일단 나부터 리팩터링 합니다 3 - 우아한테크코스 6기 크루 / 정회성, 강다빈, 권기호, 이재민, 정&quot; data-og-description=&quot;개발자라면 누구나 한 번쯤 코드 리팩터링의 필요성을 느껴봤을 거예요. 그럴 때 우리는 더 나은 구조와 논리를 찾아 리팩터링을 시작합니다. 그런데...&quot; data-og-host=&quot;bookk.co.kr&quot; data-og-source-url=&quot;https://bookk.co.kr/bookStore/6757d847a04a426ad78224cb&quot; data-og-url=&quot;https://bookk.co.kr/bookStore/6757d847a04a426ad78224cb&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hQ1yT/hyXOkskONE/oQ9e2KrLUM7l6kf2gV9KP1/img.jpg?width=515&amp;amp;height=739&amp;amp;face=0_0_515_739&quot;&gt;&lt;a href=&quot;https://bookk.co.kr/bookStore/6757d847a04a426ad78224cb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bookk.co.kr/bookStore/6757d847a04a426ad78224cb&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hQ1yT/hyXOkskONE/oQ9e2KrLUM7l6kf2gV9KP1/img.jpg?width=515&amp;amp;height=739&amp;amp;face=0_0_515_739');&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;일단 나부터 리팩터링 합니다 3 - 우아한테크코스 6기 크루 / 정회성, 강다빈, 권기호, 이재민, 정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발자라면 누구나 한 번쯤 코드 리팩터링의 필요성을 느껴봤을 거예요. 그럴 때 우리는 더 나은 구조와 논리를 찾아 리팩터링을 시작합니다. 그런데...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bookk.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 크루원들의 유연성 강화 스토리도 궁금하다면 여기서 확인할 수 있다:)&lt;/p&gt;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우아한테크코스 6기</category>
      <category>우아한테크코스 6기 소프트스킬 훈련</category>
      <category>우아한테크코스 6기 유연성 강화 훈련</category>
      <category>우아한테크코스 6기 책</category>
      <category>우아한테크코스 6기 회고</category>
      <category>우아한테크코스 소프트스킬</category>
      <category>우아한테크코스 책</category>
      <category>우아한테크코스 회고</category>
      <category>우테코  소프트스킬</category>
      <category>우테코 책</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/187</guid>
      <comments>https://pgmjun.tistory.com/187#entry187comment</comments>
      <pubDate>Thu, 19 Dec 2024 01:59:29 +0900</pubDate>
    </item>
    <item>
      <title>GitHub 브랜치 보호: CI 통과 전 머지 금지 브랜치 전략 세우기</title>
      <link>https://pgmjun.tistory.com/186</link>
      <description>&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;300&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbULA6/btsLjkL1qba/LADzLn2IR4jCkB32Sw6fT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbULA6/btsLjkL1qba/LADzLn2IR4jCkB32Sw6fT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbULA6/btsLjkL1qba/LADzLn2IR4jCkB32Sw6fT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbULA6%2FbtsLjkL1qba%2FLADzLn2IR4jCkB32Sw6fT0%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;300&quot; height=&quot;168&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;168&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;h2 data-ke-size=&quot;size26&quot;&gt;설정 근거&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 과정에서 CI/CD 배포 자동화 파이프라인을 구축하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 보통 CI 과정에서 애플리케이션의 테스트를 진행하는데, CI가 다 돌지 않아도 변경사항을 운영(prod) 환경으로 Merge할 수 있다는 점에 문제를 느꼈다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bofjAM/btsLjkL1f32/meKcrcZsMqPBIuBAJfQuqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bofjAM/btsLjkL1f32/meKcrcZsMqPBIuBAJfQuqK/img.png&quot; data-alt=&quot;Github Repository - Settings&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bofjAM/btsLjkL1f32/meKcrcZsMqPBIuBAJfQuqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbofjAM%2FbtsLjkL1f32%2FmeKcrcZsMqPBIuBAJfQuqK%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;758&quot; height=&quot;52&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github Repository - Settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 적용하려는 Repository 의 Settings에 접속한다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-14 01.01.20.png&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buZbEf/btsLjTUzNsn/rBHtS2e8WZybwr6MuNflh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buZbEf/btsLjTUzNsn/rBHtS2e8WZybwr6MuNflh1/img.png&quot; data-alt=&quot;Settings - Branches&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buZbEf/btsLjTUzNsn/rBHtS2e8WZybwr6MuNflh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuZbEf%2FbtsLjTUzNsn%2FrBHtS2e8WZybwr6MuNflh1%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;386&quot; height=&quot;372&quot; data-filename=&quot;스크린샷 2024-12-14 01.01.20.png&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Settings - Branches&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Settings 탭에서 Branches를 선택한다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-14 01.01.25.png&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPRVLb/btsLiu9O9nZ/dSGnS6a28SdjS1KwHYA800/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPRVLb/btsLiu9O9nZ/dSGnS6a28SdjS1KwHYA800/img.png&quot; data-alt=&quot;Branch protection rules - Add classic branch protection rule&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPRVLb/btsLiu9O9nZ/dSGnS6a28SdjS1KwHYA800/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPRVLb%2FbtsLiu9O9nZ%2FdSGnS6a28SdjS1KwHYA800%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;569&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2024-12-14 01.01.25.png&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Branch protection rules - Add classic branch protection rule&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 브랜치 보호 규칙 설정 페이지가 나오는데 &lt;b&gt;'classic branch protection rule'&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;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;1262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYUkl2/btsLh3rfxUg/iQCDd6m2yvUGquVk1Y6DiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYUkl2/btsLh3rfxUg/iQCDd6m2yvUGquVk1Y6DiK/img.png&quot; data-alt=&quot;브랜치 규칙 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYUkl2/btsLh3rfxUg/iQCDd6m2yvUGquVk1Y6DiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYUkl2%2FbtsLh3rfxUg%2FiQCDd6m2yvUGquVk1Y6DiK%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;607&quot; height=&quot;505&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;1262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브랜치 규칙 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Branch name pattern&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브랜치 이름 패턴을 설정하는 입력창이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 prod 브랜치에 대해서 이 규칙을 적용할 계획이므로 &lt;b&gt;'prod'&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;이렇게 설정하면 feat, refactor 등의 브랜치에서 prod로 Merge 하고자 할 때, 지금 설정하는 보호 규칙이 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 prod 브랜치에 대한 Merge를 더욱 안전하게 만들 수 있다.&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;Require status checks to pass before merging&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;'Require status checks to pass before merging'&lt;/b&gt; 선택을 통해 merge하기 전 브랜치의 상태를 점검할 수 있다.&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;나는 CI 스크립트에서 &lt;b&gt;'test'&lt;/b&gt; 라는 job을 통해 애플리케이션의 테스트를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 이 test job을 Merge 전에 체크하도록 함으로써 test job이 돌지 않으면 prod 브랜치에 변경사항을 Merge 할 수 없도록 구성한다.&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;Require branches to be up to date before merging&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge 하기 전, 브랜치가 최신 상태여야 함을 강제할 수 있는 규칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머지 전 prod 브랜치의 최근 변경 사항을 반영해야 하도록 강제함으로써 충돌과 같은 문제를 방지할 수 있다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-14 01.04.23.png&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOpzfz/btsLjEQSDB0/sLj8v0CKoah3yxskl1J0H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOpzfz/btsLjEQSDB0/sLj8v0CKoah3yxskl1J0H0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOpzfz/btsLjEQSDB0/sLj8v0CKoah3yxskl1J0H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOpzfz%2FbtsLjEQSDB0%2FsLj8v0CKoah3yxskl1J0H0%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;150&quot; height=&quot;93&quot; data-filename=&quot;스크린샷 2024-12-14 01.04.23.png&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Create 버튼을 클릭하면 위에서 설정한 브랜치 보호 규칙이 적용된다.&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;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r5crL/btsLiDr6DLV/m6JK4Epbsu7kecf09JJWo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r5crL/btsLiDr6DLV/m6JK4Epbsu7kecf09JJWo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r5crL/btsLiDr6DLV/m6JK4Epbsu7kecf09JJWo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr5crL%2FbtsLiDr6DLV%2Fm6JK4Epbsu7kecf09JJWo0%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;740&quot; height=&quot;154&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;Free 요금제의 Organization을 사용중이고, Repository가 private 이라면 해당 설정은 적용되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;Organization 기준으로 해당 설정은 '&lt;b&gt;Github Team'&lt;/b&gt; 이나 &lt;b&gt;'Enterprice'&lt;/b&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;&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>Git</category>
      <category>CI</category>
      <category>github ci</category>
      <category>github merge 규칙</category>
      <category>github merge 브랜치 규칙</category>
      <category>github merge 제한</category>
      <category>github 브랜치</category>
      <category>github 브랜치 규칙</category>
      <category>github 브랜치 보호 규칙</category>
      <category>깃허브 브랜치 규칙</category>
      <category>브랜치 규칙</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/186</guid>
      <comments>https://pgmjun.tistory.com/186#entry186comment</comments>
      <pubDate>Sat, 14 Dec 2024 01:29:01 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 레벨5 돌아보기 (2024/10/28 ~ 2024/11/29)</title>
      <link>https://pgmjun.tistory.com/185</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTrjrt/btsLfwG2mq0/UORVRTzAEPZsJzkdsRKRH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTrjrt/btsLfwG2mq0/UORVRTzAEPZsJzkdsRKRH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTrjrt/btsLfwG2mq0/UORVRTzAEPZsJzkdsRKRH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTrjrt%2FbtsLfwG2mq0%2FUORVRTzAEPZsJzkdsRKRH0%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;1280&quot; height=&quot;640&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;우테코 덕분에 1년간 많은 것을 배우고 느낄 수 있었다. 또 소중한 동료와 멘토를 만나 말로 표현할 수 없을 정도로 유익한 시간을 보낼 수 있었다고 생각한다.&lt;br&gt;이번엔 우아한테코크스의 마지막 기간인 레벨5를 마무리하고 이 과정에 대한 기록을 남겨놓고자 한다. 마지막 글인 만큼 조금은 진지하게 적어보았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;취업준비 기간&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우테코 레벨5는 취업 준비 기간이다. 때문에 우아한형제들 우테코 전형 신입 지원 기회도 주어졌다.&lt;br&gt;취업 설명회도 우형 직원분들께서 오셔서 진행해주셨고 꽤나 흥미롭게 들을 수 있었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;토스뱅크 취뽀&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nesIK/btsLgldp5xp/zsXRaChACopa4QdzwBcGV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nesIK/btsLgldp5xp/zsXRaChACopa4QdzwBcGV0/img.png&quot; data-alt=&quot;토스뱅크 최종 합격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nesIK/btsLgldp5xp/zsXRaChACopa4QdzwBcGV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnesIK%2FbtsLgldp5xp%2FzsXRaChACopa4QdzwBcGV0%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;420&quot; height=&quot;298&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;토스뱅크 최종 합격&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이 기간에 나는 토스뱅크 테크니컬 면접에 합격하여 컬처핏 면접을 준비하고 있어 굉장히 바쁜 기간이었다.&lt;br&gt;우형 자소서 마감이 11월 1일 오전 7시였는데, 컬쳐핏 면접은 10월 31일 저녁이었다.&lt;br&gt;자소서는 토뱅 면접을 준비하느라 단 한 자도 써두지 않은 상태였고, 31일 저녁 면접을 보고 와서 자소서를 쓰는 인원들과 카페에서 모였지만 손에 잡히지 않았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우형이라는 회사는 네카라쿠배의 '배'로 유망한 IT 대기업 중 하나이다.&lt;br&gt;하지만 당시에는 회사에 공감 가는 가치가 크게 없었고 그래서인지 자소서를 적을 수가 없었다. 자소서에 어느 정도 예쁘게 포장해서 쓰는 것들은 있다고 해도 아예 거짓말을 하고 싶진 않았다.&lt;br&gt;이게 당연하긴 하다. 우테코에서 교육을 받았지만 우아한형제들이라는 회사는 우테코와는 완전히 별개이다.&lt;br&gt;그래서 공감 가는 포인트를 찾아보려면 기업에 대한 서칭을 많이 했어야 했는데 개인적으로 당시 최고의 관심사였던 토뱅 취업준비를 더 우선시하느라 기업에 대해 거의 알고 있는 게 없었다. 또 우형에서 우테코에 크게 관여하는 일은 없었고 우테코의 교육 철학과 우형의 실무는 많이 다른 듯했기에 나에게 우형은 '잘 모르는 회사'였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 결국 우아한형제들 지원은 때가 아니었다는 생각을 갖고 이 시점에 포기하고 자소서 제출을 하지 않았다.&lt;br&gt;지금 생각해 보면 좋은 경험을 해 볼 기회를 하나 놓친 것은 아닌가 싶은 생각도 들지만, 당시에 그 선택은 최선이었고 후회하지 않는다.&lt;br&gt;토스뱅크 컬처핏 면접을 정말 즐겁게 보고 와서 더욱이 후회는 없었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 우아한 형제들 지원 마감시간인 11/1 오전 7시에서 2시간이 지난 오전 9시 토스뱅크에서 첫 커리어를 시작하게 되었다.&lt;br&gt;사실 아쉬워할 시간조차 없긴 했던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;자세한 토스뱅크 취업 회고가 궁금하면 &lt;a href=&quot;https://pgmjun.tistory.com/175&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;서 확인할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;구구의 재테크강의&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;레벨5는 백엔드 코치인 구구가 재테크강의(일명 '우아한(재)테크코스')도 해주어서 굉장히 유익했다.&lt;br&gt;취업을 하게 되어서 돈을 모으고 굴릴 방법을 공부해야 했는데, 많인 키워드와 힌트를 얻어갈 수 있었다.&lt;br&gt;투자, 저축에 대해 무지해서 알바비를 받으면 전부 써버렸던 과거의 나를 반성하는 시간이 되었다.&lt;br&gt;구구 얘기를 듣고 ISA 계좌도 개설했고 노후연금, 투자 등등에 대해서 하나하나 공부해보고 있다.&lt;br&gt;지금도 늦지 않았기 때문에 열심히 모아보려고 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;우테코 6기 수료식&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boF601/btsLhwZgAnF/BF2zMzRI4enj6rkuYNlMKK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boF601/btsLhwZgAnF/BF2zMzRI4enj6rkuYNlMKK/img.jpg&quot; data-alt=&quot;수료증을 들고 캡틴 포비와의 한 컷&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boF601/btsLhwZgAnF/BF2zMzRI4enj6rkuYNlMKK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboF601%2FbtsLhwZgAnF%2FBF2zMzRI4enj6rkuYNlMKK%2Fimg.jpg&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;384&quot; height=&quot;512&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수료증을 들고 캡틴 포비와의 한 컷&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코를 시작했던 날은 2월이었는데 어느덧 수료식 날이 되어버렸다.&lt;br&gt;1년간 함께한 크루원들, 그리고 정들었던 잠실캠퍼스를 떠난다고 생각하니 마음이 허전해짐을 느꼈다.&lt;br&gt;하지만 최선을 다해 달렸던 만큼 최대한 즐거운 분위기로 수료식을 즐기려고 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;소중한 추억 돌아보기&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;레벨1 솔라조 ☀️&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRiGaU/btsLfAWJSH4/OkywnJHmzdv7JFhzok4X41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRiGaU/btsLfAWJSH4/OkywnJHmzdv7JFhzok4X41/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRiGaU/btsLfAWJSH4/OkywnJHmzdv7JFhzok4X41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRiGaU%2FbtsLfAWJSH4%2FOkywnJHmzdv7JFhzok4X41%2Fimg.jpg&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;450&quot; height=&quot;450&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코에 들어와서 처음 만난 사람들인 레벨1 솔라조도 정말 많이 기억에 남는다.&lt;br&gt;함께 레벨 인터뷰 스터디를 하기도 했고 한강에 놀러 가기도 했고 술도 먹고 밥도 먹고 보드게임도 하고 정말 즐거운 시간들을 보냈다.&lt;br&gt;다들 성향이 제각각이라 다 같이 제대로 놀러 가보진 못했지만 함께 보낸 시간만으로도 충분히 즐거웠다.&lt;br&gt;서로 좋은 영향을 많이 주고받았던 것 같다. 땅콩팀과 더불어서 가장 터놓고 대화할 수 있던 사람들 중 하나였지 않았나 싶다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;땅콩팀  &lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3h8nX/btsLha3j3UB/h0JFOv4prdHkFaINEuMfmK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3h8nX/btsLha3j3UB/h0JFOv4prdHkFaINEuMfmK/img.jpg&quot; data-alt=&quot;땅콩 수료식 단체사진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3h8nX/btsLha3j3UB/h0JFOv4prdHkFaINEuMfmK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3h8nX%2FbtsLha3j3UB%2Fh0JFOv4prdHkFaINEuMfmK%2Fimg.jpg&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;481&quot; height=&quot;641&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땅콩 수료식 단체사진&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 많은 시간을 땅콩팀과 보냈기에 각별했던 우리 팀과 이제 매일 볼 수는 없게 되니 많이 아쉬웠다.&lt;br&gt;그래도 한 명 한 명 취업에 성공할 때마다, 그리고 종종 보고 싶을 때마다 모여서 얼굴을 보기로 했다.&lt;br&gt;끝나고 땅콩 디벨롭도 계속해서 이어나갈 예정이니 앞으로 볼 날이 수두룩하다~&lt;br&gt;회고를 쓰고 있는 지금 시점엔 연말파티를 하자고 파티룸도 잡아두었다.&lt;br&gt;7월부터 동고동락한 우리 땅콩팀 앞으로 오래오래 만났으면 한다!&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;평균 DAU 8&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DAV8D/btsLg6tcep1/JFNJRkq6UK62Bk2v4uyjw0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DAV8D/btsLg6tcep1/JFNJRkq6UK62Bk2v4uyjw0/img.jpg&quot; data-alt=&quot;우아한형제들 네트워킹에 소개된 땅콩&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DAV8D/btsLg6tcep1/JFNJRkq6UK62Bk2v4uyjw0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDAV8D%2FbtsLg6tcep1%2FJFNJRkq6UK62Bk2v4uyjw0%2Fimg.jpg&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;490&quot; height=&quot;277&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우아한형제들 네트워킹에 소개된 땅콩&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'땅콩'은 우아한형제들 개발 밋업에서 사용되었다. 이후 서비스에 접속하는 실제 유저들이 발생하는 중이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db2KOD/btsK9O0f6Cu/67xUvKyv07s11WapR8Ax10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db2KOD/btsK9O0f6Cu/67xUvKyv07s11WapR8Ax10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db2KOD/btsK9O0f6Cu/67xUvKyv07s11WapR8Ax10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb2KOD%2FbtsK9O0f6Cu%2F67xUvKyv07s11WapR8Ax10%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;539&quot; height=&quot;306&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 홍보도 제대로 하지 않았는데, 밋업 이후 평균 DAU가 8로 측정되었다.&lt;br&gt;모니터링 시스템을 강화하고 적극적인 홍보를 바탕으로 서비스를 더욱 키워나갈 예정이다.&lt;br&gt;우선 지금 자체로도 사용성이 좋으니, 더 다양하고 재밌는 밸런스 게임 질문들을 만들어보려고 한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;앞으로의 땅콩&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 땅콩은 사실 다들 팀원들이 취업 준비에 들어간 상태라서 뭔가 새로운 기능을 추가하는 등의 큰 작업은 하기 어렵다고 판단되었다.&lt;br&gt;때문에 앞으로는 팀원들이 모두 작업에 관여하기란 어렵다는 생각이 들었다. 그래서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;TF팀 단위&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;작업&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;을 제안&lt;/span&gt;했다.&lt;br&gt;필요하다고 생각되는 기능이 있다면 그 부분에 대한 내용을 바탕으로 시간이 되는 팀원들을 모아 TF팀을 구성한다.&lt;br&gt;그리고 &lt;b&gt;'근거-과정-결과'&lt;/b&gt;를 문서화하여 팀 협업툴 채널에 공유한다.&lt;br&gt;모두가 참여할 수 없는데 참여하도록 강요하는 방식보다는 TF팀 방식을 적용해야 지속가능한 팀이 될 것이라고 생각했기 때문이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUUFdo/btsLhjy3RKa/12tz0cHMiZDKv8SMQKZPQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUUFdo/btsLhjy3RKa/12tz0cHMiZDKv8SMQKZPQk/img.png&quot; data-alt=&quot;TF TEAM 채널&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUUFdo/btsLhjy3RKa/12tz0cHMiZDKv8SMQKZPQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUUFdo%2FbtsLhjy3RKa%2F12tz0cHMiZDKv8SMQKZPQk%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;403&quot; height=&quot;503&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1644&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TF TEAM 채널&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 의견에 팀원들도 공감해 주었고 팀 디스코드에 이런 채널이 하나 생기게 되었다. (디스코드 이름은 무시해 주세요 백엔드 팀 컨셉입니다^^)&lt;br&gt;앞으로의 땅콩과 팀의 성장도 정말 기대된다!&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;우테코를 떠나보내며&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4589&quot; data-origin-height=&quot;2581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/19r3M/btsLe5QlOtd/X7oyIUc2fDI4H0tBH1eGd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/19r3M/btsLe5QlOtd/X7oyIUc2fDI4H0tBH1eGd1/img.png&quot; data-alt=&quot;공허해진 우테코&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/19r3M/btsLe5QlOtd/X7oyIUc2fDI4H0tBH1eGd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F19r3M%2FbtsLe5QlOtd%2FX7oyIUc2fDI4H0tBH1eGd1%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;4589&quot; height=&quot;2581&quot; data-origin-width=&quot;4589&quot; data-origin-height=&quot;2581&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공허해진 우테코&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들이 북적거렸던 시간에 한산해진 잠실 캠퍼스의 사진을 보며 정말 우테코가 끝났음을 실감하게 되었다.&lt;br&gt;이 공간에서 참 많이도 배웠고 성장했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;스스로 성장하는 방법&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우테코에서 얻은 가장 큰 배움은 스스로 성장하는 방법을 얻은 것이라고 생각한다.&lt;br&gt;이곳에서 1년간 구르며 지식적인 성장도 얻었다. 하지만 우테코에서와 같이 양질의 인적 자원이나 특별한 상황이 주어지지 않더라도 스스로 성장하는 방법을 배우는 것이 앞으로 개발자로서의 커리어에 가장 도움이 될 것이라고 생각한다.&lt;br&gt;그리고 그 방법을 배워서 나가게 된 것 같아 스스로 만족스러웠다.&lt;br&gt;우테코가 아니었다면 이런 것들을 어떻게 배울 수 있었을까? 몇 년이 지난 뒤에나 많은 시도 끝에 배우지 않았을까?&lt;br&gt;이거 하나만으로도 우테코에서 얻을 건 다 얻었다고 느낀다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;협업의 소중함&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;평소 함께의 가치와 협업의 소중함을 알고 있었지만, 우테코에서 다시 한번 이러한 가치들을 깊이 있게 느끼고 나가는 것 같다.&lt;br&gt;프로젝트 과정에서 다양한 문제를 마주쳤는데, 빠른 이슈 공유와 소통으로 이루어진 협업을 통해 문제를 빠르게 분석 및 해결해 나갈 수 있었고&lt;br&gt;크루원들과 스터디를 통해 서로의 부족한 부분을 채워나갈 수 있었다. 혼자였다면 이렇게 하지 못했을 것 같았지만 함께여서 가능했고 정말 좋은 사람들을 많이 만나고 가는 것 같아 행복했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우테코에서 다시 한번 느낀 동료와 협업의 소중함을 바탕으로, 앞으로 만나게 될 동료들과도 균형 있게 어우러질 수 있는.. 그리고 함께 성장을 도모해 나갈 수 있는 사람이 된 것 같아 만족스러웠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;다시 처음으로&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;토스뱅크에서의 새로운 시작&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우테코를 마무리하고 새롭게 도착하게 된 정착지는 토스뱅크이다.&lt;br&gt;토스뱅크에서 토스를 사용하는 많은 사람들에게 혁신적이고 가치 있는 금융 서비스를 제공하여 토스와 함께 금융계의 혁신을 이끌어가고자 한다. 물론 쉽진 않을 것을 알기에 많이 노력할 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;남은 기간 동안 토스뱅크에서 주로 사용하는 언어인 Kotlin을 학습하고 기록해나가 보고자 한다. 사실 이미 대학 공지 알림이 왓캠퍼스 프로젝트에 &lt;b&gt;Java to Kotlin&lt;/b&gt; 마이그레이션을 진행하며 학습 중에 있다. 또 이미 관련해서 재밌거나 신기한 내용을 몇 가지 기록해두기도 하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;차근차근 노력해서 토스뱅크에 없어선 안 될 사람이 되고 싶다고도 생각한다.&lt;br&gt;이전에 한 크루원이 백엔드 코치였던 구구에게 &lt;b&gt;&quot;어떤 기업이 좋은 기업이고, 어떤 회사를 가는 게 좋은가요?&quot;&lt;/b&gt;라는 뉘앙스의 질문을 한 적이 있다.&lt;br&gt;그때 들었던 답변 중 이 내용이 기억에 강렬히 남아있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;네카라쿠배같은 회사에 집착하지 마시고 본인이 있는 곳을 네카라쿠배처럼 만들어보세요.&lt;/span&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;말처럼 쉬운 이야기는 아니지만, 유명하지 않았던 배달 앱인 '배달의민족'에 가치를 느끼고 입사하셔서 현재 IT 최고의 기업 '네카라쿠배' 중 하나인 배민이 될 때까지 함께 하고 계신 구구를 보면서 이 말에 큰 힘을 느꼈다.&lt;br&gt;&amp;nbsp;&lt;br&gt;내가 있는 곳을 더 나은 곳으로 만들려는 목표와 의지를 가지고 더 많은 사람들이 토스뱅크의 유저가 될 수 있도록 노력해보고자 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;앞으로의 목표&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;나는 공유와 협업 그리고 이를 통해 동료들과 함께 성장해 나가는 과정에서 가장 큰 가치를 느끼는 사람이다.&lt;br&gt;앞으로 현업에서 만나게 될 문제에 대해 이러한 가치를 지속적으로 실천해 나가면서 살아나갈 계획이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 주변 사람들에게 항상 하는 이야기지만, 언젠간 초심자도 쉽게 이해할 수 있는 백엔드 기술에 대한 강의나 책도 만들어보고 싶다.&lt;br&gt;본인 또한 김영한 님의 강의를 들으면서 정말 쉽게 스프링부트 기초를 이해하고 지금까지 왔기 때문에, 김영한 님과 같이 초심자들을 이끌어주는 이정표 같은 사람이 되고 싶다는 목표를 품고 있었다.&lt;br&gt;그러기 위해선 더 많은 지식과 경험이 필요하다. 그 경험들을 얻기 위해 꾸준히 힘차게 달려 나갈 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우아한테코크소 6기 백엔드</category>
      <category>우아한테크코스 6기</category>
      <category>우아한테크코스 6기 회고</category>
      <category>우아한테크코스 회고</category>
      <category>우아한테크코스 후기</category>
      <category>우테코 6기</category>
      <category>우테코 6기 백엔드</category>
      <category>우테코 백엔드</category>
      <category>우테코 회고</category>
      <category>우테코 후기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/185</guid>
      <comments>https://pgmjun.tistory.com/185#entry185comment</comments>
      <pubDate>Thu, 12 Dec 2024 16:22:00 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 레벨4 돌아보기 (2024/09/03 ~ 2024/10/25)</title>
      <link>https://pgmjun.tistory.com/184</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pk8Al/btsLeBIagZ7/6EnDktgcp6kwGxXmHZ3rg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pk8Al/btsLeBIagZ7/6EnDktgcp6kwGxXmHZ3rg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pk8Al/btsLeBIagZ7/6EnDktgcp6kwGxXmHZ3rg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPk8Al%2FbtsLeBIagZ7%2F6EnDktgcp6kwGxXmHZ3rg1%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;1280&quot; height=&quot;640&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우아한테크코스의 레벨 4가 끝나면서 이에 대한 기록도 남겨보고자 한다.&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;항상 조금은 무겁게 쓰던 회고였지만, 이번 회고는 사진을 많이 담아 조금 프리하게 써보려고 한다.&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #3a4954; text-align: start;&quot;&gt;어떤 일들이 있었는지 되돌아보자!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방학 중 힐링 여행&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P3uSu/btsLfWYCtaY/8ubfQKw3O9ePqTMumHsg41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P3uSu/btsLfWYCtaY/8ubfQKw3O9ePqTMumHsg41/img.jpg&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;2048&quot; data-is-animation=&quot;false&quot; width=&quot;367&quot; height=&quot;489&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P3uSu/btsLfWYCtaY/8ubfQKw3O9ePqTMumHsg41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP3uSu%2FbtsLfWYCtaY%2F8ubfQKw3O9ePqTMumHsg41%2Fimg.jpg&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;1536&quot; height=&quot;2048&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcilFC/btsLgeLvjG2/LUJKqKaTgaRYpkRXfCBeKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcilFC/btsLgeLvjG2/LUJKqKaTgaRYpkRXfCBeKk/img.jpg&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;2048&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcilFC/btsLgeLvjG2/LUJKqKaTgaRYpkRXfCBeKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcilFC%2FbtsLgeLvjG2%2FLUJKqKaTgaRYpkRXfCBeKk%2Fimg.jpg&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;1536&quot; height=&quot;2048&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;제주도 여행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨4를 들어가기에 앞서, 레벨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;그리고 제주도에서 시간을 보내며 이렇게 맘편히 힐링하며 쉬어가는 시간이 정말 필요하다고 다시 한 번 느꼈다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;우리는 사람인지라 매번 달릴 수는 없으니&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;말이다.&amp;nbsp;&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;아무튼 레벨4를 대비할 수 있는 힘을 비축한 좋은 시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 레벨4는 정말 바빠서 가장 정신없던 기간이었다.&lt;/p&gt;
&lt;p style=&quot;color: #3a4954; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #3a4954; text-align: start;&quot;&gt;&lt;b&gt;&quot;지금 이 순간이 가장 여유로울 때다&quot;&lt;/b&gt; 라고 말하던&lt;span&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;레벨3에 마지막 데모데이에서 받은 피드백을 바탕으로 서비스 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평온했던 방학 이후 시작된 레벨4는, 레벨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;&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;1098&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6DqLC/btsLeovwEzI/8f31FJEhk0QseAktK1SR1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6DqLC/btsLeovwEzI/8f31FJEhk0QseAktK1SR1k/img.png&quot; data-alt=&quot;레벨3 데모데이 피드백&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6DqLC/btsLeovwEzI/8f31FJEhk0QseAktK1SR1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6DqLC%2FbtsLeovwEzI%2F8f31FJEhk0QseAktK1SR1k%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;471&quot; height=&quot;342&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;레벨3 데모데이 피드백&lt;/figcaption&gt;
&lt;/figure&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;(그런데 대부분 프론트엔드 측의 개선사항이어서 마루,포메,썬데이가 너무 고생해줬다.. 우리팀 프엔 최고..!)&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;레벨3 이후 팀의 변화 측정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨3는 프로젝트만 진행했지만, 레벨4는 과제/프로젝트/취준/스터디 등 정말 다양한 업무 병행이 이루어진 레벨이었다.&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;nbsp;&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;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/97gTK/btsLfG9xFJe/j2679s9XLPeZ6zDtq6nmSk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/97gTK/btsLfG9xFJe/j2679s9XLPeZ6zDtq6nmSk/img.jpg&quot; data-alt=&quot;땅콩 팀의 긍정/부정 변화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/97gTK/btsLfG9xFJe/j2679s9XLPeZ6zDtq6nmSk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F97gTK%2FbtsLfG9xFJe%2Fj2679s9XLPeZ6zDtq6nmSk%2Fimg.jpg&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;491&quot; height=&quot;368&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땅콩 팀의 긍정/부정 변화&lt;/figcaption&gt;
&lt;/figure&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;일단 다른 영역의 팀원들과의 &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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ8KVO/btsLeCtDOec/BcToOu6vzKG0yRB8AzPU9K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ8KVO/btsLeCtDOec/BcToOu6vzKG0yRB8AzPU9K/img.jpg&quot; data-alt=&quot;어쩌다 찍은 측정 기념사진ㅋㅋ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ8KVO/btsLeCtDOec/BcToOu6vzKG0yRB8AzPU9K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ8KVO%2FbtsLeCtDOec%2FBcToOu6vzKG0yRB8AzPU9K%2Fimg.jpg&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;346&quot; height=&quot;461&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어쩌다 찍은 측정 기념사진ㅋㅋ&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;취업 준비와 미션 수행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨4는 인프라 설계 미션, MVC 설계 미션, Spring 설계 미션 등등 난이도가 있는 미션들이 다수 주어진다.&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;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&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;레벨4에는 토뱅의 기술면접이 겹쳐있었는데 준비 기간이 거의 한 달 가까이 되어서 레벨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;하지만 학습한 내용들을 돌아보고, 그 중에 문제점을 발견하고 개선해나가는 경험들을 바탕으로 유익하고 즐거운 시간도 많이 보낼 수 있었다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 토스뱅크 취준 기록이 궁금하다면 &lt;a href=&quot;https://pgmjun.tistory.com/175&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&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;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;포비와의 면담&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨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;&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;&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;포비는 이러한 문제에 대해서 정말 쉬운 해결책을 제시해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;그럼 한 번 상상해보세요.&quot;&amp;nbsp;&lt;/span&gt;&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-11 23.56.37.png&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmJ71B/btsLeuoNmpJ/4tZMfK4MShafLeAjqfOh2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmJ71B/btsLeuoNmpJ/4tZMfK4MShafLeAjqfOh2k/img.png&quot; data-alt=&quot;포비와의 DM&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmJ71B/btsLeuoNmpJ/4tZMfK4MShafLeAjqfOh2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmJ71B%2FbtsLeuoNmpJ%2F4tZMfK4MShafLeAjqfOh2k%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;671&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2024-12-11 23.56.37.png&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;포비와의 DM&lt;/figcaption&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;만약 우테코를 하게 된다면 꼭!! 포비와의 면담을 가져보길 바란다.&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;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;6차 데모데이 (마지막 데모데이)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 데모데이인 6차 데모데이는 레벨3의 4차 데모데이와 같이 부스 운영을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 팀은 부스 운영에 필요한 여러 작업들을 TF를 구성하여 착실히 수행해 대비했다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK0xFF/btsLfwzopaL/z9gd8pVMb5ibLli4KkmeHK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK0xFF/btsLfwzopaL/z9gd8pVMb5ibLli4KkmeHK/img.jpg&quot; data-alt=&quot;땅콩 데모데이 부스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK0xFF/btsLfwzopaL/z9gd8pVMb5ibLli4KkmeHK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK0xFF%2FbtsLfwzopaL%2Fz9gd8pVMb5ibLli4KkmeHK%2Fimg.jpg&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;497&quot; height=&quot;663&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땅콩 데모데이 부스&lt;/figcaption&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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x2mwt/btsLgF9LCuD/hZb2NGqkcrj5nUigG8iFvK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x2mwt/btsLgF9LCuD/hZb2NGqkcrj5nUigG8iFvK/img.jpg&quot; data-alt=&quot;실제로 우형 개발자 밋업에서 사용된 '땅콩'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x2mwt/btsLgF9LCuD/hZb2NGqkcrj5nUigG8iFvK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx2mwt%2FbtsLgF9LCuD%2FhZb2NGqkcrj5nUigG8iFvK%2Fimg.jpg&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;429&quot; height=&quot;572&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제로 우형 개발자 밋업에서 사용된 '땅콩'&lt;/figcaption&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;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;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0kTBO/btsLfPL2E5L/AvnRweUvTdVcwtjKxNaNj0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0kTBO/btsLfPL2E5L/AvnRweUvTdVcwtjKxNaNj0/img.jpg&quot; data-alt=&quot;땅콩 굿즈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0kTBO/btsLfPL2E5L/AvnRweUvTdVcwtjKxNaNj0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0kTBO%2FbtsLfPL2E5L%2FAvnRweUvTdVcwtjKxNaNj0%2Fimg.jpg&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;465&quot; height=&quot;620&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땅콩 굿즈&lt;/figcaption&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;인기가 꽤나 많아서 100개 만들었는데 다 털렸다.&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;스티커 제작 과정에서는 파일 형식과 관련해서 몇몇 문제가 있었는데, SOPT 라이온하트팀에서 만난 참 아끼는 디자이너 동생이 도와줘서 문제를 해결할 수 있었다. 어려운 문제들을 슥삭 해결해버리는 디자이너들이 참 대단한 것 같다고 다시 한 번 느꼈다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDsAiw/btsLe1l8DYw/Got2kzzZWhp9yMdKM9bKdK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDsAiw/btsLe1l8DYw/Got2kzzZWhp9yMdKM9bKdK/img.jpg&quot; data-alt=&quot;포비와 매칭도 100%&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDsAiw/btsLe1l8DYw/Got2kzzZWhp9yMdKM9bKdK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDsAiw%2FbtsLe1l8DYw%2FGot2kzzZWhp9yMdKM9bKdK%2Fimg.jpg&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;531&quot; height=&quot;398&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;포비와 매칭도 100%&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부스에 방문해주신 포비와 같이 땅콩을 했는데, 밸런스 게임 질문에 대한 매칭도가 100%가 떠서 기념 사진도 남겨두었다ㅋㅋ&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4928&quot; data-origin-height=&quot;3264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8qHt3/btsLeioE91j/O7DyH5T1mdFq6tCl9kZdv0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8qHt3/btsLeioE91j/O7DyH5T1mdFq6tCl9kZdv0/img.jpg&quot; data-alt=&quot;천하제일 땅콩 그리기 대회&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8qHt3/btsLeioE91j/O7DyH5T1mdFq6tCl9kZdv0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8qHt3%2FbtsLeioE91j%2FO7DyH5T1mdFq6tCl9kZdv0%2Fimg.jpg&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;635&quot; height=&quot;421&quot; data-origin-width=&quot;4928&quot; data-origin-height=&quot;3264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;천하제일 땅콩 그리기 대회&lt;/figcaption&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;근육 땅콩들 대체 누가 그렸을까..&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H8wq5/btsLe5vfeKy/Oswx1MmA8wbjHCYFQT47pK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H8wq5/btsLe5vfeKy/Oswx1MmA8wbjHCYFQT47pK/img.jpg&quot; data-alt=&quot;땅콩팀 단체사진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H8wq5/btsLe5vfeKy/Oswx1MmA8wbjHCYFQT47pK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH8wq5%2FbtsLe5vfeKy%2FOswx1MmA8wbjHCYFQT47pK%2Fimg.jpg&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;743&quot; height=&quot;557&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땅콩팀 단체사진&lt;/figcaption&gt;
&lt;/figure&gt;
&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;너무 고생한 땅콩팀 최고에요! 땅콩 포에버~!&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>부트캠프/우아한테크코스 6기</category>
      <category>우아한테크코스</category>
      <category>우아한테크코스 6기</category>
      <category>우아한테크코스 기록</category>
      <category>우아한테크코스 프로젝트</category>
      <category>우아한테크코스 회고</category>
      <category>우아한테크코스 후기</category>
      <category>우테코</category>
      <category>우테코 레벨4</category>
      <category>우테코 회고</category>
      <category>우테코 후기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/184</guid>
      <comments>https://pgmjun.tistory.com/184#entry184comment</comments>
      <pubDate>Thu, 12 Dec 2024 00:25:51 +0900</pubDate>
    </item>
    <item>
      <title>Certbot을 사용한 Nginx SSL 인증서 등록 및 자동 갱신 설정 방식</title>
      <link>https://pgmjun.tistory.com/183</link>
      <description>&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;1018&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCJigF/btsKYCAFBpG/vI1M41vT9Z6o1bznc7bt91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCJigF/btsKYCAFBpG/vI1M41vT9Z6o1bznc7bt91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCJigF/btsKYCAFBpG/vI1M41vT9Z6o1bznc7bt91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCJigF%2FbtsKYCAFBpG%2FvI1M41vT9Z6o1bznc7bt91%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;364&quot; height=&quot;351&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;982&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;h2 data-ke-size=&quot;size26&quot;&gt;설정 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 인증서 흭득에 사용했던 &lt;a href=&quot;https://eff-certbot.readthedocs.io/en/latest/using.html#nginx&quot;&gt;&lt;b&gt;certbot standalone&lt;/b&gt;&lt;/a&gt; 방식은 80번 포트에 직접 &quot;독립형&quot; 웹서버를 사용하여 인증서를 갱신하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Nginx같이 80번 포트를 사용하는 웹서버가 떠있는 경우 사용이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 Nginx를 사용하지 않아 standalone을 적용했지만, 이제는 Nginx를 사용하는 환경이기 때문에 다른 설정이 필요해졌다.&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;h2 data-ke-size=&quot;size26&quot;&gt;사전 작업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Gabia DNS 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;땅콩 서비스는 도메인 주소를 Gabia에서 구매했기 때문에 Gabia에서 설정을 건드리게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS 관리툴에 들어가서 DNS 설정을 클릭한다.&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;1625&quot; data-origin-height=&quot;36&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKTXpQ/btsKYioSfeq/8CcjBZ9JCnb8G0736BTre0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKTXpQ/btsKYioSfeq/8CcjBZ9JCnb8G0736BTre0/img.png&quot; data-alt=&quot;Gabia - DNS 관리툴 - DNS 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKTXpQ/btsKYioSfeq/8CcjBZ9JCnb8G0736BTre0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKTXpQ%2FbtsKYioSfeq%2F8CcjBZ9JCnb8G0736BTre0%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;1625&quot; height=&quot;36&quot; data-origin-width=&quot;1625&quot; data-origin-height=&quot;36&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gabia - DNS 관리툴 - DNS 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;A 레코드&lt;/span&gt; : &lt;span style=&quot;background-color: #f6e199;&quot;&gt;서브 도메인&lt;/span&gt; : &lt;span style=&quot;background-color: #f3c000;&quot;&gt;EC2 IPv4&lt;/span&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;2. Nginx 설치&lt;/h3&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apt-get update 후 Nginx 설치&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;Nginx 연결 설정&lt;/h4&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;sudo vi /etc/nginx/sites-available/default&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default 파일에 연결 설정 수행&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;설정 전) /etc/nginx/sites-available/default&lt;/h4&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;server {
        listen 80; 
        location / { 
                return 302 https://$host$request_uri;
        }
}

server {
        listen 443;
        server_name your_domain; # 본인의 도메인 주소 설정

        location / { 
                proxy_pass &amp;lt;http://127.0.0.1:8080&amp;gt;;  # 8080번 포트로 요청 전달
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
}
&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. certbot 설치&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;sudo apt-get install python3-certbot-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx 인증서 흭득이 가능한 certbot을 설치&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;2. 인증서 흭득&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;certbot Nginx를 통해 인증서를 얻고 설정&lt;/h4&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;# nginx를 사용해서 인증서를 관리하는 nginx certbot
sudo certbot --nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx를 사용하는 인증서 등록 명령어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령을 입력하면 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;ssl 인증서 관련 알림을 받을 'Email'&lt;/span&gt;&amp;nbsp;, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;SSL 설정을 수행할 '도메인 주소'&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;만약 Rollback이 필요하다면?&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo certbot --nginx rollback --nginx&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;만약 Nginx default 파일의 설정이 예상과 다르게 망가진 경우 사용하면 좋을 것 같다.&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;설정 후) /etc/nginx/sites-available/default&lt;/h4&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;server {
        listen 80;

        return 302 https://$host$request_uri;
}

server {
				listen 443 ssl; # managed by Certbot
        server_name your_domain;

        ssl_certificate /etc/letsencrypt/live/api.dev.ddangkong.kr/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/api.dev.ddangkong.kr/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot     

        location / {
                proxy_pass &amp;lt;http://127.0.0.1:8080&amp;gt;;  # 8080번 포트로 요청 전달
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 설정 후에 내가 조금 조정해준 default 파일(기본 서버 블록 설정 파일)이다.&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;3. HTTPS 정상 동작 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnpjK0/btsKZN84A7V/EPK2pZzp1AJNhA3i8kZW1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnpjK0/btsKZN84A7V/EPK2pZzp1AJNhA3i8kZW1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnpjK0/btsKZN84A7V/EPK2pZzp1AJNhA3i8kZW1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnpjK0%2FbtsKZN84A7V%2FEPK2pZzp1AJNhA3i8kZW1k%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;266&quot; height=&quot;86&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 이후에는 위와 같이 https 요청이 정상적으로 수행된다.&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;h2 data-ke-size=&quot;size26&quot;&gt;인증서 자동 갱신 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eff-certbot.readthedocs.io/en/latest/using.html#setting-up-automated-renewal&quot;&gt;User Guide &amp;mdash; Certbot 3.1.0.dev0 documentation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서를 보면 &lt;u&gt;SSL 인증서 갱신을 자동화시킬 수 있는 방안&lt;/u&gt;도 제시하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 인증서 renew 명령어와 crontab 을 활용한 방안이다.&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;1. cron 설정 명령&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;SLEEPTIME=$(awk 'BEGIN{srand(); print int(rand()*(3600+1))}'); echo &quot;0 0,12 * * * root sleep $SLEEPTIME &amp;amp;&amp;amp; certbot renew -q&quot; | sudo tee -a /etc/crontab &amp;gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령은 공식문서에서 제공한 crontab 설정 명령어이다.&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-filename=&quot;스크린샷 2024-11-28 13.41.07.png&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7mqMl/btsKZPMytTn/J5kSkFrzqWDZbJwmIJ4Vuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7mqMl/btsKZPMytTn/J5kSkFrzqWDZbJwmIJ4Vuk/img.png&quot; data-alt=&quot;/etc/crontab&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7mqMl/btsKZPMytTn/J5kSkFrzqWDZbJwmIJ4Vuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7mqMl%2FbtsKZPMytTn%2FJ5kSkFrzqWDZbJwmIJ4Vuk%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;831&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2024-11-28 13.41.07.png&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;/etc/crontab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령을 쉘에 입력하면 다음과 같이 자동으로 crontab 설정이 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 0~3600초를 랜덤하게 대기하고 실행하는 지는 잘 모르겠다만 아마도 하나의 인증서를 여러 서버에서 갱신하는 경우 Rate Limit에 의해 갱신 요청 수가 초과되어 갱신이 되지 않는 문제를 막기 위함일까 싶다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. cron 재시작&lt;/h3&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;sudo systemctl restart cron
sudo systemctl status cron
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 동작하도록 해주기 위해 cron을 재시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 완료되었다면 문제없이 지속적으로 HTTPS 통신을 이용할 수 있다.&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>BackEnd/Infra</category>
      <category>certbot ssl 인증서 갱신</category>
      <category>certbot 인증서 자동 갱신</category>
      <category>EC2 HTTPS</category>
      <category>ec2 ssl</category>
      <category>ec2 ssl 인증서 갱신</category>
      <category>nginx</category>
      <category>nginx ssl</category>
      <category>SSL 인증서</category>
      <category>ssl 인증서 자동갱신</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/183</guid>
      <comments>https://pgmjun.tistory.com/183#entry183comment</comments>
      <pubDate>Thu, 28 Nov 2024 13:42:14 +0900</pubDate>
    </item>
    <item>
      <title>AWS IAM Region 제한하기</title>
      <link>https://pgmjun.tistory.com/182</link>
      <description>&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;400&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWTG6Q/btsKX5uVi9m/nlqidFS2s59YVdCbzcl9QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWTG6Q/btsKX5uVi9m/nlqidFS2s59YVdCbzcl9QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWTG6Q/btsKX5uVi9m/nlqidFS2s59YVdCbzcl9QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWTG6Q%2FbtsKX5uVi9m%2FnlqidFS2s59YVdCbzcl9QK%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;241&quot; height=&quot;225&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;373&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코 수료일이 다가오면서, 우테코에서 제공했던 AWS 계정 만료일도 코 앞으로 다가왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 우리 팀 계정으로 서버를 이관하기 위해 사전 작업을 수행하는 과정에서 IAM 관리자 사용자에 &lt;b&gt;Region 제한 설정&lt;/b&gt;을 하게 되어 글로 남겨보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서 솔루션을 이용하다보면 &lt;u&gt;실수로 타 리전을 이용&lt;/u&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;b&gt;사용할 리전에 동일한 설정을 반복해야하는 문제&lt;/b&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;b&gt;타 리전에 실수로 만든 것을 모르고 넘어가게 된다면 비용 문제&lt;/b&gt;로도 이어진다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&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;이를 해결하고자 사용할 IAM 사용자에 권한 정책 설정을 통해 서울 리전만 사용 가능하도록 설정해보고자 했다.&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;h2 data-ke-size=&quot;size26&quot;&gt;설정 과정&lt;/h2&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;692&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccq8aS/btsKVsElaMG/fuXp7ABvC2Fck3JkynDUPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccq8aS/btsKVsElaMG/fuXp7ABvC2Fck3JkynDUPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccq8aS/btsKVsElaMG/fuXp7ABvC2Fck3JkynDUPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccq8aS%2FbtsKVsElaMG%2FfuXp7ABvC2Fck3JkynDUPk%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;361&quot; height=&quot;248&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용할 IAM에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;권한 추가 - 인라인 정책 생성&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-25 15.01.49.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qIuHu/btsKVAB8irp/lnfTyWQjL35PJZTav8qkA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qIuHu/btsKVAB8irp/lnfTyWQjL35PJZTav8qkA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qIuHu/btsKVAB8irp/lnfTyWQjL35PJZTav8qkA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqIuHu%2FbtsKVAB8irp%2FlnfTyWQjL35PJZTav8qkA1%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;652&quot; height=&quot;470&quot; data-filename=&quot;스크린샷 2024-11-25 15.01.49.png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;1536&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732514342752&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: [
		{
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Action&quot;: &quot;*&quot;,
			&quot;Resource&quot;: &quot;*&quot;,
			&quot;Condition&quot;: {
				&quot;StringEquals&quot;: {
					&quot;aws:RequestedRegion&quot;: [
						&quot;ap-northeast-2&quot;
					]
				}
			}
		},
		{
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Action&quot;: [
				&quot;iam:*&quot;,
				&quot;cloudfront:*&quot;,
				&quot;s3:*&quot;
			],
			&quot;Resource&quot;: &quot;*&quot;
		}
	]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 권한 설정 값은 &lt;b&gt;AdministratorAccess&lt;/b&gt; 권한에 &lt;u&gt;서울(ap-northeast-2) 리전 제한&lt;/u&gt;을 걸어둔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AdministratorAccess 라는 권한은 &lt;b&gt;관리자 직무&lt;/b&gt; IAM에게 설정하는 권한이다.&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;AWS 계정 내 모든 리소스와 서비스에 대해 &lt;b&gt;무제한 액세스 권한&lt;/b&gt;을 가지도록 설계된 정책인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&quot;Action&quot;: &quot;*&quot;&lt;/b&gt; 는&amp;nbsp;&lt;/span&gt;모든 AWS 서비스의 모든 작업을 허용을 뜻하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&quot;Resource&quot;: &quot;*&quot;&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;관련 내용은 &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/access_policies_job-functions.html#jf_administrator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&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;h4 data-ke-size=&quot;size20&quot;&gt;IAM, CloudFront, S3는 왜 모든 리전에서 허용해주는가?&lt;/h4&gt;
&lt;pre id=&quot;code_1732623879637&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;Effect&quot;: &quot;Allow&quot;,
	&quot;Action&quot;: [
		&quot;iam:*&quot;,
		&quot;cloudfront:*&quot;,
		&quot;s3:*&quot;
	],
	&quot;Resource&quot;: &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;IAM, CloudFront, S3는 &lt;b&gt;Global한 솔루션&lt;/b&gt;이기 때문에 ap-northeast-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;&amp;nbsp;&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-filename=&quot;스크린샷 2024-11-25 15.02.16.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BPkNe/btsKWmDaRhg/OZgsKfGytEotDGNot7Ecbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BPkNe/btsKWmDaRhg/OZgsKfGytEotDGNot7Ecbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BPkNe/btsKWmDaRhg/OZgsKfGytEotDGNot7Ecbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBPkNe%2FbtsKWmDaRhg%2FOZgsKfGytEotDGNot7Ecbk%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;414&quot; height=&quot;224&quot; data-filename=&quot;스크린샷 2024-11-25 15.02.16.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 권한의 이름은 &lt;b&gt;AdministratorAccessOnlySeoul&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Infra</category>
      <category>AWS</category>
      <category>aws iam resion restrict</category>
      <category>aws iam resion 제한</category>
      <category>aws iam 리전</category>
      <category>aws iam 리전 제한</category>
      <category>iam reision restrict</category>
      <category>iam 리전 설정</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/182</guid>
      <comments>https://pgmjun.tistory.com/182#entry182comment</comments>
      <pubDate>Tue, 26 Nov 2024 21:44:15 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin 코드를 Java 코드로 디컴파일(Decompile) 하는 방법</title>
      <link>https://pgmjun.tistory.com/181</link>
      <description>&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;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HUR97/btsKT9qYe0d/KuAYbzdgYk0rv59fwP5ne1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HUR97/btsKT9qYe0d/KuAYbzdgYk0rv59fwP5ne1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HUR97/btsKT9qYe0d/KuAYbzdgYk0rv59fwP5ne1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHUR97%2FbtsKT9qYe0d%2FKuAYbzdgYk0rv59fwP5ne1%2Fimg.webp&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;348&quot; height=&quot;348&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&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;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 코드를 코틀린 코드로 마이그레이션 하는 과정에서 &lt;b&gt;&quot;이거 자바 코드로 바꾸면 어떻게 되지?&quot;&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;특히 상수 선언 방법이 2개인 코틀린에서 &lt;b&gt;companion object, const&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Kotlin to Java 디컴파일(Decompile)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 을 Java로 디컴파일 한다는 것은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kotlin으로 작성된 언어를 .class 파일로 컴파일한 다음, 다시 Java로 디컴파일을 수행&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;결국 Kotlin으로 작성된 코드가 Java로 보면 어떤 지 확인할 수 있기 때문에 Java -&amp;gt; Kotlin 마이그레이션 상황에서 굉장히 유용하게 사용할 수 있다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ는 JetBrains에서 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotiln 또한 JetBrains에서 만든 언어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 JetBrains에선 Kotlin 언어에 대한 지원이 풍족한데, 이 기능 또한 IntelliJ에서 제공하는 기능이다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-23 15.52.59.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYelrb/btsKTZPsxN1/ag6iwb9oyq2RzWDiD1upB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYelrb/btsKTZPsxN1/ag6iwb9oyq2RzWDiD1upB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYelrb/btsKTZPsxN1/ag6iwb9oyq2RzWDiD1upB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYelrb%2FbtsKTZPsxN1%2Fag6iwb9oyq2RzWDiD1upB0%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;520&quot; height=&quot;227&quot; data-filename=&quot;스크린샷 2024-11-23 15.52.59.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;322&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;IDE 화면에서 shift를 2번 연속으로 누르게 되면 위와 같은 입력창이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;show kotlin bytecode&amp;nbsp;&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;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-filename=&quot;스크린샷 2024-11-23 15.53.06.png&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kSHt4/btsKUvUUjlW/z3HoO4W3l1AtP0pxCKwri0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kSHt4/btsKUvUUjlW/z3HoO4W3l1AtP0pxCKwri0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kSHt4/btsKUvUUjlW/z3HoO4W3l1AtP0pxCKwri0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkSHt4%2FbtsKUvUUjlW%2Fz3HoO4W3l1AtP0pxCKwri0%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;558&quot; height=&quot;170&quot; data-filename=&quot;스크린샷 2024-11-23 15.53.06.png&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그럼 위와 같이 화면 우측에 Decompile 버튼이 나타나는데, 클릭해보자&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&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0P4tI/btsKVbn5GRX/QKsOgtsxB9iorrn8RpQuh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0P4tI/btsKVbn5GRX/QKsOgtsxB9iorrn8RpQuh0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;1198&quot; data-filename=&quot;스크린샷 2024-11-23 15.59.13.png&quot; width=&quot;406&quot; height=&quot;529&quot; style=&quot;width: 45.8725%; margin-right: 10px;&quot; data-widthpercent=&quot;46.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0P4tI/btsKVbn5GRX/QKsOgtsxB9iorrn8RpQuh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0P4tI%2FbtsKVbn5GRX%2FQKsOgtsxB9iorrn8RpQuh0%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;920&quot; height=&quot;1198&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lt9SY/btsKS4xsjie/2ZTpkYVkK9CayiKKzItG9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lt9SY/btsKS4xsjie/2ZTpkYVkK9CayiKKzItG9K/img.png&quot; data-filename=&quot;스크린샷 2024-11-23 15.53.12.png&quot; data-origin-height=&quot;1306&quot; data-origin-width=&quot;1158&quot; data-is-animation=&quot;false&quot; width=&quot;406&quot; height=&quot;458&quot; style=&quot;width: 52.9647%;&quot; data-widthpercent=&quot;53.59&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lt9SY/btsKS4xsjie/2ZTpkYVkK9CayiKKzItG9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flt9SY%2FbtsKS4xsjie%2F2ZTpkYVkK9CayiKKzItG9K%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;1158&quot; height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌) Kotlin 코드 / (우) Decompile된 코드&lt;/figcaption&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;그럼 위와 같이 Kotlin 코드가 class 파일로 컴파일되었다가, 다시 Java 코드로 디컴파일된 위와 같은 결과를 얻을 수 있다.&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;이를 통해 내가 작성한 Kotlin 코드가 Java에선 어떨지 확인하여 내가 의도한 코드가 맞는 지 확인하면서 마이그레이션을 진행할 수 있게 된다.&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>Language/Kotlin</category>
      <category>kotlin to java 마이그레이션</category>
      <category>인텔리제이 코틀린 툴</category>
      <category>자바 코틀린 마이그레이션</category>
      <category>코틀린</category>
      <category>코틀린 디컴파일</category>
      <category>코틀린 마이그레이션</category>
      <category>코틀린 자바</category>
      <category>코프링</category>
      <category>코프링 마이그레이션</category>
      <category>코프링 마이그레이션 팁</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/181</guid>
      <comments>https://pgmjun.tistory.com/181#entry181comment</comments>
      <pubDate>Sat, 23 Nov 2024 16:03:36 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin에서의 상수 선언 (feat. const와 static final의 차이)</title>
      <link>https://pgmjun.tistory.com/180</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xDXFm/btsKVcAwMPS/furIgSUlBYKr7T3bkzKF01/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xDXFm/btsKVcAwMPS/furIgSUlBYKr7T3bkzKF01/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xDXFm/btsKVcAwMPS/furIgSUlBYKr7T3bkzKF01/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxDXFm%2FbtsKVcAwMPS%2FfurIgSUlBYKr7T3bkzKF01%2Fimg.webp&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;314&quot; height=&quot;314&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&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;&amp;nbsp;&lt;/p&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;상수 선언 과정&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;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732341013874&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SampleClass {
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(&quot;yyyy/MM/dd HH:mm:ss&quot;);
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 객체를 &lt;b&gt;static final&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_1732341068806&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val DATE_TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern(&quot;yyyy/MM/dd HH:mm:ss&quot;)

class SampleClass(&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;&amp;nbsp;&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-filename=&quot;스크린샷 2024-11-23 14.51.19.png&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bud2Zx/btsKTEY3KMJ/iH9LycOhcMnEvR7HPbeCZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bud2Zx/btsKTEY3KMJ/iH9LycOhcMnEvR7HPbeCZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bud2Zx/btsKTEY3KMJ/iH9LycOhcMnEvR7HPbeCZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbud2Zx%2FbtsKTEY3KMJ%2FiH9LycOhcMnEvR7HPbeCZ0%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;702&quot; height=&quot;71&quot; data-filename=&quot;스크린샷 2024-11-23 14.51.19.png&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const 키워드에 위와 같이 &lt;b&gt;primitives and String 타입만 허용&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;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;const 키워드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결 전, const가 무엇인지부터 먼저 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const는 자바의 &lt;b&gt;static final &lt;/b&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;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-23 14.56.42.png&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chsBeS/btsKTEksSBd/tTuTC17zVPUaIri5FwwSNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chsBeS/btsKTEksSBd/tTuTC17zVPUaIri5FwwSNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chsBeS/btsKTEksSBd/tTuTC17zVPUaIri5FwwSNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchsBeS%2FbtsKTEksSBd%2FtTuTC17zVPUaIri5FwwSNk%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;496&quot; height=&quot;124&quot; data-filename=&quot;스크린샷 2024-11-23 14.56.42.png&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 const로 선언해둔 상수에 변경을 시도하면 위와 같은 컴파일 에러가 발생할 것이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;알고가면 좋은 점1) Kotlin final과 Java final의 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 static final이 상수 선언이었는데 그럼 코틀린에서의 final은 뭘까?&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;코틀린 final의 역할은 불변이 아닌 &lt;b&gt;상속을 불가능하게 만드는 역할&lt;/b&gt;을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드나 클래스 옆에 &lt;b&gt;final&lt;/b&gt; 키워드를 붙이게되면 &lt;u&gt;상속이 불가능&lt;/u&gt;해지며, 코틀린은 &lt;u&gt;기본적으로 final&lt;/u&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;때문에 JPA를 사용할 때 Entity 객체는 직접 open 키워드를 붙여서 상속을 열어주거나,&lt;br /&gt;Plugin을 사용해서 JPA 관련 객체 (MappedSuperclass, Entity 등) 에 open을 자동으로 붙여주는 등의 추가적 설정이 필요하다.&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;h4 data-ke-size=&quot;size20&quot;&gt;알고가면 좋을 점2) val과 const의 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;val과 const의 차이도 간혹 헷갈려하는 사람이 있는 것 같아 적는다.&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;size18&quot;&gt;[할당되는 메모리 공간]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;const&lt;/b&gt;는 글로벌한 상수로 JVM 런타임 데이터 영역(Runtime Data Area) 중 &lt;b&gt;Method 영역&lt;/b&gt;에 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;val&lt;/b&gt;로 선언된 프로퍼티는 객체의 인스턴스가 생성될 때마다 매번 같은 값이 &lt;b&gt;Heap 영역&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;[상수와 변수]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;const&lt;/b&gt;는 &lt;b&gt;상수&lt;/b&gt;로 단 하나만 선언되어있고 글로벌한 값이기 때문에 여러 클래스에서 공유해서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &lt;b&gt;val&lt;/b&gt;는 setter를 막아 불변 설정만 되어있는 &lt;b&gt;read-only 변수&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const가 뭔지는 알았는데, 그럼 const를 사용하면 왜 Primitive Type과 String만 선언이 가능할까?&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;const&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 &lt;b&gt;static final&lt;/b&gt;은 &lt;b&gt;런타임 시점을 지원&lt;/b&gt;했지만, 코틀린의 const는 그렇지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 String, Primitive Type만 const를 통한 상수 선언이 가능한 것이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;해결 방안&lt;/h2&gt;
&lt;pre id=&quot;code_1732344153201&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val CONST_STRING = &quot;const&quot;

class SampleClass(
) {
    companion object {
        private val COMPANION_STRING = &quot;companion&quot;
    }
}&lt;/code&gt;&lt;/pre&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;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;const&lt;/b&gt; 키워드를 통해 클래스 최상위에 상수를 선언하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;companion object&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;객체를 상수로 선언하기 위해서는 const가 아닌 &lt;b&gt;companion object&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동반 객체 (companion object) 활용하기&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class SampleClass(
) {
    companion object {
        private val DATE_TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern(&quot;yyyy/MM/dd HH:mm:ss&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;companion object&lt;/b&gt;를 활용하면 이처럼 DateTimeFormatter 와 같은 객체도 상수로 선언하는 것이 가능하다.&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;h4 data-ke-size=&quot;size20&quot;&gt;Kotlin to Java 디컴파일(decompile)을 통해 결과 확인해보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ의 유용한 기능인 Kotlin to Java 디컴파일(decompile)을 통해서 정말 static final로 선언이 되는지 확인해보았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;a href=&quot;https://pgmjun.tistory.com/181&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IntelliJ Kotlin to Java 디컴파일(decompile) 사용법 알아보기&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-23 15.26.40.png&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPMlcp/btsKUiA8NbX/wvUkfQEKT1L0rRGmAr3gDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPMlcp/btsKUiA8NbX/wvUkfQEKT1L0rRGmAr3gDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPMlcp/btsKUiA8NbX/wvUkfQEKT1L0rRGmAr3gDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPMlcp%2FbtsKUiA8NbX%2FwvUkfQEKT1L0rRGmAr3gDK%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;718&quot; height=&quot;172&quot; data-filename=&quot;스크린샷 2024-11-23 15.26.40.png&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 위와 같이 DateTimeFormatter가 정상적으로 상수 선언되었다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;companion object&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;companion object&lt;/b&gt;에 상수가 정의되고, 그렇지 않은 경우에는 &lt;b&gt;const&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;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코틀린 상수 선언 규칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 상수 객체는 companion object에 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Primitive Type &amp;amp; String은 클래스 최상위에 const로 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private const val CONST_STRING = &quot;const&quot;

class SampleClass(
) {
    companion object {
        private val DATE_TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern(&quot;yyyy/MM/dd HH:mm:ss&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 결론적으로 위와 같은 코드가 만들어질 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;만약 더 좋은 방안이 있다면 댓글로 남겨주면 참고해보겠습니다!&lt;/blockquote&gt;</description>
      <category>Language/Kotlin</category>
      <category>const static final 차이</category>
      <category>오블완</category>
      <category>자바 코틀린 마이그레이션</category>
      <category>코틀린 companion object</category>
      <category>코틀린 val const 차이</category>
      <category>코틀린 상수 선언</category>
      <category>코틀린 상수 선언하는 법</category>
      <category>코틀린 자바</category>
      <category>코틀린 자바 final 차이</category>
      <category>티스토리챌린지</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/180</guid>
      <comments>https://pgmjun.tistory.com/180#entry180comment</comments>
      <pubDate>Sat, 23 Nov 2024 15:47:20 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin에서 Lombok 적용이 안되는 이유 (feat. 코틀린 Slf4j)</title>
      <link>https://pgmjun.tistory.com/179</link>
      <description>&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-filename=&quot;스크린샷 2024-11-21 02.42.38.png&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQwDAS/btsKQzC0Enb/vA8vvnxDtXGXkGje6amHj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQwDAS/btsKQzC0Enb/vA8vvnxDtXGXkGje6amHj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQwDAS/btsKQzC0Enb/vA8vvnxDtXGXkGje6amHj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQwDAS%2FbtsKQzC0Enb%2FvA8vvnxDtXGXkGje6amHj1%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;432&quot; height=&quot;375&quot; data-filename=&quot;스크린샷 2024-11-21 02.42.38.png&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;850&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왓캠퍼스 프로젝트를 Java/Spring -&amp;gt; Kotlin/Spring 으로 마이그레이션 하는 과정에서 하나의 문제를 마주했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 '&lt;b&gt;Kotlin 코드에서 Lombok 적용이 안 된다는 점' &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;이에 따라 간편한 로깅을 위해 사용하던 Lombok의 @Slf4j 애노테이션까지 사용이 불가능해졌다.&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;Lombok이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lombok &lt;a href=&quot;https://projectlombok.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;의 소개에 따르면 Lombok은 아래와 같은 라이브러리이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.&lt;br /&gt;Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.&lt;br /&gt;&lt;br /&gt;&quot;Project Lombok은 편집기와 빌드 도구에 자동으로 플러그인되는 Java 라이브러리로, Java를 더욱 즐겁게 해줍니다.&lt;br /&gt;다시는 getter나 equals 메서드를 쓰지 마세요. 하나의 주석으로 클래스에 모든 기능을 갖춘 빌더가 생깁니다. 로깅 변수를 자동화하고, 훨씬 더 많은 작업을 할 수 있습니다.&quot;&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;즉 Lombok이란 Java의 Getter, Setter, Eqauls&amp;amp;Hashcode, LoggingFactory 등과 같은 &lt;i&gt;&lt;u&gt;매번 작성해야하는 보일러플레이트 코드를 애노테이션 설정을 통해 간편화&lt;/u&gt; &lt;/i&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;h3 data-ke-size=&quot;size23&quot;&gt;Slf4j란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slf4j란&lt;b&gt; Simple Logging Facade for Java&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;Logback, Log4j, java.util.logging 등 다양한 로깅 프레임워크와 연동 가능하게 설계되어있다.&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;b&gt;Logback&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;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@Slf4j 란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Slf4j는 Lombok에서 제공하는 애노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slf4j를 사용하기 위해 필요한 보일러플레이트 코드를 애노테이션을 통해 Lombok이 컴파일 시점에 대신 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732118965813&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
class Sample {
	private val log = LoggerFactory.getLogger(this::class.java)
	//...
}&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732118996983&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
class Sample {
	//...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Lombok을 사용하면&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이렇게 간편한 Lombok의 @Slf4j 애노테이션을 사용해서 쉽게 Kotlin 코드에서도 로깅을 하려했으나 실패한 이유는 무엇일까?&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;'Lombok이 어떻게 동작하는 지'&lt;/b&gt; 와 &lt;b&gt;'Java, Kotlin의 컴파일 과정의 차이'&lt;/b&gt; 에 대해서 이해하면 빠르게 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Lombok은 javac의 컴파일 과정 중 &lt;/span&gt;&lt;b&gt;Annotation Processing&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;클래스에 선언된 Lombok 애노테이션을 바탕으로 실제 코드를 작성해주는 방식으로 동작한다.&lt;/li&gt;
&lt;li&gt;Kotlin은 Java의 컴파일러인 Javac와는 별도의 컴파일러인 Kotlinc를 사용하며, Kotlinc는 Javac보다 먼저 컴파일을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2가지 사실을 바탕으로 문제의 원인을 분석해보자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Annotation Processing 등의 Javac의 동작 과정에 대해 자세히 알고 싶다면 &lt;a href=&quot;https://pgmjun.tistory.com/178&quot;&gt;이 글&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kotlin 코드에서 Lombok 적용이 안 된 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Javac의 동작 과정 중 &lt;b&gt;Annotation Processing&lt;/b&gt; 과정에서 Lombok의 보일러플레이트 처리 작업이 수행된다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Kotlinc가 Javac보다 더 먼저 동작한다고 했다.&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;때문에 Kotlinc의 컴파일 시점에는 Lombok에 의한 코드가 작성되지 않는다.&lt;br /&gt;이로 인해 Lombok 애노테이션으로 아무리 선언하더라도 적용이되지 않던 것이었다.&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;단독으로 Kotlin만 사용하는 경우에도 마찬가지로 Javac가 동작할 일이 당연히 없으니, Lombok을 사용할 수 없다.&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;h4 data-ke-size=&quot;size20&quot;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQvvog/btsKRlRM6TB/VGnIJ4Gw2k0XvnxXzxY3sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQvvog/btsKRlRM6TB/VGnIJ4Gw2k0XvnxXzxY3sK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQvvog/btsKRlRM6TB/VGnIJ4Gw2k0XvnxXzxY3sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQvvog%2FbtsKRlRM6TB%2FVGnIJ4Gw2k0XvnxXzxY3sK%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;487&quot; height=&quot;345&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;706&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;실제로 @Getter를 설정해둔 &lt;b&gt;Campus.java &lt;/b&gt;를 컴파일하게 되면&amp;nbsp;&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-filename=&quot;스크린샷 2024-11-21 02.47.31.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A0Plf/btsKQgYhlSX/G08Mu1ZOLEbDgh0cjIbyK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A0Plf/btsKQgYhlSX/G08Mu1ZOLEbDgh0cjIbyK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A0Plf/btsKQgYhlSX/G08Mu1ZOLEbDgh0cjIbyK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA0Plf%2FbtsKQgYhlSX%2FG08Mu1ZOLEbDgh0cjIbyK1%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;523&quot; height=&quot;276&quot; data-filename=&quot;스크린샷 2024-11-21 02.47.31.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Getter들이 생성되는 것을 알 수 있었다.&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-filename=&quot;스크린샷 2024-11-21 02.48.29.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AoeSA/btsKRxxNNxq/lcO0NJ4JtLfNOUHCdOiZf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AoeSA/btsKRxxNNxq/lcO0NJ4JtLfNOUHCdOiZf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AoeSA/btsKRxxNNxq/lcO0NJ4JtLfNOUHCdOiZf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAoeSA%2FbtsKRxxNNxq%2FlcO0NJ4JtLfNOUHCdOiZf1%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;267&quot; height=&quot;237&quot; data-filename=&quot;스크린샷 2024-11-21 02.48.29.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용은 build 이후 생성되는&lt;b&gt; build - classes&lt;/b&gt; 패키지의 &lt;b&gt;.class&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;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-21 02.49.43.png&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOU0tt/btsKRgiOrcr/OZoxqAPFHUwoMiidCgUsCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOU0tt/btsKRgiOrcr/OZoxqAPFHUwoMiidCgUsCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOU0tt/btsKRgiOrcr/OZoxqAPFHUwoMiidCgUsCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOU0tt%2FbtsKRgiOrcr%2FOZoxqAPFHUwoMiidCgUsCK%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;417&quot; height=&quot;335&quot; data-filename=&quot;스크린샷 2024-11-21 02.49.43.png&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;758&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;똑같이 @Getter를 선언해둔&lt;b&gt; Member.kt&lt;/b&gt; 파일을 컴파일하는 경우&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-filename=&quot;스크린샷 2024-11-21 02.45.32.png&quot; data-origin-width=&quot;2210&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lVODC/btsKRTUWRO4/LW6pX5vEMTssl2a9YbLL20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lVODC/btsKRTUWRO4/LW6pX5vEMTssl2a9YbLL20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lVODC/btsKRTUWRO4/LW6pX5vEMTssl2a9YbLL20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlVODC%2FbtsKRTUWRO4%2FLW6pX5vEMTssl2a9YbLL20%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;753&quot; height=&quot;182&quot; data-filename=&quot;스크린샷 2024-11-21 02.45.32.png&quot; data-origin-width=&quot;2210&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 별도의 Getter 메서드는 생성되지 않은 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Slf4j 와 같은 애노테이션도 마찬가지였다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;문제 해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Kotlin Lombok Plugin (실패)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결을 위해 코틀린 공식문서를 찾아보는 과정에서 Lombok 관련 Kotlin의 플러그인이 있는 것을 발견했다.&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;공식문서에서는 Kotlin에서 Lombok을 사용할 수 있도록 도와주는 Plugin을 제시하고 있으나,&lt;br /&gt;완벽한 버전은 아니며 아직은 모든 Lombok 애노테이션이 호환되고 있지는 않는다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;u&gt;아쉽게도 여기에 @Slf4j 애노테이션은 없었다.&lt;/u&gt;&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;궁금한 사람은 해당 &lt;a href=&quot;https://kotlinlang.org/docs/lombok.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 링크&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Logger 흭득 코드 직접 작성&lt;/h3&gt;
&lt;pre id=&quot;code_1732171554417&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SampleClass {
    private val log = LoggerFactory.getLogger(this::class.java)
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Slf4j 애노테이션 없이, Logger를 흭득하려면 위와 같이 로그를 찍는 모든 클래스마다 일일히 LoggerFactory 코드를 작성해주어야 한다.&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_1732172616631&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EnableScheduling
@SpringBootApplication
class SampleClass

inline fun &amp;lt;reified T&amp;gt; T.logger() = LoggerFactory.getLogger(T::class.java)!!

fun main(args: Array&amp;lt;String&amp;gt;) {
    SpringApplication.run(SampleClass::class.java, *args)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 최상위 클래스 Application 클래스에 &lt;b&gt;inline, reified&lt;/b&gt; 키워드를 통한 LoggerFactory.getLogger() 함수를 선언해주어 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732172918090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SampleClass {
	private val log = logger()
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 선언해두게 되면, 하위 클래스에서 위와 같이 간단하게 logger를 흭득하여 사용할 수 있다.&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;inline 키워드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서의 함수는 &lt;a href=&quot;https://en.wikipedia.org/wiki/First-class_function&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;일급 함수&lt;/a&gt;이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99&quot;&gt;컴퓨터 과학&lt;/a&gt;에서&amp;nbsp;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%96%B8%EC%96%B4&quot;&gt;프로그래밍 언어&lt;/a&gt;는&amp;nbsp;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%95%A8%EC%88%98_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)&quot;&gt;함수&lt;/a&gt;를&amp;nbsp;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%BC%EA%B8%89_%EA%B0%9D%EC%B2%B4&quot;&gt;일급 객체&lt;/a&gt;로 취급하는 경우&amp;nbsp;일급 함수(first-class function)를 갖는다고 한다. 이는 언어가 함수를 다른 함수에 대한 인수로 전달하고, 이를 다른 함수의 값으로 반환하고, 변수에 할당하거나 자료 구조에 저장하는 것을 지원한다는 것을 의미한다.&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%BC%EA%B8%89_%ED%95%A8%EC%88%98#cite_note-1&quot;&gt;[1]&lt;/a&gt;&amp;nbsp;일부 프로그래밍 언어 이론가들은&amp;nbsp;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B5%EB%AA%85_%ED%95%A8%EC%88%98&quot;&gt;익명 함수&lt;/a&gt;(함수 리터럴)에 대한 지원도 요구한다.&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%BC%EA%B8%89_%ED%95%A8%EC%88%98#cite_note-test-2&quot;&gt;[2]&lt;/a&gt;&amp;nbsp;일급 함수를 사용하는 언어에서는 함수 이름에 특별한 상태가 없다. 이는 함수 유형의 일반&amp;nbsp;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B3%80%EC%88%98_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)&quot;&gt;변수&lt;/a&gt;처럼 취급된다&lt;br /&gt;&lt;br /&gt;위키피디아 (https://en.wikipedia.org/wiki/First-class_function)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 함수형 프로그래밍을 사용해봤다면 함수를 저장할 수 있는 Supplier, Consumer 등의 객체를 알고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일급이라는 의미는 &lt;b&gt;함수의 결과로 함수를 return&lt;/b&gt;하거나, &lt;b&gt;변수에 함수가 할당&lt;/b&gt;되는 등 &lt;u&gt;함수가 일반 변수처럼 취급되는 것&lt;/u&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732173692114&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private val log = logger()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 위와 같은 코드를 그냥 사용하게 되는 경우 logger() 함수를 저장하기 위한 메모리 공간이 필요해지는 등 &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;inline&lt;/b&gt; 키워드가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logger() 함수에 inline 키워드를 사용하게 되면 logger()가 일급 함수로서 특정 메모리 공간에 할당되는 것이 아니라 &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;그럼 logger() 함수 호출 시점에 호출한 함수를 찾아서 동작을 수행시키고 return 받은 값을 가져와 메모리 공간에 올리는 등의 오버헤드를 제거할 수 있게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;반면, 바이트코드의 크기가 커지는 등의 단점도 존재한다.&lt;br /&gt;자세한 내용은 &lt;a href=&quot;https://kotlinlang.org/docs/inline-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;reified 키워드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reified 키워드는 inline함수에만 사용할 수 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭을 사용해서 함수를 호출 시 매개변수로 Class 정보를 전달하는 것보다 더 쉽고 간편한 방법을 제공합니다.&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;pre id=&quot;code_1732253032740&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; isString(value: T): Boolean {
    return value is String // 경고: 'Unchecked cast'
}&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;JVM은 모든 제네릭 타입을 기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;Object&lt;span&gt;&amp;nbsp;&lt;/span&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;때문에 런타임에는 타입 체크가 불가능하다.&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;pre id=&quot;code_1732253135406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inline fun &amp;lt;reified T&amp;gt; isInstance(value: Any): Boolean {
    return value is T // 런타임에 T 타입 체크 가능
}

println(isInstance&amp;lt;String&amp;gt;(&quot;abc&quot;)) // true
println(isInstance&amp;lt;Int&amp;gt;(&quot;abc&quot;))    // false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 reified를 사용하면 제네릭 타입 정보를 런타임에도 유지하도록 만들어서 제네릭의 타입 소거 제한을 우회할 수 있다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-22 14.28.01.png&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/06IYG/btsKSSv31Lk/9kCYYaXfsHNsGuvVmkxAHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/06IYG/btsKSSv31Lk/9kCYYaXfsHNsGuvVmkxAHK/img.png&quot; data-alt=&quot;제네릭만 사용했을 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/06IYG/btsKSSv31Lk/9kCYYaXfsHNsGuvVmkxAHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F06IYG%2FbtsKSSv31Lk%2F9kCYYaXfsHNsGuvVmkxAHK%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;679&quot; height=&quot;75&quot; data-filename=&quot;스크린샷 2024-11-22 14.28.01.png&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제네릭만 사용했을 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭만 사용해서 logger() 메서드를 구현하는 경우,&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-22 14.28.13.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chz9uP/btsKRFYMLyI/QPKpJ5N0FCetkXGx0WsI40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chz9uP/btsKRFYMLyI/QPKpJ5N0FCetkXGx0WsI40/img.png&quot; data-alt=&quot;inline, reified를 사용했을 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chz9uP/btsKRFYMLyI/QPKpJ5N0FCetkXGx0WsI40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchz9uP%2FbtsKRFYMLyI%2FQPKpJ5N0FCetkXGx0WsI40%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;573&quot; height=&quot;26&quot; data-filename=&quot;스크린샷 2024-11-22 14.28.13.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;inline, reified를 사용했을 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 inline, reified는 타입 소거가 발생하지 않기 때문에 이렇게 에러가 나지 않는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;좀 더 자세한 내용은 &lt;a href=&quot;https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Kotlin-Logging 사용&lt;/h3&gt;
&lt;figure id=&quot;og_1732254114674&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 - oshai/kotlin-logging: Lightweight Multiplatform logging framework for Kotlin. A convenient and performant logging facad&quot; data-og-description=&quot;Lightweight Multiplatform logging framework for Kotlin. A convenient and performant logging facade. - oshai/kotlin-logging&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/oshai/kotlin-logging&quot; data-og-url=&quot;https://github.com/oshai/kotlin-logging&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hoT5Z/hyXzKMGcKp/O7vfynXsvaSIfvHFPsvrZk/img.png?width=1200&amp;amp;height=600&amp;amp;face=958_146_1033_228,https://scrap.kakaocdn.net/dn/bg3qWf/hyXzSYeLkN/VK2pPfsYZEZeHolKJpFMF1/img.png?width=1200&amp;amp;height=600&amp;amp;face=958_146_1033_228&quot;&gt;&lt;a href=&quot;https://github.com/oshai/kotlin-logging&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/oshai/kotlin-logging&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hoT5Z/hyXzKMGcKp/O7vfynXsvaSIfvHFPsvrZk/img.png?width=1200&amp;amp;height=600&amp;amp;face=958_146_1033_228,https://scrap.kakaocdn.net/dn/bg3qWf/hyXzSYeLkN/VK2pPfsYZEZeHolKJpFMF1/img.png?width=1200&amp;amp;height=600&amp;amp;face=958_146_1033_228');&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 - oshai/kotlin-logging: Lightweight Multiplatform logging framework for Kotlin. A convenient and performant logging facad&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Lightweight Multiplatform logging framework for Kotlin. A convenient and performant logging facade. - oshai/kotlin-logging&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;b&gt;Kotlin-Logging&lt;/b&gt; 을 사용하는 방식도 하나의 해결책으로 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 &lt;b&gt;SLF4J API&lt;/b&gt;를 사용하여 다른 로깅 프레임워크와 호환되며, logback Xml 파일을 만들어 로깅을 커스터마이징하는 것도 가능하다.&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;h4 data-ke-size=&quot;size20&quot;&gt;사용법&lt;/h4&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f6f8fa; color: #1f2328; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private val logger = KotlinLogging.logger {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 가볍고 간단하게 KotlinLogging.logger {} 만 입력하면 logger를 흭득할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732254306635&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logger.debug { &quot;Some $expensive message!&quot; }&lt;/code&gt;&lt;/pre&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 장점은 로깅 시에 &lt;b&gt;로그 수준 활성화 체크&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732254966566&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logger.debug(&quot;User data: &quot; + user.toString());&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;객체가 담겼을 경우 출력할 수 있는 형태로 변경하는 비용, &lt;/b&gt;&lt;b&gt;문자열 연산이 존재할 경우 해당 연산 비용&amp;nbsp;&lt;/b&gt;등이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 사용되지도 않을 데이터에 대해서 &lt;b&gt;메모리 할당, GC&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;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732254401106&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logger.debug { &quot;Some $expensive message!&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 kotlin-logging 프레임워크를 사용하면 위와 같은 로깅 로직이 존재할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732254630495&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// This is what happens when you write the above ^^^
if (logger.isDebugEnabled) logger.debug(&quot;Some $expensive message!&quot;)&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;blockquote data-ke-style=&quot;style3&quot;&gt;자세한 내용은 &lt;a href=&quot;https://github.com/oshai/kotlin-logging&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 깃허브&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>오블완</category>
      <category>코틀린 lombok</category>
      <category>코틀린 롬복</category>
      <category>코틀린에서 롬복 사용 불가</category>
      <category>코틀린에서 롬복 안되는 이유</category>
      <category>코틀링 스프링</category>
      <category>코프링</category>
      <category>티스토리챌린지</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/179</guid>
      <comments>https://pgmjun.tistory.com/179#entry179comment</comments>
      <pubDate>Fri, 22 Nov 2024 15:09:26 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Javac의 동작 과정은 어떻게 될까?</title>
      <link>https://pgmjun.tistory.com/178</link>
      <description>&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;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckJ9pC/btsKRFCr9BQ/7tpAzb74MozMMgvwPrpoV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckJ9pC/btsKRFCr9BQ/7tpAzb74MozMMgvwPrpoV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckJ9pC/btsKRFCr9BQ/7tpAzb74MozMMgvwPrpoV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckJ9pC%2FbtsKRFCr9BQ%2F7tpAzb74MozMMgvwPrpoV0%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;1280&quot; height=&quot;640&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;h2 data-ke-size=&quot;size26&quot;&gt;문제상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java/Spring을 &lt;b&gt;Kotlin/Spring&lt;/b&gt;으로 마이그레이션 하는 과정에서 Kotlin의 Lombok 사용에 어려움을 겪었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 이유 Lombok이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Javac&lt;/b&gt;의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Annotation Processing&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;과정에서 처리되는데, Javac보다 Kotlinc가 더 먼저 작동하여 Javac에서 Annotation Processing 과정을 통해 생성되는 코드를 Kotlin 코드에서는 알 수가 없기 때문임을 알게 되었다.&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;이 과정에서 Javac의 Annotation Processing을 포함한 전반적인 과정을 흝어보고 싶어 학습하여 기록해 두게 되었다.&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;h2 data-ke-size=&quot;size26&quot;&gt;javac의 동작 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javac의 동작과정에 대해 자세히 파헤쳐보자.&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;javac는 대략 아래 7가지 단계로 컴파일을 수행한다.&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;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Parsing&lt;/b&gt;: 소스 코드를 읽고 AST 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Annotation Processing&lt;/b&gt;: 애노테이션 처리 및 코드 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Semantic Analysis&lt;/b&gt;: 타입 검증과 의미 분석.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Intermediate Code Generation&lt;/b&gt;: 중간 코드 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Optimization&lt;/b&gt;: 코드 최적화.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Bytecode Generation&lt;/b&gt;: 바이트코드 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Class File Output&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;.class&lt;/span&gt;&lt;span&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;1. Parsing (구문 분석)&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Javac는 입력받은. java파일의 소스 코드를 읽고, 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://ko.wikipedia.org/wiki/%EC%B6%94%EC%83%81_%EA%B5%AC%EB%AC%B8_%ED%8A%B8%EB%A6%AC&quot;&gt;&lt;b&gt;추상 구문 트리(Abstract Syntax Tree, AST)&lt;/b&gt;&lt;/a&gt;로 변환한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;세부내용&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;토큰화(Tokenization): 코드의 각 요소를 토큰으로 나눈다. (ex. public class HelloWorld&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;[public] [class] [HelloWorld])&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;구문 분석(Syntax Analysis): 토큰을 기반으로 코드 구조를 파악하고, AST를 생성한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이 단계에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;구문 오류(Syntax Error)&lt;/b&gt;를 발견하면 컴파일이 중단된다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;2. Annotation Processing (애노테이션 처리)&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@Override&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;@Deprecated&lt;/span&gt;, 또는 사용자 정의 애노테이션(예: Lombok) 등을 처리한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;세부내용&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Annotation Processor&lt;/b&gt;가 등록된 애노테이션에 대해 지정된 작업을 수행한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;Lombok 라이브러리는 이 단계에서 보일러플레이트 코드를 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;출력&lt;/b&gt;: 수정된 AST 또는 추가 생성된 파일(예: 자동 생성된 클래스).&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;3. Semantic Analysis (의미 분석)&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;코드의 의미를 검증한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;세부내용&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;변수나 메서드가 선언된 위치와 호출 위치를 연결한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;타입 체크(Type Checking)&lt;/b&gt;: 변수와 메서드 호출이 올바른 타입으로 사용되는지 확인한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이 단계에서 발견된 오류는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컴파일 에러&lt;/b&gt;로 표시된다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;4. Intermediate Code Generation (중간 코드 생성)&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;AST를 바탕으로 JVM이 이해할 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;중간 표현(Intermediate Representation, IR)&lt;/b&gt;을 생성한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;세부내용&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이 단계는 소스 코드를 바이트코드로 변환하기 위한 중간 단계로, 구체적인 JVM 명령어로 변환되지 않았지만 구조가 더 단순화된 상태이다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;5. Optimization (최적화)&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;생성된 중간 코드를 최적화한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;세부내용&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;중복된 연산 제거.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;불필요한 코드 제거.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;더 효율적인 명령어로 대체.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이 단계는 JVM의 Just-In-Time(JIT) 컴파일러 최적화보다는 덜 강력하지만, 일부 성능 최적화를 수행한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;6. Bytecode Generation (바이트코드 생성)&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;최적화된 중간 코드를 JVM에서 실행될 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;바이트코드(.class 파일)&lt;/b&gt;로 변환한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;7. Class File Output (클래스 파일 출력)&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 생성된 .class 파일을 디스크에 저장한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Language/Java</category>
      <category>javac</category>
      <category>javac 동작과정</category>
      <category>jvm javac</category>
      <category>jvm lombok</category>
      <category>lombok</category>
      <category>lombok kotlin</category>
      <category>lombok 동작 원리</category>
      <category>lombok 작동원리</category>
      <category>springboot lombok</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/178</guid>
      <comments>https://pgmjun.tistory.com/178#entry178comment</comments>
      <pubDate>Thu, 21 Nov 2024 01:44:52 +0900</pubDate>
    </item>
    <item>
      <title>Test Fixture Factory 구현을 통한 테스트 코드 가독성, 생산성 개선</title>
      <link>https://pgmjun.tistory.com/177</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황 정의&lt;/h2&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;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;1178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbVG9S/btsKI3Y3yBG/l2kEv8rn2ALfYFTizpHvi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbVG9S/btsKI3Y3yBG/l2kEv8rn2ALfYFTizpHvi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbVG9S/btsKI3Y3yBG/l2kEv8rn2ALfYFTizpHvi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbVG9S%2FbtsKI3Y3yBG%2Fl2kEv8rn2ALfYFTizpHvi0%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;584&quot; height=&quot;438&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;1178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;우리 팀에서는 땅콩 개발 초기에, 테스트 코드를 작성할 때 위와 같은 &lt;b&gt;init-test.sql&lt;/b&gt; 파일을 사용했었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;초반에는 위와 같이 사용함으로 인해 테스트 코드 작성 시에 굉장히 길어질 수 있는 데이터 초기화 로직을 제거할 수 있었다.&lt;br&gt;미리 init-sql에 정의되어 있는 값만 가져다가 사용하면 되어서 편리하다고도 생각했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만 위 방식에서는 &lt;u&gt;문제점&lt;/u&gt;이 있었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buYTNx/btsKJCmw5r6/lJzyvqdIesrnNYbHc7jdJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buYTNx/btsKJCmw5r6/lJzyvqdIesrnNYbHc7jdJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buYTNx/btsKJCmw5r6/lJzyvqdIesrnNYbHc7jdJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuYTNx%2FbtsKJCmw5r6%2FlJzyvqdIesrnNYbHc7jdJK%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;737&quot; height=&quot;269&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;데이터가 얼마 없을 때는 편리할 줄 알았으나&lt;br&gt;테스트 코드에서 사용할 데이터를 참조할 때 매번 init-test.sql 파일에 접근해서 사용할 데이터의 ID값을 확인해야하는 것이 굉장히 불편했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이로 인해 테스트 코드 작성의&amp;nbsp;&lt;b&gt;생산성이 저하&lt;/b&gt;&amp;nbsp;되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;가독성 저하&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lz008/btsKJkGq3kq/YusK7GR5eLF7YDABNA2CU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lz008/btsKJkGq3kq/YusK7GR5eLF7YDABNA2CU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lz008/btsKJkGq3kq/YusK7GR5eLF7YDABNA2CU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLz008%2FbtsKJkGq3kq%2FYusK7GR5eLF7YDABNA2CU0%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;728&quot; height=&quot;425&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;생산성 저하로 인해서 잘 사용하지 않게 되자, 결국 테스트 코드는 점점 init-test.sql 파일을 사용하지 않았던 형태로 돌아가게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그러다보니 위와 같은 코드가 나타나게 되었는데, 매번 직접 테스트 코드에 필요한 데이터를 테스트 코드에 작성해주고 있으니&lt;br&gt;이번에는 &lt;b&gt;가독성&lt;/b&gt;에 이어&amp;nbsp;&lt;b&gt;생산성이&lt;/b&gt;&amp;nbsp;&lt;b&gt;함께&lt;/b&gt;&lt;b&gt;&amp;nbsp;저하&lt;/b&gt;&amp;nbsp;되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 코드를 위해 열려있는 생성자&lt;/h3&gt;&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static Room createNewRoom() {
        String uuid = UUID.randomUUID().toString().replace(&quot;-&quot;, &quot;&quot;);
        return new Room(uuid, START_ROUND, RoomStatus.READY, RoomSetting.createNewRoomSetting());
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;비즈니스 로직에서 Room 생성은 정해진&amp;nbsp;&lt;b&gt;비즈니스 규칙&lt;/b&gt;&amp;nbsp;에 따라 위와 같이 세팅된 특정 값으로 생성되어야 했다.&lt;br&gt;하지만 createNewRoom() 만 열어두게 되면&amp;nbsp;&lt;b&gt;테스트 코드에서 특정 상태를 가진 Room 객체 생성이 불가능한 문제&lt;/b&gt;가 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public Room(String uuid, int currentRound, RoomStatus status, RoomSetting roomSetting) {
        this.uuid = uuid;
        this.currentRound = currentRound;
        this.status = status;
        this.roomSetting = roomSetting;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;때문에 어쩔 수 없이 위와 같은 생성자를 하나 만들어서 다양한 형태의 Room 객체 생성을 가능하게 열어둔 상태였다.&lt;br&gt;이러한 문제점을 가지고 작성되어있는 테스트 코드가&amp;nbsp;&lt;b&gt;약 230개&lt;/b&gt;&amp;nbsp;정도 있었고, 테스트가 더 늘어나기 전에 이를 처리 해야겠다고 생각했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;문제 해결 방안 선정&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결을 위해서 Test Fixture Factory 객체를 만들어서 사용하는 방법을 선택하게 되었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Test Fixture란?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 픽스처(Test Fixture)는 테스트가 매번 일관성있게 작동할 수 있도록 환경을 준비하고 유지하는데 필요한&amp;nbsp;&lt;b&gt;코드, 데이터, 설정&lt;/b&gt;&amp;nbsp;등을 의미한다.&lt;br&gt;나는 이러한 테스트 픽스처를 세팅해주는 Fixture Factory 객체를 만들어 테스트가 일관성있게 작동할 수 있는 환경을 쉽고 간편하게 구성할 수 있도록 개선해볼 생각이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fixture Monkey&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://naver.github.io/fixture-monkey/v1-0-0/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #e6f5ff;&quot;&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;Fixture Monkey&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;는 제어 가능한 임의의 테스트 객체를 생성하도록 설계된 Java 및 Kotlin 라이브러리이다.&lt;br&gt;이 기술도 사용해볼 수 있을 것 같았지만, 굳이 직접 처리할 수 있는 작업을 외부 라이브러리에 의존하고 싶진 않았다. 정말 없어선 안되는 것이 아니라면 최대한 외부 라이브러리 의존은 지양하고 있다. (직접 만들어서 쓰고 말지)&lt;br&gt;또 세부적인 Fixture 객체 값 컨트롤을 위해 그냥 직접 Fixture 클래스를 구현해서 사용하고자 하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 코드 개선&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fixture 관련 코드&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Fixture를 생성하기 위한 코드로, 테스트를 위한 생성자 사용 문제 해결이 시급한&amp;nbsp;&lt;b&gt;Room&lt;/b&gt;&amp;nbsp;을 기준으로 작성해보겠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;RoomFixture.java&lt;/h4&gt;&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;@Component
public class RoomFixture {

    private final RoomRepository roomRepository;

    public RoomFixture(RoomRepository roomRepository) {
        this.roomRepository = roomRepository;
    }

    public Room createNotStartedRoom() {
        return roomRepository.save(Room.createNewRoom());
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;먼저 Room 픽스처를 생성하기 위한&amp;nbsp;&lt;b&gt;RoomFixture&lt;/b&gt;&amp;nbsp;클래스를 생성하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Room의 상태는&amp;nbsp;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;READY&lt;/span&gt;&amp;nbsp;,&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;PROGRESS&lt;/span&gt;&amp;nbsp;,&amp;nbsp;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;FINISH&lt;/span&gt;&amp;nbsp;가 존재한다.&lt;br&gt;그 중 READY 상태의 Room을 생성하기 위한 메서드 createNotStartedRoom() 을 먼저 만들어주었다.&lt;br&gt;이후에 필요에 따라 점점 픽스처 생성 메서드를 추가하면서 아래와 같이 변화되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;aspectj&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;@Component
public class RoomFixture {

    private static final String CURRENT_ROUND_FIELD = &quot;currentRound&quot;;
    private static final String STATUS_FIELD = &quot;status&quot;;
    private static final String ROOM_SETTING_FIELD = &quot;roomSetting&quot;;

    private final FixtureSettingManager fixtureSettingManager;
    private final RoomRepository roomRepository;

    public RoomFixture(FixtureSettingManager fixtureSettingManager, RoomRepository roomRepository) {
        this.fixtureSettingManager = fixtureSettingManager;
        this.roomRepository = roomRepository;
    }

    public Room createNotStartedRoom(int currentRound, int totalRound, int timeLimit, Category category,
                                     RoomStatus roomStatus) {
        RoomSetting roomSetting = new RoomSetting(totalRound, timeLimit, category);
        return createNotStartedRoom(currentRound, roomSetting, roomStatus);
    }

    public Room createNotStartedRoom(int currentRound, RoomSetting roomSetting, RoomStatus roomStatus) {
        Room room = Room.createNewRoom();
        setRoom(currentRound, roomSetting, roomStatus, room);
        return roomRepository.save(room);
    }

    public Room createNotStartedRoom() {
        return roomRepository.save(Room.createNewRoom());
    }

    public Room createProgressRoom() {
        int currentRound = 1;
        return createProgressRoom(currentRound);
    }

    public Room createProgressRoom(int currentRound) {
        int totalRound = 5;
        int timeLimit = 10_000;
        Category category = Category.IF;
        RoomStatus roomStatus = RoomStatus.PROGRESS;

        Room room = Room.createNewRoom();
        RoomSetting roomSetting = new RoomSetting(totalRound, timeLimit, category);
        setRoom(currentRound, roomSetting, roomStatus, room);

        return roomRepository.save(room);
    }

    public Room createProgressRoom(int currentRound, RoomSetting roomSetting) {
        RoomStatus roomStatus = RoomStatus.PROGRESS;

        Room room = Room.createNewRoom();
        setRoom(currentRound, roomSetting, roomStatus, room);

        return roomRepository.save(room);
    }

    public Room createFinishedRoom() {
        int currentRound = 5;
        int totalRound = 5;
        int timeLimit = 10_000;
        Category category = Category.IF;
        RoomStatus roomStatus = RoomStatus.FINISH;

        Room room = Room.createNewRoom();
        RoomSetting roomSetting = new RoomSetting(totalRound, timeLimit, category);
        setRoom(currentRound, roomSetting, roomStatus, room);

        return roomRepository.save(room);
    }

    private void setRoom(int currentRound, RoomSetting roomSetting, RoomStatus roomStatus, Room room) {
        fixtureSettingManager.setField(room, CURRENT_ROUND_FIELD, currentRound);
        fixtureSettingManager.setField(room, STATUS_FIELD, roomStatus);
        fixtureSettingManager.setField(room, ROOM_SETTING_FIELD, roomSetting);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;3가지 상태의 Room을 생성하기 위한 다양한 메서드를&amp;nbsp;&lt;b&gt;Overloading&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;을 활용하여 객체지향적으로 구현하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private void setRoom(int currentRound, RoomSetting roomSetting, RoomStatus roomStatus, Room room) {
        fixtureSettingManager.setField(room, CURRENT_ROUND_FIELD, currentRound);
        fixtureSettingManager.setField(room, STATUS_FIELD, roomStatus);
        fixtureSettingManager.setField(room, ROOM_SETTING_FIELD, roomSetting);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;또 눈여겨볼 점은 최하위에 있는 setRoom() 메서드이다.&lt;br&gt;Reflection을 사용하는&amp;nbsp;&lt;b&gt;FixtureSettingManager&lt;/b&gt;를 구현했고, 이를 통해 객체의 특정 필드에 특정 값을 채워넣을 수 있도록 구현하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;FixtureSettingManager.java&lt;/h3&gt;&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class FixtureSettingManager {

    public void setField(Object targetObject, String fieldName, Object newValue) {
        try {
            Field field = targetObject.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(targetObject, newValue);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(&quot;Failed to set field value&quot;, e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;FixtureSettingManager&lt;/b&gt;&amp;nbsp;클래스의 내부이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Reflection을 사용하는 경우 필드의 네이밍이 변경되는 경우 관련된 테스트 코드를 찾아 모두 변경해주어야 한다.&lt;br&gt;이때 setField() 메서드를 구현하고 이를 사용해서 필드 값을 초기화하도록 하면, 이후에 Reflection을 수행하고 있는 코드를 찾기 수월할 것이라고 생각했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;또 Reflection이 필요한 곳마다 생기는 코드 중복 제거되어&amp;nbsp;&lt;b&gt;유지보수성&lt;/b&gt;도 증가된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 결과&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lz008/btsKJkGq3kq/YusK7GR5eLF7YDABNA2CU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lz008/btsKJkGq3kq/YusK7GR5eLF7YDABNA2CU0/img.png&quot; data-alt=&quot;&amp;amp;lt;개선 전&amp;amp;gt;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lz008/btsKJkGq3kq/YusK7GR5eLF7YDABNA2CU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLz008%2FbtsKJkGq3kq%2FYusK7GR5eLF7YDABNA2CU0%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;728&quot; height=&quot;425&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;lt;개선 전&amp;gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/msaff/btsKJydtEh3/BD5vlP6ZKvLmOKhbXJX4u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/msaff/btsKJydtEh3/BD5vlP6ZKvLmOKhbXJX4u1/img.png&quot; data-alt=&quot;&amp;amp;lt;개선 후&amp;amp;gt;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/msaff/btsKJydtEh3/BD5vlP6ZKvLmOKhbXJX4u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmsaff%2FbtsKJydtEh3%2FBD5vlP6ZKvLmOKhbXJX4u1%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;1444&quot; height=&quot;588&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;lt;개선 후&amp;gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위와 같이 Test Fixture 클래스 구현을 통해 결론적으로 코드는 위와 같은 형태로 개선되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Fixture를 하나하나 생성하던 과정을 Fixture 객체에 위임함으로써 위와 같이&amp;nbsp;&lt;b&gt;가볍고 가독성 좋은 형태로 개선&lt;/b&gt;&amp;nbsp;할 수 있었다.&lt;br&gt;또한 메서드에 객체 생성의 세부적인 로직을 위임하게되니&amp;nbsp;&lt;b&gt;테스트 코드 생산성이 굉장히 향상&lt;/b&gt;됨을 느낄 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PR 링크&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/woowacourse-teams/2024-ddangkong/pull/400&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/woowacourse-teams/2024-ddangkong/pull/400&lt;/span&gt;&lt;/a&gt;&lt;br&gt;전체적인 개선 내용이 궁금하다면 PR 참조&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>Fixture</category>
      <category>fixture monkey</category>
      <category>springboot fixture</category>
      <category>springboot test fixture</category>
      <category>Test Fixture</category>
      <category>스프링부트 fixture</category>
      <category>테스트 코드 개선</category>
      <category>테스트 코드 데이터</category>
      <category>테스트 코드 데이터 관리</category>
      <category>테스트 코드 사전 데이터</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/177</guid>
      <comments>https://pgmjun.tistory.com/177#entry177comment</comments>
      <pubDate>Thu, 14 Nov 2024 22:24:43 +0900</pubDate>
    </item>
    <item>
      <title>Logback MDC(Mapped Diagnostic Context) 활용하여 Observability 개선하기</title>
      <link>https://pgmjun.tistory.com/176</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OhUjh/btsKwxzKT7C/H2y21QIiFwOQPD9YMyM5UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OhUjh/btsKwxzKT7C/H2y21QIiFwOQPD9YMyM5UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OhUjh/btsKwxzKT7C/H2y21QIiFwOQPD9YMyM5UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOhUjh%2FbtsKwxzKT7C%2FH2y21QIiFwOQPD9YMyM5UK%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;280&quot; height=&quot;280&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;280&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;h2 id=&quot;block-1251805d095880eeba19cf36c6d67279&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;고민의 시작점&lt;/h2&gt;
&lt;p id=&quot;block-1251805d095880f5a8daceee5133a464&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;다수의 사람들이 서비스를 이용하는 우아한테크코스의 데모데이 직전,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;모니터링이 수월하게 작동할 지 확인하는 도중 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;다른 크루원과 기존 로깅 방식에 대해 이야기를 나누는 과정에서&lt;span&gt; &lt;/span&gt;&lt;/span&gt;현재 로깅 방식에 아쉬움을 느끼게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;때문에 로깅 방식을 개선해보고자 몇 가지 고민을 했고, 그 과정과 결과를 기록해두고자 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-1251805d0958807b9f15e2fbddb5fa5b&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;현재 Logging의 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-1251805d0958805bb066c371c255b910&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;45&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oX8Ez/btsKw2sOMDx/Czw6K2lKtPkBIKHF0mBSH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oX8Ez/btsKw2sOMDx/Czw6K2lKtPkBIKHF0mBSH0/img.png&quot; data-alt=&quot;땅콩의 로그 형태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oX8Ez/btsKw2sOMDx/Czw6K2lKtPkBIKHF0mBSH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoX8Ez%2FbtsKw2sOMDx%2FCzw6K2lKtPkBIKHF0mBSH0%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;792.8906&quot; height=&quot;27.984375&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;45&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땅콩의 로그 형태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1251805d09588067b43efbaa1e7ac92b&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 우리 땅콩 서버의 로그는 다음과 같은 형태로 남겨지고 있다.&lt;/p&gt;
&lt;p id=&quot;block-1251805d095880a2b97aeec1483bb0e1&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Request, Response에 대한 정보를 남기고 있는 효율적인 로깅 방식으로 보여진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1251805d0958802e9cd3fc22dcc2101f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 여기에 하나의 문제점이 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1251805d09588000ba84ebd4ad93eaf3&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버에 동시에 요청이 마구잡이로 들어왔을 때, 특정 로그를 어떻게 추적하여 확인할 수 있을까?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 요청에서 예외가 발생했을 때, 빠른 속도로 어떤 요청에서 예외가 발생한 것인지 인지할 수 있을까?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;본인은 지금 상태로는 사실상 굉장히 어려운 문제라고 생각했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;때문에 이 문제를 해결하여 &lt;b&gt;Observability를 개선&lt;/b&gt;해보고자 한다.&lt;/p&gt;
&lt;div id=&quot;block-1251805d095880e7ba9af17720f64241&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-1261805d0958803bb633ee15094f43b6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Observability란?&lt;/h3&gt;
&lt;p id=&quot;block-1261805d09588089bfb4ea2b15625e35&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Observability&lt;/b&gt;는 소프트웨어 시스템의 상태를 외부에서 파악하고 이해할 수 있는 능력을 의미한다.&lt;/p&gt;
&lt;p id=&quot;block-1261805d095880ff805addb3e27f6ba8&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Observability는 아래 3가지 요소로 구분된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-1261805d0958804a9409ffc38e35ff69&quot;&gt;&lt;b&gt;로그(logging)&lt;/b&gt;: 시스템에서 발생하는 이벤트나 상태 변화를 기록한 정보. 각종 요청, 에러, 상태 변경 등과 같은 정보를 포함하며, 상세한 디버깅이나 시스템의 상태 파악에 도움을 준다.&lt;/li&gt;
&lt;li id=&quot;block-1261805d095880ffb1f3ed2ac7521615&quot;&gt;&lt;b&gt;메트릭(metrics)&lt;/b&gt;: 시스템의 성능과 관련된 수치 데이터이다. CPU 사용률, 메모리 소비량, 요청 처리 시간, 오류 발생 횟수 등의 데이터를 수집하여, 시스템 성능을 모니터링하고 병목 현상이나 성능 저하를 탐지할 수 있다.&lt;/li&gt;
&lt;li id=&quot;block-1261805d0958807c89b5f56d69b65e7f&quot;&gt;&lt;b&gt;트레이싱(tracing)&lt;/b&gt;: 개별 요청이나 트랜잭션이 시스템 내부에서 어떻게 처리되는지를 추적하는 방법이다. 여러 마이크로서비스나 애플리케이션 컴포넌트에서 요청이 어떤 경로를 통해 전달되는지 추적할 수 있어, 성능 문제나 오류가 발생한 지점을 파악하는 데 도움을 준다.&lt;/li&gt;
&lt;/ul&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-1251805d0958803b982de534c324d1e1&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;해결 방안&lt;/h2&gt;
&lt;p id=&quot;block-1251805d095880c58c3be8185dff016e&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해결방안으로 떠올린 방법은 &lt;b&gt;MDC&lt;/b&gt;를 사용하는 것이다.&lt;/p&gt;
&lt;p id=&quot;block-1251805d0958807b8a56ff877d57b573&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요청이 도착했을 때, 각 요청마다 &lt;b&gt;TraceID(UUID)&lt;/b&gt;를 생성하고, &lt;b&gt;MDC&lt;/b&gt;에 삽입한다.&lt;/p&gt;
&lt;p id=&quot;block-1251805d0958808f9718e5ef1e886bf0&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 Log를 찍을 때 해당 정보를 함께 표시해준다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1251805d095880bda392d33980b16059&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 요청별로 구분하는 것이 굉장히 쉽게 변할 것이라고 생각했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-1261805d095880c8a2a8e6fb4ac15b01&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MDC (Mapped Diagnostic Context) 란?&lt;/b&gt;&lt;/h3&gt;
&lt;p id=&quot;block-1261805d0958804ebae3cff53301ac14&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MDC&lt;/b&gt;는 주로 로그 관리에서 사용하는 개념으로, &lt;b&gt;스레드 로컬(Thread Local)&lt;/b&gt; 변수를 통해 각 스레드별로 고유한 컨텍스트 데이터를 저장하고, 이를 로그에 포함시키는 기능을 제공한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d0958809089e1d92e661ade15&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스프링 부트와 같은 환경에서 요청 단위로 고유한 &lt;b&gt;TraceID&lt;/b&gt;를 생성해 로그에 삽입할 때 자주 사용된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d09588050a9f5dca42d3ce5ad&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[MDC의 주요 기능]&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-1261805d09588091bc28e8a7c785b5e3&quot;&gt;&lt;b&gt;스레드 별로 독립된 데이터&lt;/b&gt;를 관리할 수 있다. 이는 특히 &lt;b&gt;멀티스레드&lt;/b&gt; 환경에서 유용하다.&lt;/li&gt;
&lt;li id=&quot;block-1261805d095880a3a1daffbef96bc508&quot;&gt;특정 요청이나 작업에 관련된 메타데이터를 로그에 자동으로 삽입할 수 있다.&lt;br /&gt;예를 들어 TraceID, 사용자 ID, 세션 ID 등과 같은 정보를 로그 메시지에 포함할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-12d1805d0958808c8a68e4f6e2ce0be3&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLcygx/btsKykTuPZH/iq1UuKWiuv76bDdYsr0P6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLcygx/btsKykTuPZH/iq1UuKWiuv76bDdYsr0P6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLcygx/btsKykTuPZH/iq1UuKWiuv76bDdYsr0P6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLcygx%2FbtsKykTuPZH%2Fiq1UuKWiuv76bDdYsr0P6k%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;2512&quot; height=&quot;454&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-1261805d095880e0aaf7d0e9d6beff87&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 위와 같은 예외가 발생했을 때, 요청이 굉장히 많이 발생하고 있다면 어디서 예외가 발생한 건 지 어떻게 알 수 있을까?&lt;/p&gt;
&lt;p id=&quot;block-12d1805d095880a7bf5dc47feecccc1d&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금은 DELETE 요청과 해당 요청에서 발생한 경고 로그가 붙어있지만, 굉장히 많은 요청이 들어오고 있는 와중에 저런 문제가 발생하면 오류의 원인을 로그에서 쉽게 찾아볼 수 있을까?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-12d1805d095880c0be9ae33228de8bc9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ThreadName으로 검색한다고 하더라도, Thread 는 ThreadPool에 한 번 등록된 Thread를 계속 재사용하기 때문에&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;검색한 ThreadName으로 처리된 굉장히 많은 작업 로그가 검색될 것이다.&lt;/p&gt;
&lt;p id=&quot;block-1261805d095880c1982fe170591b1a22&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내 생각엔 에러의 발생 원인 추적이 굉장히 복잡해질 것 같다고 생각한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-12d1805d095880ee92f2d130be5d70a6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0yXjj/btsKyX4wq0Z/bKd3cJXA2mF4ha6db7fytK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0yXjj/btsKyX4wq0Z/bKd3cJXA2mF4ha6db7fytK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0yXjj/btsKyX4wq0Z/bKd3cJXA2mF4ha6db7fytK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0yXjj%2FbtsKyX4wq0Z%2FbKd3cJXA2mF4ha6db7fytK%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;1022.6406&quot; height=&quot;165.202&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d09588093bcb7e444c3cc72e8&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 상황에서 요청단위로 관리되는 &lt;b&gt;TraceID(RequestID)&lt;/b&gt;를 사용하면, 위와 같이 문제 원인을 추척하기 수월해질 것이라고 생각했다.&lt;/p&gt;
&lt;p id=&quot;block-12d1805d0958800dbbeaf01f0af3d847&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 눈에 들어오지 않는가?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;block-1251805d09588098a60afb1c11d6c946&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;구현하기&lt;/h1&gt;
&lt;h3 id=&quot;block-1261805d09588062abe5c3aa7460636a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;요청이 들어올 때 Controller Aspect 에서 MDC 사용하여 traceID 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1730811763759&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
abstract class ControllerLoggingAspect {

    private static final String TRACE_ID_KEY = &quot;traceId&quot;;

    //..PointCut 설정 중략

    protected void logControllerRequest(JoinPoint joinPoint) {
        setTraceId(); // TraceID 생성
        HttpServletRequest request = getHttpServletRequest();
        String uri = request.getRequestURI();
        String httpMethod = request.getMethod();
        String queryParameters = getQueryParameters(request);
        String body = getBody(joinPoint);

        log.info(&quot;Request Logging: {} {} body - {} parameters - {}&quot;, httpMethod, uri, body, queryParameters);
    }

    protected void logControllerResponse(JoinPoint joinPoint, Object responseBody) {
        HttpServletRequest request = getHttpServletRequest();
        String uri = request.getRequestURI();
        String httpMethod = request.getMethod();

        log.info(&quot;Response Logging: {} {} Body: {}&quot;, httpMethod, uri, responseBody);
        removeTraceId(); // TraceID 삭제
    }

		private void setTraceId() {
        String traceId = UUID.randomUUID().toString();
        MDC.put(TRACE_ID_KEY, traceId);
    }

    private void removeTraceId() {
        MDC.remove(TRACE_ID_KEY);
    }
    //.. 타 메서드 중략
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;block-1261805d095880bea5e0dbf86bde601f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 setTraceId(), removeTraceId() 메서드를 바탕으로 &lt;b&gt;MDC&lt;/b&gt;에 &lt;b&gt;traceId&lt;/b&gt;를 생성 및 삭제 처리 해준다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-1261805d095880fc8831f19440632ea9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;LogBack.xml 사용해서 traceID 남기기&lt;/h3&gt;
&lt;pre id=&quot;code_1730811789374&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;appender name=&quot;DDANGKONG_LOG&quot; class=&quot;ch.qos.logback.core.rolling.RollingFileAppender&quot;&amp;gt;
        &amp;lt;file&amp;gt;${LOG_PATH}/ddangkong.log&amp;lt;/file&amp;gt;
        &amp;lt;rollingPolicy class=&quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&quot;&amp;gt;
            &amp;lt;fileNamePattern&amp;gt;${LOG_PATH}/ddangkong-%d{yyyy-MM-dd}.log&amp;lt;/fileNamePattern&amp;gt; &amp;lt;!-- 매일 새로운 파일 생성 --&amp;gt;
            &amp;lt;maxHistory&amp;gt;365&amp;lt;/maxHistory&amp;gt;
        &amp;lt;/rollingPolicy&amp;gt;
        &amp;lt;encoder&amp;gt;
            &amp;lt;pattern&amp;gt;%d{yyyy-MM-dd HH:mm:ss,Asia/Seoul} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n
            &amp;lt;/pattern&amp;gt;
        &amp;lt;/encoder&amp;gt;
&amp;lt;/appender&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d095880dfb01df82fc5b2ff94&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;logback-{env}.xml 파일에서 로그 출력 패턴을 정하는 &lt;b&gt;&amp;lt;pattern&amp;gt;&lt;/b&gt; 태그에&lt;/p&gt;
&lt;p id=&quot;block-1261805d0958804881bbeebe855a01bd&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[%X{traceId}]&lt;/b&gt; 설정을 추가하여 MDC에 기록한 traceId를 출력하도록 만든다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-1261805d095880b9ac70f972f9cff95d&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TV6Ra/btsKyVFEkme/BnzUmaT30q7lORCRH8xdpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TV6Ra/btsKyVFEkme/BnzUmaT30q7lORCRH8xdpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TV6Ra/btsKyVFEkme/BnzUmaT30q7lORCRH8xdpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTV6Ra%2FbtsKyVFEkme%2FBnzUmaT30q7lORCRH8xdpk%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;707.9922&quot; height=&quot;124.38964&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d0958800fb6bfd4abbbc84a20&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 위와 같이 로그에 &lt;b&gt;TraceID &lt;/b&gt;가 함께 기록되기 시작한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-1261805d095880b9923fed509ddda0bf&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;요청/응답이 아닌 경우 TraceID 설정&lt;/h3&gt;
&lt;div id=&quot;block-1261805d0958809aaa64db3d61670c24&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rxSFh/btsKxKrjcPH/tbm92BWX28Q90D5mYD4nE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rxSFh/btsKxKrjcPH/tbm92BWX28Q90D5mYD4nE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rxSFh/btsKxKrjcPH/tbm92BWX28Q90D5mYD4nE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrxSFh%2FbtsKxKrjcPH%2Ftbm92BWX28Q90D5mYD4nE1%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;707.9922&quot; height=&quot;124.38964&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d0958809faa03d8156c1aec24&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 위 로그에 문제가 있어보이지 않은가?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d0958804cb9f2d9b6682f55d1&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;바로 요청/응답이 아닌 경우에 &lt;b&gt;TraceID&lt;/b&gt;가 작성되지 않고 &lt;b&gt;[] &lt;/b&gt;로만 작성되는 문제인데, 이 문제도 해결해보자&lt;/p&gt;
&lt;p id=&quot;block-1261805d09588023a253ccb469258002&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 설정해주었던 &lt;b&gt;[%X{traceId}]&lt;/b&gt; 값 설정을 &lt;b&gt;[%X{traceId:-NoTraceID}] &lt;/b&gt;로 변경해주면 traceId가 없을 때 NoTraceID가 찍히게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-1261805d0958806eacc5cc3174fac4f3&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yj6ZM/btsKyon2hRZ/dsqSm9J0DIhL4nFlbfGgf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yj6ZM/btsKyon2hRZ/dsqSm9J0DIhL4nFlbfGgf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yj6ZM/btsKyon2hRZ/dsqSm9J0DIhL4nFlbfGgf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyj6ZM%2FbtsKyon2hRZ%2FdsqSm9J0DIhL4nFlbfGgf1%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;707.9844&quot; height=&quot;76.83551&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-1261805d0958803ab75febb3a3f7592a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 로그가 이렇게 위와 같이 표시된다.&lt;/p&gt;
&lt;p id=&quot;block-1261805d095880b68c69d1d5175f31a3&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;깔끔하지 않은가?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;block-1261805d095880619955e35cec7a302c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;결론&lt;/h1&gt;
&lt;p id=&quot;block-1261805d0958803f9683e904cda1f349&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다수의 사람들이 서비스를 이용하는 우아한테크코스의 데모데이 직전, 모니터링이 수월하게 작동할 지 확인하는 과정에서 현재 로깅 방식에 아쉬움을 느꼈다. 로그를 요청 별로 구분해 줄 무언가가 필요하다고 느꼈다.&lt;/p&gt;
&lt;p id=&quot;block-1261805d095880e9bb45de024adf3ecd&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해서 Thread Local을 사용하는 MDC를 사용하여 각 요청마다 TraceID를 갖도록 구성하고, 이를 로그 별로 함께 찍어줌으로써 로그 추적이 간편해지도록 구성할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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=Ifz0LsfAG94&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/pnExX/hyXs3ya8vX/ePpKMPj7yKMGoMVkjycW10/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=958_358_1042_450,https://scrap.kakaocdn.net/dn/wiGoq/hyXpB3ELbi/TUSrE1hhkDZSwRVbULR0Q1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=958_358_1042_450&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;토스ㅣSLASH 23 - 분산 추적 체계 &amp;amp; 로그 중심으로 Observability 확보하기&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Ifz0LsfAG94&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;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-12d1805d0958807aa090e85f77d1994d&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 Toss의 SLASH 23에서도 위와 같은 주제를 다루었던 것을 검색 과정에서 알게 되었다.&lt;/p&gt;
&lt;p id=&quot;block-12d1805d095880dca3c0ed27b21a41df&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기술적으로 훌륭한 실력을 가진 Toss에서도 고민했던 부분인 것을 확인하고 괜찮은 접근이라고 생각하였다.&lt;/p&gt;
&lt;p id=&quot;block-12d1805d095880228b40f509e14601ae&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자세히 알고 싶은 사람들은 위 영상을 시청하면 많은 도움이 될 것이라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Infra</category>
      <category>log trace 팁</category>
      <category>logback observability 개선</category>
      <category>logback 로그 트레이스 팁</category>
      <category>logback 잘 사용하기</category>
      <category>observability 개선</category>
      <category>springboot observability</category>
      <category>로그 observability</category>
      <category>로그 추적</category>
      <category>로그 트레이스 팁</category>
      <category>스프링부트 로그 추적 팁</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/176</guid>
      <comments>https://pgmjun.tistory.com/176#entry176comment</comments>
      <pubDate>Tue, 5 Nov 2024 22:05:57 +0900</pubDate>
    </item>
    <item>
      <title>2024 토스뱅크 Server Developer (Product) 최종 합격 회고</title>
      <link>https://pgmjun.tistory.com/175</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;최근 우아한테크코스와 취준을 병행하느라 글을 적지 못하고 있던 중에 좋은 소식이 찾아와서 오랜만에 글을 적어보려 한다.&lt;br&gt;평소 토스 문화를 살펴보면서 개발자로서 첫 커리어를 시작한다면 '토스'에서 시작하고 싶다고 생각해왔는데, 그게 실현되었다는 기쁜 소식이다.&lt;br&gt;그리고 그 과정을 회고로 남겨 종종 되돌아보고자 글을 적는다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;물론 이후에 지원하는 이들에게도 도움이 되었으면 하는 마음에 소소한 팁도 적어두었다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;1356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbdDjP/btsKylYV2j2/M6xYcSEceeFLjHsXLLaBT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbdDjP/btsKylYV2j2/M6xYcSEceeFLjHsXLLaBT0/img.png&quot; data-alt=&quot;2024 토스뱅크 테크 직군 대규모 채용 공고 홈페이지 (현재는 마감된 화면)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbdDjP/btsKylYV2j2/M6xYcSEceeFLjHsXLLaBT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbdDjP%2FbtsKylYV2j2%2FM6xYcSEceeFLjHsXLLaBT0%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;571&quot; height=&quot;354&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;1356&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2024 토스뱅크 테크 직군 대규모 채용 공고 홈페이지 (현재는 마감된 화면)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;올해 8월 &lt;b&gt;2024 토스뱅크 테크 직군 대규모 채용&lt;/b&gt;이 열렸다.&lt;br&gt;&amp;nbsp;&lt;br&gt;평소 개발자로서 생각해왔던 금융권 기업의 이미지는 '보수적인 곳', '개발 안 하는 곳', '커리어의 무덤' 이었다.&lt;br&gt;물론 &lt;span style=&quot;color: #333333;&quot;&gt;'정년은 보장되는 곳'이라는 타이틀도 있었지만 &lt;/span&gt;별로 가고싶다는 생각은 없었다.&lt;br&gt;사람들에게 필요한 가치를 찾고 개발을 통해 서비스로 실체화시키는 그 모든 과정이 너무나도 보람차고 즐거워서 개발을 하고 있는 나는&lt;br&gt;차라리 도전적인 업무를 수행해볼 수 있는 스타트업에 더 마음이 갔다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;그럼 어쩌다가 토스라는 회사에 눈이 가게 되었는가&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;토스라는 회사의 문화는 내 인생의 경험에 걸쳐보았을 때 너무나도 와닿는 문화가 많은 회사였기 때문에 꼭 가고싶은 회사였다.&lt;br&gt;토스는 내가 생각하던 금융권과는 전혀 다른 회사였다.&lt;br&gt;꼭 금융권이 아니고 모든 IT 회사와 비교해보아도 이런 문화를 만들어가고 있는 회사는 극히 드물었고, 인생 처음으로 가고싶은 회사가 토스가 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;본인은 고등학생 시절 학생회장 업무를 수행하며 하향식 업무 구조와 사람에 대한 스트레스를 받으며 업무라는 것에 대한 큰 회의감을 느껴본 적이 있다. 특히 공약을 통해 전하려던 가치를 대부분의 학생들은 원했지만 학교 측에서는 제대로된 이유도 알려주지 않고 지속적으로 기각을 시켜버린 경험 등 여기에 적어내기엔 정말 다양한 경험을 해왔다.&lt;br&gt;겁을 먹고 해보지 않았던 일들을 해보려고 나서지 않으면 결국 도태된다고 생각한다. 변화를 만들려면 실패하던 성공하던 계속 움직여야한다. 다만 학교는 그렇지 못했던 것 같다. 생각보다 보수적인 환경에 많이 실망했고 어느 순간부터 빨리 학교에서 졸업하고 싶었다. 더는 그곳에 있기 싫었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이러한 인생의 경험이 있다보니, 직급에 상관없이 전직원이 하나의 공동 목표를 추구하며 그 안에서 도전적인 시도에 대한 자유로운 의사결정권을 가지며 끝없는 시도를 통해 혁신을 이끌어갈 수 있는 토스라는 회사는 나에겐 너무나도 의미있는 기업이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;지인과의 대화를 통해 우연히 토스의 문화를 알게 되었을 땐 너무나 충격적이었는데, 토스라는 기업에 관심있는 사람은 꼭 확인해보길 바란다. [&lt;a href=&quot;https://toss.im/career/article/core-values-are-evolving&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;토스 핵심 가치 아티클&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;]&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;토스 계열사 중 토스뱅크에 가장 매력을 느낀 이유&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사실 토스의 문화 아래에 있는 모든 계열사는 어디든 훌륭하다고 생각하지만, 그중에 토스뱅크에 가장 매력을 느낀 이유는 경험 때문이었다고 생각한다.&lt;br&gt;토스뱅크의 &lt;b&gt;'키워봐요 적금'&lt;/b&gt;을 통해 즐거움과 기대감을 느끼며 돈을 모았던 경험이 있다.&lt;br&gt;'키워봐요 적금'은 어릴 적 즐겨하던 게임 '동물농장'을 벤치마킹해서 만든 것 같다고 생각이 들었는데, 적금을 계속 할 수록 동물이 성장하고 적금과 함께 최종 성장체까지 키워내는 그런 적금 상품이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;평소 돈 모으는 것에 재미를 느낀 적이 없었지만, 처음으로 돈 모으는 것이 재미있고 기대된다고 느꼈다.&lt;br&gt;때문에 나 또한 토스에 들어간다면, 여러 계열사 중 토스뱅크에 들어가서 사람들이 즐겁게 돈을 모을 수 있는 다양한 상품을 개발하며 사람들에게 '돈 모으는 즐거움' 이라는 가치를 제공하고 싶었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;채용 과정&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;[서류 전형] - [기술 면접] - [문화적합성 면접]&lt;/b&gt;&lt;br&gt;토스뱅크에 입사하기 위한 과정은 총 3가지 단계로 진행되었다.&amp;nbsp;&lt;br&gt;보안서약서를 작성했기 때문에 간단히 밖에 말 못하겠지만, 그래도 경험과 느낀점이라도 적어보려고 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;[서류 지원 과정]&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;서류는 이력서와 포트폴리오를 제출했다.&lt;br&gt;토스 개발직군은 보통 자기소개서나 정해진 이력서 형식이 없는 자율 양식 지원으로 알고 있는데 이 점이 너무 좋았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;굉장히 바쁜 우아한테크코스를 진행하면서 서류를 작성하기란 쉽지 않다.&lt;br&gt;그래도 평소에 작성해온 이력서와 포트폴리오가 있었기 때문에 지원까지는 얼마 걸리지 않았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;&quot;이력서에 쓸만한 경험이 없다면, 평소에 일을 똑바로 하고 있는 것인지 되돌아보라&quot;&lt;/b&gt; 라는 말이 있는데, 난 이 말을 꽤나 좋아했다.&lt;br&gt;내가 제대로 된 경험을 하고 있는지 판가름할 수 있는 기준을 잡아준 말이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;때문에 매번 새로운 경험을 할 때마다 이력서를 업데이트했다. 거의 1개월에 1번씩은 이력서를 고쳤던 것 같다.&lt;br&gt;먼저 현직자로 일하고 있는 지인들에게 연락을 돌려서 이력서의 경험과 가독성 검토를 부탁했고 지속적으로 개선시켜나갔다.&lt;br&gt;때문에 이번 채용에서 기회를 잡을 수 있었던 것 같다. (도와주신 분들 너무 감사합니다)&lt;br&gt;&amp;nbsp;&lt;br&gt;만약 평소에 &quot;이력서 만들어야하는데,,&quot; 라고 생각만 하면서 시도를 미루고 있다면 지금이라도 이 글을 닫고 이력서부터 만들어보길 바란다.&lt;br&gt;운과 기회는 언제 찾아올 지 모른다. 그리고 항상 준비가 되어있는 자에게 찾아간다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;[기술 면접 과정]&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ErFuK/btsKw0hfpKA/ZcA3jzLPoViZsyt7rrT371/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ErFuK/btsKw0hfpKA/ZcA3jzLPoViZsyt7rrT371/img.png&quot; data-alt=&quot;서류 전형 합격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ErFuK/btsKw0hfpKA/ZcA3jzLPoViZsyt7rrT371/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FErFuK%2FbtsKw0hfpKA%2FZcA3jzLPoViZsyt7rrT371%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;692&quot; height=&quot;353&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서류 전형 합격&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;평소의 경험을 잘 녹여낸 덕분인지 서류 전형에서 좋은 결과를 얻게 되었다.&lt;br&gt;그렇게 기술 면접 준비 과정으로 들어가게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;자세한 건 기록할 수 없지만, 토스의 기술 면접은 의미없는 CS 같은 것들에 대해 물어보지 않고, 해왔던 일들에 대해서 넓고 깊게 학습해야 한다고 정도 말해주고 싶다.&lt;br&gt;의미없는 CS라고 함은 &quot;객체지향의 의미를 설명해주세요&quot;, &quot;OSI 7계층에 대해서 설명해주세요&quot; 와 같이 내가 제출한 이력과는 어떠한 연관성도 없이 CS에 대한 내용들로만 구성된 질문이라고 할 수 있을 것 같다.&lt;br&gt;토스는 해왔던 것 바탕으로 깊게 파고드는 느낌이 컸고, 그 과정에서 관련이 있는 CS 질문만 나오는 것 같았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;평소에 항상 고민하고 생각하면서 개발해 온 사람이라면 이 면접의 준비 과정부터 면접 당일까지 너무 즐겁게 생각하고 고민하면서 지낼 수 있을 것이다.&lt;br&gt;다만 본인은 면접일이 서류전형 합격 발표날로부터 거의 한 달 뒤로 잡혀서 이 과정이 너무 길고 힘들게 느껴질 때도 있었다.&lt;br&gt;하지만 학습하고 생각했던 것을 되돌아보며 더욱 크게 성장함을 느낄 수 있었던 것이 더 컸다.&lt;br&gt;&amp;nbsp;&lt;br&gt;나는 면접 일주일 쯤 전부터 긴장도 됐지만 사실 기대감이 더 컸다.&lt;br&gt;토스의 기술 면접에 대한 내용들을 찾아보면서 훨씬 개발 선배이신 면접관분들과 대화를 주고받으며&lt;br&gt;내가 생각하지 못했던 부분에 대해 인사이트를 얻고 함께 고민해보는 즐거운 시간을 만들 수 있을 거라고 확신했다.&lt;br&gt;실제로도 그랬고, 면접이 끝난 뒤에는 너무 즐겁게 대화를 주고받았다는 느낌이 강하게 들었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;기술 면접 팁&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 모의면접&lt;/b&gt;&lt;br&gt;아무리 기술적 고민을 많이 하는 사람이더라도, 그걸 생각만 하는 것과 말로 표현하는 것은 천지차이다.&lt;br&gt;때문에 나는 모의면접을 수차례 진행했다.&lt;br&gt;함께 지원한 우테코 사람들과도 진행하고, 근처에 있는 현직자분들과도 여러 번 모의면접을 진행했다.&lt;br&gt;덕분에 내가 말하고자 하는 것을 설명할 수 있는 능력이 많이 자리 잡혀서, 면접 당일날 내가 표현하고 싶은 것을 전부 말로 표현하고 왔다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;2. 기록&lt;/b&gt;&lt;br&gt;항상 고민하고 개발했던 내용들을 기록해놨기 때문에 과거에 기술적 선택을 했을 때 어떤 근거를 바탕으로 선택했는지 빠르게 복기할 수 있었다. 인간의 기억력은 유한하기 때문에 항상 기록하는 습관을 들여놓으면 좋은 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;[문화적합성 면접]&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MCNru/btsKykeJm1O/NoRJKtQJg49kYQg8KNfYCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MCNru/btsKykeJm1O/NoRJKtQJg49kYQg8KNfYCk/img.png&quot; data-alt=&quot;기술 면접 합격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MCNru/btsKykeJm1O/NoRJKtQJg49kYQg8KNfYCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMCNru%2FbtsKykeJm1O%2FNoRJKtQJg49kYQg8KNfYCk%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;663&quot; height=&quot;317&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기술 면접 합격&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;문화적합성 면접은 토스의 문화와 지원자의 문화적 지향점이 매칭되는지 검증하는 시간이라고 생각한다.&lt;br&gt;평소 토스에서 만들어가는 문화에 대해 큰 가치를 느끼고 있었기 때문에 면접 과정에서 즐거운 시간을 보낼 수 있던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;문화적합성 면접은 기술 면접 합격 발표 이후 7일 정도의 텀이 주어졌다.&lt;br&gt;처음엔 너무 길다고 생각했는데, 준비하다보니 딱 적합한 시간이었다고 느꼈다.&lt;br&gt;&amp;nbsp;&lt;br&gt;평소에 기술에 대해서는 생각하지만, 자기 자신의 사고에 대해서 돌아보는 사람은 거의 없을 것이라고 생각한다.&lt;br&gt;나 또한 매일 회고를 통해 내 자신을 돌아보게 된 것이 오래되지 않았기 때문에 오히려 기술 면접보다 더 생각해볼 내용이 많았던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;개인적으로 면접 자체는 기술 면접때보다 더 즐거웠다고 느꼈는데, 기술 면접은 생각보다 조금 더 무거운 분위기였던 반면&lt;br&gt;문화적합성 면접은 면접관님과 웃으면서 너무 즐겁게 대화하고 왔기 때문이다.&lt;br&gt;개발자로서 내가 어떤 삶을 살아왔고, 어떤 문화를 추구하며, 협업에 있어서 어떤 성향을 가졌는 지 등을 주로 나눴던 것 같다.&lt;br&gt;기술적인 얘기도 하긴 했지만, 주는 아니었다. (근데 진짜 이것도 너무 즐거웠다...)&lt;br&gt;&amp;nbsp;&lt;br&gt;면접을 보고 나오는 길에는 그냥 행복했다. 너무 즐겁고 소중한 시간이 되었다고 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;문화적합성 면접 팁&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;준비를 어떻게 했는가에 대해서 말해보고 싶은데, 크게 말할 내용이 없는 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;1. 멍 때리면서 개발인생 돌아보기&lt;/b&gt;&lt;br&gt;그냥 매일 4~5시간 정도 멍때린 것 같다.&lt;br&gt;아무 생각없이 멍을 때린 건 아니고 그냥 인생을 계속 돌아봤다.&lt;br&gt;생각 깊은 곳까지 들어왔다 나가기를 계속 반복했다.&amp;nbsp;&lt;br&gt;&quot;난 어떤 삶을 살았지?&quot;, &quot;내가 개발자로서 이루고 싶은 목표는 뭐지&quot;, &quot;난 언제부터 컴퓨터를 좋아했지?&quot;, &quot;개발은 왜 시작했지?&quot; 등 인생을 돌아볼 수 있는 질문들을 스스로에게 끊임없이 던졌다.&lt;br&gt;&amp;nbsp;&lt;br&gt;기억을 돌아볼수록 정말 깊은 사고까지 들어갈 수 있었고 바쁘게 살아가며 잊고 지내던 일들도 많이 떠올랐다.&lt;br&gt;즐거운 기억, 아쉬운 기억, 슬픈 기억 모두 떠오르면서 신기하고 즐거운 시간을 보낼 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;2. 러버덕 디버깅&lt;/b&gt;&lt;br&gt;그리고 이 또한 모의면접을 해보니 좋았다. 사실 각잡고 하는 건 아닌 것 같고, 러버덕 디버깅이었던 것 같다.&lt;br&gt;대화할 사람을 앉혀두고 서로 자신의 개발자로서의 인생에 대해서 설명하는 시간을 갖는 걸 추천한다.&lt;br&gt;위에서 말했듯이 말로 설명하는 것과 생각하는 것은 큰 차이가 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 합격&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyuo8j/btsKwC8Hqyl/9U3tNQgoLYA0Wq5e7Qh40K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyuo8j/btsKwC8Hqyl/9U3tNQgoLYA0Wq5e7Qh40K/img.png&quot; data-alt=&quot;토스뱅크 최종 합격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyuo8j/btsKwC8Hqyl/9U3tNQgoLYA0Wq5e7Qh40K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdyuo8j%2FbtsKwC8Hqyl%2F9U3tNQgoLYA0Wq5e7Qh40K%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;613&quot; height=&quot;435&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;토스뱅크 최종 합격&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;정신없이 달려온 이 채용과정의 끝은&lt;b&gt; '합격'&lt;/b&gt; 이었다. 많이 가고싶었던 회사인 만큼 감격이 굉장히 컸다.&lt;br&gt;채용 과정이 길어서 지치는 순간들도 있었으나, 오히려 길게 주어진 시간이 기회라고 여기며 최선을 다했다.&lt;br&gt;채용 과정에 깊게 몰입했고 매 순간을 감사하고 즐기기 위해 노력했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;올해 초 우아한테크코스와 한 스타트업에 동시 합격했을 때, 성장하는 방법을 깨달은 이후에 현업으로 나가고자 우아한테크코스를 선택했었다.&lt;br&gt;그렇게 선택한 우아한테크코스 안에서 너무나 좋은 사람들을 많이 만났다.&lt;br&gt;배울 점이 다양한 여러 크루원들과 1년을 보내며 그들에게 정말 많은 것을 느끼고 배웠으며&lt;br&gt;더욱 오랜시간 개발자로서의 길을 걸어온 여러 코치님들과 대화를 주고 받으며 개발자로서 어떻게 성장해나갈 수 있을 지 생각해나갈 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;너무나 얻은 것이 많은 1년이었고, 그 경험과 과정이 있었기에 지금의 좋은 결과를 만들어낸 것이라고 생각한다.&lt;br&gt;앞으로 토스뱅크에서 걸어갈 길 위에서도 많은 것들을 바라고 느끼며 살아가길 소망한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;지금까지의 인생을 돌아보며&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 감격스런 순간이 있기까지 어떤 인생을 걸어왔는지 돌아보고자 한다.&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;되돌아보면 20대 초반에는 굉장히 막막했던 것 같다.&lt;br&gt;지원했던 모든 4년제 대학에 떨어지고 전문대로 가게 되었을 때, 전문대라는 특성에 의해서 학교에서 배우는 내용들이 인서울 4년제 대학과는 많이 다름을 알게 되었을 때 참 막막했던 것 같다.&lt;br&gt;또 백엔드로 방향을 잡기 전 안드로이드, iOS, 프론트엔드 등 여러 분야를 먼저 접해보면서 재미를 전혀 느끼지 못했었고 이때 개발을 접으려고 했던 기억도 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만 매번 포기하지 않았고 매번 나만의 방향을 찾기 위해 발버둥쳤고 항상 고민했다.&lt;br&gt;항상 포기하지 않고 노력한 그 끝에는 결국 의미있는 결과가 돌아올거라 믿었고 실제로도 그랬다.&lt;br&gt;이러한 경험을 통해 자연스럽게 &lt;b&gt;'항상 당장의 상황에 좌절하지 않고 계속 나아가야함'&lt;/b&gt; 을 느꼈던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 이 과정의 모든 순간에는 사람들이 있었다.&lt;br&gt;항상 어렵고 힘든 순간에 주위에서 힘이 되어주는 사람들이 있었다.&lt;br&gt;특히 개발을 접으려고 했을 때 백엔드로의 전향에 가장 많은 도움을 주었던 친형.&lt;br&gt;그리고 항상 즐거움과 도움이 되어준 SOPT 사람들, 우테코 사람들에게 너무 감사하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;때문에 입사 전까지는 도움을 받은 주변 사람들의 이력서나 면접을 소소하게 도우면서 개인적으로 미뤄뒀던 학습을 하며 여유있게 지내려고 한다.&lt;br&gt;또 입사 이후에는 일에 몰입할 계획이라 휴식 계획은 생각해두지 않아서, 지금 시점에 여행도 많이 가려고 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이 글을 얼마나 볼 지는 모르겠으나, 항상 현재 상황에 좌절하지 말고 포기하지 말기를 바란다.&lt;br&gt;실패를 하더라도 그 안에서 배우는 점들만 있다면 그 실패의 경험들이 언젠가 성공으로 이끌어줄 거라 전하며 글을 마친다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고</category>
      <category>우아한테크코스 취업</category>
      <category>취업 후기</category>
      <category>토스뱅크 백엔드</category>
      <category>토스뱅크 백엔드 합격</category>
      <category>토스뱅크 백엔드 합격 후기</category>
      <category>토스뱅크 테크직군 대규모 채용</category>
      <category>토스뱅크 프로덕트 백엔드</category>
      <category>토스뱅크 프로덕트 백엔드 합격 후기</category>
      <category>토스뱅크 합격</category>
      <category>토스뱅크 합격 후기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/175</guid>
      <comments>https://pgmjun.tistory.com/175#entry175comment</comments>
      <pubDate>Tue, 5 Nov 2024 19:39:51 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 레벨3 돌아보기 (2024/07/02 ~ 2024/08/23)</title>
      <link>https://pgmjun.tistory.com/174</link>
      <description>&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;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs6en2/btsK2xS9mX2/i9Rb7emHN397yxMYZQeZkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs6en2/btsK2xS9mX2/i9Rb7emHN397yxMYZQeZkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs6en2/btsK2xS9mX2/i9Rb7emHN397yxMYZQeZkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs6en2%2FbtsK2xS9mX2%2Fi9Rb7emHN397yxMYZQeZkk%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;1280&quot; height=&quot;640&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;어느덧 우아한테크코스의 레벨 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트와 함께 시작된 협업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크코스 레벨 3 시작과 함께 함께 레벨 1,2를 거치며 학습한 내용을 바탕으로 프로젝트를 시작하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨 1을 통해 단련한 자바와 레벨 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;h3 data-ke-size=&quot;size23&quot;&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;백엔드 팀원은 3명 중 2명이 레벨 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;프로젝트 기획&amp;nbsp;&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;나는 정말로 사용되는 서비스를 개발하고 싶단 생각에 여태까지의 프로젝트 경험을 바탕으로 누구보다 열심히 기획에 임하려 노력했다.&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;u&gt;운영과 출시까지에 필요한 준비기간과 리소스가 너무 크고 길어서&lt;/u&gt; 출시에 실패하였으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이전에 경험한 졸업작품 프로젝트 '코리', 걱정기록 서비스 '흐릿'은 우리 팀에서 필요하다고 생각했지만 사실은 &lt;u&gt;유저가 필요로 하지 않았던 것&lt;/u&gt;이라 실패했다. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;유저가 원하고 필요로 하는 것&lt;/span&gt;을 만들어내야 한다고 생각했다.&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;1346&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/63w2H/btsK2k7Bg9M/o89jahoitaKJ05rS8EXMv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/63w2H/btsK2k7Bg9M/o89jahoitaKJ05rS8EXMv0/img.png&quot; data-alt=&quot;다수의 기획 회의 흔적..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/63w2H/btsK2k7Bg9M/o89jahoitaKJ05rS8EXMv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F63w2H%2FbtsK2k7Bg9M%2Fo89jahoitaKJ05rS8EXMv0%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;506&quot; height=&quot;315&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다수의 기획 회의 흔적..&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'땅콩'은 심심풀이 땅콩의 약자로 심심풀이로 사용할 수 있는 아이스브레이킹 &amp;amp; 친목 서비스이다.&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;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/woowacourse-teams/2024-ddangkong&quot;&gt;프로젝트 Github&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;h3 data-ke-size=&quot;size23&quot;&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;&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;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;1984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctGu8n/btsK4rKKYfV/WUe1g7LpCdhYZLxGLnPf2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctGu8n/btsK4rKKYfV/WUe1g7LpCdhYZLxGLnPf2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctGu8n/btsK4rKKYfV/WUe1g7LpCdhYZLxGLnPf2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctGu8n%2FbtsK4rKKYfV%2FWUe1g7LpCdhYZLxGLnPf2k%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;405&quot; height=&quot;514&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;1984&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;매번 데모데이가 끝날 때마다 팀문화에 대한 진중한 회의를 거쳐가며 튼튼하고 세련된 팀문화를 만들어가기 위해 최선을 다했다고 자부한다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;회고 문화&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2248&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/miJhd/btsK4u8Ah5V/uoMlqtKNUbqiWPmyxjm6oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/miJhd/btsK4u8Ah5V/uoMlqtKNUbqiWPmyxjm6oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/miJhd/btsK4u8Ah5V/uoMlqtKNUbqiWPmyxjm6oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmiJhd%2FbtsK4u8Ah5V%2FuoMlqtKNUbqiWPmyxjm6oK%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;2248&quot; height=&quot;844&quot; data-origin-width=&quot;2248&quot; data-origin-height=&quot;844&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;내가 개인적으로 열심히 주장해서 만들어낸 문화였는데, 'KPT 방식을 통한 팀문화 회고'와 '좋아요/서운해요 회고를 통한 익명 팀원 회고'가 있었다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;땅콩팀 코드 컨벤션 (일부)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMzoUp/btsK2feuG0D/WwQ8YLQfJ51Otd908WnCB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMzoUp/btsK2feuG0D/WwQ8YLQfJ51Otd908WnCB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMzoUp/btsK2feuG0D/WwQ8YLQfJ51Otd908WnCB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMzoUp%2FbtsK2feuG0D%2FWwQ8YLQfJ51Otd908WnCB0%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;339&quot; height=&quot;386&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1526&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;blockquote data-ke-style=&quot;style2&quot;&gt;조금 더 세부적인 기록을 보고싶다면 &lt;a href=&quot;https://pgmjun.tistory.com/170&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;&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;Github Rebase로 인한 깃 대폭발 사건&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rebase에 대해 자세히 모르고 사용해서 발생했던 깃 대폭발 사건이 있었다.&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;Rebase Merge를 하게 되면 동일한 커밋 내용이어도 Merge 되는 브랜치의 '커밋 해시'와 Merge 시킨 브랜치의 &lt;u&gt;'커밋 해시'가 상이&lt;/u&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;b&gt;Rebase Merge&lt;/b&gt;로 Merge된 Develop 브랜치에서 현재 작업이 진행 중인 Feature 브랜치로 Rebase를 수행했을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이미 Develop에 반영이 되어있는 커밋&lt;/u&gt;과 &lt;u&gt;Feature에서 Rebase 받은 커밋&lt;/u&gt;은 동일한 커밋이지만 &lt;i&gt;&lt;u&gt;&lt;b&gt;서로 다른 커밋 해시&lt;/b&gt;&lt;/u&gt;&lt;/i&gt;를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 이러한 작업 이후에 Feature 브랜치에서 Develop으로 PR을 보내게 되면 커밋 해시 관련 오류가 발생한다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mdrRi/btsK33p1hOZ/4AncyZgZ0Xt1fvlD8BB2L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mdrRi/btsK33p1hOZ/4AncyZgZ0Xt1fvlD8BB2L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mdrRi/btsK33p1hOZ/4AncyZgZ0Xt1fvlD8BB2L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmdrRi%2FbtsK33p1hOZ%2F4AncyZgZ0Xt1fvlD8BB2L0%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;428&quot; height=&quot;302&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;664&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;예를 들어 A -&amp;gt; B 라는 커밋이 이미 Develop 브랜치에 반영되어, 현재 상태가 'A'에서 'B'가 된 Dev 브랜치가 있다고 가정하자.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xt1DK/btsK3m4rMWW/CjKQoii8MvYHoPSLzXc46K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xt1DK/btsK3m4rMWW/CjKQoii8MvYHoPSLzXc46K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xt1DK/btsK3m4rMWW/CjKQoii8MvYHoPSLzXc46K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxt1DK%2FbtsK3m4rMWW%2FCjKQoii8MvYHoPSLzXc46K%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;441&quot; height=&quot;309&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 A -&amp;gt; B 라는 내용을 가진 다른 커밋 해시의 커밋이 Dev 브랜치에 또 다시 올라가는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 상태가 'B'인 코드에게 A -&amp;gt; B 요청을 보내면 당연히 상태는 'A'가 아니라 'B'이니 오류가 발생할 수 밖에 없다.&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;결국 어찌저찌 해결하고 Rebase에 대차게 당한 우리 팀은 &lt;b&gt;Merge and Commit&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;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API Spec 변경 소통 미스로 인한 프론트엔드의 무한 디버깅 사건&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 팀은 개발 환경에 배포된 내용에 대해서는 RestDocs를 활용해 테스트를 거쳐 문서화를 진행하기로 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 문서가 변경될 때마다 프론트엔드가 알 방법이 없었고, 백엔드 팀에서 직접 프론트 팀원들에게 구두로 전달해주지 않으면 이를 인지할 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 프론트엔드에서는 &quot;이거 맞는데 왜 안되지 내 잘못인가ㅠㅠ&quot; 해버리는 무한 디버깅 사태가 발생하는 문제가 이따금 있었다.&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-filename=&quot;스크린샷 2024-12-02 01.46.52.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;1438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ldjRy/btsK210v0ok/ksBipACu9e528Eea4QKKQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ldjRy/btsK210v0ok/ksBipACu9e528Eea4QKKQK/img.png&quot; data-alt=&quot;Discord Document Notificator&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ldjRy/btsK210v0ok/ksBipACu9e528Eea4QKKQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FldjRy%2FbtsK210v0ok%2FksBipACu9e528Eea4QKKQK%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;308&quot; height=&quot;467&quot; data-filename=&quot;스크린샷 2024-12-02 01.46.52.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;1438&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Discord Document Notificator&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제 해결을 위해서 Github Actions와 땅콩 팀의 협업툴 Discord를 활용하여 API 문서 변경 알림 시스템 구축을 수행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 사항을 빠르게 파악할 수 있도록 PR링크와 Docs 링크를 첨부하여 생산성 향상을 위해 노력했다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Polling 과정에서 발생한 캐싱 관련 문제&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCgJXN/btsK180IZ05/Qgenm2OP71FgQMXAtThqRK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCgJXN/btsK180IZ05/Qgenm2OP71FgQMXAtThqRK/img.jpg&quot; data-alt=&quot;버스정류장 디버깅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCgJXN/btsK180IZ05/Qgenm2OP71FgQMXAtThqRK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCgJXN%2FbtsK180IZ05%2FQgenm2OP71FgQMXAtThqRK%2Fimg.jpg&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;369&quot; height=&quot;492&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버스정류장 디버깅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드의 기술 스택인 React에서 Polling을 통해 흭득한 데이터를 어떠한 저장소에 캐싱해버려서 문제가 된 적도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가볍게 설명하자면 API 요청에 대한 응답값과 React에서 어딘가에 캐싱해둔 값이 꼬여, 프론트에서 이상한 값을 가지고 비즈니스 로직을 수행하는 오류였다.&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;이 문제 때문에 게임 진행이 정상적으로 동작하지 않았고 다같이 우테코 문 닫는 시간인 11시까지 집에 가지 못했다..&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;땅콩 서비스를 QA하는 과정에서 방의 최대 인원 제한인 12명 중 11명이 방에 접속해 있는 경우, 동시에 입장 요청이 발생한다면 방의 최대 인원은 12명임에도 불구하고 13명 이상의 유저가 접속되는 동시성 문제를 발견하였다.&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;문제 해결을 위해서 synchronized, 낙관적 락 방식 등 다양한 동시성 처리 방법을 비교하며 문제해결을 시도하였고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 &lt;b&gt;'&lt;/b&gt;&lt;b&gt;비관적 락(X-Lock)'&lt;/b&gt; 을 통해 문제를 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Transactional
public RoomJoinResponse joinRoom(String nickname, String uuid) {
	Room room = roomService.getRoomWithLock(uuid);
	Member member = memberService.saveCommonMember(nickname, room);
	return new RoomJoinResponse(room.getId(), room.getUuid(), new MemberResponse(member));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방 입장 로직은 가장 먼저 요청을 통해 전달받은 roomId 를 통해 Room을 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 roomId를 가진 Room 테이블의 레코드에 비관적 락(X-Lock)을 걸어 트랜잭션이 종료될 때까지 다른 트랜잭션에서 해당 roomId를 가진 레코드에 접근을 막았다.&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 class=&quot;livescript&quot;&gt;&lt;code&gt;@Test
void 최대_인원보다_많은_인원이_방에_접속할_수_없다() {
	// given
	Room room = roomRepository.save(Room.createNewRoom());
	memberRepository.save(PRIN.master(room));
	for (int i = 0; i &amp;lt; 10; i++) {
		memberRepository.save(EDEN.common(room, i));
	}

	// when
	Thread t1 = new Thread(() -&amp;gt; roomFacade.joinRoom(&quot;t1member&quot;, room.getUuid()));
	Thread t2 = new Thread(() -&amp;gt; roomFacade.joinRoom(&quot;t2member&quot;, room.getUuid()));
	t1.start();
	t2.start();

	try {
		t1.join();
		t2.join();
	} catch (InterruptedException ignored) {
	}

	Room foundRoom = roomRepository.findById(room.getId()).orElseThrow();
	long memberCountInRoom = memberRepository.countByRoom(foundRoom);

	// then
	assertThat(memberCountInRoom).isEqualTo(12);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 해결을 정확히 검증하기 위해 Thread 활용한 동시성 테스트를 작성하여 문제가 확실히 해결되었는지 검증하기도 했던 재미난 경험이었다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;팀원들과 스터디&lt;/h2&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;/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;이 레벨 인터뷰가 메타인지에 큰 도움이 될 것이라는 생각에 꼭 경험하고 싶었지만, 이번 기수는 레벨 1,2에 레벨 인터뷰를 진행하지 않았기 때문에 개인적으로 아쉬웠다.&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;Java, Spring에 대해서 깊이있게 공부하고 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;(실제로 이 스터디 덕분에 면접 분위기를 즐길 줄 알게 되었으며 많은 메타인지를 기를 수 있어 너무나 유익한 스터디였다. 강추!)&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/woowacourse-study-2024/level-interview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github Discussion&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;h3 data-ke-size=&quot;size23&quot;&gt;Real MySQL 8.0 스터디&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 팀원들과는 우리가 선택한 MySQL DB를 더욱 이해도있게 다루기 위해서 &lt;b&gt;Real MySQL 8.0 탐구 스터디&lt;/b&gt;를 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄여서 '리마탐(리얼마이에스큐엘 탐구)' 라고 불렀는데 이 스터디를 통해 MySQL의 동작원리를 이해하고 프로젝트에 효율적으로 녹여낼 수 있는 방법을 배우는 걸 목표로 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 이 책을 통해 얻은 흰트로 User 설정, Index 설정, Lock에 대한 지식 습득 후 동시성 문제 처리 등 프로젝트에서 많은 도움을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용과 진행 방식이 궁금한 사람은 이 &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/2024-woowacourse-study/re-ma-tam&quot;&gt;Github Repository&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레벨 인터뷰&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2434&quot; data-origin-height=&quot;1442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EQLo4/btsK2WyqaRc/fRGQxY5WFoLVlmA8lP2Lw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EQLo4/btsK2WyqaRc/fRGQxY5WFoLVlmA8lP2Lw1/img.png&quot; data-alt=&quot;레벨인터뷰&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EQLo4/btsK2WyqaRc/fRGQxY5WFoLVlmA8lP2Lw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEQLo4%2FbtsK2WyqaRc%2FfRGQxY5WFoLVlmA8lP2Lw1%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;593&quot; height=&quot;351&quot; data-origin-width=&quot;2434&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;레벨인터뷰&lt;/figcaption&gt;
&lt;/figure&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 프로젝트에서 진행한 내용들을 PDF로 정리하여 배정된 팀원들에게 전달하고, 서로 인터뷰이/인터뷰어가 된 팀원들은 서로에 대한 질문과 응답을 준비하여 인터뷰를 진행할 수 있었다.&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;h3 data-ke-size=&quot;size23&quot;&gt;내가 받은 피드백&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmfacL/btsK4yC5yvh/Kij2v1fpxkr3Y8PLW77YH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmfacL/btsK4yC5yvh/Kij2v1fpxkr3Y8PLW77YH0/img.png&quot; data-alt=&quot;레벨인터뷰 피드백&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmfacL/btsK4yC5yvh/Kij2v1fpxkr3Y8PLW77YH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmfacL%2FbtsK4yC5yvh%2FKij2v1fpxkr3Y8PLW77YH0%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;1662&quot; height=&quot;379&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;레벨인터뷰 피드백&lt;/figcaption&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;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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;처음으로 사용자를 만나다&lt;/h2&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코는 데모데이라는 것이 존재하는데, 레벨3는 1차~4차 데모데이까지 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차~3차는 코치님들에게 프로젝트 진척 상황에 대해 발표하는 것이고 4차 데모데이는 우테코 크루원들끼리 서로 서비스를 사용해보고 피드백하는 시간이다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtKAEW/btsK4g3ILR6/wEvvaGFeEoyko8mZuKqoEK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtKAEW/btsK4g3ILR6/wEvvaGFeEoyko8mZuKqoEK/img.jpg&quot; data-alt=&quot;데모데이 준비(1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtKAEW/btsK4g3ILR6/wEvvaGFeEoyko8mZuKqoEK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtKAEW%2FbtsK4g3ILR6%2FwEvvaGFeEoyko8mZuKqoEK%2Fimg.jpg&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;504&quot; height=&quot;378&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1050&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데모데이 준비(1)&lt;/figcaption&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;밤 늦게까지 남아 회의하고 QA하던 기억이 새록새록 하다.&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;&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;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckE12z/btsK19SSVqE/MAaRQ8GcxsGIplC994cZfK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckE12z/btsK19SSVqE/MAaRQ8GcxsGIplC994cZfK/img.jpg&quot; data-alt=&quot;데모데이(2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckE12z/btsK19SSVqE/MAaRQ8GcxsGIplC994cZfK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckE12z%2FbtsK19SSVqE%2FMAaRQ8GcxsGIplC994cZfK%2Fimg.jpg&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;448&quot; height=&quot;597&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데모데이(2)&lt;/figcaption&gt;
&lt;/figure&gt;
&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;생각보다 훨씬 긍정적인 유저반응을 얻고 너무나 행복해던 하루였다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;땅콩 1차 MVP 개발 완료!&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-02 02.04.39.png&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tk8Gp/btsK4vGnrRt/tk7Q6knC0jFgHKBDGMaRX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tk8Gp/btsK4vGnrRt/tk7Q6knC0jFgHKBDGMaRX1/img.png&quot; data-alt=&quot;서비스 피드백&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tk8Gp/btsK4vGnrRt/tk7Q6knC0jFgHKBDGMaRX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftk8Gp%2FbtsK4vGnrRt%2Ftk7Q6knC0jFgHKBDGMaRX1%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;461&quot; height=&quot;338&quot; data-filename=&quot;스크린샷 2024-12-02 02.04.39.png&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서비스 피드백&lt;/figcaption&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;피드백 정리하며 팀원들과 회고하는 과정에서도 서로 너무 벅차올라서 땅콩의 미래에 대해서 뜨거운 토론을 주고 받았다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사소한 행복들  &lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우테코 후드티 수령..!&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2946&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clMpCP/btsK2twKGKG/LILXsnk9lq1moOYKH4CWGK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clMpCP/btsK2twKGKG/LILXsnk9lq1moOYKH4CWGK/img.jpg&quot; data-alt=&quot;이젠 나도 정말 엄연한 우테코 사람.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clMpCP/btsK2twKGKG/LILXsnk9lq1moOYKH4CWGK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclMpCP%2FbtsK2twKGKG%2FLILXsnk9lq1moOYKH4CWGK%2Fimg.jpg&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;588&quot; height=&quot;392&quot; data-origin-width=&quot;2946&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이젠 나도 정말 엄연한 우테코 사람.&lt;/figcaption&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;&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;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AhZkS/btsK4imXWVM/B3bfzOWbkV8k1dJzInhvYk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AhZkS/btsK4imXWVM/B3bfzOWbkV8k1dJzInhvYk/img.jpg&quot; data-alt=&quot;타칸의 코로나 대소동&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AhZkS/btsK4imXWVM/B3bfzOWbkV8k1dJzInhvYk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAhZkS%2FbtsK4imXWVM%2FB3bfzOWbkV8k1dJzInhvYk%2Fimg.jpg&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;591&quot; height=&quot;443&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;타칸의 코로나 대소동&lt;/figcaption&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지각하면 커피 문화&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ydh84/btsK4wL3RGW/bjh3A8h4wgXRSGm7LkH4wk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ydh84/btsK4wL3RGW/bjh3A8h4wgXRSGm7LkH4wk/img.jpg&quot; data-alt=&quot;팀원들의 7회 지각 적립으로 만들어진 커피 7잔..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ydh84/btsK4wL3RGW/bjh3A8h4wgXRSGm7LkH4wk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fydh84%2FbtsK4wL3RGW%2Fbjh3A8h4wgXRSGm7LkH4wk%2Fimg.jpg&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;364&quot; height=&quot;485&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팀원들의 7회 지각 적립으로 만들어진 커피 7잔..&lt;/figcaption&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;&amp;nbsp;&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;1126&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlV9Eu/btsK4tohX8q/hIWKDujDMRYdiSBH0axIC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlV9Eu/btsK4tohX8q/hIWKDujDMRYdiSBH0axIC1/img.png&quot; data-alt=&quot;땅콩팀 커피 문화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlV9Eu/btsK4tohX8q/hIWKDujDMRYdiSBH0axIC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlV9Eu%2FbtsK4tohX8q%2FhIWKDujDMRYdiSBH0axIC1%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;622&quot; height=&quot;34&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;땅콩팀 커피 문화&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 지각할 때마다 1개씩 적립해서 7번 지각자가 발생하면 커피 타임이 발생한다 ㅋㅋㅋ&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF72da/btsK2e7Ed2I/nALs3nIGG6nkKFwfHNwzb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF72da/btsK2e7Ed2I/nALs3nIGG6nkKFwfHNwzb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF72da/btsK2e7Ed2I/nALs3nIGG6nkKFwfHNwzb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF72da%2FbtsK2e7Ed2I%2FnALs3nIGG6nkKFwfHNwzb0%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;97&quot; height=&quot;329&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;854&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨1,2가 각자의 개발적 가치관을 쌓아가는 시기라면 레벨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;너무 좋은 우리 팀원들 프린,타칸,커찬,마루,썬데이,포메 와 함께한 덕분에 시간가는 줄 모르고 보냈던 레벨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는 레벨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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더운 여름 함께 고생한 우리 땅콩팀 수고 많았고, 레벨4,5 그리고 우테코 수료 이후에도 계속 즐겁게 달려나갔으면 좋겠다:)&lt;/p&gt;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우아한테크코스 6기 회고</category>
      <category>우아한테크코스 레벨3</category>
      <category>우아한테크코스 레벨인터뷰</category>
      <category>우아한테크코스 회고록</category>
      <category>우테코 6기</category>
      <category>우테코 6기 회고</category>
      <category>우테코 레벨3</category>
      <category>우테코 회고</category>
      <category>우테코 후기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/174</guid>
      <comments>https://pgmjun.tistory.com/174#entry174comment</comments>
      <pubDate>Mon, 26 Aug 2024 01:07:31 +0900</pubDate>
    </item>
    <item>
      <title>외부에서 접근이 불가능한 EC2에 Self Hosted Runner를 적용해서 CI/CD 구축하기</title>
      <link>https://pgmjun.tistory.com/172</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OpQAy/btsIN5jI8YC/vAeoCMJoUJlTQnlq2LRkLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OpQAy/btsIN5jI8YC/vAeoCMJoUJlTQnlq2LRkLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OpQAy/btsIN5jI8YC/vAeoCMJoUJlTQnlq2LRkLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOpQAy%2FbtsIN5jI8YC%2FvAeoCMJoUJlTQnlq2LRkLK%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;532&quot; height=&quot;401&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용기술&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS EC2&lt;/li&gt;
&lt;li&gt;Git Actions&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;Springboot/Java&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS 보안 그룹 및 권한 제한으로 인한 고민&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ec2는 우테코에서 기본 제공해주는 VPC, Subnet, 보안그룹을 사용해야했으며, 여러 기능에 대해서 권한 제한이 걸려있었다.&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;우아한형제들 계정의 IAM 사용자 계정이기 때문에 혹시라도 AWS 사용에 미숙한 크루원이 pem키 같은 중요한 정보를 Github에 업로드하는 등의 실수를 하게 되면, 회사에 큰 재산적 피해가 갈 수 있기 때문인 것 같았다.&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;AWS IAM 에서 SecretKey를 발급받을 수 있는 권한이 없었고, EC2 접근도 80,443 포트만 전체 접근으로 열려있었고 ssh 프로토콜 통신을 위한 22번 포트는 우테코 캠퍼스 내부 LAN에 연결된 상태로만 접근 가능했다.&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;때문에 원래 ssh를 통해 EC2에 접근하는 방식으로 CD 스크립트를 작성하려던 나는, 어떤 방식으로 CD 스크립트를 작성해야할 지에 대한 고민이 커지게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 도구로 Git Actions를 사용할 예정이었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Actions이 돌아가고 있는 Runner는 Github 가 구축한 네트워크 환경에서 작동 중일 것이라서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코 LAN에서만 접근 가능한 땅콩의 EC2 환경에, 일반적인 Git Actions 환경을 통해 접근하기란 불가능할 것이기 때문이었다.&lt;/p&gt;
&lt;p 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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Self hosted Runner&lt;/b&gt;를 사용해서 문제에 접근해보았고,&lt;br /&gt;다행히 Git Actions를 사용하여 우리의 프로젝트 땅콩의 API 서버를 정상적으로 배포하는 것에 성공할 수 있었다.&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 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;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 패키지 구조&lt;/h2&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-filename=&quot;스크린샷 2024-07-19 00.23.24.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T9cag/btsIHYZ40DZ/KjL2OMvvtsy9TuxgI8HO8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T9cag/btsIHYZ40DZ/KjL2OMvvtsy9TuxgI8HO8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T9cag/btsIHYZ40DZ/KjL2OMvvtsy9TuxgI8HO8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT9cag%2FbtsIHYZ40DZ%2FKjL2OMvvtsy9TuxgI8HO8K%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;301&quot; height=&quot;350&quot; data-filename=&quot;스크린샷 2024-07-19 00.23.24.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;776&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;b&gt;backend&lt;/b&gt;, &lt;b&gt;frontend&lt;/b&gt; 패키지가 나뉘어있는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 백엔드는 다른 Repository에 구현하는게 일반적으로 알고 있지만, 이러한 상태로 구현하도록 환경을 제공받아서 이렇게 구현하게 되었다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4UpIn/btsIHo52K8v/sW6YA8Zka5wPfPkfftKrj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4UpIn/btsIHo52K8v/sW6YA8Zka5wPfPkfftKrj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4UpIn/btsIHo52K8v/sW6YA8Zka5wPfPkfftKrj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4UpIn%2FbtsIHo52K8v%2FsW6YA8Zka5wPfPkfftKrj0%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;338&quot; height=&quot;290&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;606&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;CI/CD 워크플로우는&lt;b&gt; ~/.github/workflows/&lt;/b&gt; 내부에 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이곳에 작성해야 Repository의 Action 탭에서 워크플로우를 인식할 수 있다.)&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;EC2에 외부 접근이 불가능한 문제 해결하기&lt;/h2&gt;
&lt;p 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;위에서 말했듯이, Self-Hosted Runner 설정을 통해 EC2에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;외부 접근이 안되는 문제를 해결했다.&lt;/span&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;그럼 &lt;b&gt;Self-Hosted Runner&lt;/b&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;&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;GitHub Actions - Self-Hosted Runner&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Self-Hosted Runner는 기본적으로 Github Actions 환경에서 제공하는 Runner가 아닌,&lt;br /&gt;커스텀 환경에서 &lt;b&gt;Runner&lt;/b&gt;를 &lt;b&gt;Self&lt;/b&gt;로 &lt;b&gt;hosting &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;우테코 AWS 계정의 권한 이슈로 인하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부로부터 ssh 프로토콜로 EC2에 접속하여 배포 스크립트를 수행시키는 등의 작업이 제한되었기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 Runner 자체를 우리 EC2에서 Hosting하고 그 Runner를 사용함으로써&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;외부에서 EC2에 접근하여 배포작업을 시키는 방식이 아닌, &lt;br /&gt;&lt;i&gt;&lt;u&gt;EC2 내부에서 Git Action 스크립트를 수행시키는 것이다.&lt;/u&gt;&lt;/i&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;blockquote data-ke-style=&quot;style3&quot;&gt;추가로 EC2에 SSH 접속을 통해 배포를 수행하는 방법은 다음과 같은 &lt;b&gt;오버헤드&lt;/b&gt; 발생&lt;br /&gt;&lt;br /&gt;보통 SSH를 통해 배포하는 경우, EC2에 Github Repository를 clone 받아둔 상태로 사용하고&lt;br /&gt;배포할 때마다 &lt;b&gt;git pull , artifact&lt;/b&gt; &lt;b&gt;build&lt;/b&gt; 작업을 dev 환경에서 수행해야한다.&lt;br /&gt;&lt;br /&gt;반면 Self Hosted Runner를 사용하면 &lt;b&gt;artifact actions&lt;/b&gt;를 활용해 오버헤드를 줄여볼 수 있음.&lt;br /&gt;Actions Runner에서 build를 수행하고 artifact 파일을 흭득한 후, 배포할 EC2에서 artifact 파일을 &lt;b&gt;artifact actions&lt;/b&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;&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;Self-Hosted Runner 설정 과정&lt;/h3&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 개인 레포지토리에 Self-Hosted Runner를 설정하려면 Repository의 Owner여야 한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;Repository - Settings 접속&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-21 00.14.33.png&quot; data-origin-width=&quot;2282&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czzJZO/btsIJzLNNPm/S8DIulUF57zKx23OAkvsWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czzJZO/btsIJzLNNPm/S8DIulUF57zKx23OAkvsWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czzJZO/btsIJzLNNPm/S8DIulUF57zKx23OAkvsWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczzJZO%2FbtsIJzLNNPm%2FS8DIulUF57zKx23OAkvsWk%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;663&quot; height=&quot;74&quot; data-filename=&quot;스크린샷 2024-07-21 00.14.33.png&quot; data-origin-width=&quot;2282&quot; data-origin-height=&quot;256&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;설정을 원하는 Repository의 &lt;b&gt;Settings&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Actions - Runners 탭 접속&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-21 00.14.54.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEpUiP/btsIIJuBiRL/FOV2MgbGZzMoUVVyEGNf2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEpUiP/btsIIJuBiRL/FOV2MgbGZzMoUVVyEGNf2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEpUiP/btsIIJuBiRL/FOV2MgbGZzMoUVVyEGNf2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEpUiP%2FbtsIIJuBiRL%2FFOV2MgbGZzMoUVVyEGNf2K%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;345&quot; height=&quot;405&quot; data-filename=&quot;스크린샷 2024-07-21 00.14.54.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;788&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;Actions 의 &lt;b&gt;Runners&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;New self-hosted runner 버튼 클릭&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be0nrj/btsIIIoXdYd/IHumBqzA98JXQcAAoFsj2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be0nrj/btsIIIoXdYd/IHumBqzA98JXQcAAoFsj2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be0nrj/btsIIIoXdYd/IHumBqzA98JXQcAAoFsj2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe0nrj%2FbtsIIIoXdYd%2FIHumBqzA98JXQcAAoFsj2K%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;703&quot; height=&quot;76&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;174&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;runner 추가를 위해 해당 버튼을 클릭한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;EC2에 맞는 환경 설정 및 Runner 생성 스크립트 입력&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;2108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS2Ju3/btsIJffKFW3/xl0GTeQgmi0RkMNOwF39U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS2Ju3/btsIJffKFW3/xl0GTeQgmi0RkMNOwF39U1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS2Ju3/btsIJffKFW3/xl0GTeQgmi0RkMNOwF39U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS2Ju3%2FbtsIJffKFW3%2Fxl0GTeQgmi0RkMNOwF39U1%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;571&quot; height=&quot;750&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;2108&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;Runner를 설치 및 세팅할 EC2의 환경과 동일한 설정을 해준 뒤에,&lt;br /&gt;아래 나와있는 스크립트를 EC2에서 실행해주면 설정이 끝난다.&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;Download 스크립트&lt;/b&gt;를 실행하여 self-hosted runner application을 EC2에 설치하고&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;&lt;b&gt;Configure 스크립트&lt;/b&gt;를 실행하여 자체 호스팅 러너 애플리케이션을 구성하고 GitHub Actions에 등록한다.&lt;br /&gt;구성 스크립트에는 요청을 인증하기 위해 대상 URL과 자동 생성된 시간 제한 토큰이 설정되어 있는데, 검정색으로 가린 부분이다.&lt;/div&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;size18&quot;&gt;[Self-Hosted Runner Name, Label 설정]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-27 22.29.33.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7aAf/btsKZscKodt/jDMuwAgaxuT7KKUlUWZ6vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7aAf/btsKZscKodt/jDMuwAgaxuT7KKUlUWZ6vK/img.png&quot; data-alt=&quot;'ddangkong-dev'라는 runner name 지정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7aAf/btsKZscKodt/jDMuwAgaxuT7KKUlUWZ6vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7aAf%2FbtsKZscKodt%2FjDMuwAgaxuT7KKUlUWZ6vK%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;673&quot; height=&quot;36&quot; data-filename=&quot;스크린샷 2024-11-27 22.29.33.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'ddangkong-dev'라는 runner name 지정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-27 22.28.17.png&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beszWe/btsKYKLLW0S/1l7x9NfCWJBVhX10DXNLik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beszWe/btsKYKLLW0S/1l7x9NfCWJBVhX10DXNLik/img.png&quot; data-alt=&quot;'dev' label 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beszWe/btsKYKLLW0S/1l7x9NfCWJBVhX10DXNLik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeszWe%2FbtsKYKLLW0S%2F1l7x9NfCWJBVhX10DXNLik%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;620&quot; height=&quot;62&quot; data-filename=&quot;스크린샷 2024-11-27 22.28.17.png&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'dev' label 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Configure 설정에서는 self-hosted runner의 &lt;b&gt;'구별자(이름)'&lt;/b&gt;와 &lt;b&gt;'레이블(Label)'&lt;/b&gt;을 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블은 self-hosted, Linux, X64가 기본값으로 주어진다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y6wl7/btsKZCzwJHW/unUzks2fo0Zt3eFJyXNUoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y6wl7/btsKZCzwJHW/unUzks2fo0Zt3eFJyXNUoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y6wl7/btsKZCzwJHW/unUzks2fo0Zt3eFJyXNUoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY6wl7%2FbtsKZCzwJHW%2FunUzks2fo0Zt3eFJyXNUoK%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;516&quot; height=&quot;51&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 내용은 Github Runners에서 다음과 같이 보여진다.&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;div style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;성공 문구 확인&lt;/h4&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&amp;radic; Connected to GitHub&lt;br /&gt;2019-10-24 05:45:56Z: Listening for Jobs&lt;/blockquote&gt;
&lt;/div&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;EC2에서 위 문구가 출력되면 성공이다.&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;div style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;워크플로우가 동작할 Runner 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1721489151645&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  deploy:
    runs-on: [dev]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;실행기에 할당된 &lt;b&gt;레이블(Label)&lt;/b&gt;&amp;nbsp;또는 해당 &lt;b&gt;그룹 멤버 자격&lt;/b&gt; 또는 이러한 것들의 조합을 기반으로,&lt;br /&gt;워크플로에서 사용할 자체 호스트 Runner를 지정할 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;나는 레이블을 통해 위와 같이 Runner를 설정해주었다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;레이블은 Runner를 사용하려는 운영체제 또는 아키텍쳐 등에 &lt;u&gt;적합한 Runner 타겟팅 목적으로 사용&lt;/u&gt;할 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;b&gt;job&lt;/b&gt;의 Runner를 설정하는 옵션인 &lt;b&gt;runs-on &lt;/b&gt;에 레이블을 입력해두면&lt;br /&gt;입력된 모든 레이블을 가진 Runner에서 job의 스크립트가 실행된다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;이 외에도 그룹 정보를 통해 Runner 지정도 가능한데,&lt;/div&gt;
&lt;div&gt;자세한건 &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://docs.github.com/ko/actions/hosting-your-own-runners/managing-self-hosted-runners/using-self-hosted-runners-in-a-workflow#using-default-labels-to-route-jobs&quot;&gt;공식문서&lt;/a&gt; 에서 확인할 수 있다.&lt;/div&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;h4 data-ke-size=&quot;size20&quot;&gt;백그라운드 실행 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Configure 설정을 마치면 run.sh 명령을 통해 Self-Hosted Runner를 실행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 편의를 위해 해당 sh 스크립트를 백그라운드에서 실행할 수 있는 &lt;b&gt;run-bg.sh&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_1732720441018&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo vi run-bg.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 run-bg.sh 를 생성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1732720446202&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

nohup ./run.sh &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에 위와 같이 백그라운드 실행 코드를 작성한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;nohup이 아니라 sh로 설정해도 상관은 없다만, sh로 설정 시 가끔 runner가 종료되는 이슈가 있어서 nohup으로 설정했다&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-size: 1.62em; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;Nginx Reverse Proxy 설정&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;우테코에서 제공하는 EC2 보안 그룹은 80, 443 포트 외에 다른 포트는 외부에서 접근할 수 없게 되어 있었다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;아직 프로젝트 극초반이라, HTTPS 설정은 하지도 않았기 때문에&lt;/div&gt;
&lt;div&gt;우선 80번 포트로 요청이 들어오면 포트포워딩하여 스프링부트 애플리케이션으로 요청을 전달해주면 되겠다고 생각했다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;이때 리버시 프록시 서버를 WAS 앞에 두어 일시적으로 포트포워딩 문제를 해결해보고자 하였다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;프록시 서버가 무엇인지 모르는 이들은 아래 게시글을 참고하면 될 것이다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;figure id=&quot;og_1721491227791&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;스프링부트에 Nginx 리버스 프록시(Reverse Proxy) 서버 연결하기&quot; data-og-description=&quot;리버스 프록시를 도입하게된 계기  졸업작품인 코리(Co:RE)의 서버를 개발하면서 실제로 배포를 위한 환경을 만들려고 하다보니,보안적인 요소를 생각하지 않을 수 없었다.&amp;nbsp;1차적으로 생각이 &quot; data-og-host=&quot;pgmjun.tistory.com&quot; data-og-source-url=&quot;https://pgmjun.tistory.com/151&quot; data-og-url=&quot;https://pgmjun.tistory.com/151&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/299JG/hyWCKez032/QUKbtLBNtpnfARbpbKXLTk/img.png?width=322&amp;amp;height=294&amp;amp;face=0_0_322_294,https://scrap.kakaocdn.net/dn/bKIKb6/hyWCEMcdyA/jKz44lw71wcBMU0BK6K0O0/img.png?width=322&amp;amp;height=294&amp;amp;face=0_0_322_294,https://scrap.kakaocdn.net/dn/6hbRb/hyWCFqNmEE/31Hqy7nTTDRf0BHATLxKF1/img.png?width=1280&amp;amp;height=446&amp;amp;face=0_0_1280_446&quot;&gt;&lt;a href=&quot;https://pgmjun.tistory.com/151&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pgmjun.tistory.com/151&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/299JG/hyWCKez032/QUKbtLBNtpnfARbpbKXLTk/img.png?width=322&amp;amp;height=294&amp;amp;face=0_0_322_294,https://scrap.kakaocdn.net/dn/bKIKb6/hyWCEMcdyA/jKz44lw71wcBMU0BK6K0O0/img.png?width=322&amp;amp;height=294&amp;amp;face=0_0_322_294,https://scrap.kakaocdn.net/dn/6hbRb/hyWCFqNmEE/31Hqy7nTTDRf0BHATLxKF1/img.png?width=1280&amp;amp;height=446&amp;amp;face=0_0_1280_446');&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;스프링부트에 Nginx 리버스 프록시(Reverse Proxy) 서버 연결하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;리버스 프록시를 도입하게된 계기  졸업작품인 코리(Co:RE)의 서버를 개발하면서 실제로 배포를 위한 환경을 만들려고 하다보니,보안적인 요소를 생각하지 않을 수 없었다.&amp;nbsp;1차적으로 생각이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pgmjun.tistory.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;blockquote data-ke-style=&quot;style2&quot;&gt;설정법 또한 친절하게 작성해두었는데, 그래도 잘 이해가 안간다면 댓글을 남겨주세요!&lt;/blockquote&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CI 스크립트&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;name: BE CI for Dev

on:
  workflow_dispatch:

  pull_request:
    branches: [ &quot;develop&quot; ]
    paths:
      - backend/**

jobs:
  build:
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: ./backend

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew 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;이는 CI 스크립트이다.&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;h3 data-ke-size=&quot;size23&quot;&gt;on: workflow_dispatch&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;on:
  workflow_dispatch:
&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;workflow_dispatch 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;workflow_dispatch는 Repository의 Actions 탭에서 수동으로 스크립트를 실행시킬 수 있는 기능이다.&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;현재 스크립트의 trigger path가 &lt;b&gt;./backend&lt;/b&gt; 패키지로 구분되어있어서 최상위에 있는 &lt;b&gt;.github&lt;/b&gt; 디렉토리에 대한 변경 정보에 대해서는 CI/CD 가 작동하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 workflow_dispatch 설정을 통해 버튼으로 Actions에 있는 스크립트를 실행시킬 수 있도록 구현한다.&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;패키지 구조가 backend, frontend로 구분되어 있어서 어쩔 수 없이 발생하는 문제였는데, 브랜치를 통해 be, fe 작업 단위를 나눌 것이 아니라면 이렇게 관리해야했다.&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;이번 스프린트가 끝나고 브랜치 관리 전략이 be, fe 단위로 변경될 가능성이 있으나&lt;br /&gt;아직은 그렇지 않으니 이렇게 처리하는 것이 최선이었다. 자세한 내용은 &lt;a href=&quot;https://www.notion.so/Actions-workflow-workflow_dispatch-410353620c0a46d498ef1f2bc313ba1a?pvs=21&quot;&gt;여기&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;h3 data-ke-size=&quot;size23&quot;&gt;on: pull_request&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;on:
  pull_request:
	branches: [ &quot;develop&quot; ]
	paths:
	  - backend/**&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;on: &amp;lt;시점&amp;gt;&lt;/b&gt; 은 언제 해당 스크립트가 동작할 지에 대한 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시점은 pull_request로 설정하였고, branches 옵션의 값을 develop, path를 backend/** 로 두었기 때문에 develop 브랜치에 backend 디렉토리 내부 변경이 포함된 PR이 올라오면 해당 스크립트를 동작시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 PR 이후, PR에 커밋이 추가되어도 스크립트가 작동된다.&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 title=&quot;공식문서&quot; href=&quot;https://docs.github.com/ko/actions/using-workflows/workflow-syntax-for-github-actions#on&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&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;h3 data-ke-size=&quot;size23&quot;&gt;build job&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;jobs:
  build: # build 작업
    runs-on: ubuntu-latest #ubuntu 최신 버전에서 동작

    defaults: # 기본 설정
      run:
        working-directory: ./backend # 작업 디렉토리를 ./backend 로 선언

    steps:
      - name: checkout # PR 브랜치 checkout
        uses: actions/checkout@v4

      - name: Set up JDK 17 # Oracle Temurin JDK 17 설치
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Grant execute permission for gradlew # gradlew 실행 권한 설정
        run: chmod +x gradlew 

      - name: Build with Gradle # Gradle을 사용하여 빌드
        run: ./gradlew build # clean 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;jobs는 스크립트에서 수행할 작업을 나열한다.&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;CI 스크립트에는 build 작업만이 존재하는데 내용이 길어 주석으로 설명하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따로 설명할 가장 주요한 포인트는 &lt;b&gt;Build with Gradle&lt;/b&gt;인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드를 위한 세팅을 완료한 이후, 최종적으로 &lt;b&gt;테스트&lt;/b&gt;와 &lt;b&gt;컴파일&lt;/b&gt; 과정을 포함한 &lt;b&gt;./gradlew build&lt;/b&gt; 를 작동시켜 CI 과정을 완료한다.&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;CI에서 build Task를 수행하는 이유는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build Task는 모든 테스트 실행, 프로덕션 artifact 생성을 포함하여 모든 것을 빌드한다고 공식문서에 작성되어 있다. 이를 통해서 정상적으로 프로덕트가 정상 작동 가능한 지 검증하는 것이다.&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;그리고 이 과정을 CI에서 이미 거치기 때문에 성능적인 측면을 고려하여,&lt;br /&gt;CD에서는 bootJar task만 동작시켜 실행가능한 artifact 생성만 수행하도록 구현하였다.&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;이제 CD 스크립트를 살펴보자.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CD 스크립트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;name: BE CD for Dev

on:
  workflow_dispatch:

  push:
    branches: [ &quot;develop&quot; ]
    paths:
      - backend/**

jobs:
  deploy:
    runs-on: [self-hosted, linux, ARM64] # Self hosted runner 사용
    steps:
      - name: Execute script on EC2 to deploy
        run: sudo sh ~/deploy-script-dev.sh&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;CD 스크립트는 Github에서 내려받아 CI 스크립트보다 훨씬 짧다.&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;h3 data-ke-size=&quot;size23&quot;&gt;on: push&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;on:
  push:
    branches: [ &quot;develop&quot; ]
    paths:
      - backend/**
&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;pull_request 시점에 작동하는 CI 스크립트와는 달리,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CD 스크립트는 &lt;b&gt;develop&lt;/b&gt; &lt;b&gt;브랜치&lt;/b&gt;의&amp;nbsp;&lt;b&gt;backend 패키지&lt;/b&gt;에 변경사항이 &lt;b&gt;push&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;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;deploy job&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;jobs:
  deploy:
    runs-on: [self-hosted, linux, ARM64] # Self hosted runner 사용
    steps:
      - name: Execute script on EC2 to deploy
        run: sudo sh ~/deploy-script-dev.sh
&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;이번 배포의 가장 중요한 부분인 deploy job이다.&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;위에서 설명한대로 우테코 AWS 계정 EC2는 ssh 접근이 특정 네트워크로 제한되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이를 해결하기 위해 &lt;b&gt;Self hosted Runner&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;Self hosted Runner&lt;/b&gt;는 기본적으로 Github Actions 환경에서 제공하는 Runner가 아닌,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 자신의 환경에서 Runner를 Self로 host하여 사용하는 방식이다.&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;Git Actions에서 EC2에 ssh로 접근하여 스크립트를 수행시키는 등의 작업이 불가능하기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 Runner 자체를 우리 EC2에서 Host하고 그 Runner를 사용함으로써&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 EC2에 접근하는 방식이 아닌 EC2 내부에서 스크립트를 수행시킬 수 있는 것이다.&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;runs-on 에 들어간 내용은 내가 설정한 Self hosted runner의 Label이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Label을 통해 동작시킬 &lt;b&gt;Self hosted Runner&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;deploy-script-dev.sh&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;deploy-script-dev.sh&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash

# 1. 프로젝트 디렉터리로 이동
cd /home/ubuntu/2024-ddangkong/backend/ || { echo &quot;Directory not found&quot;; exit 1; }

# 2. develop 브랜치로 체크아웃
git checkout develop || { echo &quot;Failed to checkout develop branch&quot;; exit 1; }

# 3. 원격 저장소의 최신 상태를 가져와서 병합
git pull origin develop || { echo &quot;Failed to pull latest changes&quot;; exit 1; }

# 4. gradlew bootJar 명령을 실행하여 빌드
./gradlew clean bootJar || { echo &quot;Build failed&quot;; exit 1; }

# 5. 빌드된 JAR 파일을 실행
cd build/libs || { echo &quot;Build directory not found&quot;; exit 1; }
JAR_FILE=$(ls *.jar | head -n 1)

if [ -z &quot;$JAR_FILE&quot; ]; then
  echo &quot;No JAR file found&quot;
  exit 1
fi

# 이전에 돌아가고 있던 Spring 애플리케이션 종료
PID=$(lsof -t -i:8080)

if [ -n &quot;$PID&quot; ]; then
  echo &quot;Killing process $PID running on port 8080&quot;
  kill -9 $PID
  echo &quot;Process $PID has been killed&quot;
else
  echo &quot;No process is running on port 8080&quot;
fi

# 우선 nohup에 로그 출력하고 이후에 logback 붙이기
nohup java -Dspring.profiles.active=dev -Duser.timezone=Asia/Seoul -Dserver.port=8080 -jar &quot;$JAR_FILE&quot; &amp;amp;
~&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;EC2 &lt;b&gt;~/&lt;/b&gt; 경로에 작성해둔 dev 환경의 deploy 쉘 스크립트 파일이다.&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;이 sh 파일을 실행하면 &lt;b&gt;[Github에서 땅콩의 Repository를 Pull]&lt;/b&gt; &amp;rarr; &lt;b&gt;[build]&lt;/b&gt; &amp;rarr; &lt;b&gt;[기존에 작동하던 스프링부트 애플리케이션 종료]&lt;/b&gt; &amp;rarr; &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;이 스크립트를 Runner에서 작동시킴으로써 CD 과정을 마무리할 수 있었다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 작성된 내용은 아직 부족한 점이 많은 초기의 CI/CD 스크립트들이다.&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;가장 중요한 문제는 CD 과정의 핵심적인 부분인 deploy script가 EC2 환경에 의존적이라는 점인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이러한 부족한 점과, 부족한 점들을 개선해본 CI/CD 파이프라인을 가지고 돌아오려고 한다.&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;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.github.com/ko/actions&quot;&gt;GitHub Actions 설명서 - GitHub Docs&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ssol_916/Github-Action%EC%9C%BC%EB%A1%9C-CICD-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0&quot;&gt;Github Action으로 CI/CD 구성하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners&quot;&gt;About self-hosted runners - GitHub Docs&lt;/a&gt;&lt;/p&gt;</description>
      <category>BackEnd/Infra</category>
      <category>CI/CD</category>
      <category>Git Actions</category>
      <category>git actions cd</category>
      <category>git actions ci</category>
      <category>Git Actions CI/CD</category>
      <category>gitaction 사용</category>
      <category>간단한 ci/cd</category>
      <category>기초 ci/cd</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/172</guid>
      <comments>https://pgmjun.tistory.com/172#entry172comment</comments>
      <pubDate>Tue, 23 Jul 2024 01:04:02 +0900</pubDate>
    </item>
    <item>
      <title>[협업] 누군가 해주길 기다리지 않고 직접 나서기</title>
      <link>https://pgmjun.tistory.com/170</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgVqFF/btsIzTQRedK/vjSNjVcV7BpAEq5ro618Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgVqFF/btsIzTQRedK/vjSNjVcV7BpAEq5ro618Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgVqFF/btsIzTQRedK/vjSNjVcV7BpAEq5ro618Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgVqFF%2FbtsIzTQRedK%2FvjSNjVcV7BpAEq5ro618Ak%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;740&quot; height=&quot;370&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt; 
 &lt;h1 style=&quot;text-align: var(--header-title-align);&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;우테코의 프로젝트는 2주 간격으로 데모데이가 있어서, 2주 단위 스프린트 방식으로 개발이 진행된다.&lt;/span&gt;&lt;/h1&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 짧은 단위로 빠르게 치고 나가는 환경에서 같이 일하고 싶은&lt;b&gt; ‘좋은’&lt;/b&gt; 팀원이 되기 위해, 그리고 &lt;b&gt;‘좋은’&lt;/b&gt; 팀을 만들기 위해 첫 스프린트 기간인 2주 간 몇 가지 시도와 노력을 거쳐왔다.&lt;br&gt;&amp;nbsp;&lt;br&gt;좋은 팀원이 되기 위해 노력하게 된 가장 큰 계기는 우테코 때문인데&lt;br&gt;우테코에서 진행하는 소프트스킬 수업을 들으면서&lt;br&gt;&lt;b&gt;“협업에 있어서 함께 일하고 싶은 사람은 어떤 사람일까?”, “그런 사람이 되려면 어떻게 해야할까?”&lt;/b&gt; 에 대해 고민하게 되었고,&lt;br&gt;&lt;b&gt;‘좋은 팀원’&lt;/b&gt;이란 무엇일지에 대해 정의를 내려보면서 점차 그런 사람이 되기 위해 노력하게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;내가 생각하는 &lt;b&gt;‘좋은’&lt;/b&gt; 팀원이란 &lt;u&gt;누군가 해주길 기다리지 않고 직접 나서는 사람&lt;/u&gt;이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;여기서 &lt;b&gt;“해준다”&lt;/b&gt; 에 내포된 행위들은 문제 해결이 될 수도 있겠고, 귀찮은 잡무일 수도 있다.&lt;br&gt;또는 문제에 대한 적극적인 고민과 해결 방안 제시일 수도 있을 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;서로를 배려하는 생각으로 “내가 할게!”, “이거 해보는거 어떨까?” 라며 직접 나서서 움직이고 제안하는 사람이&lt;br&gt;내가 되고 싶은 모습이자 진심으로 함께 하고 싶어하는 사람의 모습이었다.&lt;br&gt;&lt;br&gt;그렇기에 이번 우테코 팀프로젝트에서 서로에게 긍정적 영향을 미치는 팀 분위기를 만들어보고자&lt;br&gt;내가 먼저 행동으로 실천하기로 다짐했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;br&gt;이번 글은 이러한 배경을 바탕으로&lt;br&gt;레벨3 스프린트1 기간을 어떤 태도로 임했는 지 되돌아보는 글이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 적극적으로 고민하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 기획하는 단계에서 괜찮은 프로덕트를 만들고 싶다는 생각을 팀원들에게 전가하지 않고&lt;br&gt;직접 행동하고 생각해서 다양한 의견들을 많이 제시하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;기획 회의 도중 막혔던 부분은&lt;br&gt;캠퍼스에서 퇴근 후 집에 가서 수 차례 고민하고 다음날 데일리 미팅 시간에 공유하였고,&lt;br&gt;바보같은 의견 같을지라도 의견을 최대한 많이 내기 위해 노력했다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;보통 그렇게 던져지는 의견 중 쓸만한 내용이 10개 중 1개씩은 있었기 때문이다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;실제로 이 중 팀원들에게 긍정적으로 받아들여진 부분이 다수 있었고, 프로젝트 기획에 열정적으로 기여를 할 수 있었다.&lt;br&gt;너무나도 소중한 우리 팀원들은 근거를 기반으로 아닌 것은 확실히 쳐내주고, 좋은 것은 받아들여주어서 더 만족스러운 기획을 할 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-9f05c17d89094fe48aca36a26a707aef&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqsT7v/btsIzWz2Tup/E1mjxTJq1tgfz62CcXFVw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqsT7v/btsIzWz2Tup/E1mjxTJq1tgfz62CcXFVw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqsT7v/btsIzWz2Tup/E1mjxTJq1tgfz62CcXFVw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqsT7v%2FbtsIzWz2Tup%2FE1mjxTJq1tgfz62CcXFVw1%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;765&quot; height=&quot;178&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 노력을 알아주었는지, 스프린트 회고 시간에 팀원들로부터 주도적으로 행동한 부분에 대해 긍정적인 피드백들을 받을 수 있었다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;티는 안냈지만 혼자 읽어보면서 감동받았고, 노력을 알아봐준 팀원들에게 고마워서 더 열정이 생겼다  &lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 적극적으로 공유하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-af7b49bb92fc42ba987550fc76738dff&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brt53a/btsIAaY7WMA/UxHvWPiLSuq7GcUksrzm51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brt53a/btsIAaY7WMA/UxHvWPiLSuq7GcUksrzm51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brt53a/btsIAaY7WMA/UxHvWPiLSuq7GcUksrzm51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrt53a%2FbtsIAaY7WMA%2FUxHvWPiLSuq7GcUksrzm51%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;738&quot; height=&quot;338.542&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개발적인 부분에 있어서 팀원들이 고민하는 포인트가 있으면 직접 나서서 확인하고 빠르게 공유해주었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;회의 시간에 백엔드 팀원인 프린으로부터 내가 주장한 &lt;b&gt;@RequestMapping&lt;/b&gt; 애노테이션 설정이&lt;br&gt;핸들러 매핑 과정에서 &lt;u&gt;&lt;i&gt;성능 이슈&lt;/i&gt;&lt;/u&gt;&lt;i&gt; &lt;/i&gt;를 유발할 수 있지 않느냐는 이야기를 듣게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;충분히 고려해볼만한 좋은 의문이었지만 이에 대해 자세히 알고 있는 팀원들이 없었기에&lt;br&gt;직접 &lt;b&gt;RequestMappingHandlerMapping&lt;/b&gt; 로직을 뜯어보고 그 결과를 팀원들에게 공유해주었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이러한 공유 문화가 활성화되면 '선삽자(먼저 삽질하는 사람)' 인 나도 학습할 수 있고,&lt;br&gt;팀원들도 학습에 필요한 시간을 줄여서 또 다른 삽질 &amp;amp; 공유 사이클을 만들어 어느 팀보다 빠르게 성장할 수 있다고 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 개발 생산성 향상 방안 고민하기 (with. 협업 툴)&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;협업 과정에서 개발 생산성을 향상시키는 방법은 다양하지만,&lt;br&gt;그중 협업을 편하게 만들어주는 &lt;b&gt;협업툴&lt;/b&gt;을 활용하는 것도 좋은 방식이라고 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우리팀은 우테코에서 사용하고 있는 &lt;b&gt;Slack&lt;/b&gt;을 사용하여 프로젝트 협업 관리를 하려했지만,&lt;br&gt;무료 버전에서는 대화 기록이 3개월 밖에 유지되지 않는 Slack의 단점이 너무 크다는 판단 하에&lt;br&gt;&lt;b&gt;Discord&lt;/b&gt;를 협업툴로 정하게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Discord는 정말 다양한 서비스와 연동하여 사용할 수 있는 협업툴인데,&lt;br&gt;Discord 채널에 Webhook을 연결하여 &lt;b&gt;Github 내에서의 Issue 생성, PR 생성&lt;/b&gt; 등 특정 행위가 발생하면&lt;br&gt;알림을 보내도록 설정할 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우리 팀의 VCS(Version Control System)는 Github이고,&lt;br&gt;VCS와 관련된 주요 동작이 발생하면 우리의 협업 공간에 알림은 주는 것은&lt;br&gt;&lt;b&gt;개발 생산성&lt;/b&gt;에 큰 효력이 있을 것이라고 생각했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이 부분은 팀원들도 공감하는 내용이었고,&lt;br&gt;때문에 Webhook을 활용하여 &lt;b&gt;‘Github to Discord 알림’ &lt;/b&gt;설정을 주도하게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;팀 Discord 채널에 연결된 Github 알림&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-3b783fd38d1441f3a4757be0cbe30c84&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkI1ez/btsIyjQKPa7/kJY5HHLdsEMIae7QOM8I8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkI1ez/btsIyjQKPa7/kJY5HHLdsEMIae7QOM8I8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkI1ez/btsIyjQKPa7/kJY5HHLdsEMIae7QOM8I8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkI1ez%2FbtsIyjQKPa7%2FkJY5HHLdsEMIae7QOM8I8K%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;432&quot; height=&quot;481.0909&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진과 같이 알림을 받겠다고 설정해둔&amp;nbsp;&lt;b&gt;이벤트&lt;/b&gt;가 발생하면, Github에서 우리의 슬랙 github 채널로 알림을 보내도록 설정하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이를 통해 팀원들이 더욱 빠르게 깃헙에서 발생하는 프로젝트 진행 상황을 공유받을 수 있게 되었고 이는 분명 우리팀의 생산성을 높여줄 것이라고 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;설정 방식과 관련 내용은 &lt;a href=&quot;https://pgmjun.tistory.com/169&quot; target=&quot;_blank&quot; title=&quot;여기&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;에서 확인할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;좋은 팀원이 되기 위한 다짐은 이번 프로젝트에 몰입할 수 있도록 만들어주는 원동력이 되고있다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;지금의 모습에서 나태해지지 말고 항상 내가 할 수 있는 것을 찾고, 분석하고, 시도하자.&lt;br&gt;팀에 긍정적인 영향을 끼칠 수 있도록 힘든 일도 앞장서는 사람이 되자.&lt;br&gt;겁먹지 말고 시도하고 깨지고 그 속에서 배움을 얻는 사람이 되자.&lt;br&gt;그리고 그 과정에서 내가 팀원들의 경험 기회를 뺏어가고 있지는 않은지 항상 경계하고 인지하자.&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우테코 6기 프로젝트</category>
      <category>우테코 6기 회고</category>
      <category>우테코 땅콩</category>
      <category>우테코 프로젝트</category>
      <category>우테코 프로젝트 땅콩</category>
      <category>우테코 프로젝트 회고</category>
      <category>함께 일하고싶은 팀원 되기</category>
      <category>협업 잘하기</category>
      <category>협업에 대한 고민</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/170</guid>
      <comments>https://pgmjun.tistory.com/170#entry170comment</comments>
      <pubDate>Mon, 15 Jul 2024 01:25:49 +0900</pubDate>
    </item>
    <item>
      <title>Github Webhook을 협업툴과 연결해서 팀의 생산성 높이기 (with. Discord)</title>
      <link>https://pgmjun.tistory.com/169</link>
      <description>&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;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beYKt9/btsIzLyvxMh/gOk1nxPcJsxJtj4otPfIq0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beYKt9/btsIzLyvxMh/gOk1nxPcJsxJtj4otPfIq0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beYKt9/btsIzLyvxMh/gOk1nxPcJsxJtj4otPfIq0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeYKt9%2FbtsIzLyvxMh%2FgOk1nxPcJsxJtj4otPfIq0%2Fimg.webp&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;352&quot; height=&quot;352&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;협업 과정에서 개발 생산성을 증가시키는 방법은 다양하지만, 그중 협업을 편하게 만들어주는 &lt;b&gt;협업툴&lt;/b&gt;을 활용하는 것도 좋은 방식이라고 생각한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;우리 땅콩 팀은 우테코에서 사용하고 있는 &lt;b&gt;Slack&lt;/b&gt;을 사용하여 프로젝트 협업 관리를 하려했지만,&lt;br /&gt;무료 버전에서는 대화 기록이 3개월 밖에 유지되지 않는 Slack의 단점이 너무 크다는 판단 하에&lt;br /&gt;&lt;b&gt;Discord&lt;/b&gt;를 협업툴로 정하게 되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Discord는 정말 다양한 서비스와 연동하여 사용할 수 있는 협업툴인데,&lt;br /&gt;Discord 채널에 Webhook을 연결하여 &lt;b&gt;Github 내에서의 Issue 생성, PR 생성&lt;/b&gt; 등 특정 행위가 발생하면 알림을 보내도록 설정할 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;우리 팀의 VCS(Version Control System)는 Github이고,&lt;br /&gt;VCS와 관련된 주요 동작이 발생하면 우리의 협업 공간에 알림은 주는 것은 개발 생산성에 큰 효력이 있을 것이라고 생각했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 부분은 팀원들도 공감하는 내용이었고, 때문에 Webhook을 활용하여 &lt;b&gt;&amp;lsquo;Github to Discord 알림&amp;rsquo; &lt;/b&gt;설정을 주도하게 되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래에는 그 과정을 설명해놓았으니 필요한 사람들은 따라서 해보면 될 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Discord에 알림을 전달받을 채널 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-6e6527cc3c9e4c71bdcb35ebfbe6b230&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du09PD/btsIzbLbMqF/rfUMAsNZTqmkV2sBrUBYb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du09PD/btsIzbLbMqF/rfUMAsNZTqmkV2sBrUBYb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du09PD/btsIzbLbMqF/rfUMAsNZTqmkV2sBrUBYb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu09PD%2FbtsIzbLbMqF%2FrfUMAsNZTqmkV2sBrUBYb1%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;336&quot; height=&quot;365.92712&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스코드 서버에 깃허브 알림을 받을 채널을 하나 생성한다.&lt;br /&gt;이름은 자신이 원하는 대로 설정하면 되는데, 나는 &lt;b&gt;github&lt;/b&gt;이라는 이름으로 설정하였다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Discord 채널 Webhook 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 Webhook 설정을 수행해보자.&lt;br /&gt;아래 사진의 설명을 따라가보자.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-e652b7c1472242b4a21effa109ffdfe7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wNXxv/btsIx29QcTc/5zjxUSxr3XkCk8PdfPfMN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wNXxv/btsIx29QcTc/5zjxUSxr3XkCk8PdfPfMN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wNXxv/btsIx29QcTc/5zjxUSxr3XkCk8PdfPfMN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwNXxv%2FbtsIx29QcTc%2F5zjxUSxr3XkCk8PdfPfMN1%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;336&quot; height=&quot;420.6614&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webhook 설정을 원하는 서버의 &lt;b&gt;&amp;lsquo;서버 설정&amp;rsquo;&lt;/b&gt; 클릭&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-60b7944c1fcd487b87ce02cc67511617&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YQ8yT/btsIz72p4Kk/KnU4XjoirYkkpKHcRC5Dwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YQ8yT/btsIz72p4Kk/KnU4XjoirYkkpKHcRC5Dwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YQ8yT/btsIz72p4Kk/KnU4XjoirYkkpKHcRC5Dwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYQ8yT%2FbtsIz72p4Kk%2FKnU4XjoirYkkpKHcRC5Dwk%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;576&quot; height=&quot;283.2604&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좌측 설정 탭 - 연동 - 웹후크 만들기 &lt;/b&gt;클릭&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-4a124c5b6e2d430f989cf92ca17437d7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dI47Xq/btsIy4SS8xG/B9Q1OZE30GYMkZP4ctdJx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dI47Xq/btsIy4SS8xG/B9Q1OZE30GYMkZP4ctdJx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dI47Xq/btsIy4SS8xG/B9Q1OZE30GYMkZP4ctdJx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdI47Xq%2FbtsIy4SS8xG%2FB9Q1OZE30GYMkZP4ctdJx0%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;576&quot; height=&quot;428.66837&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;952&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 원하는 대로 설정하고, 채널은 알림을 보내줄 채널을 선택한다.&lt;br /&gt;나는 방금 알림을 위해 만든 github 채널을 선택했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Webhook 생성 후에는 &lt;b&gt;&amp;lsquo;웹후크 URL 복사&amp;rsquo;&lt;/b&gt; 버튼을 통해 URL을 복사해주고 &lt;u&gt;알림을 받을&lt;/u&gt; &lt;b&gt;Github Repository&lt;/b&gt;로 이동한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Github Repository Webhook 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 Github Repository에서 Webhook 설정을 통해 Discord와 연결을 해주면 알림 설정이 마무리된다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-c9faafc3c9504a6c993a17c65e144546&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4utxr/btsIzeHWKSy/kpFeOnIX82QT9iKLT7OVD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4utxr/btsIzeHWKSy/kpFeOnIX82QT9iKLT7OVD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4utxr/btsIzeHWKSy/kpFeOnIX82QT9iKLT7OVD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4utxr%2FbtsIzeHWKSy%2FkpFeOnIX82QT9iKLT7OVD0%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;624&quot; height=&quot;90.91905&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Repository - Settings &lt;/b&gt;클릭&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-3995291c049e40e9b29019b318a47e9c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E1vYO/btsIz6vFy1Q/k8H4HkDeMHKKpWkkXCBo81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E1vYO/btsIz6vFy1Q/k8H4HkDeMHKKpWkkXCBo81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E1vYO/btsIz6vFy1Q/k8H4HkDeMHKKpWkkXCBo81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE1vYO%2FbtsIz6vFy1Q%2Fk8H4HkDeMHKKpWkkXCBo81%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;336&quot; height=&quot;516.84076&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좌측 설정 탭 - Webhooks&lt;/b&gt; 클릭&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-ad3eda932b22446182b94f3d95f9b394&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnXGcn/btsIy0JUR4T/iUiMGBkFj3wyw1mpCFIJK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnXGcn/btsIy0JUR4T/iUiMGBkFj3wyw1mpCFIJK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnXGcn/btsIy0JUR4T/iUiMGBkFj3wyw1mpCFIJK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnXGcn%2FbtsIy0JUR4T%2FiUiMGBkFj3wyw1mpCFIJK1%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;738&quot; height=&quot;178.04584&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측 상단에 &lt;b&gt;&amp;lsquo;Add webhook&amp;rsquo;&lt;/b&gt; 버튼 클릭&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-ac99018c28e44c4da400a7fb8bfc2da2&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1053&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYDhP/btsIyhk4Q3u/BphgNmt0aS45InBUc43ek0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYDhP/btsIyhk4Q3u/BphgNmt0aS45InBUc43ek0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYDhP/btsIyhk4Q3u/BphgNmt0aS45InBUc43ek0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYDhP%2FbtsIyhk4Q3u%2FBphgNmt0aS45InBUc43ek0%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;708&quot; height=&quot;582.5211&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1053&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 설정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lsquo;Payload URL&amp;rsquo;에 &lt;b&gt;Discord에서 복사해온 웹후크 URL + /github &lt;/b&gt;입력&lt;/li&gt;
&lt;li&gt;&amp;lsquo;Content type&amp;rsquo;에 &lt;b&gt;application/json&lt;/b&gt; 입력&lt;/li&gt;
&lt;/ul&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최하단 설정을 보면 webhook의 trigger를 설정할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째는 push 이벤트만 알림을 보내는 것&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;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-cfea898fdb484dbba6fa14b4f2f7f67e&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;1978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi0H6Q/btsIDtFplOA/Vda64PzhKgmQlPI9A1PDKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi0H6Q/btsIDtFplOA/Vda64PzhKgmQlPI9A1PDKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi0H6Q/btsIDtFplOA/Vda64PzhKgmQlPI9A1PDKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi0H6Q%2FbtsIDtFplOA%2FVda64PzhKgmQlPI9A1PDKk%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;455&quot; height=&quot;633&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;1978&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리거는 &lt;b&gt;세 번째&lt;/b&gt;를 선택하여 선택 사항에 대한 알림만 받도록 하였다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;push만 알림을 받기에는 정보가 적어 생산성 향상에 큰 도움이 되지는 않을 것이다 싶었고, 모든 이벤트에 대한 알림을 보내는 것은 오히려 알림을 통해 전달받는 정보의 질을 저하시킨다고 느꼈다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;때문에 세 번째를 선택했고, &lt;b&gt;Issue, PR, Release, PR에 리뷰가 달렸을 때&amp;nbsp;&lt;/b&gt;를 알림이 필요한 정보라고 생각해서 받도록 설정해주었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;block-d5239085bc69415999dc3426ad14b5d4&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;팀 Discord 채널에 전달된 Github 알림&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-c9f0741e29b14ce2888984ae378d29f6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cstVz3/btsIzqnQqqx/NVXKRoGTLRK33eTQmYQmD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cstVz3/btsIzqnQqqx/NVXKRoGTLRK33eTQmYQmD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cstVz3/btsIzqnQqqx/NVXKRoGTLRK33eTQmYQmD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcstVz3%2FbtsIzqnQqqx%2FNVXKRoGTLRK33eTQmYQmD0%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;432&quot; height=&quot;481.0909&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 &lt;b&gt;Issue, PR, Release, Milestone, Push&lt;/b&gt; 와 관련된 이벤트가 발생하면 위 사진과 같이 Github에서 우리의 슬랙 github 채널로 알림을 보낸다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이를 통해 팀원들이 더욱 빠르게 깃헙에서 발생하는 프로젝트 진행 상황을 공유받을 수 있게 되었고&lt;br /&gt;이는 분명 우리 팀의 생산성을 높여줄 것이라고 생각한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <category>discord webhook</category>
      <category>github to discord notification</category>
      <category>github to discord webhook</category>
      <category>깃허브 to 디스코드 알림</category>
      <category>깃허브 webhook</category>
      <category>디스코드</category>
      <category>디스코드 webhook</category>
      <category>디스코드 알림</category>
      <category>프로젝트 협업</category>
      <category>협업툴</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/169</guid>
      <comments>https://pgmjun.tistory.com/169#entry169comment</comments>
      <pubDate>Mon, 15 Jul 2024 01:12:50 +0900</pubDate>
    </item>
    <item>
      <title>스프링 MVC 내부 동작 원리 (with. 핸들러 매핑, 어뎁터 매핑, Filter, Interceptor.. etc)</title>
      <link>https://pgmjun.tistory.com/168</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 글은 김영한님의 스프링 MVC 강의 영상을 참조하여 작성되었습니다.&lt;/blockquote&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;225&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdQjC4/btsIyGK7G6a/KOiMK1BqKGgGeED0wYwrI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdQjC4/btsIyGK7G6a/KOiMK1BqKGgGeED0wYwrI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdQjC4/btsIyGK7G6a/KOiMK1BqKGgGeED0wYwrI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdQjC4%2FbtsIyGK7G6a%2FKOiMK1BqKGgGeED0wYwrI0%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;225&quot; height=&quot;225&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring MVC 동작 과정  &lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUdeRC/btsIzy6S286/0rnmYZ2ivYA0gROSo1P5r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUdeRC/btsIzy6S286/0rnmYZ2ivYA0gROSo1P5r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUdeRC/btsIzy6S286/0rnmYZ2ivYA0gROSo1P5r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUdeRC%2FbtsIzy6S286%2F0rnmYZ2ivYA0gROSo1P5r0%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;780&quot; height=&quot;436&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;h3 data-ke-size=&quot;size23&quot;&gt;0. HTTP 요청  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Client&lt;/code&gt;로부터 요청이 들어오면 &lt;b&gt;Front Controller&lt;/b&gt;인 &lt;code&gt;Dispatcher Sevlet&lt;/code&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 style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Dispatcher Sevlet은 받은 요청을 처리할 수 있는 Handler 객체를 가져온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;0번 과정에서 Dispatcher Servlet에 요청이 들어오기 직전이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Filter&lt;/b&gt;의 동작 시점이다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;Dispatcher Servlet 이후에 동작하는 ControllerAdvice는 &lt;/span&gt;이러한 동작 시점에 의해 Filter 측에서 발생하는 예외까진 잡지 못한다는 특징이 있다.&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;&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 핸들러 조회  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러 매핑을 통해 요청 URL에 매핑된 &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;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ RequestMappingHandlerMapping&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpringBoot를 사용하면 &lt;code&gt;0순위&lt;/code&gt;로 &lt;b&gt;@RequestMapping 애노테이션 기반 Handler&lt;/b&gt;를 찾아 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;URI&lt;/b&gt;에 입력된 &lt;code&gt;Endpoint&lt;/code&gt;와&lt;br /&gt;&lt;b&gt;@RequestMapping&lt;/b&gt; 애노테이션에 설정된 &lt;code&gt;Endpoint&lt;/code&gt;를 비교하여&lt;br /&gt;요청을 담당하는 &lt;code&gt;Handler&lt;/code&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;h4 data-ke-size=&quot;size20&quot;&gt;✅ BeanNameUrlHandlerMapping&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;0순위&lt;/code&gt;가 실패하면 &lt;code&gt;1순위&lt;/code&gt;로는 &lt;b&gt;요청한 URI와 일치하는 스프링 빈 이름의 Handler&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 핸들러 어댑터 조회  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&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;✅ RequestMappingHandlerAdpater&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpringBoot를 사용하면 0순위로 &lt;b&gt;@RequestMapping 애노테이션 기반 Handler의 Adapter&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;✅ HttpRequestHandlerAdapter&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;HttpRequestHandler&lt;/code&gt;를 implements하여 구현한 Handler를 처리하는 일을 수행하며 &lt;code&gt;1순위&lt;/code&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;✅ SimpleControllerHandlerAdpater&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Controller&lt;/code&gt;를 implements하여 구현한 Handler를 처리하는 일을 수행하며 &lt;code&gt;2순위&lt;/code&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 핸들러 어댑터 실행  &lt;/h3&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 핸들러 실행  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러 어댑터가 실제 핸들러를 실행한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번 과정 전/후 가 &lt;b&gt;Intercepter&lt;/b&gt;의 동작 시점이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. ModelAndView 반환  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;HandlerAdapter&lt;/code&gt;는 &lt;code&gt;Handler&lt;/code&gt;가 반환하는 정보를 &lt;code&gt;ModelAndView&lt;/code&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. viewResolver 호출  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5번 과정에서 얻은 &lt;code&gt;ModelAndView&lt;/code&gt; 객체를 통해 &lt;b&gt;view의 이름&lt;/b&gt;을 뷰 리졸버에 전달한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSP의 경우 &lt;b&gt;InternalResourceViewResolver&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. View 반환  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 &lt;b&gt;view 객체를 반환&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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;8. 뷰 렌더링  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;View Resolver&lt;/code&gt;를 통해 얻은 view 객체에 &lt;code&gt;5번 과정&lt;/code&gt;에서 얻은 &lt;code&gt;ModelAndView&lt;/code&gt;의 &lt;code&gt;Model&lt;/code&gt;을 파라미터로 넘겨주고 &lt;code&gt;render()&lt;/code&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9. 페이지 반환  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 렌더링된 페이지를 Client에게 &lt;b&gt;response&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Spring은 이미 대부분의 Handler Mapping과 Handler Adpater를 구현해 두었기 때문에 직접 구현할 일은 거의 없다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 동작원리를 충분히 이해하고 사용하는 것과 그렇지 못한 것은 큰 차이가 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>스프링</category>
      <category>스프링 filter 동작 시점</category>
      <category>스프링 interceptor 동작 시점</category>
      <category>스프링 어뎁터 매핑</category>
      <category>스프링 컨트롤러 매핑</category>
      <category>스프링부트 mvc 동작 원리</category>
      <category>스프링부트 작동 원리</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/168</guid>
      <comments>https://pgmjun.tistory.com/168#entry168comment</comments>
      <pubDate>Sat, 13 Jul 2024 17:01:18 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 레벨2 돌아보기 (2024/04/15 ~ 2024/6/14)</title>
      <link>https://pgmjun.tistory.com/167</link>
      <description>&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;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mLmc2/btsIqAYbZLG/gbmlglbFlXG8KLg8GrkPb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mLmc2/btsIqAYbZLG/gbmlglbFlXG8KLg8GrkPb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mLmc2/btsIqAYbZLG/gbmlglbFlXG8KLg8GrkPb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmLmc2%2FbtsIqAYbZLG%2FgbmlglbFlXG8KLg8GrkPb1%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;1280&quot; height=&quot;640&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;우테코에 들어온 지 진짜 얼마 안된 것 같은데 어느덧 시간이 지나 레벨3가 시작하기 전날이 되었다.&lt;br /&gt;레벨3를 시작하기 전, &lt;b&gt;&quot;내가 어떤 것들을 중점에 두고 레벨2에 임했는가&quot;, &quot;레벨2를 거치며 어떤 성장을 이루었는가&quot;&lt;/b&gt; 를 되돌아보는 글을 적어보려고 한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  무엇을 중점에 두었는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 레벨은 내가 원하는 걸 다 챙길 수 없는 2달 가량의 짧은 기간이기 때문에&lt;br /&gt;이 안에서 가장 효율적으로 무언가를 얻을 수 있는 방법을 찾기 위해 힘쓴 것 같았다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우테코에서 가장 많이 얻어갈 수 있는 학습 방법 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 학습 방법에 고민이 많았다.&lt;br /&gt;레벨1을 되돌아보면 생각보다 공부할 시간이 많이 없다고 느꼈다.&lt;br /&gt;미션을 수행하면서 스터디도 하고 책도 보고 하는 사람들 정말 신기했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;만약 어떤 날 하루동안 A,B를 하려고 했다면 A를 다 끝내지도 못한 상태로 하루가 지나가는 경우가 너무 많았다.&lt;br /&gt;시간 컨트롤이 정말 힘들었다.&lt;br /&gt;(레벨3 팀원들과 시간관리에 대해서 얘기해보고 싶다.)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아무튼 나는 이 짧디 짧은 시간 속에서 가장 많은 정보를 챙겨가는 방법을 찾아나섰고&lt;br /&gt;내가 사용한 방법은 이것들이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 학습 키워드 기록&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미션 1개에 2주의 시간이 주어지는데, 2주는 생각보다 짧다.&lt;br /&gt;이 안에 가장 많이 챙겨가는 방법은 하나 뿐이었다.&lt;br /&gt;바로 &lt;b&gt;'기록'&lt;/b&gt;&lt;br /&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;1576&quot; data-origin-height=&quot;1420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1PBN6/btsIjgeV1Us/aSQvVWlFHJibMhoDXveXyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1PBN6/btsIjgeV1Us/aSQvVWlFHJibMhoDXveXyK/img.png&quot; data-alt=&quot;키워드 학습 기록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1PBN6/btsIjgeV1Us/aSQvVWlFHJibMhoDXveXyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1PBN6%2FbtsIjgeV1Us%2FaSQvVWlFHJibMhoDXveXyK%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;442&quot; height=&quot;398&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;1420&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;키워드 학습 기록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;나는 미션을 수행하면서 정말 궁금하고 당장 알지 못하면 못참겠다 싶은 내용들은 그날그날 학습하면서 기록했지만&lt;br /&gt;일단 돌아가게 만들고 나중에 찾아보아도 괜찮은 부분들에 대해서는 학습 키워드만 기록해두고 넘어갔다.&lt;br /&gt;그리고 나중에 시간에 여유가 있을 때, 궁금했던 부분들 부터 하나하나 학습하며 기록해두곤 하였다.&lt;br /&gt;그리고 내가 기록하지 않아도 이미 잘 정리된 래퍼런스가 있는 경우는 그냥 래퍼런스를 기록해두곤 하였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;최근 블로그에 하나씩 올리는 포스팅은 이때 작성해둔 글들을 다듬어서올리는 것이다ㅋㅋ&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 좋은 질문 많이하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 질문한다는 것 자체가 정말 어려웠다.&lt;br /&gt;내가 뭘 모르는 지 몰랐고, 모르는 것을 드러내는 것이 부끄럽다고 생각했다.&lt;br /&gt;그래서 질문을 잘 하는 사람들이 정말 멋있어보였고 나도 그렇게 되고 싶었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 우아한 테크코스의 분위기는 질문을 권장하고, 질문을 제대로 못해도 아무도 비난하지 않았다.&lt;br /&gt;때문에 질문이라는 것 자체에 대한 부담이 많이 없어졌다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;또한 질문은 그저 많이한다고 좋은 것은 아니다.&lt;br /&gt;의미있는 질문을 던져야 똑같이 의미있는 답변을 들을 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;항상 질문 잘 하는 사람들을 보면 &quot;어떻게 저렇게 근본적인 의문을 가지게 되는 걸까?&quot; 라는 생각이 들었는데,&lt;br /&gt;생각하며 학습하다보면 자연스럽게 근본적인 질문이 만들어지는 것 같았다.&lt;br /&gt;&lt;br /&gt;생각하며 학습한다는 것은 &quot;이게 뭐지&quot; 하고 넘기는 것이 아니라 그 의문을 정확히 캐치해서 &lt;b&gt;'궁금증'&lt;/b&gt;으로 바꿔내는 능력이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;항상 개발을 하다가 문제가 발생하면 어떻게든 해결만 하고 넘어가던 나는 질문할 거리가 없는 것이 당연했을 터다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;&lt;b&gt;문제가 어떻게든 해결되었다고 그냥 넘기지 말고 &quot;이건 왜 이렇게 되는거지?&quot; 라는 의문을 항상 품자&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 잡담은 경쟁력!&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부하다가 내가 어떤 사고의 흐름을 만들어냈다고 가정하자.&lt;br /&gt;예를 들어 어떠한 근거를 통해 DB 테이블을 설계했다.&lt;br /&gt;그럼 그냥 넘어가지 말고 근처에 사람들이 있는 곳에 가서 이러한 설계에 대해서 어떻게 생각하는 지 물어보았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다양한 사람들이 있고 다양한 의견이 있다.&lt;br /&gt;여러 사람들의 의견을 듣다보면 내 의견을 더욱 좋은 방향으로 개선해나갈 수 있다.&lt;br /&gt;또 다른 이들도 그 대화속에서 배움을 얻을 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;우테코는 이런 분위기인 것 같다.&lt;br /&gt;많이 떠드는 사람이 많이 얻는다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;레벨1에서는 이를 깨닫지 못해 다른 이들과 대화를 많이 시도하지 않았지만, 레벨2에서는 대화를 많이했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;라이트닝 토크 ⚡️&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;가벼운 주제라도 내 생각을 얘기하고 상대방의 의견을 물어보는 것은 &lt;b&gt;'라이트닝 토크' &lt;/b&gt;라는 활동을 통해 우테코에서도 제공되었다.&lt;br /&gt;&lt;br /&gt;나는 API를 유연하게 설계하는 방법에 대해서 고민한 기록을 가볍게 발표를 해보았고 사람들의 의견을 들을 수 있었다.&lt;br /&gt;&amp;nbsp;&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=X5vyiEyg_Gs&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/LYxsF/hyWrKGP6nR/0dPzMQGEacGsPVAKUhlFPk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&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;라이트닝토크 - API 유연하게 설계하기&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/X5vyiEyg_Gs&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;&amp;nbsp;&lt;br /&gt;위는 내가&lt;b&gt; 'API 유연하기 설계하기'&lt;/b&gt; 에 대해 &lt;span style=&quot;color: #333333;&quot;&gt;발표한&amp;nbsp;영상이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;솔직히 여기 올리는게 부끄럽지만 성장하는 과정이니 귀엽게 봐줬으면 좋겠다..ㅋㅋ&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 내용에 대한 사람들의 답변을 들으면서 유연하게 설계하는 것도 좋지만 클라이언트를 너무 믿지 않아야한다는 점도 생각하게 되었다.&lt;br /&gt;&lt;br /&gt;만약 내가 조회할 데이터의 수 등을 클라이언트가 설정할 수 있도록 열어준다면 비슷한 API를 여러개 만들 지 않고 재활용할 수 있을 것이다.&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;br /&gt;나는 유연한 설계에 대한 고민에 빠져서 이 부분은 생각하지 못했지만 라이트닝 토크를 통해 좁아졌던 시야를 넓힐 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 여러 잡담을 거치고 다양한 의견에 반박도 당해보는 과정에서, 스스로 생각할 때도 다양한 관점에서 접근해보아야 더 좋은 프로덕트를 만들어나갈 수 있다는 깨달음도 얻을 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;역시&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;잡담은 경쟁력&quot;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;리뷰어와 많은 대화  &lt;/b&gt;&lt;/h4&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;1036&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lHsg7/btsIieCg1cP/hfJMASAPv8s6w1BBwloyXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lHsg7/btsIieCg1cP/hfJMASAPv8s6w1BBwloyXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lHsg7/btsIieCg1cP/hfJMASAPv8s6w1BBwloyXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlHsg7%2FbtsIieCg1cP%2FhfJMASAPv8s6w1BBwloyXk%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;536&quot; height=&quot;86&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;레벨1에서 아쉬운 점을 꼽으라면 리뷰어와 의미있는 대화를 많이 하지 못한 점이다.&lt;br /&gt;내가 적극적으로 주도해서 질문하지 못했었고, 어떤 질문을 해야하는 지도 제대로 떠올리지 못했다.&lt;br /&gt;또 한편으로는 현업자인 리뷰어에게 내 코드를 보여주는 것이 부끄럽기도 무섭기도 했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 레벨2에서는 보다 열심히 질문하고 의문을 가지려고 노력했다.&lt;br /&gt;&lt;b&gt;'2. 좋은 질문 많이하기'&lt;/b&gt; 에서 말했던 것처럼 개발하면서 스쳐지나가는 의문을 놓치지 않고 궁금증으로 이어나갔다.&lt;br /&gt;&amp;nbsp;&lt;br /&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;1484&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nlw0N/btsIkcwxiIx/7PfikEsKy5kV8ziLd4jp1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nlw0N/btsIkcwxiIx/7PfikEsKy5kV8ziLd4jp1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nlw0N/btsIkcwxiIx/7PfikEsKy5kV8ziLd4jp1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnlw0N%2FbtsIkcwxiIx%2F7PfikEsKy5kV8ziLd4jp1k%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;637&quot; height=&quot;180&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;그리고 혼자만으로 해결하지 못한 문제들을 리뷰어에게 질문했고, 좋은 답변들을 받을 수 있었다.&lt;br /&gt;그 과정에서 필요이상의 내용을 과하게 학습하는 실수도 있었지만, 레벨1에서는 받지 못했던 &quot;의미있는 질문들을 했다&quot;라는 리뷰어들의 피드백을 보면서 성장을 느꼈으며 꽤나 뿌듯해했던 기억이 난다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  어떤 성장을 이루었는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 레벨2에서 가장 성장했다고 할 법한 부분은 &lt;b&gt;'내면의 유연성' &lt;/b&gt;과 &lt;b&gt;'학습 방식'&lt;/b&gt; 이었다고 생각한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;'유연성' 이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고통스럽다고 느끼지 않고 그런 상황에서 유연하게 대처할 수 있게 만들 수 있는 스킬이다.&lt;br /&gt;고통스러운 상황을 이겨내고 그 상황으로부터 무언가를 배우는 것이 목표이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;각 레벨마다 유연성 강화 목표를 설정하는데&lt;br /&gt;레벨2에서 나의 목표는 &lt;b&gt;'나에 대한 메타인지 기르기'&lt;/b&gt; 였다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나에 대한 메타인지 기르기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이런 목표를 설정한 것은 큰 이유가 있다.&lt;br /&gt;나는 남들이 공부했다고 하는 것들에 자극을 많이 받았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;누군가 A를 학습했다고 하고, 누구는 B를 학습했다고 얘기하면&lt;br /&gt;나는 &quot;나도 사람들을 따라잡으려면 저것들을 학습해야하는데&quot; 라는 생각이 들어서 학습목표가 점점 넓어지고&lt;br /&gt;그렇게 넓어지는 학습 목표를 보며 의욕이 떨어지고 자신감이 줄어들던 사람이었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이를 해결하기 위해 레벨1에서는 페이스 조절을 목표로 생활하였고 &lt;a title=&quot;조급한 마음을 느슨하게 풀어가는 과정&quot; href=&quot;https://github.com/PgmJun/woowa-writing/blob/level2/level1.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;조급한 마음을 느슨하게 풀어가는 과정&lt;/span&gt;&lt;/a&gt;이라는 돌아보기 글을 작성했었다.&lt;br /&gt;그리고 레벨2에서는 개발이라는 행위 자체를 즐기던 내가 언제부터 이렇게 학습에 대한 강박과 조급한 마음을 가지게 되었는가를 찾는 근본적인 원인 탐색 시간을 가지게 되었다.&lt;br /&gt;나의 인생의 흐름과 나 자신에 대해 이해하는 것이&lt;br /&gt;앞으로 평생 학습을 해야하는 직업인 개발자로 살아가는데에 큰 도움이 될 것이라고 생각했다.&lt;br /&gt;&lt;br /&gt;그렇게 레벨2의 유연성 강화 목표는 자연스레 &lt;b&gt;'나에 대한 메타인지 기르기'&lt;/b&gt;가 되었고 &lt;a href=&quot;https://github.com/PgmJun/woowa-writing/blob/level2/level2.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;그 과정에 대해 돌아보는 글&lt;/span&gt;&lt;/a&gt;도 작성하였다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;일일회고&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;1574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tcucI/btsIkujqNrM/97cAPEGfoauL8nCNxKqxsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tcucI/btsIkujqNrM/97cAPEGfoauL8nCNxKqxsK/img.png&quot; data-alt=&quot;일일 회고 기록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tcucI/btsIkujqNrM/97cAPEGfoauL8nCNxKqxsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtcucI%2FbtsIkujqNrM%2F97cAPEGfoauL8nCNxKqxsK%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;405&quot; height=&quot;575&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;1574&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;일일 회고 기록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 활동을 제대로 하기 위해 레벨2 기간동안 매일 나의 &lt;b&gt;생각/느낀점/감정/배운점&lt;/b&gt; 을 글로 풀어내는 일일회고를 해보았다.&lt;br /&gt;&lt;br /&gt;이 활동을 하게된 이유는 내가 평소에 어떤 생각을 하고 사는지 스스로 캐치하기 위함이다.&lt;br /&gt;생각을 기록해야한다고 인지하고 있으면 어떤 생각이 떠올랐을 때 다시 한 번 곱씹어보게 되었고&lt;br /&gt;기록하고 다시 한 번 흝어보면서 내 솔직한 생각의 흐름들을 파악할 수 있어 유익했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;나는 이러한 유연성 강화 훈련을 통해 나 자신에 대해 약간이나마 이해할 수 있었고, 이로인해 개발에 임하는 자세 자체가 많이 변했다.&lt;br /&gt;이제는 남들의 시선에 휘둘리지 않고 부족함을 인정하고 천천히 나아가려는 태도를 갖게 되었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;남들이 보기에는 잘못된 주장일 수도 있어 부끄럽지만 내 라이트닝 토크 영상을 여기 올린 것도 내 자세가 개선되었음을 반증하는 것이라 생각한다. 예전의 나였다면 올리지 않고 숨겼을 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 방식 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전의 나는 문제를 해결할 때, 어느정도 해결이 되었다면 그냥 넘어가는 학습방식을 가졌었다.&lt;br /&gt;왜그랬는지 생각해보니 공부라는 것에 대해 '조급함'이 너무 커서 그랬다.&lt;br /&gt;남들은 이미 할 줄 아는 것들을 쩔쩔매며 공부하는 내가 부족해보였고 싫었던 것 같다.&lt;br /&gt;이러한 생각 때문에 질문하는 것도 부끄럽고 어려워했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;다시 생각해보면 남들은 쩔쩔매며 공부해서 습득한 지식일텐데 나는 왜 쩔쩔매는 것이 스스로 부끄럽고 부족해보였나 싶다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아무튼, 그랬던 나는 유연성 강화 훈련을 거치며 조급한 마음이 덜어져서인지 더는 그렇게 학습하지 않는다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;개발을 하다가 모르는 부분이 나오면 원인을 생각해본 뒤에, &lt;br /&gt;구글에 마주한 상황을 &lt;span style=&quot;color: #333333;&quot;&gt;영문으로&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;검색하고 여러 래퍼런스, 공식문서들을 참조한다.&lt;br /&gt;그리고 글을 아주 천천히 읽어본다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;영문으로 검색하는 이유는 보통 공식문서가 영어이고, 외국 자료가 훨씬 양질이라고 느꼈기 때문이다. (ex. Baeldung)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이정도로 아주 작은 개선만 했는데 정말 큰 변화가 있었다.&lt;br /&gt;찾은 래퍼런스들을 급하지 않게 천천히 읽으니 웬만한 내용은 다 적혀있었다.&lt;br /&gt;급하게 찾을 때는 쓸만한 참고자료가 거의 없다고 느꼈는데 사실은 내 문제였다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;우테코에 오지 않았더라면 이렇게 될 수 없지 않았을까?? 라고 나는 항상 생각한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레벨2를 떠나보내며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;레벨2를 떠나보내기에는 솔직히 아쉬운 점이 많이 떠오른다.&lt;br /&gt;&lt;b&gt;&quot;스프링 스터디라도 하면서 책 한 권이라도 읽을걸..&quot;, &quot;레벨1때 자바 공부 더 할 걸..&quot;, &quot;사람들이랑 좀 더 많은 대화를 나눠볼걸..&quot;&lt;/b&gt; 등등 많은 후회가 든다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 이러한 아쉬움보다 얻어간 것이 더 많다. 우테코에서 만난 소중한 인연들, 내면의 유연성, 앞으로 어떻게 학습해나가야하는지 등등&lt;br /&gt;&amp;nbsp;&lt;br /&gt;또 우테코의 레벨은 아직 3개나 남았고 이 아쉬움을 떨쳐낼 충분한 시간이 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;깨달은 부족한 점 아쉬운 점은 남은 기간동안 채워나가고&lt;br /&gt;배운 점들은 이 회고를 계속 돌아보며 잊지않고 습관화해나가고자 한다.&lt;br /&gt;&lt;br /&gt;내일부터 시작되는 레벨3는 같은 아쉬움이 남지 않기를 바라며 글을 마친다.&lt;/p&gt;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우아한테크코스</category>
      <category>우테코 6기 백엔드</category>
      <category>우테코 6기 회고</category>
      <category>우테코 회고</category>
      <category>우테코 후기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/167</guid>
      <comments>https://pgmjun.tistory.com/167#entry167comment</comments>
      <pubDate>Mon, 1 Jul 2024 23:52:52 +0900</pubDate>
    </item>
    <item>
      <title>Spring @Sql 애노테이션의 동작 원리와 트러블슈팅 기록</title>
      <link>https://pgmjun.tistory.com/166</link>
      <description>&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;225&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvs3dA/btsH9J8QAPo/p9gyZVej8K9dfi5qoyThw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvs3dA/btsH9J8QAPo/p9gyZVej8K9dfi5qoyThw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvs3dA/btsH9J8QAPo/p9gyZVej8K9dfi5qoyThw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvs3dA%2FbtsH9J8QAPo%2Fp9gyZVej8K9dfi5qoyThw1%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;298&quot; height=&quot;298&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&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;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;h1 style=&quot;text-align: var(--header-title-align);&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;우아한테크코스 레벨2에서 스프링부트를 활용한 미션을 수행하면서, 테스트 코드를 어느때보다 열심히 작성하고 있는 것 같다.&lt;/span&gt;&lt;/h1&gt;
&lt;/div&gt;
&lt;p id=&quot;block-b8f7987091904434b6e45b8ab1fe86cb&quot; data-ke-size=&quot;size16&quot;&gt;하지만 테스트 코드에서 &lt;b&gt;@Sql&lt;/b&gt; 을 사용하는 과정에서 문제를 마주하게 되었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;@Sql&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-b3e2a22f47374cb997215ec551aab36b&quot; data-ke-size=&quot;size26&quot;&gt;@Sql 애노테이션이란?&lt;/h2&gt;
&lt;p id=&quot;block-8cd567c826444a3795b4bf40701ddd6a&quot; data-ke-size=&quot;size16&quot;&gt;마주한 문제에 대해 알아보기 전에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Sql 애노테이션이 무엇인지부터 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-31ba21d91b5a474eb425b203780c9658&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A4MCM/btsH8iLSMoG/HilNffvwoAy27ztc9MRxk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A4MCM/btsH8iLSMoG/HilNffvwoAy27ztc9MRxk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A4MCM/btsH8iLSMoG/HilNffvwoAy27ztc9MRxk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA4MCM%2FbtsH8iLSMoG%2FHilNffvwoAy27ztc9MRxk1%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;2100&quot; height=&quot;478&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-3dd5b807569e4820bcab92e5b0f2e900&quot; data-ke-size=&quot;size16&quot;&gt;정확한 설명을 위해 &lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/jdbc/Sql.html&quot;&gt;공식문서&lt;/a&gt;의 말을 빌려보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-cd99e2e12f814a95b3a73150bf7ee240&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lsquo;@Sql 애노테이션은 통합 테스트 환경에서 주어진 데이터베이스에 실행시키기 위한 SQL의 scripts 또는 statements를 구성하기 위한 목적으로 테스트 클래스 또는 테스트 메서드에 붙여주는 애노테이션&amp;rsquo; &lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-4a67306b97d54892948bae0948f8e669&quot; data-ke-size=&quot;size16&quot;&gt;간단히 &lt;b&gt;@Sql&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-2df61fd445f44567952f42b1b7abe95c&quot; data-ke-size=&quot;size26&quot;&gt;마주한 문제 상황&lt;/h2&gt;
&lt;pre id=&quot;code_1719060476243&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@JdbcTest
@Sql(scripts = &quot;/truncate.sql&quot;, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
class ThemeRepositoryTest { 
   
    //...
   
    @Test
    @DisplayName(&quot;예약 수 상위 10개 테마를 조회했을 때 내림차순으로 정렬된다. 만약 예약 수가 같다면, id 순으로 오름차순 정렬된다.&quot;)
    @Sql(scripts = &quot;/reservationData.sql&quot;, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
    void readTop10ThemesDescOrder() {&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;block-0ceb11aabadc4e4c8ec9e50dbb07a09f&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p id=&quot;block-363f57491b4c47a58e72d5b089f03b03&quot; data-ke-size=&quot;size16&quot;&gt;나는 &lt;b&gt;RepositoryTest&lt;/b&gt; 에서 클래스 레벨에 &lt;b&gt;@Sql&lt;/b&gt; 애노테이션을 선언하여&amp;nbsp;&lt;b&gt;truncate.sql&lt;/b&gt; 을 실행시켜 DB를 초기화하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 레벨에 선언한 &lt;b&gt;reservationData.sql&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 id=&quot;block-6e7198c97a9c4e83ad64e11c0930bc5c&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;reservationData.sql&lt;/b&gt;은 수행되지만&lt;b&gt; truncate.sql &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_1719061045775&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@JdbcTest
@Sql(scripts = &quot;/truncate.sql&quot;, executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
class ThemeRepositoryTest { 
   
    //...
   
    @Test
    @DisplayName(&quot;예약 수 상위 10개 테마를 조회했을 때 내림차순으로 정렬된다. 만약 예약 수가 같다면, id 순으로 오름차순 정렬된다.&quot;)
    @Sql(scripts = &quot;/reservationData.sql&quot;, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
    void readTop10ThemesDescOrder() {&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;block-faf1059339414111b3fea78ebd21d582&quot; data-ke-size=&quot;size16&quot;&gt;이때 truncate.sql을 &lt;b&gt;ExecutionPhase.AFTER_TEST_METHOD&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-039e66e3a2b24268b7cd83bbb1c6d075&quot; data-ke-size=&quot;size26&quot;&gt;원인 분석&lt;/h2&gt;
&lt;p id=&quot;block-76480e71db704169829a47a8fb76be64&quot; 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;div id=&quot;block-59043867b241451e98d3e8643844e01f&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct2d8t/btsH8GFnY2r/T44Nr9vQa2IHk92LXkLK60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct2d8t/btsH8GFnY2r/T44Nr9vQa2IHk92LXkLK60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct2d8t/btsH8GFnY2r/T44Nr9vQa2IHk92LXkLK60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct2d8t%2FbtsH8GFnY2r%2FT44Nr9vQa2IHk92LXkLK60%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;734.0057&quot; height=&quot;229.48224&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-fd15db3fe6e54662b6043e8471eeb393&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Sql&lt;/b&gt; 애노테이션은 &lt;b&gt;클래스 레벨 + 메서드 레벨&lt;/b&gt; 모두 선언되어 있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'메서드 레벨 설정'이 '클래스 레벨 설정'을 오버라이드하여, 메서드 레벨에 명시된 sql 파일만 수행된다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-225d45badd5345da875a4e7250fe17fe&quot; data-ke-size=&quot;size16&quot;&gt;때문에 truncate.sql과 reservationData.sql 실행을 모두 &lt;b&gt;BEFORE_TEST_METHOD&lt;/b&gt;로 설정해두면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드에 선언된 @Sql 애노테이션이 오버라이드되면서 reservationData.sql 만 실행시킨 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 이때 truncate.sql을 &lt;b&gt;AFTER_TEST_METHOD&lt;/b&gt;로 설정하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 시점(AFTER_TEST, BEFORE_TEST) 의 설정이기 때문에 오버라이드가 되지 않아서 문제가 해결되었던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;하지만 DB 초기화는 테스트 실행 전에 수행시켜주는 것이 훨씬 안정성있기 때문에&lt;br /&gt;truncate.sql과 reservationData.sql을 &lt;b&gt;BEFORE_TEST&lt;/b&gt; 시점에 한 번에 실행시킬 방법을 찾기로 했다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1719060506100&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;Executing SQL Scripts :: Spring Framework&quot; data-og-description=&quot;In addition to the aforementioned mechanisms for running SQL scripts programmatically, you can declaratively configure SQL scripts in the Spring TestContext Framework. Specifically, you can declare the @Sql annotation on a test class or test method to conf&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/executing-sql.html#testcontext-executing-sql-declaratively&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/executing-sql.html#testcontext-executing-sql-declaratively&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/executing-sql.html#testcontext-executing-sql-declaratively&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/executing-sql.html#testcontext-executing-sql-declaratively&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;Executing SQL Scripts :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In addition to the aforementioned mechanisms for running SQL scripts programmatically, you can declaratively configure SQL scripts in the Spring TestContext Framework. Specifically, you can declare the @Sql annotation on a test class or test method to conf&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-b5611fc2b84f4cc6a6d710d490df849a&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h2 id=&quot;block-29e5c4881d1b4e2d98eeab5fb5cb1d25&quot; data-ke-size=&quot;size26&quot;&gt;해결 방안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-6a1c43f8aa684b1191502e82cf59d45f&quot; data-ke-size=&quot;size23&quot;&gt;1. Mulitple scripts 사용&lt;/h3&gt;
&lt;div id=&quot;block-91df3573b68642e096843f81030ae3fa&quot;&gt;
&lt;pre id=&quot;code_1719060549800&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@JdbcTest
@Sql(scripts = &quot;/truncate.sql&quot;, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
class ThemeRepositoryTest { 
    
    //...
    
    @Test
    @DisplayName(&quot;예약 수 상위 10개 테마를 조회했을 때 내림차순으로 정렬된다. 만약 예약 수가 같다면, id 순으로 오름차순 정렬된다.&quot;)
    @Sql(scripts = {&quot;/truncate.sql&quot;, &quot;/reservationData.sql&quot;}, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
    void readTop10ThemesDescOrder() {​&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p id=&quot;block-02a16aff5a594d2dba4401effc8522ae&quot; data-ke-size=&quot;size16&quot;&gt;같은 시점에 &lt;b&gt;truncate.sql&lt;/b&gt; 외에 다른 쿼리를 가지는 메서드에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 &lt;b&gt;@Sql&lt;/b&gt; 애노테이션 내에 &lt;b&gt;truncate.sql&lt;/b&gt;을 함께 추가해주면 오버라이드로 인해&amp;nbsp;&lt;b&gt;reservationData.sql&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;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-d21b6913cef6423d8b3958eae95d9bfb&quot; data-ke-size=&quot;size23&quot;&gt;2. @SqlGroup 애노테이션 사용&lt;/h3&gt;
&lt;div id=&quot;block-9beec788e8744ea4a4991fe9317e74ee&quot;&gt;
&lt;pre id=&quot;code_1719060635185&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@JdbcTest
@Sql(scripts = &quot;/truncate.sql&quot;, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
class ThemeRepositoryTest { 
    
    //...
        
    @Test
    @DisplayName(&quot;예약 수 상위 10개 테마를 조회했을 때 내림차순으로 정렬된다. 만약 예약 수가 같다면, id 순으로 오름차순 정렬된다.&quot;)
    @SqlGroup({
            @Sql(scripts = {&quot;/truncate.sql&quot;}, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD),
            @Sql(scripts = {&quot;/reservationData.sql&quot;}, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
    })
    void readTop10ThemesDescOrder() {​&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p id=&quot;block-8801f817b6bf479f9c05a7a96467da6e&quot; data-ke-size=&quot;size16&quot;&gt;또다른 방법으로는 &lt;b&gt;@SqlGroup &lt;/b&gt;을 사용하는 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@SqlGroup &lt;/b&gt;을 사용하면 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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-26e496a100a84dca94889919031e3c3e&quot; data-ke-size=&quot;size26&quot;&gt;@Sql 애노테이션에 선언한 scripts 실행 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 truncate.sql과 reservationData.sql 을 같은 시점인 BEFORE_TEST에 실행시킬 수 있게 되었다.&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;truncate.sql을 통해 DB가 truncate된 후에 reservationData.sql를 실행시켜서 데이터를 초기화시켜주어야 하는데,&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;때문에 @Sql 애노테이션에 선언한 스크립트의 실행 순서를 알아보기로 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-b11a154f1ecb478486c623e582734446&quot; data-ke-size=&quot;size16&quot;&gt;우선위 &lt;b&gt;@Sql&lt;/b&gt; 애노테이션의 'scripts' 속성에 선언된 스크립트는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트가 선언된 순서대로 실행될 것이라는 믿음 아래서 작성된 코드이다.&lt;/p&gt;
&lt;p id=&quot;block-a6f0be7d58854a73b8922286440f81ad&quot; 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 id=&quot;block-72ddaecefc8f47e492f921f8b5406846&quot; 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 id=&quot;block-656b9f8a3e7540ccb3df225d9f28df38&quot; data-ke-size=&quot;size23&quot;&gt;ResourceDatabasePopulator&lt;/h3&gt;
&lt;div id=&quot;block-98565298e05e435ab22b72d61c9e7822&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D1arD/btsH8SS3MPm/amhhxN1pqOyW5oGD3XOJ11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D1arD/btsH8SS3MPm/amhhxN1pqOyW5oGD3XOJ11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D1arD/btsH8SS3MPm/amhhxN1pqOyW5oGD3XOJ11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD1arD%2FbtsH8SS3MPm%2FamhhxN1pqOyW5oGD3XOJ11%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;432&quot; height=&quot;307.41574&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-d927768eed37484c957cb41db36904d7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Sql &lt;/b&gt;애노테이션은 org.springframework.jdbc 에 선언이 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-5d53f7294fb64588840ddbaee81083df&quot; data-ke-size=&quot;size16&quot;&gt;해당 패키지에 들어가보니 @Sql 애노테이션과 더불어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Sql 애노테이션의 스크립트 실행 리스너인 &lt;b&gt;SqlScriptsTestExecutionListener &lt;/b&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 id=&quot;block-b5b115c487b84faf80eb4deb7a1e733d&quot; style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;&lt;b&gt;SqlScriptsTestExecutionListener&lt;/b&gt;에 존재하는 메서드들을 파고 하위로 들어가다보면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;org.springframework.jdbc.datasource.init 에 존재하는&amp;nbsp;&lt;b&gt;ResourceDatabasePopulator&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&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;스크립트의 실행은 정확히 이 객체의 execute() 메서드에서 시작된다.&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;&amp;nbsp;&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;h4 id=&quot;block-1e1873f277624bf6b98b50ef43313535&quot; data-ke-size=&quot;size20&quot;&gt;ResourceDatabasePopulator의 execute() 메서드&lt;/h4&gt;
&lt;div id=&quot;block-aa291a67a78146c58749d90df37b58f4&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b08t3i/btsH9rN9wum/lZgnZZtateHAhKx5mtjSp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b08t3i/btsH9rN9wum/lZgnZZtateHAhKx5mtjSp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b08t3i/btsH9rN9wum/lZgnZZtateHAhKx5mtjSp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb08t3i%2FbtsH9rN9wum%2FlZgnZZtateHAhKx5mtjSp0%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;734.0057&quot; height=&quot;110.5671&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-92c8af85d26743e18555159f7d9afaff&quot; data-ke-size=&quot;size16&quot;&gt;해당 객체는 execute() 라는 메서드를 통해 자기 자신(ResourceDatabasePopulator)과 dataSource를 넘겨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DatabasePopulatorUtils&lt;/b&gt;의 메서드 execute()를 호출한다.&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;h4 id=&quot;block-5f916d6f8db5493fa3d590b3fe828d65&quot; data-ke-size=&quot;size20&quot;&gt;DatabasePopulatorUtils의 populate(), execute() 메서드&lt;/h4&gt;
&lt;div id=&quot;block-3704eae70ddc4109b463fbea20877d59&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baIKB2/btsH92G7kWi/f7RLgAxWAviuaUzNTtN27k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baIKB2/btsH92G7kWi/f7RLgAxWAviuaUzNTtN27k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baIKB2/btsH92G7kWi/f7RLgAxWAviuaUzNTtN27k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaIKB2%2FbtsH92G7kWi%2Ff7RLgAxWAviuaUzNTtN27k%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;1832&quot; height=&quot;690&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-a728bcb66cbd4298bcd37a6ce6a835ed&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DatabasePopulatorUtils &lt;/b&gt;의 execute() 메서드는, try/catch 구문 내에서 매개변수로 전달받은 dataSource를 사용하여&amp;nbsp;&lt;b&gt;DataSourceUtils &lt;/b&gt;로부터 커넥션을 얻어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-4504d4a3617d4b5aac2957788a4bc05c&quot; data-ke-size=&quot;size16&quot;&gt;그리고 그 connection을 매개변수로 담아&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;execute() 메서드의 호출자인 &lt;b&gt;ResourceDatabasePopulator&lt;/b&gt;의 populate() 메서드를 호출한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;바로 아래에서 살펴볼 예정이지만 살짝 설명하자면 populate()는 script를 실행시켜 DB에 쿼리를 날리는 메서드이다.&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;span&gt;이때 populate() 메서드가 예외 발생없이 정상적으로 수행되는 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;만약 connection이 autoCommit 상태가 아니고, 트랜잭션을 타고있지 않다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 connection을 직접 commit한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;block-1cff6e15487c45fa85a968d7810ec51d&quot; data-ke-size=&quot;size20&quot;&gt;ResourceDatabasePopulator의 populate() 메서드&lt;/h4&gt;
&lt;div id=&quot;block-115ed416358a443dad3682f68b5c570c&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tixTd/btsH8kpdprE/qw9SbsmPfHrB9B9NwbloAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tixTd/btsH8kpdprE/qw9SbsmPfHrB9B9NwbloAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tixTd/btsH8kpdprE/qw9SbsmPfHrB9B9NwbloAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtixTd%2FbtsH8kpdprE%2Fqw9SbsmPfHrB9B9NwbloAK%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;1944&quot; height=&quot;506&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-f98a3e75d40a4a7d9d8cafbae4325a66&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DatabasePopulatorUtils &lt;/b&gt;의 execute() 에서 호출한 ResourceDatabasePopulator의 &lt;b&gt;populate()&lt;/b&gt; 메서드이다.&lt;/p&gt;
&lt;p id=&quot;block-00d210b281f34737933a785b920a801a&quot; data-ke-size=&quot;size16&quot;&gt;코드를 살펴보면 String[] scripts의 Iterator를 얻어 순차적으로 쿼리를 실행시키는 것을 확인할 수 있었다.&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;이 scripts는 우리가 @Sql 애노테이션의 scripts 속성에 정의한 바로 그 스크립트이며,&lt;br /&gt;입력 순서대로 scripts String배열에 담겨있다가 이 시점에 하나씩 순서대로 실행되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;block-0cc3d95ef13645b4bf971610af29fc9d&quot; data-ke-size=&quot;size20&quot;&gt;ScriptUtils의 executeSqlScript() 메서드&lt;/h4&gt;
&lt;div id=&quot;block-be7aaed63cb146198d88bf0c0949b93b&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-22 23.02.47.png&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v5s8r/btsH9KNtJ4T/Fl3oc8h3OkVKEZtKKuGyQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v5s8r/btsH9KNtJ4T/Fl3oc8h3OkVKEZtKKuGyQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v5s8r/btsH9KNtJ4T/Fl3oc8h3OkVKEZtKKuGyQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv5s8r%2FbtsH9KNtJ4T%2FFl3oc8h3OkVKEZtKKuGyQk%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;2040&quot; height=&quot;684&quot; data-filename=&quot;스크린샷 2024-06-22 23.02.47.png&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;684&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 id=&quot;block-1c1b614cc6d04552acb582ca32dce37f&quot; data-ke-size=&quot;size16&quot;&gt;executeSqlScript() 메서드는 내부적으로 Statement를 생성하여, script에 담긴 쿼리를 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-13a1bc28eb90486d8f007236fdf3b9dd&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;테스트에서 Multiple Scripts 또는 @SqlGroup 애노테이션을 통해 여러 sql을 같은 시점에 실행시킬 수 있다.&lt;/li&gt;
&lt;li&gt;실행하고자 입력한 Sql 스크립트는 입력해준 순서대로 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>@sql</category>
      <category>@sql 동작</category>
      <category>@sql 스프링부트</category>
      <category>@sql 애노테이션</category>
      <category>@sql 애노테이션이란</category>
      <category>jdbctest</category>
      <category>spring @sql</category>
      <category>springboot @sql</category>
      <category>스프링 테스트코드 sql</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/166</guid>
      <comments>https://pgmjun.tistory.com/166#entry166comment</comments>
      <pubDate>Sat, 22 Jun 2024 23:05:57 +0900</pubDate>
    </item>
    <item>
      <title>Springboot Toss 결제 API 연동</title>
      <link>https://pgmjun.tistory.com/165</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUMJgd/btsH83Uind9/uGvJWtpTlvPg5SIOxc3W1k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUMJgd/btsH83Uind9/uGvJWtpTlvPg5SIOxc3W1k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUMJgd/btsH83Uind9/uGvJWtpTlvPg5SIOxc3W1k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUMJgd%2FbtsH83Uind9%2FuGvJWtpTlvPg5SIOxc3W1k%2Fimg.jpg&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;372&quot; height=&quot;372&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;외부 API 연동하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우테코에서 방탈출 미션을 수행하면서, 방탈출 예약 결제 처리에 Toss 결제 API 를 연동하게 되었다.&lt;br&gt;방탈출 미션은 방탈출 예약 웹 서비스를 만드는 미션으로 &lt;b&gt;JS/CSS/HTTP 기반 웹 클라이언트 &lt;/b&gt;와 &lt;b&gt;Springboot/Java 기반 백엔드&lt;/b&gt;를 함께 개발한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만 백엔드에서 고려할 부분에 대한 흐름을 기록하고싶기 때문에&lt;br&gt;이번 내용은 클라이언트 측이 아닌 백엔드 측의 기준으로 설명한다.&lt;br&gt;또한 Toss API 연결이라는 하나의 작업보다는 &lt;b&gt;외부 API 연결이라는 큰 틀에 집중&lt;/b&gt;해보았다는 점을 참고해서 글을 읽으면 도움이 될 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;또한 이번에 다루는 내용은 실제 결제까지의 과정이 아니라 학습을 위한 과정이기 때문에&lt;br&gt;토스에서 지원하는 '테스트'용 키값 등을 사용했다는 점도 참고바란다.&lt;br&gt;(실제 결제까지 연결하려면 사업자등록부터 꽤나 복잡한 것으로 알고 있다.)&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Toss 공식 문서&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사실 내가 어떻게 쓰든간에 중요한건 공식문서니까 맨 위에 첨부하고 글을 시작한다.&lt;br&gt;여태껏 보았던 문서 중 가장 친절한 문서인 것 같다.&lt;br&gt;문서보는게 편한 사람이라면 이 글을 통해서는 외부 API 연결 시 고려해야하는 부분들에 대해서만 구경하고 가면 좋을 것 같다 :)&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-595b71548c5a4baca972dcd19bd37c97&quot;&gt; 
 &lt;figure id=&quot;og_1718991246939&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;연동하기 | 토스페이먼츠 개발자센터&quot; data-og-description=&quot;토스페이먼츠의 간편한 결제 연동 과정을 한눈에 볼 수 있습니다. 각 단계별 설명과 함께 달라지는 UI와 코드를 확인해보세요.&quot; data-og-host=&quot;docs.tosspayments.com&quot; data-og-source-url=&quot;https://docs.tosspayments.com/guides/payment-widget/integration&quot; data-og-url=&quot;https://docs.tosspayments.com/guides/payment-widget/integration&quot; data-og-image=&quot;&quot;&gt; &lt;a href=&quot;https://docs.tosspayments.com/guides/payment-widget/integration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.tosspayments.com/guides/payment-widget/integration&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;연동하기 | 토스페이먼츠 개발자센터&lt;/p&gt; 
    &lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;토스페이먼츠의 간편한 결제 연동 과정을 한눈에 볼 수 있습니다. 각 단계별 설명과 함께 달라지는 UI와 코드를 확인해보세요.&lt;/p&gt; 
    &lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.tosspayments.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;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Toss 결제 API Flow&lt;/h2&gt;&lt;div id=&quot;block-027c5faa658b4e3ab51c3056cf67c787&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;947&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqQf7A/btsH7Zk2TbI/Fwv0zLKJkfMhzn284Zpb6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqQf7A/btsH7Zk2TbI/Fwv0zLKJkfMhzn284Zpb6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqQf7A/btsH7Zk2TbI/Fwv0zLKJkfMhzn284Zpb6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqQf7A%2FbtsH7Zk2TbI%2FFwv0zLKJkfMhzn284Zpb6k%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;576&quot; height=&quot;426.58218&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;947&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Toss 결제는 어떤 플로우로 진행되는 지 알아보자&lt;br&gt;위 이미지는 결제 플로우이며, 우리는 &lt;a href=&quot;https://docs.tosspayments.com/reference/widget-sdk#promise%EB%A1%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Promise 방식&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;을 사용하기 때문에 successUrl, failUrl 이 없다는 점에서 흐름이 조금 다르다는 점을 인지하고 보자.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;[참고사항] 방탈출 예약 서비스는 웹 서비스이기 때문에 Promise 방식을 사용할 수 있지만, 앱이라면 Promise 방식 사용이 불가능하다.&lt;/blockquote&gt;&lt;div id=&quot;block-b284e638d80e4be2a1e7b40e02b0a876&quot; style=&quot;background-color: #000000;&quot;&gt;
  &amp;nbsp; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;0. 상품 선택&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2666&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ERQnL/btsH84FE3rj/vwMknxBPMuGOKoZpIAMzE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ERQnL/btsH84FE3rj/vwMknxBPMuGOKoZpIAMzE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ERQnL/btsH84FE3rj/vwMknxBPMuGOKoZpIAMzE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FERQnL%2FbtsH84FE3rj%2FvwMknxBPMuGOKoZpIAMzE0%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;2666&quot; height=&quot;1022&quot; data-origin-width=&quot;2666&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방탈출 웹사이트에서 원하는 상품을 선택하고 결제버튼을 클릭한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 결제위젯 렌더 요청&lt;/h3&gt;&lt;div id=&quot;block-401a892777b0453f99e01f9d5a5b1789&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mkMgf/btsH7Dbuf23/4ZBR6fSYMgqJd30oaBKm01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mkMgf/btsH7Dbuf23/4ZBR6fSYMgqJd30oaBKm01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mkMgf/btsH7Dbuf23/4ZBR6fSYMgqJd30oaBKm01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmkMgf%2FbtsH7Dbuf23%2F4ZBR6fSYMgqJd30oaBKm01%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;240&quot; height=&quot;265.62277&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;Toss 결제 연동의 첫 단추는 Toss 공식문서에서 제공하는 결제위젯을 클라이언트 코드에 추가하는 것이다.&lt;br&gt;해당 코드가 존재하는 페이지가 열리면, 클라이언트는 토스에 결제위젯 렌더 요청을 보내어 위와 같은 화면을 띄워준다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 결제 요청 호출&lt;/h3&gt;&lt;div id=&quot;block-9261fa1bc98d42448e9ed81db5051f05&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drM1cE/btsH8IQq8cU/ZhxFTtzbhqFH3QVotexea1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drM1cE/btsH8IQq8cU/ZhxFTtzbhqFH3QVotexea1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drM1cE/btsH8IQq8cU/ZhxFTtzbhqFH3QVotexea1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrM1cE%2FbtsH8IQq8cU%2FZhxFTtzbhqFH3QVotexea1%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;240&quot; height=&quot;283.79562&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1513&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;결제 수단을 선택하고 나면 위와 같은 화면으로 넘어온다.&lt;br&gt;여기서 화면 하단에 보이는 ‘동의하고 결제하기’ 버튼을 클릭하면 ‘Toss 결제요청 API’ 를 호출한다.&lt;br&gt;‘결제 요청’ 은 토스 서버에 현재 수행하려하는 결제에 대한 정보를 등록하는 과정이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-94c9cadca2f74e788a253f3e54575cc4&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZtig3/btsH9EM4abB/w2Ui26ufuED3c0XRoZLlrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZtig3/btsH9EM4abB/w2Ui26ufuED3c0XRoZLlrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZtig3/btsH9EM4abB/w2Ui26ufuED3c0XRoZLlrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZtig3%2FbtsH9EM4abB%2Fw2Ui26ufuED3c0XRoZLlrk%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;576&quot; height=&quot;120.11375&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;현재 서버에서는 orderId, orderName, amount 를 통해 결제 요청을 호출하여 Toss에 해당 결제정보를 저장한다.&lt;br&gt;결제 요청은 PaymentWidget의 requestPayment() 메서드를 통해 수행할 수 있는데, 각각의 파라미터는 아래와 같은 역할을 갖는다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;orderId&lt;/b&gt;: 주문을 구분하는 ID이다. 충분히 무작위한 값을 생성해서 각 주문마다 고유한 값을 넣어야한다. 값은 영문 대소문자, 숫자, 특수문자&amp;nbsp;-,&amp;nbsp;_,&amp;nbsp;=로 이루어진 6자 이상 64자 이하의 문자열이어야 한다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;orderName:&lt;/b&gt; 구매상품명이다. 예를 들면&amp;nbsp;생수 외 1건&amp;nbsp;같은 형식입니다. 최대 길이는 100자입니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;amount:&lt;/b&gt; 상품의 가격이다.&lt;/li&gt;&lt;/ul&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 외에 &lt;b&gt;더 많은 파라미터&lt;/b&gt;가 존재하는데 &lt;a href=&quot;https://docs.tosspayments.com/reference/widget-sdk#requestpayment%EA%B2%B0%EC%A0%9C-%EC%A0%95%EB%B3%B4&quot; target=&quot;_self&quot;&gt;&lt;span&gt;해당 문서&lt;/span&gt;&lt;/a&gt;에서 확인할 수 있다.&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;requestPayment()&lt;/b&gt; 메서드는 응답을 리다이렉트URL 또는 Promise 형식으로 반환받을 수 있다. 관련 내용은 &lt;a href=&quot;https://docs.tosspayments.com/reference/widget-sdk#%EA%B2%B0%EC%A0%9C-%EC%9A%94%EC%B2%AD-%EC%9D%91%EB%8B%B5-%EC%B2%98%EB%A6%AC&quot; target=&quot;_self&quot;&gt;&lt;span&gt;해당 문서&lt;/span&gt;&lt;/a&gt;를 참고하자.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;어찌되었건 위 과정을 거쳐 ‘결제 요청’ 에 성공하면 클라이언트는 토스 서버로부터 paymentType,&amp;nbsp;paymentKey,&amp;nbsp;orderId,&amp;nbsp;amount이 4가지 값을 응답받는다.&lt;br&gt;orderId와 amount는 위에서 설명했으니 나머지에 대해서 설명한다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;paymentKey:&lt;/b&gt; 결제를 식별하는 키 값으로 토스페이먼츠에서 발급한다. 결제에 대한 처리(&lt;a href=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결제 승인&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://docs.tosspayments.com/reference#paymentkey%EB%A1%9C-%EA%B2%B0%EC%A0%9C-%EC%A1%B0%ED%9A%8C&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결제 조회&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%B7%A8%EC%86%8C&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결제 취소&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&amp;nbsp;등)와 운영에 필요한 값이다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;paymentType:&lt;/b&gt; 결제 유형으로&amp;nbsp;NORMAL,&amp;nbsp;BRANDPAY,&amp;nbsp;KEYIN&amp;nbsp;중 하나이다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;결제 요청 실패 시에는 error code 를 응답받는데, 그에 대한 내용은 &lt;a href=&quot;https://docs.tosspayments.com/reference/error-codes#failurl%EB%A1%9C-%EC%A0%84%EB%8B%AC%EB%90%98%EB%8A%94-%EC%97%90%EB%9F%AC&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 문서&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;를 참고하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 결제정보 전달&lt;/h3&gt;&lt;div id=&quot;block-d8ab592705684f9387cebab6fc3b7af1&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0wCYl/btsH8zfaTlz/Fzqq3tU6CK2cLfs04E2Zkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0wCYl/btsH8zfaTlz/Fzqq3tU6CK2cLfs04E2Zkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0wCYl/btsH8zfaTlz/Fzqq3tU6CK2cLfs04E2Zkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0wCYl%2FbtsH8zfaTlz%2FFzqq3tU6CK2cLfs04E2Zkk%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;480&quot; height=&quot;271.65958&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;결제 요청이 정상적으로 완료되어 클라이언트로부터 paymentType,&amp;nbsp;paymentKey,&amp;nbsp;orderId,&amp;nbsp;amount 를 응답받았다면 클라이언트는 결제승인 요청을 위해 우리 서버의 &lt;b&gt;방탈출 예약 API &lt;/b&gt;를 통해 해당 정보와 예약에 필요한 정보를 전달한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 결제 승인 API 호출&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 결제승인 API 호출을 위해 클라이언트로부터 paymentType,&amp;nbsp;paymentKey,&amp;nbsp;orderId,&amp;nbsp;amount 와 그 외 예약 관련 정보들을 전달받았다.&lt;br&gt;서버는 클라이언트로 전달받은 값 중 &lt;b&gt;paymentKey, orderId, amount&lt;/b&gt; 와 토스에서 발급받을 수 있는 &lt;b&gt;SecretKey&lt;/b&gt; 를 통해 토스의 결제승인 API를 호출한다.&lt;br&gt;이때 orderId가 이전에 클라이언트 측에서 보낸 orderId와 동일한 지 비교하고 호출한다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;SecretKey 발급 방법은 &lt;a href=&quot;https://docs.tosspayments.com/reference/using-api/api-keys#%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%ED%82%A4%EC%99%80-%EC%8B%9C%ED%81%AC%EB%A6%BF-%ED%82%A4&quot; target=&quot;_self&quot;&gt;&lt;span&gt;공식문서&lt;/span&gt;&lt;/a&gt;를 참조하자&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-49a4f66690454161b94ac63727bbbb38&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/esmUb0/btsH9rUHesI/9iSm3H25euynemlxKjKg81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/esmUb0/btsH9rUHesI/9iSm3H25euynemlxKjKg81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/esmUb0/btsH9rUHesI/9iSm3H25euynemlxKjKg81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FesmUb0%2FbtsH9rUHesI%2F9iSm3H25euynemlxKjKg81%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;240&quot; height=&quot;261.50537&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;만약 승인에 성공했다면 위와 같은 화면이 클라이언트 측 토스 위젯에 출력될 것이다.&lt;br&gt;하지만 승인을 요청한 사용자나 클라이언트 또는 서버에 모두 문제가 없다면 결제가 완료되고, 문제가 있다면 실패한다.&lt;br&gt;이때 발생할 수 있는 문제들은 &lt;a href=&quot;https://docs.tosspayments.com/reference/using-api/req-res#%EC%97%90%EB%9F%AC-%EA%B0%9D%EC%B2%B4&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 문서&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;를 참고하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;이번 글에서 다룰 내용&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PaymentWidget을 초기화하고 결제요청(PaymentRequest)를 보내서 PaymentKey를 흭득하여 서버로 보내주는 것은 클라이언트의 역할이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;위에서 말했듯이 이번 글에서는 서버의 역할에 대해서만 다룰 예정이기 때문에 사전 설명은 여기까지하고 아래 글에서는 서버 관점에서의 &lt;b&gt;결제승인 API 호출, 결제취소 API 호출 &lt;/b&gt;만을 다루겠다.&lt;br&gt;클라이언트를 포함한 전체 코드가 보고싶다면 깃허브 주소를 올려둘테니 참고하길 바란다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - PgmJun/spring-roomescape-payment&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Contribute to PgmJun/spring-roomescape-payment development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/PgmJun/spring-roomescape-payment/tree/step2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sxktc/hyWoNiSIsv/yncJS6OS3apkVgTzjBgtd1/img.png?width=1200&amp;amp;height=600&amp;amp;face=986_140_1047_207&quot; data-og-url=&quot;https://github.com/PgmJun/spring-roomescape-payment&quot;&gt;&lt;a href=&quot;https://github.com/PgmJun/spring-roomescape-payment&quot; target=&quot;_blank&quot; data-source-url=&quot;https://github.com/PgmJun/spring-roomescape-payment/tree/step2&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sxktc/hyWoNiSIsv/yncJS6OS3apkVgTzjBgtd1/img.png?width=1200&amp;amp;height=600&amp;amp;face=986_140_1047_207')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;GitHub - PgmJun/spring-roomescape-payment&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Contribute to PgmJun/spring-roomescape-payment development by creating an account on GitHub.&lt;/p&gt;&lt;p class=&quot;og-host&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;blockquote data-ke-style=&quot;style3&quot;&gt;코드에 대해 질문이나 의견이 있으시다면 댓글에 남겨주세요!&lt;/blockquote&gt;&lt;div id=&quot;block-d802578c494545679862fb8ff38ee0dc&quot;&gt; &lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/PgmJun/spring-roomescape-payment/tree/step2&quot;&gt; 
  &lt;div&gt;
    &amp;nbsp; 
  &lt;/div&gt; 
  &lt;div&gt;
    &amp;nbsp; 
  &lt;/div&gt; &lt;/a&gt; 
&lt;/div&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;&lt;div id=&quot;block-23a084e883574367b480977a710f86a3&quot;&gt; 
 &lt;figure id=&quot;og_1718989561193&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;코어 API | 토스페이먼츠 개발자센터&quot; data-og-description=&quot;토스페이먼츠 API 엔드포인트(Endpoint)와 객체 정보, 파라미터, 요청 및 응답 예제를 살펴보세요.&quot; data-og-host=&quot;docs.tosspayments.com&quot; data-og-source-url=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8&quot; data-og-url=&quot;https://docs.tosspayments.com/reference&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDmawP/hyWoCuUUnz/cGqJRnGSZbgmkd83uvOf8K/img.png?width=780&amp;amp;height=390&amp;amp;face=0_0_780_390&quot;&gt; &lt;a href=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8&quot;&gt; 
   &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDmawP/hyWoCuUUnz/cGqJRnGSZbgmkd83uvOf8K/img.png?width=780&amp;amp;height=390&amp;amp;face=0_0_780_390');&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;코어 API | 토스페이먼츠 개발자센터&lt;/p&gt; 
    &lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;토스페이먼츠 API 엔드포인트(Endpoint)와 객체 정보, 파라미터, 요청 및 응답 예제를 살펴보세요.&lt;/p&gt; 
    &lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.tosspayments.com&lt;/p&gt; 
   &lt;/div&gt; &lt;/a&gt; 
 &lt;/figure&gt; 
&lt;/div&gt;&lt;div id=&quot;block-aedc492b04984db895edef3b3cff872b&quot;&gt; &lt;a href=&quot;https://docs.tosspayments.com/reference/widget-sdk#promise%EB%A1%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0&quot;&gt; 
  &lt;div&gt; 
   &lt;div&gt;
     &amp;nbsp; 
   &lt;/div&gt; 
  &lt;/div&gt; &lt;/a&gt; 
 &lt;figure id=&quot;og_1718989567350&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;결제위젯 JavaScript SDK | 토스페이먼츠 개발자센터&quot; data-og-description=&quot;결제위젯 JavaScript SDK를 추가하고 메서드를 사용하는 방법을 알아봅니다.&quot; data-og-host=&quot;docs.tosspayments.com&quot; data-og-source-url=&quot;https://docs.tosspayments.com/reference/widget-sdk#promise%EB%A1%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://docs.tosspayments.com/sdk/widget-js&quot; data-og-image=&quot;&quot;&gt; &lt;a href=&quot;https://docs.tosspayments.com/reference/widget-sdk#promise%EB%A1%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.tosspayments.com/reference/widget-sdk#promise%EB%A1%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0&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;결제위젯 JavaScript SDK | 토스페이먼츠 개발자센터&lt;/p&gt; 
    &lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;결제위젯 JavaScript SDK를 추가하고 메서드를 사용하는 방법을 알아봅니다.&lt;/p&gt; 
    &lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.tosspayments.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;&amp;nbsp;&lt;/p&gt; 
 &lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;HttpClient 선택&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;외부 API를 사용하기 전에는 어떤 HttpClient를 사용할 지 정하고 시작해야한다.&lt;br&gt;HttpClient는 HTTP로 요청을 날릴 수 있도록 도와주는 역할을 하는데, 바로 이 HttpClient를 통해 Toss 결제 API에 요청을 보낼 계획이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Client-Server에 대해서&lt;/h3&gt;&lt;div id=&quot;block-0fd41d5004574a42b9d14b1c968cd3f9&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRa3Uh/btsH70EiLw7/NULoKiPHG2sz63bpXV88rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRa3Uh/btsH70EiLw7/NULoKiPHG2sz63bpXV88rK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRa3Uh/btsH70EiLw7/NULoKiPHG2sz63bpXV88rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRa3Uh%2FbtsH70EiLw7%2FNULoKiPHG2sz63bpXV88rK%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;600&quot; height=&quot;291&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우리 서비스의 Client에게는 스프링부트 애플리케이션이 Server지만, Toss 입장에서는 우리 스프링부트 애플리케이션이 Client가 된다.&lt;br&gt;이렇듯 요청을 보내는 쪽이 Client, 요청을 받아 무언가를 제공하는 곳이 Server이며, HttpClient 사용 학습은 꼭 스프링부트 애플리케이션이라고 해서 Server의 역할만을 가지지는 않는다는 살짝의 학습 키 포인트가 있다고 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아무튼 스프링 환경에서 외부 API 연결은 HttpClient로 수행할 수 있는데, 스프링에서는 HttpClient로 WebClient, RestClient, RestTemplate가 존재한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;각각의 클라이언트들은 장단점이 존재하는데, 이 중에 나는 &lt;b&gt;RestClient&lt;/b&gt;를 선택하였다.&lt;br&gt;그 이유는 아래와 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;WebClient&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;WebClient는 WebFlux 의존성에 있는 &lt;b&gt;비동기/논블로킹&lt;/b&gt; 을 지원하는 HttpClient이다.&lt;br&gt;조금 사용해보긴 했지만 능숙하게 다룰만큼 사용해보지 않았던 기술을 시간 제한이 있고, 그 시간이 조금은 부족하다고 느껴지는 현재 시점에 공부해보면서 적용하는 것은 좋지 않은 판단이라고 생각했다.&lt;br&gt;그리고 WebClient를 도입한다고 해도 해당 클라이언트의 장점인 비동기/논블로킹 방식을 통해 얻을 수 있는 이점이 크게 없다고 생각했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;RestTemplate Vs RestClient&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우테코에서 제공해준 자료중 RestTemplate이 Deprecated된다는 내용의 Baeldung 글이 있었는데, 반면 RestClient는 스프링 6.2부터 나온 메서드 체이닝 형식의 깔끔한 HttpClient 이라고 소개되고 있었다.&lt;br&gt;이때 굳이 Deprecated라는 단점을 감안하고 RestTemplate을 선택할 이유가 없다고 느꼈고 메서드체이닝 방식의 편리하고 깔끔한 코드를 장점삼아 RestClient를 선택하였다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-145fbc5c72654a0592de892300841a7f&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6EKlz/btsH8GZr62V/REp7d3e9KoiujIfMwk29r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6EKlz/btsH8GZr62V/REp7d3e9KoiujIfMwk29r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6EKlz/btsH8GZr62V/REp7d3e9KoiujIfMwk29r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6EKlz%2FbtsH8GZr62V%2FREp7d3e9KoiujIfMwk29r1%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;672&quot; height=&quot;320.2276&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그런데 그 뒤에 조금 더 알아보니 RestTemplate이 사실은 Deprecated 되지 않는 것 같았다.&lt;br&gt;RestTemplate에 대한 공식 문서를 아무리 찾아보아도 Deprecated에 대한 내용은 존재하지 않았다.&lt;/p&gt;&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=S4W3cJOuLrU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bgOjrQ/hyWoGYmAxn/GyX5KtluWZK9ANi30qMKFK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&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-video-title=&quot;RestTemplate은 스프링에서 제거되나요?&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/S4W3cJOuLrU&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;토비님이 말씀하시는 RestTemplate Deprecated&lt;/figcaption&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-8760c6ecabe145439c032a307a32e11b&quot;&gt; 
 &lt;div&gt;
   &amp;nbsp; 
 &lt;/div&gt; 
 &lt;div&gt;
   &amp;nbsp; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에 Baeldung에서 이상한 정보를 줬나? 싶어서 더 찾아보게 되었는데, 위 영상에서 알게 된 점은 스프링 공식 문서에 표시가 되었었지만 이후에 Deprecated를 철회했다고 한다.&lt;br&gt;(아마 이미 RestTemplate을 사용하고 있는 코드가 많아서 Deprecated 된다면 유지보수에 어려움을 겪을까봐 철회한 것 같다.)&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이번 HttpClient 선택 과정을 통해 직접 본 게 아니면 완전히 믿지 말자는 신념이 조금 자라난 것 같다. Baeldung이 아무리 인지도가 높고 의미있는 글이 많은 곳이라고는 하지만, 거기서 보았다고 하더라도 직접 공식문서를 흝어보고 찾아보는 습관을 가지자.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;아무튼 RestTemplate이 Deprecated 되지 않는다고 하더라도 나는 RestClient를 선택하게 되었다.&lt;br&gt;공식문서에서 RestTemplate 보다 현대적인 API를 제공한다고 권장하는데 한 번 써봐야하지 않겠는가.&lt;br&gt;그리고 개인적으로 나는 가독성, 불필요한 변수 생성 로직X 등의 이유로 메서드 체이닝 방식을 좋아한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;RestClient 공식문서&lt;/h3&gt;&lt;div id=&quot;block-48ec2680ecd545439e2556501f12b637&quot;&gt;
  &amp;nbsp; 
&lt;/div&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;REST Clients :: Spring Framework&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;WebClient is a non-blocking, reactive client to perform HTTP requests. It was introduced in 5.0 and offers an alternative to the RestTemplate, with support for synchronous, asynchronous, and streaming scenarios. WebClient supports the following: Non-blocki&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/integration/rest-clients.html&quot;&gt;&lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;REST Clients :: Spring Framework&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;WebClient is a non-blocking, reactive client to perform HTTP requests. It was introduced in 5.0 and offers an alternative to the RestTemplate, with support for synchronous, asynchronous, and streaming scenarios. WebClient supports the following: Non-blocki&lt;/p&gt;&lt;p class=&quot;og-host&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;div id=&quot;block-aedc492b04984db895edef3b3cff872b&quot; style=&quot;color: #333333; text-align: start;&quot;&gt; 
 &lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Timeout 처리&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;외부 API를 연결하기 위해선 Timeout에 대한 처리에 대한 고민도 필요하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Timeout 이란?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;타임아웃이란 서버로 요청을 보냈을 때, 요청 시간이 오래 걸리거나 요청은 도달했으나 응답까지의 시간이 너무 오래 지연되는 경우, 일정 시간이 지나면 요청을 취소하는 것을 의미한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;타임아웃은 시스템 자원을 효율적으로 사용하기 위해 중요한 요소 중 하나이다.&lt;br&gt;연결되지 않는 API 또는 응답이 너무 늦는 API에 쓰레드가 묶여있으면 이는 자원 낭비이다.&lt;br&gt;따라서 타임아웃을 적절히 설정하여, 특정 시간이 지나도 응답이 없는 경우 해당 요청을 취소하고 시스템 자원을 해제하는 타임아웃이 필요한 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;타임아웃은 클라이언트 측과 서버 측에서 각각 설정될 수 있다. 클라이언트 측에서는 요청을 보내고 일정 시간 내에 응답이 없으면 요청을 취소하고, 서버 측에서는 요청을 받아들이고 처리하는 과정에서 일정 시간 내에 작업이 완료되지 않으면 해당 요청을 중단할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이러한 타임아웃은 크게 2종류로 구분할 수 있는데, 바로 살펴보자.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Connection Timeout&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;요청한 서버에 연결하는 시간에 대한 타임아웃이다.&lt;/li&gt;&lt;li&gt;HTTP 연결은 TCP 3way handshake를 통해 수행되는데, 해당 연결 과정 중에 설정해둔 ConnectionTimeout 설정 시간이 지나게 되면 해당 타임아웃이 발생한다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Read Timeout&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;요청을 보낸 서버에서 데이터를 읽어오는 시간에 대한 타임아웃이다.&lt;/li&gt;&lt;li&gt;Connection이 성공하고, 그 이후 서버에서 데이터를 읽어오는 과정에서 설정해둔 ReadTimeout 설정 시간이 지나게 되면 해당 타임아웃이 발생한다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;ConnectTimeout은 몇 초가 적당할까?&lt;br&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 3way-handshaking 과정을 통해 연결하고, 결국 ConnectTimeout의 Time은 TCP 3way-handshacking에 걸리는 시간을 의미한다.&lt;br&gt;&lt;br&gt;나는 이러한 핸드셰이킹 과정의 타임아웃 발생은 몇 초 뒤에 발생해야 적당하며, 어떤 기준을 둘 수 있을까에 대해 아래와 같이 정의해보았다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;핸드셰이킹(handshaking) 과정에서 패킷은 광섬유를 통해 전달된다.&lt;/li&gt;&lt;li&gt;광섬유에서는 데이터(패킷)가 빛의 속도로 이동하며, 빛은 1초에 약 300,000km, 1밀리초(ms)에 약 300km를 이동한다.&lt;/li&gt;&lt;li&gt;제주도에서 강원도 철원까지의 최대 거리는 약 545km이다. 한국 내에서만 서비스를 제공한다는 기준 하에 크게 잡아 요청의 end-to-end 최대 거리를 600km로 설정한다.&lt;/li&gt;&lt;li&gt;이 거리를 기준으로 대한민국 끝에서 끝으로 1번 패킷이 전달되는 데 걸리는 시간은 약 2ms이고, 3번 오가는 데 걸리는 시간은 약 6ms 이다.&lt;/li&gt;&lt;li&gt;위에서 설명한 네트워크 연결에서 발생하는 다양한 상황을 고려하여, connectTimeout 시간을 넉넉하게&lt;b&gt; 1초~3초 &lt;/b&gt;로 설정하면 된다고 생각하였다.&lt;/li&gt;&lt;/ul&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개인적인 생각일 뿐이기 때문에 이견이 있으시다면 남겨주시면 감사하겠습니다!&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;ReadTimeout은 몇 초가 적당할까?&lt;/h3&gt;&lt;div id=&quot;block-3cc735b762d94977a15c88b5a255193d&quot;&gt; 
 &lt;figure id=&quot;og_1718990317433&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;타임아웃(Timeout) | 토스페이먼츠 개발자센터&quot; data-og-description=&quot;네트워크 타임아웃(Timeout)은 서버로 요청을 보냈지만 일정 시간 동안 답변을 받지 못하면 발생해요.&quot; data-og-host=&quot;docs.tosspayments.com&quot; data-og-source-url=&quot;https://docs.tosspayments.com/resources/glossary/timeout&quot; data-og-url=&quot;https://docs.tosspayments.com/resources/glossary/timeout&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/A4LHP/hyWoLZGTtj/EokyAe9undXljAHYT6B9kk/img.png?width=3004&amp;amp;height=1004&amp;amp;face=0_0_3004_1004&quot;&gt; &lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/timeout&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.tosspayments.com/resources/glossary/timeout&quot;&gt; 
   &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/A4LHP/hyWoLZGTtj/EokyAe9undXljAHYT6B9kk/img.png?width=3004&amp;amp;height=1004&amp;amp;face=0_0_3004_1004');&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;타임아웃(Timeout) | 토스페이먼츠 개발자센터&lt;/p&gt; 
    &lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 타임아웃(Timeout)은 서버로 요청을 보냈지만 일정 시간 동안 답변을 받지 못하면 발생해요.&lt;/p&gt; 
    &lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.tosspayments.com&lt;/p&gt; 
   &lt;/div&gt; &lt;/a&gt; 
 &lt;/figure&gt; 
 &lt;div&gt;
   &amp;nbsp; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;토스에서 제공하는 문서를 읽어보면 토스 API의 ReadTimeout은 &lt;b&gt;30~60초&lt;/b&gt;로 설정하는 것이 적절하다고 적혀있다.&lt;br&gt;ReadTimeout은 토스 서버 내부에 의해 응답시간이 결정되는 것이기 때문에 내가 생각해서 처리하는 것보다 토스에서 계산한 값을 사용하는 것이 가장 안정적일 것이라고 생각한다.&lt;br&gt;때문에 권장하는 값 중 가장 작은 값인 &lt;b&gt;30초로 설정&lt;/b&gt;하였다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-784f63bcd0294b03a69a8c92cf1dea4e&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgVSv4/btsH7BdFZ5s/ZLvwAHRSU2N60hsmyyNEW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgVSv4/btsH7BdFZ5s/ZLvwAHRSU2N60hsmyyNEW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgVSv4/btsH7BdFZ5s/ZLvwAHRSU2N60hsmyyNEW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgVSv4%2FbtsH7BdFZ5s%2FZLvwAHRSU2N60hsmyyNEW1%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;384&quot; height=&quot;307.2&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;API 문서를 흝어보면 위와 같이 기능마다 최대로 소요되는 초가 적혀있는데, 이걸 기준으로 설정하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-c60d79a89a3243d7b2206063b54fcce5&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WS27y/btsH8DV9zSR/H9UdYY5mL2ubyMZR1sFRS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WS27y/btsH8DV9zSR/H9UdYY5mL2ubyMZR1sFRS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WS27y/btsH8DV9zSR/H9UdYY5mL2ubyMZR1sFRS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWS27y%2FbtsH8DV9zSR%2FH9UdYY5mL2ubyMZR1sFRS0%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;2440&quot; height=&quot;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;리뷰어였던 호돌에게도 ReadTimeout과 관련해서 질문했는데, 다음과 같은 답변을 얻을 수 있었다.&lt;br&gt;요약하자면 결제와 같이 시간이 오래걸려도 사용자가 용인할 수 있는 부분이라면 타임아웃을 길게 잡아도 괜찮을 것 같다는 의견이었고 나도 충분히 동의했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;타임아웃 처리 로직이 제대로 터지는 지도 테스트를 해야할까?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그렇지 않다고 생각한다.&lt;br&gt;타임아웃이 터지는 지를 테스트하는 것은 라이브러리의 기능을 테스트하는 것이므로 내가 하고자하는 테스트의 본질과는 조금 거리가 먼 것같다고 생각한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;나는 타임아웃이 제대로 발생하는 지를 테스트하기 보다, 타임아웃 발생 시 서버에서 설정한 커스텀 예외로 변환이 잘 되는 지 등의 우리 애플리케이션 로직을 테스트하는 것이 더 적절한 테스트라고 생각했다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;물론 제대로 잘 터지는 지 궁금해서 테스트해보는 것은 절대 나쁘지 않다. 하지만 ‘타임아웃이 잘 터지는 지’를 테스트하는 것은 사실 상 라이브러리를 테스트하는 것이기 때문에 “과연 적절한 테스트일까?” 라는 생각이 든다. ”타임아웃을 발생시키는 로직은 라이브러리 로직이며, 라이브러리 로직은 우리 애플리케이션의 로직이 아니기에 테스트 범위가 아니라고 생각한다.”&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[TIL] TCP 3-way Handshake와 네트워크 레이턴시(latency)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;TCP는 신속보다는 정확한 데이터 교환에 초점이 맞춰진 프로토콜로, 3-way Handshake를 통해 클라이언트/서버 간 통신이 진행된다. 따라서 최초 1회 왕복 패킷 교환이 반드시 수반되어야 하며, 이는 &quot; data-og-host=&quot;chaewonkong.github.io&quot; data-og-source-url=&quot;https://chaewonkong.github.io/posts/til-tcp-3-way-handshake-and-latency.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/C06Wp/hyWoMddbVZ/CMEds46LWEmksQvhsjMLG0/img.jpg?width=1050&amp;amp;height=700&amp;amp;face=0_0_1050_700,https://scrap.kakaocdn.net/dn/bOyfOr/hyWoB3RjI2/prIwZExqDEZtNwlCKaFWG0/img.jpg?width=1050&amp;amp;height=700&amp;amp;face=0_0_1050_700&quot; data-og-url=&quot;http://leonkong.cc/posts/til-tcp-3-way-handshake-and-latency.html&quot;&gt;&lt;a href=&quot;http://leonkong.cc/posts/til-tcp-3-way-handshake-and-latency.html&quot; target=&quot;_blank&quot; data-source-url=&quot;https://chaewonkong.github.io/posts/til-tcp-3-way-handshake-and-latency.html&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/C06Wp/hyWoMddbVZ/CMEds46LWEmksQvhsjMLG0/img.jpg?width=1050&amp;amp;height=700&amp;amp;face=0_0_1050_700,https://scrap.kakaocdn.net/dn/bOyfOr/hyWoB3RjI2/prIwZExqDEZtNwlCKaFWG0/img.jpg?width=1050&amp;amp;height=700&amp;amp;face=0_0_1050_700')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;[TIL] TCP 3-way Handshake와 네트워크 레이턴시(latency)&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;TCP는 신속보다는 정확한 데이터 교환에 초점이 맞춰진 프로토콜로, 3-way Handshake를 통해 클라이언트/서버 간 통신이 진행된다. 따라서 최초 1회 왕복 패킷 교환이 반드시 수반되어야 하며, 이는 &lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;chaewonkong.github.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;div id=&quot;block-aedc492b04984db895edef3b3cff872b&quot; style=&quot;color: #333333; text-align: start;&quot;&gt; 
 &lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;결제승인 API 연동해보기&lt;/h2&gt;&lt;div id=&quot;block-8c7aa29aa2bb4b94ad3585f293c693e1&quot;&gt; 
 &lt;figure id=&quot;og_1718990425970&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;코어 API | 토스페이먼츠 개발자센터&quot; data-og-description=&quot;토스페이먼츠 API 엔드포인트(Endpoint)와 객체 정보, 파라미터, 요청 및 응답 예제를 살펴보세요.&quot; data-og-host=&quot;docs.tosspayments.com&quot; data-og-source-url=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8&quot; data-og-url=&quot;https://docs.tosspayments.com/reference&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDmawP/hyWoCuUUnz/cGqJRnGSZbgmkd83uvOf8K/img.png?width=780&amp;amp;height=390&amp;amp;face=0_0_780_390&quot;&gt; &lt;a href=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8&quot;&gt; 
   &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDmawP/hyWoCuUUnz/cGqJRnGSZbgmkd83uvOf8K/img.png?width=780&amp;amp;height=390&amp;amp;face=0_0_780_390');&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;코어 API | 토스페이먼츠 개발자센터&lt;/p&gt; 
    &lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;토스페이먼츠 API 엔드포인트(Endpoint)와 객체 정보, 파라미터, 요청 및 응답 예제를 살펴보세요.&lt;/p&gt; 
    &lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.tosspayments.com&lt;/p&gt; 
   &lt;/div&gt; &lt;/a&gt; 
 &lt;/figure&gt; 
 &lt;div&gt;
   &amp;nbsp; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;사전에 필요한 퍼즐조각을 모두 모았으니, 이제 직접 코드로 API 연결 퍼즐을 맞춰보자&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;application.yml 설정&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;payment:
&amp;nbsp;&amp;nbsp;secret-key: test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6
&amp;nbsp;&amp;nbsp;base-url: https://api.tosspayments.com/v1/payments
&amp;nbsp;&amp;nbsp;confirm-endpoint: /confirm&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;먼저 application.yml 에 API 연동에 필요한 프로퍼티 값들을 담아준다.&lt;br&gt;결제 승인 API 호출에 필요한 정보는 &lt;b&gt;결제승인 API URL&lt;/b&gt;, 그리고 &lt;b&gt;SecretKey &lt;/b&gt;이다.&lt;br&gt;API URL은 공식문서에서 확인할 수 있으며, &lt;b&gt;SecretKey&lt;/b&gt; 또한 토스 개발자 센터에서 얻을 수 있다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;다만 실제로 결제를 연동하기 위해서는 여러 복잡한 사전 작업을 수행해야하며, 지금 만드는 것은 연습용이니 테스트용 SecretKey를 사용한다. 테스트용 SecretKey 또한 개발자 센터의 문서에서 제공한다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PaymentProperties.java 설정&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@ConfigurationProperties(prefix = &quot;payment&quot;)
public class PaymentProperties {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String secretKey;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String baseUrl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String confirmEndpoint;


&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getSecretKey() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return secretKey;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getBaseUrl() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return baseUrl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getConfirmUrl() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return baseUrl + confirmEndpoint;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void setSecretKey(final String secretKey) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.secretKey = secretKey;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void setBaseUrl(String baseUrl) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.baseUrl = baseUrl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void setConfirmEndpoint(String confirmEndpoint) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.confirmEndpoint = confirmEndpoint;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;다음으로 만들어줄 것은 PaymentProperties 클래스이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;@Value 애노테이션을 통해 yml 파일의 프로퍼티 값을 가져올 수 있지만, 필요한 값들을 하나하나 가져와야하기 때문에 코드가 길어지고 보기도 안좋다.&lt;br&gt;&amp;nbsp;&lt;br&gt;스프링의 &lt;b&gt;@ConfigurationProperties(prefix = &quot;&quot;)&lt;/b&gt; 를 활용하면 이런 문제를 없애준다.&lt;br&gt;&amp;nbsp;&lt;br&gt;prefix에 yml파일의 프로퍼티값 prefix를 적어주면 prefix 하위에 존재하는 값들을 객체에 담아준다.&lt;br&gt;해당 객체는 @Component 를 통해 빈으로 등록하여 싱글톤으로 어디서든 재활용할 수 있다.&lt;br&gt;또한 baseUrl과 confirmEnpoint 를 외부에서 별도로 합칠 필요없이 최종 URL을 만들어주는 메서드를 두어 편리하게 값을 다룰 수도 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PaymentClient.java - RestClient 설정&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class PaymentClient {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final String BASIC_DELIMITER = &quot;:&quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final String AUTH_HEADER_PREFIX = &quot;Basic &quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final int CONNECT_TIMEOUT_SECONDS = 1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final int READ_TIMEOUT_SECONDS = 30;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private final ObjectMapper objectMapper;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private final PaymentProperties paymentProperties;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private RestClient restClient;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public PaymentClient(PaymentProperties paymentProperties,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ObjectMapper objectMapper) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.paymentProperties = paymentProperties;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.objectMapper = objectMapper;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.restClient = RestClient.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.requestFactory(createPaymentRequestFactory())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.requestInterceptor(new PaymentExceptionInterceptor())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.defaultHeader(HttpHeaders.AUTHORIZATION, createPaymentAuthHeader(paymentProperties))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private ClientHttpRequestFactory createPaymentRequestFactory() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withConnectTimeout(Duration.ofSeconds(CONNECT_TIMEOUT_SECONDS))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.withReadTimeout(Duration.ofSeconds(READ_TIMEOUT_SECONDS));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return ClientHttpRequestFactories.get(SimpleClientHttpRequestFactory.class, settings);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String createPaymentAuthHeader(PaymentProperties paymentProperties) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte[] encodedBytes = Base64.getEncoder().encode((paymentProperties.getSecretKey() + BASIC_DELIMITER).getBytes(StandardCharsets.UTF_8));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return AUTH_HEADER_PREFIX + new String(encodedBytes);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;PaymentClient는 위에서 정한 RestClient를 통해 결제 관련 HTTP 요청을 수행하는 객체이다.&lt;br&gt;코드를 하나하나 살펴보자&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;requestFactory() 로 요청 관련 설정값 적용&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;.requestFactory(createPaymentRequestFactory())&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;createPaymentRequestFactory() &lt;/b&gt;메서드에서 &lt;b&gt;ConnectTimeout&lt;/b&gt;, &lt;b&gt;ReadTimeout&lt;/b&gt; 시간을 설정 후, SimpleClientHttpRequestFactory 객체로 만들어 Return 한다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;ClientRequestFactory&lt;/b&gt;는 다양한 종류가 존재하니 학습해보고 필요한 팩토리를 골라 사용하면 된다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;예외 핸들링 Interceptor 적용&lt;/h3&gt;&lt;div id=&quot;block-cb3b78fcd0b442e3a7e636a2e6beda4e&quot;&gt; 
 &lt;pre id=&quot;code_1718991458228&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.requestInterceptor(new PaymentExceptionInterceptor())&lt;/code&gt;&lt;/pre&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;RestClient는 요청에 대해 Interceptor를 따로 적용시킬 수 있다.&lt;br&gt;요청 시, 발생하는 예외에 대한 처리역할을 맡길 수 있는데 직접 핸들링하는 것보다 인터셉터에게 시키는 것이 간편하고 코드도 보기 좋다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;ClientHttpRequestInterceptor 코드&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import roomescape.exception.payment.PaymentConfirmException;
import roomescape.exception.payment.PaymentTimeoutException;

import java.io.IOException;

public class PaymentExceptionInterceptor implements ClientHttpRequestInterceptor {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return execution.execute(request, body);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (IOException e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentTimeoutException(e);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (Exception e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentConfirmException(e);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;ClientHttpRequestInterceptor를 implements하면 되는데, execution을 execute하는 과정에서 예외를 잡을 수 있도록 try/catch 문을 사용했고, 내부에서 잡히는 예외를 직접 만든 커스텀 예외로 변환시켰다.&lt;br&gt;굳이 이렇게까지 하나 싶을 수도 있지만 아래 남겨놓은 적용 전, 적용 후 코드를 통해 장점을 비교해보면 그 효용을 자세히 이해할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;Interceptor 적용 전&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public PaymentConfirmOutput confirmPayment(PaymentConfirmInput confirmRequest) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return restClient.method(HttpMethod.POST)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.uri(&quot;/confirm&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.contentType(MediaType.APPLICATION_JSON)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(confirmRequest)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retrieve()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onStatus(HttpStatusCode::is4xxClientError, (request, response) -&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentConfirmException(getPaymentConfirmErrorCode(response));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onStatus(HttpStatusCode::is5xxServerError, (request, response) -&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentConfirmException(getPaymentConfirmErrorCode(response));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(PaymentConfirmOutput.class);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (ResourceAccessException e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (e.getCause() instanceof SocketTimeoutException) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentTimeoutException(e);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentIOException(e);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (Exception e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentConfirmException(e);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;Interceptor 적용 후&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public PaymentConfirmOutput confirmPayment(PaymentConfirmInput confirmRequest) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return restClient.method(HttpMethod.POST)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.uri(&quot;/confirm&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.contentType(MediaType.APPLICATION_JSON)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(confirmRequest)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retrieve()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onStatus(HttpStatusCode::isError, (request, response) -&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentConfirmException(getPaymentConfirmErrorCode(response));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(PaymentConfirmOutput.class);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;DefaultHeader 설정&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;.defaultHeader(HttpHeaders.AUTHORIZATION, createPaymentAuthHeader(paymentProperties))&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;토스 결제 요청은 secretKey 뒤에 콜론(:)을 붙이고 Base64로 인코딩한 값 앞에 &lt;b&gt;“Basic ” &lt;/b&gt;prefix를 붙여 Authorization 헤더에 담아 요청을 보내야한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;때문에 기본 헤더에 이를 추가해주었다.&lt;br&gt;헤더 Value 생성은 PaymentClient.java의 &lt;b&gt;createPaymentAuthHeader()&lt;/b&gt; 메서드를 참고하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PaymentClient.java - 결제승인(Confirm) API 호출 로직&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class PaymentClient {
		//.. 생략
		
		// 결제 요청 API 호출
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public PaymentConfirmOutput confirmPayment(PaymentConfirmInput confirmRequest) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return restClient.method(HttpMethod.POST)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.uri(paymentProperties.getConfirmUrl())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.contentType(MediaType.APPLICATION_JSON)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(confirmRequest)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retrieve()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onStatus(HttpStatusCode::isError, (request, response) -&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentConfirmException(getPaymentConfirmErrorCode(response));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(PaymentConfirmOutput.clbass);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @see&amp;nbsp;&amp;nbsp;결제 승인 API 에러 코드 문서
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private PaymentConfirmErrorCode getPaymentConfirmErrorCode(final ClientHttpResponse response) throws IOException {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PaymentFailOutput confirmFailResponse = objectMapper.readValue(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response.getBody(), PaymentFailOutput.class);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return PaymentConfirmErrorCode.findByName(confirmFailResponse.code());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위에서 세팅을 마친 RestClient에 결제승인 API에서 요구하는 값들을 담아 호출하는 메서드이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;body&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;body에 담는 값(paymentConfirmInput)은 &lt;b&gt;orderId, amount, paymentKey &lt;/b&gt;이다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;각 값들의 역할은 위에서 설명했으니 넘어간다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;onStatus&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;RestClient는 onStatus 메서드에서 HttpStatusCode의 isError() 메서드를 통해 4xx, 5xx 응답을 한 번에 잡을 수 있다.&lt;br&gt;onStatus 메서드는 Request, Response 값을 모두 제공하기 때문에 요청 실패 응답 시, 예외를 발생시키는 것 또한 간편하게 처리할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;에러 발생 시, 토스는 에러 코드, 메시지가 담긴 &lt;b&gt;에러 객체&lt;/b&gt;를 resposne하는데, 해당 에러 객체 매핑역할은 getPaymentConfirmErrorCode 에서 수행하며 &lt;b&gt;PaymentConfirmErrorCode&lt;/b&gt; 객체에서 에러코드를 관리한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;PaymentConfirmErrorCode 객체는 단순히 에러 응답을 우리 서버만의 객체로 변경해주는 역할 뿐만 아니라, 에러 메시지 중, 클라이언트에게 공개하기 어려운 정보(예를 들어 보안과 관련된 정보)가 담긴 메시지를 공개할 수 있는 정보만 담은 메시지로 변환하여 클라이언트에게 제공하는 역할도 수행한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;PaymentConfirmErrorCode.java&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public enum PaymentConfirmErrorCode {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ALREADY_PROCESSED_PAYMENT(HttpStatus.BAD_REQUEST, &quot;이미 처리된 결제 입니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PROVIDER_ERROR(HttpStatus.BAD_REQUEST, &quot;일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INVALID_REQUEST(HttpStatus.BAD_REQUEST, &quot;잘못된 요청입니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INVALID_API_KEY(HttpStatus.BAD_REQUEST, &quot;잘못된 시크릿키 연동 정보 입니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INVALID_EXCEED_MAX_DAILY_PAYMENT_COUNT(HttpStatus.BAD_REQUEST, &quot;하루 결제 가능 횟수를 초과했습니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EXCEED_MAX_PAYMENT_AMOUNT(HttpStatus.BAD_REQUEST, &quot;하루 결제 가능 금액을 초과했습니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NOT_FOUND_TERMINAL_ID(HttpStatus.BAD_REQUEST, &quot;단말기번호(Terminal Id)가 없습니다. 관리자에게 문의해주세요.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UNAPPROVED_ORDER_ID(HttpStatus.BAD_REQUEST, &quot;아직 승인되지 않은 주문번호입니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UNAUTHORIZED_KEY(HttpStatus.BAD_REQUEST, &quot;인증되지 않은 시크릿 키 혹은 클라이언트 키 입니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INVALID_PASSWORD(HttpStatus.BAD_REQUEST, &quot;결제 비밀번호가 일치하지 않습니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NOT_FOUND_PAYMENT(HttpStatus.BAD_REQUEST, &quot;존재하지 않는 결제 정보 입니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NOT_FOUND_PAYMENT_SESSION(HttpStatus.BAD_REQUEST, &quot;결제 시간이 만료되어 결제 진행 데이터가 존재하지 않습니다.&quot;),

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INVALID_AUTHORIZE_AUTH(HttpStatus.BAD_GATEWAY, &quot;유효하지 않은 인증 방식입니다.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NOT_AVAILABLE_PAYMENT(HttpStatus.BAD_GATEWAY, &quot;결제가 불가능한 시간대입니다&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INCORRECT_BASIC_AUTH_FORMAT(HttpStatus.BAD_GATEWAY, &quot;서버 측 오류로 결제에 실패했습니다. 관리자에게 문의해주세요.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FAILED_PAYMENT_INTERNAL_SYSTEM_PROCESSING(HttpStatus.BAD_GATEWAY, &quot;결제가 완료되지 않았어요. 다시 시도해주세요.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FAILED_INTERNAL_SYSTEM_PROCESSING(HttpStatus.BAD_GATEWAY, &quot;내부 시스템 처리 작업이 실패했습니다. 잠시 후 다시 시도해주세요.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UNKNOWN_PAYMENT_ERROR(HttpStatus.BAD_GATEWAY, &quot;결제에 실패했어요. 같은 문제가 반복된다면 은행이나 카드사로 문의해주세요.&quot;),

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PAYMENT_CONFIRM_ERROR_MISMATCH_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, &quot;결제 과정에서 서버 에러가 발생했습니다. 관리자에게 문의해주세요.&quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private final HttpStatus httpStatus;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private final String message;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public static PaymentConfirmErrorCode findByName(String name) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Arrays.stream(values())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(v -&amp;gt; v.name().equals(name))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.findAny()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.orElse(PAYMENT_CONFIRM_ERROR_MISMATCH_ERROR);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PaymentConfirmErrorCode(final HttpStatus httpStatus, final String message) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.httpStatus = httpStatus;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.message = message;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public HttpStatus getHttpStatus() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return httpStatus;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getMessage() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return message;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;PAYMENT_CONFIRM_ERROR_MISMATCH_ERROR&lt;/b&gt; 는 토스에서 전달하는 에러는 아니고,&lt;br&gt;해당 객체의 에러코드와 토스 객체의 에러코드명이 일치하지 않을 때 발생시키기 위해 따로 만든 에러코드이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;retrieve 이후의 body&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;retrieve 는 요청을 전송하는 메서드이기 때문에, retrieve 이후의 body는 응답값을 담을 body이다.&lt;br&gt;응답에서 받을 값(PaymentConfirmOutput) 은 다음 값들이다. &lt;b&gt;paymentKey, orderId, totalAmount(총 결제금액), orderName, requestedAt(요청시간), approvedAt(승인시간), status(결제상태)&lt;/b&gt;&lt;br&gt;응답값에 대한 자세한 설명은 위에 첨부한 공식문서를 참고하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;&lt;div id=&quot;block-f40371daba764005962fb25fbb0e3b0b&quot;&gt; 
 &lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boF202/btsH9uKDMIg/02fMHk4Lg1VCAEapBKF840/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boF202/btsH9uKDMIg/02fMHk4Lg1VCAEapBKF840/img.png&quot; width=&quot;288&quot; height=&quot;623.2615384615384&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2769&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boF202/btsH9uKDMIg/02fMHk4Lg1VCAEapBKF840/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboF202%2FbtsH9uKDMIg%2F02fMHk4Lg1VCAEapBKF840%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;1280&quot; height=&quot;2769&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNuasS/btsH8Ipo6zA/mGbLeoTxY4KUWmxhfiRqwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNuasS/btsH8Ipo6zA/mGbLeoTxY4KUWmxhfiRqwK/img.png&quot; width=&quot;288&quot; height=&quot;623.2615384615384&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2769&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNuasS/btsH8Ipo6zA/mGbLeoTxY4KUWmxhfiRqwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNuasS%2FbtsH8Ipo6zA%2FmGbLeoTxY4KUWmxhfiRqwK%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;1280&quot; height=&quot;2769&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-aedc492b04984db895edef3b3cff872b&quot; style=&quot;color: #333333; text-align: start;&quot;&gt; 
 &lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;로깅(Logging)&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 거의 다 왔으니 이제 로깅에 대해서 고려해보면 좋을 것 같다.&lt;br&gt;주변 대기업에 다니는 지인들에게 물어보니, 보통 로깅에 있어서 개인정보 관련 고려사항이 많은걸로 알고 있는데 회사마다 규정도 다르고 관리방법도 달라서 이것까진 고려하지 않고 로깅 자체에 대해서만 생각해보겠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우선 나누고 싶은 주제는 &lt;b&gt;“외부 API 호출 시, 로깅은 왜 해야할까?”&lt;/b&gt; 이다.&lt;br&gt;주변 크루원들의 코드를 보면 LoggingInterceptor 등의 로깅 시스템을 구축해서 외부 API에 대한 request, response 정보를 로깅하고 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;사실 처음에는 이걸 로깅할 필요가 있을까? 싶었는데, 조금 생각해보니 그 이유는 확실히 &lt;b&gt;있었다.&lt;/b&gt;&lt;br&gt;만약 우리 방탈출 서비스에서 누군가 결제를 했는데 돈만 빠져나가고 예약이 안됐다고 전화가 오면, 개발자는 무엇을 해야할까?&lt;br&gt;로그를 통해 해당 고객이 실제로 결제 요청을 보내 결제에 성공까지 했는지에 대해 확인해야한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그러나 로그가 없다면 어떻게 될까?&lt;br&gt;&amp;nbsp;&lt;br&gt;우리는 아무런 정보도 얻을 수 없어 문제 해결 과정이 복잡해진다. 아마 소비자에게 이런저런 증빙자료를 요구해야할 것이고 소비자는 돈을 돌려받는 것에 어려움을, 우리는 이 문제를 해결하는 데에 어려움을 느낄 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;실제로 개인정보 보호 문제로 필수적인 몇몇 데이터만 로깅하기 때문에, 위와 같은 CS가 들어왔을 때 로그가 없어 추적이 힘들 때도 있다는 IT 대기업에 근무하는 지인의 이야기도 들어볼 수 있었다.&lt;br&gt;이와 같이 로깅은 critical한 외부 API 호출에 있어서 필수적이라고 생각이 든다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;물론 지금 한 이야기들은 방금 말했듯이 결제 등과 같은 critical한 API에 한해서이다. 날씨 정보 조회 API 와 같은 내용은 실패하던 성공하던 로깅을 남길 필요가 전혀 없는 non-critical의 범주에 있다. 이런 부분들을 잘 고려하면서 로깅 처리도 잘 해두면 분명 효용을 얻을 수 있을 것이다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이러한 로깅은 아까 예외처리 방식과 동일하게, 로깅 또한 Interceptor를 활용할 수 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PaymentLoggingInterceptor&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

public class PaymentLoggingInterceptor implements ClientHttpRequestInterceptor {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private static final Logger logger = LoggerFactory.getLogger(PaymentLoggingInterceptor.class);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String host = request.getURI().getHost();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String path = request.getURI().getPath();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String httpMethod = request.getMethod().toString();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(&quot;[Payment Request] {}\t: {} {} \n \t{}&quot;, path, httpMethod, host, new String(body));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return execution.execute(request, body);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;요청을 보내는 host, path, httpMethod 그리고 Http Body를 기록한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PaymentClient - 로깅 Interceptor 등록&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class PaymentClient {
		//.. 생략
		
		public PaymentClient(PaymentProperties paymentProperties,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ObjectMapper objectMapper) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.paymentProperties = paymentProperties;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.objectMapper = objectMapper;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.restClient = RestClient.builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.requestFactory(createPaymentRequestFactory())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.requestInterceptor(new PaymentExceptionInterceptor())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.requestInterceptor(new PaymentLoggingInterceptor()) // 로깅 인터셉터 등록
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.defaultHeader(HttpHeaders.AUTHORIZATION, createPaymentAuthHeader(paymentProperties))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이렇게 만든 Interceptor를 RestClient builder에 requestInterceptor 메서드로 등록한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div id=&quot;block-aedc492b04984db895edef3b3cff872b&quot; style=&quot;color: #333333; text-align: start;&quot;&gt; 
 &lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot;&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;결제취소 API&lt;/h2&gt;&lt;div id=&quot;block-c95a3d94393c48afab738a848cab834e&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
  &amp;nbsp; 
&lt;/div&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;결제 취소하기 | 토스페이먼츠 개발자센터&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;토스페이먼츠 결제 취소 API를 호출하세요. 결제 금액 전액 또는 일부를 고객에게 환불하는 방법, 가상계좌 결제 취소하는 방법, 결제위젯에서 가상계좌 정보 확인하는 방법을 소개합니다.&quot; data-og-host=&quot;docs.tosspayments.com&quot; data-og-source-url=&quot;https://docs.tosspayments.com/guides/cancel-payment&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/biNUIP/hyWoLrPLu5/0TAF2Kibh3g5ENNjcTsIr1/img.png?width=1632&amp;amp;height=920&amp;amp;face=0_0_1632_920&quot; data-og-url=&quot;https://docs.tosspayments.com/guides/cancel-payment&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/guides/cancel-payment&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.tosspayments.com/guides/cancel-payment&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/biNUIP/hyWoLrPLu5/0TAF2Kibh3g5ENNjcTsIr1/img.png?width=1632&amp;amp;height=920&amp;amp;face=0_0_1632_920')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;결제 취소하기 | 토스페이먼츠 개발자센터&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;토스페이먼츠 결제 취소 API를 호출하세요. 결제 금액 전액 또는 일부를 고객에게 환불하는 방법, 가상계좌 결제 취소하는 방법, 결제위젯에서 가상계좌 정보 확인하는 방법을 소개합니다.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;docs.tosspayments.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;br&gt;예약을 취소하면 결제 또한 취소되어야 한다.&lt;br&gt;때문에 우리는 결제승인 API 뿐만 아니라 취소 API도 연결해야한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;한 번 구현해보자.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;application.yml 에 cancel 관련 값 추가&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;payment:
&amp;nbsp;&amp;nbsp;secret-key: test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6
&amp;nbsp;&amp;nbsp;base-url: https://api.tosspayments.com/v1/payments
&amp;nbsp;&amp;nbsp;confirm-endpoint: /confirm
&amp;nbsp;&amp;nbsp;cancel-endpoint: /%s/cancel #cancel 엔드포인트 추가&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위에 세팅해둔 코드가 있으니 cancelEndpoint만 추가해준다.&lt;br&gt;endpoint에 &lt;b&gt;%s &lt;/b&gt;가 들어가있는 이유는 저 부분에 &lt;b&gt;PaymentKey &lt;/b&gt;가 들어가기 때문에&lt;br&gt;&lt;b&gt;String.format() &lt;/b&gt;으로 값을 삽입해줄 예정이기 때문이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PaymentProperties.java 에 cancel 프로퍼티값 추가&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@ConfigurationProperties(prefix = &quot;payment&quot;)
public class PaymentProperties {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String secretKey;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String baseUrl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String confirmEndpoint;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private String cancelEndpoint; // cancelEndpoint 추가


&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getSecretKey() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return secretKey;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getBaseUrl() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return baseUrl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getConfirmUrl() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return baseUrl + confirmEndpoint;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public String getCancelUrl(String paymentKey) { // cancelUrl 흭득 메서드 추가
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return String.format(baseUrl + cancelEndpoint, paymentKey);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void setSecretKey(final String secretKey) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.secretKey = secretKey;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void setBaseUrl(String baseUrl) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.baseUrl = baseUrl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void setConfirmEndpoint(String confirmEndpoint) { // cancelEnpoint Setter 추가
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.confirmEndpoint = confirmEndpoint;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void setCancelEndpoint(String cancelEndpoint) { // cancelEnpoint Getter 추가
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.cancelEndpoint = cancelEndpoint;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;Properties 객체에도 cancel과 관련된 필드, 메서드를 추가한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;PaymentClient.java에 cancel 메서드 추가&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class PaymentClient {
		//.. 생략
		
		// 결제 취소 API 호출
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public PaymentCancelOutput cancelPayment(Payment payment) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PaymentCancelInput cancelRequest = new PaymentCancelInput(&quot;단순 변심&quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return restClient.method(HttpMethod.POST)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.uri(paymentProperties.getCancelUrl(payment.getPaymentKey()))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.contentType(MediaType.APPLICATION_JSON)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(cancelRequest)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.retrieve()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onStatus(HttpStatusCode::isError, (request, response) -&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new PaymentCancelException(getPaymentCancelErrorCode(response));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.body(PaymentCancelOutput.class);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @see&amp;nbsp;&amp;nbsp;결제 취소 API 에러 코드 문서
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private PaymentCancelErrorCode getPaymentCancelErrorCode(final ClientHttpResponse response) throws IOException {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PaymentFailOutput cancelFailResponse = objectMapper.readValue(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response.getBody(), PaymentFailOutput.class);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return PaymentCancelErrorCode.findByName(cancelFailResponse.code());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;RestClient API 요청에 대해서는 위에서 많이 살펴보았기 때문에 이곳에서는 Body만 살펴보자.&lt;br&gt;&amp;nbsp;&lt;br&gt;나머지 다른 부분(PaymentCancelException, getPaymentCancelErrorCode, PaymentCancelErrorCode)가 존재하지만, 에러만 다르지 내부 구현은 똑같다.&lt;br&gt;관련 에러는 역시 위에 첨부한 공식 문서를 참고하면 쉽게 확인할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Body&lt;/h3&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;PaymentCancelInput cancelRequest = new PaymentCancelInput(&quot;단순 변심&quot;);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;결제 취소 API의 필수 요청 값은 paymentKey와 cancelReason이다. 이 외의 값은 지금 필요하지 않기 때문에 추가하지 않았다.&lt;br&gt;paymentKey는 결제취소 API URL에 포함되었으니, body에는 cancelReason만 담아주면 된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;취소 사유는 사용자에게 받을 수도 있지만, 클라이언트를 그 부분까지는 구현해두지 않아서 &lt;b&gt;단순 변심&lt;/b&gt; 으로 통일했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Retrieve 이후의 Body&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;결제 취소 Response body 중 나는 다음 값만 받았다.&lt;br&gt;&lt;b&gt;paymentKey, orderId, orderName, status, requestedAt, approvedAt&lt;/b&gt;&lt;br&gt;이 값을 통해 DB에 취소 작업 처리 시간을 기록한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;적용 결과&lt;/h3&gt;&lt;div id=&quot;block-b82b556d40474032880ff201d12d0cf7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt; 
 &lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2769&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVXVyb/btsH9fUtXAS/p2T3O62uwhDEOlAWaf94G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVXVyb/btsH9fUtXAS/p2T3O62uwhDEOlAWaf94G0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVXVyb/btsH9fUtXAS/p2T3O62uwhDEOlAWaf94G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVXVyb%2FbtsH9fUtXAS%2Fp2T3O62uwhDEOlAWaf94G0%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;288&quot; height=&quot;623.26154&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2769&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt; 
 &lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;외부 API 테스트&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;외부 API가 정상 작동하는지 검증하는 테스트도 작성해야할까?&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이번에 학습을 진행하면서 외부 API를 꼭 테스트 해야할 지에 대해 고민해보았다.&lt;br&gt;결론적으로 나는 외부 API를 테스트하지는 않고, 외부 서버에서 특정 응답이 왔을 때 서버가 어떻게 작동할 지에 대한 테스트만 Mocking을 통해 진행하기로 하였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그 이유는 다음과 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Toss API는 외부 API이기 때문에, 우리가 원하는 대로 컨트롤할 수 없다.&lt;br&gt;성공하던 테스트가 항상 성공하고 실패하는 테스트는 항상 실패해야한다는 테스트의 일관성이 외부의 상황 또는 네트워크 상태에 따라 어긋날 가능성이 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;때문에 이렇게 외부에 의존하는 부분은 Mock 또는 테스트 더블로 처리해야한다는 생각이 들었다.&lt;br&gt;이번 미션에 배포가 있어서 함께 고려해보자면 우리 코드에 문제는 없지만 Toss API가 잠깐 먹통이 되었을 때, build가 되지 않아 배포가 되지 않는다면 이건 문제가 아닐까 싶었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Toss의 결제 API가 먹통이되는 것은 새로운 버전을 배포하나 현재 버전에서나 똑같이 발생하고 있을텐데&lt;br&gt;이 문제 때문에 저희 애플리케이션의 새로운 버전이 배포되는 것이 막히는 건 문제가 아닐까 싶었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 외부 서버 문제가 아니라 정말로 우리가 요청을 잘못보내고 있는 경우도&lt;br&gt;“테스트가 통과되더라도 개발단에서의 검증과 QA만 제대로 되면 발생하지 않는 문제이지 않을까?” 라는 생각이 들었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;물론 검증과 QA로 모든 것을 해결할 순 없겠지만 최소 95% 정도는 웬만한 버그 발생을 잡을 수 있을 거라고 생각한다.&lt;br&gt;트레이드 오프의 관점으로 보았을 때도 테스트를 외부에 의존시키는 것보다 꼼꼼한 개발단의 검증과 QA를 하는 것이 더 낫지 않을까 라는 생각이 들었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;때문에 나는 외부 API를 직접 호출해서 테스트하지는 않기로 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 외부 API를 적용할 때는 빨리빨리 되는대로만 구현했던 경험이 있다. 그때는 왜 그랬는 지 모르겠지만 연결만 되면 끝인 줄 알았다.&lt;br&gt;반면, 우테코에 들어와서 이것저것 많이 배우고 주워들은 결과 한 번의 외부 API 요청에 대해 정말 많은 것들을 고려해야한다는 것을 깨닫게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;항상 코드 한 줄 한 줄 생각하며 개발하자.&lt;br&gt;그리고 그 생각을 충분히 코드에 녹여낼 수 있는 개발자가 되기 위해 수준을 길러나가자.&lt;br&gt;성장에 지름길은 없다. 꼼수없이 깊게 배우고 기억을 위해 기록하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;+ 추가로 토스 API 문서 진짜 친절했다..! 짱!&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;긴 글 읽어주셔서 감사합니다. 피드백은 환영입니다! 댓글에 얼마든지 남겨주세요!&lt;/span&gt;&lt;/blockquote&gt;</description>
      <category>BackEnd/Spring</category>
      <category>springboot toss api</category>
      <category>스프링 토스</category>
      <category>스프링 토스 결제</category>
      <category>스프링 토스 결제 연동</category>
      <category>스프링 토스 연동</category>
      <category>토스 api</category>
      <category>토스 결제 api</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/165</guid>
      <comments>https://pgmjun.tistory.com/165#entry165comment</comments>
      <pubDate>Sat, 22 Jun 2024 02:15:55 +0900</pubDate>
    </item>
    <item>
      <title>GPT API의 응답 값을 원하는 형태로 만들어보자 (with. Chat Completion API &amp;amp; Fine-Tuning)</title>
      <link>https://pgmjun.tistory.com/164</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfQU4Y/btsH7jXWFmc/SOVAzYTeAvlgkGGvHhBLQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfQU4Y/btsH7jXWFmc/SOVAzYTeAvlgkGGvHhBLQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfQU4Y/btsH7jXWFmc/SOVAzYTeAvlgkGGvHhBLQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfQU4Y%2FbtsH7jXWFmc%2FSOVAzYTeAvlgkGGvHhBLQ0%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;371&quot; height=&quot;371&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;졸업작품 프로젝트에서 마주한 문제&lt;/h2&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-88af95ac1f154db098b88300a14af96c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;졸업작품 프로젝트 &amp;lsquo;코리&amp;rsquo;를 개발하는 과정에서 GPT API를 사용하여 AI로부터 원하는 응답값을 얻어오는 기능을 구현하던 중 너무나도 어지러운 문제에 직면했다.&lt;/p&gt;
&lt;p id=&quot;block-8697f0c3f46d43079a3f490b2d2d6186&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코리는 GPT를 사용해서 개발자들의 전반적인 작업에 도움을 주는 어시스턴트 서비스이다.&lt;/p&gt;
&lt;p id=&quot;block-23a090ea63fc4b71bb431d1fceda6b10&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lsquo;코드 리팩토링&amp;rsquo;, &amp;lsquo;변수명 추천&amp;rsquo;, &amp;lsquo;주석을 통한 코드 설명&amp;rsquo;&lt;/b&gt; 기능이 존재하는데, 이를 구현하기 위해 SpringBoot 환경에서 HttpClient를 사용하여 GPT API에서 제공하는 gpt-3.5-turbo 모델과 통신하여 응답 값을 얻어오는 것 까지는 성공했다.&lt;/p&gt;
&lt;p id=&quot;block-141dba740c3444cd97858ba9ddef2e9f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 여기서부터 몇 가지 문제에 직면하면서 고민이 시작되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-b854c0a3708d4af99ae5f4cb50e23490&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문제1. 일정하지 않은 응답 형태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-a6b5f1bd91594935b4f58a244bd7ddf6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;div id=&quot;block-43d6f13897344d70b4bfbeb9532ae082&quot;&gt;
&lt;div id=&quot;block-22951a1df13e4f879d246c56c401ad6a&quot;&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbBD2E/btsH6ZrSgTG/KK40o72e0OLMdsyXEzMD00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbBD2E/btsH6ZrSgTG/KK40o72e0OLMdsyXEzMD00/img.png&quot; width=&quot;842&quot; height=&quot;519&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;789&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.5666%; margin-right: 10px;&quot; data-widthpercent=&quot;48.13&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbBD2E/btsH6ZrSgTG/KK40o72e0OLMdsyXEzMD00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbBD2E%2FbtsH6ZrSgTG%2FKK40o72e0OLMdsyXEzMD00%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;1280&quot; height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uwl0E/btsH7TEsCcY/z04qchhF98WwztYkv7rU91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uwl0E/btsH7TEsCcY/z04qchhF98WwztYkv7rU91/img.png&quot; width=&quot;864&quot; height=&quot;494.00000000000006&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;732&quot; data-is-animation=&quot;false&quot; style=&quot;width: 51.2706%;&quot; data-widthpercent=&quot;51.87&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uwl0E/btsH7TEsCcY/z04qchhF98WwztYkv7rU91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUwl0E%2FbtsH7TEsCcY%2Fz04qchhF98WwztYkv7rU91%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;1280&quot; height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;block-07ebd89d6f634e7bb351747cb9c7b41b&quot;&gt;
&lt;div id=&quot;block-fee2df38352d45d996e154ca1e2e1444&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p id=&quot;block-0c490077a3084330948077237c059e28&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GPT 사이트에 접속하여 질문을 던질 때, 질문을 조금만 다르게 보내도 GPT가 응답하는 형식이 계속 바뀌었으며, 정해진 형식 없이 텍스트로 나열하는 형태였다.&lt;/p&gt;
&lt;p id=&quot;block-4ce63f1369c6411980ff2eb584a032ad&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아무래도 프로덕트를 구성한다면, 이렇게 얻어온 GPT의 응답 값을 그대로 사용하는 것은 유저 경험과 프로덕트의 완성도 측면에서 문제가 된다고 생각했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-c7d99d91a47b4a129e1aa9ca5d54a71f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문제2. 유효하지 않은 요청도 어떻게든 응답함&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-0c2e40819f7742c59597f2bed80f342f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;div id=&quot;block-3f4cdd8c90c441f48de1d8801b71f9c4&quot;&gt;
&lt;div id=&quot;block-ca3ab9ce271049349a069579d9c8ee91&quot;&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr5qUI/btsH8H4kQAz/KIJ10dxvYCtqZOXe2hNKU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr5qUI/btsH8H4kQAz/KIJ10dxvYCtqZOXe2hNKU1/img.png&quot; width=&quot;866&quot; height=&quot;1522&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2249&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.8902%; margin-right: 10px;&quot; data-widthpercent=&quot;41.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr5qUI/btsH8H4kQAz/KIJ10dxvYCtqZOXe2hNKU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr5qUI%2FbtsH8H4kQAz%2FKIJ10dxvYCtqZOXe2hNKU1%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;1280&quot; height=&quot;2249&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s8QXQ/btsH8GEkIBQ/QvSf0uuo0Q6kWP1jLbOIV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s8QXQ/btsH8GEkIBQ/QvSf0uuo0Q6kWP1jLbOIV0/img.png&quot; width=&quot;1290&quot; height=&quot;1600&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1587&quot; data-is-animation=&quot;false&quot; style=&quot;width: 57.947%;&quot; data-widthpercent=&quot;58.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s8QXQ/btsH8GEkIBQ/QvSf0uuo0Q6kWP1jLbOIV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs8QXQ%2FbtsH8GEkIBQ%2FQvSf0uuo0Q6kWP1jLbOIV0%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;1280&quot; height=&quot;1587&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;block-7defe6d78c7745ffaa53741ce45686a5&quot;&gt;
&lt;div id=&quot;block-8994234eaa264488a7ac5b679cb68e33&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-c10f96b589a347df9c1f598998c69aaa&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GPT에게 Java 코드를 Python 코드라고 말하면서 주석 설명을 붙여달라고 요청해보았다.&lt;/p&gt;
&lt;p id=&quot;block-d49c2b038220417abfc2d9b0050fe382&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;말도 안되는 요청이지만 GPT는 어떻게든 응답을 쥐어짜내어 Java코드를 Python 코드로 변경하고, 주석을 달아서 응답하였다.&lt;/p&gt;
&lt;p id=&quot;block-7e5b69a386a34e1db90d34025d72f6a6&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 이건 요청, 응답 모두 유효하지 않은 경우이다.&lt;/p&gt;
&lt;p id=&quot;block-ceb15f66182942068bcc653b781ca89b&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GPT API는 돈을 내고 사용하는 것이기 때문에 이렇게 유효하지 않은 요청과 응답을 주고받는 것은 의미없는 비용만 낭비하게 되는 것이다.&lt;/p&gt;
&lt;p id=&quot;block-5dcefd48637743db994dcdd93e16a109&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 유효하지 않은 요청 상황이 발생하면 예외처리를 할 수단이 필요하다고 느껴졌다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-011e29a46a5546baac6dd1503fcc12aa&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Chat Completion API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-71f78b14cdf7424ca75040f76261e8bc&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 나열한 문제들을 해결하기 위해 선택한 방법은 Chat Completion API 이다.&lt;/p&gt;
&lt;p id=&quot;block-a9f1dab6b0ce4bdb8b5cabbf8cee95b4&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Chat Completion API를 사용하면 대화 형식으로 GPT와 응답을 주고받을 수 있는데, 이 방식을 통해 &lt;b&gt;assistant&lt;/b&gt;에게 가상의 역할을 부여하고, 응답 형태를 알려줄 수 있어서 문제를 해결할 수 있을 것으로 생각했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-12c5b834fa2741ed8d3d7a0dcfb7e9f8&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;공식 문서&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  채팅 모델은 메시지 목록을 입력으로 받고 모델이 생성한 메시지를 출력으로 반환합니다. 채팅 형식은 여러 차례에 걸친 대화를 쉽게 할 수 있도록 설계되었지만, 대화가 없는 단일 턴 작업에도 유용합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-549771ea64824ae89868da6f0e4e2f5a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;라는 Chat Completion API에 대한 공식문서의 내용을 읽을 수 있었다.&lt;/p&gt;
&lt;p id=&quot;block-892974faf898496bb1e852ca799980f2&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Chat Completion 방식은 우리가 일반적으로 웹사이트에 접속하여 사용하는 GPT 처럼 이전에 대화한 내용을 통해 응답값을 유추하는 방식과 같은 방식으로, 우리는 이 API를 통해 이전 요청, 응답 내용을 알려줄 수 있고 이를 바탕으로 다음 대답을 생성하도록 유도할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-2e4c11971f4f4acf8444c9a3886ff4c8&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예시를 통해 살펴보자&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-daacbfb17f95457e99df9f3a3d055860&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SnY2a/btsH7hFNiqK/SsGFphpTx76kgQuZjS5rf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SnY2a/btsH7hFNiqK/SsGFphpTx76kgQuZjS5rf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SnY2a/btsH7hFNiqK/SsGFphpTx76kgQuZjS5rf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSnY2a%2FbtsH7hFNiqK%2FSsGFphpTx76kgQuZjS5rf1%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;576&quot; height=&quot;409.32138&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-3aa8e91302e648808088a1eaf2db4fbb&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리가 무턱대고 GPT에게 &lt;b&gt;&amp;ldquo;그것이 어디서 진행되었나?&amp;rdquo; &lt;/b&gt;라는 질문을 한다면 GPT는 이상한 답변을 내놓게 될 것이다.&lt;/p&gt;
&lt;p id=&quot;block-c43c7a30dbcf4f58898c077a7c085344&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Chat Completion 방식을 통해 &lt;b&gt;&amp;ldquo;그것이 어디서 진행되었나?&amp;rdquo;&lt;/b&gt; 라는 질문 이전 &amp;lsquo;2020 World Series&amp;rsquo;에 대한 대화의 문맥이 포함된 대화뭉치를 제공한다면 GPT는 훨씬 정확하고 수월한 답을 내놓을 수 있게 되는 것이다.&lt;/p&gt;
&lt;p id=&quot;block-e8ad1ef5d405448fbd6a491c8745a7af&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이것이 Completion 방식의 기본 개념이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-40039e2d6ad747ce8b6b3fb2c8491d37&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Chat Completion Role&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-d06eacf9b544440c825fc37694b58170&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GPT의 Chat Completion에는 3가지 Role이 존재한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-8cc1c79f972f47ba9c08804a09fb1237&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;system Role (시스템 역할):&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-0a3d7febc2be4f538072444a172a29a3&quot;&gt;&quot;system&quot; 역할은 API 요청에서 GPT-3 모델에게 주어지는 지시 또는 문맥을 정의한다.&lt;/li&gt;
&lt;li id=&quot;block-4669271ea3484c72b6d3971d120d3893&quot;&gt;이 역할은 모델이 요청에 대답할 때 원하는 행동을 나타내며, 일반적으로 문맥을 설정하고 대화 스타일의 상호 작용을 정의하는 데 사용된다.&lt;/li&gt;
&lt;li id=&quot;block-af555a93b69f47abb9e424285af37079&quot;&gt;system Role을 통해 model에게 너는 &amp;lsquo;어떤 역할인지&amp;rsquo;, &amp;lsquo;어떻게 답변해야하는 지&amp;rsquo; 등을 알려주어 대화 스타일을 지정할 수 있었다. 예를 들어 &amp;ldquo;말 끝마다 ^^를 붙여서 대답해&amp;rdquo; 라고 하면 모든 응답의 끝에 &amp;ldquo;^^&amp;rdquo;를 붙여서 응답하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;block-3158800d9ad646ada31c443e1e40841c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;user Role (사용자 역할):&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-b09d2f2efc9c46aaba47d57804e657de&quot;&gt;&quot;user&quot; 역할은 API 요청에서 사용자의 입력 또는 질문을 정의한다.&lt;/li&gt;
&lt;li id=&quot;block-390d8dc55edd4f95a0c3a93e4f67189f&quot;&gt;이 역할은 모델이 질문에 답하거나 특정 주제에 대한 정보를 생성하는 데 사용된다. 쉽게말해 사용자는 &quot;user&quot; 역할을 통해 모델에게 질문하거나 정보를 요청할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-a898a7c4431b4a4ea40590ed2c6f689b&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;assistant Role (응답자 역할):&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-7980d1a64bf940d390a7dad85d9959ee&quot;&gt;요청에 대한 AI model의 응답을 전달하는 응답자 역할입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p id=&quot;block-a0a320ae4c9e4f8c95ecca408de217c0&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 user Role을 통해 model에게 질문을 전송하면, model은 system으로부터 부여받은 내용을 통해 응답을 생성하여 assistant 역할로 return한다.&lt;/p&gt;
&lt;blockquote id=&quot;block-487173dfff574332858e5542c5c17879&quot; data-ke-style=&quot;style3&quot;&gt;나는 이 방식이 응답 형태를 고정하는 하나의 열쇠라고 생각했다.&lt;br /&gt;때문에 Chat Completion 방식을 어떻게 활용하여 API 호출에 응용할 수 있을 지 연구해보기로 결정했다.&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-5bd921171cb746ddba871382cb966f56&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Fine Tuning&lt;/h2&gt;
&lt;p id=&quot;block-30aeef910df644b7b6bc3be43e91d905&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Chat Completion 방식을 API 호출 과정에서 응용할 수 있는 해결책을 찾는 과정에서 Fine Tuning이라는 것을 알게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-c7a96ab19f2a4463b69777c31f39baea&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;미세 조정' 이라는 뜻을 가진 Fine Tuning은 어떤 요청을 받았을 때 어떤 형식으로 응답을 해야하는 지에 대한 데이터셋을&lt;br /&gt;Chat Completion API 방식으로 만들어서 GPT 모델에 학습시키면 원하는 출력 포맷팅을 학습시킬 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-d291826544754008b02ae92ac731522c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;공식문서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-253e0d466db14675934449acc0953947&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diynYm/btsH62a2IoG/MijMzYDerEZaeEXUu9hDk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diynYm/btsH62a2IoG/MijMzYDerEZaeEXUu9hDk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diynYm/btsH62a2IoG/MijMzYDerEZaeEXUu9hDk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiynYm%2FbtsH62a2IoG%2FMijMzYDerEZaeEXUu9hDk0%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;720&quot; height=&quot;432.17444&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div id=&quot;block-3378d4b6eb144ceea7a3ef52a8d9aae8&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/83n1o/btsH6SGucBy/9gbGDy4ZPT0Rx2zVlK0LD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/83n1o/btsH6SGucBy/9gbGDy4ZPT0Rx2zVlK0LD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/83n1o/btsH6SGucBy/9gbGDy4ZPT0Rx2zVlK0LD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F83n1o%2FbtsH6SGucBy%2F9gbGDy4ZPT0Rx2zVlK0LD1%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;576&quot; height=&quot;70.64151&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-3c71bf917fbb479d924950191894e13c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공식문서에 나와있듯이 Fine Tuning을 통해 믿을 수 있는 출력 포맷팅 이라는 이점을 얻을 수 있었다.&lt;/p&gt;
&lt;blockquote id=&quot;block-932189bcbfb943ce927a5744a0631942&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;즉 학습 시킨대로 출력 형태를 지정할 수 있다는 뜻이었다.&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-f1162b44c02744fa8492ee2a477c46d4&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Fine-Tuning 가능 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-ea60960a875c43aba5cb6c208a85f4fd&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO4M6g/btsH7UDkvMW/qBkz6O7KsfPwKDyAfNe6b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO4M6g/btsH7UDkvMW/qBkz6O7KsfPwKDyAfNe6b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO4M6g/btsH7UDkvMW/qBkz6O7KsfPwKDyAfNe6b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO4M6g%2FbtsH7UDkvMW%2FqBkz6O7KsfPwKDyAfNe6b1%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;842&quot; height=&quot;297&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-b995b45cac474f1cab578dd9443fbd9a&quot;&gt;현재 공식 문서에서 말하는 Fine-Tuning이 가능한 모델은 이렇게 3가지가 존재하는데&lt;/li&gt;
&lt;li id=&quot;block-6c569f7a447449ccae2b8304af5d0ce5&quot;&gt;나는 애초에 gpt-3.5-turbo 모델을 사용하고 있기도 했고, 가장 권장하는 모델이기에 gpt-3.5-turbo-0613을 택했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote id=&quot;block-6a53b6e964d84ef6b3005ab01339f3e9&quot; data-ke-style=&quot;style3&quot;&gt;뒤에 0613은 해당 모델의 릴리즈 날짜인데, 이는 21년 6월 13일에 릴리즈 되었음을 뜻한다. 다음 모델이 릴리즈 되면 뒤에 숫자는 바뀔 수 있으니 참고하자.&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-76f044c21347497ab979f7192705adf9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;학습시킬 대화 형식을 담은 파일 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;block-296abe2d9a3b4d7f8e9bdf9f4e5317d3&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;학습파일&lt;/h4&gt;
&lt;p id=&quot;block-756e770fef364378b47104362d7345a0&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;학습 파일은 Completions API&lt;b&gt; &lt;/b&gt;형태이며, 무조건 jsonl 형식여야함&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-4120cebeafc446c386242926d14ca166&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;학습파일 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730810706478&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 java 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;자동차&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. car\n2. automobile\n3. vehicle\n4. motorcar\n5. auto\n6. ride\n7. wheels\n8. transport\n9. machine\n10. drive&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 python 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;자동차&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. car\n2. automobile\n3. vehicle\n4. motorcar\n5. auto\n6. ride\n7. wheels\n8. transport\n9. machine\n10. drive&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 java 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;게임결과&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. gameResult\n2. resultOfGame\n3. gameOutcome\n4. gameScore\n5. gameRecord\n6. gameStatistics\n7. gameAchievement\n8. gameProgress\n9. gameEvaluation\n10. gameSummary&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 python 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;게임결과&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. game_result\n2. result_game\n3. gameOutcome\n4. gameScore\n5. game_result_1\n6. game_result_2\n7. game_result_3\n8. game_result_4\n9. game_result_5\n10. game_result_final&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 javascript 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;던전입구&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. dungeonEntrance\n2. entranceToDungeon\n3. dungeonGate\n4. dungeonEntry\n5. dungeonAccess\n6. entranceOfDungeon\n7. dungeonIngress\n8. gatewayToDungeon\n9. dungeonOpening\n10. dungeonPortal&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 go 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;던전입구&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. dungeonEntrance\n2. entranceToDungeon\n3. dungeonGate\n4. dungeonEntry\n5. gateToDungeon\n6. dungeonAccess\n7. entranceOfDungeon\n8. dungeonIngress\n9. dungeonPortal\n10. dungeonWaypoint&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 typescript 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;던전입구&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. dungeonEntrance\n2. entranceToDungeon\n3. dungeonGate\n4. dungeonAccess\n5. dungeonEntry\n6. entranceOfDungeon\n7. dungeonIngress\n8. gatewayToDungeon\n9. dungeonOpening\n10. dungeonPortal&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 ruby 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;가게&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. store\n2. shop\n3. market\n4. boutique\n5. emporium\n6. outlet\n7. establishment\n8. storehouse\n9. mart\n10. bazaar&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 java 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;로또&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. lotto\n2. lottery\n3. luckyNumber\n4. drawResult\n5. jackpot\n6. winningTicket\n7. numberSelection\n8. prizeMoney\n9. ticketPurchase\n10. randomPick&quot;}]}
{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 python 언어 변수명 추천 전문가이다. 단어를 입력받으면 그 단어의 영어 변수명을 10개 생성하여 출력해라&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;유저 위치&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;1. user_location\n2. location_user\n3. userPos\n4. user_place\n5. userLoc\n6. position_user\n7. user_location_data\n8. user_location_info\n9. user_position\n10. user_geo&quot;}]}

//...&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;block-aa0ad580ea594de0b7aaba1f668c561a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-ac1a2f294bb74ac88ac82d071a2ebad4&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유효하지 않은 요청 처리 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730810715629&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{&quot;messages&quot;: [{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 java 언어로 작성된 코드를 받으면 이를 분석하여 설명 주석을 달아주는 코드 전문가이다. 만약 받은 코드가 java 언어의 문법이 아니거나 java라는 언어가 존재하지 않다면 X를 출력하고, 받은 코드가 java 언어로 작성된 것이 맞다면 각 코드 라인의 윗줄에 코드 기능 설명을 주석으로 언어 형식에 맞는 주석으로 달아라.&quot;}, {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;a = int(input())\nprint(a)\n&quot;}, {&quot;role&quot;: &quot;assistant&quot;, &quot;content&quot;: &quot;X&quot;}]}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-5b9fe428b1c24a2887a25bf2130cbef8&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 언급했던 유효하지 않은 요청에 대해서는 &lt;b&gt;&amp;lsquo;X&amp;rsquo; &lt;/b&gt;를 응답하도록 학습시켰다.&lt;/p&gt;
&lt;p id=&quot;block-39651cbb4ba344378b91a6506d725a67&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버 내에서는 응답값이 &lt;b&gt;&amp;lsquo;X&amp;rsquo;&lt;/b&gt; 이면 그에 따라 예외처리를 수행해줄 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-8cbd4e1a4ad745909a01b1fab932961f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;학습 파일 업로드 API 호출&lt;/h3&gt;
&lt;div id=&quot;block-1062505e4c92426f96d609dbe1f2c1d7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1730810729829&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl https://api.openai.com/v1/files \
-H &quot;Authorization: Bearer ${GPT-API-KEY}&quot; \
-F purpose=&quot;fine-tune&quot; \
-F file=&quot;@학습파일명.jsonl&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-ee27d2fd03b24b8292d85e57dc56c111&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;학습파일이 준비되었다면 학습파일 업로드 API(&lt;a href=&quot;https://api.openai.com/v1/files)&quot;&gt;https://api.openai.com/v1/files)&lt;/a&gt; 를 호출하여 준비된 학습파일을 OpenAI에 업로드해야한다.&lt;/p&gt;
&lt;blockquote id=&quot;block-3605a130ceca4a1fa961ab64b784c088&quot; data-ke-style=&quot;style2&quot;&gt;이때 GPT API KEY가 사용되는데, 이 KEY는 유료로 사용할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-df72187ef9074c73b50d73dce2159e6d&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;응답값&lt;/h3&gt;
&lt;div id=&quot;block-431bc3513801425baa3b0104046ac7b9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1730810739579&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;object&quot;: &quot;file&quot;,
  &quot;id&quot;: &quot;학습파일ID&quot;,
  &quot;purpose&quot;: &quot;fine-tune&quot;,
  &quot;filename&quot;: &quot;학습파일명.jsonl&quot;,
  &quot;bytes&quot;: 4372,
  &quot;created_at&quot;: 1697785733,
  &quot;status&quot;: &quot;uploaded&quot;,
  &quot;status_details&quot;: null
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-c200abdc3cdc43f0be27b67d448d876a&quot;&gt;여기서 사용할 내용은 학습파일ID이다.&lt;/li&gt;
&lt;li id=&quot;block-ea01a652709a46fbbc1b4a8843605e8b&quot;&gt;이는 OpenAI에 학습파일이 등록된 ID를 뜻하며 이를 통해 파인튜닝을 시작하거나 종료하는 등의 작업을 수행할 수 있다.&lt;/li&gt;
&lt;/ul&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-e90feb724dce4d7e9a524e9b290e110a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;파인튜닝 시작&lt;/h3&gt;
&lt;pre id=&quot;code_1730810748958&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl https://api.openai.com/v1/fine_tuning/jobs \
-H &quot;Content-Type: application/json&quot; \
-H &quot;Authorization: Bearer ${GPT-API-KEY}&quot; \
-d '{
&quot;training_file&quot;: &quot;학습파일ID&quot;,
&quot;model&quot;: &quot;gpt-3.5-turbo-0613&quot;
}'&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;block-f96f5010a822473e9efe83804159e170&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습파일ID과 FineTuning할 모델명을 포함한 curl요청으로 파인튜닝을 시작할 수 있다.&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;div id=&quot;block-6c7d498eb4864c8f8d2afe931e9b1941&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1029&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B48rE/btsH6QWdGjn/lmQvmLeA2ipJ4UVNoKlsuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B48rE/btsH6QWdGjn/lmQvmLeA2ipJ4UVNoKlsuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B48rE/btsH6QWdGjn/lmQvmLeA2ipJ4UVNoKlsuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB48rE%2FbtsH6QWdGjn%2FlmQvmLeA2ipJ4UVNoKlsuK%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;432&quot; height=&quot;347.28143&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1029&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-1b8daf90e62d4070ba24729bef1c7c99&quot;&gt;파인 튜닝의 진행상황은 OpenAI Playground에서 실시간 확인이 가능하다&lt;/li&gt;
&lt;li id=&quot;block-7ef737511c7b496b997cd21dec67428d&quot;&gt;위 FineTuning 과정은 Playground웹페이지에서 UI로 간편하게 진행이 가능하다.&lt;/li&gt;
&lt;/ul&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-5fe1704bcc6b40ffab6538cb82dda1a9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Fine-Tuning 완료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-86a78937def0451786650ece676a587d&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uPEMu/btsH6PQxo2m/P2N4kk3gaNkKQ408JTUpzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uPEMu/btsH6PQxo2m/P2N4kk3gaNkKQ408JTUpzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uPEMu/btsH6PQxo2m/P2N4kk3gaNkKQ408JTUpzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuPEMu%2FbtsH6PQxo2m%2FP2N4kk3gaNkKQ408JTUpzk%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;480&quot; height=&quot;194.14803&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-a6adce4d817a40cfa17cd7657879fee0&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Fine-Tuning이 완료되면 이렇게 튜닝된 모델의 이름이 출력된다!&lt;/p&gt;
&lt;p id=&quot;block-324291805d744041bb10b8432658cad9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 이 모델을 사용하기만 하면 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;block-ca8de314969f481f9f36563645d1fedb&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;Fine-Tuning gpt-3.5-turbo VS Default gpt-3.5-turbo 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-61d3e8b468ee4c93bff311cf7f886a02&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파인튜닝이 정상적으로 완료되어서 우리가 원하던 고정된 형태의 응답값을 얻을 수 있는 지 검증하기 위해서 기존의 GPT 기본 모델과 파인튜닝한 모델의 &lt;b&gt;&amp;lsquo;응답 형태&amp;rsquo;&lt;/b&gt; 를 비교해보았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; 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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-3edbd3e9dd7d4444ba30e5693e1bff22&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;변수명 추천&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-528189849cd440809e5f1e977d2bf16b&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-0df5a96f5e334996af62f1eae355f198&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zOw8a/btsH84kCFWH/RLpPsvbcKeMGmc7Ssg9F61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zOw8a/btsH84kCFWH/RLpPsvbcKeMGmc7Ssg9F61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zOw8a/btsH84kCFWH/RLpPsvbcKeMGmc7Ssg9F61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzOw8a%2FbtsH84kCFWH%2FRLpPsvbcKeMGmc7Ssg9F61%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;720&quot; height=&quot;406.257&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-40ab111c9d7240adbb69dae1b308c59c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[응답 형태]&lt;/b&gt;&lt;/p&gt;
&lt;p id=&quot;block-c733baada69c4667bbe1354643c36657&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;고정되지 않은 형태의 응답 형태이며 매 요청마다 달라진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-23bcbafaaa644c489a133c79c903b322&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo FineTuning 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-09a5ef51427c42409691edf0754fc7e7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yZoGu/btsH8zFteyQ/u8H2iYiXvewS6DBcVNpAc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yZoGu/btsH8zFteyQ/u8H2iYiXvewS6DBcVNpAc1/img.png&quot; width=&quot;918.671875&quot; height=&quot;249.87875000000003&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;348&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.4701%; margin-right: 10px;&quot; data-widthpercent=&quot;33.24&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yZoGu/btsH8zFteyQ/u8H2iYiXvewS6DBcVNpAc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyZoGu%2FbtsH8zFteyQ%2Fu8H2iYiXvewS6DBcVNpAc1%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;1280&quot; height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efPOVq/btsH6MsPc8Q/0BGkNmViPk8rmf7tOKKvxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efPOVq/btsH6MsPc8Q/0BGkNmViPk8rmf7tOKKvxk/img.png&quot; width=&quot;918.6718139648438&quot; height=&quot;271.7639551967611&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;378&quot; data-is-animation=&quot;false&quot; style=&quot;width: 29.8931%; margin-right: 10px;&quot; data-widthpercent=&quot;30.6&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efPOVq/btsH6MsPc8Q/0BGkNmViPk8rmf7tOKKvxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefPOVq%2FbtsH6MsPc8Q%2F0BGkNmViPk8rmf7tOKKvxk%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;1280&quot; height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjcRnL/btsH8NpUo89/UwtttwrZEB5BLZHinw8qA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjcRnL/btsH8NpUo89/UwtttwrZEB5BLZHinw8qA0/img.png&quot; width=&quot;918.6718139648438&quot; height=&quot;229.66795349121094&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;320&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.3112%;&quot; data-widthpercent=&quot;36.16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjcRnL/btsH8NpUo89/UwtttwrZEB5BLZHinw8qA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjcRnL%2FbtsH8NpUo89%2FUwtttwrZEB5BLZHinw8qA0%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;1280&quot; height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-31c86f057661403ebf9a2a89f723f9dd&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;block-acbb6e309e844c9e99abf0bfa271ff42&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p id=&quot;block-e9ee01a190e841a1a71845946f13454d&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[응답 형태]&lt;/b&gt;&lt;/p&gt;
&lt;p id=&quot;block-0e823ea8416f468193746654e14217ed&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모든 요청에서 항상 고정된 형태의 응답 형태이며, 언어의 종류에 따라 각 언어의 변수명 네이밍 패턴에 맞게 작명한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-fe98747f932d4675b51c8ed735f495c7&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;코드 리팩터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-c3638d6fb77d464992ff1615e72b7c8b&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-921327cc06614c348513fa36c51f0c53&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Q2yy/btsH8yzOujG/2CTrck5jAn6ODc7HiohLYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Q2yy/btsH8yzOujG/2CTrck5jAn6ODc7HiohLYK/img.png&quot; width=&quot;2430&quot; height=&quot;1002&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;528&quot; data-is-animation=&quot;false&quot; style=&quot;width: 55.5293%; margin-right: 10px;&quot; data-widthpercent=&quot;56.18&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Q2yy/btsH8yzOujG/2CTrck5jAn6ODc7HiohLYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Q2yy%2FbtsH8yzOujG%2F2CTrck5jAn6ODc7HiohLYK%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;1280&quot; height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BBF3t/btsH7A6aSPN/vUQrMJKWT2XHCGgAC5ctl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BBF3t/btsH7A6aSPN/vUQrMJKWT2XHCGgAC5ctl0/img.png&quot; width=&quot;2448&quot; height=&quot;1296&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;677&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.3079%;&quot; data-widthpercent=&quot;43.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BBF3t/btsH7A6aSPN/vUQrMJKWT2XHCGgAC5ctl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBBF3t%2FbtsH7A6aSPN%2FvUQrMJKWT2XHCGgAC5ctl0%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;1280&quot; height=&quot;677&quot;/&gt;&lt;/span&gt;&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;block-511e46dcc3d74658ae45cd1e07533561&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 id=&quot;block-e54adede186140debe01bd05ecf69e77&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo FineTuning 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-85fd2f6ec8564d36ba39c187ac1016fe&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLNOb/btsH7hZ4Xg2/QnqRga8hfLRdYIHo5gVKD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLNOb/btsH7hZ4Xg2/QnqRga8hfLRdYIHo5gVKD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLNOb/btsH7hZ4Xg2/QnqRga8hfLRdYIHo5gVKD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLNOb%2FbtsH7hZ4Xg2%2FQnqRga8hfLRdYIHo5gVKD0%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;918.6718&quot; height=&quot;363.26566&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-a1c24a48e1bb41eaa292901df78cc81a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터셋을 통해 리팩토링된 코드를 출력하고 마지막에 리팩토링 내용 요약을 출력하도록 학습시켰다. 한 눈에 들어오는 깔끔하고 통일성있는 응답결과로 변한 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-bec68d02678f428093cbecca3b34b2ef&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo FineTuning 모델 - 오류 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-e1709828f5814070aa3a05355e2d4cec&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rtpTG/btsH845ZVu0/jTqxYu5wzbjbAXKZljjJFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rtpTG/btsH845ZVu0/jTqxYu5wzbjbAXKZljjJFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rtpTG/btsH845ZVu0/jTqxYu5wzbjbAXKZljjJFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrtpTG%2FbtsH845ZVu0%2FjTqxYu5wzbjbAXKZljjJFK%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;918.6718&quot; height=&quot;261.51425&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-cc51d735ee00468faed456372e3d4e9e&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모델을 학습하는 데이터셋에 유저가 전송한 코드와 언어가 매칭되지 않으면 &amp;lsquo;X&amp;rsquo;라는 응답 결과만을 Response하도록 학습시켰다.&lt;/p&gt;
&lt;p id=&quot;block-cee67ee58e064b3d98f2a71995932c6f&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버에서는 요청 결과로 &amp;lsquo;X&amp;rsquo;를 Response받으면 다음과 같이 예외를 발생시키도록 구현하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; 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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-bef2daa0153a4484ba19c10b898f321a&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;주석 달기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-380547e0092646ad8b0c09d02bfba720&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-bedc243acc7b4db8ae015731f0fec527&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9asgY/btsH7gmBjhz/hRtrtgZwhLLht7sknN72q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9asgY/btsH7gmBjhz/hRtrtgZwhLLht7sknN72q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9asgY/btsH7gmBjhz/hRtrtgZwhLLht7sknN72q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9asgY%2FbtsH7gmBjhz%2FhRtrtgZwhLLht7sknN72q0%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;2408&quot; height=&quot;1144&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;block-605db7b733ce46d3a44e413b09a9d889&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo FineTuning 모델&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-c7abf05e70494304baed9917e36a84e9&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebjbaL/btsH8k9CtDp/NGRExise731XoOdFWyDHd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebjbaL/btsH8k9CtDp/NGRExise731XoOdFWyDHd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebjbaL/btsH8k9CtDp/NGRExise731XoOdFWyDHd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebjbaL%2FbtsH8k9CtDp%2FNGRExise731XoOdFWyDHd0%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;918.6718&quot; height=&quot;382.7188&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-74a99624f3fb43c3b10ae52fb5d365ee&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;설명 주석 달기 기능은 &amp;ldquo;입니다.&amp;rdquo; 등의 표현없이 단답으로 깔끔하게 응답하도록 만든 부분 외에 많이 다른 점이 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-7ee6798c431b47d596987d0374d85d27&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;gpt-3.5-turbo FineTuning 모델 - 오류 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-5df17f0ce8c54f8c8fe26f1da88bcb72&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzxwtB/btsH8SEvSoZ/NeDXbSlf9UdsH1M9XmCEe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzxwtB/btsH8SEvSoZ/NeDXbSlf9UdsH1M9XmCEe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzxwtB/btsH8SEvSoZ/NeDXbSlf9UdsH1M9XmCEe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzxwtB%2FbtsH8SEvSoZ%2FNeDXbSlf9UdsH1M9XmCEe1%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;918.6718&quot; height=&quot;262.91187&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-57bcb0fcfb1b48b6954ccadf4d533642&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리팩토링 기능의 오류처리와 동일하게 모델을 학습하는 데이터셋에 유저가 전송한 코드와 언어가 매칭되지 않으면 서버에서는 다음과 같은 예외를 발생시키도록 구현하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-92d433b9ab6c46a4a44130a998dc8e57&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;사용 비용 (Cost)&lt;/h2&gt;
&lt;p id=&quot;block-47433065cbd84b8885c29c6db70ee202&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;FineTuning을 하면, 튜닝 중에 발생하는 초기 훈련 비용과 사용 비용 두 가지의 비용이 분류된다.&lt;/p&gt;
&lt;p id=&quot;block-11b3a07c405c41b3b59904909ee321cb&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래에는 공식문서에서 볼 수 있는 가격표이니 확인하길 바란다.&lt;/p&gt;
&lt;div id=&quot;block-556e16e639304a1ebef9bc617ed7987c&quot; style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GqJd2/btsH6Y0JFK8/KdU61xiRk7CaDbgTPfEkYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GqJd2/btsH6Y0JFK8/KdU61xiRk7CaDbgTPfEkYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GqJd2/btsH6Y0JFK8/KdU61xiRk7CaDbgTPfEkYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGqJd2%2FbtsH6Y0JFK8%2FKdU61xiRk7CaDbgTPfEkYK%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;624&quot; height=&quot;241.5&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;495&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>BackEnd</category>
      <category>chat completion</category>
      <category>chat completion api</category>
      <category>GPT</category>
      <category>gpt api</category>
      <category>gpt api 응답값</category>
      <category>gpt api 응답형태 고정</category>
      <category>gpt completion api</category>
      <category>gpt finetuning</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/164</guid>
      <comments>https://pgmjun.tistory.com/164#entry164comment</comments>
      <pubDate>Fri, 21 Jun 2024 15:54:23 +0900</pubDate>
    </item>
    <item>
      <title>스프링부트 S3 PreSignedURL을 통한 이미지 저장</title>
      <link>https://pgmjun.tistory.com/163</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJxKaf/btsH6RGwlk5/ozjqKgc2cV9VMRfnwl4bD0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJxKaf/btsH6RGwlk5/ozjqKgc2cV9VMRfnwl4bD0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJxKaf/btsH6RGwlk5/ozjqKgc2cV9VMRfnwl4bD0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJxKaf%2FbtsH6RGwlk5%2FozjqKgc2cV9VMRfnwl4bD0%2Fimg.jpg&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;289&quot; height=&quot;350&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1549&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;block-ca9d0453f9c5465880389fe4142d3580&quot; data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;적용을 고려한 이유&lt;/h2&gt;
&lt;p id=&quot;block-758697c5188e4b0ca31bb245d3ffe1c2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;우리집 반려동물의 AI 프로필 사진관&amp;rdquo; 펫튜디오 프로젝트를 개발하는 과정에서 반려동물 AI 프로필 사진을 만들기 위해 Stable Diffusion 모델에 학습시킬 10~12장의 이미지를 S3에 저장해야하는 상황이 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-d06b0c00035a4d9397c59c3127f91432&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 이러한 경우에 클라이언트&amp;rarr;서버 방향으로 이미지 파일을 전송하고 서버&amp;rarr;S3 방향으로 이미지를 저장시키는 구조를 대체적으로 많이 봐왔을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-a6ec1ba80aa54c5fbf11bb2a1224ab9e&quot; data-ke-size=&quot;size16&quot;&gt;하지만 10장~12장이나 되는 이미지를 서버로 전송하는 일은 일반적은 요청과는 다르게 서버에 가하는 부하가 컷고, 금전적인 이슈로 AWS 프리티어의 t2.micro를 사용 중인 현재 인프라 환경에서 최대한 부하를 줄이는 작업은 불가피했다.&lt;/p&gt;
&lt;p id=&quot;block-3fc17068d5424d43be9a41ec35c524a3&quot; data-ke-size=&quot;size16&quot;&gt;때문에 이미지 저장의 책임을 클라이언트 측에 위임하여 서버에 가해지는 부하를 감소시키고자 PreSignedURL 방식을 도입하기로 결정하였다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-41d61e88b21443919ac535abcc1aba20&quot; data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;PreSignedURL이란?&lt;/h2&gt;
&lt;p id=&quot;block-5eb71544dfa9428bb1fe169371036058&quot; data-ke-size=&quot;size16&quot;&gt;PreSignedURL은 S3 버킷의 특정 디렉토리URL에 대하여 미리 접근에 대한 &lt;b&gt;서명&lt;/b&gt;(권한을 부여하는 일이라고 생각하면 된다)을 받은 후, 서명된 URL,&lt;b&gt; &lt;/b&gt;즉 PreSignedURL&lt;b&gt; &lt;/b&gt;을 사용자에게 넘겨주어 특정 권한에 대한 요구없이 S3에 파일을 업로드할 수 있는 방식이다.&lt;/p&gt;
&lt;p id=&quot;block-4dc3222de2a6478ca969c4d328267a60&quot; data-ke-size=&quot;size16&quot;&gt;원래는 S3에 파일을 업로드하려면 S3 관련 권한을 가진 IAM 또는 루트 계정으로 접근해야 하지만, 미리 &lt;b&gt;서명&lt;/b&gt;을 통해 권한을 가진 PreSignedURL을 사용하면 권한에 대한 증명없이 &lt;b&gt;서명된 S3 버킷 디렉토리 경로&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;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-86f13ab258994341a9dee8e5934cddb3&quot; data-ke-size=&quot;size23&quot;&gt;미리 권한을 부여한 URL이면 보안적으로 안 좋지 않을까?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-30 오전 1.08.39.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vqCtU/btsH7nsLEqT/6K1zUfOCsUlK4PQCBMSrBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vqCtU/btsH7nsLEqT/6K1zUfOCsUlK4PQCBMSrBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vqCtU/btsH7nsLEqT/6K1zUfOCsUlK4PQCBMSrBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvqCtU%2FbtsH7nsLEqT%2F6K1zUfOCsUlK4PQCBMSrBk%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;1204&quot; height=&quot;296&quot; data-filename=&quot;스크린샷 2024-01-30 오전 1.08.39.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;296&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 id=&quot;block-5436526698464c24a8cbdfea82e29b98&quot; data-ke-size=&quot;size16&quot;&gt;PreSignedURL은 생성 시에 만료 일자를 정할 수 있기 때문에 안전한 관리가 가능하다.&lt;/p&gt;
&lt;p id=&quot;block-a54285a6e56b47a2ac81693d22004134&quot; data-ke-size=&quot;size16&quot;&gt;우리가 활용할 IAM 사용자는 AWS 서명 버전 4를 사용할 경우 최대 7일 동안 유효하다고 문서에 적혀있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 최댓값인 7일을 다 사용할 건 아니고, 업로드에 필요한 시간에서 조금 넉넉하게 잡은 &lt;b&gt;1~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;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-c22a07b7cced435d89bcf399cecf4830&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 id=&quot;block-9f28183a1f064f41b4bc44c77068c608&quot; data-ke-size=&quot;size23&quot;&gt;업로드된 파일의 소유권&lt;/h3&gt;
&lt;p id=&quot;block-405e6b8dc3244d6a8543198478ee3ae4&quot; data-ke-size=&quot;size16&quot;&gt;PreSignedURL 방식을 통해 권한이 없는 유저도 S3에 파일을 올릴 수 있지만, 파일의 소유권은 버킷 소유자가 객체를 소유하게 된다.&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 id=&quot;block-9e8ede298ad14872a31d7a5d8d804ec8&quot; data-ke-size=&quot;size23&quot;&gt;AWS Docs&lt;/h3&gt;
&lt;p id=&quot;block-361849ed91484033b89a699e454dc868&quot; 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;div id=&quot;block-7d29a35192bb4b4dacfb9b0c3458dc1b&quot;&gt;
&lt;figure id=&quot;og_1718957586234&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;미리 서명된 URL을 통해 객체 공유 - Amazon Simple Storage Service&quot; data-og-description=&quot;미리 서명된 URL을 통해 객체 공유 미리 서명된 URL을 사용하여 다른 사람이 Amazon S3 버킷에 객체를 업로드하도록 허용할 수 있습니다. 미리 서명된 URL을 사용하면 상대방에게&amp;nbsp;AWS&amp;nbsp;보안 자격 증명&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html&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;미리 서명된 URL을 통해 객체 공유 - Amazon Simple Storage Service&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;미리 서명된 URL을 통해 객체 공유 미리 서명된 URL을 사용하여 다른 사람이 Amazon S3 버킷에 객체를 업로드하도록 허용할 수 있습니다. 미리 서명된 URL을 사용하면 상대방에게&amp;nbsp;AWS&amp;nbsp;보안 자격 증명&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;block-e19a4c9d00b14a38a14671050a899a94&quot; data-ke-size=&quot;size26&quot;&gt;♻️&amp;nbsp;PreSignedURL 동작 플로우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-acaefab8a3514fa6ad6bd4bdd94def97&quot; data-ke-size=&quot;size16&quot;&gt;펫튜디오에선 클라이언트에서 PreSignedURL을 요청하는 API를 호출하면, 로직을 통해 &lt;b&gt;서명된 URL&lt;/b&gt;과 &lt;b&gt;저장될 Image의 경로&lt;/b&gt; 를 클라이언트에게 전송할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-ba3e23d8cb20481485109a67a0bc177d&quot; 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;div id=&quot;block-8df4efc82c8f432eae96cf5fc7325c66&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJLpCm/btsH6dJ1ir4/sqRNKmgk0q82UYjBzKIYS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJLpCm/btsH6dJ1ir4/sqRNKmgk0q82UYjBzKIYS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJLpCm/btsH6dJ1ir4/sqRNKmgk0q82UYjBzKIYS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJLpCm%2FbtsH6dJ1ir4%2FsqRNKmgk0q82UYjBzKIYS0%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;1191&quot; height=&quot;479&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;block-8a0b088f6ce1488b9f02f258c9adb78e&quot; data-ke-size=&quot;size23&quot;&gt;1. PreSignedURL 요청&lt;/h3&gt;
&lt;p id=&quot;block-c691c43c7cef4c07ba8d44f7cf254520&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트에서 파일 저장을 위해 S3 관련 권한을 가지고 있는 WAS에게 PreSignedURL 발급을 요청한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-0fdb19988ea94a4a9e9366901b559edd&quot;&gt;PreSignedURL은 &lt;u&gt;각 파일 별로 생성&lt;/u&gt;해야 하기 때문에 2개 이상의 파일을 저장할 예정이라면, 해당 과정에서 &lt;u&gt;&lt;b&gt;파일 개수&lt;/b&gt;&lt;/u&gt;에 대한 정보를 WAS에 함께 넘긴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-a161af2c0ead46a1ac0f4d57d84ca2c7&quot; data-ke-size=&quot;size23&quot;&gt;2. PreSignedURL 생성 요청&lt;/h3&gt;
&lt;p id=&quot;block-e0e6dc4434d44110a0eb3da3b57d8094&quot; data-ke-size=&quot;size16&quot;&gt;WAS는 &lt;b&gt;&lt;u&gt;파일 개수&lt;/u&gt;&lt;/b&gt;&lt;u&gt;에 따른 파일을 저장할 S3 버킷 디렉토리 경로&lt;/u&gt;를 지정 후, 요청에 담아 S3에 PreSignedURL 생성을 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-12743ac9a3ff4870956a753ee800aefd&quot; data-ke-size=&quot;size23&quot;&gt;3. PreSignedURL 발급&lt;/h3&gt;
&lt;p id=&quot;block-7ac528abf72843c39494180679f0a7d7&quot; data-ke-size=&quot;size16&quot;&gt;S3는 WAS의 권한을 확인 후, 요청받은 &lt;u&gt;S3 버킷 디렉토리 경로&lt;/u&gt;에 대한 PreSignedURL을 발급하여 WAS에게 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-d3560024d3fa43fd963c2bcae7ae63fd&quot; data-ke-size=&quot;size23&quot;&gt;4. 발급받은 PreSignedURL, ImageURI, S3DirectoryPath 전달&lt;/h3&gt;
&lt;p id=&quot;block-809289ced8a144cea2c20040bd10ff3a&quot; data-ke-size=&quot;size16&quot;&gt;WAS는 발급받은 PreSignedURL과 ImageURI, S3DirectoryPath 를 클라이언트에게 전달한다.&lt;/p&gt;
&lt;p id=&quot;block-cf418642dc94437f95e0a4b45c2fc83a&quot; data-ke-size=&quot;size16&quot;&gt;여기서 ImageURI 란, 이미지가 S3에 저장되었을 때 해당 이미지에 접근할 수 있는 주소이다. WAS는 이미지 저장의 역할을 Client에게 넘기기 때문에 이미지가 정상적으로 저장되었는 지 확인할 방법이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-51393ad810a7433fb91b45a33dcf7eec&quot; data-ke-size=&quot;size16&quot;&gt;때문에 클라이언트가 PreSignedURL을 통해 이미지를 저장한 후, 저장에 성공한 Image의 URI를 서버에게 전달하여 해당 이미지가 업로드에 성공하였음을 전달하도록 하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-f1488c3a239643f4be50fd9a983fe1af&quot;&gt;ImageURI는 클라이언트로부터 다시 전달받은 후에 DB에 저장되어 모델 파인튜닝 등에 적극적으로 활용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;block-87c113897ceb456da38ff376b6a02d32&quot; data-ke-size=&quot;size16&quot;&gt;S3DirectoryPath는 이미지가 저장될 S3의 디렉토리 경로이다. 해당 디렉토리 안에 이미지들이 저장되며, 문제 발생 시 이미지 삭제 요청 등에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-32b3c30eff3d4755a0b6c200ee0fa99d&quot; data-ke-size=&quot;size23&quot;&gt;5. PreSignedURL을 통해 이미지 업로드&lt;/h3&gt;
&lt;div id=&quot;block-4ffda4d798dc4d6097e23c1c622ea597&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y9jZ0/btsH5IqfuAZ/ARUdfayfOE1uqcdsye2CCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y9jZ0/btsH5IqfuAZ/ARUdfayfOE1uqcdsye2CCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y9jZ0/btsH5IqfuAZ/ARUdfayfOE1uqcdsye2CCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY9jZ0%2FbtsH5IqfuAZ%2FARUdfayfOE1uqcdsye2CCk%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;672&quot; height=&quot;158.62949&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p id=&quot;block-038fab430fb3442892cb8f7bf77efb87&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트는 PreSignedURL을 통해 이미지를 업로드한다. 이미지 업로드는 PreSignedURL에 PUT 메서드로 바이너리 파일을 전송한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-939a408ca2594c8cb420ddf9a5a2e264&quot;&gt;만약 이때 &lt;b&gt;업로드 실패 등의 오류 발생 시&lt;/b&gt; 클라이언트에선 오류를 발생에 대한 처리를 수행 후, 4번 과정에서 받은 S3DirectoryPath를 WAS의 이미지 삭제 API에 요청하여 해당 디렉토리와 디렉토리에 저장된 이미지를 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-536872c7c350493aae78909ed38bd15a&quot; data-ke-size=&quot;size23&quot;&gt;6. ImageURI, S3DirectoryPath 전달&lt;/h3&gt;
&lt;p id=&quot;block-de48c25a128d41c3a5b5c1a1a09af225&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트는 이미지를 업로드 후 ImageURI와 S3DirectoryPath를 WAS에게 전달한다.&lt;/p&gt;
&lt;p id=&quot;block-8e7d1f75e775476489a29dc4af2ff963&quot; data-ke-size=&quot;size16&quot;&gt;WAS는 해당 데이터를 활용하여 애완동물 정보를 저장하고, 이후 이미지 관련 작업에 활용한다.&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;div id=&quot;block-7d29a35192bb4b4dacfb9b0c3458dc1b&quot; style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;block-1470874e55654060ae6d4e5f9c712b39&quot; data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;앱에서 직접 S3에 업로드 VS 서버에서 PreSignedURL 발급받아 업로드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-be70ffa101144f7c9b4164b7a5d0c523&quot; data-ke-size=&quot;size16&quot;&gt;PreSignedURL을 도입하기로 했던 개발 초기에는 이런 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote id=&quot;block-7081f01f20aa465e892e2192d13f13eb&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;서버에서 PreSignedURL을 발급받지 않고 앱에서 직접 S3에 업로드하는 방식을 사용하면 PreSignedURL을 발급하는 복잡한 코드를 작성하지 않고 속도도 더욱 개선되지 않을까?&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-ede35a511ea540039b07569968e1253b&quot; data-ke-size=&quot;size16&quot;&gt;이러한 고민을 해결하기 위해 인터넷 여러 글들을 찾아본 결과, 위처럼 굳이 서버에 PreSignedURL을 요청하지 않고 앱에서 직접 S3에 업로드하는 방식 또한 가능하다는 것을 알게 되어 고민을 시작하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-e0836205d89e4f789ed1ab8aa5a89f8a&quot; data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면 본인은 이 문제에 대한 깊은 고민의 끝에 서버에서 PreSignedURL을 발급받아 업로드하는 방식을 선택했고, 같은 고민을 할 누군가를 위해 내 생각을 기록해두고자 한다.&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 id=&quot;block-34934f6804df4e6797e0e4584d4fef0c&quot; data-ke-size=&quot;size23&quot;&gt;왜 서버에서 PreSignedURL을 발급하도록 구현했을까?&lt;/h3&gt;
&lt;p id=&quot;block-04d2b8f0b80e4bc7ab8bc8bbf856a24d&quot; data-ke-size=&quot;size16&quot;&gt;S3에 접근하기 위해선 S3에 대한 Access 권한을 가진 AWS IAM의 사용자 정보를 사용자의 로컬에 직접적으로 설치되는 앱에 포함하여 개발해야한다.&lt;/p&gt;
&lt;p id=&quot;block-8f15393547674cb7ae9cb03931caba3a&quot; data-ke-size=&quot;size16&quot;&gt;하지만 앞으로 만들어질 AI프로필 이미지와 사용자가 등록한 본인의 애완동물 사진 등의 주요 정보가 담길 S3에 대한 주요 권한 정보를 앱에 지니게 하는 것은 보안적으로 큰 문제를 불러일으킬 수 있을 것 같다고 생각했다.&lt;/p&gt;
&lt;p id=&quot;block-44a458cb7b9a49bab6021bce6ff3b181&quot; data-ke-size=&quot;size16&quot;&gt;때문에 앱보다 훨씬 안전한 환경인 WAS에 S3에 대한 주요 정보를 관리하면서 필요할 때 API 호출을 통해 PreSignedURL을 발급할 수 있도록 구현하기로 결정하였다.&lt;/p&gt;
&lt;div id=&quot;block-7d29a35192bb4b4dacfb9b0c3458dc1b&quot; style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;block-1f9e81e6d2604f61b7ff3dbf937b0ec0&quot; data-ke-size=&quot;size26&quot;&gt;✨&amp;nbsp;구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-015bafe84c494f749feed8b7c1870a93&quot; data-ke-size=&quot;size23&quot;&gt;S3 버킷 생성&lt;/h3&gt;
&lt;div id=&quot;block-844028b209e143979dc80187e18a1eb7&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhlLIe/btsH5P32AoY/O5NKrtxdNwU9q8vijTyguk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhlLIe/btsH5P32AoY/O5NKrtxdNwU9q8vijTyguk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhlLIe/btsH5P32AoY/O5NKrtxdNwU9q8vijTyguk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhlLIe%2FbtsH5P32AoY%2FO5NKrtxdNwU9q8vijTyguk%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;872&quot; height=&quot;510&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ap-northeast-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;div id=&quot;block-20f4ea921819490cac7b249d40e84296&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnLCAs/btsH6P20G1O/8J4kZfJCJGP5X9Vfi42IXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnLCAs/btsH6P20G1O/8J4kZfJCJGP5X9Vfi42IXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnLCAs/btsH6P20G1O/8J4kZfJCJGP5X9Vfi42IXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnLCAs%2FbtsH6P20G1O%2F8J4kZfJCJGP5X9Vfi42IXK%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;793&quot; height=&quot;552&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-4f13bff68b6a4055a502744f1a8c5868&quot;&gt;ACL 비활성화됨 선택&lt;/li&gt;
&lt;li id=&quot;block-f1ac266cdbc4468ea131b68982f6b283&quot;&gt;버킷 소유자 선호&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote id=&quot;block-26cff675a6714f85859cff5661458016&quot; data-ke-style=&quot;style3&quot;&gt; &amp;nbsp;해당 설정을 해주지 않으면 ACL 관련 400번대 오류가 발생&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;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-5c6f85c289794834a88481e7eaf6265d&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rH7G7/btsH7y7klbD/avgcw37H0v1MoaL5iXDGkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rH7G7/btsH7y7klbD/avgcw37H0v1MoaL5iXDGkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rH7G7/btsH7y7klbD/avgcw37H0v1MoaL5iXDGkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrH7G7%2FbtsH7y7klbD%2Favgcw37H0v1MoaL5iXDGkk%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;798&quot; height=&quot;646&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-bb65735e837e4db4af775f795dac2666&quot;&gt;외부에서 이미지 URI를 통해 이미지에 접근하도록 할 예정이므로, 버킷을 퍼블릭 엑세스로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;block-89e7faac694246ec97f51cd88e69ad88&quot; 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-015bafe84c494f749feed8b7c1870a93&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;S3 버킷 권한 설정&lt;/h3&gt;
&lt;div id=&quot;block-5d84eda6b40a469398d6718f1b5a80e1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/muVXD/btsH6dDhX2p/sDkJCt4Lce1x3OieOkvkl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/muVXD/btsH6dDhX2p/sDkJCt4Lce1x3OieOkvkl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/muVXD/btsH6dDhX2p/sDkJCt4Lce1x3OieOkvkl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmuVXD%2FbtsH6dDhX2p%2FsDkJCt4Lce1x3OieOkvkl0%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;638&quot; height=&quot;357&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;PreSignedURL을 활용하기 위해선 만들어진 버킷의 권한을 수정해야함.&lt;/span&gt;&lt;/p&gt;
&lt;/div&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;div id=&quot;block-6a1e85dd6ac24c0e8eba684acc7cec82&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;937&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OOqMy/btsH7FehVOW/IfYui2mOSTn7VQrW3FZjKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OOqMy/btsH7FehVOW/IfYui2mOSTn7VQrW3FZjKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OOqMy/btsH7FehVOW/IfYui2mOSTn7VQrW3FZjKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOOqMy%2FbtsH7FehVOW%2FIfYui2mOSTn7VQrW3FZjKk%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;695&quot; height=&quot;509&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;937&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;block-3187e50130d94c10a8b94de1e847e758&quot;&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: [
                &quot;s3:Put*&quot;,
                &quot;s3:Get*&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:s3:::{현재_버킷_이름}/*&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-1172ba42b8384f7dba9ba8cfb889fe5f&quot;&gt;버킷이 Put 또는 Get 작업을 허용하도록 정책 설정&lt;/li&gt;
&lt;/ul&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;div id=&quot;block-7d4d342c0a4c4f13848d996599ec0cb3&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1069&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqWndc/btsH7jvMJgW/i1DsaVBEk1nnvLEolC6UDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqWndc/btsH7jvMJgW/i1DsaVBEk1nnvLEolC6UDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqWndc/btsH7jvMJgW/i1DsaVBEk1nnvLEolC6UDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqWndc%2FbtsH7jvMJgW%2Fi1DsaVBEk1nnvLEolC6UDK%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;724&quot; height=&quot;605&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1069&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div id=&quot;block-0cf9fb988204445181a49ff8f897fd98&quot;&gt;&lt;br /&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;[
    {
        &quot;AllowedHeaders&quot;: [
            &quot;*&quot;
        ],
        &quot;AllowedMethods&quot;: [
            &quot;HEAD&quot;,
            &quot;GET&quot;,
            &quot;PUT&quot;,
            &quot;POST&quot;
        ],
        &quot;AllowedOrigins&quot;: [
            &quot;*&quot;
        ],
        &quot;ExposeHeaders&quot;: [
            &quot;ETag&quot;
        ]
    }
]&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;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HEAD, GET, PUT, POST 메서드에 대해 CORS 설정&lt;/p&gt;
&lt;blockquote id=&quot;block-fee5da14ac5c44b0bbcb40d89ceebbb9&quot; data-ke-style=&quot;style3&quot;&gt;해당 설정을 해주지 않으면 CORS 에러 발생&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-fdd689ed66f343a4845f995334a00dec&quot; data-ke-size=&quot;size23&quot;&gt;IAM 생성&lt;/h3&gt;
&lt;p id=&quot;block-b73ab7e809c440b2b12c877e1d1f4313&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;b&gt;S3&lt;/b&gt;에 PreSignedURL 요청을 가능하게 하기 위한 권한을 얻기 위해 AWS IAM을 통해 S3FullAccess 권한을 가진 사용자를 생성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-5c2189ee1a0646ecb4c3fab572a3f2ca&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFvEbi/btsH5P32AnG/RMNNmcTOtbcWA6pBIP2ck0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFvEbi/btsH5P32AnG/RMNNmcTOtbcWA6pBIP2ck0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFvEbi/btsH5P32AnG/RMNNmcTOtbcWA6pBIP2ck0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFvEbi%2FbtsH5P32AnG%2FRMNNmcTOtbcWA6pBIP2ck0%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;1769&quot; height=&quot;534&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;AWS IAM에서 사용자 생성 클릭&lt;/span&gt;&lt;/p&gt;
&lt;/div&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;div id=&quot;block-eeb12c520fb644d6a44e220be4c45a74&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qeOJv/btsH6dDhX0h/7MtMuSk0BMJ1djxKy0pRDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qeOJv/btsH6dDhX0h/7MtMuSk0BMJ1djxKy0pRDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qeOJv/btsH6dDhX0h/7MtMuSk0BMJ1djxKy0pRDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqeOJv%2FbtsH6dDhX0h%2F7MtMuSk0BMJ1djxKy0pRDk%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;1440&quot; height=&quot;624&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;S3FullAccess 권한 선택&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;block-5627be84b7af4813b406ec288fc94e04&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkbGzZ/btsH5WBMNDx/UH4uKUR9MOUexYUoDlvjQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkbGzZ/btsH5WBMNDx/UH4uKUR9MOUexYUoDlvjQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkbGzZ/btsH5WBMNDx/UH4uKUR9MOUexYUoDlvjQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkbGzZ%2FbtsH5WBMNDx%2FUH4uKUR9MOUexYUoDlvjQ0%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;1490&quot; height=&quot;813&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&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;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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;block-a3a69fdccf6f4d1780ad3ce17cd3324c&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boLdJO/btsH7gZ8AOF/BbH5lj7hDhiDDaYmx47cak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boLdJO/btsH7gZ8AOF/BbH5lj7hDhiDDaYmx47cak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boLdJO/btsH7gZ8AOF/BbH5lj7hDhiDDaYmx47cak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboLdJO%2FbtsH7gZ8AOF%2FBbH5lj7hDhiDDaYmx47cak%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;500&quot; height=&quot;81&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&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;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;/div&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;div id=&quot;block-1d3554adbe4848cd99caef3398928fd1&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BPNoo/btsH7hrcMi8/1JcuN6Eq03Q74YBAkq2xF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BPNoo/btsH7hrcMi8/1JcuN6Eq03Q74YBAkq2xF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BPNoo/btsH7hrcMi8/1JcuN6Eq03Q74YBAkq2xF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBPNoo%2FbtsH7hrcMi8%2F1JcuN6Eq03Q74YBAkq2xF1%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;1464&quot; height=&quot;129&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&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;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;/div&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;div id=&quot;block-ccdbb70d7c3f4e84b190d502823624c5&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ubrq9/btsH5OjHBmV/axAnJicyLIGnCAu1cvRRHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ubrq9/btsH5OjHBmV/axAnJicyLIGnCAu1cvRRHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ubrq9/btsH5OjHBmV/axAnJicyLIGnCAu1cvRRHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbrq9%2FbtsH5OjHBmV%2FaxAnJicyLIGnCAu1cvRRHK%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;972&quot; height=&quot;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;사용 사례는 CLI 선택&lt;/span&gt;&lt;/p&gt;
&lt;/div&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;div id=&quot;block-0977bdf302c94a6aa5d0eaa34c2e9106&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIa6T1/btsH6kWLnyp/FKuKorbK7w6cBKwviWGbK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIa6T1/btsH6kWLnyp/FKuKorbK7w6cBKwviWGbK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIa6T1/btsH6kWLnyp/FKuKorbK7w6cBKwviWGbK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIa6T1%2FbtsH6kWLnyp%2FFKuKorbK7w6cBKwviWGbK1%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;1187&quot; height=&quot;375&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&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;h3 id=&quot;block-9d8c4afadc8345f0bb6a4d97bc90bfee&quot; data-ke-size=&quot;size23&quot;&gt;build.gralde&lt;/h3&gt;
&lt;div id=&quot;block-ccfea533a41c489581d8b23705f2b4a4&quot;&gt;
&lt;pre class=&quot;clean&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;// AWS
implementation &quot;org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring-cloud-starter-aws 라이브러리 &lt;b&gt;implementation&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;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-1153f2718ea1446885c090a59fe2e2a7&quot; data-ke-size=&quot;size23&quot;&gt;application.yml&lt;/h3&gt;
&lt;div id=&quot;block-645e2d57a47140cab6ede79e8cefcd51&quot;&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;cloud:
  aws:
    credentials:
      accessKey: ${AWS_ACCESS_KEY_DEV} # S3 관련 권한을 가진 AWS IAM Access Key
      secretKey: ${AWS_SECRET_KEY_DEV} # S3 관련 권한을 가진 AWS IAM Secret Key
    s3:
      bucket: ${AWS_S3_BUCKET_NAME} # S3 버킷 이름
      path: ${AWS_S3_FILE_DEFAULT_PATH} # S3에서 이미지 저장에 활용할 기본 경로
    region:
      static: ${AWS_REGION_DEV} # AWS Region
    stack:
      auto: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;에 PreSignedURL 구현에 필요한 환경변수 등록&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 id=&quot;block-2d63adce506e446baff15d82f528fb36&quot; data-ke-size=&quot;size23&quot;&gt;S3Config&lt;/h3&gt;
&lt;div id=&quot;block-ad21c008539544b9964b85da2ba92201&quot;&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;@Configuration
public class S3Config {
    @Value(&quot;${cloud.aws.credentials.accessKey}&quot;)
    private String accessKey;
    @Value(&quot;${cloud.aws.credentials.secretKey}&quot;)
    private String secretKey;
    @Value(&quot;${cloud.aws.region.static}&quot;)
    private String region;

    @Bean
    public AmazonS3 amazonS3() {
        BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
        return AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-5c260f8234c84f919ff5d65b869c9c07&quot;&gt;&lt;b&gt;S3Config&lt;/b&gt;에서 &lt;b&gt;S3FullAccess&lt;/b&gt; 권한을 가진 사용자의 accessKey, secretKey와 region을 통해 &lt;b&gt;AmazonS3&lt;/b&gt; Bean 생성&lt;/li&gt;
&lt;li id=&quot;block-a0e6157a4f3d49709119d395f6862788&quot;&gt;&lt;b&gt;AmazonS3&lt;/b&gt; Bean을 PreSignedURL 생성에 사용&lt;/li&gt;
&lt;/ul&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 id=&quot;block-a46b9b2b134149c69cfb821d54c2b1a2&quot; data-ke-size=&quot;size23&quot;&gt;S3FileService&lt;/h3&gt;
&lt;div id=&quot;block-39eeee76a80b4a7f8ca46d4d1837ba48&quot;&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class S3FileService {
    private final AmazonS3 amazonS3;

    @Value(&quot;${cloud.aws.s3.bucket}&quot;)
    private String bucketName;
    @Value(&quot;${cloud.aws.s3.path}&quot;)
    private String s3DefaultPath;
    private static final String BUCKET_DIRECTORY_NAME = &quot;petProfile&quot;;
    private static final String FILE_NAME_PREFIX = &quot;Petudio_&quot;;


    /**
     * presigned url 발급
     *
     * @param s3DirectoryPath 파일을 저장할 S3 디렉토리 경로
     * @param index           이미지 순서
     * @return presigned url
     */
    public String getPreSignedUrl(String s3DirectoryPath, int index) {
        try {
            String filePath = createFilePath(s3DirectoryPath, index);

            GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(bucketName,
                    filePath);
            URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
            return url.toString();
        } catch (Exception exception) {
            throw new BadGatewayException(ErrorCode.BAD_GATEWAY_EXCEPTION,
                    String.format(&quot;S3 버킷의 디렉토리(%s/%d) 에 대한 PreSignedURL을 생성하는 과정에서 에러가 발생하였습니다.&quot;, s3DirectoryPath, index));
        }
    }

    /**
     * 파일의 전체 경로를 생성
     *
     * @param index 이미지 순서
     * @return 파일의 전체 경로
     */
    private String createFilePath(String directoryPath, int index) {
        // 경로: {BUCKET_DIRECTORY_NAME}/{memberId}/Petudio_{fileId}/{index}
        return String.format(&quot;%s/%d&quot;, directoryPath, index);
    }

    /**
     * 파일 업로드용(PUT) presigned url 생성
     *
     * @param bucket   버킷 이름
     * @param filePath S3 업로드용 파일 경로
     * @return presigned url
     */
    private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String bucket, String filePath) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
                new GeneratePresignedUrlRequest(bucket, filePath)
                        .withMethod(HttpMethod.PUT)
                        .withExpiration(getPreSignedUrlExpiration());
        generatePresignedUrlRequest.addRequestParameter(
                Headers.S3_CANNED_ACL,
                CannedAccessControlList.PublicRead.toString());
        return generatePresignedUrlRequest;
    }

    /**
     * presigned url 유효 기간 설정
     *
     * @return 유효기간
     */
    private Date getPreSignedUrlExpiration() {
        Date expiration = new Date();
        long expTimeMillis = expiration.getTime();
        expTimeMillis += 1000 * 60 * 2;
        expiration.setTime(expTimeMillis);
        return expiration;
    }

    /**
     * 파일을 저장할 디렉토리 경로를 생성
     *
     * @param memberId 회원 Primary Key
     * @return 파일의 전체 경로
     */
    public String createS3DirectoryPath(Long memberId) {
        return String.format(&quot;%s/%d/%s&quot;, BUCKET_DIRECTORY_NAME, memberId, FILE_NAME_PREFIX + createFileId());
    }

    /**
     * 파일 고유 ID를 생성
     *
     * @return 36자리의 UUID
     */
    private String createFileId() {
        return UUID.randomUUID().toString();
    }

    /**
     * s3 파일 접근 URI 생성
     *
     * @param filePath S3 업로드용 파일 경로
     * @return s3 파일 접근 URI
     */
    public String createImageUri(String filePath) {
        return s3DefaultPath + filePath;
    }


    /**
     * s3 Directory 데이터 삭제
     *
     * @param s3DirectoryPath 파일 삭제를 위한 S3 디렉토리 경로
     */
    public void deleteImagesByS3DirectoryPath(String s3DirectoryPath) {
        try {
            // s3DirectoryPath에 속한 객체들 나열
            ListObjectsV2Request listObjectsRequest = new ListObjectsV2Request()
                    .withBucketName(bucketName)
                    .withPrefix(s3DirectoryPath);
            ListObjectsV2Result objectsResult = amazonS3.listObjectsV2(listObjectsRequest);

            // s3DirectoryPath 내의 객체들 삭제
            for (S3ObjectSummary objectSummary : objectsResult.getObjectSummaries()) {
                String key = objectSummary.getKey();
                amazonS3.deleteObject(new DeleteObjectRequest(bucketName, key));
            }
        } catch (Exception exception) {
            throw new BadGatewayException(ErrorCode.BAD_GATEWAY_EXCEPTION,
                    String.format(&quot;S3 버킷의 디렉토리(%s) 내부 데이터를 삭제하는 과정에서 에러가 발생하였습니다.&quot;, s3DirectoryPath));
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3의 파일을 다루기 위한 객체로 &lt;b&gt;PreSignedURL 생성&lt;/b&gt;, &lt;b&gt;S3 파일 삭제&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;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-5c8f732d147f428e97ef1cd6cbff2661&quot; data-ke-size=&quot;size23&quot;&gt;PetController&lt;/h3&gt;
&lt;div id=&quot;block-a781ab5a10d8416db2244e75fccaf24c&quot;&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;    // 반려동물 이미지 저장용 S3 PreSignedURL 요청
    @GetMapping(&quot;/pet/images/presigned-url&quot;)
    public ApiResponse&amp;lt;CreatePetImagesUploadUrlsResponse&amp;gt; createPreSignedUrlForSavePetImages(
            @MemberId final Long memberId, @RequestBody @Valid final CreatePetImagesUploadUrlsRequest request) {
        return ApiResponse.success(petQueryService.createPreSignedUrlForSavePetImages(memberId, request));
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PreSignedURL 요청을 받는 Controller 로직이다.&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 id=&quot;block-20ae5ae9bdd4497c85677bf701c27766&quot; data-ke-size=&quot;size23&quot;&gt;CreatePetImagesUploadUrlsRequest&lt;/h3&gt;
&lt;div id=&quot;block-546ea5ce6135418aaa54dc24fc9533d8&quot;&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;public record CreatePetImagesUploadUrlsRequest(
        @Min(value = 10, message = &quot;이미지 갯수는 10개 이상, 12개 이하이어야 합니다.&quot;)
        @Max(value = 12, message = &quot;이미지 갯수는 10개 이상, 12개 이하이어야 합니다.&quot;)
        short imageAmount
) {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이미지는 10~12개 이어야하기 때문에 Validation 로직 구현&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 id=&quot;block-b4aea935346d46a487bcd38e1d83db69&quot; data-ke-size=&quot;size23&quot;&gt;PetService&lt;/h3&gt;
&lt;div id=&quot;block-e267ec869ee54ac1ad89ca45a361f36d&quot;&gt;
&lt;pre class=&quot;haxe&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;public CreatePetImagesUploadUrlsResponse createPreSignedUrlForSavePetImages(Long memberId,
                                                                                CreatePetImagesUploadUrlsRequest request) {
        String s3DirectoryPath = s3FileService.createS3DirectoryPath(memberId);
        List&amp;lt;PetImageUploadInfo&amp;gt; petImageUploadInfos = new ArrayList&amp;lt;&amp;gt;();
        
        // s3Directory 경로를 통해, 요청받은 이미지 갯수 만큼 PreSignedURL, ImageURI 생성  
        for (int i = 1; i &amp;lt;= request.imageAmount(); i++) {
            String s3FilePath = s3DirectoryPath + &quot;/&quot; + i;
            petImageUploadInfos.add(
                    new PetImageUploadInfo(s3FileService.getPreSignedUrl(s3DirectoryPath, i),
                            s3FileService.getImageUri(s3FilePath)));
        }

        return new CreatePetImagesUploadUrlsResponse(s3DirectoryPath, petImageUploadInfos);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p id=&quot;block-308a16d1b49043c193c504fc8a365118&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;입력받은 이미지 개수만큼 PreSignedURL, ImageURI를 생성하기 위한 Service 메서드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;block-e13522053772453aa6d3afd621ab4f95&quot;&gt;S3FileService의 createS3DirectoryPath를 활용하여 파일을 저장할 디렉토리 경로 &lt;b&gt;s3DirectoryPath&lt;/b&gt;를 생성 후, 슬래시(/) + index 를 덧붙여 각 파일의 &lt;b&gt;s3filePath&lt;/b&gt;를 생성한다.&lt;/li&gt;
&lt;li id=&quot;block-b22922bf8ea941f9910e499d336afe8c&quot;&gt;그리고 &lt;b&gt;s3DirectoryPath, s3filePath &lt;/b&gt;를 활용하여 PreSignedURL, ImageURI를 생성하고 만들어진 경로와 s3DirectoryPath를 &lt;b&gt;CreatePetImagesUploadUrlsResponse&lt;/b&gt; DTO 객체에 담아 보낸다.&lt;/li&gt;
&lt;/ul&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 id=&quot;block-98b860e2b785420e92d02181c0649035&quot; data-ke-size=&quot;size23&quot;&gt;CreatePetImagesUploadUrlsResponse&lt;/h3&gt;
&lt;div id=&quot;block-7ce44291fbc344ffba934b770e0f75ed&quot;&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;public record CreatePetImagesUploadUrlsResponse(
        String s3DirectoryPath,
        List&amp;lt;PetImageUploadInfo&amp;gt; petImageUploadInfos) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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 id=&quot;block-1eea91c84d134392b6572450b5bd98ac&quot; data-ke-size=&quot;size23&quot;&gt;PetImageUploadInfo&lt;/h3&gt;
&lt;div id=&quot;block-37adc905c2244fdfa4e0ab5acd999ba5&quot;&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background-color: #000000; color: #000000; text-align: left;&quot;&gt;&lt;code&gt;public record PetImageUploadInfo(String preSignedUrl, String imageUri) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-5aeac2abe44a48c1a13ab068788c663b&quot; data-ke-size=&quot;size26&quot;&gt;✅ 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;block-b2183d81debd4e8db1f6f34211eabdee&quot; data-ke-size=&quot;size23&quot;&gt;Postman&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;block-b1dc1d6b16c94022bacefc148c91f71a&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctuqce/btsH7yzt8ED/C5OgwSOe2qJlCmszfBzZdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctuqce/btsH7yzt8ED/C5OgwSOe2qJlCmszfBzZdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctuqce/btsH7yzt8ED/C5OgwSOe2qJlCmszfBzZdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fctuqce%2FbtsH7yzt8ED%2FC5OgwSOe2qJlCmszfBzZdk%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;528&quot; height=&quot;381.36374&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-5de38f16db6a47349c5d2800250692da&quot; data-ke-size=&quot;size16&quot;&gt;Postman으로 해당 API에 요청을 보낸 결과 이렇게 10개의 PreSignedURL이 정상적으로 호출되는 것을 확인할 수 있었다.&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 id=&quot;block-419dd0342f9b44a1a18323287a3f19f4&quot; data-ke-size=&quot;size23&quot;&gt;S3&lt;/h3&gt;
&lt;div id=&quot;block-5e7a383c83044438b18665daacd56947&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceb2IU/btsH6B40WDK/U9AXCoHXQokTWbxfqyKhUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceb2IU/btsH6B40WDK/U9AXCoHXQokTWbxfqyKhUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceb2IU/btsH6B40WDK/U9AXCoHXQokTWbxfqyKhUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fceb2IU%2FbtsH6B40WDK%2FU9AXCoHXQokTWbxfqyKhUK%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;1938&quot; height=&quot;200&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-f9b2aa9afe104a3cb216169dfb4af8ac&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트에서 PreSignedURL을 통해 S3에 이미지 저장을 시도해보았는데 이미지가 정상적으로 저장되었음을 확인할 수 있었다.&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 id=&quot;block-ebc7b8d0fc6b41898ee7dd5650afecd4&quot; data-ke-size=&quot;size23&quot;&gt;ImageUri&lt;/h3&gt;
&lt;div id=&quot;block-a7a8549377d842d6b9e78b6d5b97658b&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdV8IJ/btsH6LzCpZu/GfRW9n8YctUQHDFKAscK80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdV8IJ/btsH6LzCpZu/GfRW9n8YctUQHDFKAscK80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdV8IJ/btsH6LzCpZu/GfRW9n8YctUQHDFKAscK80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdV8IJ%2FbtsH6LzCpZu%2FGfRW9n8YctUQHDFKAscK80%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;240&quot; height=&quot;240.78432&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p id=&quot;block-c06eba64cd3542a3b62aa0b7b6f9a5a3&quot; data-ke-size=&quot;size16&quot;&gt;PreSignedURL 요청 시에 함께 받은 imageURI를 통해서도 정상적으로 이미지에 접근할 수 있었다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;block-e8d56cef48cf4496b19fee5e649f85fd&quot; data-ke-size=&quot;size26&quot;&gt;추후 개선 방향&lt;/h2&gt;
&lt;p id=&quot;block-507d390c3d334c949d401a354111a3a2&quot; data-ke-size=&quot;size16&quot;&gt;지금으로도 괜찮은 것 같다고 생각했지만, 문제가 되는 부분이 아직 남아있었다.&lt;/p&gt;
&lt;p id=&quot;block-fb54488d03ba4cb596e4c97ef173074d&quot; data-ke-size=&quot;size16&quot;&gt;PreSignedURL을 통한 이미지 저장에 실패하여, &lt;b&gt;삭제 API&lt;/b&gt;가 요청되었을 때 만약 &lt;b&gt;삭제 API &lt;/b&gt;또한 실패한다면 잘못 저장된 이미지는 계속해서&lt;b&gt; S3&lt;/b&gt;에 남아있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;block-6e9e0375364f4cba9c2046f93150a51f&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제 해결을 위한 개선 방향을 고민할 필요가 있을 것 같다.&lt;/p&gt;
&lt;p id=&quot;block-f712738fbc6c4d37a8dbbe22997e10ce&quot; data-ke-size=&quot;size16&quot;&gt;향후에 개선이 완료된다면 이곳에 기록하겠다.&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>aws presignedurl</category>
      <category>aws s3</category>
      <category>presignedUrl</category>
      <category>S3</category>
      <category>s3 presignedurl</category>
      <category>s3 springboot</category>
      <category>스프링 presignedurl</category>
      <category>스프링 S3</category>
      <category>스프링 s3 presignedurl</category>
      <category>스프링부트 presignedurl</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/163</guid>
      <comments>https://pgmjun.tistory.com/163#entry163comment</comments>
      <pubDate>Fri, 21 Jun 2024 01:04:48 +0900</pubDate>
    </item>
    <item>
      <title>H2 DB - 인메모리(In-Memory) 모드</title>
      <link>https://pgmjun.tistory.com/162</link>
      <description>&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;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RE24G/btsH6xuwUnO/KuJZtZakR0uzMF42tYsXp0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RE24G/btsH6xuwUnO/KuJZtZakR0uzMF42tYsXp0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RE24G/btsH6xuwUnO/KuJZtZakR0uzMF42tYsXp0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRE24G%2FbtsH6xuwUnO%2FKuJZtZakR0uzMF42tYsXp0%2Fimg.jpg&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;377&quot; height=&quot;377&quot; data-origin-width=&quot;500&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;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H2 DB를 공부하면서 H2를 In-Memory 모드로 사용할 수 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(H2 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;이 In-Memory 모드를 사용하면 개발환경 및 테스트 환경에서 유용하게 사용할 수 있을 것 같다는 생각에 학습하여 적용해보았고,&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;인메모리 모드 (In-Memory Mode)&lt;/h2&gt;
&lt;p 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;H2의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;인 메모리 모드&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;영속이&amp;nbsp;&lt;/span&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;H2 DB는 그러한 상황을 위해 데이터가 영속되지 않는 인 메모리 모드를 지원한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인메모리 모드는 모든 Connection이 close되면, DB도 close되며 DB에 요청된 CRUD 작업에 대한 내용이 영속되지 않고 모두 휘발된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;H2에 대해 자세히 알기 이전에는 사람들이 왜 테스트 DB로 H2를 선택하는 지 몰랐지만,&lt;br /&gt;RDB와 똑같이 사용할 수 있지만 모드를 지원한다는 점과, 인메모리 모드로 사용함으로써 얻을 수 있는 성능상의 이점만으로&lt;br /&gt;테스트 DB로 사용할만하다고 생각이 들었다. (테스트 속도는 빠를 수록 좋으니까)&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;In-Memory Mode의 2가지 종류&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 인 메모리 모드는 2가지 종류가 있고 DB URL을 통해 설정이 가능하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 2가지 모드에 대해 알아보자.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;bull; 1 Connection per 1 Database 모드&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 모드를 적용하는 경우, 동일한 JVM 내에서 두 개의 커넥션을 열면 각각의 커넥션은 서로 다른 DB를 사용할 수 있다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;  해당 모드를 사용하면 각각의 커넥션이 각각의 DB를 사용하기 때문에 성능면에서 단점이 있어보였다.&lt;br /&gt;커넥션 별로 다른 DB를 사용해야하는 특수한 상황이 아니라면 사용을 지양하는 편이 좋겠다고 생각이 든다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;bull; N Connections per 1 Database&lt;span&gt;&amp;nbsp;&lt;/span&gt;모드&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 커넥션을 통해 하나의 DB에 접근하려면 해당 모드를 선택하면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 모드를 적용하는 경우, 동일한 JVM 내에서 두 개의 커넥션을 여는 경우 두 커넥션은 하나의 같은 DB에 접근한다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;  굳이 커넥션 단위로 DB를 분리할 이유가 없다면, 이 N Connections per 1 Database 모드를 사용하는 것을 추천한다.&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;In-Memory Mode 의 장단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB를 설치하고 DB 서버를 실행할 필요없이 build.gradle에서 의존성만 추가해주어도 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;모드에 따라 커넥션 단위로 각각의 DB를 사용하게 할 수 있는 등, 세부 옵션이 존재한다.&lt;/li&gt;
&lt;li&gt;In-Memory 모드는 매우 빠른 초기화 및 쿼리 성능을 제공하므로, 테스트 및 개발 환경에서 신속한 반복 작업이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 JVM을 사용하는 분산환경에서 자원 공유가 불가능하다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;메모리 관리는 JVM 내에서 수행하기 때문에 Embedded 모드와 마찬가지로&lt;span&gt; &lt;/span&gt;&lt;/span&gt;동일한 DB에 접근하는 것은 오직 같은 JVM 내에서만 가능하다.&lt;/li&gt;
&lt;li&gt;JVM의 메모리를 사용하기 때문에 메모리가 부족한 환경에서 OOM(Out Of Memory)를 유발할 수 있다.&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;적용 방법&lt;/h2&gt;
&lt;p 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;size18&quot;&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;dependencies {
    runtimeOnly 'com.h2database:h2' // h2 의존성 추가
}
&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;bull; 1 Connection per 1 Database 모드&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem
    username: sa
    password:
&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;bull; N Connections per 1 Database 모드&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:이름
    username: sa
    password:
&lt;/code&gt;&lt;/pre&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;b&gt;application.properties&lt;/b&gt;의 datasource URL 설정 값을 다음과 같이 설정한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게만 설정해주면 H2DB 설치 없이 인 메모리 방식으로 H2 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;figure id=&quot;og_1718874716268&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;Features&quot; data-og-description=&quot;&amp;nbsp; Features Feature List H2 in Use Connection Modes Database URL Overview Connecting to an Embedded (Local) Database In-Memory Databases Database Files Encryption Database File Locking Opening a Database Only if it Already Exists Closing a Database Ignore &quot; data-og-host=&quot;www.h2database.com&quot; data-og-source-url=&quot;https://www.h2database.com/html/features.html#in_memory_databases&quot; data-og-url=&quot;https://www.h2database.com/html/features.html#in_memory_databases&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dtrHu1/hyWoMw5LkZ/BdOTh7f50cCukxGiTut1cK/img.png?width=806&amp;amp;height=480&amp;amp;face=0_0_806_480,https://scrap.kakaocdn.net/dn/8CeQ6/hyWoJ1qEdc/tU9iZk7jcQDXAkRQDAinh0/img.png?width=752&amp;amp;height=436&amp;amp;face=0_0_752_436,https://scrap.kakaocdn.net/dn/cdVFBk/hyWoNv1KUM/gOW7nlx90h1VKRmOqsg0f0/img.png?width=416&amp;amp;height=519&amp;amp;face=112_213_310_411&quot;&gt;&lt;a href=&quot;https://www.h2database.com/html/features.html#in_memory_databases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.h2database.com/html/features.html#in_memory_databases&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dtrHu1/hyWoMw5LkZ/BdOTh7f50cCukxGiTut1cK/img.png?width=806&amp;amp;height=480&amp;amp;face=0_0_806_480,https://scrap.kakaocdn.net/dn/8CeQ6/hyWoJ1qEdc/tU9iZk7jcQDXAkRQDAinh0/img.png?width=752&amp;amp;height=436&amp;amp;face=0_0_752_436,https://scrap.kakaocdn.net/dn/cdVFBk/hyWoNv1KUM/gOW7nlx90h1VKRmOqsg0f0/img.png?width=416&amp;amp;height=519&amp;amp;face=112_213_310_411');&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;Features&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Features Feature List H2 in Use Connection Modes Database URL Overview Connecting to an Embedded (Local) Database In-Memory Databases Database Files Encryption Database File Locking Opening a Database Only if it Already Exists Closing a Database Ignore&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.h2database.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;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/DB</category>
      <category>H2 DB</category>
      <category>h2 In-memory</category>
      <category>h2 in-memory mode</category>
      <category>h2 인메모리</category>
      <category>h2 인메모리 모드</category>
      <category>인메모리 DB</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/162</guid>
      <comments>https://pgmjun.tistory.com/162#entry162comment</comments>
      <pubDate>Thu, 20 Jun 2024 17:45:37 +0900</pubDate>
    </item>
    <item>
      <title>H2 DB - 3가지 Connection 모드</title>
      <link>https://pgmjun.tistory.com/161</link>
      <description>&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;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HOCIL/btsH5eiAix5/4XnwaK9oCT4HIjrYwV2eyk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HOCIL/btsH5eiAix5/4XnwaK9oCT4HIjrYwV2eyk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HOCIL/btsH5eiAix5/4XnwaK9oCT4HIjrYwV2eyk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHOCIL%2FbtsH5eiAix5%2F4XnwaK9oCT4HIjrYwV2eyk%2Fimg.jpg&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;354&quot; height=&quot;354&quot; data-origin-width=&quot;500&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;&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;우테코 레벨2의 미션을 수행하면서 로컬 DB로 H2를 사용하게 되었는데, 신기한 점이 있어 기록해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H2는 DB를 작동시키지 않아도 사용이 가능했던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwJLRA/btsH5YzbqL4/LI6IBiDscIOUeO0z7Plfv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwJLRA/btsH5YzbqL4/LI6IBiDscIOUeO0z7Plfv1/img.png&quot; style=&quot;width: 38.4823%; margin-right: 10px;&quot; data-widthpercent=&quot;38.94&quot; data-filename=&quot;스크린샷 2024-04-21 23.40.58.png&quot; data-origin-height=&quot;502&quot; data-origin-width=&quot;868&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwJLRA/btsH5YzbqL4/LI6IBiDscIOUeO0z7Plfv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwJLRA%2FbtsH5YzbqL4%2FLI6IBiDscIOUeO0z7Plfv1%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;868&quot; height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvkiOp/btsH5XfYhFz/T6YEeSLoxqmMKUDwNZguL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvkiOp/btsH5XfYhFz/T6YEeSLoxqmMKUDwNZguL1/img.png&quot; style=&quot;width: 60.3549%;&quot; data-widthpercent=&quot;61.06&quot; data-filename=&quot;스크린샷 2024-04-21 23.42.04.png&quot; data-origin-height=&quot;236&quot; data-origin-width=&quot;640&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvkiOp/btsH5XfYhFz/T6YEeSLoxqmMKUDwNZguL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvkiOp%2FbtsH5XfYhFz%2FT6YEeSLoxqmMKUDwNZguL1%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;640&quot; height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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 data-ke-size=&quot;size16&quot;&gt;위 코드는 실제로 DB에 접근하는 통합 테스트인데, &lt;br /&gt;로컬에 H2 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;어떻게 그럴 수 있는 지에 대해서 H2 DB의 3가지 모드를 살펴보면서 알아보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;H2 DB의 3가지 Connection Mode&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H2는 다른 DB와 다르게 3가지 연결 모드를 가지고 있다는 특징이 있다.&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;1096&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ELovq/btsH5VigFqM/1iaWCMBJ4lLGSxybpnmFy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ELovq/btsH5VigFqM/1iaWCMBJ4lLGSxybpnmFy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ELovq/btsH5VigFqM/1iaWCMBJ4lLGSxybpnmFy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FELovq%2FbtsH5VigFqM%2F1iaWCMBJ4lLGSxybpnmFy1%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;697&quot; height=&quot;107&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;168&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;H2 DB는 위와 같이 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;그 중 &lt;b&gt;Embedded Mode&lt;/b&gt; 와 &lt;b&gt;Mixed&amp;nbsp;Mode&lt;/b&gt; 는 H2 DB 를 설치해서 서버를 실행할 필요없이&lt;br /&gt;스프링부트에서 h2db 의존성만 build.gradle에 추가해주면&lt;br /&gt;그 의존성만으로 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;나는 Embedded Mode를 사용하고 있었기 때문에 DB 설치 및 DB 서버 실행 과정을 거치지 않고 H2 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;그럼 이 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;임베디드 모드 (Embedded Mode)&lt;/h2&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;416&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtjqYj/btsH6vXC4aL/MRLy9E11mdc9SLeedrzatk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtjqYj/btsH6vXC4aL/MRLy9E11mdc9SLeedrzatk/img.png&quot; data-alt=&quot;H2 Embedded Mode&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtjqYj/btsH6vXC4aL/MRLy9E11mdc9SLeedrzatk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtjqYj%2FbtsH6vXC4aL%2FMRLy9E11mdc9SLeedrzatk%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;243&quot; height=&quot;303&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;H2 Embedded Mode&lt;/figcaption&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;임베디드 모드는 JDBC를 사용하여, JVM 내에 DB를 열게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embedded Mode를 사용하면 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Embedded Mode의 장단점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;H2가 제공하는 모드들 중에 가장 빠르고 가장 쉬운 커넥션 모드이다.&lt;/li&gt;
&lt;li&gt;H2를 설치하지 않고 build.gradle에서 의존성만 추가해주어도 H2 DB를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Embedded Mode의 DB는 JVM(및 클래스 로더) 당, 단 하나만 열려있을 수 있다. 데이터베이스가 JVM의 내부에 직접 포함되어 있기 때문에 다른 JVM에서는 접근할 수 없는 것이다.&lt;/li&gt;
&lt;li&gt;때문에 분산 환경에서는 Embedded Mode 를 사용하는 것은 좋지 않은 선택일 것이다.&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;적용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;dependencies {
    runtimeOnly 'com.h2database:h2' // h2 의존성 추가
}
&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;size18&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2://path/name
    username: sa
    password:
&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버 모드 (Server-Mode)&lt;/h2&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;752&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cybH9i/btsH6Q8hYxE/TkEbUBwDqAZQxjq3e21Ur1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cybH9i/btsH6Q8hYxE/TkEbUBwDqAZQxjq3e21Ur1/img.png&quot; data-alt=&quot;H2 Server Mode&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cybH9i/btsH6Q8hYxE/TkEbUBwDqAZQxjq3e21Ur1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcybH9i%2FbtsH6Q8hYxE%2FTkEbUBwDqAZQxjq3e21Ur1%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;461&quot; height=&quot;267&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;H2 Server Mode&lt;/figcaption&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;h2는 h2 DB의 Server를 통해 DB를 사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 소개한 In-Memory 모드와 Embedded 모드는 build.gradle에서 의존성만 추가해주면&lt;br /&gt;메모리 내에서, 또는 애플리케이션 내에서 정상적으로 작동했지만&lt;br /&gt;H2를 DB Server를 통해 사용하려면 DB가 완전히 외부 의존성으로 분리되므로 H2를 직접 설치하고 서버를 가동시켜주어야 접근이 가능해진다.&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;h3 data-ke-size=&quot;size23&quot;&gt;적용 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;dependencies {
    runtimeOnly 'com.h2database:h2' // h2 의존성 추가
}
&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;size18&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:tcp://localhost/path/name
    username: sa
    password:
&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;DB의 URL에 tcp를 붙여 &lt;b&gt;h2:tcp://localhost/{path}/{name}&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;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;혼합 모드 (Mixed-Mode)&lt;/h2&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;806&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Keb1o/btsH7ndvhD8/eWL8Q1aEScc2HEWZuVSPlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Keb1o/btsH7ndvhD8/eWL8Q1aEScc2HEWZuVSPlk/img.png&quot; data-alt=&quot;H2 Mixed Mode&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Keb1o/btsH7ndvhD8/eWL8Q1aEScc2HEWZuVSPlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKeb1o%2FbtsH7ndvhD8%2FeWL8Q1aEScc2HEWZuVSPlk%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;525&quot; height=&quot;313&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;H2 Mixed Mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;혼합 모드는 임베디드 모드와 서버 모드가 합쳐진 모드이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;때문에 이름이 Mixed Mode 인 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;혼합 모드를 사용하면 먼저 실행된 애플리케이션은 &lt;b&gt;'임베디드'&lt;/b&gt; 모드로 DB와 연결되지만,&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;다른 프로세스 또는 JVM에서 실행중인 &lt;/span&gt;애플리케이션이 동일한 데이터에 동시에 엑세스 할 수 있도록 &lt;b&gt;'DB 서버'&lt;/b&gt;를 실행시킬 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;즉, 애플리케이션이 DB서버의 호스팅을 담당하는 것이다.&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;혼합 모드 DB 서버는 'Server API 사용'을 통해 애플리케이션 내에서 또는 종료 또는 시작시킬 수 있으며,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;'Automatic Mixed Mode'를 사용하면 자동으로 시작 또는 종료시킬 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;Automatic Mixed Mode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Automatic Mixed Mode를 사용하면, DB에 연결하려고 하는 모든 클라이언트는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;로컬이던 리모트이던 상관없이 완전히 동일한 DB URL을 사용해서 연결을 수행할 수 있다.&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;&amp;nbsp;&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;build.gradle&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;dependencies {
    runtimeOnly 'com.h2database:h2' // h2 의존성 추가
}
&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;size18&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/${dbName};AUTO_SERVER=TRUE
    username: sa
    password:&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;데이터베이스 URL 끝에 &lt;b&gt;AUTO_SERVER=TRUE&lt;/b&gt; 옵션을 추가해주면 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게만 설정해주면 다른 애플리케이션에서도&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;DB Connection URL에 &lt;b&gt;`jdbc:h2:~/${dbName};AUTO_SERVER=TRUE`&lt;/b&gt; 을 사용하여 같은 DB에 접근할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;dbName.lock.db 파일&lt;/h4&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;588&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S4u75/btsH6ixyPBP/c4QkU8PtKsaOOCbsr3uAYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S4u75/btsH6ixyPBP/c4QkU8PtKsaOOCbsr3uAYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S4u75/btsH6ixyPBP/c4QkU8PtKsaOOCbsr3uAYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS4u75%2FbtsH6ixyPBP%2Fc4QkU8PtKsaOOCbsr3uAYK%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;588&quot; height=&quot;86&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Mixed Mode로 실행시키면, url로 설정한 경로에 dbName.lock.db 라는 파일이 생성된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; 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-filename=&quot;스크린샷 2024-06-20 17.31.31.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEjrsR/btsH7xfXHtT/2yNLLCQuIbQSIJEjKvIToK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEjrsR/btsH7xfXHtT/2yNLLCQuIbQSIJEjKvIToK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEjrsR/btsH7xfXHtT/2yNLLCQuIbQSIJEjKvIToK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEjrsR%2FbtsH7xfXHtT%2F2yNLLCQuIbQSIJEjKvIToK%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;434&quot; height=&quot;144&quot; data-filename=&quot;스크린샷 2024-06-20 17.31.31.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;144&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;dbName.lock.db 파일을 살펴보면 DB서버의 &lt;b&gt;Private IP&lt;/b&gt;와 &lt;b&gt;Port&lt;/b&gt;를 확인할 수 있다.&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-filename=&quot;스크린샷 2024-06-20 17.32.51.png&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lEEXT/btsH6P2HGaS/VngqS8CRYcbi4H4ShXvaX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lEEXT/btsH6P2HGaS/VngqS8CRYcbi4H4ShXvaX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lEEXT/btsH6P2HGaS/VngqS8CRYcbi4H4ShXvaX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlEEXT%2FbtsH6P2HGaS%2FVngqS8CRYcbi4H4ShXvaX1%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;705&quot; height=&quot;73&quot; data-filename=&quot;스크린샷 2024-06-20 17.32.51.png&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;118&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;b&gt;'lsof -i :PORT'&lt;/b&gt; 명령으로 터미널에서 조회하면 정상적으로 작동중인 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;&amp;nbsp;&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;/h4&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-filename=&quot;스크린샷 2024-06-20 17.35.44.png&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;946&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2d5zN/btsH52Ph5Mr/mcQ8qiCGHNMOJFBDChFHI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2d5zN/btsH52Ph5Mr/mcQ8qiCGHNMOJFBDChFHI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2d5zN/btsH52Ph5Mr/mcQ8qiCGHNMOJFBDChFHI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2d5zN%2FbtsH52Ph5Mr%2FmcQ8qiCGHNMOJFBDChFHI1%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;548&quot; height=&quot;391&quot; data-filename=&quot;스크린샷 2024-06-20 17.35.44.png&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;946&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;Mixed Mode를 사용하면 정말 로컬이 아닌 Remote에서도 접근이 되는 지 확인해보았다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Mixed Mode의 장단점&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&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;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;혼합 모드를 사용하면 로컬에서의 연결은 임베디드 모드를 사용했을 때처럼 빠르다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&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;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;혼합 모드를 사용하면&lt;span&gt; 로컬이 아닌 다른 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;연결은 로컬 연결에 비해 조금 느리다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;figure id=&quot;og_1718898485256&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;Features&quot; data-og-description=&quot;&amp;nbsp; Features Feature List H2 in Use Connection Modes Database URL Overview Connecting to an Embedded (Local) Database In-Memory Databases Database Files Encryption Database File Locking Opening a Database Only if it Already Exists Closing a Database Ignore &quot; data-og-host=&quot;www.h2database.com&quot; data-og-source-url=&quot;https://www.h2database.com/html/features.html#connection_modes&quot; data-og-url=&quot;https://www.h2database.com/html/features.html#connection_modes&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bASotr/hyWoBP1L8T/8YVW3ulrpk911WHIUGkxxk/img.png?width=806&amp;amp;height=480&amp;amp;face=0_0_806_480,https://scrap.kakaocdn.net/dn/bovbCw/hyWoAQ60BH/VK5kTe7fzIlgKSeCSlX8Z0/img.png?width=416&amp;amp;height=519&amp;amp;face=112_213_310_411&quot;&gt;&lt;a href=&quot;https://www.h2database.com/html/features.html#connection_modes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.h2database.com/html/features.html#connection_modes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bASotr/hyWoBP1L8T/8YVW3ulrpk911WHIUGkxxk/img.png?width=806&amp;amp;height=480&amp;amp;face=0_0_806_480,https://scrap.kakaocdn.net/dn/bovbCw/hyWoAQ60BH/VK5kTe7fzIlgKSeCSlX8Z0/img.png?width=416&amp;amp;height=519&amp;amp;face=112_213_310_411');&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;Features&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Features Feature List H2 in Use Connection Modes Database URL Overview Connecting to an Embedded (Local) Database In-Memory Databases Database Files Encryption Database File Locking Opening a Database Only if it Already Exists Closing a Database Ignore&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.h2database.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;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/DB</category>
      <category>h2 connection mode</category>
      <category>H2 DB</category>
      <category>h2 db 커넥션 모드</category>
      <category>h2 embedded mode</category>
      <category>h2 mixed mode</category>
      <category>h2 mode</category>
      <category>h2 server mode</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/161</guid>
      <comments>https://pgmjun.tistory.com/161#entry161comment</comments>
      <pubDate>Thu, 20 Jun 2024 17:12:35 +0900</pubDate>
    </item>
    <item>
      <title>내 테스트 코드는 제대로 작성되었을까? (with. 코드 커버리지)</title>
      <link>https://pgmjun.tistory.com/160</link>
      <description>&lt;h2 id=&quot;내-테스트-코드는-제대로-작성되었을까-&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;내 테스트 코드는 제대로 작성되었을까?  &lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우테코 프리코스를 진행하면서 요구사항에 의해 테스트 코드를 작성하였다.&lt;br /&gt;실수를 방지하기 위해 정말 꼼꼼하게 테스트 코드를 작성하며, 엣지 케이스를 찾기 위해 열심히 노력하였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 미션 코드가 길어지다보니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;모든 로직에 대한 테스트가 되어있는가?&quot;라는 스스로에 대한 의구심이 들기 시작했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;때문에 이 의구심을 해결하기 위한 방법을 찾아보았고, 인텔리제이에서 플러그인 설치없이&lt;span&gt;&amp;nbsp;&lt;/span&gt;코드 커버리지를 측정을 할 수 있다는 것을 알 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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 id=&quot;-코드-커버리지란&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  코드 커버리지란?&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드 커버리지는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내 프로젝트의 코드들의 테스트 코드에서 사용된 정도&lt;/b&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;백분율(%)로 나타낸 것이다.&lt;br /&gt;이를 확인함으로써 내가 테스트 코드를 작성하지 못한 부분이 존재하는지를 확인하며, 꼼꼼히 테스트를 작성할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 대해 자세히 학습하고자 한다면 아래 블로그를 참고하면 좋을 것 같아 함께 첨부한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1718803066535&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;[소프트웨어 공학] Code Coverage(코드 커버리지)란 무엇인가?&quot; data-og-description=&quot;Coverage란, 소프트웨어를 테스팅하는 데 사용되는 중요한 지표중 하나로, 보통 Code가 얼마나 실행되었느냐를 측정합니다. 보통 Coverage를 측정하기 위해서 instrumentation을 합니다. Instrumentation은 많&quot; data-og-host=&quot;educoder.tistory.com&quot; data-og-source-url=&quot;https://educoder.tistory.com/16&quot; data-og-url=&quot;https://educoder.tistory.com/entry/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B3%B5%ED%95%99-Code-Coverage%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/br8F5K/hyWoNCxCpI/fLzzbmhy5krD3wiCaHSPZ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bteR89/hyWoA4hyxv/IX6slXbL93YGoLyA1ceek1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://educoder.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://educoder.tistory.com/16&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/br8F5K/hyWoNCxCpI/fLzzbmhy5krD3wiCaHSPZ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bteR89/hyWoA4hyxv/IX6slXbL93YGoLyA1ceek1/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;[소프트웨어 공학] Code Coverage(코드 커버리지)란 무엇인가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Coverage란, 소프트웨어를 테스팅하는 데 사용되는 중요한 지표중 하나로, 보통 Code가 얼마나 실행되었느냐를 측정합니다. 보통 Coverage를 측정하기 위해서 instrumentation을 합니다. Instrumentation은 많&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;educoder.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1718803064141&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;[IntelliJ] 코드 커버리지 확인하기&quot; data-og-description=&quot;코드 커버리지와 IntelliJ에서 브랜치 커버리지를 확인하는 방법에 대해 알아보자&quot; data-og-host=&quot;amaran-th.github.io&quot; data-og-source-url=&quot;https://amaran-th.github.io/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%20%EC%84%A4%EA%B3%84/%5BIntelliJ%5D%20%EC%BD%94%EB%93%9C%20%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80%20%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0/&quot; data-og-url=&quot;https://amaran-th.github.io/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%20%EC%84%A4%EA%B3%84/%5BIntelliJ%5D%20%EC%BD%94%EB%93%9C%20%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80%20%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wWMeh/hyWoCHMVkX/C1S5GkSJjjYRTSAJcADbe0/img.png?width=565&amp;amp;height=801&amp;amp;face=0_0_565_801,https://scrap.kakaocdn.net/dn/eZhEw/hyWoFdsh1K/5sl09bMT8b3SWPjj6faO8k/img.png?width=630&amp;amp;height=425&amp;amp;face=0_0_630_425&quot;&gt;&lt;a href=&quot;https://amaran-th.github.io/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%20%EC%84%A4%EA%B3%84/%5BIntelliJ%5D%20%EC%BD%94%EB%93%9C%20%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80%20%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://amaran-th.github.io/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%20%EC%84%A4%EA%B3%84/%5BIntelliJ%5D%20%EC%BD%94%EB%93%9C%20%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80%20%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wWMeh/hyWoCHMVkX/C1S5GkSJjjYRTSAJcADbe0/img.png?width=565&amp;amp;height=801&amp;amp;face=0_0_565_801,https://scrap.kakaocdn.net/dn/eZhEw/hyWoFdsh1K/5sl09bMT8b3SWPjj6faO8k/img.png?width=630&amp;amp;height=425&amp;amp;face=0_0_630_425');&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;[IntelliJ] 코드 커버리지 확인하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 커버리지와 IntelliJ에서 브랜치 커버리지를 확인하는 방법에 대해 알아보자&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;amaran-th.github.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;커버리지-측정-방법-&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;커버리지 측정 방법  &lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  먼저 이 글은&amp;nbsp;IntelliJ Ultimate&amp;nbsp;기준으로 작성되었음을 밝힌다.&lt;/p&gt;
&lt;/blockquote&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;865&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9sWNH/btsH4HqZqSN/6EypUXJh3EzOU4EdjIKEC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9sWNH/btsH4HqZqSN/6EypUXJh3EzOU4EdjIKEC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9sWNH/btsH4HqZqSN/6EypUXJh3EzOU4EdjIKEC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9sWNH%2FbtsH4HqZqSN%2F6EypUXJh3EzOU4EdjIKEC1%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;865&quot; height=&quot;275&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Test 패키지를 오른쪽 마우스로 클릭하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;More Run/Debug를 선택한 다음&lt;br /&gt;Run 'Tests in '...' with Coverage를 클릭한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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;356&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YntDY/btsH503XwQM/Q0tDqeKWwk2xLHaHRHpR71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YntDY/btsH503XwQM/Q0tDqeKWwk2xLHaHRHpR71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YntDY/btsH503XwQM/Q0tDqeKWwk2xLHaHRHpR71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYntDY%2FbtsH503XwQM%2FQ0tDqeKWwk2xLHaHRHpR71%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;356&quot; height=&quot;172&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 인텔리제이는 코드를 분석하여 백분율로 코드의&lt;span&gt;&amp;nbsp;&lt;/span&gt;Coverage를 출력해준다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;더-정확한-coverage-확인법-️♂️&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;더 정확한 Coverage 확인법  ️&amp;zwj;♂️&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인텔리제이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;브랜치 커버리지(Branch Coverage)&lt;span&gt;&amp;nbsp;&lt;/span&gt;측정 방식을 지원하여,&lt;br /&gt;위보다 더욱 정확하고 세밀하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;Coverage를 측정할 수 있도록 돕는다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #212529; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  브랜치 커버리지(Branch Coverage)란?&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;브랜치 커버리지는&lt;span&gt;&amp;nbsp;&lt;/span&gt;조건문(if, else if, else)에서 각&lt;span&gt;&amp;nbsp;&lt;/span&gt;조건(branch)이 실행되었는지를 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&amp;nbsp;if(조건1 || 조건2 || 조건3)과 같은 조건식이 존재할 때,&lt;br /&gt;3개의 조건에 대해&amp;nbsp;true인 경우와&amp;nbsp;false인 경우를 각각 측정한다고 알고 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;️-설정-방법&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;⚙️ 설정 방법&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ayGV/btsH41bGmId/k8I8JlgrZPEKIZXn073TQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ayGV/btsH41bGmId/k8I8JlgrZPEKIZXn073TQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ayGV/btsH41bGmId/k8I8JlgrZPEKIZXn073TQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ayGV%2FbtsH41bGmId%2Fk8I8JlgrZPEKIZXn073TQ1%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;831&quot; height=&quot;350&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Test 패키지를 오른쪽 마우스로 클릭하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;More Run/Debug를 선택한 다음&lt;br /&gt;Modify Run Configuration을 클릭한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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;286&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3x2ln/btsH5Pawwr6/diHE3Ybg8jXlWkD76Uszmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3x2ln/btsH5Pawwr6/diHE3Ybg8jXlWkD76Uszmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3x2ln/btsH5Pawwr6/diHE3Ybg8jXlWkD76Uszmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3x2ln%2FbtsH5Pawwr6%2FdiHE3Ybg8jXlWkD76Uszmk%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;286&quot; height=&quot;325&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우측에 존재하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Modify options를 클릭한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t8WoE/btsH6jPKof3/5cnvUtuyGeBHhK2GNNsb1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t8WoE/btsH6jPKof3/5cnvUtuyGeBHhK2GNNsb1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t8WoE/btsH6jPKof3/5cnvUtuyGeBHhK2GNNsb1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft8WoE%2FbtsH6jPKof3%2F5cnvUtuyGeBHhK2GNNsb1K%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;432&quot; height=&quot;414&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Code Coverage&lt;span&gt;&amp;nbsp;&lt;/span&gt;탭에 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Enable branch coverage and test tracking을 선택한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전에 따라&amp;nbsp;Use tracking&amp;nbsp;이라고 나와있을 수도 있다고 한다.&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;&amp;nbsp;&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;533&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsTze/btsH5C3AifG/CSTrCjeKnAw1kvbn7mGj91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsTze/btsH5C3AifG/CSTrCjeKnAw1kvbn7mGj91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsTze/btsH5C3AifG/CSTrCjeKnAw1kvbn7mGj91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlsTze%2FbtsH5C3AifG%2FCSTrCjeKnAw1kvbn7mGj91%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;533&quot; height=&quot;290&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 이렇게&lt;span&gt;&amp;nbsp;&lt;/span&gt;Branch Coverage에 대한 결과를 얻을 수 있는데,&lt;br /&gt;이렇게 확인하니 아직 테스트 면에서 부족한 부분이 드러났다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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 id=&quot;-그럼-문제의-위치는-어떻게-파악하는데&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  그럼 문제의 위치는 어떻게 파악하는데?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;72&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB1Ukk/btsH6ja8seN/SvPSDkgth9C5FKPRsqA5ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB1Ukk/btsH6ja8seN/SvPSDkgth9C5FKPRsqA5ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB1Ukk/btsH6ja8seN/SvPSDkgth9C5FKPRsqA5ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB1Ukk%2FbtsH6ja8seN%2FSvPSDkgth9C5FKPRsqA5ik%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;72&quot; height=&quot;477&quot; data-origin-width=&quot;72&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;100%로 측정되지 않은 클래스에 접근하여 좌측 코드라인을 살펴보면 이렇게&lt;br /&gt;녹색과&lt;span&gt;&amp;nbsp;&lt;/span&gt;갈색이 표시되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트되지 않은 부분&lt;/b&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;갈색,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;테스트되는 부분&lt;/b&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;녹색으로 표시되는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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;162&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yLKLo/btsH5vwEky5/0q6nMd4ilZl4I3h4qcDta0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yLKLo/btsH5vwEky5/0q6nMd4ilZl4I3h4qcDta0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yLKLo/btsH5vwEky5/0q6nMd4ilZl4I3h4qcDta0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyLKLo%2FbtsH5vwEky5%2F0q6nMd4ilZl4I3h4qcDta0%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;162&quot; height=&quot;44&quot; data-origin-width=&quot;162&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 클릭해보면&lt;span&gt;&amp;nbsp;&lt;/span&gt;테스트 코드 상에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;조건이&lt;span&gt;&amp;nbsp;&lt;/span&gt;true였던 경우와&lt;span&gt;&amp;nbsp;&lt;/span&gt;false였던 경우의 수가 출력되는데,&lt;br /&gt;갈색인 부분은 결과값이 한 번도&lt;span&gt;&amp;nbsp;&lt;/span&gt;true였던 적이 없었기 때문에, 이 클래스의 커버리지가 낮게 측정되었음을 확인할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;h2 id=&quot;마치며-&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드가 제대로 작성되었는 지, 확인하고 싶다면 이 방법을 통해 측정해보는 것을 추천한다!&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>Code Coverage</category>
      <category>스프링 커버리지</category>
      <category>스프링 테스트코드 커버리지</category>
      <category>인텔리제이</category>
      <category>자바 스프링 테스트코드</category>
      <category>코드 커버리지</category>
      <category>테스트코드 커버리지</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/160</guid>
      <comments>https://pgmjun.tistory.com/160#entry160comment</comments>
      <pubDate>Wed, 19 Jun 2024 22:16:46 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 우테코 최종 테스트 및 합격 회고</title>
      <link>https://pgmjun.tistory.com/159</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;
&lt;h2 id=&quot;실패의-연속- &quot; data-ke-size=&quot;size26&quot;&gt;실패의 연속  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 작년 말 우테코 5기의 최종 테스트에서 떨어졌었다.&lt;br /&gt;사실 아쉽게 떨어진 수준이 아니라, 간단한 문제였지만 문서를 디테일하게 보지 않은 탓이 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(A로직 수행 후, B로직 수행)을 n번 반복해야 했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A를 n번 수행 후, B를 n번 수행하는 방식으로 구현해 버린 것이다. 문서에 설명이 나와있었지만 그 당시의 나는 초긴장 상태였고 글 읽는 것을 평소에 하지 않아 꼬여잇는 글은 잘 이해하지 못했던 것 같다.&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;1206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RpE6g/btsLc3xADXq/vf1kyxKuYm65W2t4xlFcd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RpE6g/btsLc3xADXq/vf1kyxKuYm65W2t4xlFcd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RpE6g/btsLc3xADXq/vf1kyxKuYm65W2t4xlFcd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRpE6g%2FbtsLc3xADXq%2Fvf1kyxKuYm65W2t4xlFcd1%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;463&quot; height=&quot;491&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;1206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 마음을 놓고 지내던 작년 12월 28일, 탈락 소식을 찜질방에서 보게 되었다.&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;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbn2dJ/btsLdH1L3CL/zIyyhuuqJw17CcKKiOCCAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbn2dJ/btsLdH1L3CL/zIyyhuuqJw17CcKKiOCCAk/img.png&quot; data-alt=&quot;디프만 탈락&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbn2dJ/btsLdH1L3CL/zIyyhuuqJw17CcKKiOCCAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbn2dJ%2FbtsLdH1L3CL%2FzIyyhuuqJw17CcKKiOCCAk%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;601&quot; height=&quot;293&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디프만 탈락&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1mZDn/btsLcIHpzbl/h4ls3WMNig1z2x4AIxdk6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1mZDn/btsLcIHpzbl/h4ls3WMNig1z2x4AIxdk6k/img.png&quot; data-alt=&quot;SW마에스트로 탈락&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1mZDn/btsLcIHpzbl/h4ls3WMNig1z2x4AIxdk6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1mZDn%2FbtsLcIHpzbl%2Fh4ls3WMNig1z2x4AIxdk6k%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;497&quot; height=&quot;241&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SW마에스트로 탈락&lt;/figcaption&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;우테코 탈락, 소프트웨어 마에스트로 탈락, 디프만 탈락 등등..&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;&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;h2 id=&quot;sopt를-만나다- &quot; data-ke-size=&quot;size26&quot;&gt;SOPT를 만나다  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 시간이 흘러 올해 3월, 지금은 내 개발 인생의 전환점이 된 SOPT라는 동아리를 모집 마감 4시간 전에 우연히 알게 되었고 4시간 동안 5000자가량의 지원서를 작성해서 냈다. 이상할 만큼 문항 하나하나가 내가 공감 가는 내용들이었고 때문에 빠른 속도로 작성할 수 있었다.&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;총 5000자 이상의 글을 4시간 내로 작성하다 보니, 아카이빙을 제대로 해두지 못했을 정도로 급하게 제출하게 되었는데 운 좋게 서류 합격을 얻어낼 수 있었다.&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;기분은 좋았지만 서류를 저장해 둔 것이 없으니 자소서에 썼던 내용들의 기억만으로 면접을 준비하게 되었다. 하지만 너무나 소중한 기회로 다가올 것 같은 이 동아리에 모든 걸 걸어보고자 여태껏 공부했던 것들을 모두 흝으며 면접을 준비했고, 덕분에 32기 서버파트에 최종 합격을 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://pgmjun.tistory.com/m/118&quot;&gt;[SOPT] 솝트 32기 서버 파트 지원 및 합격 후기&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때부터 너무나 우울했던 내 개발 인생이 변하기 시작했다. 주변에 서버에 대해 깊게 공부한 사람들이 없어서 개발 공부를 매번 혼자 해야 했던 내게 너무나도 좋은 동반자들이 생겼고, 서버뿐만 아니라 기획, 디자인, iOS, 안드로이드 등 다양한 파트의 사람들과 친해지며 개발자로서 다양한 관련 업종의 사람들과 소통하는 법을 배울 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 활동 후 매번 있는 뒤풀이를 통해 내가 가장 못하는 &amp;lsquo;놀 땐 노는 방법&amp;rsquo;도 배워나가게 되었다.&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;그리고 정말 신기하게도 나와 같이 탈락의 딜레마에 빠졌었고, 똑같이 우테코 5기 백엔드 최종에서 탈락하게 된 동규형을 만나게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 형과 금세 친해지게 되어 동아리 내에서 우테코 대비 스터디를 만들고 뜻이 있는 사람들을 모아 6기를 함께 준비해 나갔다.&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1071&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br6FuJ/btsLdyDSlYv/ypjjhTPNNqrkgkod9Ka3K0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br6FuJ/btsLdyDSlYv/ypjjhTPNNqrkgkod9Ka3K0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br6FuJ/btsLdyDSlYv/ypjjhTPNNqrkgkod9Ka3K0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr6FuJ%2FbtsLdyDSlYv%2FypjjhTPNNqrkgkod9Ka3K0%2Fimg.jpg&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;411&quot; height=&quot;376&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1071&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이펙티브 자바 책을 읽고 우테코 미션에 적용한 후 함께 온라인에서 모여 자신이 적용한 것을 공유하며, 프리코스와 최종 코딩 테스트를 대비했고 지식을 길러나갔다. 또 운 좋게도 SOPT를 수료하시고 우테코 5기에 합격하여 활동 중이신 정훈님께서 선뜻 멘토로 나서주셔서 코드 리뷰 등 깊이 있는 활동 또한 진행할 수 있었다.&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;이 외에도 SOPT에서 파트 세미나, 클디서&amp;nbsp;합동 세미나, 솝커톤(1박 2일 단기 해커톤), 앱잼(5주간의 장기 해커톤) 등을 거치며 너무나도 빠르게 성장할 수 있었고 어디에서도 못 만날 소중한 인연들을 정말 많이 만나게 되었다.&lt;/p&gt;
&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 상반기가 지나고 10월이 되면서 우테코의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;새로운 모집 소식&lt;/b&gt;이 들려왔다. 그때부터 이전에 우테코 대비 스터디를 함께 이끌었던 형과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lsquo;진짜&amp;rsquo;&lt;span&gt;&amp;nbsp;&lt;/span&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;span&gt;&amp;nbsp;&lt;/span&gt;우테코 커뮤니티(디스코드)에서 만나게 된 인연도 생겨 이들과도 코드 리뷰 스터디를 진행하며 근거 있는 코드 작성을 위해 노력하고 몰입했다.&lt;br /&gt;그러다 보니 코드에 대해 얘기하는데 5시간이 순식간에 지나가는 경험도 할 수 있었고 개발 얘기를 너무나도 재밌게 하는 내가 진짜 개발자에 한 걸음 가까워졌구나 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다시 찾아온 기회&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcmHKe/btsLeve6dZf/YhLhLtqlPHk21KiEw81xT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcmHKe/btsLeve6dZf/YhLhLtqlPHk21KiEw81xT1/img.png&quot; data-alt=&quot;우테코 6기 1차 합격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcmHKe/btsLeve6dZf/YhLhLtqlPHk21KiEw81xT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcmHKe%2FbtsLeve6dZf%2FYhLhLtqlPHk21KiEw81xT1%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;470&quot; height=&quot;338&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우테코 6기 1차 합격&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;4주의 프리코스를 마친 나는 정성 들여 쓴 지원서류, 그리고 매 주차 별 배운 점과 느낀 점을&lt;span&gt;&amp;nbsp;&lt;/span&gt;4~5000자씩 꽉꽉 채워냈던 소감문 덕분인지 다시 한번&lt;span&gt;&amp;nbsp;&lt;/span&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;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3atQi/btsLdJkYRNc/VIaBtrBq2kH4JvaJCDzNiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3atQi/btsLdJkYRNc/VIaBtrBq2kH4JvaJCDzNiK/img.png&quot; data-alt=&quot;인턴 합격&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3atQi/btsLdJkYRNc/VIaBtrBq2kH4JvaJCDzNiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3atQi%2FbtsLdJkYRNc%2FVIaBtrBq2kH4JvaJCDzNiK%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;414&quot; height=&quot;512&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인턴 합격&lt;/figcaption&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;하지만 1년간 다시 한번 도전하기 위해 칼을 갈고 있던 우테코가 남아있었기에 합격한 곳이 있음에도 불구하고 큰 긴장과 부담감을 가지고 최종 시험을 준비했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 코딩 테스트 전날 밤엔 심장이 쾅쾅 뛰어서 잠도 제대로 자지 못했다..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 몽롱한 상태로 침대에서 일어난 나는 밥을 든든히 챙겨 먹고 시험 장소로 향했다.&lt;br /&gt;두 번째이다 보니 지난번과는 다르게 조금은 마음이 편했고, 같이 열심히 준비한 형도 합격했기 때문에 함께 점심도 먹고 커피도 마시며 시험장 입장을 대기하는 시간들은 꽤나 평온했다. 그렇게 시간을 보내다 어느덧 12시가 되어 입장이 시작되었고 자리를 잡고 마음을 가다듬은 뒤 1시 반부터 시험을 보기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id=&quot;시험-시작-&quot; 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;나는 최대한 마음을 가다듬고 기능구현 문서부터 꼼꼼히 검수하며 50분가량 작성했다.&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;&amp;nbsp;&lt;/span&gt;문제 분석 불충분이었기 때문에&lt;br /&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;그렇게 문서 작성 후, 순조롭게 개발을 시작한 지 1시간 30분 뒤 나는 거대한 난관에 마주치게 됐다 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt; &lt;span&gt;&amp;nbsp;&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;/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시간 이상 찾아지지 않는 불상사가 발생했고, 심장이 미친 듯이 뛰기 시작했다.&lt;br /&gt;포기라는 생각이 머릿속에 살짝이나마 들었지만 오늘을 위해 노력해 온 1년 간의 나를 실망시키고 싶지 않았다. 그리고 곧바로 큰 결심과 함께 해당 로직을 모두 지우고 다시 작성하기 시작했다.&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;span&gt;&amp;nbsp;&lt;/span&gt;ArrayList의 특정 인덱스 값을 교체할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;add(index, Object)&lt;span&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/span&gt;set(index, Object)&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드를 사용해야 하는 것을 긴장하다 보니 잘못 끼워 넣고 만 것이었다.&lt;br /&gt;이 포인트를 수정하고 길었던 코드를 하나하나 메서드로 분리해 나가니 로직은 정상적으로 작동하기 시작했고 &lt;b&gt;시험 시작 3시간 후&lt;/b&gt;&lt;span&gt;&amp;nbsp;&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;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJxpuP/btsLd9czxF1/ZjKZUbT3Zd3IVyP2n6ws81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJxpuP/btsLd9czxF1/ZjKZUbT3Zd3IVyP2n6ws81/img.png&quot; data-alt=&quot;최종 코딩테스트 예제 테스트 통과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJxpuP/btsLd9czxF1/ZjKZUbT3Zd3IVyP2n6ws81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJxpuP%2FbtsLd9czxF1%2FZjKZUbT3Zd3IVyP2n6ws81%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;603&quot; height=&quot;214&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최종 코딩테스트 예제 테스트 통과&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 그렇게 보고 싶었던 화면이 올해는 나와주었고 1년을 멋지게 마무리했다는 생각과 함께 만족스러운 퇴실을 할 수 있었다.&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;h2 id=&quot;최종-합격-그리고-1년을-돌아보며-&quot; data-ke-size=&quot;size26&quot;&gt;최종 합격, 그리고 1년을 돌아보며 ✨&lt;/h2&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1063&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sdWtU/btsLdIzEcoU/97RKzkLPBkoKKWIljXkWz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sdWtU/btsLdIzEcoU/97RKzkLPBkoKKWIljXkWz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sdWtU/btsLdIzEcoU/97RKzkLPBkoKKWIljXkWz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsdWtU%2FbtsLdIzEcoU%2F97RKzkLPBkoKKWIljXkWz0%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;537&quot; height=&quot;446&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1063&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;집에 돌아와 코드를 살펴보니 조금 실수한 부분이 있어 걱정이 되었지만, 정말 다행히도&lt;span&gt;&amp;nbsp;&lt;/span&gt;12월 27일 오늘 오후 3시,,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;우테코 6기&lt;/b&gt;에&lt;span&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv6Hic/btsLdfR3yxm/oLLFKTVF8PKDLy0qwPv7V0/img.png&quot; width=&quot;277&quot; height=&quot;346&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PatpK/btsLcreJtaI/2M2OSUUzN5dmgzt1rc49jk/img.png&quot; width=&quot;485&quot; height=&quot;345&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/essEUH/btsLcGbJJPt/pGwNIdtScX82m4B5gRxgH1/img.png&quot; width=&quot;656&quot; height=&quot;29&quot; /&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년간 열심히 달린 결과, 다양한 합격을 경험하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;개발자로서 그리고&lt;span&gt;&amp;nbsp;&lt;/span&gt;대학생으로서 다신 없을 추억들을 쌓아온 소중한 24살을 보낼 수 있었고&lt;br /&gt;&lt;b&gt;인턴쉽&lt;/b&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;우테코&lt;/b&gt;&lt;span&gt;&amp;nbsp;&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;&amp;nbsp;&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 절벽에서 땅 위로 올라오는 과정을 1년이라는 시간 동안 겪어온 것 같다. 위에서 말한 내용은 우테코와 관련된 2023년 만을 적어놓은 것이며 이 외에도 글 읽는 능력 향상을 위해 독서를 정말 많이 시도했으며, 졸업작품 프로젝트 설계 및 개발, SOPT 32/33기의 다양한 활동, 기술에 근거를 가지고 사용하기 위해 활용 방법과 이유 등을 꼼꼼히 체크하고 나중의 나를 위해 기록하는 습관 들이기, 대학 컴퓨터공학부 전공 동아리 회장, 동아리 부원 대상 8주간의 자바 특강 진행, 무중단 CI/CD 과정 익히기, GPT 모델 FineTuning 하여 응답 형태 고정 및 성능 개선 경험 등 내 인생 중 2023년은 개발자로서 가장 큰 성장을 했다고 자신 있게 말할 수 있을 정도로 할 수 있는 건 모두 다 하며 1년을 보냈다.&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;ldquo;포기하지 않고 끝까지 노력하는 사람에게 기회는 잡힌다.&amp;rdquo;&lt;/b&gt;라고 지금의 나는 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;21년 10월의 나는 여러 언어와 프레임워크를 공부해 봤지만 전혀 흥미가 생기지 않아 개발이 적성에 맞지 않는다고 생각되어 포기하려고 했었다. 하지만 포기하지 않고 달려온 지금의 나는 꽤나 멋진 개발자로 성장하고 있지 않은가.&lt;/p&gt;
&lt;br /&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;미친 듯이 해도 안되면 그때 포기해라&amp;rdquo;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;마치며&quot; 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;/div&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;/div&gt;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우아한테크코스 6기 백엔드</category>
      <category>우테코 6기 후기</category>
      <category>우테코 백엔드</category>
      <category>우테코 백엔드 합격</category>
      <category>우테코 백엔드 합격 후기</category>
      <category>우테코 합격</category>
      <category>우테코 합격 후기</category>
      <category>우테코 합격 후기 6기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/159</guid>
      <comments>https://pgmjun.tistory.com/159#entry159comment</comments>
      <pubDate>Wed, 19 Jun 2024 01:53:32 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 프리코스 4주차 회고</title>
      <link>https://pgmjun.tistory.com/158</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfjuIe/btsH2Qu41pk/HKvBHrSKm1ybqhggYnWUf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfjuIe/btsH2Qu41pk/HKvBHrSKm1ybqhggYnWUf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfjuIe/btsH2Qu41pk/HKvBHrSKm1ybqhggYnWUf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfjuIe%2FbtsH2Qu41pk%2FHKvBHrSKm1ybqhggYnWUf0%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;632&quot; height=&quot;316&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot;&gt;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718729772259&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;[우테코 6기] 프리코스 4주차 회고&quot; data-og-description=&quot;매주 미션을 수행하면서 코드에 애정을 가지고 최선을 다해 몰입하여 열심히 했기 때문인지, 코드가 정말 만족스러웠고 더 수정할 곳이 없어보였다. 하지만 항상 한 곳 정도는 문제가 발생해 급&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; data-og-url=&quot;https://velog.io/@pgmjun/우테코-6기-프리코스-4주차-회고&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cjZKqb/hyWldpbSp7/zXOtKkU4bVSxiQespDQvMk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/vc7we/hyWoLj6lLW/w0oCIDdJXce7l0fduNewm1/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/bc3QCF/hyWoCOpOxN/JDDRT7V4JpQfrzRv2eKej1/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279&quot;&gt;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cjZKqb/hyWldpbSp7/zXOtKkU4bVSxiQespDQvMk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/vc7we/hyWoLj6lLW/w0oCIDdJXce7l0fduNewm1/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/bc3QCF/hyWoCOpOxN/JDDRT7V4JpQfrzRv2eKej1/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279');&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;[우테코 6기] 프리코스 4주차 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;매주 미션을 수행하면서 코드에 애정을 가지고 최선을 다해 몰입하여 열심히 했기 때문인지, 코드가 정말 만족스러웠고 더 수정할 곳이 없어보였다. 하지만 항상 한 곳 정도는 문제가 발생해 급&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/158</guid>
      <comments>https://pgmjun.tistory.com/158#entry158comment</comments>
      <pubDate>Wed, 19 Jun 2024 01:53:24 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 프리코스 3주차 회고</title>
      <link>https://pgmjun.tistory.com/157</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NFpZm/btsH3kWQwxu/UkNYl5vcHzzqbp6Lhw8zK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NFpZm/btsH3kWQwxu/UkNYl5vcHzzqbp6Lhw8zK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NFpZm/btsH3kWQwxu/UkNYl5vcHzzqbp6Lhw8zK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNFpZm%2FbtsH3kWQwxu%2FUkNYl5vcHzzqbp6Lhw8zK1%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;632&quot; height=&quot;316&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718729698368&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;[우테코 6기] 프리코스 3주차 회고&quot; data-og-description=&quot;3주차가 시작된 후 새로운 미션을 수행하기 전에, 우테코 커뮤니티(디스코드)에서 만난 사람들과 함께2주차 미션에 대한 자신의 코드를 설명하고 서로 리뷰하며 토론하는 시간을 가졌다.처음엔 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; data-og-url=&quot;https://velog.io/@pgmjun/우테코-6기-프리코스-3주차-회고&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ObkE4/hyWoGXydwG/l0uKqdu2LXe5H5tEAUQ9R0/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/bh0b4L/hyWljQuhT4/lL6Kfb7qEOJhiLVtfKP1Vk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/tsgKe/hyWoB9L9LO/Ke42oexJSqOq6KwChKNmmk/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279&quot;&gt;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ObkE4/hyWoGXydwG/l0uKqdu2LXe5H5tEAUQ9R0/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/bh0b4L/hyWljQuhT4/lL6Kfb7qEOJhiLVtfKP1Vk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/tsgKe/hyWoB9L9LO/Ke42oexJSqOq6KwChKNmmk/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279');&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;[우테코 6기] 프리코스 3주차 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;3주차가 시작된 후 새로운 미션을 수행하기 전에, 우테코 커뮤니티(디스코드)에서 만난 사람들과 함께2주차 미션에 대한 자신의 코드를 설명하고 서로 리뷰하며 토론하는 시간을 가졌다.처음엔&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/157</guid>
      <comments>https://pgmjun.tistory.com/157#entry157comment</comments>
      <pubDate>Wed, 19 Jun 2024 01:53:15 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 프리코스 2주차 회고</title>
      <link>https://pgmjun.tistory.com/156</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bexn8x/btsH2S0C3Xy/OomPLrbTKGvjMryKlb9Cc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bexn8x/btsH2S0C3Xy/OomPLrbTKGvjMryKlb9Cc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bexn8x/btsH2S0C3Xy/OomPLrbTKGvjMryKlb9Cc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbexn8x%2FbtsH2S0C3Xy%2FOomPLrbTKGvjMryKlb9Cc0%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;632&quot; height=&quot;316&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718729659235&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;[우테코 6기] 프리코스 2주차 회고&quot; data-og-description=&quot;2주간의 프리코스   1주차 미션이 끝나고 사람들에게 코드리뷰를 받는 과정에서 내 코드에 대해 명쾌하게 설명할 수 없는 내 모습을 통해 아직&amp;nbsp;메타인지가 부족함을 깨달을 수 있었다. 나름&amp;nbsp;&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; data-og-url=&quot;https://velog.io/@pgmjun/우테코-6기-2주차-회고&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ciexM8/hyWk98cung/Ghd4QOpRlCuWzP3SgtQSek/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/SF3kZ/hyWoFj1r7T/6Kdggz9yungketoVnbMVUK/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/cSM8Eu/hyWoNCmrUw/bQnfMuTHQnPIJcqAOM6Jwk/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279&quot;&gt;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ciexM8/hyWk98cung/Ghd4QOpRlCuWzP3SgtQSek/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/SF3kZ/hyWoFj1r7T/6Kdggz9yungketoVnbMVUK/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/cSM8Eu/hyWoNCmrUw/bQnfMuTHQnPIJcqAOM6Jwk/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279');&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;[우테코 6기] 프리코스 2주차 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2주간의 프리코스   1주차 미션이 끝나고 사람들에게 코드리뷰를 받는 과정에서 내 코드에 대해 명쾌하게 설명할 수 없는 내 모습을 통해 아직&amp;nbsp;메타인지가 부족함을 깨달을 수 있었다. 나름&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우아한테크코스</category>
      <category>우아한테크코스 6기</category>
      <category>우아한테크코스 프리코스</category>
      <category>우테코</category>
      <category>우테코 6기</category>
      <category>우테코 프리코스</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/156</guid>
      <comments>https://pgmjun.tistory.com/156#entry156comment</comments>
      <pubDate>Wed, 19 Jun 2024 01:53:06 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 프리코스 1주차 회고</title>
      <link>https://pgmjun.tistory.com/155</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZSXT9/btsH3xPcdD3/UYO0HajkvVmytMwYL9Kzo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZSXT9/btsH3xPcdD3/UYO0HajkvVmytMwYL9Kzo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZSXT9/btsH3xPcdD3/UYO0HajkvVmytMwYL9Kzo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZSXT9%2FbtsH3xPcdD3%2FUYO0HajkvVmytMwYL9Kzo1%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;632&quot; height=&quot;316&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718729471489&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;[우테코 6기] 프리코스 1주차 회고&quot; data-og-description=&quot;프리코스와 함께 보낸 1주는 좋은 사람들을 많이 만나 행복이 꽉꽉 들어찼던 시간이었다.열심히 활동해서 그런지 1주 밖에 지나지 않았는데 벌써부터 우테코에 정이 많이 든 것 같다. 살면서 이&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; data-og-url=&quot;https://velog.io/@pgmjun/우테코-6기-프리코스-1주차-회고&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/In9Jc/hyWlf8pvms/vMwhLBtboYYyT95cFK53Pk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/wAmfV/hyWlehilvA/NRq2FMggmOUkXdWHuGgWRk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/cbmoiN/hyWk89gwvZ/4WYkyFPZLVl0u5Ty4rWtY1/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279&quot;&gt;&lt;a href=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@pgmjun/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/In9Jc/hyWlf8pvms/vMwhLBtboYYyT95cFK53Pk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/wAmfV/hyWlehilvA/NRq2FMggmOUkXdWHuGgWRk/img.png?width=600&amp;amp;height=193&amp;amp;face=0_0_600_193,https://scrap.kakaocdn.net/dn/cbmoiN/hyWk89gwvZ/4WYkyFPZLVl0u5Ty4rWtY1/img.jpg?width=460&amp;amp;height=460&amp;amp;face=157_145_280_279');&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;[우테코 6기] 프리코스 1주차 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프리코스와 함께 보낸 1주는 좋은 사람들을 많이 만나 행복이 꽉꽉 들어찼던 시간이었다.열심히 활동해서 그런지 1주 밖에 지나지 않았는데 벌써부터 우테코에 정이 많이 든 것 같다. 살면서 이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>우아한테크코스</category>
      <category>우아한테크코스 프리코스</category>
      <category>우테코</category>
      <category>우테코 6기 프리코스</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/155</guid>
      <comments>https://pgmjun.tistory.com/155#entry155comment</comments>
      <pubDate>Wed, 19 Jun 2024 01:51:50 +0900</pubDate>
    </item>
    <item>
      <title>[인증/인가] JWT란? (with. AccessToken, RefreshToken 사용법)</title>
      <link>https://pgmjun.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 중 로그인 기능 구현을 위해 JWT, OAuth2 를 사용해서 카카오 로그인하기 구현을 해보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 중 사용했던 JWT는 서버에서 인증/인가에 사용되는&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서버 레벨에서 보안과 가장 밀접한 내용이고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;깊이있게 학습해보면 많은 도움이될 거라는 생각에 학습과 함께 이곳에 기록하게 되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;JWT 공식문서&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;jwt의&lt;span&gt;&amp;nbsp;&lt;/span&gt;더 깊고&amp;nbsp;자세한 내용은 아래 공식문서를 통해 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1718802248045&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;JWT.IO&quot; data-og-description=&quot;JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.&quot; data-og-host=&quot;jwt.io&quot; data-og-source-url=&quot;https://jwt.io/introduction&quot; data-og-url=&quot;http://jwt.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bN4Kut/hyWoJ7ZW1e/lKCDrLZ57fc4anqoJCg6f0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/fMNWX/hyWoOg9GrV/FOZYYACqE632zpvIcAEMtk/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://jwt.io/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jwt.io/introduction&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bN4Kut/hyWoJ7ZW1e/lKCDrLZ57fc4anqoJCg6f0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/fMNWX/hyWoOg9GrV/FOZYYACqE632zpvIcAEMtk/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;JWT.IO&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jwt.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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;JWT  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Jwt란 Json Web Token 의 줄임말로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'&lt;span style=&quot;background-color: #ffffff; color: #303a3e;&quot;&gt;인증에 필요한 최소한의 정보들을 담아 암호화시킨&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;토큰'&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서버와 클라이언트 통신 시 HTTP의 Authorization 헤더에 담아서&amp;nbsp;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;인증/인가 수단&lt;/span&gt;으로 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;JWT에는 어떤 정보를 담아야할까?&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;방금 JWT에 인증에 필요한 최소한의 정보를 담는다고 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 이유는 JWT 토큰의 Body, Header 정보는 쉽게 열어볼 수 있기 때문에 인증에 필요한 최소한의 정보만을 두어야한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;당장에도 아래 사이트에 들어가서 jwt 토큰을 올려두면 헤더와 바디를 파싱해서 보여준다.&lt;/p&gt;
&lt;figure id=&quot;og_1718726981139&quot; contenteditable=&quot;false&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NWzPC/hyWoz5ciTU/uq55eKSGRY3j7TmEK4cgkk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cYxoBm/hyWldQflZe/Oeh1Z1idUIW3KhKw0sOaGk/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot; data-og-url=&quot;http://jwt.io/&quot; data-og-source-url=&quot;https://jwt.io/&quot; data-og-host=&quot;jwt.io&quot; data-og-description=&quot;JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.&quot; data-og-title=&quot;JWT.IO&quot; data-og-type=&quot;website&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://jwt.io/&quot; data-source-url=&quot;https://jwt.io/&quot;&gt;
&lt;div style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NWzPC/hyWoz5ciTU/uq55eKSGRY3j7TmEK4cgkk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cYxoBm/hyWldQflZe/Oeh1Z1idUIW3KhKw0sOaGk/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;JWT.IO&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;jwt.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;때문에 나는 보통 JWT가 어떤 회원의 토큰인지 정도만 알 수 있도록&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;u&gt;회원의 고유 식별자이자 DB의 PrimaryKey&lt;/u&gt;&lt;/i&gt;인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'memberId'&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만을 담아두곤 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;JWT의 구조&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br0QSu/btsH3PhPFfJ/yUkdKHjTpQ543Y59nyWuk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br0QSu/btsH3PhPFfJ/yUkdKHjTpQ543Y59nyWuk0/img.png&quot; data-alt=&quot;Jwt Header&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br0QSu/btsH3PhPFfJ/yUkdKHjTpQ543Y59nyWuk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr0QSu%2FbtsH3PhPFfJ%2FyUkdKHjTpQ543Y59nyWuk0%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;579&quot; height=&quot;237&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jwt Header&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;JWT는 위와 같은 구조로 디코드 되어있는데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;을 중심으로&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Header&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;Payload&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Signiture&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 나누어진다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Header&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;297&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlUYUF/btsH2STRGaq/yU0k0lCt6wZuMqXrtxwVx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlUYUF/btsH2STRGaq/yU0k0lCt6wZuMqXrtxwVx0/img.png&quot; data-alt=&quot;Jwt Header(Decoded)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlUYUF/btsH2STRGaq/yU0k0lCt6wZuMqXrtxwVx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlUYUF%2FbtsH2STRGaq%2FyU0k0lCt6wZuMqXrtxwVx0%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;297&quot; height=&quot;161&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jwt Header(Decoded)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Jwt의 헤더는 alg와 typ으로 구성되어 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&quot;&lt;/b&gt;alg&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 암호화할 해싱 알고리즘&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&quot;&lt;/b&gt;typ&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 토큰의 타입&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Payload&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;319&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRdIos/btsH3oLwsF8/uSA0dDLNEKufvd61JzrDmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRdIos/btsH3oLwsF8/uSA0dDLNEKufvd61JzrDmk/img.png&quot; data-alt=&quot;Jwt Payload(Decoded)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRdIos/btsH3oLwsF8/uSA0dDLNEKufvd61JzrDmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRdIos%2FbtsH3oLwsF8%2FuSA0dDLNEKufvd61JzrDmk%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;319&quot; height=&quot;190&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jwt Payload(Decoded)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Payload는 위와 같은 형식으로 주요 내용들을 담고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 하나의 정보를 클레임(Claim) 이라고 부른다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Payload에 들어가는 클레임은 3종류로 이루어져 있으며 아래와 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;등록된 (registered) 클레임&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://tools.ietf.org/html/rfc7519#section-4.1&quot;&gt;Registered claims&lt;/a&gt;: These are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims. Some of them are:&amp;nbsp;iss&amp;nbsp;(issuer),&amp;nbsp;exp&amp;nbsp;(expiration time),&amp;nbsp;sub&amp;nbsp;(subject),&amp;nbsp;aud&amp;nbsp;(audience), and&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://tools.ietf.org/html/rfc7519#section-4.1&quot;&gt;others&lt;/a&gt;.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;등록된 클레임은 유용하고 상호운용 가능한 클레임 설정을 제공하며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;필수는 아니지만 사용이 권장되는 JWT 에서 미리 선언된 클레임이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;등록된 클레임의 종류로는 아래 클래임들이 존재한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iss&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;토큰 발급자 (issuer)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sub&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;토큰 제목 (subject)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;aud&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;토큰 대상자 (audience)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;exp&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;(자바에서 설정시 new Date(System.currentTimeMillis()) 사용)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nbf&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념이다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iat&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;jti&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용된다. 일회용 토큰에 사용하면 유용하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;공개 (public) 클레임&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://tools.ietf.org/html/rfc7519#section-4.2&quot;&gt;Public claims&lt;/a&gt;: These can be defined at will by those using JWTs. But to avoid collisions they should be defined in the&amp;nbsp;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.iana.org/assignments/jwt/jwt.xhtml&quot;&gt;IANA JSON Web Token Registry&lt;/a&gt;&amp;nbsp;or be defined as a URI that contains a collision resistant namespace.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공개 클레임은 JWT를 사용하는 사람들이 마음대로 정의할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 공개 클레임을 사용하려면 다른 클레임들 간의 충돌을 방지하기 위해서 IANA JSON 웹 토큰 레지스트리에 정의하거나&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;충돌 방지 네임스페이스를 포함하는 URI로 정의해야 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;비공개 (private) 클레임&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://tools.ietf.org/html/rfc7519#section-4.3&quot;&gt;Private claims&lt;/a&gt;: These are the custom claims created to share information between parties that agree on using them and are neither&amp;nbsp;registered&amp;nbsp;or&amp;nbsp;public&amp;nbsp;claims.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;등록된 클레임도 아니고, 공개된 클레임들도 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;양 측간에 (클라이언트 - 서버) 협의 하에 특정 정보를 공유하기 위해 사용되는 커스텀 클레임이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;{
  &quot;memberId&quot;: 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. signature&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; 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;406&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4g4wr/btsH29VmtrV/toVmrmVvWea0fy1Pah59k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4g4wr/btsH29VmtrV/toVmrmVvWea0fy1Pah59k1/img.png&quot; data-alt=&quot;Jwt Signature(Decoded)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4g4wr/btsH29VmtrV/toVmrmVvWea0fy1Pah59k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4g4wr%2FbtsH29VmtrV%2FtoVmrmVvWea0fy1Pah59k1%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;406&quot; height=&quot;207&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jwt Signature(Decoded)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #303a3e;&quot;&gt;&lt;b&gt;Header&lt;/b&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Payload&lt;/b&gt;를 더한 뒤 비밀키로 해싱하여 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #303a3e;&quot;&gt;Header와 Payload는 단순히 인코딩 된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #303a3e;&quot;&gt;Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #303a3e;&quot;&gt;따라서 Signature는 토큰의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;위변조 여부를 확인하는 데&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;해싱(Hashing)이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;해시 함수 또는 해시 알고리즘 또는 해시함수 알고리즘은 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;해시 함수에 의해 얻어지는 값은 해시 값, 해시 코드, 해시 체크섬 또는 간단하게 해시라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;이렇게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;해시 알고리즘을 사용하여 해시 코드를 얻는 과정&lt;/span&gt;을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;해싱&lt;/b&gt;이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;JWT 사용법&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보통 JWT 를 사용할 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AccessToken, RefreshToken&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방식으로 사용한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;우선 AccessToken과 RefreshToken에 대해서 먼저 알아보자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;AccessToken 이란?&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AccessToken은 그 이름과 같이 접근에 사용되는 JWT 토큰이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AccessToken은 로그인 시에 서버에서 클라이언트에게 인증/인가의 수단으로 발급하며&lt;br /&gt;클라이언트는 이 AccessToken을 사용하여 서버에&lt;span&gt;&amp;nbsp;&lt;/span&gt;자신의 권한 증명을 포함한&amp;nbsp;요청을 보낼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;유효 시간(exp)&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AccessToken의 유효시간은 보통 10분~1시간 정도로 굉장히 짧게 주어지는 편이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 말했듯이 AccessToken은 회원의 인증/인가이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;때문에 이 AccessToken이 공격자에게 탈취당한다면 문제가 발생한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;HTTP는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;무상태성(Stateless)&lt;/b&gt;이라는 특징이 있기 때문에 JWT를 한 번 발급하게 되면&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;누구에게 어떤 엑세스 토큰을 발급했는지에 대한 정보를 가지고 있지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 서버에서 발급한 올바른 형태의 JWT만 포함하여 요청을 보내면 공격자든, 진짜 회원이든 응답 결과를 내려준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제가 있기 때문에 AccessToken은 짧은 유효시간을 갖는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;RefreshToken 이란?&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;RefreshToken은 AccessToken을 갱신하기 위해 사용되는 JWT 토큰이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 말했듯이 AccessToken은 굉장히 짧은 생명주기를 갖는다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그런 AccessToken만을 통해 회원이 서비스를 사용하게 된다면, 짧은 주기마다 재로그인을 해야하고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 된다면 보안성은 향상되겠지만, 유저경험(UX)는 그닥 좋지 않은 서비스가 만들어질 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 AccessToken을 갱신하는 목적을 가진 JWT 토큰인 RefreshToken 이라는 개념이 생겨나게 되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;RefreshToken을 사용하면 만료된 AccessToken을 갱신하여&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;재로그인 없이 계속해서 인증/인가를 할 수 있으며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AccessToken의 유효시간을 짧게 설정하는 것에 의해 유저경험이 떨어지는 문제를 고려하지 않아도 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;JWT 방식 동작 플로우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;JWT는 정답이 없다고 할 정도로 다양하게 사용할 수 있는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 설명할 플로우는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다양한 방법 중 하나&lt;/b&gt;&lt;/span&gt;이니 이 점을 참고하자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;2416&quot; data-origin-height=&quot;3736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxYsxS/btsH4YZaMmA/eTWtXtNHs5sby5c15W4bz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxYsxS/btsH4YZaMmA/eTWtXtNHs5sby5c15W4bz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxYsxS/btsH4YZaMmA/eTWtXtNHs5sby5c15W4bz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxYsxS%2FbtsH4YZaMmA%2FeTWtXtNHs5sby5c15W4bz0%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;604&quot; height=&quot;934&quot; data-origin-width=&quot;2416&quot; data-origin-height=&quot;3736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 클라이언트는 서버에 로그인 요청을 보낸다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 로그인에 성공하면 서버는 클라이언트에게 AccessToken &amp;amp; RefreshToken을 발급한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 클라이언트는 발급받은 AccessToken을 HTTP Header에 담아 인증/인가가 필요한 API를 요청할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;보통 JWT 토큰은 Authorization 헤더의 값으로 담아 보내며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Token값의 앞에 'Bearer ' 라는 Prefix를 붙여서 사용한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Ex) Authorization: Bearer AccessToken&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. AccessToken이 만료된다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. 클라이언트가 AccessToken을 HTTP Header에 담아 인증/인가가 필요한 API를 요청한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6. 서버는 AccessToken이 만료되었음을 확인하고 AccessToken가 만료되었다고 Response한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;7. 클라이언트는 RefreshToken, AccessToken을 Body에 담고, 서버에 토큰 갱신 요청을 보내 만료된 AccessToken을 갱신한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이때 RefreshToken을 함께 갱신해주는 방법도 있고, AccessToken만 갱신하는 방법도 있고 다양한 방식이 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서 다룰 내용은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;i&gt;후자&lt;/i&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&quot;RefreshToken은 어떻게 전달해야할까?&quot;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;AccessToken은 Authorization 헤더에 담는데 RefreshToken은 어떻게 담아보낼지 고민하다가&lt;br /&gt;토큰 갱신을 위해 RefreshToken, AccessToken을 함께 보낼때는 Header가 아니라,&lt;br /&gt;Http Body에 담아서 보내기로 하였다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;RefreshToken 만료 시, 동작 플로우&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 다룰 방식은 토큰 갱신 시점에 AccessToken만을 갱신하기 때문에&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;RefreshToken이 만료되면 유저가 로그아웃되고 재로그인을 하는 방식으로 설계해보았다고 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 RefreshToken이 만료되면 어떤 플로우로 동작하는 지에 대해 알아보자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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-filename=&quot;Untitled (1).png&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;3176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUhYRT/btsH27wscAU/cwmkKVDSofEzCDLfD5t16k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUhYRT/btsH27wscAU/cwmkKVDSofEzCDLfD5t16k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUhYRT/btsH27wscAU/cwmkKVDSofEzCDLfD5t16k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUhYRT%2FbtsH27wscAU%2FcwmkKVDSofEzCDLfD5t16k%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;582&quot; height=&quot;766&quot; data-filename=&quot;Untitled (1).png&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;3176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. RefreshToken이 만료되고, 그 이후에 AccessToken 도 만료된다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 클라이언트는 RefreshToken, AccessToken을 Body에 담고, 서버에 토큰 갱신 요청을 보내 만료된 AccessToken을 갱신한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 서버는 RefreshToken이 만료되어 토큰 갱신이 불가능하다는 Response를 전달한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 클라이언트는 유저를 '로그아웃' 시킨다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. 로그아웃된 유저가 재로그인을 시도하면 클라이언트는 서버에 로그인 요청을 보낸다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6. 로그인에 성공하면 서버는 클라이언트에게 AccessToken &amp;amp; RefreshToken을 발급한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 JWT란 무엇인지, 그리고 JWT를 다루는 하나의 플로우에 대해서 학습해보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;JWT를 다루는 플로우는 정말 다양하며, 이번에 소개한 방식은 정말 기초적인 방식 중 하나라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;탈취에 취약한 JWT를 안전하게 사용하기 위해&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;토큰 갱신마다 RefreshToken을 함께 갱신시키며 DB에서 RefreshToken을 관리하는&lt;br /&gt;&lt;b&gt;RTR(Refresh Token Rotation)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방식도 존재하며,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;JWT를 탈취당하지 않기 위한 다양한 보안적인 요소들도 존재한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;탈취에 취약한 JWT를 사용하려면, 안전하게 토큰을 다루기 위한 여러 요소들을 학습하는 것은 필수라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 포스팅으로 JWT 이해를 마치기보단 고민과 탐색과정을 더하여&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;JWT를 안전하게 다루는 방법을 생각하고 적용하여 안전한 통신 환경을 구축하시길 바란다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;현재 JWT 관련 로직의 성능/보안과 관련하여&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://pgmjun.tistory.com/125&quot;&gt;RTR 방식 + Redis에서 RefreshToken을 다루는 이유에 대한 포스팅&lt;/a&gt;을 작성해두었으니,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;더 자세히 학습하고 싶은 분들은 참고하길 바란다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1718802166447&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;[인증/인가] RefreshToken은 왜 Redis를 사용해 관리할까? (with. RTR 방식)&quot; data-og-description=&quot;1. &amp;ldquo;왜?&amp;rdquo;  우리는 MySQL과 같은 일반 DB를 다룰 줄 아는데 사람들은왜 굳이 리프레시 토큰을 Redis에 저장하여 사용하는지 이유가 궁금했으며,왜 다들 Redis를 사용해서 리프레시 토큰을 저장하는&quot; data-og-host=&quot;pgmjun.tistory.com&quot; data-og-source-url=&quot;https://pgmjun.tistory.com/125&quot; data-og-url=&quot;https://pgmjun.tistory.com/125&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2FBbI/hyWoPmQuPZ/HJLRRKSV4581hEkkwBFCUk/img.png?width=308&amp;amp;height=260&amp;amp;face=0_0_308_260,https://scrap.kakaocdn.net/dn/nMnxk/hyWoO2xEPy/5RvUESzz8vD6uGUrOpnD51/img.png?width=308&amp;amp;height=260&amp;amp;face=0_0_308_260,https://scrap.kakaocdn.net/dn/cILTHf/hyWoGpTbHv/TmLt0CnnAamGmKJ0P80Z40/img.png?width=2416&amp;amp;height=3736&amp;amp;face=0_0_2416_3736&quot;&gt;&lt;a href=&quot;https://pgmjun.tistory.com/125&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pgmjun.tistory.com/125&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2FBbI/hyWoPmQuPZ/HJLRRKSV4581hEkkwBFCUk/img.png?width=308&amp;amp;height=260&amp;amp;face=0_0_308_260,https://scrap.kakaocdn.net/dn/nMnxk/hyWoO2xEPy/5RvUESzz8vD6uGUrOpnD51/img.png?width=308&amp;amp;height=260&amp;amp;face=0_0_308_260,https://scrap.kakaocdn.net/dn/cILTHf/hyWoGpTbHv/TmLt0CnnAamGmKJ0P80Z40/img.png?width=2416&amp;amp;height=3736&amp;amp;face=0_0_2416_3736');&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;[인증/인가] RefreshToken은 왜 Redis를 사용해 관리할까? (with. RTR 방식)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. &amp;ldquo;왜?&amp;rdquo;  우리는 MySQL과 같은 일반 DB를 다룰 줄 아는데 사람들은왜 굳이 리프레시 토큰을 Redis에 저장하여 사용하는지 이유가 궁금했으며,왜 다들 Redis를 사용해서 리프레시 토큰을 저장하는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pgmjun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd</category>
      <category>accesstoken refreshtoken 동작 플로우</category>
      <category>accesstoken refreshtoken 흐름</category>
      <category>accesstoken 플로우</category>
      <category>JWT</category>
      <category>jwt accesstoken refreshtoken</category>
      <category>jwt 동작 플로우</category>
      <category>jwt 플로우</category>
      <category>jwt 흐름</category>
      <category>refreshtoken 플로우</category>
      <category>wt 동작 과정</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/154</guid>
      <comments>https://pgmjun.tistory.com/154#entry154comment</comments>
      <pubDate>Wed, 19 Jun 2024 01:11:54 +0900</pubDate>
    </item>
    <item>
      <title>Bucket4j를 사용해서 스프링부트 트래픽 제한하기</title>
      <link>https://pgmjun.tistory.com/153</link>
      <description>&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;226&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/owgEv/btsH3kvy01w/8MXaRUUa2xjCkLjECZOUb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/owgEv/btsH3kvy01w/8MXaRUUa2xjCkLjECZOUb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/owgEv/btsH3kvy01w/8MXaRUUa2xjCkLjECZOUb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FowgEv%2FbtsH3kvy01w%2F8MXaRUUa2xjCkLjECZOUb1%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;226&quot; height=&quot;223&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;223&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;h2 data-ke-size=&quot;size26&quot;&gt;트래픽 제한을 고려하게 된 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽 제한에 대한 고민은 gpt-3.5-turbo 모델을 Fine-Tuning하여 사용하는 졸업작품 코리를 개발하면서 시작되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코리는 초기 유저들을 모으기 위해 한동안은 유료인 GPT API 사용 비용을 운영측에서 부담하는 구조로 운영하게 되었고, 아직은 BM(Business Model)이 딱히 존재하지 않았기 때문에 당장은 손해로 지어질 수 밖에 없는 구조였다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java/SpringBoot 환경에서 트래픽 제한 구현하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;토큰 형태의 트래픽 제한 라이브러리 Bucket4j&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원들의 트래픽을 제한할 수 있는 방법을 찾던 와중에 Bucket4j라는 라이브러리를 발견하게 되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Token Bucket 알고리즘이 적용된 해당 라이브러리는 회원마다 일정 개수의 &lt;b&gt;Token&lt;/b&gt;이 담긴 &lt;b&gt;Bucket&lt;/b&gt;을 쥐어주고 &lt;b&gt;Token&lt;/b&gt;을 사용하여 요청을 보낼 수 있도록 해준다.&lt;/li&gt;
&lt;li&gt;만약 &lt;b&gt;Bucket&lt;/b&gt;에 &lt;b&gt;Token&lt;/b&gt;이 존재하지 않는다면 429 Too Many Requests를 return 하여 요청을 제한할 수 있다.&lt;/li&gt;
&lt;li&gt;유저의 &lt;b&gt;Bucket&lt;/b&gt;에 &lt;b&gt;Token&lt;/b&gt;이 존재한다면 토큰을 사용하고 요청을 수행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bucket&lt;/b&gt;에 담긴 &lt;b&gt;Token&lt;/b&gt;은 설정한 시간마다 설정한 갯수만큼 &lt;b&gt;충전&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&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;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 17&lt;/li&gt;
&lt;li&gt;SpringBoot 3.0.6&lt;/li&gt;
&lt;/ul&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;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;// Bucket4j
implementation group: 'com.github.vladimir-bukhtoyarov', name: 'bucket4j-core', version: '7.0.0'
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RateLimitBucketProvider&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class RateLimitBucketProvider {
    private Map&amp;lt;Long, Bucket&amp;gt; buckets = new ConcurrentHashMap&amp;lt;&amp;gt;();

    public Bucket getBucket(Member member) {
        long memberId = member.getId();
        if (!buckets.containsKey(memberId)) {
            Bucket memberBucket = createBucketByRole(member.getRole());
            buckets.put(memberId, memberBucket);

            return memberBucket;
        }
        return buckets.get(memberId);
    }

    private Bucket createBucketByRole(MemberRole role) {
        Bandwidth memberLimit = BucketPlan.findByMemberRole(role);
        return Bucket.builder()
                .addLimit(memberLimit)
                .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;회원의 id를 통해 Map에서 userId에 적합한 Bucket을 찾아 return해주는 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 id에 해당하는 Bucket이 존재하지 않는다면 유저의 권한을 담은 객체인 MemberRole에 따라 Bucket을 생성하여 return해준다.&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;concurrentHashMap을 통한 유저 Bucket 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷에 대한 정보는 메모리 내에서 Key(UserId), Value(Bucket) 기반의 ConcurrentHashMap에서 관리하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 구현할 때는, 일반적인 HashMap을 사용하였는데 ConcurrentHashMap을 사용하면 Thread Safety하게 데이터에 접근 및 제어가 가능했기 때문에 변경하였다.&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;h4 data-ke-size=&quot;size20&quot;&gt;왜 ConcurrentHashMap을 사용했을까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConcurrentHashMap은 HashMap의 멀티스레드 환경에서의 안전한 버전이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Map 내에 있는 하나의 데이터에 대해 두 Thread에서 동시에 수정 요청을 보내면 먼저 요청한 측에 Lock을 주고 수정이 끝나면 Lock을 반환, 그리고 그 다음 순서로 요청을 보낸 측에서 Lock을 흭득하고 데이터를 수정을 하는 방식으로 동시에 데이터가 수정되어 문제가 발생되는 일을 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 읽기 연산에서 Lock을 사용하지 않기 때문에 &lt;b&gt;여러 쓰레드에서 동시에 읽기 연산이 가능&lt;/b&gt;하여 읽기 연산이 많은 환경의 경우 성능 향상을 기대할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Map의 데이터를 수정할 일이 많진 않아 처음 말한 이점이 적용되어 이점을 얻을 일은 많이 없겠지만, 유저가 로직을 보낼 때마다 Map에서 Bucket 정보를 읽어와야 해야하기 때문에 읽기 연산이 많은 환경이라고 판단이 되었고, 때문에 읽기에 대한 Lock이 걸리지 않는 다는 점이 성능 향상에 도움을 줄 수 있을 것이라고 생각되어 ConcurrentHashMap을 적용하였다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BucketPlan&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public enum BucketPlan {
    // 총 5개의 토큰, 1분마다 5개의 토큰 충전
    MEMBER {
        public Bandwidth getLimit() {
            return Bandwidth.classic(5, Refill.intervally(5, Duration.ofMinutes(1)));
        }
    };

    public abstract Bandwidth getLimit();

    public static Bandwidth findByMemberRole(MemberRole memberRole) {
        return Arrays.stream(values())
                .filter(v -&amp;gt; v.name().equals(memberRole.name()))
                .findFirst()
                .orElseThrow(() -&amp;gt; new IllegalArgumentException(ErrorCode.NOT_EXIST_MEMBER_ROLE))
                .getLimit();
    }
}
&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;BucketPlan&lt;/b&gt;객체는 회원에 권한에 다른 버킷/토큰 정보를 담고 있는 Enum객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MemberRole과 똑같이 &lt;b&gt;BucketPlan&lt;/b&gt; Enum의 열거 상수명을 네이밍하여, MemberRole을 통해 Role에 따른 버킷을 조회할 수 있도록 하였다.&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;Bandwidth&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum에 담겨있는 &lt;b&gt;Bandwidth&lt;/b&gt;는 버킷에 대한 설정 객체이며, 다음과 같이 2가지 매개변수를 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Bucket이 가질 수 있는 최대 토큰 개수&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Token Refill 규칙&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 설정을 통해 role이 MEMBER이면 최대 5개의 토큰을 가질 수 있으며, 1분마다 5개의 토큰이 채워진다는 것을 알 수 있다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RateLimitFilter&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@Slf4j
@RequiredArgsConstructor
public class RateLimitFilter implements Filter {
    private final JwtUtils jwtUtils;
    private final RateLimitBucketProvider bucketProvider;
    private final MemberRepository memberRepository;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String authorizationHeader = httpRequest.getHeader(&quot;Authorization&quot;);

        // authorizationHeader가 없는 경우 Filter 동작 생략
        if(authorizationHeader == null || !authorizationHeader.startsWith(&quot;Bearer &quot;)) {
            chain.doFilter(request, response);
            return;
        }

        //authorizationHeader가 있는 경우 JWT AccessToken이 정상적인 경우 token을 사용
        String accessToken = authorizationHeader.substring(&quot;Bearer &quot;.length());
        if (jwtUtils.validateToken(accessToken)) {
            Long memberId = jwtUtils.getMemberIdFromJwt(accessToken);
            Member member = MemberServiceUtils.findMemberById(memberRepository, memberId);
            //관리자 계정이면 Token 사용X
            if(member.getRole() == MemberRole.ADMIN) {
                log.info(&quot;MemberRole이 ADMIN이기 때문에 토큰이 소모되지 않았습니다.&quot;);
                chain.doFilter(request, response);
                return;
            }

            Bucket memberBucket = bucketProvider.getBucket(member);
            if (!memberBucket.tryConsume(1)) {
                log.error(String.format(&quot;회원ID(%d)의 잔여 토큰이 존재하지 않습니다.&quot;, memberId));
                throw new RateLimitException(ErrorCode.TOKEN_NOT_EXIST_EXCEPTION);
            }
            log.info(String.format(&quot;회원ID(%d)의 토큰 사용 : 잔여 토큰(%d)&quot;, memberId, memberBucket.getAvailableTokens()));
        }

        chain.doFilter(request, response);
    }
}&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;해당 클래스가 지금까지 만든 버킷 로직을 통해 트래픽 제한을 적용시켜줄 Filter이며, Filter 인터페이스를 implements 하여 구현할 수 있다.&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;들어온 요청에 대해 Authorization Header를 검사하여 만약 Header가 존재하지 않으면 doFilter를 통해 다음 필터로 요청을 넘긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한에 대한 검사는 이후 Intercepter에서 처리하도록 구현했기 때문에 여기서는 넘겨도 괜찮다.&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;만약 Authorization 헤더가 존재하면 Authorization 헤더에 담겨있는 JWT 토큰을 검사 후, 토큰을 통해 memberId를 흭득하여 member에 대한 정보를 얻는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 만약 관리자 권한(ADMIN)의 유저이면 토큰을 사용하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 bucketProvider를 통해 Bucket을 흭득하여 토큰을 소모하고 다음 Filter로 요청을 전달하는데, 사용 가능한 token이 존재하지 않으면 잔여 토큰이 존재하지 않는다는 예외를 발생시킨다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ExceptionHandlerFilter&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@Order(Integer.MIN_VALUE)
@Slf4j
public class ExceptionHandlerFilter implements Filter {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        try {
            chain.doFilter(request, response);
        } catch (RateLimitException | IllegalArgumentException exception) {
            setErrorResponse((HttpServletResponse) response, exception);
        }
    }

    private void setErrorResponse(HttpServletResponse response, CustomException exception) throws IOException {
        ApiResponse&amp;lt;Object&amp;gt; errorResponse = ApiResponse.error(exception.getErrorCode());

        response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(exception.getErrorCode().getHttpStatusCode());
    }
}&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;보통 Rest API 서버를 개발할 때 예외를 핸들링은 @RestControllerAdvice 애노테이션을 적용한 ExceptionController를 개발하여 수행한다.&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;하지만 @RestControllerAdvice 애노테이션을 통해 캐치할 수 있는 예외는 컨트롤러 단에서 발생한 예외뿐이다. Filter는 DispatcherServlet에 요청이 도달하기 전 실행되는 영역이므로 ExceptionController에서 처리가 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 Filter에서 발생한 예외처리를 담당하는 ExceptionHandlerFilter를 작성해주었다.&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;@Order 애노테이션을 통한 Filter를 최상단에 위치시키기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 필터를 최상단에 위치시키고 try/catch 를 통해 다음 필터들에서 발생하는 예외를 캐치하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 캐치된 예외는 setErrorResponse메서드를 통해 response되어 사용자에게 전달되도록 구현하였다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작동 결과&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-11-27 오후 3.24.21.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XAzHh/btsH4Yq7JeX/TT4HODzjhVMm2KAKyY8BwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XAzHh/btsH4Yq7JeX/TT4HODzjhVMm2KAKyY8BwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XAzHh/btsH4Yq7JeX/TT4HODzjhVMm2KAKyY8BwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXAzHh%2FbtsH4Yq7JeX%2FTT4HODzjhVMm2KAKyY8BwK%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;708&quot; height=&quot;229&quot; data-filename=&quot;스크린샷 2023-11-27 오후 3.24.21.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;382&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;유저가 1분 내에 5번 연속으로 API를 호출하자 예측한대로 429 Too Many Requests가 발생하여 23ms라는 빠른 속도로 요청을 거부하였다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들마다 구현하는 방식이 다르겠지만 본인은 SpringBoot의 &lt;b&gt;Filter&lt;/b&gt;단에서 요청에 대한 Token 개수를 검사하도록 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 인터넷에서 찾은 예시에는 &lt;b&gt;Interceptor&lt;/b&gt;에서 토큰 개수 검사를 수행하도록 구현하고 있었고 본인도 처음에는 그 예제를 따라 코드를 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현재 프로젝트는 전체적인 트래픽 조절을 위해 모든 API에 대해서 토큰 검사를 수행하고 있기 때문에 &lt;b&gt;Handler&lt;/b&gt;의 정보가 필요하지 않았고 때문에 &lt;b&gt;Interceptor&lt;/b&gt;가 아닌, &lt;b&gt;Filter&lt;/b&gt;에서 검사해야 불필요한 Controller Mapping등의 작업을 수행하지 않기 때문에 성능 면에서 가장 최적화된 방법이라고 생각했다.&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;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-07 오후 10.01.36.png&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/weM6F/btsH3mUsTJd/FdPgrWia86IV6ERXynpGB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/weM6F/btsH3mUsTJd/FdPgrWia86IV6ERXynpGB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/weM6F/btsH3mUsTJd/FdPgrWia86IV6ERXynpGB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FweM6F%2FbtsH3mUsTJd%2FFdPgrWia86IV6ERXynpGB1%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;227&quot; height=&quot;34&quot; data-filename=&quot;스크린샷 2023-12-07 오후 10.01.36.png&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-07 오후 10.01.53.png&quot; data-origin-width=&quot;170&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWYXLF/btsH3yf3XlL/3WjI4DHE5Syi7OECmKLNJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWYXLF/btsH3yf3XlL/3WjI4DHE5Syi7OECmKLNJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWYXLF/btsH3yf3XlL/3WjI4DHE5Syi7OECmKLNJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWYXLF%2FbtsH3yf3XlL%2F3WjI4DHE5Syi7OECmKLNJ1%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;230&quot; height=&quot;27&quot; data-filename=&quot;스크린샷 2023-12-07 오후 10.01.53.png&quot; data-origin-width=&quot;170&quot; data-origin-height=&quot;20&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;b&gt;Filter&lt;/b&gt;와 &lt;b&gt;Interceptor&lt;/b&gt;의 차이는 &lt;b&gt;386msec&lt;/b&gt;만큼 차이가 났다. 작은 수 같지만 이런 작은 차이가 쌓여 성능을 낮추기 때문에, 이런 것들을 하나하나 줄여나가는 것이 개발의 재미이자 덕목이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✅ 만약 이후에 모든 API가 아닌 특정 API에 대해서만 요청을 제한하도록 변경한다면 다시 Interceptor에서 토큰 검사를 수행하거나, 다른 방안을 생각해볼 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>Bucket4j</category>
      <category>tokenbucket</category>
      <category>스프링 트래픽제한</category>
      <category>스프링부트 트래픽자한 라이브러리</category>
      <category>스프링부트 트래픽제한</category>
      <category>자바 토큰버킷</category>
      <category>자바 토큰버킷 라이브러리</category>
      <category>자바 트래픽제한</category>
      <category>자바 트래픽제한 라이브러리</category>
      <category>토큰 버킷 알고리즘</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/153</guid>
      <comments>https://pgmjun.tistory.com/153#entry153comment</comments>
      <pubDate>Tue, 18 Jun 2024 18:05:42 +0900</pubDate>
    </item>
    <item>
      <title>[우아한테크코스 6기] 테코톡 - 방어적 복사</title>
      <link>https://pgmjun.tistory.com/152</link>
      <description>&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;&amp;nbsp;&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=VsYw2GWgZV0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bwVmms/hyWlojEqiS/Oew9bgCYueDv1nMeD5UcP1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=932_172_1008_254&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;[10분 테코톡] 이든의 방어적 복사&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/VsYw2GWgZV0&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;&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;</description>
      <category>부트캠프/우아한테크코스 6기</category>
      <category>woowacourse</category>
      <category>방어적복사</category>
      <category>우아한테크코스</category>
      <category>우아한테크코스 6기</category>
      <category>우테코</category>
      <category>우테코 6기</category>
      <category>자바 방어적복사</category>
      <category>참조객체</category>
      <category>코틀린 방어적복사</category>
      <category>테코톡</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/152</guid>
      <comments>https://pgmjun.tistory.com/152#entry152comment</comments>
      <pubDate>Mon, 17 Jun 2024 11:40:42 +0900</pubDate>
    </item>
    <item>
      <title>스프링부트에 Nginx 리버스 프록시(Reverse Proxy) 서버 연결하기</title>
      <link>https://pgmjun.tistory.com/151</link>
      <description>&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;322&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7qOBN/btsHtGl5fVp/KierUOEAZGaWlHpOgbQdxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7qOBN/btsHtGl5fVp/KierUOEAZGaWlHpOgbQdxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7qOBN/btsHtGl5fVp/KierUOEAZGaWlHpOgbQdxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7qOBN%2FbtsHtGl5fVp%2FKierUOEAZGaWlHpOgbQdxk%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;322&quot; height=&quot;294&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;294&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;리버스 프록시를 도입하게된 계기  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;졸업작품인 코리(Co:RE)의 서버를 개발하면서 실제로 배포를 위한 환경을 만들려고 하다보니,&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;1차적으로 생각이 난 부분은 당시 학습하고 있었던 프록시 서버였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프록시 서버에 대해 모르는 사람은 &lt;a title=&quot;해당 포스팅&quot; href=&quot;https://pgmjun.tistory.com/150&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해당 포스팅&lt;/a&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;/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;h4 data-ke-size=&quot;size20&quot;&gt;⚙️ 보안&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 도구에 WAS의 주소가 그대로 노출되어 다양한 공격의 가능성이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 리버스 프록시를 적용하게 되면 서버를 직접 노출시키지 않고 클라이언트와의 통신을 중계하므로 서버의 실제 위치를 숨기고 보안을 강화할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프록시 서버로 인해 본래 서버의 IP 주소를 노출시키지 않을 수 있어, 해커들의 DDos 공격과 같은 공격을 막는데 용이하다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&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;Scale Out&lt;/b&gt; 했을 때&lt;b&gt; Load Balancing&lt;/b&gt; 기능까지 수행할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;b&gt;&amp;bull;&lt;/b&gt; AWS EC2 &lt;b&gt;'t2.micro'&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;'ubuntu 22.04'&lt;/b&gt; AMI&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;`Nginx 1.18.0`&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;생략 과정 ✂️&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;AWS 계정 생성&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;EC2 생성 (Ubuntu 22.04 AMI 사용)&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull; &lt;/span&gt;보안규칙&lt;/b&gt; 설정&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;HTTP 80번 포트&lt;/b&gt;에 대해 IPv4, IPv6 허용&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;HTTPS 443번 포트&lt;/b&gt;에 대해 IPv4, IPv6 허용&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;SSH 22번 포트&lt;/b&gt; IPv4 허용&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;&amp;bull;&lt;/b&gt; &lt;/span&gt;&lt;b&gt;볼륨 크기&lt;/b&gt;는 EC2 프리티어에서 사용할 수 있는 최댓값인 &lt;b&gt;27GiB&lt;/b&gt; 설정&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;스프링부트 서버가 죽지 않도록 `Swap Memory` 설정하여 `t2.micro` 환경에서 3GB의 메모리 흭득&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx Reverse Proxy 설정 방법 ⚙️&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;  Shell 명령&lt;/h3&gt;
&lt;pre id=&quot;code_1716135390724&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# apt-get 업데이트
sudo apt-get update

# Nginx 설치
sudo apt-get install nginx
sudo service start nginx

# nginx Config 파일 열기
sudo vi /etc/nginx/site-available/default

# http 설정 바로 아래에 server 설정을 추가하여 리버스 프록시 설정(사용하지 않는 주석 전부 제거)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;br /&gt;  /etc/nginx/site-available/default 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1716135458031&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http {
        server {
                listen 80; # 포트를 필요에 따라 조정하세요.
                #server_name your_domain.com; # 도메인 이름 대신 IP 주소를 사용할 경우 필요 없습니다.

                location / {
                        proxy_pass http://서버_주소:포트; # 백엔드 서버의 IP 주소
						proxy_http_version 1.1;

                        proxy_set_header Connection &quot;&quot;; #504 Bad GateWay 문제 방지
                        proxy_set_header Host $host;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                }
        }

        sendfile on;
        tcp_nopush on;
        types_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;⚙️ proxy_set_header Host $host;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이&amp;nbsp;헤더는&amp;nbsp;원격&amp;nbsp;서버로&amp;nbsp;전달되는&amp;nbsp;HTTP&amp;nbsp;요청&amp;nbsp;헤더&amp;nbsp;중&amp;nbsp;하나이다.&lt;br /&gt;&lt;b&gt;`$host`&amp;nbsp;&lt;/b&gt;변수는&amp;nbsp;클라이언트가&amp;nbsp;요청한&amp;nbsp;호스트&amp;nbsp;헤더&amp;nbsp;값을&amp;nbsp;가지고&amp;nbsp;있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;호스트 이름은 일반적으로 `도메인 이름`이나 `IP 주소`인데,&lt;br /&gt;이를&amp;nbsp;통해&amp;nbsp;리버스&amp;nbsp;프록시는&amp;nbsp;요청을&amp;nbsp;받은&amp;nbsp;원격&amp;nbsp;서버에게&amp;nbsp;어디로부터&amp;nbsp;온&amp;nbsp;요청인지&amp;nbsp;알려준다.&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;&amp;nbsp;&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;br /&gt;⚙️ proxy_set_header X-Real-IP $remote_addr;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이&amp;nbsp;헤더는&amp;nbsp;&lt;b&gt;`클라이언트의&amp;nbsp;실제&amp;nbsp;IP&amp;nbsp;주소`&lt;/b&gt;를&amp;nbsp;리버스&amp;nbsp;프록시를&amp;nbsp;통해&amp;nbsp;전달한다.&lt;br /&gt;이것은 원격 서버가 클라이언트의 실제 IP를 알고자 할 때 사용된다.&lt;br /&gt;&lt;br /&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;⚙️ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;일반적으로&amp;nbsp;HTTP&amp;nbsp;요청에는&amp;nbsp;&lt;b&gt;`X-Forwarded-For`&lt;/b&gt;&amp;nbsp;헤더가&amp;nbsp;포함되어&amp;nbsp;있다.&lt;br /&gt;이&amp;nbsp;헤더는&amp;nbsp;`클라이언트`가&amp;nbsp;`어떤&amp;nbsp;IP&amp;nbsp;주소를&amp;nbsp;통해&amp;nbsp;요청을&amp;nbsp;보냈는지`를&amp;nbsp;기록하며,&amp;nbsp;여러&amp;nbsp;프록시&amp;nbsp;서버를&amp;nbsp;거친&amp;nbsp;경우&amp;nbsp;쉼표로&amp;nbsp;구분된&amp;nbsp;목록으로&amp;nbsp;기록된다.&lt;br /&gt;&lt;br /&gt;Nginx에서 &lt;b&gt;`$proxy_add_x_forwarded_for`&lt;/b&gt;&amp;nbsp;변수는&amp;nbsp;이&amp;nbsp;&lt;b&gt;`X-Forwarded-For`&lt;/b&gt;&amp;nbsp;헤더의&amp;nbsp;값을&amp;nbsp;가져와&amp;nbsp;저장한다.&lt;br /&gt;이를&amp;nbsp;통해&amp;nbsp;서버는&amp;nbsp;실제&amp;nbsp;클라이언트의&amp;nbsp;IP&amp;nbsp;주소를&amp;nbsp;파악할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;예를 들어, 클라이언트 &lt;b&gt;A&lt;/b&gt;에서 요청이 오고, 그 요청이 &lt;b&gt;Nginx 프록시 서버&lt;/b&gt; 를 거쳐 백엔드 서버로 전달되면,&lt;br /&gt;&lt;b&gt;X-Forwarded-For&lt;/b&gt; 헤더에는 &lt;b&gt;&quot;A, Nginx_IP&quot;&lt;/b&gt; 와 같이 표시된다.&lt;br /&gt;&lt;br /&gt;이때 &lt;b&gt;$proxy_add_x_forwarded_for&lt;/b&gt; 변수에는 &lt;b&gt;&quot;A, Nginx_IP&quot;&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연결 완료 ✅&lt;/h3&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;1280&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdELKf/btsHI0kzpnt/Pk0cGsNfVp9pFyS02rCszk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdELKf/btsHI0kzpnt/Pk0cGsNfVp9pFyS02rCszk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdELKf/btsHI0kzpnt/Pk0cGsNfVp9pFyS02rCszk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdELKf%2FbtsHI0kzpnt%2FPk0cGsNfVp9pFyS02rCszk%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;1280&quot; height=&quot;446&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;`Nginx`&amp;nbsp;EC2의&amp;nbsp;IP에&amp;nbsp;`80번&amp;nbsp;포트`로&amp;nbsp;접속했는데&amp;nbsp;`SpringBoot`&amp;nbsp;EC2의&amp;nbsp;IP에&amp;nbsp;`8080번&amp;nbsp;포트`로&amp;nbsp;접속한&amp;nbsp;것과&amp;nbsp;동일한&amp;nbsp;화면이&amp;nbsp;출력되는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;또한&amp;nbsp;`SpringBoot`의&amp;nbsp;포트인&amp;nbsp;`8080`번으로&amp;nbsp;접속하면,&amp;nbsp;접속할&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;것을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;이는&amp;nbsp;우리가&amp;nbsp;`AWS의&amp;nbsp;EC2&amp;nbsp;보안&amp;nbsp;그룹의&amp;nbsp;인바운드&amp;nbsp;규칙`에서&amp;nbsp;`8080번`&amp;nbsp;포트를&amp;nbsp;외부에서&amp;nbsp;접속하지&amp;nbsp;가능하도록&amp;nbsp;설정해주지&amp;nbsp;않았기&amp;nbsp;때문이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추후 프로젝트 개선 방향  &lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`WAS`에 대한 접근과 정보 노출을 방어했지만 아직 `Nginx`를 통해서 요청하면 `WAS`에 요청하는 것과 똑같이 요청하여 데이터를 흭득할 수 있다. 그렇기에 고의적으로 `리버스 프록시 서버`에 많은 요청을 보내게 되면, 그대로 받아들여 서버에 과부하가 오게 된다.&lt;/li&gt;
&lt;li&gt;또한 지금 개발하고 있는 서비스 `Co:RE`는 유저들이 운영 측의 `GPT API Key`를 사용하여 무료로 응답값을 얻는 형식이다. 때문에 더더욱 많은 고의적인 요청이 보내지면 금전적으로 큰 손해를 볼 수 밖에 없다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이에 대한 결론으로 다음 개발 과제는 &lt;b&gt;`과도한 트래픽에 대한 방어`&lt;/b&gt;로 잡았다.&lt;/li&gt;
&lt;li&gt;`Bucket4j` 라이브러리를 사용하면 Token-Bucket 알고리즘을 통해&lt;br /&gt;유저들에게 API를 요청할 수 있는 &lt;b&gt;`Token`&lt;/b&gt;을 주고 Token의 `사용 시간` 또는 `사용 횟수`에 제한을 걸어,&lt;br /&gt;&lt;b&gt;고의적인 요청을 방어&lt;/b&gt;할 수 있다고 한다.&lt;br /&gt;때문에 이를 공부하여&amp;nbsp; 적용해보고자 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>BackEnd</category>
      <category>nginx</category>
      <category>nginx 리버스 프록시</category>
      <category>nginx 프록시 서버</category>
      <category>springboot reverse proxy</category>
      <category>was nginx</category>
      <category>스프링 nginx</category>
      <category>스프링부트 nginx</category>
      <category>스프링부트 리버스 프록시</category>
      <category>스프링부트 프록시</category>
      <category>스프링부트 프록시 서버</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/151</guid>
      <comments>https://pgmjun.tistory.com/151#entry151comment</comments>
      <pubDate>Mon, 20 May 2024 01:24:27 +0900</pubDate>
    </item>
    <item>
      <title>프록시(Proxy) 서버란?</title>
      <link>https://pgmjun.tistory.com/150</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;프록시 서버란?  &lt;/h2&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;`Proxy`&lt;/b&gt;라는 단어는 대리(행위)나 대리권, 대리 투표, 대리인 등을 뜻하며 말 그대로 서버 요청에 대한 대리자 역할을 수행하는 것을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 서버 라고 한다.&lt;br /&gt;&lt;br /&gt;프록시 서버(Proxy Server) 는 클라이언트와 서버 사이에서 동작하는 중간 서버를 의미한다.&lt;br /&gt;&lt;br /&gt;클라이언트가&amp;nbsp;서버에&amp;nbsp;직접&amp;nbsp;요청을&amp;nbsp;보내는&amp;nbsp;대신&amp;nbsp;`프록시&amp;nbsp;서버`는&amp;nbsp;클라이언트&amp;nbsp;요청을&amp;nbsp;받아&amp;nbsp;서버로&amp;nbsp;전달하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서&amp;nbsp;받은&amp;nbsp;응답을&amp;nbsp;클라이언트에게&amp;nbsp;다시&amp;nbsp;전달하는&amp;nbsp;중계자&amp;nbsp;역할을&amp;nbsp;한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;프록시 서버의 종류 ⚙️&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;h3 data-ke-size=&quot;size23&quot;&gt;  포워드 프록시(Forward Proxy)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HMX1o/btsHuzGeyrt/WJKCDpkiYbRfu9Oz8VUwu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HMX1o/btsHuzGeyrt/WJKCDpkiYbRfu9Oz8VUwu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HMX1o/btsHuzGeyrt/WJKCDpkiYbRfu9Oz8VUwu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHMX1o%2FbtsHuzGeyrt%2FWJKCDpkiYbRfu9Oz8VUwu1%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;651&quot; height=&quot;407&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;407&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;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;클라이언트의 요청을 가로채 통신을 대리 수행해준다.&lt;/li&gt;
&lt;li&gt;때문에 `WAS`는 `클라이언트`에 대해 알 수 없다. IP 역추적을 해도 프록시 서버의 IP만 보인다.&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;연결된 클라이언트가 요청한 데이터를 반환할 때 저장했다가 다른 클라이언트가 동일한 데이터를 요청하면 &lt;b&gt;인터넷을 거치지 않고 바로 반환&lt;/b&gt;해준다.&lt;/li&gt;
&lt;li&gt;전송 시간이 절약되고 외부 요청이 감소되어 &lt;b&gt;네트워크 병목 현상을 방지&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;포워드 프록시는 요청을 보내는 `클라이언트 측`에 설정해두는 프록시 서버로,&lt;br /&gt;클라이언트에서&amp;nbsp;특정&amp;nbsp;서버로&amp;nbsp;요청을&amp;nbsp;보내기&amp;nbsp;전에&amp;nbsp;프록시를&amp;nbsp;거쳐서&amp;nbsp;요청을&amp;nbsp;전송하는&amp;nbsp;것이다.&lt;br /&gt;즉,&amp;nbsp;클라이언트가&amp;nbsp;아닌&amp;nbsp;프록시&amp;nbsp;서버의&amp;nbsp;입장에서&amp;nbsp;요청을&amp;nbsp;보내&amp;nbsp;익명성을&amp;nbsp;보장할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;것이다.&lt;br /&gt;&lt;br /&gt;자세한 내용과 Nginx를 통한 설정 방법은 아래 참고 자료를 통해 확인해보길 바란다.&lt;/blockquote&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Nginx 포워드 프록시 사용법1](&lt;a href=&quot;https://recordsoflife.tistory.com/m/924)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://recordsoflife.tistory.com/m/924)&lt;/a&gt;&lt;br /&gt;[Nginx&amp;nbsp;포워드&amp;nbsp;프록시&amp;nbsp;사용법2](&lt;a href=&quot;https://earth-95.tistory.com/m/138)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://earth-95.tistory.com/m/138)&lt;/a&gt;&lt;br /&gt;[Nginx&amp;nbsp;포워드&amp;nbsp;프록시와&amp;nbsp;리버스&amp;nbsp;프록시](&lt;a href=&quot;https://medium.com/humanscape-tech/nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-forward-proxy-%EA%B5%AC%ED%98%84-91f3555549be)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/humanscape-tech/nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-forward-proxy-%EA%B5%AC%ED%98%84-91f3555549be)&lt;/a&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;h3 data-ke-size=&quot;size23&quot;&gt;  리버스 프록시(Reverse Proxy)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WmKv0/btsHtwKEDHA/K56KkPqHBRDfY59c3Bk9x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WmKv0/btsHtwKEDHA/K56KkPqHBRDfY59c3Bk9x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WmKv0/btsHtwKEDHA/K56KkPqHBRDfY59c3Bk9x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWmKv0%2FbtsHtwKEDHA%2FK56KkPqHBRDfY59c3Bk9x0%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;682&quot; height=&quot;438&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리버스 프록시는 대표적으로 &lt;b&gt;로드 밸런싱(Load Balancing)&lt;/b&gt;에 사용된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리버스 프록시를 2개 이상의 서버 앞에 두면 대용량 요청이 들어왔을 때 서버가 과부화 되지않도록&lt;br /&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;클라이언트는 `리버스 프록시`이 진짜 서버인 것처럼 요청을 보내게 되기 때문에 클라이언트는 서버의 정체를 알 수 없어 보안성이 좋다.&lt;/li&gt;
&lt;li&gt;본인은 AWS EC2에서 서버들을 관리하기 때문에 인바운드 보안 규칙 설정을 통해&lt;br /&gt;Reverse Proxy Server를 거쳐서만 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;WAS에&lt;/span&gt;&amp;nbsp;접근할 수 있도록 설정하여 &lt;b&gt;보안성을 더욱 개선&lt;/b&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;Forward Proxy와 동일하지만 이미 프록시 서버가 캐싱하여 알고 있는 내용도 인터넷을 거친다는 차이가 있어&lt;br /&gt;Forward Proxy가 캐싱된 데이터를 조금 더 빠르게 전송하는 것으로 이해했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;리버스 프록시는 요청을 받는 `서버 측`에 설정해두는 프록시 서버로,&lt;br /&gt;WAS에&amp;nbsp;요청이&amp;nbsp;도달하기&amp;nbsp;전에&amp;nbsp;프록시를&amp;nbsp;거치게&amp;nbsp;되는&amp;nbsp;것이다.&lt;br /&gt;&lt;br /&gt;이는&amp;nbsp;WAS가&amp;nbsp;직접&amp;nbsp;요청을&amp;nbsp;받는&amp;nbsp;것이&amp;nbsp;아니라&amp;nbsp;프록시&amp;nbsp;서버가&amp;nbsp;WAS의&amp;nbsp;앞단에서&amp;nbsp;요청을&amp;nbsp;대신&amp;nbsp;전달받는&amp;nbsp;것이기&amp;nbsp;때문에&amp;nbsp;WAS의&amp;nbsp;`익명성`을&amp;nbsp;보장할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;것이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&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;&lt;b&gt;`포워드 프록시`&lt;/b&gt;는 &lt;b&gt;`클라이언트의 정보를 보호`&lt;/b&gt;하는 것에 효과적이며&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`리버스 프록시`&lt;/b&gt;는 &lt;b&gt;`서버의 정보를 보호`&lt;/b&gt;하는 것에 효과적이다.&lt;/li&gt;
&lt;/ul&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 title=&quot;다음 포스팅&quot; href=&quot;https://pgmjun.tistory.com/151&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 포스팅&lt;/a&gt;에서는 직접 Nginx를 통해 리버스 프록시 서버를 만드는 과정을 진행해보겠다 :)&lt;/p&gt;</description>
      <category>BackEnd</category>
      <category>Forward Proxy</category>
      <category>nginx</category>
      <category>proxy server</category>
      <category>Reverse Proxy</category>
      <category>리버스 프록시</category>
      <category>리버스 프록시 서버</category>
      <category>스프링부트</category>
      <category>포워드 프록시</category>
      <category>프록시 서버</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/150</guid>
      <comments>https://pgmjun.tistory.com/150#entry150comment</comments>
      <pubDate>Sun, 19 May 2024 23:26:06 +0900</pubDate>
    </item>
    <item>
      <title>[길벗 리뷰어] 현직 LINE 개발자가 알려주는 읽기 쉽고 코드 리뷰하기 좋은 코드 작성 가이드 리뷰</title>
      <link>https://pgmjun.tistory.com/149</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI3Q9J/btsHvvC5PDp/zUgyrkkBlXBkHdr9SvUGyk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI3Q9J/btsHvvC5PDp/zUgyrkkBlXBkHdr9SvUGyk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI3Q9J/btsHvvC5PDp/zUgyrkkBlXBkHdr9SvUGyk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI3Q9J%2FbtsHvvC5PDp%2FzUgyrkkBlXBkHdr9SvUGyk%2Fimg.jpg&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;344&quot; height=&quot;510&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;1200&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;&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;최근 길벗에서 &quot;읽기 쉽고 코드 리뷰하기 좋은 코드 작성 가이드&quot;라는 책 리뷰어를 구하는 것을 보고 고민없이 지원하여 읽게 되었다.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 책을 받기 전, 서적을 판매하는 각종 사이트에 들어가 해당 책에 대한 리뷰를 확인하였는데, 대부분의 리뷰가 만점일 정도로 좋은 책이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 받아본 이 책은 현직 LINE 개발자들의 실제 경험을 바탕으로 작성된 이 책은 실용적인 조언과 유용한 팁으로 가득 차 있었고&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;이 책도 국내 개발자분이 아니라 일본 LINE 개발자분이 작성하신 책이라 사실 조금은 걱정이 되었다.&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;h3 style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;주요 내용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 내용을 살펴보자면 아래와 같았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코딩 스타일, 클린 코드 원칙, 리팩토링, 테스트 작성, 코드 리뷰, 도구 및 설정 등 여섯 가지 주제로 나뉘어 있는 책이었고&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; 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;span style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot;&gt;본인은 그 중 이 2가지 주제가 가장 도움이 되었던 것 같다.&lt;/span&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 style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 작성하다보면 세팅을 위한 부수적인 코드가 늘어나 테스트 코드의 가독성을 향상시켜야겠다는 필요성을 느끼고 있었는데, 마침 해당 책에서 도움을 받을 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 현재 코드 리뷰를 정말 많이 주고받고 있는 만큼 효과적인 코드 리뷰 방식을 잡아가고 싶었는데 이 또한 책에서 주로 다루는 내용이었기에 많이 도움을 받았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size23&quot;&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;&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;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 책을 읽은 후, 좋은 코드와 좋은 코드 리뷰 문화를 조성하기 위해 책에서 좋다고 느낀 부분들을 계속 흝어보며 노력하고 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 철저한 테스트 코드 작성에 더 많은&amp;nbsp; 기존 코드의 품질을 리팩토링을 통해 향상시키고 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1918; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로도 이 책에서 제공하고 있는 지식들을 꾸준히 습득해나가며 어제보다 더 나은 내가 되기 위해 계속 노력할 것이다.&lt;/p&gt;</description>
      <category>외부활동/도서 리뷰어</category>
      <category>line 개발자 서적</category>
      <category>개발 서적</category>
      <category>길벗 리뷰어</category>
      <category>길벗 현직 line 개발자가 알려주는 읽기 쉽고 코드 리뷰하기 좋은 코드 작성 가이드 리뷰</category>
      <category>좋은 코드 작성 서적</category>
      <category>코드 리뷰 관련 서적</category>
      <category>코드 작성 관련 서적</category>
      <category>코틀린</category>
      <category>현직 line 개발자가 알려주는 읽기 쉽고 코드 리뷰하기 좋은 코드 작성 가이드 리뷰</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/149</guid>
      <comments>https://pgmjun.tistory.com/149#entry149comment</comments>
      <pubDate>Sun, 19 May 2024 21:51:08 +0900</pubDate>
    </item>
    <item>
      <title>[Git] Github 협업 이것만은 알자!</title>
      <link>https://pgmjun.tistory.com/148</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;&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;figure id=&quot;og_1713017245143&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;시리즈 | Github 협업, 이것만은 알자 - pgmjun.log&quot; data-og-description=&quot;이슈(Issue)란 프로젝트에서 작업해야할 단위라고 할 수 있습니다.개발해야하는 기능 발생, 수정해야할 사항 버그 발생, 리팩터링 해야하는 코드 발생 등 프로젝트에서 발생되는 작업들을 이슈로&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@pgmjun/series/Github-%ED%98%91%EC%97%85-%EC%9D%B4%EA%B2%83%EB%A7%8C%EC%9D%80-%EC%95%8C%EC%9E%90&quot; data-og-url=&quot;https://velog.io/@pgmjun/series/Github-%ED%98%91%EC%97%85-%EC%9D%B4%EA%B2%83%EB%A7%8C%EC%9D%80-%EC%95%8C%EC%9E%90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bUfWem/hyVPUWjgyP/rbjP9goTv0APFCpmI4TOUK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/XFwWu/hyVMZkFgJW/a70QQYU27ISQcbrIztVgSK/img.png?width=1100&amp;amp;height=619&amp;amp;face=0_0_1100_619,https://scrap.kakaocdn.net/dn/Edo8E/hyVPSEcblc/yXFfgIRmqkn4cIPXjHbLG1/img.png?width=1100&amp;amp;height=619&amp;amp;face=0_0_1100_619&quot;&gt;&lt;a href=&quot;https://velog.io/@pgmjun/series/Github-%ED%98%91%EC%97%85-%EC%9D%B4%EA%B2%83%EB%A7%8C%EC%9D%80-%EC%95%8C%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@pgmjun/series/Github-%ED%98%91%EC%97%85-%EC%9D%B4%EA%B2%83%EB%A7%8C%EC%9D%80-%EC%95%8C%EC%9E%90&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bUfWem/hyVPUWjgyP/rbjP9goTv0APFCpmI4TOUK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/XFwWu/hyVMZkFgJW/a70QQYU27ISQcbrIztVgSK/img.png?width=1100&amp;amp;height=619&amp;amp;face=0_0_1100_619,https://scrap.kakaocdn.net/dn/Edo8E/hyVPSEcblc/yXFfgIRmqkn4cIPXjHbLG1/img.png?width=1100&amp;amp;height=619&amp;amp;face=0_0_1100_619');&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 협업, 이것만은 알자 - pgmjun.log&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이슈(Issue)란 프로젝트에서 작업해야할 단위라고 할 수 있습니다.개발해야하는 기능 발생, 수정해야할 사항 버그 발생, 리팩터링 해야하는 코드 발생 등 프로젝트에서 발생되는 작업들을 이슈로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Git</category>
      <category>git</category>
      <category>git 협업</category>
      <category>github</category>
      <category>github 협업</category>
      <category>github 협업 기초</category>
      <category>vcs</category>
      <category>vcs 협업</category>
      <category>깃허브</category>
      <category>깃허브 기초</category>
      <category>깃허브 협업</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/148</guid>
      <comments>https://pgmjun.tistory.com/148#entry148comment</comments>
      <pubDate>Sat, 13 Apr 2024 23:11:10 +0900</pubDate>
    </item>
    <item>
      <title>@PostConstruct, Static Block, Instance Block 초기화 시점 차이</title>
      <link>https://pgmjun.tistory.com/147</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;&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;237&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clfNfS/btsGA1j851A/HaYFXeo0Oro8tvqwyKJU00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clfNfS/btsGA1j851A/HaYFXeo0Oro8tvqwyKJU00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clfNfS/btsGA1j851A/HaYFXeo0Oro8tvqwyKJU00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclfNfS%2FbtsGA1j851A%2FHaYFXeo0Oro8tvqwyKJU00%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;237&quot; height=&quot;213&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;213&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 스프링 기술로 개발을 해봤다면&amp;nbsp;&lt;b&gt;@PostConstruct&lt;/b&gt;&amp;nbsp;과&amp;nbsp;&lt;b&gt;Static block&lt;/b&gt;, &lt;b&gt;Instance block &lt;/b&gt;을 사용해본 경험이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 객체 초기화를 위한 콜백(callback) 함수를 선언하기 위한 기술이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;콜백(Callback) 함수란?&lt;/h4&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;다른 함수 인자로서 사용되는 함수&lt;/li&gt;
&lt;li&gt;어떠한 이벤트에 의해 호출되는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 글에서의 설명은 2번째 의미인 '어떠한 이벤트에 의해 호출되는 함수' 에 가깝다.&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;&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;b&gt;@PostConstruct&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;b&gt;Static block&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;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;Static Block&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;static&lt;/b&gt; 키워드로 선언된 블록이며, 클래스가 처음으로 메서드(Method) 영역에 로드될 때 실행된다.&lt;/li&gt;
&lt;li&gt;클래스 로딩 시 한 번만 실행되며, 클래스 변수(static 변수)의 초기화나 클래스 수준의 작업을 수행할 수 있다.&lt;/li&gt;
&lt;li&gt;일반적으로 클래스 변수의 초기화에 사용된다.&lt;/li&gt;
&lt;li&gt;여러 개의 static 블록이 있다면, 선언된 순서대로 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public class MyClass {
    static {
        // static block
        // 클래스가 로드될 때 실행됩니다.
        // 클래스 변수의 초기화 등에 사용됩니다.
    }
}
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;Instance Block&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의인스턴스를 생성할 때마다 실행되는 블록이다.&lt;/li&gt;
&lt;li&gt;각 인스턴스마다 초기화 작업을 할 수 있다.&lt;/li&gt;
&lt;li&gt;생성자가 호출되기 전에 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;public class MyClass {
    {
        // instance block
        // 인스턴스를 생성할 때마다 실행됩니다.
        // 생성자가 호출되기 전에 실행됩니다.
    }
}
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;@PostConstruct&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 프레임워크에서 사용되는 어노테이션으로, &lt;b&gt;객체의 Bean 생성 및 DI(Dependency Injection) 후&lt;/b&gt;에 호출된다.&lt;/li&gt;
&lt;li&gt;주로 Spring Bean의 초기화 작업에 사용되며, Spring Bean의 생명주기 콜백 메서드를 정의하기 위해 사용된다.&lt;/li&gt;
&lt;li&gt;해당 객체의 모든 의존성이 주입된 후 호출된다.&lt;/li&gt;
&lt;li&gt;@PostConstruct와 반대로 스프링 빈이 소멸되기 직전에 호출되는 @PreDestroy 라는 애노테이션도 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class MyClass {
    @PostConstruct
    void init() {
        // @PostConstruct annotated method
        // 객체 생성 및 의존성 주입 후 호출됩니다.
        // 주로 초기화 작업에 사용됩니다.
    }
}
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Static Block:&lt;/b&gt; 클래스가 로드될 때 실행되며, 한 번만 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Instance Block:&lt;/b&gt; 인스턴스가 생성될 때마다 실행되며, 생성자 호출 전에 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@PostConstruct&lt;/b&gt;: Spring Bean의 초기화 작업에 사용되며, 의존성 주입 후 호출&lt;/li&gt;
&lt;/ul&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;흐름에 따른, 각 콜백 함수 호출 시점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(Static Block 호출)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;-&amp;gt; 스프링 컨테이너 생성 -&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;스프링 빈 생성(Instance Block 호출)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;-&amp;gt; 의존관계 주입&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;초기화 콜백(@PostConstruct 호출)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;빈&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;사용&lt;/span&gt;&lt;span style=&quot;color: #d9d9d9;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;소멸전 콜백(@PreDestroy 호출)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;-&amp;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 @PostConstruct와 Static Block의 차이가 크지 않을 줄 알았으나 이번 계기를 통해 내용을 정리하게 되면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@PostConstruct는 클래스 로딩 시점에 사용되는 Static Block과는 별개로, 스프링 빈의 생명주기와 관련된 기술임을 깨닫게 되었다.&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;보통 객체 초기화 콜백 정의는 항상 @PostConstruct를 사용하곤 했는데, 앞으로는 상황에 맞게 사용할 수 있는 지식이 쌓인 것 같다고 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;혹시라도 정리된 내용 중 잘못 설명된 내용이 있다면 댓글 부탁드립니다!&lt;/blockquote&gt;</description>
      <category>BackEnd/Spring</category>
      <category>@PostConstruct</category>
      <category>Instance Block</category>
      <category>static block</category>
      <category>자바 블럭 차이</category>
      <category>자바 스태틱 블랙</category>
      <category>자바 인스턴스 블럭</category>
      <category>자바 정적 블럭</category>
      <category>자바 초기화 콜백</category>
      <category>자바 콜백</category>
      <category>자바 콜백 메서드</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/147</guid>
      <comments>https://pgmjun.tistory.com/147#entry147comment</comments>
      <pubDate>Sat, 13 Apr 2024 15:36:44 +0900</pubDate>
    </item>
    <item>
      <title>의존성 주입 시, 같은 Type의 빈이 2개 이상일 경우</title>
      <link>https://pgmjun.tistory.com/146</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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-13 12.41.56.png&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXRJf/btsGBZeHXpj/uSbSg6Hecyre0ykWr4NJw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXRJf/btsGBZeHXpj/uSbSg6Hecyre0ykWr4NJw0/img.png&quot; data-alt=&quot;DiscountPolicy 정적 의존관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXRJf/btsGBZeHXpj/uSbSg6Hecyre0ykWr4NJw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXRJf%2FbtsGBZeHXpj%2FuSbSg6Hecyre0ykWr4NJw0%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;332&quot; height=&quot;230&quot; data-filename=&quot;스크린샷 2024-04-13 12.41.56.png&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DiscountPolicy 정적 의존관계&lt;/figcaption&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;그런데 의존성을 주입하는 경우, 2개의 의존성 중 하나를 주입해야하는 경우가 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface DiscountPolicy {}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class FixDiscountPolicy implements DiscountPolicy {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class RateDiscountPolicy implements DiscountPolicy {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Autowired
private DiscountPolicy discountPolicy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위와 같이 FixDiscountPolicy와 RateDiscountPolicy가 스프링 빈으로 존재할 때,&lt;br /&gt;다음과 같이 @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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예외 발생&lt;/p&gt;
&lt;pre id=&quot;code_1712979909085&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NoUniqueBeanDefinitionException: No qualifying bean of type
 'hello.core.discount.DiscountPolicy' available: expected single matching bean
 but found 2: fixDiscountPolicy,rateDiscountPolicy&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;결과는 다음과 같이 NoUniqueBeanDefinitionException 예외가 발생할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 빈이 매칭되어야 하지만, 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;@Autowired는 Type으로 빈을 매칭하기 때문에&lt;br /&gt;`applicationContext&lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.getBean(DiscountPolicy.class)` 이렇게 빈을 조회하는 것과 유사하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 해결 방안으로 다음 3가지 방식이 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Autowired 필드 명 매칭&lt;/li&gt;
&lt;li&gt;@Qualifier 사용&lt;/li&gt;
&lt;li&gt;@Primary 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. @Autowired 필드 명 매칭&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@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;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Autowired
private DiscountPolicy discountPolicy&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;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Autowired
private DiscountPolicy rateDiscountPolicy&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;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이렇게 타입의 이름을 필드 명으로 정의하게 되면 동일한 타입의 빈이 2개 이상 있을 경우,&lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. @Qualifier 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Qualifier는 '예선의' 라는 뜻으로, 추가 구분자를 붙여주고 의존성 주입 시 어떤 빈을 주입할 지 지정하여&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;&lt;b&gt;ComponentScan 등록 시 예시&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@Qualifier(&quot;mainDiscountPolicy&quot;)
public class RateDiscountPolicy implements DiscountPolicy {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712981611222&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@Qualifier(&quot;fixDiscountPolicy&quot;)
public class FixDiscountPolicy implements DiscountPolicy {}&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;다음과 같이 빈 등록 시, @Qualifier 애노테이션과 함께 추가 구분자(이름)를 설정하면 된다.&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;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Bean 애노테이션을 통한 빈 직접 등록 시 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712982022007&quot; class=&quot;less&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;@Bean
@Qualifier(&quot;mainDiscountPolicy&quot;)
public DiscountPolicy discountPolicy() {
   return new ...
}&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;@Bean 애노테이션을 통해 빈을 직접 등록하는 경우에도 다음과 같이 @Qualifier 애노테이션을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1712981683038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                        @Qualifier(&quot;mainDiscountPolicy&quot;) DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}&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;생성자 자동 주입을 하는 경우, 생성자의 파라미터 옆에 @Qualifier 애노테이션과 함께 어떤 이름의 빈을 주입시킬 지 정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정자 자동 주입 예시&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1712981737717&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier(&quot;mainDiscountPolicy&quot;)
DiscountPolicy discountPolicy) {
    this.discountPolicy = discountPolicy;
}&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;수정자 자동 주입을 하는 경우, 수정자 메서드의 파라미터 옆에 @Qualifier 애노테이션과 함께 어떤 이름의 빈을 주입시킬 지 정할 수 있다.&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;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;@Qualifier 구분자를 찾지 못할 경우&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;만약 @Qualifier(&quot;mainDiscountPolicy&quot;)를 찾지 못한다면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 스프링은 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 @Qualifier는 @Qualifier를 찾는 용도로만 사용하는 것이 명확하고 유지보수성과 휴먼에러 방지에 탁월하다.&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;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;@Primary &lt;/span&gt;&lt;span&gt;사용&lt;br /&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@Primary는 여러 빈이 매칭되는 경우에 우선 순위를 부여할 수 있는 애노테이션이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;같은 타입의 빈이 여러 개 조회되는 경우, @Primary 애노테이션을 가진 빈이 우선권을 가진다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;사용법&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712982175947&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;@Qualifier 를 사용할 경우 해당 애노테이션을 사용하는 모든 생성자 주입 과정에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Qualifier 애노테이션을 통해 주입할 빈을 특정해주는 코드를 작성해주어야 하는데&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Primary, @Qualifier 함께 사용하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서비스가 대체로 사용되는 메인DB와, 가끔 특정 동작을 위해 사용되는 서브DB가 모두 연결되었다고 가정하자.&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;이렇게 2개의 빈이 모두 사용되는데 하나는 자주 사용되고, 하나는 가끔 사용되는 경우 @Primary, @Qualifier를 함께 사용함으로써&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링은 자동 보단 수동이, 넓은 범위의 선택권 보다는 좁은 범위의 선택권이 우선순위가 훨씬 높다.&lt;/span&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;때문에 @Qualifier 애노테이션 설정이 없는 경우, @Primary로 설정해놓은 빈이 주입될 것이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Qualifier를 설정한 경우 해당 설정이 적용될 것이다.&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;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Custom Annotation을 통한 @Qualifier 사용 방식 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Qualifier는 위에서 설명한 예제와 같이 사용하면 정말 편리하게 의존성을 조절하며 개발을 해나갈 수 있다.&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;하지만 @Qualifier는 한 가지 단점이 존재한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;컴파일 시점에서 구분자 오류가 잡히지 않음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Qualifier는 컴파일 시점에서 구분자(이름)이 잘못되었음을 체크하지 못한다.&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;뿐만 아니라 문자열로 이름을 설정해주기 때문에, 이름에 변경이 발생하면 모든 @Qualifier 애노테이션을 찾아다니면서&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;이러한 문제를 해결해야 @Qualifier를 더욱 안전하게 사용할 수 있을 것 같은데, 어떻게 해결할 수 있을까?&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;h4 data-ke-size=&quot;size20&quot;&gt;Custom Annotaion 만들기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712984110512&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
 
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier(&quot;mainDiscountPolicy&quot;)
public @interface MainDiscountPolicy {}&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;커스텀 애노테이션 내부에 정확한 구분자를 설정해놓은 @Qualifier 애노테이션을 추가해놓으면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 @Qualifier를 사용할 때마다, 구분자를 문자열로 설정해줄 필요없이 애노테이션만 추가해주면 되어&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;뿐만 아니라 구분자가 변경된 경우 해당 애노테이션 내부의 @Qualifier 애노테이션의 구분자만 수정해주면 전체적으로 변경이 발생하기에 관리가 편리해진다.&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;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;p data-ke-size=&quot;size18&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&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1712984359734&quot; class=&quot;java&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;@Autowired
 public OrderServiceImpl(MemberRepository memberRepository,
                         @MainDiscountPolicy DiscountPolicy discountPolicy) {
     this.memberRepository = memberRepository;
     this.discountPolicy = discountPolicy;
}&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;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정자 자동 주입 예시&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1712984359735&quot; class=&quot;java&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;@Autowired
 public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
     this.discountPolicy = discountPolicy;
 }&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;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조회한 빈이 모두 필요한 경우 (with. Collection)&lt;/h3&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;예를 들어서 할인 서비스를 제공하는데, 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있다고 가정해보자.&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;h4 data-ke-size=&quot;size20&quot;&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;pre id=&quot;code_1712985039482&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hello.core.AutoAppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import
org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
 
public class AllBeanTest {
    @Test
    void findAllBean() {
        ApplicationContext ac = new
AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, &quot;userA&quot;, Grade.VIP);
        int discountPrice = discountService.discount(member, 10000,
&quot;fixDiscountPolicy&quot;);

        assertThat(discountService).isInstanceOf(DiscountService.class);
        assertThat(discountPrice).isEqualTo(1000);
    }
    
    static class DiscountService {
    
        private final Map&amp;lt;String, DiscountPolicy&amp;gt; policyMap;
        private final List&amp;lt;DiscountPolicy&amp;gt; policies;
        
        public DiscountService(Map&amp;lt;String, DiscountPolicy&amp;gt; policyMap,
List&amp;lt;DiscountPolicy&amp;gt; policies) {
            this.policyMap = policyMap;
            this.policies = policies;
            System.out.println(&quot;policyMap = &quot; + policyMap);
            System.out.println(&quot;policies = &quot; + policies);
        }
        
        public int discount(Member member, int price, String discountCode) {
        
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            
            System.out.println(&quot;discountCode = &quot; + discountCode);
            System.out.println(&quot;discountPolicy = &quot; + discountPolicy);
            
            return discountPolicy.discount(member, price);
		}
	}
}&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;고정 할인 정책(fixDiscountPolicy)은 1000원을 고정으로 할인해주는 전략이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우리는 클라이언트로 부터 &quot;fixDiscountPolicy&quot; 라는 discountCode를 입력받은 상태이고,&lt;br /&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;DiscountService를 살펴보면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Map&amp;lt;String, DiscountPolicy&amp;gt; policyMap, List&amp;lt;DiscountPolicy&amp;gt; policies 두 개의 필드가 존재하는 것을 알 수 있다.&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;&amp;nbsp;&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;주입 동작 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;Map&amp;lt;String, Type&amp;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;Map&amp;lt;String,DiscountPolicy&amp;gt; 형태의 필드가 존재하면 스프링은 의존성 주입 시, DiscountPolicy 타입의 빈을 모두 탐색한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 Key(String) 에는 discountPolicy 빈의 이름, Value(DiscountPolicy) 에는 빈 객체를 삽입한 Map을 생성하여&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;List&amp;lt;Type&amp;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;List&amp;lt;DiscountPolicy&amp;gt; 형태의 필드가 존재한다면 스프링은 의존성 주입 시, DiscountPolicy 타입의 빈을 모두 탐색한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 List 내부에 DiscountPolicy 타입의 빈을 모두 담아 의존성을 주입한다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;찾는 타입의 빈이 존재하지 않을 경우&lt;/h4&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;컬렉션 주입 사용 시 주의점!&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 로직과 같이 Map,List를 통해 다형성을 적극적으로 활용하는 경우 내가 개발한 코드라면 이해하기 쉽겠지만&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;때문에 해당 방식을 사용하는 경우 같은 타입의 빈을 Config 파일에서 수동으로 등록하여 응집도를 높게 관리하거나,&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;</description>
      <category>BackEnd/Spring</category>
      <category>스프링 DI 순서</category>
      <category>스프링 DI 우선순위</category>
      <category>스프링 다중컴포넌트 설정</category>
      <category>스프링 의존성</category>
      <category>스프링 의존성 다루기</category>
      <category>스프링 의존성 주입</category>
      <category>스프링 의존성 주입 우선순위</category>
      <category>의존성 주입 순서</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/146</guid>
      <comments>https://pgmjun.tistory.com/146#entry146comment</comments>
      <pubDate>Sat, 13 Apr 2024 14:29:58 +0900</pubDate>
    </item>
    <item>
      <title>컴포넌트 스캔(ComponentScan)</title>
      <link>https://pgmjun.tistory.com/145</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;a href=&quot;https://pgmjun.tistory.com/144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;을 통해 @Configuration 애노테이션을 통한 스프링 빈 등록에 대해 알아보았다.&lt;/p&gt;
&lt;figure id=&quot;og_1712851002815&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] @Configuration과 싱글톤 (with. CGLIB)&quot; data-og-description=&quot;@Configuration public class AppConfig { @Bean public MemberService memberService() { return new MemberServiceImpl(memberRepository()); } @Bean public OrderService orderService() { return new OrderServiceImpl(memberRepository(), discountPolicy()); } @Bean p&quot; data-og-host=&quot;pgmjun.tistory.com&quot; data-og-source-url=&quot;https://pgmjun.tistory.com/144&quot; data-og-url=&quot;https://pgmjun.tistory.com/144&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dDmj2T/hyVMQASjZm/wB9uJDdaksrd9OVA0abccK/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/bT5dRP/hyVMPhFoDT/EB4URXa5u7nAYbklzdUjh1/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/caZJZO/hyVM1bjvIO/0FdELrvMZsCbnnH2Jwpr5K/img.png?width=1218&amp;amp;height=270&amp;amp;face=0_0_1218_270&quot;&gt;&lt;a href=&quot;https://pgmjun.tistory.com/144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pgmjun.tistory.com/144&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dDmj2T/hyVMQASjZm/wB9uJDdaksrd9OVA0abccK/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/bT5dRP/hyVMPhFoDT/EB4URXa5u7nAYbklzdUjh1/img.png?width=800&amp;amp;height=422&amp;amp;face=0_0_800_422,https://scrap.kakaocdn.net/dn/caZJZO/hyVM1bjvIO/0FdELrvMZsCbnnH2Jwpr5K/img.png?width=1218&amp;amp;height=270&amp;amp;face=0_0_1218_270');&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] @Configuration과 싱글톤 (with. CGLIB)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;@Configuration public class AppConfig { @Bean public MemberService memberService() { return new MemberServiceImpl(memberRepository()); } @Bean public OrderService orderService() { return new OrderServiceImpl(memberRepository(), discountPolicy()); } @Bean p&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pgmjun.tistory.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;하지만 꼭 빈을 등록할 때, @Configuration, @Bean 애노테이션을 선언해야만 할까?&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;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 스캔 (Component Scan) 이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔은 스캔 범위 내에 존재하는 @Component 애노테이션이 붙은 클래스를 스프링 빈으로 등록하기 위한 스캔 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔은 @ComponentScan 애노테이션을 통해 설정이 가능하다.&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;@SpringBootApplication&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-11 21.14.18.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FR2bP/btsGzLAcxjB/FQ0Lm7FksPXCzWECnjAnYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FR2bP/btsGzLAcxjB/FQ0Lm7FksPXCzWECnjAnYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FR2bP/btsGzLAcxjB/FQ0Lm7FksPXCzWECnjAnYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFR2bP%2FbtsGzLAcxjB%2FFQ0Lm7FksPXCzWECnjAnYK%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;559&quot; height=&quot;375&quot; data-filename=&quot;스크린샷 2024-04-11 21.14.18.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;716&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;스프링부트 프로젝트 생성 시, Main 클래스에 설정되는 @SpringBootApplication 애노테이션은&lt;br /&gt;내부에 @ComponentScan 애노테이션을 기본으로 가지고 있다.&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;덕분에 우리는 따로 @ComponentScan 애노테이션을 선언하지 않고도 @Component 애노테이션이 붙어있는 클래스들을 스프링 컨테이너에 빈으로 등록할 수 있던 것이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;컴포넌트 스캔이 작동하는 애노테이션 종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔은 @Component 애노테이션이 붙어있는 클래스에만 해당되는 이야기라고 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 @Component 애노테이션 외에도 몇 가지 애노테이션도 컴포넌트 스캔 범위에 해당된다.&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;그 종류는 바로 @Controller, @RestController, @Service, @Configuration, @Repository 이다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;@Repository 살펴보기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-11 21.18.12.png&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn6yCX/btsGzkJKMsL/V05kdG2YkBXetHn0waJfPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn6yCX/btsGzkJKMsL/V05kdG2YkBXetHn0waJfPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn6yCX/btsGzkJKMsL/V05kdG2YkBXetHn0waJfPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn6yCX%2FbtsGzkJKMsL%2FV05kdG2YkBXetHn0waJfPK%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;508&quot; height=&quot;179&quot; data-filename=&quot;스크린샷 2024-04-11 21.18.12.png&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;236&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;예시로 사진을 가져와본 Repository 애노테이션의 코드를 보면 알 수 있듯이&lt;br /&gt;해당 애노테이션들은 내부에 @Component 애노테이션을 포함하고 있다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;/h3&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;컴포넌트 스캔 basePackage 설정&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@ComponentScan(
        basePackages = &quot;com.example&quot;
)
public class AutoAppConfig {
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔은 위와 같이 basePackage 설정을 통해 컴포넌트 스캔의 기반 패키지를 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;basePackage를 설정하게 되면, 설정된 패키지와 그 하위의 패키지의 클래스들을 모두 탐색하며 컴포넌트 스캔을 수행한다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;@SpringBootApplication의 basePackage 설정&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication(
       scanBasePackages = &quot;&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpringBootApplication 또한 내부에 @ComponentScan 애노테이션을 가지고 있기 때문에,&lt;br /&gt;scanBasePackages 이라는 속성을 통하여 컴포넌트 스캔 범위를 설정할 수 있도록 되어있다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;컴포넌트 스캔의 Default basePackage&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위와 같이 컴포넌트 스캔 범위를 지정해주지 않는다면, @ComponentScan 애노테이션을 선언해준 클래스의 패키지를 basePackage로 설정한다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; 스프링 신 김영한 선생님의 권장 방식&lt;br /&gt;&lt;/b&gt;&lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;basePackageClasses 옵션&lt;/h4&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@ComponentScan(
        basePackageClasses = {
                AutoAppConfig.class
        }
)
public class AutoAppConfig {
}&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;basePackages 외에 basePackageClasses 라는 속성도 존재하는데,&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;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;컴포넌트 스캔 탐색 제외(exclude) / 포함(include) 속성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔은 basePackages와 basePackageClasses 속성 외에도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;특정 부분에 대해 컴포넌트 스캔을 제외(exclude) 시킬 수 있는 excludeFilters와&lt;br /&gt;특정 부분에 대해 컴포넌트 스캔을 포함(include)시킬 수 있는 includeFilters 속성이 존재한다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;includeFilters&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 includeFilters에 대해 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@ComponentScan(
        includeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
        }
)
public class AutoAppConfig {
}&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;includeFilters 속성을 @ComponentScan 애노테이션의 속성으로 추가하게 되면,&lt;br /&gt;해당 속성에 정의한 type, classes를 바탕으로 컴포넌트 스캔에서 포함(include) 시킬 클래스를 정의할 수 있다.&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;위 코드와 같이 includeFilters 옵션을 사용하게 되면,&lt;br /&gt;컴포넌트 스캔 범위에 해당하는 클래스 + @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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;excludeFilters&lt;/h4&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@ComponentScan(
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
        }
)
public class AutoAppConfig {
}&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;excludeFilters 속성을 @ComponentScan 애노테이션의 속성으로 추가하게 되면,&lt;br /&gt;해당 속성에 정의한 type, classes를 바탕으로 컴포넌트 스캔에서 제외(exclude) 시킬 클래스를 정의할 수 있다.&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;위 코드와 같이 excludeFilters 옵션을 사용하게 되면,&lt;br /&gt;컴포넌트 스캔 범위에 해당하는 클래스를 탐색하되, @Configuration 애노테이션이 붙은 클래스를 발견하면 스캔에서 제외하고&lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;FilterType의 종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FilterType이 Enum으로 되어있기에, 어떤 종류가 있는 지 궁금해서 정리해보았다.&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-filename=&quot;스크린샷 2024-04-12 00.27.59.png&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVQ4Mq/btsGwNNr4V4/pOWM5GBUBug15vax7dIeU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVQ4Mq/btsGwNNr4V4/pOWM5GBUBug15vax7dIeU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVQ4Mq/btsGwNNr4V4/pOWM5GBUBug15vax7dIeU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVQ4Mq%2FbtsGwNNr4V4%2FpOWM5GBUBug15vax7dIeU0%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;549&quot; height=&quot;441&quot; data-filename=&quot;스크린샷 2024-04-12 00.27.59.png&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;598&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;FilterType Enum 클래스는 다음과 같은 열거 상수들을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ANNOTATION&lt;/b&gt; : 지정된 애노테이션을 가지고 있는 클래스를 탐색&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ASSIGNABLE_TYPE&lt;/b&gt; : &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot;&gt;지정된 클래스 또는 그 클래스를 상속하는 클래스들을 탐색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ASPECTJ&lt;/b&gt; : &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot;&gt;AspectJ 패턴을 기반으로 빈을 탐색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;REGEX&lt;/b&gt; : &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot;&gt;정규 표현식을 사용하여, 정규 표현식 패턴에 일치하는 이름의 클래스들을 탐색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CUSTOM&lt;/b&gt; : &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot;&gt;사용자 정의 필터 로직을 구현하여 원하는 방식으로 빈을 선택적으로 탐색&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;(&lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;TypeFilter &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;인터페이스를 구현해서 처리)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 자동 빈 등록 vs 자동 빈 등록&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 만약 같은 이름으로 설정한 빈이 존재하면&lt;br /&gt;스프링은 &lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;ConflictingBeanDefinitionException 예외를 발생시킨다.&lt;/span&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;2. 자동 빈 등록 vs 수동 빈 등록&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지금의 스프링은 자동 빈 vs 자동 빈 등록 케이스와 마찬가지로 예외를 발생시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@ComponentScan(
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
        }
)
public class AutoAppConfig {

    @Bean(name = &quot;memoryMemberRepository&quot;)
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 memoryMemberRepository를 @Configuration 클래스 내부에서 Bean으로 등록하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Repository
public class MemoryMemberRepository implements MemberRepository {
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 동일한 MemoryMemberRepository를 @Repository 애노테이션을 통해 컴포넌트 스캔 범위로 잡아 스프링 빈으로 등록한다.&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;때문에 위 Config에 수동으로 등록한 빈의 이름(&quot;memoryMemberRepository&quot;)과 동일함을 인지할 수 있다.&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;br /&gt;아래와 같은 로그를 출력하고 수동 빈 등록이 우선권을 가지게 하였다.&lt;br /&gt;(수동 빈이 자동 빈을 오버라이드 해버렸다.)&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Overriding bean definition for bean 'memoryMemberRepository' with a different&lt;br /&gt;definition: replacing&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;p data-ke-size=&quot;size16&quot;&gt;그 이유는 대부분의 빈 네임 중복은 개발자의 의도가 아닌 휴먼 에러에 의해서 발생했던 것이었는데,&lt;br /&gt;이로 인하여 나도 모르는 사이에 빈 네임 중복으로 인해 스프링 빈이 2개 중 1개만 생성이 되는 등 스프링 빈을 다루는 데에 문제점을 느끼게 되었다고 한다.&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;빈 네이밍 중복 시, 오버라이딩 시키기&lt;/h4&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Consider renaming one of the beans or enabling overriding by setting&lt;br /&gt;spring.main.allow-bean-definition-overriding=true&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&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;이때 예외 메시지를 잘 살펴보면 `&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;spring.main.allow-bean-definition-overriding` 옵션을 `true`로 설정하면 Bean 오버라이딩이 가능해질 것이라고 한다.&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 style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;실제로 해당 옵션을 yaml 또는 properties 파일에 추가해주면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;수동 빈 등록 vs 자동 빈 등록 상황에서 &lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;빈 네이밍이 중복되어도&amp;nbsp;&lt;/span&gt;예외가 발생하지 않고&lt;br /&gt;수동 빈이 자동 빈을 오버라이드 하도록 설정이 가능하다.&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BackEnd/Spring</category>
      <category>@Component</category>
      <category>@ComponentScan</category>
      <category>@ComponentScan 사용법</category>
      <category>@ComponentScan 속성</category>
      <category>ComponentScan</category>
      <category>스프링 @ComponentScan 범위</category>
      <category>스프링 컴포넌트 스캔</category>
      <category>컴포넌트 스캔 범위</category>
      <category>컴포넌트 스캔 속성</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/145</guid>
      <comments>https://pgmjun.tistory.com/145#entry145comment</comments>
      <pubDate>Fri, 12 Apr 2024 00:56:09 +0900</pubDate>
    </item>
    <item>
      <title>@Configuration과 싱글톤 (with. CGLIB)</title>
      <link>https://pgmjun.tistory.com/144</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;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
 public class AppConfig {
 
     @Bean
     public MemberService memberService() {
         return new MemberServiceImpl(memberRepository());
     }
     
     @Bean
     public OrderService orderService() {
         return new OrderServiceImpl(memberRepository(), discountPolicy());
		 }
		 
     @Bean
     public MemberRepository memberRepository() {
         return new MemoryMemberRepository();
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 &lt;b&gt;OrderService&lt;/b&gt;, &lt;b&gt;MemberService&lt;/b&gt;, &lt;b&gt;MemberRepository&lt;/b&gt; 빈(Bean)을 정의하는 설정 파일의 코드이다.&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;&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;MemberRepository&lt;/b&gt; 를 빈으로 등록하기 위해 return new로 구현체를 생성해주는 것은 알겠지만,&lt;br /&gt;memberRepository() 메서드를 orderService(), memberService() 에서도 호출해주고 있으니&lt;br /&gt;총 &lt;b&gt;MemoryMemberRepository&lt;/b&gt;를 3번 생성해주는 것이 아닐까?&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;싱글톤 테스트&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@SpringBootTest
public class AppConfigTest {

    @Test
    @DisplayName(&quot;싱글톤 테스트&quot;)
    void singleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberRepository memberRepository = ac.getBean(&quot;memberRepository&quot;, MemberRepository.class);

        OrderServiceImpl orderService = ac.getBean(&quot;orderService&quot;, OrderServiceImpl.class);
        MemberRepository memberRepository1 = orderService.getMemberRepository();

        MemberServiceImpl memberService = ac.getBean(&quot;memberService&quot;, MemberServiceImpl.class);
        MemberRepository memberRepository2 = memberService.getMemberRepository();

        Assertions.assertThat(memberRepository).isSameAs(memberRepository1);
        Assertions.assertThat(memberRepository).isSameAs(memberRepository2);
    }
}&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;위 코드는 memberRepository가 싱글톤이 맞는 지 검증하는 테스트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 컨테이너에 존재하는 MemberRepository 빈과 OrderService, MemberService에 주입된 MemberRepository 가 같은 지 검사하는 테스트 코드로 구성되었다.&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;h3 data-ke-size=&quot;size23&quot;&gt;테스트 결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-11 01.04.07.png&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N3K1S/btsGwyVTQuw/o0ZeccqHfAczwQ6BHjPBK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N3K1S/btsGwyVTQuw/o0ZeccqHfAczwQ6BHjPBK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N3K1S/btsGwyVTQuw/o0ZeccqHfAczwQ6BHjPBK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN3K1S%2FbtsGwyVTQuw%2Fo0ZeccqHfAczwQ6BHjPBK0%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;530&quot; height=&quot;216&quot; data-filename=&quot;스크린샷 2024-04-11 01.04.07.png&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;216&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;b&gt;성공&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 MemberRepository를 싱글톤으로 관리할 수 있도록 처리해주었다는 얘기인데 과연 어떻게 그럴 수 있는 걸까?&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Configuration&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 바로 @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;/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-filename=&quot;스크린샷 2024-04-11 01.13.27.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOJAAg/btsGurYanZQ/y3nrvO1YKxrTiViEdDhPL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOJAAg/btsGurYanZQ/y3nrvO1YKxrTiViEdDhPL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOJAAg/btsGurYanZQ/y3nrvO1YKxrTiViEdDhPL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOJAAg%2FbtsGurYanZQ%2Fy3nrvO1YKxrTiViEdDhPL0%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;756&quot; height=&quot;400&quot; data-filename=&quot;스크린샷 2024-04-11 01.13.27.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 스프링은 바이트코드를 조작하는 CGLIB 라이브러리를 사용하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Configuration 내에서 @Bean 을 통해 스프링 빈을 등록 시,&lt;br /&gt;바이트코드 조작 라이브러리가 Config 객체의 코드를 조작하여 싱글톤을 보장하도록 코드를 변경시킨 CBLIB 객체를 생성하고&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 AppConfig의 class를 출력시켜보면 아래와 같이 출력되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;AppConfig appConfig = ac.getBean(AppConfig.class);
System.out.println(appConfig.getClass());
        
// 출력 값: class com.example.demo.AppConfig$$SpringCGLIB$$0
// 일반 객체의 경우, 이렇게 출력된다(class com.example.demo.AppConfig)&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;AppConfig가 CGLIB에 의해 컨테이너에 등록된 AppConfig@CGLIB 예상 코드를 보자면 아래와 같지 않을까 싶다.&lt;/b&gt; (실제로는 훨씬 복잡하게 구현되어있을 것이며, 이 코드는 간략한 예상 코드이다.)&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean
public MemberRepository memberRepository() {
		if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) { 
				return 스프링 컨테이너에서 찾아서 반환;
		} else { //스프링 컨테이너에 없으면
				기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
				return 반환
		}
}&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Bean 만 사용한다면?&lt;/h2&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 애노테이션만 사용한다면, 스프링 빈을 싱글톤으로 관리하기 어려울까?&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 class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}&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;이전 AppConfig 코드에서 @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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-11 01.22.39.png&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9In1h/btsGyfgWwBw/o3AFnb7V3A0kuH8wtT0eH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9In1h/btsGyfgWwBw/o3AFnb7V3A0kuH8wtT0eH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9In1h/btsGyfgWwBw/o3AFnb7V3A0kuH8wtT0eH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9In1h%2FbtsGyfgWwBw%2Fo3AFnb7V3A0kuH8wtT0eH1%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;1218&quot; height=&quot;270&quot; data-filename=&quot;스크린샷 2024-04-11 01.22.39.png&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&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;그럼 @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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내부 동작&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선 Configuration 애노테이션이 없으니 CGLIB 라이브러리가 바이트코드를 조작하지 않고 싱글톤을 보장하지 않는다.&lt;/li&gt;
&lt;li&gt;위와 같은 AppConfig 코드라면 memberRepository가 총 3개 생성되고 각각의 스프링 빈으로 등록된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Bean
public MemberRepository memberRepository() {
		System.out.println(&quot;memberRepository 생성&quot;);
    return new MemoryMemberRepository();
}
&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;memberRepository() 메서드에 위와 같이 로그를 추가하는 경우&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-filename=&quot;스크린샷 2024-04-11 01.26.22.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1KjHU/btsGwAzrm1Q/jLkF2JYyrHgR6FHD7dOvxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1KjHU/btsGwAzrm1Q/jLkF2JYyrHgR6FHD7dOvxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1KjHU/btsGwAzrm1Q/jLkF2JYyrHgR6FHD7dOvxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1KjHU%2FbtsGwAzrm1Q%2FjLkF2JYyrHgR6FHD7dOvxk%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;410&quot; height=&quot;128&quot; data-filename=&quot;스크린샷 2024-04-11 01.26.22.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;128&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;총 3번의 memberRepository 생성이 발생하는 것을 알 수 있다.&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-filename=&quot;스크린샷 2024-04-11 01.28.16.png&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doI9HN/btsGu1YRcnq/cc4OOtBOMmg6o6TkD6mBNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doI9HN/btsGu1YRcnq/cc4OOtBOMmg6o6TkD6mBNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doI9HN/btsGu1YRcnq/cc4OOtBOMmg6o6TkD6mBNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoI9HN%2FbtsGu1YRcnq%2Fcc4OOtBOMmg6o6TkD6mBNk%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;374&quot; height=&quot;50&quot; data-filename=&quot;스크린샷 2024-04-11 01.28.16.png&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;50&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;@Configuration 애노테이션이 존재하는 경우에는 단 1번만 호출되는 것을 확인할 수 있었다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 스캔 방식이 아닌 직접 Config 객체 선언하여 IoC 컨테이너를 구성하는 경우에는 @Configuration, @Bean 애노테이션을 통해 스프링 빈들의 싱글톤을 보장할 수 있다.&lt;/li&gt;
&lt;li&gt;스프링에서 @Configuration 내부에 @Bean 애노테이션을 통해 선언한 빈들이 싱글톤을 보장할 수 있는 것은 @Configuration 애노테이션에 의해 동작하는 CGLIB이라는 바이트 코드 조작 라이브러리에 의해서이다.&lt;/li&gt;
&lt;li&gt;@Configuration 애노테이션 없이 @Bean 만 붙이더라도, 스프링 빈 등록은 되지만 싱글톤 보장은 되지 않는다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>BackEnd/Spring</category>
      <category>@Configuration CGLIB</category>
      <category>@Configuration 원리</category>
      <category>CGLib</category>
      <category>스프링 AppConfig</category>
      <category>스프링 CGLIB</category>
      <category>스프링 Configuration</category>
      <category>스프링 싱글톤 보장 원리</category>
      <category>스프링부트</category>
      <category>스프링부트 CGLIB</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/144</guid>
      <comments>https://pgmjun.tistory.com/144#entry144comment</comments>
      <pubDate>Thu, 11 Apr 2024 01:45:41 +0900</pubDate>
    </item>
    <item>
      <title>AWS 예상치 못한 과금 환불기 (with. 과금 방지법)</title>
      <link>https://pgmjun.tistory.com/143</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cS83oe/btsGtMHN3sO/OFaO6TuloexZzz8Nlaa62k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cS83oe/btsGtMHN3sO/OFaO6TuloexZzz8Nlaa62k/img.png&quot; data-alt=&quot;킹갓AWS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cS83oe/btsGtMHN3sO/OFaO6TuloexZzz8Nlaa62k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcS83oe%2FbtsGtMHN3sO%2FOFaO6TuloexZzz8Nlaa62k%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;397&quot; height=&quot;237&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;킹갓AWS&lt;/figcaption&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;이 글은 AWS에서 예상치 못한 과금을 때려맞은 본인의 환불 기록을 담은 글이다.&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;사건 수습 이후, 누구든 실수로 인해 AWS에서 과금폭탄을 맞은 경우&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여행 중, 갑작스런 비용 결제 알림  &lt;/h2&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-filename=&quot;302106783-7aca9883-f8d0-4539-b3bf-4b6bad488373.png&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pm4AT/btsGssv0bVW/HXyK6FTThRDSwPUKXPdsq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pm4AT/btsGssv0bVW/HXyK6FTThRDSwPUKXPdsq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pm4AT/btsGssv0bVW/HXyK6FTThRDSwPUKXPdsq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpm4AT%2FbtsGssv0bVW%2FHXyK6FTThRDSwPUKXPdsq1%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;496&quot; height=&quot;360&quot; data-filename=&quot;302106783-7aca9883-f8d0-4539-b3bf-4b6bad488373.png&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;302106809-162512ca-0f91-4a8b-b564-993470297553.png&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwdg1I/btsGwPoW7fS/PqJp9kxPKwK9kW2gceOacK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwdg1I/btsGwPoW7fS/PqJp9kxPKwK9kW2gceOacK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwdg1I/btsGwPoW7fS/PqJp9kxPKwK9kW2gceOacK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwdg1I%2FbtsGwPoW7fS%2FPqJp9kxPKwK9kW2gceOacK%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;706&quot; height=&quot;119&quot; data-filename=&quot;302106809-162512ca-0f91-4a8b-b564-993470297553.png&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;178&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;즐거운 제주도 가족 여행 마지막 날 새벽 6시 50분,,, 내 폰에 한 통의 결제 알림이 수신되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS로부터 결제 금액 75000원 가량이 빠져나가려다 잔액부족으로 실패했다는 알림이었다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticache 생성 시에, 요금제에 손대지 않고 기초 설정대로 생성했는데 프리티어에서 무료 제공되는 t2.micro가 아니라 r6g.Large 로 설정되어 과금이 되었던 것.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 자동으로 t2.micro로 설정이 되어 있었기 때문에 이번에는 Elasticache에서 Redis 생성 시 요금제를 자세히 확인하지 않았고,, 이건 명백히 나의 잘못이었다&amp;hellip;ㅠ&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS 지원 센터(Support Center)에 문의 넣기  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 잘못이었기에 일단 Elasticache 인스턴스를 삭제하고 발생된 비용은 결제를 하려던 찰나에 AWS에 문의하여 결제를 취소했다고 말하던 블로그가 생각나 시도라도 해보기로 하였다. (1월, 2월 합치면 10만원 정도의 돈이었고 학생인 나의 입장에선 너무나도 큰 금액이었다..ㅠ)&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;인터넷을 찾아보니 이렇게 예상치 못한 결제가 발생할 경우 AWS 지원 센터에 문의를 넣으면 AWS 측의 판단 하에 결제를 취소 시켜줄 수 있다고 했다.&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;h3 data-ke-size=&quot;size23&quot;&gt;문의 방법&lt;/h3&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-filename=&quot;스크린샷 2024-02-06 오후 3.04.21.png&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XX2dK/btsGtNUdPyT/A03Xtkyg2yhoLQMPJnSJ41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XX2dK/btsGtNUdPyT/A03Xtkyg2yhoLQMPJnSJ41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XX2dK/btsGtNUdPyT/A03Xtkyg2yhoLQMPJnSJ41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXX2dK%2FbtsGtNUdPyT%2FA03Xtkyg2yhoLQMPJnSJ41%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;401&quot; height=&quot;245&quot; data-filename=&quot;스크린샷 2024-02-06 오후 3.04.21.png&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;245&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;&amp;nbsp;&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-filename=&quot;스크린샷 2024-02-06 오후 3.04.40.png&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIXSl2/btsGwfhn80O/bVAyxtdcXpQNvVmutvTBp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIXSl2/btsGwfhn80O/bVAyxtdcXpQNvVmutvTBp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIXSl2/btsGwfhn80O/bVAyxtdcXpQNvVmutvTBp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIXSl2%2FbtsGwfhn80O%2FbVAyxtdcXpQNvVmutvTBp1%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;567&quot; height=&quot;211&quot; data-filename=&quot;스크린샷 2024-02-06 오후 3.04.40.png&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;369&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;&amp;nbsp;&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-filename=&quot;스크린샷 2024-02-06 오후 3.05.54.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Agkp5/btsGuOkxr6J/xnnhmm9JAsbW63ME0kvPbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Agkp5/btsGuOkxr6J/xnnhmm9JAsbW63ME0kvPbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Agkp5/btsGuOkxr6J/xnnhmm9JAsbW63ME0kvPbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAgkp5%2FbtsGuOkxr6J%2Fxnnhmm9JAsbW63ME0kvPbk%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;616&quot; height=&quot;608&quot; data-filename=&quot;스크린샷 2024-02-06 오후 3.05.54.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;928&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래에 있는 부가 정보들을 입력하면 AWS에 문의를 넣을 수 있다. (물론 제목, 내용 외에는 부가 정보들이기 때문에 적지 않아도 된다. 하지만 신속한 처리를 하고 싶다면 꼭 넣자)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Name of the Service&lt;/b&gt; : 비용 발생 서비스명&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Invoice ID&lt;/b&gt; : 결제 청구서의 고유번호&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Total Amount&lt;/b&gt; : 총 발생 금액이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Time Frame&lt;/b&gt; : 문제 발생 기간을 상세히 적어주면 된다.&lt;/li&gt;
&lt;/ul&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;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-06 오후 4.35.18.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NA4DO/btsGtjZO2uY/MS8UEKcCpk9RNnQ4H2t3Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NA4DO/btsGtjZO2uY/MS8UEKcCpk9RNnQ4H2t3Y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NA4DO/btsGtjZO2uY/MS8UEKcCpk9RNnQ4H2t3Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNA4DO%2FbtsGtjZO2uY%2FMS8UEKcCpk9RNnQ4H2t3Y0%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;1634&quot; height=&quot;330&quot; data-filename=&quot;스크린샷 2024-02-06 오후 4.35.18.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-06 오후 4.33.56.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQwwzc/btsGwEBdWsM/TRkXEQEuyD7giMbqDptnV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQwwzc/btsGwEBdWsM/TRkXEQEuyD7giMbqDptnV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQwwzc/btsGwEBdWsM/TRkXEQEuyD7giMbqDptnV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQwwzc%2FbtsGwEBdWsM%2FTRkXEQEuyD7giMbqDptnV0%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;1634&quot; height=&quot;391&quot; data-filename=&quot;스크린샷 2024-02-06 오후 4.33.56.png&quot; data-origin-width=&quot;1634&quot; data-origin-height=&quot;974&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;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;&quot;I'm poor student. plz help me&quot;&lt;/b&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;한글도 지원을 해준다고는 하는데 나는 혹시 몰라 이렇게 영어 버전과 한글 버전 2개로 AWS에 문의를 넣었고,&lt;br /&gt;AWS에서는 두 내용 모두 같은 내용이라 하나만 답변하겠다고 연락이 왔다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS의 답변  &lt;/h2&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-filename=&quot;스크린샷 2024-02-06 오후 2.52.38.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;915&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7eM0d/btsGuMG2YnH/qkyLoZ5fvhzq4u1GEVEmAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7eM0d/btsGuMG2YnH/qkyLoZ5fvhzq4u1GEVEmAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7eM0d/btsGuMG2YnH/qkyLoZ5fvhzq4u1GEVEmAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7eM0d%2FbtsGuMG2YnH%2FqkyLoZ5fvhzq4u1GEVEmAK%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;571&quot; height=&quot;782&quot; data-filename=&quot;스크린샷 2024-02-06 오후 2.52.38.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;915&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;AWS는 과금이 발생되는 부분을 모두 삭제하고, 비용 조정 접수 의사를 밝힌 회신을 해달라고 했다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-06 오후 2.52.45.png&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEPkuB/btsGvvY4H8V/W8WvSgq7kwr5acYq8ZT3E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEPkuB/btsGvvY4H8V/W8WvSgq7kwr5acYq8ZT3E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEPkuB/btsGvvY4H8V/W8WvSgq7kwr5acYq8ZT3E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEPkuB%2FbtsGvvY4H8V%2FW8WvSgq7kwr5acYq8ZT3E0%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;677&quot; height=&quot;198&quot; data-filename=&quot;스크린샷 2024-02-06 오후 2.52.45.png&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;198&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RDS 인스턴스 삭제&lt;/li&gt;
&lt;li&gt;EC2 인스턴스 종료&lt;/li&gt;
&lt;li&gt;EBS 볼륨 삭제&lt;/li&gt;
&lt;li&gt;키 페어 삭제&lt;/li&gt;
&lt;/ul&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;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-filename=&quot;스크린샷 2024-04-09 15.37.44.png&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1074&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NCQuU/btsGwOjhvR0/Klu0zD2n9u0hX0QWQgScyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NCQuU/btsGwOjhvR0/Klu0zD2n9u0hX0QWQgScyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NCQuU/btsGwOjhvR0/Klu0zD2n9u0hX0QWQgScyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNCQuU%2FbtsGwOjhvR0%2FKlu0zD2n9u0hX0QWQgScyk%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;715&quot; height=&quot;478&quot; data-filename=&quot;스크린샷 2024-04-09 15.37.44.png&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1074&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;요청받은 서비스를 모두 삭제하고 나니, AWS에서 본인의 계정을 모니터링하기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 더는 비용이 발생하지 않도록 조치했으니 조용히 AWS에서 승인이 떨어지기를 기다렸다.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과 ⭐️&lt;/h2&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-filename=&quot;스크린샷 2024-04-09 15.41.42.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWwu4/btsGvrQbkFg/pWK1j21skWWAEvkXN85WZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWwu4/btsGvrQbkFg/pWK1j21skWWAEvkXN85WZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWwu4/btsGvrQbkFg/pWK1j21skWWAEvkXN85WZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWwu4%2FbtsGvrQbkFg%2FpWK1j21skWWAEvkXN85WZ1%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;315&quot; data-filename=&quot;스크린샷 2024-04-09 15.41.42.png&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;664&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;1월 금액은 면제되었고, 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;애꿎은 10만원이 사라질 뻔했지만, 자비로우신 AWS 선생님들께서 선처해주셨다&amp;hellip;.. (정말 감사합니다..)&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달 간의 여정이었다....&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1달 정도의 기간에 거쳐 환불을 받게 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글에서는 생략되었지만, AWS에서 삭제해야 조치가 가능하다는 서비스를 본인이 잘못 알아들어 오래 걸렸던 부분이 큰데&lt;br /&gt;AWS 서비스 센터 자체는 답변이 빠른 편이니 요구사항을 빠르게 처리한다면 더 빠르게 환불받을 수 있을 것이다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;과금 발생 방지법  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 요금제 확인하고 서비스 이용&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;&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://aws.amazon.com/ko/free/?gclid=Cj0KCQjwq86wBhDiARIsAJhuphnFWfqpgLAXJqQDCt4igXyy2qqiZF_UB4X9vcglVB0TcbXAlgP08qwaAulgEALw_wcB&amp;amp;trk=bdc27120-6d06-4bae-9be1-22d07b8ec0a7&amp;amp;sc_channel=ps&amp;amp;ef_id=Cj0KCQjwq86wBhDiARIsAJhuphnFWfqpgLAXJqQDCt4igXyy2qqiZF_UB4X9vcglVB0TcbXAlgP08qwaAulgEALw_wcB:G:s&amp;amp;s_kwcid=AL!4422!3!444218216096!e!!g!!aws%20free%20tier!10287751098!99328587821&amp;amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Types=*all&amp;amp;awsf.Free%20Tier%20Categories=*all&quot;&gt;프리티어 지원 내용 확인하러 가기&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;h3 data-ke-size=&quot;size23&quot;&gt;2. AWS Budget(예산) 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-managing-costs.html&quot;&gt;Managing your costs with AWS Budgets - AWS Cost Management&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;예산을 설정하면, 해당 예산이 초과되는 과금이 발생할 경우 설정해둔 메일을 통해 알림을 보내준다.&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;&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>BackEnd/Infra</category>
      <category>AWS</category>
      <category>aws 결제 취소</category>
      <category>AWS 과금</category>
      <category>aws 과금 문의</category>
      <category>aws 과금 발생</category>
      <category>aws 과금 발생 방지</category>
      <category>aws 과금 발생 해결법</category>
      <category>aws 서비스센터</category>
      <category>aws 예상치못한 결제</category>
      <category>AWS 환불</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/143</guid>
      <comments>https://pgmjun.tistory.com/143#entry143comment</comments>
      <pubDate>Tue, 9 Apr 2024 16:05:26 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Java 반복 작업 수행 시, For-Loop와 Stream 사이 성능 비교</title>
      <link>https://pgmjun.tistory.com/141</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xewcq/btsGq2oYdN0/Wk3NjIt1epYvVwkqzfKd4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xewcq/btsGq2oYdN0/Wk3NjIt1epYvVwkqzfKd4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xewcq/btsGq2oYdN0/Wk3NjIt1epYvVwkqzfKd4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxewcq%2FbtsGq2oYdN0%2FWk3NjIt1epYvVwkqzfKd4K%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;780&quot; height=&quot;390&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;미션 도중 생긴 고민  &lt;/h2&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;public Cars(List&amp;lt;String&amp;gt; carNames) {
        ArrayList&amp;lt;Car&amp;gt; cars = new ArrayList&amp;lt;&amp;gt;();
        for (String carName : carNames) {
            cars.add(new Car(carName));
        }

        this.cars = cars;
    }&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;Cars 객체를 생성할 때, &lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt;형태의 &lt;code&gt;carNames&lt;/code&gt;를 입력받은 뒤,&lt;br /&gt;&lt;code&gt;For-Loop&lt;/code&gt; 문을 통해 cars 필드를 초기화 해주었다.&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;code&gt;For-Loop&lt;/code&gt;문을 &lt;code&gt;생성자 로직&lt;/code&gt;에 적용하니 개인적으로 생성자 로직이 지저분하다는 생각이 들었다.&lt;br /&gt;때문에 &lt;code&gt;Stream&lt;/code&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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;그리고 이때 머릿속에 한가지 고민이 생겼다  &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미션의 요구사항에 자동차의 수는 제한이 없는데, &lt;br /&gt;그럼 Integer.MAX_VALUE 대의 자동차 이름을 입력받았을 때 For-Loop와 Stream 중&lt;br /&gt;무엇이 더 성능 면에서 뛰어날까?&lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고민 해소을 위한 웹서칭 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소 자세한 이유는 몰랐지만 주워들은 것이 있어 &lt;code&gt;Stream&lt;/code&gt;의 성능이 더 좋지 않을 것이라고 생각했는데,&lt;br /&gt;정말 &lt;code&gt;Stream&lt;/code&gt;을 사용하는 것이 &lt;code&gt;For-Loop&lt;/code&gt;를 사용하는 것보다 성능이 안 좋다고 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  가장 큰 이유는 For-Loop는 만들어진지 40년 이상 지났기 때문에 최적화가 굉장히 잘 되어 있었고,&lt;br /&gt;Stream은 2015년 쯤에 Java8과 함께 생겨났기 때문에 아직 최적화 작업이 덜 되어있다는 것이 원인이라고 하는데,,&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;여기서 또 하나의 고민이 생겼다  &lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  그럼 무조건 For-Loop만을 사용해야만 하는 걸까?&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;code&gt;Stream&lt;/code&gt;과 &lt;code&gt;For-Loop&lt;/code&gt;의 차이에 대해 공부를 시작하게 하게 되었고,&lt;br /&gt;그 과정에서 &lt;a href=&quot;https://dogpaw.tistory.com/6&quot;&gt;좋은 글&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stream과 For-Loop의 성능 비교  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Primitive Type 성능 비교  &lt;code&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Primitive Type&lt;/code&gt;의 경우 &lt;code&gt;JVM&lt;/code&gt; 메모리의 &lt;code&gt;Stack&lt;/code&gt;에 값을 저장하고 있기 때문에&lt;br /&gt;접근 속도가 빨라 &lt;code&gt;For-Loop&lt;/code&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;h4 data-ke-size=&quot;size20&quot;&gt;스택(Stack)이란?  &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JVM&lt;/code&gt;의 &lt;code&gt;스택(Stack)&lt;/code&gt;은 실행중인 스레드의 &lt;b&gt;함수 호출&lt;/b&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;&lt;code&gt;Primitive Type&lt;/code&gt;은 변수 선언 시 &lt;code&gt;JVM&lt;/code&gt;의 &lt;code&gt;Stack&lt;/code&gt; 에 값을 저장하고, 변수 호출 시에 &lt;code&gt;Stack&lt;/code&gt;에서 값을 바로 불러오는 방식을 채택하기 때문에 접근 속도가 굉장히 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어, &lt;code&gt;Primitive Type&lt;/code&gt;인 &lt;code&gt;int&lt;/code&gt; 타입의 변수에 &lt;b&gt;42&lt;/b&gt;라는 값을 저장하는 경우 &lt;b&gt;42가 스택에 저장&lt;/b&gt;되며,&lt;br /&gt;&lt;code&gt;Stack&lt;/code&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;code&gt;Primitive Type&lt;/code&gt;의 경우 &lt;code&gt;Stream&lt;/code&gt;을 사용했을 때와, &lt;code&gt;For-Loop&lt;/code&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Test 로직  &lt;/h4&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class StreamAndForLoop {
    static final int MAX_INT = 5000000;
    static int[] testData = new int[MAX_INT];

    public static void main(String[] args) {
        init();

        calcForLoop();
        calcStream();
    }

    private static void init() {
        for (int i = 0; i &amp;lt; MAX_INT; i++) {
            testData[i] = i;
        }
    }

    private static void calcStream() {
        int standard = 0;
        long before = System.currentTimeMillis();
        Arrays.stream(testData)
                .filter(t -&amp;gt; t &amp;gt; standard)
                .count();
        System.out.println(&quot;Stream : &quot; + (System.currentTimeMillis() - before));
    }

    private static void calcForLoop() {
        int standard = 0;
        int count = 0;

        long before = System.currentTimeMillis();
        for (int data : testData) {
            if(data &amp;gt; standard) {
                count++;
            }
        }
        System.out.println(&quot;For-Loop : &quot; + (System.currentTimeMillis() - before));
    }
}&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;code&gt;standard&lt;/code&gt;라는 변수에 저장된 값과, &lt;code&gt;testData&lt;/code&gt;에 저장된 값들의 크기를 비교하여&lt;br /&gt;더 큰 값이 존재하면 &lt;code&gt;count&lt;/code&gt;하는 로직을 &lt;code&gt;Stream&lt;/code&gt;과 &lt;code&gt;For-Loop&lt;/code&gt; 방식으로 각각 &lt;code&gt;5000000번&lt;/code&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;h4 data-ke-size=&quot;size20&quot;&gt;로직 수행 결과  &lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/pgmjun/post/113c1bf8-cd20-4197-b65c-67562415e495/image.png&quot; alt=&quot;&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;code&gt;Primitive Type&lt;/code&gt;은 접근 속도가 빠르기 때문에 일반적인 경우인&lt;br /&gt;&lt;b&gt;&amp;ldquo;&lt;/b&gt;&lt;code&gt;For-Loop&lt;/code&gt; &lt;b&gt;방식이 더 빠르다&amp;rdquo;&lt;/b&gt; 라는 말에 들어맞는 결과가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로,&lt;br /&gt;실험한 5000000번의 경우에는 3배 가까이 차이가 나는 것을 확인할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Wrapped Type 성능 비교  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;code&gt;Wrapped Type&lt;/code&gt;은 어떨까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Wrapped Type&lt;/code&gt;의 경우 &lt;code&gt;Primitive Type&lt;/code&gt;과 달리 값을 저장할 때, &lt;code&gt;Heap 메모리&lt;/code&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;h4 data-ke-size=&quot;size20&quot;&gt;힙 메모리(Heap Memory)란?  &lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Wrapped Type&lt;/code&gt; 변수가 선언되면 &lt;code&gt;Heap 메모리&lt;/code&gt;에 객체가 할당되고, &lt;code&gt;Stack&lt;/code&gt;에는 객체를 담고 있는 &lt;code&gt;Heap 메모리 주소&lt;/code&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;code&gt;Stack&lt;/code&gt;에 저장된 &lt;code&gt;Heap 메모리 주소&lt;/code&gt;를 참조하여 &lt;code&gt;Heap메모리&lt;/code&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;code&gt;Stack&lt;/code&gt;에 값을 저장하고 바로 참조할 수 있는 &lt;code&gt;Primitive Type&lt;/code&gt;의 접근 방식보다 느려보이는데,&lt;br /&gt;과연 위에서 사용했던 로직과 같은 로직을 &lt;code&gt;Wrapped Type&lt;/code&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;h4 data-ke-size=&quot;size20&quot;&gt;Test 로직  &lt;/h4&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class StreamAndForLoop {
    static final int MAX_INT = 5000000;
    static List&amp;lt;Integer&amp;gt; testData = new ArrayList&amp;lt;&amp;gt;();

    public static void main(String[] args) {
        init();

        calcForLoop();
        calcStream();
    }

    private static void calcStream() {
        int standard = 0;
        long before = System.currentTimeMillis();
        testData.stream()
                .filter(t -&amp;gt; t &amp;gt; standard)
                .count();
        System.out.println(&quot;Stream : &quot; + (System.currentTimeMillis() - before));
    }

    private static void calcForLoop() {
        int standard = 0;
        int count = 0;

        long before = System.currentTimeMillis();
        for (Integer data : testData) {
            if(data &amp;gt; standard) {
                count++;
            }
        }
        System.out.println(&quot;For-Loop : &quot; + (System.currentTimeMillis() - before));
    }

    private static void init() {
        for (int i = 0; i &amp;lt; MAX_INT; i++) {
            testData.add(i);
        }
    }
}&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;code&gt;standard&lt;/code&gt;라는 변수에 저장된 값과 &lt;code&gt;testData&lt;/code&gt;에 저장된 값들의 크기를 비교하여&lt;br /&gt;더 큰 값이 존재하면 &lt;code&gt;count&lt;/code&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;code&gt;Primitive Type&lt;/code&gt;을 사용하지 않고 &lt;code&gt;Wrapped Type&lt;/code&gt;인 &lt;code&gt;Integer&lt;/code&gt;를 사용하도록 변경하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과연 &lt;code&gt;Wrapped Type&lt;/code&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;h4 data-ke-size=&quot;size20&quot;&gt;로직 수행 결과  &lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/pgmjun/post/1eff1dae-a23d-462e-8e07-857922b5801e/image.png&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://velog.velcdn.com/images/pgmjun/post/a1c470be-8bd2-4bce-9b6b-47f8aa99bdcf/image.png&quot; alt=&quot;&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;code&gt;3배&lt;/code&gt; 가까이 차이가 나던 실행 결과가 이제는 거의 &lt;code&gt;동일한 수준&lt;/code&gt;으로 측정되거나 &lt;code&gt;역전&lt;/code&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;code&gt;Stack&lt;/code&gt;에 저장된 &lt;code&gt;데이터(객체)의 주소&lt;/code&gt;를 통해 &lt;code&gt;Heap 메모리&lt;/code&gt;에 접근하여 참조하는 비용이&lt;br /&gt;&lt;code&gt;Stack&lt;/code&gt;에 값을 저장하고 호출 시에 바로 값에 접근하는 비용에 비해 굉장히 크기 때문에,&lt;br /&gt;&lt;code&gt;Stream&lt;/code&gt;과 &lt;code&gt;For-Loop&lt;/code&gt;의 속도 격차를 완화시켜버린 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이를 통해 Wrapped Type을 사용하는 For-Loop 경우,&lt;br /&gt;Stream을 사용하는 경우와 비용이 크게 차이나지 않으므로,&lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;/h3&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;/h4&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;public Cars(List&amp;lt;String&amp;gt; carNames) {
    ArrayList&amp;lt;Car&amp;gt; cars = new ArrayList&amp;lt;&amp;gt;();
    for (String carName : carNames) {
        cars.add(new Car(carName));
    }

    this.cars = cars;
}&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;code&gt;String&lt;/code&gt;이라는 &lt;code&gt;Wrapped Type&lt;/code&gt;을 사용하고 있었기에,&lt;br /&gt;관리하던 생성자 로직에 &lt;code&gt;Stream&lt;/code&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;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public Cars(List&amp;lt;String&amp;gt; carNames) {
    this.cars = carNames.stream()
              .map(Car::new)
              .toList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ArrayList&amp;lt;Car&amp;gt;&lt;/code&gt; 객체 선언 후, 반복문을 돌며 객체에 값을 저장하지 않아도 되기 때문에 &lt;code&gt;ArrayList&amp;lt;Car&amp;gt; cars&lt;/code&gt; 변수 선언부 삭제 후, 필드에 바로 초기화하도록 수정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Stream&lt;/code&gt;을 사용하여 짧고 간결한 코드 작성으로 &lt;b&gt;가독성 향상&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;굉장히 많은 반복 작업을 거친다고 해도 &lt;code&gt;For-Loop&lt;/code&gt;문과 비교하여 &lt;code&gt;더 좋거나 비슷한 성능&lt;/code&gt;을 발휘함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;</description>
      <category>Language/Java</category>
      <category>for문 stream 성능 비교</category>
      <category>Java</category>
      <category>java 성능비교</category>
      <category>Stream</category>
      <category>stream VS For-loop</category>
      <category>자바</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/141</guid>
      <comments>https://pgmjun.tistory.com/141#entry141comment</comments>
      <pubDate>Sat, 6 Apr 2024 14:43:29 +0900</pubDate>
    </item>
    <item>
      <title>테스트를 위한 객체, 테스트 더블(Test Double)</title>
      <link>https://pgmjun.tistory.com/140</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVZ2dB/btsGoP3MAgX/FdLcGkDAmUZkPzgWdFHiak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVZ2dB/btsGoP3MAgX/FdLcGkDAmUZkPzgWdFHiak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVZ2dB/btsGoP3MAgX/FdLcGkDAmUZkPzgWdFHiak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVZ2dB%2FbtsGoP3MAgX%2FFdLcGkDAmUZkPzgWdFHiak%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;712&quot; height=&quot;356&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Test Double이란?&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Test Double 이란 명칭의 유래  &lt;/h3&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 '테스트 주도개발 시작하기' 라는 책을 읽은 적이 있는데, 해당 책 &lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;저자의 말을 빌리자면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;테스트 더블은 외부 요인에 의존하는 객체에 대한 테스트를 작성할 때 &lt;/span&gt;&quot;실패하는 테스트는 항상 실패하고, 성공하는 테스트는 항상 성공한다&quot; 라는 테스트의 일관성을 지키기 위해 사용한다고 했다.&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, 외부 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;h4 data-ke-size=&quot;size20&quot;&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;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;API 서버 개발에 사용하는 스프링부트 애플리케이션이 아닌 모든 외부 의존성들을 외부 요인이라고는 생각하지는 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;예를 들어 H2 DB를 In-Memory &amp;amp; Embedded 모드로 사용한다면, 이는 스프링부트 애플리케이션 내부에서 작동하며 네트워크가 없이도 애플리케이션 내부에서 정상적으로 작동할 수 없으니 외부 의존성인 DB라고 해도 외부 요인으로 생각하지 않는다.&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 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;외부 API, 데이터베이스 서버, 그리고 다른 포트에서 동작하는 다른 애플리케이션 등&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;애플리케이션의 동작에 영향을 미칠 수 있는 외부 의존성을 외부 요인이라고 생각했고&lt;br /&gt;이에 대해 타당한 의견인 것 같다는 리뷰도 받아 합리적인 생각에 가깝다고 판단할 수 있었다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;0~10 사이의 난수&lt;/b&gt;를 발생시키는 generate() 메서드를 가진 &lt;b&gt;RandomNumberGenerator&lt;/b&gt;가 있고 외부에서는 해당 난수 값이 &lt;b&gt;0~4&lt;/b&gt;면 &lt;b&gt;false&lt;/b&gt;, &lt;b&gt;5~10&lt;/b&gt;이면 &lt;b&gt;true&lt;/b&gt;를 return하는 로직이 존재한다고 가정하자.&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;NumberGeneratorStub&lt;/b&gt;을 만들고 generate() 메서드의 return값을 &lt;b&gt;0~10&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;물론 해당 결과는 우리가 조작한 가짜이지만, 이렇게 통제가 100% 가능한 상황이 아닌 때에만 적절히 활용한다면 테스트를 통해 기능에 대해 쉽게 파악할 수 있고, 내부적으로 어떻게 동작하는 지 이해하는 것에 큰 도움을 줄 수 있는 훌륭한 테스트를 작성에 큰 도움을 줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt; &amp;nbsp;Test Double의 종류&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/&quot;&gt;https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Dummy&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더미는 &lt;b&gt;어떠한 동작도 필요하지 않지만&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;예제를 통해 정확한 사용 상황을 살펴보자&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;Accelerator.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface Accelerator {
    int push();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동차를 앞으로 가게하기 위한 자동차의 힘(int)을 return하는 push()라는 행위에 책임을 가진 &lt;b&gt;Accelerator&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RandomMoveAccelerator.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class RandomMoveAccelerator implements Accelerator {
    public static final int MIN_ACCEL_POWER = 0;
    public static final int MAX_ACCEL_POWER = 9;
    public static final int MIN_MOVABLE_POWER = 4;

    public int push() {
        return RandomNumberUtils.generate(MIN_ACCEL_POWER, MAX_ACCEL_POWER);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션의 프로덕션 코드는 push() 메서드 실행 시에, 0~9 사이의 난수를 생성하여 return해야한다.&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;Car.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Car {
    private final CarName name;
    private int position;
    private final Accelerator accelerator;

    public Car(String name, Accelerator accelerator) {
        this.name = new CarName(name);
        this.position = 0;
        this.accelerator = accelerator;
    }

    public void pushAccelerator() {
        moveForward(accelerator.push());
    }

    private void moveForward(int power) {
        if (power &amp;gt;= RandomMoveAccelerator.MIN_MOVABLE_POWER) {
            position++;
        }
    }
&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;Car 클래스는 이러한 &lt;b&gt;RandomMoveAccelerator&lt;/b&gt;를 생성자 주입받아, 애플리케이션 내부에서 랜덤값을 활용하여 position을 증가시킨다.&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;하지만 테스트에서 이러한 Car 객체를 생성할 때, &lt;b&gt;RandomMoveAccelerator&lt;/b&gt;는 사용하지 않지만, Car 객체를 생성하기 위해 Accelerator는 주입해야주어야 하는 경우에는 어떻게 할까?&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;BridgeGeneratorDummy.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class AcceleratorDummy implements Accelerator {
    @Override
    public int push() {
        return 0;
    }
}
&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;AcceleratorDummy&lt;/b&gt; 클래스를 통해 &lt;b&gt;&amp;rdquo;어떠한 동작도 하지 않고 인스턴스 자체만을 제공한다&amp;rdquo;&lt;/b&gt; 라는 Dummy의 역할에 대해 직관적으로 이해할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;물론 return type이 int형이기 때문에 0을 return하긴 하지만, 어떠한 로직처리도 하지 않으며 그저 0을 리턴한다.&lt;/li&gt;
&lt;li&gt;Dummy로 사용될 것이기 때문에 push 라는 메서드가 사용될 일이 없는 경우에만 사용한다.&lt;/li&gt;
&lt;/ul&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;/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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Fake&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이크 객체는 &lt;b&gt;복잡한 로직&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;예제를 통해 자세히 살펴보자&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;/h3&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;MemberRepository.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface MemberRepository {
    Member save(Member member);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Member 객체를 입력받아서 저장소에 저장한다는 행위의 책임을 가진 MemberRepository 인터페이스의 save() 메서드가 존재한다고 가정하자.&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;MemberRepositoryImpl.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class MemberRepositoryImpl {
    Member save(Member member) {
				// DB 저장 로직..
		}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로덕션 코드에선 MemberRepositoryImpl이라는 구현체가 Member 객체를 DB에 저장하는 로직을 수행할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 저장되는 Member 엔티티의 PK는 1번부터 자동으로 증가하는 Auto Increment 설정이 되어있다.&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;DB는 애플리케이션 서버의 외부로 분리되어 있다. 때문에 네트워크에 문제가 발생하면, DB에 접근해서 데이터를 추가하는 로직을 테스트하는 과정에서 에러가 발생한다. 네트워크에 따라 테스트가 실패할 수도 있게 된다는 것이다.&lt;/p&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;b&gt;Fake&lt;/b&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;b&gt;FakeMemberRepository.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class FakeMemberRepository implements MemberRepository {
    private static final Map&amp;lt;Long, Member&amp;gt; members = new ConcurrentHashMap&amp;lt;&amp;gt;();
    private static Long idSequence = 1L;
    
    @Override
    public Member save(Member member) {
        members.put(idSequence++, member);
        return member;
    }
}
&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;FakeMemberRepository는 실제 DB가 아닌 Map에 데이터를 저장함으로써 데이터가 저장소에 저장되는 것과, idSequence를 사용함으로써 Member의 PK가 Auto Increment 되는 것을 정확히 구현한 Fake 객체이다.&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에 수행되는 것과 동일한 결과를 전달하며, 외부 요인에 영향을 받지 않고 항상 성공하는 테스트를 작성할 수 있도록 돕는 것이 바로 Fake 객체의 역할이다.&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;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;nbsp;Stub&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;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;예제를 통해 스텁을 이해해보자&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;/h3&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;Accelerator.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface Accelerator {
    int push();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동차를 앞으로 가게하기 위한 자동차의 힘(int)을 return하는 push()라는 행위에 책임을 가진 &lt;b&gt;Accelerator&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;RandomMoveAccelerator.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class RandomMoveAccelerator implements Accelerator {
    public static final int MIN_ACCEL_POWER = 0;
    public static final int MAX_ACCEL_POWER = 9;
    public static final int MIN_MOVABLE_POWER = 4;

    public int push() {
        return RandomNumberUtils.generate(MIN_ACCEL_POWER, MAX_ACCEL_POWER);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션의 프로덕션 코드는 push() 메서드 실행 시에, 0~9 사이의 난수를 생성하여 return해야한다.&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;Car.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Car {
    private final CarName name;
    private int position;
    private final Accelerator accelerator;

    public Car(String name, Accelerator accelerator) {
        this.name = new CarName(name);
        this.position = 0;
        this.accelerator = accelerator;
    }
		
		public void pushAccelerator() {
        moveForward(accelerator.push());
    }

    private void moveForward(int power) {
        if (power &amp;gt;= RandomMoveAccelerator.MIN_MOVABLE_POWER) {
            position++;
        }
    }
&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;Car 클래스는 &lt;b&gt;Accelerator&lt;/b&gt; 인터페이스를 통해 이러한 &lt;b&gt;RandomMoveAccelerator&lt;/b&gt;를 생성자 주입받고, moveForward(int power) 메서드를 통해 내부에서 랜덤값을 활용하여 position을 증가시킨다.&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;position 증가로직의 분기처리는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;난수가 0~4이면 position을 그대로&lt;/li&gt;
&lt;li&gt;5~10이면 position을 1 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 moveForward(int power)는 난수에 의존하기 때문에 moveForward의 position 증가 테스트를 작성할 때, 우리가 원하는 결과를 만들기 어려워보인다.&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;테스트를 하겠다고 private인 moveForward의 접근제어자를 public으로 변경할 수도 없는 노릇이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 Stub 객체를 사용하여, 우리가 원하는 값을 return하도록 구현하면 moveForward에 대해 원하는 결과를 만들어낼 수 있다.&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;StubAccelerator.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;public class StubAccelerator implements Accelerator {
    private int power;
    
    public StubAccelerator() {
        this.power = 0; // NPE 방지를 위해 기본값 주입
    }
    
    public void setPower(final int power) {
        this.power = power;
    }

    public int push() {
        return power;
    }
}
&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;Accelerator&lt;/b&gt;에 대한 스텁을 생성하면 push() 메서드가 랜덤 값이 아닌 우리가 설정한 값을 return하도록 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 위 코드와 같이 원하는 설정 값을 필드로 만들어두고 &lt;b&gt;setter&lt;/b&gt;를 통해 값을 설정하도록 구현한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성자를 통해 power의 값을 지정하는 방법도 있겠지만, &lt;br /&gt;프로덕션 코드의 Accelerator 구현체는 power를 인자로 입력받는 생성자가 존재하지 않는다. &lt;br /&gt;때문에 테스트를 위해 새로운 인스턴스 생성 규칙(생성자)을 만들어주는 것은 테스트 코드를 통해 프로덕션 코드의 동작 로직을 파악하는 것에 혼란을 야기할 수 있다. &lt;br /&gt;때문에 Stub 사용에 있어서는 생성자보단 &lt;b&gt;Setter&lt;/b&gt; 사용을 지향하자!&lt;/li&gt;
&lt;/ul&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;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Spy&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;&amp;nbsp;&lt;/p&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;b&gt;실행 속도&lt;/b&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;&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;/h3&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;SpyRandomMoveAccelerator.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class SpyRandomMoveAccelerator implements Accelerator {
    public static final int MIN_ACCEL_POWER = 0;
    public static final int MAX_ACCEL_POWER = 9;
    public static final int MIN_MOVABLE_POWER = 4;

    private int calledCount = 0;

    public int push() {
        calledCount++;
        return RandomNumberUtils.generate(MIN_ACCEL_POWER, MAX_ACCEL_POWER);
    }
    
    public int getCalledCount() {
        return calledCount;
    }
}
&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;RandomMoveAccelerator&lt;/b&gt;의 Spy 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 소개한 &lt;b&gt;RandomMoveAccelerator&lt;/b&gt; 클래스와 똑같이 작동하지만, 내부적으로 &lt;b&gt;calledCount&lt;/b&gt; 라는 정수 자료형이 push() 메서드의 호출 횟수를 기록한다.&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;또한 getCalledCount() 메서드를 통해 push() 메서드가 얼마나 호출 되었는 지 확인할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Mock&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mock이란 한글로 모의의, 가짜의 라는 뜻을 가지는 테스트 더블 객체로, 실제 객체와 동일한 모의 객체를 만들어 테스트의 효용성 높이기 위해 사용한다.&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;Stub&lt;/b&gt;과는 다르게, 어떠한 행위를 잘 수행하는 지에 대해 검증하는 &lt;b&gt;행위 검증&lt;/b&gt; 을 위한 테스트 더블 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java환경에선 주로 &lt;b&gt;Mockito&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;Mockito란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mock을 쉽게 만들고 &lt;b&gt;mock의 행동을 정하는 stubbing&lt;/b&gt;, &lt;b&gt;정상적으로 작동하는 지 검증하는 verify&lt;/b&gt; 등 다양한 기능을 제공해주는 프레임워크이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB연동 작업등과 같은 테스트 자동화가 어렵거나, 요청에 시간이 오래 걸리는 내용들은 Mockito를 통해 가짜 객체를 생성하여 행위에 대한 검증을 신속하게 수행한다.&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;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&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;/h3&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>BackEnd</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/140</guid>
      <comments>https://pgmjun.tistory.com/140#entry140comment</comments>
      <pubDate>Thu, 4 Apr 2024 21:15:20 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Fluent API란? (feat. JDBC에 적용해보기)</title>
      <link>https://pgmjun.tistory.com/139</link>
      <description>&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;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kek0t/btsGnK9TwjO/yTItdtlJV6eL8l4oWbVhpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kek0t/btsGnK9TwjO/yTItdtlJV6eL8l4oWbVhpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kek0t/btsGnK9TwjO/yTItdtlJV6eL8l4oWbVhpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKek0t%2FbtsGnK9TwjO%2FyTItdtlJV6eL8l4oWbVhpK%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;720&quot; height=&quot;360&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fluent API는 API의 패턴 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fluent API는 메서드 체이닝 형태로 설계된 API이며, 그 목표는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain-specific_language&quot;&gt;도메인별 언어&lt;/a&gt; (DSL)를 생성하여 코드 가독성을 높이는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 용어는 &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Eric_Evans_(technologist)&amp;amp;action=edit&amp;amp;redlink=1&quot;&gt;Eric Evans&lt;/a&gt; 와 &lt;a href=&quot;https://en.wikipedia.org/wiki/Martin_Fowler_(software_engineer)&quot;&gt;Martin Fowler&lt;/a&gt; 가 2005년에 만들어냈다고 한다.&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;Fluent API를 사용하는 대표적인 라이브러리로 AssertJ가 존재한다.&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;Fluent API 패턴을 적용한 AssertJ를 통해, 직관적인 형태로 메서드를 풀어내어 테스트 코드를 작성할 수 있도록 구현되어 있다.&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Junit
Assertions.assertTrue(true);

// assertJ
Assertions.assertThat(true).isTrue();

// ===

// Junit
 Assertions.assertEquals(object1,object2);

 // assertJ
 Assertions.assertThat(object1).isEqaulTo(object2);&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;하나의 메서드를 통해 테스트를 수행하던 이전 Junit이 아닌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fluent API로 구현된 assertJ를 사용함으로써 테스트 질의를 다양화하는데에 도움을 얻고,&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보다 assertJ를 사용한 코드가 더 직관적임을 확인할 수 있다.&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;Fluent는 &lt;b&gt;&amp;lsquo;유창한&amp;rsquo;&lt;/b&gt; 이라는 의미처럼 메서드 체이닝 방식을 통해 자신의 행위에 대해 유창하게 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Fluent 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;적용 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Override
    public Long add(Connection conn, GameEntity game) throws SQLException {
        try {
            PreparedStatement preparedStatement = conn.prepareStatement(
                    &quot;INSERT INTO game (turn) VALUES(?)&quot;,
                    Statement.RETURN_GENERATED_KEYS
            );
            preparedStatement.setString(1, game.getTurn().now().name());
            preparedStatement.executeUpdate();

            ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
            generatedKeys.next();

            return generatedKeys.getLong(1);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 Java, JDBC를 사용해 DB 접근을 수행해야하는 우테코 체스 미션을 수행하면서, 위와 같은 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;JDBC를 사용하는 로직은 위와 같이 연결된 Connection을 통해 PreparedStatement를 얻고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 쿼리를 설정하여 execute 메서드를 통해 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리뷰어 알렉스의 리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 미션에서 리뷰어를 맡아주신 알렉스는 이 코드를 보고 비슷한 패턴이 반복되는 DAO 클래스 메서드들의 개선을 제안했다.&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;&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;예를 들어 PreparedStatement를 사용하면 쿼리를 입력하고 이후에 setString, setLong 등을 통해 쿼리에 사용되는 값들을 주입해주어야 했는데 이러한 처리 하나하나를 분리하는 것이 굉장히 복잡하게 느껴졌다.&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;PreparedStatement 자체를 생성하는 것도 Create 로직은 AUTO_INCREMENT Key 설정을 적용해두었기 때문에 이를 알아내어 사용하기 위해 Statement.RETURN_GENERATED_KEYD 설정을 적용해주어야했다.&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;이때 문뜩 Query 작성을 메서드 체이닝 방식을 기반으로 편리하게 만들어준 QueryDSL이 떠올랐다.&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;코드의 가독성을 높여보고자 Fluent API 패턴을 JDBC 사용에 적용해보고자 한다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;QueryManager&lt;/b&gt;: Connection 연결을 담당
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ConnectedQueryManager&lt;/b&gt;: Connection이 연결된 상태로 insert, select, update, delete 중 하나를 선택하고 쿼리를 입력받음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CrudQueryManager&lt;/b&gt;: CRUD 각각의 쿼리 매니저의 중복로직을 가지고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;InsertQueryManager&lt;/b&gt;: Create 쿼리 수행을 담당
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AfterExecuteQueryManager&lt;/b&gt;: Create 쿼리 수행 이후, Auto_Increment로 생성된 키값을 리턴하는 로직을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SelectQueryManager&lt;/b&gt;: Read 쿼리 수행을 담당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UpdateQueryManager&lt;/b&gt;: Update 쿼리 수행을 담당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DeleteQueryManager&lt;/b&gt;: Delete 쿼리 수행을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;코드 보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;QueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;import java.sql.Connection;

public class QueryManager {

    public static ConnectedQueryManager setConnection(final Connection conn) {
        return new ConnectedQueryManager(conn);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ConnectedQueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;import java.sql.Connection;
import java.sql.SQLException;

public class ConnectedQueryManager {
    private Connection conn;

    public ConnectedQueryManager(final Connection conn) {
        this.conn = conn;
    }

    public InsertQueryManager insert(final String query) throws SQLException {
        return new InsertQueryManager(conn, query);
    }

    public SelectQueryManager select(final String query) throws SQLException {
        return new SelectQueryManager(conn, query);
    }

    public UpdateQueryManager update(final String query) throws SQLException {
        return new UpdateQueryManager(conn, query);
    }

    public DeleteQueryManager delete(final String query) throws SQLException {
        return new DeleteQueryManager(conn, query);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CrudQueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

sealed class CrudQueryManager permits InsertQueryManager, SelectQueryManager, UpdateQueryManager, DeleteQueryManager {
    private final PreparedStatement pstmt;

    protected CrudQueryManager(final PreparedStatement pstmt) {
        this.pstmt = pstmt;
    }

    protected void setStringParameter(final int paramIndex, final String value) throws SQLException {
        this.pstmt.setString(paramIndex, value);
    }

    protected void setLongParameter(final int paramIndex, final Long value) throws SQLException {
        this.pstmt.setLong(paramIndex, value);
    }

    protected void setIntParameter(final int paramIndex, final int value) throws SQLException {
        this.pstmt.setInt(paramIndex, value);
    }

    protected ResultSet executeQuery() throws SQLException {
        return pstmt.executeQuery();
    }

    protected void executeUpdate() throws SQLException {
        pstmt.executeUpdate();
    }

    public PreparedStatement getPreparedStatement() {
        return pstmt;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;InsertQueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.regex.Pattern;

public final class InsertQueryManager extends CrudQueryManager {
    private static final Pattern INSERT_PATTERN = Pattern.compile(&quot;(?i)^insert\\s.*&quot;);

    public InsertQueryManager(final Connection conn, final String query) throws SQLException {
        super(conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS));
        validate(query);
    }

    private void validate(final String query) {
        if (!INSERT_PATTERN.matcher(query).matches()) {
            throw new IllegalArgumentException(&quot;삽입 쿼리는 INSERT 구문으로 시작해야합니다.&quot;);
        }
    }

    public InsertQueryManager setString(final int paramIndex, final String value) throws SQLException {
        setStringParameter(paramIndex, value);
        return this;
    }

    public InsertQueryManager setLong(final int paramIndex, final Long value) throws SQLException {
        setLongParameter(paramIndex, value);
        return this;
    }

    public AfterExecuteQueryManager execute() throws SQLException {
        super.executeUpdate();
        return new AfterExecuteQueryManager(super.getPreparedStatement());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AfterExecuteQueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class AfterExecuteQueryManager {

    private final PreparedStatement pstmt;

    public AfterExecuteQueryManager(final PreparedStatement pstmt) {
        this.pstmt = pstmt;
    }

    public ResultSet getGeneratedKeys() throws SQLException {
        return pstmt.getGeneratedKeys();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SelectQueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.regex.Pattern;

public final class SelectQueryManager extends CrudQueryManager {
    private static final Pattern SELECT_PATTERN = Pattern.compile(&quot;(?i)^select\\s.*&quot;);

    public SelectQueryManager(final Connection conn, final String query) throws SQLException {
        super(conn.prepareStatement(query));
        validate(query);
    }

    private void validate(final String query) {
        if (!SELECT_PATTERN.matcher(query).matches()) {
            throw new IllegalArgumentException(&quot;조회 쿼리는 SELECT 구문으로 시작해야합니다.&quot;);
        }
    }

    public SelectQueryManager setString(final int paramIndex, final String value) throws SQLException {
        setStringParameter(paramIndex, value);
        return this;
    }

    public SelectQueryManager setLong(final int paramIndex, final Long value) throws SQLException {
        setLongParameter(paramIndex, value);
        return this;
    }

    public ResultSet execute() throws SQLException {
        return super.executeQuery();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UpdateQueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;import java.sql.Connection;
import java.sql.SQLException;
import java.util.regex.Pattern;

public final class UpdateQueryManager extends CrudQueryManager {
    private static final Pattern UPDATE_PATTERN = Pattern.compile(&quot;(?i)^update\\s.*&quot;);

    public UpdateQueryManager(final Connection conn, final String query) throws SQLException {
        super(conn.prepareStatement(query));
        validate(query);
    }

    private void validate(final String query) {
        if (!UPDATE_PATTERN.matcher(query).matches()) {
            throw new IllegalArgumentException(&quot;수정 쿼리는 UPDATE 구문으로 시작해야합니다.&quot;);
        }
    }

    public UpdateQueryManager setString(final int paramIndex, final String value) throws SQLException {
        setStringParameter(paramIndex, value);
        return this;
    }

    public UpdateQueryManager setLong(final int paramIndex, final Long value) throws SQLException {
        setLongParameter(paramIndex, value);
        return this;
    }

    public void execute() throws SQLException {
        super.executeUpdate();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DeleteQueryManager&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;import java.sql.Connection;
import java.sql.SQLException;
import java.util.regex.Pattern;

public final class DeleteQueryManager extends CrudQueryManager {
    private static final Pattern DELETE_PATTERN = Pattern.compile(&quot;(?i)^delete\\s.*&quot;);

    public DeleteQueryManager(final Connection conn, final String query) throws SQLException {
        super(conn.prepareStatement(query));
        validate(query);
    }

    private void validate(final String query) {
        if (!DELETE_PATTERN.matcher(query).matches()) {
            System.out.println(query);
            throw new IllegalArgumentException(&quot;삭제 쿼리는 DELETE 구문으로 시작해야합니다.&quot;);
        }
    }

    public DeleteQueryManager setString(final int paramIndex, final String value) throws SQLException {
        setStringParameter(paramIndex, value);
        return this;
    }

    public DeleteQueryManager setLong(final int paramIndex, final Long value) throws SQLException {
        setLongParameter(paramIndex, value);
        return this;
    }

    public void execute() throws SQLException {
        super.executeUpdate();
    }
}&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;hr data-ke-style=&quot;style7&quot; data-ke-type=&quot;horizontalRule&quot; /&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;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;적용 전/후 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Before&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Override
    public Long add(Connection conn, GameEntity game) throws SQLException {
        try {
            PreparedStatement preparedStatement = conn.prepareStatement(
                    &quot;INSERT INTO game (turn) VALUES(?)&quot;,
                    Statement.RETURN_GENERATED_KEYS
            );
            preparedStatement.setString(1, game.getTurn().now().name());
            preparedStatement.executeUpdate();

            ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
            generatedKeys.next();

            return generatedKeys.getLong(1);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }&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;h3 data-ke-size=&quot;size23&quot;&gt;After&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public Long add(Connection conn, GameEntity game) throws SQLException {
        ResultSet generatedKeys = QueryManager
                .setConnection(conn)
                .insert(&quot;INSERT INTO game (turn) VALUES(?)&quot;)
                .setString(1, game.getTurn().now().name())
                .execute()
                .getGeneratedKeys();

        generatedKeys.next();
        return generatedKeys.getLong(1);
    }&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;확실히 이전보다 JDBC 코드를 다루는 것이 간편해졌으며,&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;역할 분리 및 구현을 위해 Fluent 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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Fluent_interface&quot;&gt;Fluent interface&lt;/a&gt;&lt;/p&gt;</description>
      <category>Language/Java</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/139</guid>
      <comments>https://pgmjun.tistory.com/139#entry139comment</comments>
      <pubDate>Thu, 4 Apr 2024 21:06:15 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 생성자 체이닝(Constructor Chaining) 기법</title>
      <link>https://pgmjun.tistory.com/138</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2GVTf/btsGnZy3MMJ/qqTmdap1kivR8dSWAxihn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2GVTf/btsGnZy3MMJ/qqTmdap1kivR8dSWAxihn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2GVTf/btsGnZy3MMJ/qqTmdap1kivR8dSWAxihn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2GVTf%2FbtsGnZy3MMJ%2FqqTmdap1kivR8dSWAxihn1%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;720&quot; height=&quot;360&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성자 체이닝(Constructor Chaining)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 체이닝은 생성자가 2개 이상일 경우, 전체 인스턴스 필드의 값 초기화를 담당하는 하나의 &lt;b&gt;주 생성자&lt;/b&gt;를 두고 이 외의 부 생성자들은, 자신보다 상위의 부 생성자를 호출하는 체이닝 구조를 거쳐 최종적으로 주 생성자를 호출하여 객체를 생성하도록 만드는 방식이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;적용&lt;/h2&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 class=&quot;reasonml&quot;&gt;&lt;code&gt;public Player(final String name) {
	super(Cards.createEmpty());
	this.name = new Name(name);
}

public Player(final String name, final Cards cards) {
	super(cards);
	this.name = new Name(name);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성자 체이닝 적용&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public Player(final String name) {
		this(new Name(name), Cards.createEmpty());
}

public Player(final Name name, final Cards cards) {
	super(cards);
	this.name = name;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성자들 중, 가장 아래에 &lt;b&gt;주 생성자&lt;/b&gt;를 배치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부 생성자&lt;/b&gt;들은 체이닝 순서에 맞게 순차적으로 &lt;b&gt;주 생성자&lt;/b&gt; 위쪽에 배치&lt;/li&gt;
&lt;/ul&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;사용 목적&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 중복 로직 제거&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기존 코드&lt;/b&gt;를 보면 `this.name = new Name(name);` 코드가 두 생성자에서 중복되고 있다.&lt;br /&gt;지금은 인스턴스 필드가 간단하게만 존재하지만, 인스턴스 필드의 종류와 오버로드가 더 많다면 중복 코드가 훨씬 많이 생성될 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기에 &lt;b&gt;생성자 체이닝 적용&lt;/b&gt;을 통해 &lt;b&gt;부 생성자&lt;/b&gt;에서 this 키워드를 통해 &lt;b&gt;주 생성자&lt;/b&gt;를 호출하도록 함으로써&lt;br /&gt;`코드 중복을 제거` 및 `생성자의 재사용`이 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 유지보수성 향상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`생성자 체이닝 패턴 적용`을 통해 중복 코드가 제거되고, 인스턴스 필드 초기화 책임이 하나의 생성자(주 생성자)에만 존재하므로&lt;br /&gt;변경이나 확장이 더 간단하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 가독성 향상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복 코드가 제거되고, 필드 초기화의 책임이 하나의 생성자(주 생성자)에만 존재하며 부생성자들이 체이닝 순서에 맞게 순차적으로 배치되기 때문에 객체 필드 초기화의 흐름을 읽을 수 있어 가독성이 향상&lt;/li&gt;
&lt;/ul&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>Language/Java</category>
      <category>Java</category>
      <category>Java Constructor Chaning</category>
      <category>생성자 관리 전략</category>
      <category>생성자 체이닝</category>
      <category>자바 생성자</category>
      <category>자바 생성자 체이닝</category>
      <author>PgmJUN</author>
      <guid isPermaLink="true">https://pgmjun.tistory.com/138</guid>
      <comments>https://pgmjun.tistory.com/138#entry138comment</comments>
      <pubDate>Tue, 2 Apr 2024 16:15:54 +0900</pubDate>
    </item>
  </channel>
</rss>