<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>✨ iirin.context</title>
    <link>https://new-pow.tistory.com/</link>
    <description>별건 없고요, 조금씩 했던 것을 쌓아가고 있습니다.
이 블로그는 호기심과 재미로 추동됩니다  </description>
    <language>ko</language>
    <pubDate>Sun, 17 May 2026 12:43:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Iirin</managingEditor>
    <image>
      <title>✨ iirin.context</title>
      <url>https://tistory1.daumcdn.net/tistory/5810054/attach/6768b4ce86f64f05824181d309bec1c7</url>
      <link>https://new-pow.tistory.com</link>
    </image>
    <item>
      <title>토스 Learner's High 서버 2기, 탈락한 뒤늦은 후기</title>
      <link>https://new-pow.tistory.com/148</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;지난 2025년 12월부터 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;/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;1280&quot; data-origin-height=&quot;2181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH9RNn/dJMcaakA3sH/pLps5waKaEhUzK313uuOd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH9RNn/dJMcaakA3sH/pLps5waKaEhUzK313uuOd1/img.png&quot; data-alt=&quot;러너스하이의 커리큘럼. 제가 캡처를 못했어서 다른 선정자분의 블로그에서 가져왔어요. (https://velog.io/@jay_be)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH9RNn/dJMcaakA3sH/pLps5waKaEhUzK313uuOd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH9RNn%2FdJMcaakA3sH%2FpLps5waKaEhUzK313uuOd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1022&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2181&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;러너스하이의 커리큘럼. 제가 캡처를 못했어서 다른 선정자분의 블로그에서 가져왔어요. (https://velog.io/@jay_be)&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;결론적으로 토스에서 진행하는 Learner's High(이하 '러너스하이' 라고도 씀.) 는 토스에서 진행하는 현직자 대상 채용 프로세스 중 하나입니다.&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L350i/dJMcaiiCIZI/3WciFJCED8zwBINT2S0KKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L350i/dJMcaiiCIZI/3WciFJCED8zwBINT2S0KKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L350i/dJMcaiiCIZI/3WciFJCED8zwBINT2S0KKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL350i%2FdJMcaiiCIZI%2F3WciFJCED8zwBINT2S0KKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;217&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;490&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기대와 실제&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 멘토링 보다는 채용프로세스였다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상과 달랐던 점 중 하나는 이 과정이 멘토링이나 커뮤니티와 같은 설계보다는 채용프로세스로서의 정체성이 강하다는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주니어 채용에서, 기술적 적합성을 보는 프로세스인 'Next'와 컬쳐핏 적합성을 보는 프로세스는 바로 이 'Learner's High' 를 진행하고 있었습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 재직중인 회사의 임팩트에 집중할 수 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러너스하이의 가장 추천할 만한 이유중 하나는 바로 이것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러너스하이는 '현직자'들을 대상으로 모집하고 있는데요. 참여자가 직접 정하는 성장 과제는 토스를 위한 것이나 참여자 스스로가 성장하기 위한 과제라기엔 강조하는 바가 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가이드세션에서는 토스에서 업무를 평가하는 기준과 지난 기수의 경험담에 대해서 들을 수 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'왜'&lt;/b&gt; 와 &lt;b&gt;'투자 대비 임팩트&lt;/b&gt;' 두 가지 키워드가 가장 인상깊었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 다니는 회사에서 이 두가지를 염두하여 과제를 정하고, 1개월이라는 단기간동안 계획하여 배포까지 진행한다면 회사에 기여를 할 수 있는 것이죠. 그리고 개발자로서 유효한 프로젝트를 한 경험을 얻을 수 있기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;회고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 저는 이 러너스하이에 참여를 확정하게 되었을 때, 당시 다니던 회사에서 퇴사를 2주 후 앞두고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 진행중이던 MVP 기능 개발에 최대한 집중해도 힘든 일정이었기 때문에   아쉽게도 성장 과제에 대한 설정과 수행에 집중할 수가 없는 상황이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 얻었던 인사이트를 정리해둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;일을 하는 방식을 되돌아보다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 겪으며 저는 그동안 회사에서 어떻게 일해왔나 돌아볼 수 있는 기회가 되었습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스스로 성장하는 방식을 만드는 것&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;목표가 명확하면 오히려 한계가 생긴다.&lt;br /&gt;스스로 성장하는 사람은 배워서 따라하는 게 아니라 &lt;b&gt;내 환경을 이해하고 새로운 경험을 만드는 사람&lt;/b&gt;.&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;/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;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;목표는 혼자 잡지 않는다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번&amp;nbsp;경험을&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;개인이&amp;nbsp;생각하는&amp;nbsp;&amp;lsquo;좋은&amp;nbsp;목표&amp;rsquo;와&amp;nbsp;조직에서&amp;nbsp;실제로&amp;nbsp;필요한&amp;nbsp;&amp;lsquo;유효한&amp;nbsp;목표&amp;rsquo;는&amp;nbsp;다를&amp;nbsp;수밖에&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;특히 서비스나 제품에 영향을 주는 작업일수록, 목표는 개인의 성장 관점이 아니라 팀과 조직의 맥락 안에서 정의되어야 합니다.&lt;br /&gt;이&amp;nbsp;과정에서&amp;nbsp;리더나&amp;nbsp;동료와의&amp;nbsp;정렬(alignment)은&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;임팩트를&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;방향으로&amp;nbsp;수정되기&amp;nbsp;때문입니다.&lt;br /&gt;&lt;br /&gt;앞으로는 목표를 설정할 때 혼자 고민하고 결정하기보다, 초기 단계에서부터 &amp;ldquo;이 목표가 정말 의미 있는가?&amp;rdquo;, &amp;ldquo;이 결과가 실제로 어떤 변화를 만드는가?&amp;rdquo;를 함께 검증하는 과정을 거치려고 합니다.&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;b&gt;마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clMvYJ/dJMcagE82dH/XBEDKh0UnKFOlSvckyUKMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clMvYJ/dJMcagE82dH/XBEDKh0UnKFOlSvckyUKMK/img.png&quot; data-alt=&quot;2기를 마무리해준 메일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clMvYJ/dJMcagE82dH/XBEDKh0UnKFOlSvckyUKMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclMvYJ%2FdJMcagE82dH%2FXBEDKh0UnKFOlSvckyUKMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;256&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;654&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;p data-ke-size=&quot;size16&quot;&gt;이번 러너스하이는 충분히 활용하지 못했습니다. 목표 설정도, 과정, 결과 측정까지 온전히 경험하지 못한 것이 아쉬운 마음입니다. 하지만 일을 정의하는 기준, 목표를 설정하는 방식, 그리고 임팩트를 바라보는 관점에 대해 다시 생각해보게 된 계기였다는 점에서 의미가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 다음 러너스하이 3기에 참여하고자 하는 분이 있다면, 사실 이 채용프로세스에 합격하는 것도 물론 중요한 성과이지만.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개월이라는&amp;nbsp;제한된&amp;nbsp;시간을&amp;nbsp;강한&amp;nbsp;제약이자&amp;nbsp;동기로&amp;nbsp;활용해,&amp;nbsp;스스로&amp;nbsp;목표를&amp;nbsp;정의하고&amp;nbsp;임팩트를&amp;nbsp;검증하는&amp;nbsp;&amp;lsquo;성장의&amp;nbsp;구조&amp;rsquo;를&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;&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/148</guid>
      <comments>https://new-pow.tistory.com/148#entry148comment</comments>
      <pubDate>Sat, 11 Apr 2026 05:28:31 +0900</pubDate>
    </item>
    <item>
      <title>기다려왔던 일의 시기가 왔다: ⟪일의 감각⟫을 읽은 후기</title>
      <link>https://new-pow.tistory.com/147</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며, 기다려왔던 일의 시기가 왔다&lt;/b&gt;&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;가장 먼저 걱정이 되었던 것은 그동안 무뎌져 있었을 '일의 감각'을 다시 날카롭게 세우는 데에 걸리는 시간들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 약간의 긴장감으로 '팽팽해지는 감각'을 이완된 감각과 거의 비슷하게 좋아합니다. 눈이 번쩍 뜨이고, 듣거나 입수하는 정보마다 습득하는 집중력이 올라가는 감각이요. 무뎌져 있을 일의 감각을 깨운다는 것은 이 긴장감의 시위를 팽팽히 당기고 싶다는 의지이기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나름 욕심이 많아서 새로운 직장에서 '일을 잘하는' 사람이 되고 싶은 마음도 들고요 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 ⟪일의 감각⟫ 이라는 책을 접하게되고, 읽게 된 것은 아주 자연스러운 흐름이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QHpXJ/dJMcabX1W97/zMHinOnsOtBV8PKcNYF1q0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QHpXJ/dJMcabX1W97/zMHinOnsOtBV8PKcNYF1q0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QHpXJ/dJMcabX1W97/zMHinOnsOtBV8PKcNYF1q0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQHpXJ%2FdJMcabX1W97%2FzMHinOnsOtBV8PKcNYF1q0%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;350&quot; height=&quot;467&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;611&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;책은 디자이너 출신이자 전 카카오 공동대표, 매거진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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;일에 대한 태도&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일을 할 때 저는 일부러 나와 감정적으로 분리를 하려는 노력을 많이해왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일로부터 얻는 성취도 좋아하고, 몰입하는 것도 좋아하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 일의 성패, 일에 대한 피드백, 내가 컨트롤 할 수 없는 것으로부터 얻은 결과 등과 '나'를 동일시 하여 힘들었던 경험이 있었기 때문이었습니다. 그래서 일에 몰입하고 책임감을 가지고 최선을 다하되 일이 곧 내가 아님을 계속 염두해두었던 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;style2&quot;&gt;이처럼 신뢰를 쌓으려면 일을 잘 하는 것보다 더 중요한 게 있습니다. 그것은 바로 오너보다 더 오너십을 가지는 것입니다. 물론 오너십을 가지고 일하면 시키는 대로 컨펌을 받으며 일 할 때보다 부담이 엄청납니다. 하지만, 결국 그 부담이 쌓여 내 자산이 됩니다. 쉽게 말해, 오너의 신뢰를 얻으려면 오너의 고민을 내가 대신 해주면 됩니다. - p.25&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;'어디까지 내 일인지' 정의를 내리는 데에 두려움을 갖지 않아야 한다&lt;/b&gt;는 것입니다. 감정적인 몰입보다는 문제 정의와 어디까지 내가 해결하고자 나의 역할을 확장할 것인가에 대한 문제였습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;'오너십을 가지라'는 말은 마음만 그렇게 먹으라는 말이 아닙니다. 실제로 내가 맡은 일의 주인이 되라는 말입니다. 그러려면 첫 삽을 뜨고, 마지막 흙을 덮는 일까지 직접 살피려 노력해야 합니다. - p.40&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;구성원 모두가 이런 오너십을 가지고 있다면, 그리고 서로에게 주어지는 피드백에 긍정적인 태도로(수용이 아니라, 날카롭게 받아들이지 않는다는 이야기) 대화한다면 더더욱 좋겠습니다.&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;b&gt;성실함이 나다운 기준을 만든다&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;성실함으로 감각을 조금씩 성장시키고 나면 마치 직관처럼 그것이 떠오르게 됩니다. (중략)&lt;br /&gt;성실한 과정의 결과로 나의 선호가 생기면 반드시 타인의 취향 또한 같은 깊이로 인정하게 되기 때문입니다. 이처럼 좋고 나쁨의 이분법이 아닌 다양성의 눈으로 세상을 보는 것은 감각을 키우는 데 아주 중요한 과정입니다. (중략)&lt;br /&gt;가장 현명한 결정을 내리기 위해서는 상대의 감각을 존중하며 서로의 생각과 이유를 차분히 묻는 과정을 꼭 거쳐야합니다. - p.88&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;하나&amp;nbsp;인상&amp;nbsp;깊었던&amp;nbsp;것은&amp;nbsp;&amp;lsquo;성실함&amp;rsquo;에&amp;nbsp;대한&amp;nbsp;정의였습니다.&lt;br /&gt;성실하게 쌓아온 과정은 결국 &amp;lsquo;나의 선호&amp;rsquo;를 만들고, 그 선호가 타인의 취향을 이해하는 기반이 됩니다.&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;들었습니다.&lt;br /&gt;&lt;b&gt;반복적으로 보고, 고민하고, 선택하는 경험이 쌓여야 비로소 &amp;lsquo;왜 이게 좋은지&amp;rsquo;를 설명할 수 있게 됩니다.&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;/p&gt;
&lt;p data-ke-size=&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;b&gt;본질에 집중하자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서는 브랜딩을 이야기하면서, 자연스럽게 &amp;lsquo;본질&amp;rsquo;에 대한 이야기를 합니다.&lt;br /&gt;우리가 흔히 브랜딩이라고 하면 시각적인 요소나 차별화된 콘셉트를 먼저 떠올리지만, 책은 그보다 앞선 질문을 던지고 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;대부분의 경우 우리는 &amp;lsquo;어떻게 만들 것인가&amp;rsquo;에 집중하지, &amp;lsquo;왜 만들어야 하는가&amp;rsquo;까지는 깊게 들어가지 않기 때문입니다.&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;방향은&amp;nbsp;흐려지고,&amp;nbsp;선택은&amp;nbsp;늘어나지만&amp;nbsp;설득력은&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;style2&quot;&gt;감각적인 기획을 생각해내는 방법은 이렇습니다. 가장 상식적이고도 기본적인 생각에서 출발합니다. (p.149)&lt;br /&gt;(중략)&lt;br /&gt;감각적인 아이디어는 상식에서 착안해 본질부터 다듬아 나가는 것입니다.&lt;br /&gt;(중략)&lt;br /&gt;아이디어가 만일 상식과 본질에서 시작되었다면 실행이 비교적 수월합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&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;하지&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;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기획이라는 일에는 정해진 틀이 없습니다. 자기 분야의 벽을 개고, 이 일이 가야 할 방향과 그 본질에 대해 깊게 고민할 수 있는 사람이 하는 일, 그것이 기획입니다. (중략)&lt;br /&gt;브랜딩이란 일의 본질이자 존재 의미를 뾰족하게 하는 일 (p.162)&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;책을&amp;nbsp;읽으며&amp;nbsp;&amp;lsquo;일을&amp;nbsp;잘한다&amp;rsquo;는&amp;nbsp;것이&amp;nbsp;무엇인지에&amp;nbsp;대한&amp;nbsp;기준을&amp;nbsp;다시&amp;nbsp;정리하게&amp;nbsp;되었습니다.&lt;br /&gt;결국 일의 감각이라는 것은 타고나는 재능이라기 보다는 어떤 관찰을 하는지, 어떤 질문을 던지는지, 어떤 태도로 일에 임해왔는지에 대한 훈련이라는 생각이 듭니다.&lt;br /&gt;&lt;br /&gt;이제&amp;nbsp;곧&amp;nbsp;새로운&amp;nbsp;환경에서&amp;nbsp;다시&amp;nbsp;일을&amp;nbsp;시작하게&amp;nbsp;됩니다.&lt;br /&gt;이전과 완전히 다른 사람이 된다기 보다는 좀 더 업그레이드를 해봐야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성실하게 내 감각을 갈고 닦으며, 오너십을 가지고 일해보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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://github.com/new-pow/bookshelf/blob/main/90-%F0%9F%A7%91%F0%9F%92%BC-work/WorkWell/Work-and-sence.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[링크]&lt;/a&gt;를 참고해주세요.&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/147</guid>
      <comments>https://new-pow.tistory.com/147#entry147comment</comments>
      <pubDate>Sat, 4 Apr 2026 06:36:09 +0900</pubDate>
    </item>
    <item>
      <title>파리에 가면 탱고를 배워보세요  : 통제가 가능한 불안에 대하여</title>
      <link>https://new-pow.tistory.com/144</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 달, 친구가 파리에 다녀온 후에 한 서점에서 관련한 이야기를 나누는 자리를 만들어서 다녀왔습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;1116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pOzPH/dJMcagx3pgk/GnKRGKiVDK33XnleG5acU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pOzPH/dJMcagx3pgk/GnKRGKiVDK33XnleG5acU1/img.png&quot; data-alt=&quot;워크숍은 서점 'moire'에서 진행되었습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pOzPH/dJMcagx3pgk/GnKRGKiVDK33XnleG5acU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpOzPH%2FdJMcagx3pgk%2FGnKRGKiVDK33XnleG5acU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;433&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;1116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;워크숍은 서점 'moire'에서 진행되었습니다.&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;처음에는 단순하게 친구의 경험담과 생각을 들으러 간 것이었습니다. 하지만 예상치 못하게 다채로운 이야기를 들을 수 있고, 새로운 사람들을 만나 대화를 나눌 수 있는 기회가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에는 조르주 바타유의 '에로티즘 Erotism' 을 다룬 정신분석학적 관점의 이야기가 있었는데요. 최근 본 영화나 생각했던 것들과 연결지점이 있어서 글을 작성해보았습니다.&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;&lt;b&gt;불안을 선택할 수 있는가?&lt;/b&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;LLM의 급격한 성장으로 인한 고용 불안정. 부동산, 경제의 흐름과 국제 정세. 그리고 '내가 뒤쳐지고 있는 것은 아닐까?', '왜 나만 빼고 다들 빠르게 변해가는 것 같지?'라는 불안 등.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 결핍이 있기 때문에 욕망이 생기고. 이런 욕망에 반해, 미래에 대해 통제할 수 없기 때문에 우리는 자주 불안합니다. 내가 원하는 대로 앞날이 정해진다면 우리는 불안하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본질적으로 불안과 에로티즘은 같습니다. 다만 가장 큰 차이는 '내가 그것을 컨트롤할 수 있는가' 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 불안의 방향과 끝을 제어할 수 있다면, 불안은 우리에게 가장 큰 동력이 되어 행동하는 기반이 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 왜 우리는 이렇게 못하는 것일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;파리에서 탱고를 배워보는 것 , 그리고 우리가 여행을 가는 이유&lt;/b&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;파리에서 유명한 댄스 교습소의 가장 기초 탱고 레슨을 듣는 것입니다.&lt;/p&gt;
&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;적절한 불안을 유지하는 경우, 우리에게 주어지는 것&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적당한 긴장과 결핍은 우리에게 이런 것들을 줄 수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 지속 가능한 추진력을 줍니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;2. 깊은 몰입감을 줍니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;목표를 위한 긴장감을 유지하면 우리의 오감이 목표에 집중하게 됩니다. 불필요한 자극을 차단하고 몰입하는 경험을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 몰입으로 우리는 종종 작은 경험도 극대화 되어 생생하게 인지되기도 합니다. 그리고 우리의 창의력, 퍼포먼스를 최대로 끌어올릴 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 관계의 밀도가 증가합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일상적인 관계가 아니라, 거리와 긴장이 적당히 유지가 될 때 관계의 밀도를 높입니다. 밀도가 높아진 관계는 일상적인 관계보다 높은 의미를 갖습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;우리가 여행을 떠나는 이유도 같지 않을까요?&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5a6Vs/dJMcaio8rmU/WhS4rDUrphIaqxznfYUfzk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5a6Vs/dJMcaio8rmU/WhS4rDUrphIaqxznfYUfzk/img.jpg&quot; data-alt=&quot;최근에 본 '여행과 나날'이라는 영화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5a6Vs/dJMcaio8rmU/WhS4rDUrphIaqxznfYUfzk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5a6Vs%2FdJMcaio8rmU%2FWhS4rDUrphIaqxznfYUfzk%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;265&quot; height=&quot;375&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;375&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;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;불안을 컨트롤 하는 방법은 뭘까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론에서 던졌던 질문을 다시 생각해봅시다.&lt;/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;대부분의&amp;nbsp;불안을&amp;nbsp;'선택하지&amp;nbsp;않았기'&amp;nbsp;때문입니다.&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;없습니다.&lt;br /&gt;이런 불안은 사용할 수 없습니다. 그저 사람을 소모시킬 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 우리가 선택한 불안조차, 매번 우리를 앞으로 나아가게 만드는 것은 아닙니다. 잘못 다루면 쉽게 우리를 잠식할 수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;추상적인 불안이라면 쪼개어 구체적인 문제로 만들어봅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;반드시&amp;nbsp;돌아올&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;출구를&amp;nbsp;함께&amp;nbsp;설계합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;행동으로 바꾸어봅니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;To do 를 작성해본다.&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;핵심은 불안을 없애는 것이 아니라, 사용 가능한 크기로 줄이는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불안은&amp;nbsp;사라지지&amp;nbsp;않습니다.&lt;br /&gt;&lt;br /&gt;대신, 선택하거나 쪼갤 수는 있습니다.&lt;br /&gt;여행을 가듯, 낯선 사람과 탱고를 추듯, 우리는 스스로 불안의 방향을 정할 수 있습니다.&lt;br /&gt;그리고 선택된 불안은 더 이상 우리를 무너뜨리지 않고, 움직이게 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문득 돌이켜보면 저는 불안에서 벗어나본 적이 별로 없는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 나라를 가거나, 새로운 영역을 공부하고. 평소에 조금 버겁게 일을 해왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이런 불안감이 나에게 얼마나 큰 동력이 되었는지 모릅니다. 특히나 단순한 재미의 단계가 지나가고, 고루한 지지부진한 시기에 적당한 챌린지가 제게는 흥미를 던져주었던 것같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 불안조차 항상 우리를 성장시키는 것은 아닙니다. 때로는 그것 역시 우리를 소진시키기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝까지 컨트롤 할 수 있는가가 중요할 것같습니다. 내가 소진되거나 과부하가 되지 않게, 감당 가능하게 만드는 것 말이지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 통제할 수 있을 만큼의 불안을 설계해보아야 겠습니다.&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/144</guid>
      <comments>https://new-pow.tistory.com/144#entry144comment</comments>
      <pubDate>Fri, 6 Mar 2026 15:06:52 +0900</pubDate>
    </item>
    <item>
      <title>의도를 가지자, 날카롭고 분명한.</title>
      <link>https://new-pow.tistory.com/143</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;i&gt;이 글은 2026.01-02 회고입니다.&lt;/i&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 style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;2026년도 시작한지 벌써 두 달이 지났습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;2025년 12월을 마지막으로 이전 회사에서 퇴사를 하게 되었는데요. 그러다보니 정말 오랜만에 '백수의 일상'을 보내게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;1월은 휴식기, 2월부터 본격적인 이직시장에 뛰어들었는데요. 그 와중에 한 것들. 생각한 것을 정리해보려고합니다.&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 style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;b&gt;다시 회고모임 시작!&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;24년 11월부터 시작했던 &lt;a href=&quot;https://new-pow.tistory.com/87&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;신입 개발자 취업준비 회고모임&lt;/a&gt;이 약간의 쉬는 기간을 거쳐 다시 시작했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;1기부터 현재 4기까지, 멤버들은 조금씩 변경되었지만 저는 꾸준히 하고 있어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;어느새 부터인가 저의 생활에 큰 기둥을 차지하는 시간입니다. 일주일 중 1시간씩 투자해서 한 주의 고민과 인사이트를 나누는 것이 조금씩 쌓여 벌써 이렇게나 기록이 많이 누적되었다니. 정말 뿌듯한 마음입니다.&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;이번 4기의 회고모임에서 얻고 싶은 것은,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;역시나 이직 성공꿀팁  . 그리고 개발 트랜드와 정보들.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;b&gt;수영과 등산으로 체력 강화 중&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;작년 12월부터 꾸준히 수영을 하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;수영은 작년부터 꾸준히 도전해왔는데, 정말 가기가 싫었거든요. 근데 그 이유를 알았습니다   새벽 7시에 가려고하니 당연히... 싫을 수밖에..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;자유인의 최대 장점인 '아침 일정없음' 을 이용하여 아침 10시에 수영을 다니고 있어요. 시간을 옮기니 수영이 정말 재미있어졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;심지어 수영 선생님과 잘 맞아서 하나하나 꼼꼼하게 동작을 알려주셔서 하루가 다르게 할 수 있는 것들이 늘어나니 너무 재미있더라고요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;한 달에 한 번씩 등산을 하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;집 근처에 있는 청계산, 그리고 인천에 있는 소래산을 다녀왔어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3g4fR/dJMcaiCspd3/qxKW7WyVb3nmBLVDzUz8Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3g4fR/dJMcaiCspd3/qxKW7WyVb3nmBLVDzUz8Hk/img.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;315&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;54.35&quot; style=&quot;width: 53.7159%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3g4fR/dJMcaiCspd3/qxKW7WyVb3nmBLVDzUz8Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3g4fR%2FdJMcaiCspd3%2FqxKW7WyVb3nmBLVDzUz8Hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;315&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sjBWW/dJMcad2bIxQ/SyKRt90SKQxzhNk4botmfK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sjBWW/dJMcad2bIxQ/SyKRt90SKQxzhNk4botmfK/img.jpg&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1536&quot; data-is-animation=&quot;false&quot; width=&quot;500&quot; height=&quot;375&quot; style=&quot;width: 45.1213%;&quot; data-widthpercent=&quot;45.65&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sjBWW/dJMcad2bIxQ/SyKRt90SKQxzhNk4botmfK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsjBWW%2FdJMcad2bIxQ%2FSyKRt90SKQxzhNk4botmfK%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;2048&quot; height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;내가 좋아하는 사람들의 모습. 정말 귀엽다. / 소래산의 아파트 뷰. 처음 볼때 CG인줄알았어요&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;b&gt;'날아가는 새는 뒤를 돌아보지 않는다'&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;올해 처음으로 모두 읽은 책입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;저와 비슷하게 선택의 기로에 선 친구들과 놀다가 같이 소리내어 읽었는데, 많은 용기와 자신감을 주었어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;필사한 내용을 아래 붙입니다. 평생 우리는 자신의 길을 닦아갑니다. 그 과정을 선택하는데 솔직해지고, 여정을 사랑할 수 있다면 얼마나 좋을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWJi1n/dJMcah4EfPT/0cSfe1b8n1J9CrWLmRuplK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWJi1n/dJMcah4EfPT/0cSfe1b8n1J9CrWLmRuplK/img.png&quot; data-alt=&quot;이 책입니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWJi1n/dJMcah4EfPT/0cSfe1b8n1J9CrWLmRuplK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWJi1n%2FdJMcah4EfPT%2F0cSfe1b8n1J9CrWLmRuplK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;405&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;405&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;'나-너'의 관계는 온 마음을 기울이는 관계이며, '너'를 나의 의도에 따라 판단하지 않는다. 판단은 '나-그것'의 관계에서 주로 일어난다. '나-너'의 관계는 사랑의 관계이고, '나-그것'의 관계는 쓸모의 관계이다. 인간관계에서 가장 큰 상실은 '나-너'의 만남을 잃는 일이다. 모든 관계의 불행과 갈등은 '나-너'의 관계가 되지 못하고 '나-그것'이 됨으로써 온다.&lt;/span&gt;&lt;/blockquote&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;죽는 날까지 자신이 가야 할 길을 선택하는 것이 삶이다.따라서 자신이 걸어가는 길에 확신을 가져야 한다.그 길에 기쁨과 설렘이 있어야 한다.그리고 세상 사람들과 자신의 다름을 담담히 받아들일 수 있어야 한다.'길'의 어원이 '길들이다'임을 기억하고 스스로 길을 들여 자신의 길을 만들어 가야만 한다.&lt;/span&gt;&lt;/blockquote&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;b&gt;의도를 가지자, 날카롭고 분명한.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;최근 제가 집중하고 있는 것은, 나의 강점은 무엇일까 하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;아무래도 이직 준비를 하고 있기도 했고, AI로 인해 내가 하는 '일' 에 대한 개념을 재정립하는 시기이기도 한 것같아요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그런 와중에 GeekNews를 통해 이런 글을 보게 됩니다. &lt;a href=&quot;https://news.hada.io/topic?id=26881&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[실력 없음. 취향 없음.]&lt;/a&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 style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;대부분 공감하는 내용들이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그럼 결국 상품간, 개발자간 차이를 만드는 것은 무엇일까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;AI로 인해 내가 원하는 것을 아주 세밀하게 조정하여 만들 수 있는 시대가 온 지금, &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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2712&quot; data-origin-height=&quot;1646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V3R9S/dJMcagYW4rv/LsnqJfLTLabBKKomik2XIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V3R9S/dJMcagYW4rv/LsnqJfLTLabBKKomik2XIk/img.png&quot; data-alt=&quot;회고모임에서 관련 생각을 공유했던 부분.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V3R9S/dJMcagYW4rv/LsnqJfLTLabBKKomik2XIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV3R9S%2FdJMcagYW4rv%2FLsnqJfLTLabBKKomik2XIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2712&quot; height=&quot;1646&quot; data-origin-width=&quot;2712&quot; data-origin-height=&quot;1646&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;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;AI로 기술 구현 단계를 급격하게 줄일 수 있는 이때에는 의도가 없는 결과물들도 쉽게 만들어집니다. 기능 하나를 구현하기 위한 방대한 코드, 그럴듯 해보이는 UI이지만 더이상 확장할 수 없는 서비스 등이 그 예시입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;무엇을 만들지 모르는 사람에게 AI는 단지 더 빠르게 평범한 결과물을 생산하는 도구가 될 뿐이지만, 명확한 의도를 가진 사람에게는 AI가 날개를 달아주는 역할을 합니다.&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;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그래서 요즘 계속 이런 질문을 스스로에게 던지고 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그래서 나는 어떤 의도를 가지고 움직이는 사람인가? 원하는 것을 명확하게 알고 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;AI 시대에 경쟁력은 더 빠르게 만드는 능력이 아니라 더 확실한 취향이 될지 모릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;기술은 점점 쉬워지고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그래서 이제 더 중요한 것은 기술이 아니라 의도입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그래서 요즘 계속 명심하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;의도를&amp;nbsp;가지자.&amp;nbsp;날카롭고&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 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;b&gt;확증편향과 AI&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;지난 2월 중에 44bits 주최로 진행한 OpenClaw 밋업에 다녀왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그곳에서 아주 특별한 연사를 만났는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;개발자는 아니지만, 개발을 취미로 하시는 신학 박사님이셨습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;주로 관련 논문을 찾는데에 AI를 쓰고 계셨는데, 그분의 여러 사용법 중 인상깊었던 부분이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그분은 주기적으로 AI 계정을 바꿔가며 사용하신다는 것이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&quot;내가 하는 실수를 AI도 하면 안되니까&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;저와 제 주변의 모든 개발자들은 AI 코드 에이전트를 '또 하나의 나' 로 만드는데 힘씁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;맥락을 학습시키고, 내 스타일을 기억하게 하고 나와 비슷하게 일하게 만드려고합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그분의 정반대 사용법에 깜짝 놀랐던 기억이 있습니다. 물론 사용 용도가 다르겠지만.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;b&gt;AI를 나와 다른 관점으로 볼 수 있는 도구&lt;/b&gt;로 계속 유지하고자 한 것이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;AI는 종종 우리의 확증편향을 강화할 수 있습니다. 질문에 매우 잘 맞춰 답해주기 때문입니다. 그 질문의 의도도 아주 잘 간파하곤합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그 대화는 아주 잘 흘러갑니다. 틀린 방향일지도 모르지만요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;문득 AI를 사용하며 종종 설계 방향이 혼돈이 되었던 기억이 납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;ctext-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;설계를 고민하다가 AI에게 질문을 던지고, 답을 받고&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;다시 질문을 하고, 다시 확장하고&amp;hellip;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;이 과정을 계속 반복하다 보면 설계가 점점 커집니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;처음에는 단순했던 구조가 어느 순간 굉장히 거대한 아키텍처가 되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&amp;ldquo;이거 오버엔지니어링 아니야?&amp;rdquo; 하고 AI에게 물어 결국 방향을 다시 잡아야 했던 경험이 한두 번이 아니었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;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;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;그래서 요즘 AI를 사용할 때 AI가 내 생각을 강화하는 도구가 되고 있는지, 아니면 새로운 관점을 던져주는 도구로 쓰이고 있는지, 인지하려고합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;예를 들어 일부러 반대 의견을 질문하거나, 미리 공부한 여러가지 방법에 대해 AI에게 질문해가며 비교하는 것도 좋은 방법일 것같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;AI를 나와 똑같이 만드는 것이 정답이 아닐지도 모릅니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;때로는 나와 다른 관점을 유지하도록 사용하는 것이 더 건강한 방법일 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;정말 사람과 협업하는 것처럼요!&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/143</guid>
      <comments>https://new-pow.tistory.com/143#entry143comment</comments>
      <pubDate>Fri, 6 Mar 2026 15:06:05 +0900</pubDate>
    </item>
    <item>
      <title>  〈도메인 주도 개발 시작하기- DDD 핵심 개념 정리부터 구현까지〉 읽은 후기</title>
      <link>https://new-pow.tistory.com/142</link>
      <description>&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://github.com/new-pow/bookshelf/blob/main/20-%F0%9F%8F%97%EF%B8%8F-architecture/DDD/%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0.md&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k9FMl/dJMcadnv1P9/FS0gqhaUjJ6JDgrunjewi0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k9FMl/dJMcadnv1P9/FS0gqhaUjJ6JDgrunjewi0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k9FMl/dJMcadnv1P9/FS0gqhaUjJ6JDgrunjewi0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk9FMl%2FdJMcadnv1P9%2FFS0gqhaUjJ6JDgrunjewi0%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;458&quot; height=&quot;593&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;593&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;첫번째 읽었을 때에는 당장 DDD 라는 아키텍처 개념을 처음 접하기 시작했고, 이벤트 드리븐과 접목해서 구현하고 싶었던 상황에서 읽었던 기억이 납니다. SpringBoot 환경에서 이벤트 발행과 handler 구현을 하는 것이 당시에는 맨땅에 헤딩하는 것과 같이 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 때 이 책을 읽고 구체적인 구현 사례들을 많이 참고했었습니다. 덕분에ㅎㅎ 당시에 제가 하고싶었던 Application Event 와 DDD 에서 다루는 Event 의 개념이 좀 달랐다는 것도 깨닫게 되기도 했습니다.&lt;/p&gt;
&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;DDD의 철학이 지금 이 시점에 왜 필요한가 하는 고민들을 쌓고, 실제로 경험해본 바와 비교하고 회고할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방법을 제안해주는 친절한 책입니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 주도 개발에 대한 책은 바이블로 여겨지는 에릭 에반스의 도메인 주도 개발 등, 다양하게 있습니다. 하지만 이 책만큼 친절한 개념 설명과 실제 코드로 직접 손에 잡히게끔 방식을 제안해주는 책은 흔치 않았던 것으로 기억합니다. 자바 스프링 프레임워크, JPA 등 ORM 을 사용한다면 막연하게 느껴졌던 개념을 어떤 방식으로 실현시키는 지 쉽게 볼 수 있습니다. 각 레이어를 어떻게 구성하고, 어떠한 요소는 어떤 책임을 가져야하는 지 구체적인 코드예시로 알 수 있는 것이지요. 반대로 말하면 코드에서 의도가 느껴지게 하려면 어떻게 할 수 있는 지 느껴볼 수 있는 입문서였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여담으로 저자인 최범균님의 말투가 이렇듯 조근조근하고 구체적인 것은 유튜브 채널에도 느낄 수 있습니다   구독자로서 추천합니다!&amp;nbsp;&lt;a href=&quot;https://www.youtube.com/@madvirus/videos&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;물론 책에 나온 실습 코드가 프로덕션 수준에서 그대로 쓰인다기 보다는, 본인 팀에 맞게 수정되어야 할 것같습니다.&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;b&gt;도메인 주도 개발을 하는 다양한 방법들이 있습니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DDD는 사실 고정된 방법론이라기 보다는 일종의 철학이라고 느껴집니다. 디자인 패턴 모음집이라기 보다는 복잡성을 격리하는 사고 체계이자 도메인 지식을 코드 구조와 시스템 설계에 반영하는 방식들인 것이지요.&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;458&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcuEuC/dJMcaadi9DA/t1CfweemiQb0REidYWXk5K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcuEuC/dJMcaadi9DA/t1CfweemiQb0REidYWXk5K/img.jpg&quot; data-alt=&quot;에릭에반스의 책 부제도 '소프트웨어의 복잡성을 다루는 지혜' 입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcuEuC/dJMcaadi9DA/t1CfweemiQb0REidYWXk5K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcuEuC%2FdJMcaadi9DA%2Ft1CfweemiQb0REidYWXk5K%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;458&quot; height=&quot;361&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;361&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;&lt;span style=&quot;text-align: start;&quot;&gt;책에서도 구체적인 방법을 제안해주면서도 결국 필요에 따라 결정하라, 논의하여 결정하라, 전문가와 논의하라 라는 등의 표현도 많이 나옵니다.&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;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;&lt;span style=&quot;text-align: start;&quot;&gt;도메인에 대한 지식, 유비쿼터스 언어, 바운디드 컨텍스트 등 팀의 합의를 일궈 내야하는 설계의 요소들이 참 많습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;그리고 이러한 설계 목적을 이루는 다양한 방법들도 많습니다.&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt; 책은 코드레벨 뿐만 아니라, 이벤트나 MSA, CQRS 등 다방면의 설계 방식에 대해 다루기도 합니다. 그리고 그것을 입문자의 눈높이에서 친절하게 다루고 있습니다.&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 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;복잡한 문제를 푸는 설계 철학으로서의, DDD&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;도메인 주도 개발의 목적은 결국 '복잡한 문제를 어떻게 풀어갈 것인가', 그리고 이것을 '일관성있게 유지보수할 수 있을 것인가' 라는 질문의 답이지 않을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;지금 AI로 인해 코드 생산성이 급격히 높아진 지금, 그 답의 가치는 더 중요해졌다고 생각합니다.&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;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;</description>
      <category> ️ Architecture</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/142</guid>
      <comments>https://new-pow.tistory.com/142#entry142comment</comments>
      <pubDate>Wed, 25 Feb 2026 18:06:22 +0900</pubDate>
    </item>
    <item>
      <title>Github Workflow CD/CD 개선기: SSH로 접근하지 않고 Secret Key 대신 OIDC 로 권한 인증</title>
      <link>https://new-pow.tistory.com/139</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;그 중에 하나가 바로 CI/CD 파이프라인을 어떻게 개선할 것인가 하는 과제였습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기존 방식의 한계 (동적 보안규칙 사용 후 SSH 접근)&lt;/b&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;Github Workflow Runner 가 AWS EC2 에 인증을 할 수 있어야 하는 구조입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771313248365&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌───────────────────────────────────────────────┬──────────────────────────────────────────┐
│                 GitHub Actions                │                   EC2                    │
├───────────────────────────────────────────────┼──────────────────────────────────────────┤
│                                               │                                          │
│  Tag Push (backend/*)                         │                                          │
│          │                                    │                                          │
│          ▼                                    │                                          │
│  Workflow Triggered                           │                                          │
│          │                                    │                                          │
│          ▼                                    │                                          │
│  Build                                        │                                          │
│          │                                    │                                          │
│          ▼                                    │                                          │
│  Test                                         │                                          │
│          │                                    │                                          │
│          ▼                                    │                                          │
│  보안그룹 Runner IP 추가                         │                                          │
│          │                                    │                                          │
│          ├────────────── SSH Connect ────────▶│  EC2 Host                                │
│          │                                    │     │                                    │
│          │                                    │     ▼                                    │
│          │                                    │  Update Source (Git pull)                │
│          │                                    │     │                                    │
│          │                                    │     ▼                                    │
│          │                                    │  Restart Backend Service                 │
│          │                                    │     │                                    │
│          │                                    │     ▼                                    │
│          │                                    │  Running                                 │
│          │                                    │     │                                    │
│          │◀────────── Health Check Result ────┤     ▼                                    │
│          │                                    │  Health Check                            │
│  보안그룹 Runner IP 제거                         │                                          │
│                                               │                                          │
└───────────────────────────────────────────────┴──────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Actions 러너는 매 실행마다 IP가 바뀝니다. SSH 배포 시 서버 보안그룹에 IP를 고정할 수 없어 임시로 보안그룹에 넣고 빼는 작업을 EC2 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;이 경우 다음과 같은 문제점이 발생하였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 포트를 &lt;b&gt;전체 허용하면 보안 위험&lt;/b&gt; (runner IP 목록: &lt;a href=&quot;https://api.github.com/meta&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.github.com/meta&lt;/a&gt;)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;private subnet 에 EC2를 위치해있었고, SSH 포트는 관리자가 주로 접근하는 IP만을 오픈해둔 상황.&lt;/li&gt;
&lt;li&gt;모든 Runner IP를 추가할 경우 다른 Runner 에서 접근할 수 있습니다.&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;중간에 EC2에서 배포 실패할 경우, 마지막 단계를 진행하지 못해 &amp;rarr; 보안그룹에 IP가 남음&lt;/li&gt;
&lt;li&gt;여러 워크플로우가 동시에 돌면 보안그룹 규칙이 잘못될 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Private Access Key로 AWS EC2 SSH 접근시 단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Github 레포지토리&amp;nbsp;&lt;/span&gt;&lt;b&gt;Secret 으로 Access Key를 등록해두어야했기 때문에 보안 문제&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwAsfh/dJMcafFzv0v/m1zGvvKfVffhjcVER2ozD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwAsfh/dJMcafFzv0v/m1zGvvKfVffhjcVER2ozD0/img.png&quot; data-alt=&quot;실제로 Secret 에 등록했던 secret&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwAsfh/dJMcafFzv0v/m1zGvvKfVffhjcVER2ozD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwAsfh%2FdJMcafFzv0v%2Fm1zGvvKfVffhjcVER2ozD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;235&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제로 Secret 에 등록했던 secret&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;해당 문제점으로 다방면으로 리서칭을 하다 개선가능한 방안을 찾아 적용한 기록을 남깁니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개선1: AWS SSM(AWS System Manager) 을 사용하여 EC2 인스턴스 접근&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/what-is-systems-manager.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS SSM&lt;/a&gt;을 사용하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS SSM 은 AWS, 온프로미스, multi cloud 환경의 리소스를 중앙에서 관리하고 자동화하는 운영 허브&lt;span style=&quot;text-align: start;&quot;&gt;입니다. 그 아키텍처에 관련해서는 &lt;a href=&quot;https://malwareanalysis.tistory.com/867#:~:text=AWS%20SSM%EC%9D%B4%EB%9E%80?%20SSM(Systems%20Manager%2C%20%EC%9D%B4%ED%95%98%20SSM)%EC%9D%80%20%EC%A4%91%EC%95%99%EC%97%90%EC%84%9C,%EB%AA%A8%EB%93%A0%20%EC%84%9C%EB%B2%84%EC%97%90%20%EC%A0%91%EC%86%8D%ED%95%B4%EC%84%9C%20%EC%9E%91%EC%97%85%ED%95%98%EB%A0%A4%EB%A9%B4%20%EB%A7%8E%EC%9D%80%20%EC%8B%9C%EA%B0%84%EC%9D%B4%20%EA%B1%B8%EB%A6%BD%EB%8B%88%EB%8B%A4.&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 링크&lt;/a&gt;를 참조해주세요.&lt;/span&gt;&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;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QBsLN/dJMcaiCgEq1/dUfX3GOuHfWkS0se6VXe21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QBsLN/dJMcaiCgEq1/dUfX3GOuHfWkS0se6VXe21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QBsLN/dJMcaiCgEq1/dUfX3GOuHfWkS0se6VXe21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQBsLN%2FdJMcaiCgEq1%2FdUfX3GOuHfWkS0se6VXe21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;577&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;577&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; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 접속 포트를 열지 않아도 됨 -&amp;gt; 443 outbound 만 열어주어도됩니다.&lt;/li&gt;
&lt;li&gt;IAM 기반 인증으로 보안 안정성 강화 가능&lt;/li&gt;
&lt;li&gt;SSM Run Command는 무료&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Workflow 에서 SSM 을 경유하여 EC2를 관리하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771328495935&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EC2 (SSM Agent)
        │  HTTPS 443 (Outbound)
        ▼
AWS Systems Manager
        ▲
        │  IAM 인증된 호출
GitHub Actions (또는 AWS CLI)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;적용 절차 요약&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS SSM 을 경유하는 것을 적용하기 위해 다음과같은 것들을 설정해야합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2에 ssm-agent 확인&lt;/li&gt;
&lt;li&gt;EC2에 IAM role 과 필요 정책 연결&lt;/li&gt;
&lt;li&gt;Github Action Runner 에 부여할 IAM에 정책 연결&lt;/li&gt;
&lt;li&gt;Action workflow yaml 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;EC2에 ssm-agent 확인&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서 제공하는 EC2라면 ssm-agent 가 이미 설치되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동으로설치해야한다면, 이 문서를 확인해주세요. &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/manually-install-ssm-agent-linux.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Linux용 EC2 인스턴스에 수동으로 SSM Agent 설치 및 제거]&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트를 미리 하고 세팅을 진행하면 좋습니다. &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/ssm-agent-automatic-updates.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[SSM Agent 업데이트 자동화]&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;저는 ubuntu 환경에서 진행했기 때문에, 커맨드를 참고해주세요.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# SSM Agent 설치 확인
sudo systemctl status amazon-ssm-agent&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;EC2에 IAM role 과 AmazonSSMManagedInstanceCore 정책 연결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSM 의 관리대상인 EC2에 role 을 생성하여 연결해줍니다. AmazonSSMManagedInstanceCore 을 가진 정책을 생성하는 것이 중요한 점입니다.&lt;/p&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 &amp;gt; Roles &amp;gt; Create role 메뉴로 진입하여 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신뢰할 수 있는 entity 는 EC2로 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;1338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGeZQ/dJMcacvkMGt/g0hm1SLdOKZRnMc8TvPNo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGeZQ/dJMcacvkMGt/g0hm1SLdOKZRnMc8TvPNo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGeZQ/dJMcacvkMGt/g0hm1SLdOKZRnMc8TvPNo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGeZQ%2FdJMcacvkMGt%2Fg0hm1SLdOKZRnMc8TvPNo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2220&quot; height=&quot;1338&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;1338&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;정책은 AmazonSSMManagedInstanceCore 를 연결합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEcY2U/dJMcagdqyK6/j5kxKGbrePxr41wtIf2uF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEcY2U/dJMcagdqyK6/j5kxKGbrePxr41wtIf2uF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEcY2U/dJMcagdqyK6/j5kxKGbrePxr41wtIf2uF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEcY2U%2FdJMcagdqyK6%2Fj5kxKGbrePxr41wtIf2uF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2220&quot; height=&quot;844&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&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;ssm:DescribeAssociation&quot;,
                &quot;ssm:GetDeployablePatchSnapshotForInstance&quot;,
                &quot;ssm:GetDocument&quot;,
                &quot;ssm:DescribeDocument&quot;,
                &quot;ssm:GetManifest&quot;,
                &quot;ssm:GetParameter&quot;,
                &quot;ssm:GetParameters&quot;,
                &quot;ssm:ListAssociations&quot;,
                &quot;ssm:ListInstanceAssociations&quot;,
                &quot;ssm:PutInventory&quot;,
                &quot;ssm:PutComplianceItems&quot;,
                &quot;ssm:PutConfigurePackageResult&quot;,
                &quot;ssm:UpdateAssociationStatus&quot;,
                &quot;ssm:UpdateInstanceAssociationStatus&quot;,
                &quot;ssm:UpdateInstanceInformation&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;ssmmessages:CreateControlChannel&quot;,
                &quot;ssmmessages:CreateDataChannel&quot;,
                &quot;ssmmessages:OpenControlChannel&quot;,
                &quot;ssmmessages:OpenDataChannel&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;ec2messages:AcknowledgeMessage&quot;,
                &quot;ec2messages:DeleteMessage&quot;,
                &quot;ec2messages:FailMessage&quot;,
                &quot;ec2messages:GetEndpoint&quot;,
                &quot;ec2messages:GetMessages&quot;,
                &quot;ec2messages:SendReply&quot;
            ],
            &quot;Resource&quot;: &quot;*&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;p data-ke-size=&quot;size16&quot;&gt;이제 생성한 정책을 EC2 와 연결시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔 메뉴에서 EC2 &amp;gt; Instances &amp;gt; 해당Instance 접근 후,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actions &amp;gt; Security &amp;gt; Modify IAM role 로 접근하여 생성한 역할을 등록해주면 됩니다.&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/EJGvC/dJMcabXsUfx/cPG8oGJkxbZm6K8ReaFyM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EJGvC/dJMcabXsUfx/cPG8oGJkxbZm6K8ReaFyM1/img.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;534&quot; data-is-animation=&quot;false&quot; width=&quot;400&quot; height=&quot;242&quot; style=&quot;width: 20.9621%; margin-right: 10px;&quot; data-widthpercent=&quot;21.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EJGvC/dJMcabXsUfx/cPG8oGJkxbZm6K8ReaFyM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEJGvC%2FdJMcabXsUfx%2FcPG8oGJkxbZm6K8ReaFyM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;884&quot; height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBsaKN/dJMcagR1ywB/EScF5fi5VFOc5yCIn1mXm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBsaKN/dJMcagR1ywB/EScF5fi5VFOc5yCIn1mXm1/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;48&quot; data-is-animation=&quot;false&quot; width=&quot;300&quot; height=&quot;49&quot; data-widthpercent=&quot;78.79&quot; data-filename=&quot;blob&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBsaKN/dJMcagR1ywB/EScF5fi5VFOc5yCIn1mXm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBsaKN%2FdJMcagR1ywB%2FEScF5fi5VFOc5yCIn1mXm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;48&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Action workflow yaml 수정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설정한 역할을 통해 workflow yaml 파일에 적용하여 수정합니다. 인증방식과 커맨드 방법을 확인해주세요.&lt;/p&gt;
&lt;pre id=&quot;code_1771330913338&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AI를 사용한 예제입니다.

name: Deploy backend via SSM
// 생략
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      // 생략
  cd:
    needs: [ci]
    runs-on: ubuntu-latest
    env:
      AWS_REGION: ap-northeast-2
      SSM_INSTANCE_ID: ${{secrets.INSTANCE_ID}}
      
    steps:
      - name: Configure AWS credentials (Access Keys)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Send SSM command
        id: ssm
        shell: bash
        run: |
          // script&lt;/code&gt;&lt;/pre&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;b&gt;개선2: AWS 인증에 OIDC 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 개선한 점은, 바로 AWS 인증 방식입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771329476600&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Caller]
   │  IAM Auth // 이 인증방식을 개선합니다.
   ▼
[SSM Service]
   │  
   ▼
[EC2 with SSM Agent]
   │
   ▼
[Command Execution]&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;현재 이런 방식으로 SSM 에 접근하기 전에 IAM 인증을 하고 있는데, 이 인증 방식을 OIDC 방식으로 개선해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OIDC(OpenID Connect)란 서비스에 액세스하기 위해 로그인할 때 사용자를 인증하고 권한을 부여하는 프로세스를 표준화하기 위한 OAuth(Open Authorization) 2.0이 확장된 ID 인증 프로토콜입니다. &lt;a href=&quot;https://openid.net/developers/how-connect-works/&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;AWS도 이 &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_roles_providers_create_oidc.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OIDC 공급자&lt;/a&gt;를 만들 수 있습니다. 기존 Access Key 를 이용한 방식에 비해 임시 토큰 방식과 secret 관리 변수 개수를 비교해보면 훨씬 보안상 안정적입니다. OIDC는 영구 액세스 키 없이 실행 시 임시 토큰을 발급받으므로. GitHub Secrets에 AWS 키를 저장할 필요가 없는 것이죠.&lt;/p&gt;
&lt;table style=&quot;height: 71px; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Access Key&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;OIDC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;GitHub Secrets&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;3개 (KEY_ID, SECRET, REGION)&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;0개 (저의 경우 INSTANCE_ID 만 등록해둠)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;키 유출 위험&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;있음 (영구 키)&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;없음 (임시 토큰, 만료있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;키 교체&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;수동으로 교체 필요&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;불필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OIDC를 설정한 기초 가이드는 &lt;a href=&quot;https://blog.outsider.ne.kr/1750&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;과 &lt;a href=&quot;https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws#adding-the-identity-provider-to-aws&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;적용 절차 요약&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;OIDC Provider 추가&lt;/li&gt;
&lt;li&gt;Github Action Runner 용 IAM 역할 생성
&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;/li&gt;
&lt;li&gt;Github Workflow yaml 수정&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;b&gt;AWS에 OIDC Provider 추가&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 콘솔에서 IAM &amp;gt; Identity Providers &amp;gt; Add provider 로 접근해 OpenID Connect 를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2310&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H7TDs/dJMcah4tetA/4VD44lZDj6TDz4a1Muzf10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H7TDs/dJMcah4tetA/4VD44lZDj6TDz4a1Muzf10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H7TDs/dJMcah4tetA/4VD44lZDj6TDz4a1Muzf10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH7TDs%2FdJMcah4tetA%2F4VD44lZDj6TDz4a1Muzf10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2310&quot; height=&quot;974&quot; data-origin-width=&quot;2310&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;Provider URL 은 `https://token.actions.githubusercontent.com`, Audience 는 sts.amazonaws.com 로 설정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GitHub Actions용 IAM 정책과 역할 생성 (OIDC)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM &amp;gt; Role &amp;gt; Create Role 로 이전에 만들었던 oidc-provider 를 사용하여 역할을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Custom trust policy` 를선택해서 Github 에서 제공한 예시를 붙여넣으면 틀릴 위험이 적고 편합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2814&quot; data-origin-height=&quot;1786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M2B5S/dJMcadVfO54/55qdMioLbWjTfl5H7UrAU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M2B5S/dJMcadVfO54/55qdMioLbWjTfl5H7UrAU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M2B5S/dJMcadVfO54/55qdMioLbWjTfl5H7UrAU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM2B5S%2FdJMcadVfO54%2F55qdMioLbWjTfl5H7UrAU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2814&quot; height=&quot;1786&quot; data-origin-width=&quot;2814&quot; data-origin-height=&quot;1786&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;pre id=&quot;code_1771482403285&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;Principal&quot;: { // 아까 생성한 OIDC 제공자 arn 을 추가해줍니다.
                &quot;Federated&quot;: &quot;arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com&quot;
            },
            &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
            &quot;Condition&quot;: {
                &quot;StringLike&quot;: { // repo:{org}/{repo}:{branch}
                    &quot;token.actions.githubusercontent.com:sub&quot;: &quot;repo:octo-org/octo-repo:*&quot;
                },
                &quot;StringEquals&quot;: {
                    &quot;token.actions.githubusercontent.com:aud&quot;: &quot;sts.amazonaws.com&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;권한 정책은 새로운 IAM Policy 를 생성하여 추가해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 생성한 IAM Role 에 Permissions 탭에서 추가할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQEK61/dJMcaaYy2QX/Qc2d1F51Bib6KOYkghP970/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQEK61/dJMcaaYy2QX/Qc2d1F51Bib6KOYkghP970/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQEK61/dJMcaaYy2QX/Qc2d1F51Bib6KOYkghP970/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQEK61%2FdJMcaaYy2QX%2FQc2d1F51Bib6KOYkghP970%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2264&quot; height=&quot;418&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 생성한 권한 정책은 다음과 같이 구성하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771482560085&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;Sid&quot;: &quot;Statement1&quot;,
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Action&quot;: [
				&quot;ssm:SendCommand&quot;, // 명령을 EC2에 보냄
				&quot;ssm:GetCommandInvocation&quot; // 그 명령이 성공, 실패/로그 조회
			],
			&quot;Resource&quot;: [
				&quot;*&quot; // 모든 리소스에 대해 (EC2에만 한정할수있지만, 추후 S3 등에 접근하기 위해 열어두었습니다.
			]
		}
	]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 1.44em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;GitHub Secrets 업데이트&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 1.44em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;OIDC 방식이므로 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY는 불필요해서 삭제하였습니다. 역할 ARN 중 계정 ID부분만 secret 처리, 리전은 워크플로우에 직접 기입하였습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;height: 87px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;b&gt;기존&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;b&gt;신규&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;SSH_HOST&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;ACCOUNT_ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;code&gt;SSH_USER&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;INSTANCE_ID&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;code&gt;SSH_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;code&gt;SSH_PORT&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;yaml 파일 변경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 인증 방식에서 OIDC&lt;/p&gt;
&lt;pre id=&quot;code_1771483228290&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  deploy:
    needs: [verify, build]
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::{{ID}}:role/hearoom-deployer-role // 위에서 생성한 IAM Role arn 추가
          aws-region: {{region}}

      - name: Deploy via SSM
        run: |
          COMMAND_ID=$(aws ssm send-command \
            --instance-ids &quot;${{ secrets.INSTANCE_ID }}&quot; \
            --document-name &quot;AWS-RunShellScript&quot; \
            ## 실행하고싶은 커맨드 전달
            --parameters 'commands=[&quot;{deploy.sh 실행}&quot;]' \
            --region ap-northeast-2 \
            --query &quot;Command.CommandId&quot; \
            --output text)&lt;/code&gt;&lt;/pre&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트러블슈팅&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;appleboy/ssh-action에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;i/o timeout&lt;span&gt;&amp;nbsp;&lt;/span&gt;에러 발생&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1771484231285&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dial tcp 3.35.123.45:22: i/o timeout&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;: AWS EC2 SSH 접속시 네트워크 레벨에서 접근이 불가능할 때 (보안그룹 설정이 잘못되는 등) 나타난 오류.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결&lt;/b&gt;: EC2 보안그룹에 Runner IP 를 추가함으로 OIDC로 설정한 이후 발생하지 않음.&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;b&gt;&lt;code&gt;peterkimzz/aws-ssm-send-command@v1&lt;/code&gt; 임시&amp;nbsp;자격증명(AWS_SESSION_TOKEN)&amp;nbsp;미지원&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;UnrecognizedClientException: The security token included in the request is invalid.&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;: 액션 내부에서 sessionToken을 AWS SDK에 전달하지 않음. OIDC 임시 토큰은 accessKeyId + secretAccessKey + sessionToken 3개가 필요한데 2개만 전달. [관련 이슈: &lt;a href=&quot;https://github.com/peterkimzz/aws-ssm-send-command/issues/28&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/peterkimzz/aws-ssm-send-command/issues/28&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;u&gt; AWS CLI 직접 호출로 교체&lt;/u&gt;. AWS CLI는 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;configure-aws-credentials&lt;/code&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;/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;b&gt;git &lt;code&gt;dubious ownership&lt;/code&gt; 에러&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;fatal: detected dubious ownership in repository at '/home/ubuntu/hearoom'
To add an exception for this directory, call:
    git config --global --add safe.directory /home/ubuntu/hearoom
failed to run commands: exit status 128&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSM Run Command는 기본적으로 root 사용자로 실행함.&lt;/li&gt;
&lt;li&gt;/home/ubuntu/hearoom 레포는 ubuntu 사용자 소유임. (여기에&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Git 2.35+ 부터는 소유자가 다른 디렉토리에서 git 명령 실행을 차단&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의&lt;/b&gt;:&amp;nbsp;git&amp;nbsp;config&amp;nbsp;--global&amp;nbsp;...을&amp;nbsp;ubuntu&amp;nbsp;유저로&amp;nbsp;실행하면&amp;nbsp;~ubuntu/.gitconfig에만&amp;nbsp;반영된다.&amp;nbsp;SSM은&amp;nbsp;root로&amp;nbsp;실행되므로&amp;nbsp;반드시&amp;nbsp;sudo를&amp;nbsp;붙여&amp;nbsp;/root/.gitconfig에&amp;nbsp;추가해야&amp;nbsp;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; EC2에서 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;root&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;의 git config에 추가하거나, `ubuntu` 유저로 커맨드 실행.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// git config 추가
sudo git config --global --add safe.directory /home/ubuntu/hearoom

// ubuntu 유저로 실행
sudo -u ubuntu /home/ubuntu/hearoom/scripts/deploy.sh&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배포 경로 오류&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;cd: can't cd to /hearoom&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;: SSM은 root로 실행되어 ~가 /root로 해석된다. `~/hearoom` 이라면 경로을 찾을 수 없음. 상대 경로/홈 경로 사용 불가.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 절대 경로로 사용 (&lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;/home/ubuntu/hearoom&lt;/code&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;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/systems-manager-setting-up.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/systems-manager/latest/userguide/systems-manager-setting-up.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://musma.github.io/2019/11/29/about-aws-ssm.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://musma.github.io/2019/11/29/about-aws-ssm.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.outsider.ne.kr/1750&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.outsider.ne.kr/1750&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws#adding-the-identity-provider-to-aws&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws#adding-the-identity-provider-to-aws&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>☁️ Infra</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/139</guid>
      <comments>https://new-pow.tistory.com/139#entry139comment</comments>
      <pubDate>Sat, 14 Feb 2026 09:41:33 +0900</pubDate>
    </item>
    <item>
      <title>AWS 새로운 프리티어 정책 (25.7.15 ~)</title>
      <link>https://new-pow.tistory.com/138</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 사이드 프로젝트를 다시 시작하며, vercel, Firebase 등 다양한 배포 도구들을 알아보던 중.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS 프리티어 정책이 2025년 7월 15일부터 변경되었다&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FPJRJ/dJMcahJ6AkV/q9bYrBmPffiQjONFUm1SKK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FPJRJ/dJMcahJ6AkV/q9bYrBmPffiQjONFUm1SKK/img.jpg&quot; data-alt=&quot;오히려 좋다 :)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FPJRJ/dJMcahJ6AkV/q9bYrBmPffiQjONFUm1SKK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFPJRJ%2FdJMcahJ6AkV%2Fq9bYrBmPffiQjONFUm1SKK%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;400&quot; height=&quot;400&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;736&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;사이드 프로젝트를 진행했던 경험을 돌이켜볼때, 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;span style=&quot;text-align: start;&quot;&gt;&lt;b&gt;2025년 7월 15일부터 생성되는 계정에 한해서 적용&lt;/b&gt;됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ust6V/dJMcacPyiJN/9KATlg2oA7pE3nKaGRZ5o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ust6V/dJMcacPyiJN/9KATlg2oA7pE3nKaGRZ5o0/img.png&quot; data-alt=&quot;회원 가입시 plan 을 미리 정할 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ust6V/dJMcacPyiJN/9KATlg2oA7pE3nKaGRZ5o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fust6V%2FdJMcacPyiJN%2F9KATlg2oA7pE3nKaGRZ5o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1990&quot; height=&quot;1382&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1382&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;회원 가입시 plan 을 미리 정할 수 있습니다.&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;b&gt;현금형 크레딧을 최대 $200 제공합니다.&lt;br /&gt;다 써도 추가로 내는 돈은 없어요.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 프리티어는 AWS가 제공하는 서비스마다 형태가 달랐습니다. EC2의 경우, 특정 모델에 대해 750시간 무료를 제공하는 형태였고, (아래 참고) S3의 경우 GET 요청과 다른 요청의 상한이 달랐습니다. 이를 각각 서비스마다 기억하고 관리하기가 여간 힘든 게 아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770555927886&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;750 hours per month of Linux, RHEL, or SLES t2.micro or t3.micro instance dependent on region
750 hours per month of Windows t2.micro or t3.micro instance dependent on region
750 hours per month of public IPv4 address regardless of instance type&lt;/code&gt;&lt;/pre&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;이제는 최대 $200 크래딧 내에서 자유롭게 사용이 가능합니다. 첫 가입을 하면 $100 크래딧이 주어지고 서비스를 구축하는 연습을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서는 각 서비스의 가격 정책이 상세히 나와있습니다. 추후, 유료 서비스 사용시에 예산 안에서 서비스를 조합하는 연습이 되기도 할 것 같습니다. &lt;a href=&quot;https://aws.amazon.com/free/webapps/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[freetier 제공 서비스 참조]&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 만약 &lt;b&gt;이 크래딧을 다 썼더라도 계정이 잠길 뿐, 이전처럼 요금 폭탄을 맞지는 않습니다.&lt;/b&gt; 이 추가 요금이 없는 기간은 6개월동안으로, 가입후 크래딧을 다 사용한 상태라면 계정잠김 상태로 변경됩니다. 이 상태에서는 리소스에 접근할 수가 없는데 (S3 파일 등) 유료 플랜(Paid Plan)으로 변경하게 되면 요금을 지불하면서 리소스 접근이 가능합니다.&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;b&gt;활동 기반 크레딧 획득할 수 있습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 가입 직후, $100 크래딧을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 재미있게도 AWS에서 활동하는 바에 따라 추가 크래딧을 얻을 수 있습니다. &lt;a href=&quot;https://aws.amazon.com/ko/blogs/korea/aws-free-tier-update-new-customers-can-get-started-and-explore-aws-with-up-to-200-in-credits/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[참고 link]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/osy8U/dJMcaaYt6Ak/tv6BX7kUl7ILM8yTMQBIY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/osy8U/dJMcaaYt6Ak/tv6BX7kUl7ILM8yTMQBIY0/img.png&quot; data-alt=&quot;EC2를 생성했더니 $20 크래딧을 얻었다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/osy8U/dJMcaaYt6Ak/tv6BX7kUl7ILM8yTMQBIY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fosy8U%2FdJMcaaYt6Ak%2Ftv6BX7kUl7ILM8yTMQBIY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;321&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;EC2를 생성했더니 $20 크래딧을 얻었다!&lt;/figcaption&gt;
&lt;/figure&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;b&gt;Amazon EC2&lt;/b&gt; &amp;ndash; EC2 인스턴스를 시작하고 종료하는 방법을 배웁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Amazon RDS&lt;/b&gt; &amp;ndash; RDS 데이터베이스를 시작하기 위한 기본 구성 옵션을 배웁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS Lambda&lt;/b&gt; &amp;ndash; 함수 URL이 있는 Lambda 함수로 구성된 간단한 웹 애플리케이션을 구축하는 방법을 배웁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Amazon Bedrock&lt;/b&gt; &amp;ndash; Amazon Bedrock 텍스트 플레이그라운드에서 프롬프트를 제출하여 답변을 생성하는 방법을 배웁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS Budgets&lt;/b&gt; &amp;ndash; 예산 금액을 초과할 경우 알림을 보내도록 예산을 설정하는 방법을 배웁니다.&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;&lt;b&gt;알아두어야 할 사항&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프리 플랜 크레딧이 적용되지 않는 서비스들&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 서비스들은 프리 플랜에서 제공되는 크레딧으로 사용할 수 없으므로 아키텍처 설계시 제외합니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프리 플랜 계정이라고 해서 모든 서비스를 크래딧으로 사용할 수 없고, 프리티어에만 제공되는 30여개의 서비스를 프리티어로 사용할 수 있습니다. &lt;a href=&quot;https://aws.amazon.com/free/webapps/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[참고 link]&lt;/a&gt;&lt;/span&gt;&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;Amazon&amp;nbsp;EC2&lt;/li&gt;
&lt;li&gt;Amazon&amp;nbsp;CloudFront&lt;/li&gt;
&lt;li&gt;Amazon&amp;nbsp;Lightsail&lt;/li&gt;
&lt;li&gt;AWS&amp;nbsp;Lambda&lt;/li&gt;
&lt;li&gt;Elastic&amp;nbsp;Load&amp;nbsp;Balancing&lt;/li&gt;
&lt;li&gt;Amazon&amp;nbsp;S3&lt;/li&gt;
&lt;li&gt;AWS&amp;nbsp;Amplify&amp;nbsp;Hosting&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외의 다른 카테고리의 서비스도 있으므로 제작할 애플리케이션에 따라 적절하게 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프리플랜은 6개월, 크레딧은 12개월 만료&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;프리 플랜: 6개월 유지&lt;/li&gt;
&lt;li&gt;지급받은 크레딧 유효기간: 12개월&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 6개월이 지나 유료 플랜으로 전환되더라도 가입 후 1년이 될 때까지는 남은 크레딧을 계속 사용할 수 있습니다. 크래딧을 최대한 아껴서 길게 사용할 수도 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리플랜은 6개월이 지나거나, 6개월 전에 크래딧을 모두 소진하면 종료되므로 주의합니다.&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;프리플랜 종료 후, 90일 유예 기간&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정이 잠기면 90일 동안 리소스가 보존됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, S3의 객체일 경우 다운로드 불가합니다. (콘솔 접근은 가능하지만 실질적인 데이터 접근 불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 다시 보려면 유료 플랜 전환이 필요하며,&amp;nbsp; 유예기간인 90일이 지나면 모든 리소스는 영구 삭제됩니다. 만약 유료 플랜을 사용하지 않을 예정이지만, 데이터를 이전하거나 보존하고 싶다면 백업이 필수인 셈입니다. &lt;a href=&quot;https://aws.amazon.com/free/terms/?p=ft&amp;amp;z=subnav&amp;amp;loc=6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[참고 link]&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프리 플랜이 바로 끝나는 경우&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Organizations 가입하면 프리플랜이 바로 종료되게 됩니다. 약관에 있으니 주의하세요!&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;&lt;b&gt;다들 즐거운 사이드 프로젝트!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 MVP를 빠르게 개발하고 배포하는 트랜드로는 사실 AWS를 사용하기 보다는 더 가벼운 인프라를 사용하는 추세여서 저도 AWS를 사용할까 고민중에 있는 상황이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그와중에 이런 AWS 프리티어의 변화를 확인했는데, 이런 개편이 개발자들의 마음을 한결 가볍게 만들어 클라우드 환경으로의 진입을 쉽게 만들 수 있지 않을까 싶습니다. 이제 갑자기 내게 떨어진 AWS Billing   은 역사 속으로 사라지게 되겠네요....  &lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;참고&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/free/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/free/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://library.gabia.com/aws/14099/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;가비아 라이브러리: https://library.gabia.com/aws/14099/&lt;/a&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>☁️ Infra</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/138</guid>
      <comments>https://new-pow.tistory.com/138#entry138comment</comments>
      <pubDate>Sun, 8 Feb 2026 22:18:57 +0900</pubDate>
    </item>
    <item>
      <title>2년 꽉 채운 주니어 백엔드 개발자의 작은 회고</title>
      <link>https://new-pow.tistory.com/137</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 글 슬럼프에 빠져있습니다. 뭘 쓰든 진솔되어 보이지 않는 요 요상한 병...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일을 잘 하기 위해서는 주제를 쪼개듯, 올해는 조금 더 작은 주제로 가볍게 써보기를 다짐했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 지금 이 시점의 생각들을 두서없이 정리해두겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발을 잘함에 대하여&lt;/b&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;1년이 지나고 개발자로서 회사 일이 얼추 손에 익자 가장 자주 떠올렸던 질문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일을 잘 하고 싶은 욕심이 계속 있어서 하나하나 시도하다가도 개발자의 본업은 결국 '개발'입니다. 회사 일이 손에 익고 작업 일정 산정과 기능 구현을 하면서도 내가 개발을 잘 하고 있는가? 하고 자문해보면 괜히 움츠러들곤 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 작년 한해동안 주변에서 이 사람은 개발을 잘해- 라고 평 받았던 사람들을 관찰해보기도 하고, 나는 어떤 개발을 하고 싶은가 고민해왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 저는 &quot;개발을 잘함&quot;을 요렇게 정의하고 있습니다.&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;기술의 임계점을 잘 찾는 것.&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;도입하는 인프라, 코딩이 소화할 수 있는 임계점을 잘 측정하고 오버엔지니어링하지 않음.&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;새로운 것을 잘 흡수할 수 있고, 관심있는 것에 집요함이 있는 것.&lt;/b&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;올해 배운 것 중에 인상깊었던 것은 &quot;내가 할 수 있는 것에서 할 수 있는 일을 하자&quot; 라는 방식.&lt;/li&gt;
&lt;li&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AI시대 개발자로서 살아남기&lt;/b&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;b&gt;새로운 언어를 익히는 순간&lt;/b&gt;이 된 것이라는 생각이 듭니다. 고급 언어로 코딩을 하던 때에서 LLM에게 자연어로 커맨드한 코딩으로의 전환인 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 새로운 기술을 습득하고 시도해보는 것에 적극적인 편이라고 자평하고 있었지만, 하루아침마다 쏟아지는 업데이트들을 모두 팔로업하기란 흥미롭지만 참으로 힘든 일입니다. 지금은 다른 개발자들 사이에서 자주 언급되는 툴을 위주로 테스트해보고 있습니다. (지금은 Claude Code, Codex 를 중심으로 사용중)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코드 에이전트와 작업하며 얻은 노하우는 다양하지만, 가장 큰 것은 아무래도 &lt;b&gt;문서화나 테스트 구성을 잘 해가면서 작업&lt;/b&gt;하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 통해 병행으로 작업하는 것은 한계가 있습니다. 아무래도 사람이다보니 너무 폭이 넓은 병행 작업은 오히려 컨텍스트 스위칭에 부하가 걸립니다. 그걸 보완하는 방법이 테스트나 문서를 먼저 작성하여 AI의 컨텍스트도 이어주고 작업자인 저의 컨텍스트도 이어주는 것이 매우 중요했습니다. 프롬프팅을 잘 하는 것보다는 한 가지 작업에 대한 문서를 계속 작성하고 갱신해가며 작업하는 것이 저에겐 더 효율적인 방식이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &lt;b&gt;AI 가 작성한 코드를 판단하는 것은 작업자&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;아쉬웠던 점은, 코드 외의 루틴한 일을 AI 로 개선해보는 작업을 못한 것&lt;/b&gt;입니다. 이미 제가 스스로 구축해놓은 루틴한 업무의 가장 효율적인 길을 일종의 마이그레이션해야하는데, 이 마이그레이션 후에는 테스트를 해보고 사내 공유를 통해 방법도 공유해야하는 작업이 뒤따릅니다. 이런 실험의 여유가 없었던 점이 가장 아쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 AI 툴들을 잘 오케스트레이션 하는 것이 개발 생산성에 큰 영향을 미치는 시기가 된 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새해에는 가벼운 개인 프로젝트나 개인 생활 보조 용도의 툴을 AI를 활용해서 배포해보고 싶은 포부를 밝히며 요 단락은 여기서 마무리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스타트업 백엔드 개발자로서의 회고&lt;/b&gt;&lt;/h2&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;&lt;b&gt;견고한 설계와 투여 시간과의 균형을 잡는 것이 매우 중요&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 하반기, 팀에 격변이 생기며 거의 1인으로서 기능을 구현하고 CS 를 하면서 부각된 중요성입니다. 설계는 얼마든지(?) 견고하게 할 수 있습니다. 다만 시간 내에 내가 소화할 수 있는가가 매우 중요한 지점이었어요. 스케줄러를 구현할 때, 당연히 스케줄링의 상태 관리와 로깅, 에러 알림, 보상로직 등 해야할 것들은 쏟아지지만 절대적인 시간이 부족한 경우가 많았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실한 수익을 내는 비즈니스를 찾기 전까지, 늘 &lt;b&gt;사업의 방향은 변경&lt;/b&gt;됩니다. 그것도 아주 빠른 주기로.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 사업의 방향 뿐만 아니라 일하는 방식까지 이어집니다. 빠른 도입과 검증의 반복이 매우 중요한 시기입니다. 이게 당연히 스트레스를 병행합니다만, 저는 이렇게 일하는 것이 더 흥미롭기도 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉬운 점이라면 이미 구현한 기능을 고도화 하는 일이 별로 없다는 점입니다. 아마도 확실한 수익을 내는 것이 검증된 기능이라면 충분히 고도화할 가치가 있다고 판단되었겠지요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 &lt;b&gt;사람이 매우 중요&lt;/b&gt;합니다. 1,000명이 일하는 회사라면 한 사람의 영향력은 1/1,000 정도 수준에서 약간의 가점이겠지만. 10명이 일하는 회사라면 한 사람의 영향력은 1/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;&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;b&gt;聰明不如鈍筆(총명불여둔필)&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;2026년은 특히 회고나 단상같은 것을 주로 기록해 보려고하는데요. AI로 많은 것을 해결할 수 있게 된 지금 가장 중요한 것은 직접 내 이야기를 기록해두는 것이라는 생각이 자주 듭니다. 특히나 블로그라는 공간을 조금 더 사람의 냄새(?)를 풍길 수 있도록 하는 것이 매우 중요하다는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각과 말로 하는 회고는 한계가 있습니다. 늘 시간에 마모되더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 그때 적어두는 것이 당시를 기억하기에 효과적입니다. 그리고 작은 기록들이 쌓여서 결국 큰 기록이 되고, 나를 드러내는 가장 큰 레퍼런스가 될 것임을 믿고 있습니다.&lt;/p&gt;
&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;작년 한해는 글 한 줄, 말 한마디가 매우 힘겹게 느껴졌던 2025년이었습니다.&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;style1&quot; /&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;다들 새해 복 많이 받으세요!&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/137</guid>
      <comments>https://new-pow.tistory.com/137#entry137comment</comments>
      <pubDate>Mon, 12 Jan 2026 21:00:23 +0900</pubDate>
    </item>
    <item>
      <title>2025년 연말정산 : 올해의 X</title>
      <link>https://new-pow.tistory.com/136</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;매년 오랜 친구와 매년 함께 하는 연말정산을 블로그에도 써봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년도 잘 지었습니다.&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;768&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq0Do6/dJMcagRJGYd/GTXcMHOwSyEJ3tXJhr6I3k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq0Do6/dJMcagRJGYd/GTXcMHOwSyEJ3tXJhr6I3k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq0Do6/dJMcagRJGYd/GTXcMHOwSyEJ3tXJhr6I3k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq0Do6%2FdJMcagRJGYd%2FGTXcMHOwSyEJ3tXJhr6I3k%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;400&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;올해의 X&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙1. 최대 3개만 정하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙2. 올해의 나를 기준으로 뽑기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해의 사회사건&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;u&gt;&lt;b&gt;6월 3일 장미대선&lt;/b&gt;&lt;/u&gt;: 이 사건에 영향을 받지 않은 한국인이 있을까?&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;AI 코딩툴 매달 쏟아지는 업데이트&lt;/b&gt;&lt;/u&gt;: 올해 나에게 단연 큰 &quot;사건&quot;이라고 한다면 역시나 AI 활용도의 성장을 꼽아야하지 않을까? 현재는 Claude Code, Codex, Chat GPT, Antigravity 를 중점적으로 쓰고있다. 이렇게 정착(?)하기까지 Claude Desktop 과 MCP, Cursor 등 참으로 여러 툴들을 거쳐보았다. 이러한 거대한 변화기가 스마트폰이 일상화되는 것과 비슷한 일종의 과도기로 느껴진다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;국가정보자원관리원 화재 사고&lt;/b&gt;&lt;/u&gt;: 사용하고 있던 온갖 서비스 인증이 불가능했던 개인적인 경험이 있었음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해의 시리즈&lt;/b&gt;&lt;/h3&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;u&gt;&lt;b&gt;삼체&lt;/b&gt;&lt;/u&gt;: 이제야 봤습니다. 이거 정말 재밌습니다. 유튜브 리뷰까지 엄청 독파했던 시리즈.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;선덕여왕&lt;/b&gt;&lt;/u&gt;: 생애 처음으로 경주로 여행을 간 것을 계기로 정주행하게된 드라마. 2009년 드라마라 지금보면 어색하고 튀는 연출이나 연기 스타일이라 처음에는 생소했지만, 캐릭터의 매력과 어디선가 들은 것 같은 역사적 지식이 버무려져 매우 몰입해서 정주행 했다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;신인감독 김연경&lt;/b&gt;&lt;/u&gt;: 여자 배구 너무 재밌습니다  시즌2도 주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해의 영화&lt;/b&gt;&lt;/h3&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;u&gt;&lt;b&gt;원배틀애프터어나더&lt;/b&gt;&lt;/u&gt;: 올해의 영화를 줄 세워보자면 가장 좋았던 영화&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;위키드: 포 굿&lt;/b&gt;&lt;/u&gt;: 작년 위키드를 처음 접했을 때 (뮤지컬안봄) 글린다가 왜 매력적일까 고민했었는데, 이번 편을 보고 모든 게 이해되었음. 두 여성 캐릭터의 성장서사 중에서도 결국 세상을 바꾼 게 성장한 글린다여서 좋았다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;아바타&lt;/b&gt;&lt;/u&gt;: 첫 번째 시리즈. 전 세계가 들썩들썩할 때 안 보고 지금 뒤늦게 봤는데, 그때 당시 왜 센세이션 했을지 이해가 되는 영상 콘텐츠였다. 비주얼이 참 인상적. 2, 3의 경우 스토리면에서는 좀 아쉽다고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해의 도서&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000;&quot; 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;개발 도서는 회사 스택에서 바로 사용할 수 있는 &quot;실용서&quot;를 위주로 읽었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;스프링으로 시작하는 리액티브 프로그래밍&lt;/b&gt;&lt;/u&gt;: Webflux를 사용하면서 관련된 이해가 별로 없다는 생각에 초반에 읽기 시작한 책. 리액티브 프로그래밍의 기본 개념을 습득하는 데에 매우 도움을 받았다. 특히 Reactor에 대한 설명이 재미있었던 기억이 남. 이후 코루틴할 때도 드는 것이지만 비동기로 프로그래밍을 한다는 것은, 흐름에 대한 상상력이 중요하다는 생각을 했었다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;이펙티브 소프트웨어 테스팅&lt;/b&gt;&lt;/u&gt;: 테스트에 대한 단위(통합테스트, 단위테스트 등 테스트 범위에 따른 단위)와 올바른 테스트에 대해 생각해 볼 수 있었던 책. 이때 당시는 AI를 테스트에 적극활용하고 있지 않을 때여서 실제 테스트 예제를 따라 해보곤 해서 도움이 되었다. 지금은 AI에게 테스트 대다수 작성을 시킴에도 불구하고 테스트의 명세를 정의하고, 필요한 테스트를 가늠하는 기준이 되었던 책이다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;코틀린 코루틴&lt;/b&gt;&lt;/u&gt;: 마찬가지로 코틀린 코루틴을 사용하고 있지만, 제대로 이해하고 있는 바가 없다는 생각이 들어서 오래간만에 스터디를 하며 읽은 책. 1달 동안 빠르게 독파했지만, 실무에 바로 테스트해 보고 사용해 볼 수 있어서 뿌듯했다. 특히 Fire&amp;amp;Forget 스타일로 로직을 짜보거나 예외 전파 제어 하며 로직을 개선했던 것. 구조화된 동시성을 활용해 코드를 간략하게 리팩터링 했던 경험이 기억에 남는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;기술서 외 책&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;u&gt;&lt;b&gt;먼저 온 미래&lt;/b&gt;&lt;/u&gt;: 긴 휴가기간 동안 가볍게 읽으려고 했는데 생각보다 철학적이라 재밌게 읽었었던 책. AI가 급하게 다가온 개발 생태계에 대한 이해를 높이기 위해 읽었다. 바둑계는 '잘한다'라는 기준이 바뀌었다. 과연 우리는 어떻게 될 것인가? 해답을 얻기보다는 생각 범위를 넓히는데 도움이 되었다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;괴테는 모든 것을 말했다&lt;/b&gt;&lt;/u&gt;: 이동진 평론가님이 추천해서 읽기 시작한 소설. 문장은 문맥 속에 자주 곡해되고 이용된다. 내가 지금 읽고 있는 것조차 정확하다고 할 수 있을까? 하면서 읽었는데, 이 또한 재창조의 영역이겠지요.&lt;/li&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;&lt;u&gt;&lt;b&gt;사랑과 멸종을 바꿔 읽어보십시오&lt;/b&gt;&lt;/u&gt;: 여행지마다 책을 사 오는 습성이 있습니다 ㅎㅎ 경주 갔다가 데려온 시집. 시집을 읽으면 좋은 게 아무 데나 펼쳐 읽으면 되잖아요. 난해한 시도 있지만, 공감 가는 시가 많았습니다. 사랑과 멸종을 왜 닮았다고 생각했을까? 생각하며 제목의 시를 읽었던 기억이 나네요. &quot;멸종해 너를 멸종해&quot;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해의 음악&lt;/b&gt;&lt;/h3&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;u&gt;&lt;b&gt;하기장기하 (장기하/앨범)&lt;/b&gt;&lt;/u&gt;: 콘서트 라이브 앨범. 노동요로 딱이다. 통으로 들어줘야 함.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;BIG VOID (실리카겔)&lt;/b&gt;&lt;/u&gt;: 실리카겔 콘서트 갔다가 발매를 손꼽아 기다리게 된 음악. 올해의 캐럴  &lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;안녕 나의 사랑 (성시경)&lt;/b&gt;&lt;/u&gt;: 명곡은 많은 이야기를 담을 수 있다는 것을 새삼 깨달음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해의 소프트웨어&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;u&gt;&lt;b&gt;tickTick&lt;/b&gt;&lt;/u&gt;: 생산성 관련 애플리케이션 유명한 것은 거의 써보았지만, 가장 오래 사용하고 있는 애플리케이션. 기본적인 To do 관리 외에도 아이젠하우어 스타일로 레이아웃을 변경하는 것이나, 포모도로/스톱워치로 실제 진행한 일을 측정할 수 있는 등 다양한 기능들을 활용하고 있어서 유료 구독 중.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;Claude Code&lt;/b&gt;&lt;/u&gt;: 저도 Max 구독해보고 싶어요.... 엄청 유용하게 사용 중!&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;b&gt;올해의 도전&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;u&gt;&lt;b&gt;방송통신대학교 컴퓨터공학과 편입&lt;/b&gt;&lt;/u&gt;: 작년 1년 내내 고민했던 방송통신대학교 입학을 드디어 했다. 일도 못하고 학업도 못하고 다른 것도 못하면 어쩌지 했는데.. 결국 이 도전으로 갉아먹은 것은 나의 건강 이 아니었을까? 학점 평점은 3.4로 비교적 양호하지만, 이것은 A+ 과 C로 이루어진 극단적인 학습의 결과입니다. 다음 해는 반드시 건강을 챙기면서 할 수 있는 만큼 학업을 병행하리라.. (일단 상반기 학기는 쉬기로 함)&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;글또 10기&lt;/b&gt;&lt;/u&gt;: 개발자가 되기 전부터 &quot;이 커뮤니티는 반드시 해본다&quot;라고 생각했던 커뮤니티. 10기가 마지막이라고 해서 부랴부랴 신청하고 되었던 경험이 있습니다. 매주 글쓰기에 성공하지는 못했지만, 지금도 스터디를 할 때 소문을 퍼뜨리는 커뮤니티이자 인재풀. 글또&amp;nbsp;커뮤니티 사람들은 꽤 괜찮다(?)는 편견을 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;홀로서기 시작&lt;/b&gt;&lt;/u&gt;: 3월부터 시작한 자취. 지금도 잘 살고 있습니다   그리고 팀에서도 홀로 서보기로 결정한 것. 고생은 많이 했지만 후회하지 않습니다.&lt;/li&gt;
&lt;/ul&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;일단 요기까지!&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/136</guid>
      <comments>https://new-pow.tistory.com/136#entry136comment</comments>
      <pubDate>Thu, 8 Jan 2026 17:37:55 +0900</pubDate>
    </item>
    <item>
      <title>이펙티브 소프트웨어 테스팅 정리하기</title>
      <link>https://new-pow.tistory.com/134</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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;1424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMnTp6/btsNAb1sDqk/zCazdiGjay5GjCmFkCAAm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMnTp6/btsNAb1sDqk/zCazdiGjay5GjCmFkCAAm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMnTp6/btsNAb1sDqk/zCazdiGjay5GjCmFkCAAm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMnTp6%2FbtsNAb1sDqk%2FzCazdiGjay5GjCmFkCAAm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;785&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;1424&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달동안 이 책을 사내 스터디에서 야금야금 읽었습니다.&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-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 효율적이고 체계적인 소프트웨어 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;효율적인 소프트웨어 테스트란?&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;예시 기반 테스트 &amp;ndash; 테스트를 위해 한 가지 데이터 포인트 작성&lt;/li&gt;
&lt;li&gt;특이한 경우에는 속성 기반 테스트를 사용해서 코드에서 발생할 수 있는 버그 발견&lt;/li&gt;
&lt;li&gt;계약과 스스로 고안한 방법의 사전, 사후 조건&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.1. 개발 과정에서의 효율적인 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기능 개발은 종종 개발자가 어떤 종류의 요구사항을 받는 것으로 시작된다. UML 유스케이스나 애자일 유저 스토리 같은 포맷에 따라 요구사항 분석 후 코드 작성을 시작한다.&lt;/li&gt;
&lt;li&gt;기능 개발을 유도하기 위해 개발자는 짧은 테스트 주도 개발(TDD) 과정을 반복한다.&lt;/li&gt;
&lt;li&gt;요구사항이 매우 크고 복잡할 경우, 여러 단위(클래스, 메서드)를 만들게 되고, 각 단위는 다른 계약(Contract) 을 가지며 서로 어울려 전체 기능을 구성한다. 클래스를 테스트하기 쉽게 만드는 일은 어렵지만, 개발자는 항상 테스트 가능성(testability) 을 염두에 두고 설계해야 한다.&lt;/li&gt;
&lt;li&gt;개발자가 자신이 만든 단위에 만족하고 요구사항을 충족한다고 생각하면 테스트를 작성한다. 새로 만든 단위를 대상으로 도메인 테스트, 경계 테스트, 구조적 테스트를 수행한다.&lt;/li&gt;
&lt;li&gt;시스템 수준에서는 대규모 테스트(통합/시스템 테스트) 가 필요하다.&lt;/li&gt;
&lt;li&gt;다양한 기법으로 테스트 케이스를 만든 후에는 자동화된 지능형 테스트 도구(테스트 케이스 생성, 돌연변이 테스트, 정적 분석 등)를 사용하여 사람이 도출하지 못한 케이스를 찾아낸다.&lt;/li&gt;
&lt;li&gt;테스트를 마친 개발자는 편안한 마음으로 기능을 배포한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.2. 반복 프로세스로서의 효율적 테스트&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.3. 개발에 먼저 집중하고 나서 테스트하기&lt;/b&gt;&lt;/h2&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;&quot;개발 먼저, 테스트 나중&quot; 접근법에서도 테스트는 여전히 중요한 역할을 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.4. '제대로 된 설계'에 대한 미신&lt;/b&gt;&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.5. 테스트 비용&lt;/b&gt;&lt;/h2&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;br /&gt;언제 멈추고, 어디까지 해야 할지에 대한 균형 감각이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.6. 효율적이면서 체계적이라는 것의 의미&lt;/b&gt;&lt;/h2&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;li&gt;체계적이라는 말은, 어떤 코드 조각을 테스트할 때 누가 테스트하더라도 동일한 테스트 스위트를 만들 수 있도록 표준화된 프로세스를 갖추는 것을 의미한다.&lt;br /&gt;즉, 감에 의존하지 않는 테스트가 되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소프트웨어 테스트 원칙&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템을 잘 테스트하고 싶다면 계속해서 더 많은 테스트를 추가해야 한다고 생각할 수 있지만, 현실은 그렇지 않다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;완벽한 테스트는 불가능하다
&lt;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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;맥락(Context)이 핵심이다
&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;/li&gt;
&lt;li&gt;검증은 유효성 검사가 아니다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검증(Verification)은 시스템이 &quot;제대로 만들어졌는가&quot;,&lt;br /&gt;유효성 검사(Validation)는 &quot;올바른 시스템을 만들고 있는가&quot; 에 대한 질문이다.&lt;br /&gt;둘 다 필요하다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 명세 기반 테스트&lt;/b&gt;&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.1 요구사항이 모든 걸 말한다.&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;예제 1. substringsBetween
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;str 에 대한 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null 일경우 null 을 반환해야한다.&lt;/li&gt;
&lt;li&gt;문자열이 1글자일 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;open 만 존재하는 경우&lt;/li&gt;
&lt;li&gt;close 만 존재하는 경우&lt;/li&gt;
&lt;li&gt;둘 다 존재하지 않는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문자열이 2글자일 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;open 만 존재하는 경우&lt;/li&gt;
&lt;li&gt;close 만 존재하는 경우&lt;/li&gt;
&lt;li&gt;둘 다 존재하지 않는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문자열이 매우 길 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;open
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null 일 경우 null 을 반환해야한다. . . .&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1단계 : 요구사항과 입출력에 대해 이해하기&lt;/li&gt;
&lt;li&gt;2단계 : 여러 입력값에 대해 프로그램이 수행하는 바를 탐색하기&lt;/li&gt;
&lt;li&gt;3단계 : 테스트 가능한 입출력과 구획을 탐색하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;두 입력은 동등하다.&quot;
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4단계 : 경계 분석하기
&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;/li&gt;
&lt;li&gt;5단계 : 테스트 케이스 고안하기
&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;/li&gt;
&lt;li&gt;6단계 : 테스트 케이스 자동화하기
&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;/li&gt;
&lt;li&gt;7단계 : 창의성과 경험을 발휘해서 테스트 스위트를 강화하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명세 테스트 이후의 다음 단계는 구현을 실행하고 테스트 스위트를 보강하는 일. -&amp;gt; 3장에서&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;도메인 지식은 좋은 테스트 케이스를 만들기 위해 중요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.4 현업에서의 명세 테스트&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;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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;관심 없는 입력에 대해 합리적인 값을 선택하자&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;요구사항은 잘게 쪼갤 수 있다&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.구조적 테스트와 코드 커버리지&lt;/b&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;gt; 소스 코드를 활용해 테스트 스위트를 확장한다.&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.2 구조적 테스트 간략히 살펴보기&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;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;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;/li&gt;
&lt;li&gt;다른 흥미로운 테스트 찾아보기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.3 코드 커버리지 기준&lt;/b&gt;&lt;/h2&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;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;각 조건을 true|false 로 만족하도록 하는 ㅔㅌ스트를 적어도 하나 만들 것&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그리고 전체 분기문을 true|false 로 만족하는 테스트를 적어도 하나 만들 것&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;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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡한 조건과 MC/DC 커버리지 기준&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;필요한 테스트 개수가 N+1 이다&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;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;보통은 0, 1, 여러번 으로 나눔&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.6 기준 포함과 선택&lt;/b&gt;&lt;/h2&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;li&gt;&lt;b&gt;탄탄한 기준은 비용을 많이 들여서 더 엄격하게 코드를 수행할 수 있도록 개발함&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.8 경계 테스트와 구조적 테스트&lt;/b&gt;&lt;/h2&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;? TDD 를 하게 되면 구조적 테스트를 못하는가?&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.9 구조적 테스트만 적용하는 것은 충분하지 않다.&lt;/b&gt;&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.10 현업에서의 구조적 테스트&lt;/b&gt;&lt;/h2&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;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;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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&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;&lt;b&gt;얼마아 엄격하고 싶은지, 무엇을 테스트 하는지에 따라 다름.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명세 기반 테스트를 보완하기 위한것이므로 수행하지 않은 부분을 찾을 때 코드 커버리지 적용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분기 커버리지와 조건+분기 커버리지, MC/DC 를 복합적으로 사용하는 것을 선호&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;&lt;b&gt;커버리지 100 달성? 불가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;equal, hash, getter, setter 테스트&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.11 돌연변이 테스트&lt;/b&gt;&lt;/h2&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;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;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;/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;/ul&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;b&gt;파이테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;계약 설계&lt;/b&gt;&lt;/h1&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.1 사전조건과 사후 조건&lt;/b&gt;&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ex. 간단한 if 문으로 유효하지 않은 값이 통과되지 않도록 한다.&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;&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;&lt;b&gt;assert : 작성한바와 같지 않으면 JVM 은 AssertionError 를 던진다.&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;if 대신 사용 가능&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;b&gt;약한 조건과 강한 조건을 쓸지에는 정답은 없다.&lt;/b&gt;&lt;/li&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.2 불변식&lt;/b&gt;&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.3 계약 변경과 리스코프 치환 법칙&lt;/b&gt;&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.4 계약에 의한 설계가 테스트와 어떤 관련이 있는가?&lt;/b&gt;&lt;/h2&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;li&gt;&lt;b&gt;명시적인 계약은 소비자의 삶을 편안하게 해준다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.5 현업에서의 계약에 의한 설계&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;강한 사전조건 vs. 약한 사전조건&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;/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;li&gt;&lt;b&gt;계약 : 클래스간의 의사소통이 문제없이 일어나도록 한다. 계약 위반이 일어난다면 프로그램은 중지한다.&lt;/b&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;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://stackoverflow.com/questions/5049163/when-should-i-use-apache-commons-validate-istrue-and-when-should-i-just-use-th/5452329#5452329&quot;&gt;https://stackoverflow.com/questions/5049163/when-should-i-use-apache-commons-validate-istrue-and-when-should-i-just-use-th/5452329#5452329&lt;/a&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;Validate.isTrue와 'assert'는 완전히 다른 목적을 위해 사용됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;assert&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;Java의 assert 문은 일반적으로 메서드가 어떤 상황에서 호출될 수 있는지, 그리고 호출자가 나중에 무엇이 참일 것으로 기대할 수 있는지를 (assertion을 통해) 문서화하는 데 사용됩니다. assertion은 선택적으로 런타임에 검사할 수 있으며, 유지되지 않으면 AssertionError 예외가 발생합니다.&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;li&gt;&lt;b&gt;Validate.isTrue&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;org.apache.commons.lang.Validate는 다릅니다. 조건을 확인하고 조건이 충족되지 않으면 &quot;IllegalArgumentException&quot;을 throw하는 간단한 JUnit 유사 메서드 세트를 제공합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일반적으로 공개 API가 잘못된 입력에 대해 관대해야 할 때 사용됩니다. 이 경우 계약은 잘못된 입력에 대해 IllegalArgumentException을 throw하도록 약속할 수 있습니다. Apache Validate는 이를 구현하기 위한 편리한 약어를 제공합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IllegalArgumentException이 throw되므로 Apache의 Validate를 사용하여 사후 조건이나 불변식을 확인하는 것은 의미가 없습니다. 마찬가지로, 사용자 입력 검증에 'assert'를 사용하는 것은 잘못된 것입니다. 왜냐하면 어설션 확인은 런타임에 비활성화될 수 있기 때문입니다.&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;&lt;b&gt;하지만, 다른 목적이기는 하지만 동시에 둘 다 사용할 수 있습니다. 이 경우, 계약은 특정 유형의 입력에 대해 IllegalArgumentException이 발생하도록 명시적으로 요구해야 합니다. 그런 다음 Apache Validate를 통해 구현합니다. 그런 다음 불변식과 사후 조건이 간단히 주장되고, 가능한 추가 사전 조건(예: 객체의 상태에 영향을 미침)도 주장됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;d&quot;&gt;&lt;code&gt;public int m(int n) {
  // the class invariant should hold upon entry;
  assert this.invariant() : &quot;The invariant should hold.&quot;;

  // a precondition in terms of design-by-contract
  assert this.isInitialized() : &quot;m can only be invoked after initialization.&quot;;

  // Implement a tolerant contract ensuring reasonable response upon n &amp;lt;= 0:
  // simply raise an illegal argument exception.
  Validate.isTrue(n &amp;gt; 0, &quot;n should be positive&quot;);

  // the actual computation.
  int result = complexMathUnderTrickyCircumstances(n);

  // the postcondition.
  assert result &amp;gt; 0 : &quot;m's result is always greater than 0.&quot;;
  assert this.processingDone() : &quot;processingDone state entered after m.&quot;;
  assert this.invariant() : &quot;Luckily the invariant still holds as well.&quot;;

  return result;
}
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Bertrand Meyer, &quot;계약에 의한 설계 적용&quot;, IEEE Computer, 1992(&amp;nbsp;&lt;a href=&quot;http://se.ethz.ch/~meyer/publications/computer/contract.pdf&quot;&gt;pdf&lt;/a&gt;&amp;nbsp;)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Johsua Bloch.&amp;nbsp;Effective Java&amp;nbsp;, 2nd ed., 항목 38. 유효성을 위한 매개변수 확인. (&amp;nbsp;&lt;a href=&quot;http://books.google.com/books?id=ka2VUBqHiWkC&amp;amp;lpg=PA182&amp;amp;ots=yXKkOgn3R4&amp;amp;dq=assertions%20%22effective%20java%22&amp;amp;pg=PA181#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;구글 도서&lt;/a&gt;&amp;nbsp;)&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;&lt;b&gt;둘 중하나를 사용해야 하는 경우&lt;/b&gt;&lt;/li&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;li&gt;&lt;b&gt;데이터가 이전 레이어에서 정제되었다는 것을 알고 있다면 단언문을 선호, 그러나 정제되어 있는지 모를 경우 예외를 선택한다.&lt;/b&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;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;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;a href=&quot;https://studynote.oopy.io/books/13#49d0df11-3b0a-4432-a2ba-ec4c9e4df58e&quot;&gt;https://studynote.oopy.io/books/13#49d0df11-3b0a-4432-a2ba-ec4c9e4df58e&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;&lt;b&gt;더 전문적이고 의미있는 예외를 사용하는 것이 좋다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외 vs. 부드러운 반환값&lt;/b&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;li&gt;&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;&lt;b&gt;유효성 검사에 대해서 자동 테스트를 작성하는 것을 추천.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단언문에 대해서는 비즈니스 규칙을 다루는 다른 테스트에 의해 자연스럽게 수행됨.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://stackoverflow.com/questions/4995471/cobertura-coverage-and-the-assert-keyword/6486294#6486294&quot;&gt;https://stackoverflow.com/questions/4995471/cobertura-coverage-and-the-assert-keyword/6486294#6486294&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Design_by_contract&quot;&gt;설계 계약&lt;/a&gt;&amp;nbsp;스타일 로 단언을 사용하는 경우&amp;nbsp;Java 명령문을 실패하게 만드는 테스트를 추가할 필요가 없습니다&amp;nbsp;assert. 사실, 많은 단언(예: 불변식, 사후 조건)의 경우 실패하게 만드는 객체를 생성할 수도 없으므로 이러한 테스트를 작성하는 것은&amp;nbsp;불가능합니다&amp;nbsp;. 그러나 할 수 있는 것은 불변식/사후 조건을 사용하여&amp;nbsp;경계를&amp;nbsp;연습하는 테스트 사례를 도출하는 것입니다 (Robert Binder의&amp;nbsp;&lt;a href=&quot;http://books.google.com/books?id=P3UkDhLHP4YC&amp;amp;pg=PA448&amp;amp;lpg=PA448&amp;amp;dq=robert%20binder%20invariant%20boundaries&amp;amp;source=bl&amp;amp;ots=_Eee5n7ZB7&amp;amp;sig=RDmq6wCNOUdriIww1qO_AHtXEdk&amp;amp;hl=en&amp;amp;ei=qe8fTtKvGI7pOdmDjZYD&amp;amp;sa=X&amp;amp;oi=book_result&amp;amp;ct=result&amp;amp;resnum=1&amp;amp;ved=0CBQQ6AEwAA#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;불변 경계&lt;/a&gt;&amp;nbsp;패턴 참조). 하지만 이렇게 하면 단언이 실패하지 않습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;5. 속성 기반 테스트&lt;/b&gt;&lt;/h1&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;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;/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;/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;indexOf 메서드가 그 값을 애매모호하지 않게 찾을 수 있도록 해야한다.&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.6 현업에서의 속성 기반 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예시기반 테스트 vs 속성 기반 테스트&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;명세 기반 테스트와 구조적 테스트를 수행할 때 예시 기반 테스트를 사용한다.&lt;/b&gt;&lt;/li&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;li&gt;&lt;b&gt;테스트가 충분하지만 확신할 수 없을 때 속성 기반 테스트를 사용한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주의사항&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;매우 비용이 많이 들거나 심지어 불가능한 데이터를 생성해야한다.&lt;/b&gt;&lt;/li&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;창의성이 핵심이다.&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;속성을 나타내는 방법을 찾고, 임의의 데이터를 생성하고, 구체적인 입력을 모른채로 예상 동작을 단언하는 일은 쉽지 않다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h2&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;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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;속성 기반 테스트를 작성하는 것이 조금 더 어렵다. 속성을 표현하기 위해서는 창의적이어야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;테스트 더블과 모의 객체&lt;/b&gt;&lt;/h1&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;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;li&gt;&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;&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;구성요소 B의 동작을 모방하는 객체를 생성하여 테스트 맥락에 따라 B처럼 행동할 수 있도록 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;b&gt;더 큰 제어권을 가진다.&lt;/b&gt;&lt;/li&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;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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6.1 더미, 페이크, 스텁, 모의 객체, 스파이&lt;/b&gt;&lt;/h2&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;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;/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;li&gt;&lt;b&gt;예를 들어 실제 DB 대신 인메모리 DB를 사용하는 등&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;&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;&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;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;li&gt;&lt;b&gt;감시하고 있는 근본 객체와의 모든 상호작용을 기록한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;6.3 현업에서의 모의 객체&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;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;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;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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모의해야하는 대상과 하지 말아야 하는 대상&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;모의해야할 때&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;li&gt;&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;&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;/ul&gt;
&lt;/li&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;날짜 및 시간 래퍼&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;모의할 수 있는 방법 알아두기&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;소유하지 않은 것을 모의하기&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;라이브러리와의 모든 상호작용을 캡슐화하는 추상화 객체 생성할 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추상화 자체는 통합 테스트해야한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모의에 관한 외부 의견&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;테스트 더블을 사용하려면 시스템 테스트 가능성을 가지도록 설계해야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실제 구현에 충실하게 테스트 더블ㅇ르 구축하는 것은 어렵다. 하지만 가능한 그렇게 해야한다.&lt;/b&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실제 구현을 사용하는 일이 불가능하거나 너무 비용이 많이 든다면 모의 객체보다 페이크를 사용한다.&lt;/b&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;li&gt;&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;/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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;좋은 상호작용 테스트를 작성하려면 테스트 대상 시스템을 설계할 때 엄격한 지침이 필요하다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;테스트 가능성을 위한 설계&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 소프트웨어 시스템을 테스트할 수 있지만, 어떤 시스템에서는 테스트하기 힘들다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소프트웨어 시스템은 때때로 테스트를 할 수 있도록 설계되어 있지 않다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 가능성 이라는 말은 테스트 대상 시스템이나 클래스, 메서드에 대해 자동 테스트를 얼마나 쉽게 작성할 수 있는지를 말한다.&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 도메인 코드에서 인프라 코드 분리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인 코드에서 인프라 코드 분리하기.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;package com.likelen.openapi;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

class InvoiceFilter {

    private List&amp;lt;Invoice&amp;gt; all() {
        try {
            Connection connection = DriverManager.getConnection(&quot;db&quot;, &quot;root&quot;, &quot;&quot;);
            PreparedStatement ps = connection.prepareStatement(&quot;select * from invoice&quot;);
            ResultSet rs = ps.executeQuery();

            List&amp;lt;Invoice&amp;gt; allInvoices = new ArrayList&amp;lt;&amp;gt;();
            while (rs.next()) {
                allInvoices.add(new Invoice(rs.getString(&quot;name&quot;), rs.getInt(&quot;value&quot;)));
            }
            ps.close();
            connection.close();
            return allInvoices;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }

    public List&amp;lt;Invoice&amp;gt; lowValueInvoices() {
        List&amp;lt;Invoice&amp;gt; issuedInvoices = all();
        return issuedInvoices.stream().filter(invoice -&amp;gt; invoice.value &amp;lt; 100).collect(Collectors.toList());
    }

    private class Invoice {

        private final String name;
        private final int value;

        public Invoice(String name, int value) {

            this.name = name;
            this.value = value;
        }
    }
}
&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;도메인 코드와 인프라 코드가 뒤섞여 있다. lowValueInvoices호출 시 DB 접속을 피할 수 없다. public 메서드를 수행하면서 어떻게 private 메서드를 스텁으로 만들 수 있을까? DB를 다루는 부분은 스텁으로 만들 수 없다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;책임이 클수록 더 복잡해지고 버그가 발생할 가능성이 증가한다. 덜 응집된 클래스는 코드양이 많다. 코드양이 많다는 것은 버그가 발생할 확률이 크다는 뜻.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 책에서는 핵사고날 아키텍쳐를 언급하는데, 이 부분에 대해서 논쟁이 있다. 인터페이스를 무조건 만들어야 하는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'모든 포트에 대해 인터페이스를 만들어야 하나요?' 옭고 그른 것은 없고, 모든 것은 상황에 따라 다르며, 실용성이 관건이라는 것을 납득시키고자 한다. 소프트웨어 시스템의 모든 것에 대해 인터페이스를 생성할 필요는 없다. 필자는 구현체가 두 개 이상이 되는 포트에 대해서는 인터페이스를 만든다. 또한 추상적 행위를 표현하는 인터페이스를 만들지 않을 때는, 구체적인 구현에서 구현 세부사항이 유출되지 않도록 한다. 언제나 문맥에 따라 판단하는 실용주의가 최고의 방법이다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 의존성 주입과 제어 가능성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스 수준에서 필자는 클래스를 완전히 제어할 수 있고(즉 테스트 대상 클래스의 행위를 쉽게 제어할 수 있고), 관찰할 수 있도록(테스트 대상 클래스에서 무슨 일이 일어나는지 알 수 있고 출력 결과를 검사할 수 있도록)해야 한다는 것.a&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제어 가능성은 일반적으로 테스트 스위트(모의 객체, 페이크, 스텝)을 활용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존성 주입의 경우, 생성자를 통해서 주입하는 것을 설명한다.&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;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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;1242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1GzBB/btsNBbsAcWp/Li9z8piCviD5d3ROvrN7Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1GzBB/btsNBbsAcWp/Li9z8piCviD5d3ROvrN7Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1GzBB/btsNBbsAcWp/Li9z8piCviD5d3ROvrN7Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1GzBB%2FbtsNBbsAcWp%2FLi9z8piCviD5d3ROvrN7Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2056&quot; height=&quot;1242&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;1242&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 클래스 및 메서드를 관찰 가능하게 하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스 수준에서 관찰 가능성은 기능이 기대했던 대로 동작하는지를 얼마나 쉽게 단언할 수 있는가에 관한 것.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;예제1 - 단언을 보조하는 메서드 도입여기서 isReadyForDelivery 메서드는 테스트 코드 작성시 단언을 보조해주는 역할을 한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;public class ShoppingCart { private boolean readyForDelivery = false; // 장바구니에 대한 정ㅂ public void markAsReadyForDelivery(Calendar estimatedDayOfDelivery) { this.readyForDelivery = true; // ... } public boolean isReadyForDelivery(){ return readyForDelivery } }&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예제2 - void 메서드의 행위를 관찰하기generateInstallments 메서드를 어떻게 테스트할 수 있을까? 모키토를 잘 알고 있다면 모의 객체에 전달된 모든 인스턴스를 얻는 방법(argumentCaptor)을 사용하는 것이다. '테스트 도중에 전달된 모든 인스턴스를 돌려줄래?'또는 리스트를 반환하게 해서 테스트 코드를 변경할 수도 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핵심은 실용주의이다. 테스트 가능성을 개선해주는 작은 설계 변경은 괜찮다는 점만 기억하자. 가끔은 변경사항이 코드 설계를 망치지 않을지 판단하기 어려울 수 있다. 시도해보고 마음에 안들면 폐기하자.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;public class InstallmentGeneratorTest { @Mock private InstallmentRepository repository; @Test void checkInstallments() { InstallmentGenerator generator = new InstallmentGenerator(repository); ShoppingCart cart = new ShoppingCart(100.0); generator.generateInstallments(cart,10); ArgumentCaptor&amp;lt;Installment&amp;gt; captor = ArgumentCaptor.forClass(Installment.class); verify(repository, times(10)).persist(captor.capture()); List&amp;lt;Installment&amp;gt; allInstallments = captor.getAllValues(); } }&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;public class InstallmentGenerator { private InstallmentRepository repo; public void generateInstallments(ShoppingCart cart, int numberOfInstallments) { ... } }&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 의존성 전달 방법: 클래스생성자와 메서드 매개변수&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 현업에서의 테스트 가능성 설계&lt;/b&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;&lt;b&gt;클래스를 훌륭하게 설계하는 일은 객체 지향 시스템에서 어려운 작업. 도움을 더 얻을수록 너 나은 설계를 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;테스트가 코드 설계에 대해 피드백을 제공한다&quot;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트는 테스트 대상 클래스의 인스턴스를 생성.&lt;/b&gt;&lt;/li&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;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.1 테스트 대상 클래스의 응집도&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응집도란 아키텍쳐상의 모듈, 클래스, 메서드 또는 어떤 요소든지 단 하나의 책임을 가지는 것을 뜻한다. 단일 책임을 결정하는 것은 문맥에 따라 달라진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 코드를 작성하면서 약간의 징후가 있을 수 있는 부분은 다음과 같았다.&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;응집력이 없는 클래스에 대한 테스트 스위트는 거대하다.&lt;/b&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;계속해서 크기가 커지는 클래스는 SOLID 지침에 있는 단일 책임 원칙/개방 폐쇄 원칙을 모두 어긴다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.2 테스트 대상 클래스의 결합&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응집력 있는 클래스를 사용하면, 여러 클래스를 조합해서 큰 행위를 구성한다. 하지만 이렇게 하면 결합도가 높은 설계를 하게 될 수 있다. 과도한 결합은 진화를 해칠 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 코드를 통해 결합도가 높은 클래스를 발견할 수 있다.&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;만약 제품 코드의 클래스를 테스트할 때 수많은 의존성 인스턴스가 필요하다면 이것은 나쁜 징후일 수 있다. 클래스 재설계를 고려하자. 다양한 리팩터링 전략을 사용할 수 있다. 아마 클래스가 구현하고 있는 큰 행위를 두 단계로 나눌 수 있을 것이다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ATest클래스에서 어떤 테스트(A 행위에 대한 테스트)가 실패했는데 디버깅 해보니 클래스 B의 문제를 발견하는 경우다. 이 클래스들이 어떻게 결합되어 있고, 어떻게 상호작용하는지, 그리고 그러한 설계 오류를 시스템의 다음 버전에서 예방할 수 있는지 재확인&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.3 복잡한 조건과 테스트 가능성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복잡한 조건들을 여러 개의 작은 조건들로 분할하는 방식으로 복잡성을 감소시키는 방법은 문제의 전체적인 복잡성을 감소시키지는 않겠지만 적어도 확산시키지는 않을 것. Specification 과 Condition 고민하기&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.4 private 메서드와 테스트 가능성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 private 메서드를 테스트하고 싶다는 마음이 생긴다면, 이것은 트세트가 우리에게 무엇간를 알려주는 좋은 예시다. 설계 관점에서 private 메서드가 현재 위치에 있어서는 안 된다는 뜻일 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.5 정적 메서드, 싱글톤, 테스트 가능성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정적 메서드는 테스트 가능성에 악영향을 미친다. 따라서 가능하면 정적 메서드를 만들지 않는 것이 좋다. 만약 LocalDate 클래스에서 수행한 것과 같이 추상화를 그 위에 추가하는 방식으로 테스트 가능성이 높이는게 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;![image-20240128234452956](/Users/len/Library/Application Support/typora-user-images/image-20240128234452956.png)&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 가능성은 단순히 &amp;ldquo;테스트 코드가 잘 돌아가게 하는 기술&amp;rdquo;이 아니라, 좋은 설계의 결과물이다. 테스트가 잘 되도록 설계하는 과정은, 클래스의 책임을 나누고, 의존성을 정리하고, 동작을 명확하게 만드는 방향으로 우리를 이끈다.&lt;/b&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;br /&gt;&lt;b&gt;테스트하기 쉬운 코드는 유지보수도 쉬운 코드다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 작성에 if/loop/mock이 너무 많아지면 SRP 위반 의심&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;void 함수는 테스트를 고려해서 이벤트/콜백/리턴값 설계 고민&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;테스트 주도 개발&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전통적인 개발 방법론은. 먼저 구현을 한다. 그러고 나서 구현을 한 뒤에 꼭 테스트를 한다. 반대는 하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜일까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 주도 개발은 '코드를 약간 작성하고 테스트한다' 라는 기존의 코딩 방식에 도전한다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 첫번째 TDD 세션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TDD의 예를 들어 로마 숫자를 정수형으로 바꾸는 프로그램이라 가정해보자.&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;I, unus, 1&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;V, quinque, 5&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;X, decem, 10&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;L quiquaginta, 50&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C, cemtum, 100&lt;/b&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;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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예를 들어 XV는 15(10+ 5). XXIV는 24(10 + 10 - 1 + 5)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;먼저 예시를 만드는 일은 TDD의 일부이다&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;단순하게 단일 문자를 사용한 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;숫자가 여러 문자로 이루어진 경우(뺄셈 규칙 사용 X)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;간단한 뺄셈 규칙 사용&lt;/b&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;입력값: &quot;XIV&quot;, 기댓값: 14&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입력값: &quot;XXIX&quot;, 기댓값: 29&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;li&gt;&lt;b&gt;테스트를 통과할 수 있을 만큼 제품 코드 작성&lt;/b&gt;&lt;/li&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;/ol&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package io.agistep.contractual;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class RomanNumberConverterTest {

    @Test
    void shouldUnderstandSymbolTest() {
        RomanNumberConverter roman = new RomanNumberConverter();
        int number = roman.convert(&quot;I&quot;);
        assertThat(number).isEqualTo(1);
    }

    @Test
    void shouldUnderstandSymbol_V_Test() {
        RomanNumberConverter roman = new RomanNumberConverter();
        int number = roman.convert(&quot;V&quot;);
        assertThat(number).isEqualTo(5);
    }

    @ParameterizedTest
    @CsvSource({&quot;I,1&quot;, &quot;V,5&quot;, &quot;X,10&quot;, &quot;C, 100&quot;, &quot;D, 500&quot;, &quot;M, 1000&quot;})
    void parmasTest(String num, int number) {
        RomanNumberConverter roman = new RomanNumberConverter();
        int cNumber = roman.convert(num);
        assertThat(number).isEqualTo(cNumber);
    }

    @Test
    void 여러문자로_이뤄진_로마숫자() {
        RomanNumberConverter roman = new RomanNumberConverter();
        int xxx = roman.convert(&quot;II&quot;);
        assertThat(xxx).isEqualTo(2);
    }

    private class RomanNumberConverter {

        private static final Map&amp;lt;String, Integer&amp;gt; table = new HashMap&amp;lt;&amp;gt;() {{
            put(&quot;I&quot;, 1);
            put(&quot;V&quot;, 5);
            put(&quot;X&quot;, 10);
            put(&quot;L&quot;, 50);
            put(&quot;C&quot;, 100);
            put(&quot;D&quot;, 500);
            put(&quot;M&quot;, 1000);
        }};

        public int convert(String i) {
            int finalNumber = 0;

            for (int j = 0; j &amp;lt; i.length(); j++) {
                String key = i.charAt(j) + &quot;&quot;;
                finalNumber += table.get(key);
            }
            return finalNumber;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. TDD 에 대한 고찰&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;li&gt;&lt;b&gt;제품 코드와 테스트 코드 리팩터링&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 TDD 프로세스를 빨강-초록-리팩터 주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TDD 실무자들은 이 접근법이 개발 과정에서 다음과 같은 이점이 있다고 말한다.&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;요구사항을 먼저 살펴본다.&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;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;/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;TDD 주기로 일하지 않는 개발자들은 큰 덩어리의 제품 코드를 만들고 나서야 피드백을 얻음. TDD 주기를 사용하면 개발자들은 한 번에 하나씩 수행한다. 테스트를 하나 작성하고, 이를 통과시키고, 다시 고찰. 고찰을 통해 발생할 수 있는 새로운 문제를 쉽게 발견. 이전 주기에서 모든 것을 통제하고 있고, 그 이후로 적은 양의 코드를 작성했기 때문&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;&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;&lt;b&gt;테스트 코드를 종종 개발 중인 클래스나 구성요소의 첫번째 클라이언트.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;b&gt;&lt;a href=&quot;https://www.jamesshore.com/v2/projects/lets-play-tdd&quot;&gt;https://www.jamesshore.com/v2/projects/lets-play-tdd&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 현업에서의 TDD&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;TDD인가 아닌가?&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;TDD 를 하지 않아도 동일한 이점을 누릴 수 있다고 한다. 그러나 필자는 TDD가 주는 리듬에 감사하다고 말한다. 다음으로 개발할 가장 간단한 기능을 찾고, 그에 맞는 테스트를 작성하고, 필요한 만큼만 구현하고, 작업 내용을 고찰하는 일은 필자의 개발 속도에 따라 조절할 수 있다. TDD 는 혼란과 좌절의 무한 루프에서 빠져나오도록 해준다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클래스를 설계하는 일은 어려운 편에 속하는데, TDD 덕분에 개발 초기부터 코드를 수행해볼 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;항상 TDD를 사용해야 할까?&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설계나 아키텍쳐, 특정 요구사항에 대한 구현 방법이 명확하지 않을 때 TDD를 사용한다. 이러한 경우에는 조금 천천히 진행하면서 여러 가능성을 실험하는 것이 좋다. 잘 알고 있는 문제를 작업하고 있다면 문제 해결 방안을 이미 알고 있으므로 몇몇 단계 생략&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필자는 복잡한 문제를 다루거나 그 문제에 관한 전문성이 부족할 때 TDD 사용. 구현 난이도가 있는 경우에 TDD를 적용하면 한 걸음 뒤로 물러서서 요구사항을 학습하고 작은 테스트부터 작성하게 해준다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발 과정에서 배울만한 게 없으면 TDD를 사용하지 않는다. 이미 어떤 문제와 그 해결책을 잘 알고 있다면 편안하게 해결 방안을 바로 코딩한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;**TDD는 설계관점(원하는 대로 구조화되었는가)에서뿐만 아니라 구현 관점(코드가 필요한 일을 하고 있는가)**에서 작성 중인 코드를 배울 수 있도록 해준다. 하지만 일부 복잡한 기능에 대해서는 어떤 테스트를 먼저 작성할지 결정하기 어렵다. 이 경우 TDD를 사용하지 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우리는 멈춰 서서 지금 무엇을 하고 있는지 생각할 수 있는 도구가 필요하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TDD는 그 목적을 위한 완벽한 접근 방식이지만, 유일한 방법은 아니다. 언제 TDD를 사용할지 결정하는 일은 경험이 필요하다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;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;특히 병렬성, 동기화, 캐싱, 외부 API 등 복잡한 로직을 다룰 땐 **&amp;ldquo;테스트 실패 패턴 분석&amp;rdquo;**이 좋은 디버깅 루틴이 돼.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. 대규모 테스트 작성&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.1 언제 대규모 테스트를 고려할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다음과 같은 상황에서는 대규모 테스트를 작성하는 것이 도움이 된다:&lt;/b&gt;&lt;/p&gt;
&lt;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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이처럼 복수의 요소가 유기적으로 작동하는 시나리오를 확인하려면, 대규모 테스트가 필요해진다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.2 DB와 연동된 테스트를 위한 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스와 통합하여 테스트하려면, 다음과 같은 인프라를 준비해두는 것이 좋다:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;전용 DB 연결 클래스 구성&lt;/b&gt;&lt;br /&gt;&lt;b&gt;테스트 전용 DB에 연결하고 트랜잭션 처리나 초기화, 정리 등을 담당할 클래스를 만든다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트용 데이터 생성기 작성&lt;/b&gt;&lt;br /&gt;&lt;b&gt;매번 반복해서 데이터를 입력하지 않도록 도와주는 생성기를 준비하되, 불필요한 데이터를 만들지 않도록 최소화한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단언 도우미 함수 정의&lt;/b&gt;&lt;br /&gt;&lt;b&gt;assert 문이 복잡해지는 경우엔 이를 감싸는 헬퍼 함수를 만들어, 테스트 코드를 더 읽기 쉽고 재사용 가능하게 만든다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이러한 인프라를 잘 갖추면, DB 연동 테스트도 복잡하지 않게 작성할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.3 시스템 테스트는 언제 필요할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시스템 테스트는 애플리케이션 전체의 흐름이 제대로 동작하는지를 검증하는 데 쓰인다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;예를 들어, 회원가입 전체 흐름을 테스트할 때는 다음과 같이 진행할 수 있다:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;셀레늄(Selenium) 등의 도구를 사용해 브라우저를 자동으로 띄우고&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입력 &amp;rarr; 버튼 클릭 &amp;rarr; 결과 확인의 전 과정을 자동화&lt;/b&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9.4 대규모 테스트는 얼마나, 어디까지?&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;가능한 로직은 단위 테스트로 커버하는 것이 기본&lt;/b&gt;&lt;/li&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;핵심은 &amp;lsquo;테스트 인프라&amp;rsquo;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;통합/시스템 테스트의 품질과 유지 보수성을 높이려면 다음이 중요하다:&lt;/b&gt;&lt;/p&gt;
&lt;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;li&gt;&lt;b&gt;누구나 쉽게 테스트를 작성할 수 있도록 유틸성 도구와 프레임워크를 마련&lt;/b&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10. 테스트 코드 품질&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋은 테스트는 단순히 &quot;돌아가는 코드&quot;가 아니라, 읽기 쉽고, 쉽게 고칠 수 있는 코드여야 한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;이 장에서는 테스트 코드의 품질을 높이기 위해 지켜야 할 원칙들과, 피해야 할 테스트 냄새들을 정리해본다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10.1 좋은 테스트를 위한 원칙&lt;/b&gt;&lt;/h3&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;&lt;b&gt;느린 테스트는 개발자의 실행 빈도를 떨어뜨린다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;가능하면 Mock 등을 활용해 빠르게 만들고, 느린 테스트는 따로 묶어 관리하는 것도 방법이다.&lt;/b&gt;&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;&lt;b&gt;하나의 테스트는 하나의 행위만 검증하도록 하고, 실행 순서나 다른 테스트에 영향을 받지 않도록 작성한다.&lt;/b&gt;&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;&lt;b&gt;&quot;그냥 만들어둔 테스트&quot;는 나중에 오히려 방해가 될 수 있다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;어떤 행위나 로직을 보호하기 위한 목적이 명확한 테스트만 유지하자.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 테스트는 반복 가능하고 안정적이어야 한다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행할 때마다 결과가 바뀌는 테스트는 신뢰를 잃게 만든다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;환경에 따라 실패하는 테스트는 먼저 의심해야 할 대상이다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 단언문(Assert)은 확실하고 구체적이어야 한다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 결과를 검증하는 핵심이다. assert가 애매하면, 실패했을 때 원인을 알기 어렵다.&lt;/b&gt;&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;&lt;b&gt;테스트가 깨졌다는 건 코드 변화가 테스트에 반영되었다는 뜻이다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;오히려 테스트가 멀쩡하다면, 보호받지 못하는 코드가 있다는 신호일 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 테스트는 명확한 이유 하나로 실패해야 한다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 테스트가 실패하면, 무엇 때문에 실패했는지 바로 파악 가능해야 한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;여러 책임이 얽혀 있다면 테스트를 나누자.&lt;/b&gt;&lt;/p&gt;
&lt;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;&lt;b&gt;단위 테스트는 쉬워도, 통합 테스트는 인프라가 필요하다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;복잡한 시나리오도 쉽게 작성할 수 있도록 테스트 환경을 잘 구축하자.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 테스트는 읽기 쉬워야 한다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이해하기 어려운 테스트는 유지보수하기 어렵다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;테스트는 코드를 설명해주는 문서이기도 하다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;의도가 잘 드러나는 이름과 구조를 지키자.&lt;/b&gt;&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;&lt;b&gt;제품 코드가 바뀌면 테스트도 바뀌어야 한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;리팩터링과 캡슐화를 통해 테스트도 변화에 유연하게 대응할 수 있어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10.2 피해야 할 테스트 냄새&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트도 일반 코드처럼 안티패턴이나 냄새가 존재한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;다음과 같은 냄새가 있다면 리팩터링을 고민해보자.&lt;/b&gt;&lt;/p&gt;
&lt;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;&lt;b&gt;여러 테스트에서 비슷한 코드가 반복된다면, 공통 설정이나 헬퍼 메서드로 추출하자.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;테스트도 리팩터링의 대상이다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 불분명한 단언문&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;assertThat(...).isTrue()처럼 결과가 모호한 단언은 실패 원인을 찾기 어렵게 만든다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;무엇을 검증하는지 명확히 표현하자.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 외부 자원에 대한 의존&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네트워크, 파일 시스템, 데이터베이스 등 외부 환경에 의존하면 테스트가 불안정해진다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;가능하다면 Stub이나 Mock을 사용하거나, 테스트 전용 환경을 따로 구성하자.&lt;/b&gt;&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;&lt;b&gt;테스트 데이터를 너무 일반적으로 만들면, 각 테스트의 목적이 흐려진다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;필요한 만큼만 구성하고, 테스트에 맞는 픽스처를 명확히 나누자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Practice</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/134</guid>
      <comments>https://new-pow.tistory.com/134#entry134comment</comments>
      <pubDate>Sat, 26 Apr 2025 10:13:24 +0900</pubDate>
    </item>
    <item>
      <title>PostgreSQL에도 Pub/Sub이 있다는 사실? : PostgreSQL LISTEN &amp;amp; NOTIFY</title>
      <link>https://new-pow.tistory.com/133</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL의&amp;nbsp;&lt;code&gt;LISTEN&lt;/code&gt;과 &lt;code&gt;NOTIFY&lt;/code&gt;를 이용하여 이벤트 기반 아키텍처를 구축할 수 있다는 사실을 아셨나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Pub/Sub 처럼 PostgreSQL도 데이터 변경에 대해 구독할 수 있고 변경사항을 받아 처리할 수 있답니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능에 대해 요약하자면 '트랜젝션이 종료되고 commit 된 시점에 서버가 해당 이벤트를 받아 알 수 있는 기능' 입니다.&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;b&gt;PostgreSQL LISTEN &amp;amp; NOTIFY&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LISTEN 과 &lt;code&gt;NOTIFY&lt;/code&gt;는 PostgreSQL 이 제공하는 이벤트기반 비동기 처리 방식입니다. 한쪽에서는 특정 이벤트를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;LISTEN으&lt;/span&gt;로 알리고, 다른 쪽에서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;LISTEN&lt;span&gt;&amp;nbsp;&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;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;Polling 없이도 실시간 변경사항 유무를 받을 수 있음&lt;/li&gt;
&lt;li&gt;이벤트가 발생할 때만 처리해주면 되므로 불필요한 부하 감소&lt;/li&gt;
&lt;li&gt;PostgreSQL 클라이언트 라이브러리에서 지원하는 API 활용 가능&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;LISTEN &amp;amp; NOTIFY 기본 사용법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. NOTIFY &amp;ndash; 이벤트 발생&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;NOTIFY&lt;/code&gt;는 특정 채널에 알림을 보내는 역할을 합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;NOTIFY new, 'https://test.com/post/1';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 쿼리는 &lt;code&gt;newanswer&lt;/code&gt;라는 채널을 구독하고 있는 모든 세션에 해당 URL을 알림으로 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. LISTEN &amp;ndash; 이벤트 구독&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;LISTEN&lt;/code&gt;을 사용하면 특정 채널을 구독할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;LISTEN new;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 세션은 &lt;code&gt;newanswer&lt;/code&gt; 채널에서 &lt;code&gt;NOTIFY&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 이벤트 감지 (LISTEN 대기)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 파이썬 코드 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;cursor = conn.cursor()
cursor.execute(&quot;LISTEN new;&quot;)

while True: # 계속해서 발행된 이벤트에 대해 poll합니다.
    if select.select([conn], [], [], 5) == ([], [], []):
        continue  # 타임아웃 대기
    conn.poll()
    while conn.notifies:
        notify = conn.notifies.pop(0)&lt;/code&gt;&lt;/pre&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;b&gt; 주의할 점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LISTEN을&amp;nbsp;등록한 세션이 닫히면 자동으로 해제됩니다.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;NOTIFY&lt;/code&gt;를 보냈을 때 LISTEN을 하고 있는 세션이 없으면 해당 알림은 유실됩니다.&lt;/li&gt;
&lt;li&gt;특정 세션만 &lt;code&gt;NOTIFY&lt;/code&gt;를 받을 수 있도록 설정하는 기능은 없으며, 채널 단위로만 작동합니다.&lt;/li&gt;
&lt;li&gt;오랫동안 실행되는 프로세스에서는 LISTEN을&amp;nbsp;계속 유지할 수 있도록 세션이 닫히지 않게 관리해야 합니다.&lt;br /&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;color: #333333; text-align: start;&quot;&gt;하지만 새로 연결된 세션에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;LISTEN&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;을 다시 실행하지 않으면 알림을 받을 수 없습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;LISTEN은 세션별로 유지되기 때문에 연결이 끊어지면 다시 등록해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;같은 채널을 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;LISTEN을 &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;/li&gt;
&lt;/ul&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;&lt;b&gt;참고링크&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://postgresql.kr/docs/current/sql-notify.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://postgresql.kr/docs/current/sql-notify.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://postgresql.kr/docs/current/sql-listen.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://postgresql.kr/docs/current/sql-listen.html&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m-falcon.tistory.com/528&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://m-falcon.tistory.com/528&lt;/a&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;</description>
      <category>  Database</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/133</guid>
      <comments>https://new-pow.tistory.com/133#entry133comment</comments>
      <pubDate>Sat, 15 Mar 2025 09:32:58 +0900</pubDate>
    </item>
    <item>
      <title>Python에서 `self` 는 뭔데 계속 파라미터에 들어가있지?  &amp;zwj;♂️</title>
      <link>https://new-pow.tistory.com/131</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;  `self` 가 뭐길래 계속 호출해줘야 쓸 수 있는거지?&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;class MovieSimilarityCalculator:
    def __init__(self, movies): # self 가 뭐지??
        self.movies = movies  # Movies 객체를 주입받음&lt;/code&gt;&lt;/pre&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;b&gt;Self 란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Self는 클래스의 인스턴스를 나타냅니다. &lt;code&gt;self&lt;/code&gt;를 사용하면 파이썬에서 클래스의 attribute와 메서드에 액세스할 수 있습니다. 주어진 인수를 사용하여 어트리뷰트를 바인딩합니다.&lt;br /&gt;클래스에서 생성된 객체의 메서드를 호출할 때마다 &quot;self&quot; 매개변수를 사용하여 객체가 자동으로 첫 번째 인수로 전달됩니다. 이를 통해 객체의 속성을 수정하고 해당 특정 인스턴스에 고유한 작업을 실행할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python은 &lt;b&gt;명시적 철학&lt;/b&gt;을 따르는 언어라고합니다. (이부분은 제대로 이해를 못해서 다른 기회에 언어 철학을 이해하는 것으로...)&lt;/li&gt;
&lt;li&gt;다른 언어(C++나 Java 등)에서 암묵적으로 &lt;code&gt;this&lt;/code&gt;를 사용하는 것과 달리, Python에서는 &lt;code&gt;self&lt;/code&gt;를 명시적으로 추가해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 언어들도 암묵적으로 메서드의 첫번째 파라미터로 자기 자신을 가르키는 파라미터를 전달하기도 합니다만, `self` 로 매번 명시해주는 것이 신기했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참, 여기서 `self` 는 python 의 예약어가 아닌 그냥 기본 파라미터 이름이기 때문에 다른 이름으로 바꿔도 무방하다고 합니다   그냥 무조건 그렇게 타입이 추론되는듯?&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;b&gt;self 는 이런 특징을 갖습니다.&lt;/b&gt;&lt;/h2&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;/li&gt;
&lt;li&gt;출처 : &lt;a href=&quot;https://www.geeksforgeeks.org/self-in-python-class/&quot;&gt;https://www.geeksforgeeks.org/self-in-python-class/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class Check:
    def __init__(self):
        print(&quot;Address of self = &quot;,id(self))

obj = Check()
print(&quot;Address of class object = &quot;,id(obj))

# Address of self =  140273244381008
# Address of class object =  140273244381008&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인스턴스 변수에 접근할 수 있습니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;self&lt;/code&gt;를 사용하여 각 객체가 고유의 상태(변수 값)를 유지할 수 있도록 합니다. 예를 들어, 아래 코드에서 &lt;code&gt;self.movies&lt;/code&gt;는 해당 인스턴스의 &lt;code&gt;movies&lt;/code&gt; 데이터를 참조합니다.&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;class Movies:
    def __init__(self):
        self.movies = {}  # 각 인스턴스마다 고유한 movies 딕셔너리&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;self.jaccard_similarity&lt;/code&gt;를 호출할 때 &lt;code&gt;self&lt;/code&gt;를 사용하여 동일한 클래스 내의 메서드에 접근합니다&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;class MovieSimilarityCalculator:
    def jaccard_similarity(self, a, b):
        if len(a) == 0 or len(b) == 0:
            return 0
        return len(a &amp;amp; b) / len(a | b)

    def get_topk_jaccard_genres(self, target_mid, k=20):
        # self를 사용하여 동일 클래스의 메서드 호출
        similarity = self.jaccard_similarity(set1, set2)&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;self&lt;/code&gt;를 통해 동일한 데이터를 참조할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드에서 &lt;code&gt;self.movies&lt;/code&gt;, &lt;code&gt;self.genresets&lt;/code&gt; 같은 속성들은 &lt;code&gt;self&lt;/code&gt;를 통해 객체 전체에서 접근 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1739627051466&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import csv

class Movies:
    def __init__(self):
        self.movies = {}  # movie_id -&amp;gt; movie title
        self.genresets = {}  # movie_id -&amp;gt; set of genres

        with open('./data/movies.csv', newline='') as csvfile:
            csvreader = csv.reader(csvfile)
            next(csvreader)  # Skip header

            for mid, title, genres in csvreader:
                self.movies[int(mid)] = title
                self.genresets[int(mid)] = set(genres.split('|'))

    def get_movie_title(self, movie_id):
        return self.movies.get(movie_id)

    def get_movie_genres(self, movie_id):
        return self.genresets.get(movie_id)&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;self를 빼면 어떻게 되나요?&lt;/b&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;code&gt;self&lt;/code&gt;를 빼고 코드를 작성하면 Python은 그것을 인스턴스 속성이 아니라 &lt;b&gt;지역 변수&lt;/b&gt;로 처리합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Movies:
    def __init__(self):
        movies = {}  # 지역 변수로 처리됨&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;movies&lt;/code&gt; 변수는 메서드가 끝나자마자 삭제되며, 다른 메서드에서 접근할 수 없습니다. 따라서, 인스턴스 변수로 사용하려면 반드시 &lt;code&gt;self.movies&lt;/code&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;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wikidocs.net/1742&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wikidocs.net/1742&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/self-in-python-class/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/self-in-python-class/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Languages</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/131</guid>
      <comments>https://new-pow.tistory.com/131#entry131comment</comments>
      <pubDate>Sat, 15 Feb 2025 22:48:12 +0900</pubDate>
    </item>
    <item>
      <title>Spring WebFlux: 요청 처리 흐름 DispatcherHandler, HttpWebHandlerAdapter...</title>
      <link>https://new-pow.tistory.com/130</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux는 Spring MVC와 마찬가지로 &lt;b&gt;프론트 컨트롤러 패턴&lt;/b&gt;을 사용하여 설계되었습니다. WebFlux의 프론트 컨트롤러인 &lt;b&gt;DispatcherHandler&lt;/b&gt;는 요청 처리의 핵심 알고리즘을 제공하며, 다양한 위임 컴포넌트를 통해 실제 처리가 이루어집니다. 이번 글에서는 DispatcherHandler의 구조와 역할을 상세히 살펴보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;WebFlux 구성 요소&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 WebFlux 설정은 다음과 같은 구성 요소를 포함합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;webHandler&lt;/code&gt; 빈으로 선언된 DispatcherHandler&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebFilter&lt;/code&gt; 및 &lt;code&gt;WebExceptionHandler&lt;/code&gt; Bean&lt;/li&gt;
&lt;li&gt;DispatcherHandler 관련 Bean&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;b&gt;HttpWebHandlerAdapter&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpWebHandlerAdapter는 HttpHandler의 구현체로, 가장 저수준의 HTTP 요청 처리를 담당합니다. 이 클래스는 WebHandler(주로 DispatcherHandler)로 요청을 전달하고, 처리된 결과를 HTTP 응답으로 반환하는 역할을 합니다.&lt;/p&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;&lt;b&gt;요청 전달:&lt;/b&gt; HTTP 요청을 DispatcherHandler로 전달합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;필터 체인 실행:&lt;/b&gt; FilteringWebHandler를 통해 WebFilter 클래스들의 필터 체인이 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저수준 구현체이기 때문에 일반적으로 개발자가 직접 수정할 일은 없습니다. 그러나 필터링 동작을 이해하기 위해 이 클래스의 역할을 파악하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DispatcherHandler&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherHandler는 &lt;b&gt;스프링 설정으로부터 필요한 위임 컴포넌트(예: HandlerMapping, HandlerAdapter)&lt;/b&gt;를 검색합니다. 또한, 스프링 빈으로 등록되며 ApplicationContextAware 인터페이스를 구현하여 실행 중인 애플리케이션 컨텍스트에 접근합니다. DispatcherHandler가 webHandler라는 이름으로 선언되어 있다면, WebHttpHandlerBuilder에 의해 자동으로 검색됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z55x7/btsMks3fPLV/pnRZjCr92ADb69I3LWEZ6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z55x7/btsMks3fPLV/pnRZjCr92ADb69I3LWEZ6K/img.png&quot; data-alt=&quot;출처 : https://dreamchaser3.tistory.com/12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z55x7/btsMks3fPLV/pnRZjCr92ADb69I3LWEZ6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz55x7%2FbtsMks3fPLV%2FpnRZjCr92ADb69I3LWEZ6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;402&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://dreamchaser3.tistory.com/12&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Bean 타입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherHandler는 다양한 빈을 검색하여 요청 처리 과정에서 활용합니다. 이러한 빈은 주로 스프링이 제공하는 WebFlux 계약을 구현합니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bean&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;HandlerMapping&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;요청과 핸들러를 연결합니다. 주로 URL 패턴 매핑이나 애너테이션 기반 매핑을 지원합니다. 주요 구현체는 &lt;code&gt;RequestMappingHandlerMapping&lt;/code&gt;, &lt;code&gt;RouterFunctionMapping&lt;/code&gt;, &lt;code&gt;SimpleUrlHandlerMapping&lt;/code&gt; 등이 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;HandlerAdapter&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;핸들러가 호출될 수 있도록 지원합니다. DispatcherHandler는 이 빈을 통해 세부 구현에 구애받지 않고 핸들러를 호출할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;HandlerResultHandler&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;핸들러의 반환값을 처리하고 최종 응답을 생성합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;b&gt;요청 처리 흐름&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherHandler는 다음과 같은 순서로 요청을 처리합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Handler 검색:&lt;/b&gt; 각 &lt;code&gt;HandlerMapping&lt;/code&gt; 빈을 호출하여 적합한 핸들러를 찾습니다. 첫 번째로 매칭된 핸들러를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Handler 호출:&lt;/b&gt; 적절한 &lt;code&gt;HandlerAdapter&lt;/code&gt;를 사용하여 핸들러를 실행하고 반환값을 &lt;code&gt;HandlerResult&lt;/code&gt;로 포장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 처리:&lt;/b&gt; &lt;code&gt;HandlerResultHandler&lt;/code&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;&lt;b&gt;반환값 처리 (Result Handling)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러의 반환값은 &lt;code&gt;HandlerAdapter&lt;/code&gt;를 통해 &lt;code&gt;HandlerResult&lt;/code&gt;로 감싸지며, 추가 컨텍스트 정보도 포함됩니다. &lt;code&gt;HandlerResult&lt;/code&gt;는 첫 번째로 매칭된 &lt;code&gt;HandlerResultHandler&lt;/code&gt;로 처리됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 HandlerResultHandler 구현체&lt;/h4&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;HandlerResultHandler 타입&lt;/th&gt;
&lt;th&gt;반환 값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ResponseEntityResultHandler&lt;/td&gt;
&lt;td&gt;@Controller에서 생성된 &lt;code&gt;ResponseEntity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ServerEntityResultHandler&lt;/td&gt;
&lt;td&gt;함수형 엔드포인트에서 생성된 &lt;code&gt;ServerResponse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResponseBodyResultHandler&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@ResponseBody&lt;/code&gt; 메소드 또는 &lt;code&gt;@RestController&lt;/code&gt; 반환값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ViewResolutionResultHandler&lt;/td&gt;
&lt;td&gt;&lt;code&gt;View&lt;/code&gt;, &lt;code&gt;Model&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt; 등을 뷰로 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Exception Handling&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherHandler는 예외 처리를 위해 &lt;code&gt;DispatchExceptionHandler&lt;/code&gt;를 사용할 수 있습니다. 핸들러 실행 중 또는 그 이전 단계에서 발생한 예외를 처리할 수 있으며, 비동기 요청에서 예외 처리가 미루어질 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
class ExampleController {
    @GetMapping(&quot;/example&quot;)
    fun exampleEndpoint(): ResponseEntity&amp;lt;String&amp;gt; {
        throw IllegalArgumentException(&quot;예외 발생!&quot;)
    }

    @ExceptionHandler(IllegalArgumentException::class)
    fun handleIllegalArgument(ex: IllegalArgumentException): ResponseEntity&amp;lt;String&amp;gt; {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(&quot;에러 처리: ${ex.message}&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;View Resolution&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux의 뷰 해석(View Resolution)은 HTML 템플릿과 모델 데이터를 렌더링하여 브라우저에 표시하는 과정입니다. &lt;code&gt;ViewResolver&lt;/code&gt; 인스턴스를 사용하여 문자열 뷰 이름을 실제 뷰로 해석합니다.&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;&lt;b&gt;Spring, CharSequence:&lt;/b&gt; 문자열로 된 뷰 이름을 &lt;code&gt;ViewResolver&lt;/code&gt;가 해석합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;void:&lt;/b&gt; 요청 경로를 기반으로 기본 뷰를 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Rendering:&lt;/b&gt; 뷰 해석 시나리오에 적합한 API를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Model, Map:&lt;/b&gt; 요청 모델에 추가 속성을 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
class ExampleViewController {
    @GetMapping(&quot;/view&quot;)
    fun showView(model: Model): String {
        model.addAttribute(&quot;message&quot;, &quot;Hello, WebFlux!&quot;)
        return &quot;exampleView&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Content Negotiation&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ViewResolutionResultHandler&lt;/code&gt;는 클라이언트의 요청 미디어 타입과 뷰가 지원하는 미디어 타입을 비교하여 적합한 뷰를 선택합니다. JSON, XML 등 다양한 포맷을 지원하기 위해 &lt;code&gt;HttpMessageWriterView&lt;/code&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
class WebConfig : WebFluxConfigurer {
    override fun configureContentTypeResolver(resolver: RequestedContentTypeResolverBuilder) {
        resolver.favorPathExtension(true).favorParameter(true)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HandlerAdapter&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HandlerAdapter는 DispatcherHandler가 다양한 유형의 핸들러를 호출할 수 있도록 지원하는 핵심 컴포넌트입니다. 핸들러의 종류에 따라 다양한 HandlerAdapter 구현체가 존재하며, 각 구현체는 특정 핸들러 타입에 대한 호출 로직을 담당합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 HandlerAdapter 구현체&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;SimpleHandlerAdapter&lt;/code&gt;: &lt;code&gt;HttpRequestHandler&lt;/code&gt; 인터페이스를 구현한 핸들러를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpRequestHandlerAdapter&lt;/code&gt;: Spring의 &lt;code&gt;HttpRequestHandler&lt;/code&gt; 인터페이스를 구현한 핸들러를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ControllerClassNameHandlerAdapter&lt;/code&gt;: 클래스 이름 규칙을 기반으로 핸들러를 매핑하고 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RequestMappingHandlerAdapter&lt;/code&gt;: &lt;code&gt;@RequestMapping&lt;/code&gt; 어노테이션을 사용하여 매핑된 핸들러를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherHandler는 각 요청에 대해 적합한 HandlerAdapter를 찾아 핸들러를 실행합니다. 이를 통해 DispatcherHandler는 핸들러의 실제 타입에 의존하지 않고 요청을 처리할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ViewResolver&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewResolver는 뷰 이름(View Name)을 실제 뷰 객체(View Object)로 변환하는 역할을 담당합니다. Spring WebFlux는 다양한 ViewResolver 구현체를 제공하며, 각 구현체는 특정 뷰 기술(예: Thymeleaf, FreeMarker, JSP)을 지원합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 ViewResolver 구현체&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;InternalResourceViewResolver&lt;/code&gt;: JSP와 같은 내부 리소스 뷰를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ThymeleafViewResolver&lt;/code&gt;: Thymeleaf 템플릿 엔진을 사용하는 뷰를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FreeMarkerViewResolver&lt;/code&gt;: FreeMarker 템플릿 엔진을 사용하는 뷰를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherHandler는 뷰 이름을 기반으로 적절한 ViewResolver를 선택하고, ViewResolver는 해당 뷰 이름을 해석하여 실제 뷰 객체를 생성합니다. 생성된 뷰 객체는 모델 데이터를 사용하여 뷰를 렌더링하고, 렌더링된 결과는 클라이언트에게 응답됩니다.&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;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Web on Reactive Stack&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/webflux/dispatcher-handler.html&quot;&gt;https://docs.spring.io/spring-framework/reference/web/webflux/dispatcher-handler.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>⚙️ Frameworks, Libraries/  Spring</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/130</guid>
      <comments>https://new-pow.tistory.com/130#entry130comment</comments>
      <pubDate>Sat, 15 Feb 2025 09:02:24 +0900</pubDate>
    </item>
    <item>
      <title>`Spring Webflux`의 등장과 Reactive Programming</title>
      <link>https://new-pow.tistory.com/129</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 오랜만에 Spring 이야기로 왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통의 많은 Spring 프레임워크 입문자가 그렇듯 저도 Spring MVC 로 웹 애플리케이션 개발을 익혔고 그에 많이 익숙해져 있는 상태였는데요. 지금의 회사에서는 Spring Webflux 를 사용하고 있고, 저도 사용한지 약 1년이 되었습니다. 초반에 학습하느라 굉장히 힘들었던 기억이 나는데 이것을 따로 정리해본 적이 없어서 조금씩 정리해보려고합니다. (역시 학습은 Output을 해야 명확해지는 것이죠!)&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;Blocking, Non-blocking&lt;/li&gt;
&lt;li&gt;Thread, Thread Pool, Context switching&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spring WebFlux의 등장 &lt;/b&gt; &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC 등 전통적인 웹 개발 방식은 대부분 동기(Blocking) 방식이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 요청을 보내면, 서버는 그 요청을 처리할 때까지 다른 요청은 그 동안 처리할 수 없습니다. 하나의 요청에 대해 하나의 스레드가 점유되며 사용되는 방식입니다. 이 방식이 문제가 되는 지점은 바로 여러 요청에 대해 동시 처리 능력이 부족하다는 점입니다. 스레드 하나가 I/O작업을 하거나 시간이 오래 걸리는 네트워킹 작업들을 할 때에는 아무것도 안하고 대기 상태로 쉬고 있어 스레드를 비효율적으로 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 PC 외에 스마트 기기 등 서버에 요청을 보낼 클라이언트의 수가 배로 늘어나는 &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;그래서 등장한 게 바로 &lt;b&gt;WebFlux&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Non-Blocking I/O 방식은 스레드가 I/O 작업을 대기하는 대신, 다른 작업을 처리할 수 있게 해줍니다. 즉, 요청을 기다리는 동안 스레드는 다른 일을 하게 되는 거죠. 이렇게 되면, Context Switching을 최소화하고 성능을 크게 향상시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebFlux를 사용하면 그 동안의 동기 방식에서 벗어나 비동기적이고 효율적인 웹 애플리케이션을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 점에서 WebFlux는 대규모 트래픽 처리, 실시간 통신, I/O 바운드 작업이 많은 애플리케이션에서 특히 유용합니다.&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;Part of the answer is the need for a non-blocking web stack to handle concurrency with a small number of threads and scale with fewer hardware resources. Servlet non-blocking I/O leads away from the rest of the Servlet API, where contracts are synchronous (Filter,&amp;nbsp;Servlet) or blocking (getParameter,&amp;nbsp;getPart). This was the motivation for a new common API to serve as a foundation across any non-blocking runtime. That is important because of servers (such as Netty) that are well-established in the async, non-blocking space.&lt;br /&gt;&lt;br /&gt;Servlet의 non-blocking I/O는 Servlet API의 나머지 부분과 조화롭지 않습니다. Servlet API의 다른 부분들은 계약이 동기적이거나 (Filter, Servlet) blocking 방식 (getParameter, getPart)이기 때문입니다. 이것이 비동기, non-blocking 환경에서 이미 확고히 자리 잡은 Netty와 같은 서버들 때문에 모든 non-blocking 런타임에서 기반이 될 수 있는 새로운 공통 API가 필요한 이유입니다.&lt;br /&gt;&lt;br /&gt;- 출처 : https://docs.spring.io/spring-framework/reference/web/webflux/new-framework.html&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spring WebFlux 전 핵심 개념, Reactive Programming이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reactive Programming&lt;/b&gt;은 &lt;b&gt;데이터 흐름&lt;/b&gt;을 &lt;b&gt;비동기적이고 선언적으로&lt;/b&gt; 처리하는 방식입니다. 말 그대로 데이터를 &quot;반응&quot;하면서 처리하는 패러다임입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 리액티브 Reactive 란, 어떠한 이벤트나 상황이 발생했을 때 반응을 잘 하는 것을 뜻합니다. 웹 애플리케이션에 있어서는 클라이언트 요청에 따라 즉각적으로 응답을 함을 뜻합니다.&lt;/p&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 WebFlux 의 방식에는 이 Reactive Programming 개념에 기반을 두고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;The term, &amp;ldquo;reactive,&amp;rdquo; refers to programming models that are built around reacting to change&amp;thinsp;&amp;mdash;&amp;thinsp;network components reacting to I/O events, UI controllers reacting to mouse events, and others. In that sense, non-blocking is reactive, because, instead of being blocked, we are now in the mode of reacting to notifications as operations complete or data becomes available.&lt;br /&gt;&lt;br /&gt;&quot;리액티브&quot;라는 용어는 변화에 반응하는 방식으로 구축된 프로그래밍 모델을 의미합니다. 네트워크 구성 요소가 I/O 이벤트에 반응하거나, UI 컨트롤러가 마우스 이벤트에 반응하는 것과 같은 예시를 생각해 볼 수 있습니다. 이러한 관점에서 볼 때, non-blocking은 리액티브하다고 할 수 있습니다. 왜냐하면 blocking 되는 대신 작업이 완료되거나 데이터가 준비되는 시점에 알림에 반응하는 방식이기 때문입니다.&lt;br /&gt;&lt;br /&gt;- 출처 : https://docs.spring.io/spring-framework/reference/web/webflux/new-framework.html&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;리액티브 프로그래밍이란 아래와 같은 정의 아래 &quot;더 빠르고 효율적이고 유연하며 회복 탄력성 있는(장애에 강한) 애플리케이션&quot;을 만드는 철학입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HyYIB/btsL4XCtu4v/ip1Jkr9nTzxOkbDEwxQ6O0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HyYIB/btsL4XCtu4v/ip1Jkr9nTzxOkbDEwxQ6O0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HyYIB/btsL4XCtu4v/ip1Jkr9nTzxOkbDEwxQ6O0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHyYIB%2FbtsL4XCtu4v%2Fip1Jkr9nTzxOkbDEwxQ6O0%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;919&quot; height=&quot;316&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;316&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;비동기 메시지를 기반으로 하여 구성 요소들간의 느슨한 결합, 격리성, 위치 투명성을 보장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;형태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;장애가 발생한 지점이 있더라도 다른 부분의 응답성은 유지되어야 합니다. 느슨한 결합과 격리성, 위치 투명성을 보장하는 이유입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;탄력성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 작업량이 변화하더라도 이에 탄력적으로 대응하며 응답성(Reactive)을 유지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&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;동작하는&amp;nbsp;시스템&amp;nbsp;입니다.&amp;nbsp;우리는&amp;nbsp;이것을&amp;nbsp;리액티브&amp;nbsp;시스템(Reactive&amp;nbsp;Systems)라고&amp;nbsp;부릅니다.&lt;br /&gt;&lt;br /&gt;- 출처 : 리액티브 선언문 https://www.reactivemanifesto.org/ko&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/webflux/new-framework.html#webflux-programming-models&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring Webflux 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reactivemanifesto.org/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;리액티브 선언문&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;책: 실전! 스프링 5를 활용한 리액티브 프로그래밍&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>⚙️ Frameworks, Libraries/  Spring</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/129</guid>
      <comments>https://new-pow.tistory.com/129#entry129comment</comments>
      <pubDate>Sun, 2 Feb 2025 23:51:18 +0900</pubDate>
    </item>
    <item>
      <title>  툴친자의 기록 시도의 기록 : 개인적인 용도 편</title>
      <link>https://new-pow.tistory.com/128</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;2024년도 이제 막 마무리를 하고 있고, 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매년 연말에 다른 다이어리를 사고 그 다이어리를 1개월쓰고 멈췄을 고등학생 시절부터 저는 기록에 미쳐있었고&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;늘 일복이 넘쳐 흘렀던 지금으로서는 한정된 시간에 얼마나 많은 일을 집중해서 할 수 있는가에 많은 시도들을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기록에 있어서도 이런저런 시도들을 했었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 그 변천사와 시도에 대해서 정리해보려고합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저와 같은 목적으로 기록을 시도하는 분들이 이 글을 보고 아이디어를 얻어가셔도 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개인 학습을 정리하자&lt;/b&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;실시간 학습 내용 저장 : 옵시디언 (백업 github)&lt;/li&gt;
&lt;li&gt;정제된 저장 : Tistory 블로그&amp;nbsp;&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;b&gt;Notion 노션&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에도 개인적인 용도의 정리를 했었지만, 일정 관리에 가까웠고 2023년부터 개인 학습한 내용을 정리하기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 첫번째로 시도했던 것은 notion 노션 이었습니다. &lt;a href=&quot;https://new-pow.notion.site/New-pow-TIL-7f785053fa264ea3b25bff4ac010267d?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[직접 작성했던 기록]&lt;/a&gt;&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/6m6by/btsLqxeTuFO/6k4lPW2D4cKAn2GyDkEpG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6m6by/btsLqxeTuFO/6k4lPW2D4cKAn2GyDkEpG1/img.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;1296&quot; data-is-animation=&quot;false&quot; width=&quot;600&quot; height=&quot;630&quot; data-widthpercent=&quot;35.5&quot; style=&quot;width: 34.679%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6m6by/btsLqxeTuFO/6k4lPW2D4cKAn2GyDkEpG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6m6by%2FbtsLqxeTuFO%2F6k4lPW2D4cKAn2GyDkEpG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1234&quot; height=&quot;1296&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D5IhG/btsLrDFoTuE/WvCsLw2QkhaoQ7a0Yh5Js0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D5IhG/btsLrDFoTuE/WvCsLw2QkhaoQ7a0Yh5Js0/img.png&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;1368&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.4097%; margin-right: 10px;&quot; data-widthpercent=&quot;36.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D5IhG/btsLrDFoTuE/WvCsLw2QkhaoQ7a0Yh5Js0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD5IhG%2FbtsLrDFoTuE%2FWvCsLw2QkhaoQ7a0Yh5Js0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1330&quot; height=&quot;1368&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xDpmI/btsLrwTSXJR/nFAaKcS9Ko10TC5gedX5rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xDpmI/btsLrwTSXJR/nFAaKcS9Ko10TC5gedX5rk/img.png&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;1756&quot; data-is-animation=&quot;false&quot; style=&quot;width: 27.5857%;&quot; data-widthpercent=&quot;28.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xDpmI/btsLrwTSXJR/nFAaKcS9Ko10TC5gedX5rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxDpmI%2FbtsLrwTSXJR%2FnFAaKcS9Ko10TC5gedX5rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1330&quot; height=&quot;1756&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션은 가장 오래 썼던 생산성 툴이었고 전 직장에서도 가장 많이 썼기 때문에 익숙했던 툴이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이리 저리 문서 계층을 만드는 데에 가장 자유로웠습니다.&lt;/p&gt;
&lt;p data-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;노션의 포인트는 &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;당시 TIL 보드를 쓸 때는 총 2개의 데이터베이스를 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두개의 데이터베이스를 활용한 TIL 보드는 부트캠프 당시 다른 학생들에게 전파하고 설명할 정도로 유용했으니 해당 템플릿 링크도 첨부합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 데이터베이스는 카테고라이징 하는 용도로 사용하고, 부모-자식 요소 관계를 맺을 수 있게하여 wiki view 로 카테고리를 한 눈에 볼 수 있게 합니다. 과거의 기록을 찾아보기 위해서는 바로바로 찾아볼 수 있는 것이 중요합니다.&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;제가 취업 준비를 하는 시기에는 매일 어떤 공부를 했는지 보여주는 TIL 이 중요한 성과이기도 했지만.&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;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;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;나중에 전체 검색이 편하다  &amp;nbsp;&lt;/li&gt;
&lt;li&gt;실시간으로 함께 작업 가능&lt;/li&gt;
&lt;li&gt;내부적으로 일부 문서만 공개를 제한할 수 있다  &lt;/li&gt;
&lt;li&gt;기능이 많고 유연해서 내 맘대로 커스텀하기 쉽다.&lt;/li&gt;
&lt;li&gt;예쁘게 보여줄 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;노션의 단점&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;보여주는 것에 신경을 너무 많이 쓰게 된다.&lt;/li&gt;
&lt;li&gt;기록에 군더더기가 많아진다. 뭔가 설정을 바꿔준다든지...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드를 작성할 때 불편하다  &lt;/li&gt;
&lt;li&gt;Github 와 연동이 안된다&lt;/li&gt;
&lt;li&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;&lt;b&gt;Obsidian 옵시디언&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션의 한계를 직접 경험하고 옮겨왔던 툴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 플랫폼 기반인 노션에 비해 로컬 파일을 기반으로 구성할 수 있기 때문에 오프라인 작업이나 로컬에서 작성한 코드를 함께 첨부하기에 더 효과적이었습니다. &lt;a href=&quot;https://github.com/new-pow/bookshelf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[직접 작성했던 기록]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;1756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m2HmM/btsLsMuOHtI/ovENvbA9YFKxrMlkbigU9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m2HmM/btsLsMuOHtI/ovENvbA9YFKxrMlkbigU9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m2HmM/btsLsMuOHtI/ovENvbA9YFKxrMlkbigU9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm2HmM%2FbtsLsMuOHtI%2FovENvbA9YFKxrMlkbigU9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2144&quot; height=&quot;1756&quot; data-origin-width=&quot;2144&quot; data-origin-height=&quot;1756&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;TIL처럼 일자별로 학습한 내용을 정리하는 것을 포기하는 대신 문서를 만들고 쓰는 속도가 더 효과적이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵시디언도 많은 확장들이 있지만, 제가 필요한 것을 초반에 몇개 세팅하고 난 다음 별로 신경쓸 필요없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 기록에서 해결해야하는 문제점이 몇가지 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹서비스 기반이 아니다보니 다른 기기에서 접속하기 힘들다는 점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가에게 공유하기 힘들다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 문제점은 옵시디언 valut(root folder) 를 iCloud 에 저장하고&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;깃허브 연동 플러그인 + 깃허브 워크플로우로 README 를 자동으로 갱신시켜주면 어떤 문서들이 있는지 한눈에 확인도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 로컬에서 이미지 파일을 관리하기 싫다면 imgur 에 자동으로 업로드 후 url로 바꿔주는 옵시디언 플러그인을 쓰면 좋습니다.&amp;nbsp;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 관리 플러그인 -&amp;gt; Imgur&lt;/li&gt;
&lt;li&gt;깃허브 자동 commit, pull -&amp;gt; Obsidian-git&lt;/li&gt;
&lt;li&gt;사용하지 않는 파일 삭제 -&amp;gt; File Cleaner&lt;/li&gt;
&lt;li&gt;마크다운 표 잘 사용하기 -&amp;gt; Advanced Tables&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;현재 학습 기록은 이 방식에 정착한 상태입니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;(노션에 비해) 가볍고 빠르게 문서 작성 준비를 할 수 있다.&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;/li&gt;
&lt;li&gt;옵시디언 단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;(깃허브에서도 동일하게 작동하기 위해서는... 무조건 root에서부터 상대 경로 베이스!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;노션의 강력한 데이터베이스 기능들은 포기할 수밖에....&lt;/li&gt;
&lt;li&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;&lt;b&gt;블로그&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막, 개인 학습 기록의 최강자 블로그입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 학습 저장소로 노션이나 옵시디언을 사용했다면 장기 학습 저장소는 역시나 블로그입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 가장 많이 기록을 되찾아보는 것도 블로그입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022년부터 지금까지 블로그도 계속해서 시도해왔었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Velog(2022) -&amp;gt; Tistory(2023) -&amp;gt; github.io(2023 이력서용으로 추가))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 생각하는 것은 블로그 플랫폼도 중요하지만 가장 중요한 것은 꾸준히 나의 이야기를 쓰는 것이라는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 플랫폼은 '접근성이 쉽고 가장 의옥을 내게 만드는가'가 가장 첫번째 기준으로 삼아 선택해야 한다는 지론인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(왜냐면 블로그 쓰기는 정말 장벽이 높기 때문에 반드시 허들이 가장 낮은 곳을 선택해야 합니다 흑흑....)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습기록은 물론 글을 쓰는 당시 내가 어떤 생각을 했는지, 어떤 것을 느꼈는지 남기는 것도 지금의 저에게 큰 자산이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 기록의 차원에서는 '가장 정제된 기록'을 한다는 의의가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 받아쓰는 것이 아닌 나의 해석을 덧붙인 output을 제대로 함으로써 장기 기억으로 승화시킵니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;가장 기억에 많이 남는다  &lt;/li&gt;
&lt;li&gt;나를 소개하기 가장 좋은 기록!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;블로그의 단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;그래서 기록이 귀찮다;;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;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;p data-ke-size=&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>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/128</guid>
      <comments>https://new-pow.tistory.com/128#entry128comment</comments>
      <pubDate>Sun, 22 Dec 2024 23:52:04 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin의 Scope function`filter, map, let, apply, also, run` 를 적절하게 써보자</title>
      <link>https://new-pow.tistory.com/126</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;얼마전 PR리뷰 받을 때, 코드에서 `let` 보다는 `run`이 어울린다는 리뷰를 받은 적이 있습니다.&lt;br /&gt;제가 이것을 크게 구별하지 않고 감(...)으로 사용하고 있었다는 것을 그제야 알아챘죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;코틀린을 쓰다 보면 함수형 프로그래밍을 자연스럽게 접하게 됩니다.&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;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Scope function 이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 표준 라이브러리에 포함된 함수들입니다.&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; 사례와 헷갈리는 것들을 정리한 내용을 소개합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 팀 내의 약속과 프로젝트 일관성을 맞추는 것도 중요합니다만, 아래 사례들로 함수 선택에 참고가 되기를 바랍니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. filter&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`filter`는 컬렉션 안에서 조건에 맞는 데이터만 쏙쏙 골라주는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 내부에서 조건에 맞는 요소들만 남기고 새로운 컬렉션으로 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;반대로 조건에 맞지 않는 함수만 반환하는 `filterNot` 도 매우 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5, 6)
val evenNumbers = numbers.filter { it % 2 == 0 }  // [2, 4, 6]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터링 작업은 데이터 처리할 때 정말 자주 쓰이는데, 불필요한 데이터를 깔끔하게 걸러낼 수 있어서 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 순서도 원래 리스트와 같아서 정렬 걱정도 덜 수 있어요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. map&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map은 컬렉션 안의 요소를 변형해 새로운 값으로 만든 후 새로운 컬렉션을 반환하는 함수입니다.&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;yaml&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map { it * it }  // [1, 4, 9, 16, 25] 제곱수로 반환&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;map이랑 많이 쓰이는 것이 `mapNotNull` 인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map과 동일하게 동작하지만 null 인 요소는 더하지 않는다는 차이점이 있습니다.&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_1728788216538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val userIds = listOf(1, 2, 3, 4, 5)
val users = userIds.mapNotNull { userService.getOrNull(it) }  // 회원 정보 반환시 `null` 인 경우는 컬렉션 요소에서 제거함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. let&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 객체를 변형할 때 주로 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;val yearMonth = &quot;2024-10&quot;
yearMonth.let {
    plusMonth(1) // 코드블록 작성할 때 사용합니다.
}
//
yearMonth.plusMonth(1) // 단일 함수를 연쇄할때는 let을 쓰지 않습니다.&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;그런데 위 예시에서도 얘기하듯 굳이 `let` 을 사용하지 않아도 되는 경우가 많은데요. `let`을 많이 사용하는 부분은 따로 있습니다.&lt;br /&gt;`let` 함수는 Nullable 타입 처리에 매우 유용합니다.&lt;br /&gt;우리가 흔히 널 체크하면서 귀찮을 때 있잖아요? 그럴 때 &lt;code&gt;let&lt;/code&gt;을 쓰면 깔끔하게 null safe하게 코드를 작성할 수 있습니다.&lt;br /&gt;객체가 null이 아닐 때만 실행되는 구조라서 실수할 일도 적고요.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val name: String? = &quot;Alice&quot;
name?.let { // null 일 경우에는 해당 블록은 동작하지 않습니다.
    println(&quot;Name is not null: $it&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;위 예시는 `name` 이 null이 아닐 때만 &quot;Name is not null: Alice&quot;라는 문장을 출력하는 방식이에요. 예전처럼 자바로 일일이 &lt;code&gt;if (name != null)&lt;/code&gt; 쓰지 않아도 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. apply&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply는 객체의 여러 속성이나 함수를 호출하면서, 그 객체를 다시 반환하는 함수입니다.&lt;/p&gt;
&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;apply의 블록 안에서 this를 사용해 객체에 직접 접근하고, 마지막에 객체 자신을 반환하죠. 그래서 apply는 주로 객체 설정과 관련된 코드에서 자주 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728694549547&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val adam = Person(&quot;Adam&quot;).apply { 
	age = 32 
    city = &quot;London&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply는 여러 속성을 한 번에 설정해야 할 때 코드를 간결하게 만들어줘요. 코드를 읽을 때도, &quot;이 객체에 대해 이런 설정을 적용한다&quot;라고 쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. also&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`also`는 `apply`와 비슷하지만 약간 다른 용도로 사용됩니다. &lt;span style=&quot;text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&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;text-align: start;&quot;&gt;객체 자체가 아니라, 그 객체에 대해 무언가를 하고 싶을 때 쓰입니다&lt;/span&gt;. 원본 객체는 그대로 두고요.&lt;br /&gt;그래서 주로 객체 생성 후 로깅, 검증, 부가적인 작업이 필요할 때 사용해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val user = User(&quot;Charlie&quot;).also {
    logger.info(&quot;User created: $it&quot;)
    // 추가 작업 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. run&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run은 앞의 함수들과는 다르게 객체 참조 없이 실행할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;val result = run {
    val x = 10
    val y = 20
    x + y  // 30 반환
}&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;이 코드는 run 블록 내에서 두 값을 더한 결과를 반환하는 방식이에요. 간단한 계산이나 로직을 처리할 때 run을 사용하면 코드가 더욱 깔끔해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 개인적으로 이보다 더 자주 사용하는 것은 `runCatching` 입니다.&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;7. runCatching&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runCatching은 예외 처리를 위한 함수로, 코드 블록에서 발생할 수 있는 예외를 안전하게 처리할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 블록을 실행한 후, 예외가 발생하면 Result.Failure를, 성공하면 Result.Success를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수를 사용하면 try-catch를 일일이 작성하지 않아도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 블록에서 예외가 발생한 것이 이를 호출한 코드의 진행에 영향을 미쳐 종료되는 일을 방지하고 싶을 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;val result = runCatching {
    val numerator = 10
    val denominator = 0
    numerator / denominator  // 예외 발생
}.getOrElse { 
    println(&quot;An error occurred: $it&quot;)  // 예외 메시지 출력
    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;이 코드에서 runCatching은 예외를 감싸서 안전하게 처리하고, 예외가 발생했을 때 기본값을 반환하는 구조를 갖고 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외 처리를 더 깔끔하게 할 수 있는 방법입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리하며&lt;/b&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;
&lt;p data-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;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/scope-functions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kotlin Standard library / Scope functions&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Languages/✔ Kotlin</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/126</guid>
      <comments>https://new-pow.tistory.com/126#entry126comment</comments>
      <pubDate>Sat, 12 Oct 2024 09:41:29 +0900</pubDate>
    </item>
    <item>
      <title>나는 어떻게 살아왔나  ️ </title>
      <link>https://new-pow.tistory.com/125</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&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;하네요&amp;nbsp; &lt;br /&gt;이 글은 글또 10기 지원을 위해 작성을 시작한 글입니다.&amp;nbsp;&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-origin-width=&quot;480&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPqj1Z/btsJDR6Bwtd/KxqNoQ2ojM35KRkp2XM7c0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPqj1Z/btsJDR6Bwtd/KxqNoQ2ojM35KRkp2XM7c0/img.jpg&quot; data-alt=&quot;https://youtu.be/5_j9AB8gmXM&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPqj1Z/btsJDR6Bwtd/KxqNoQ2ojM35KRkp2XM7c0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPqj1Z%2FbtsJDR6Bwtd%2FKxqNoQ2ojM35KRkp2XM7c0%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;480&quot; height=&quot;302&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://youtu.be/5_j9AB8gmXM&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;b&gt;자기소개가 미숙한 사람 은 항상 자기소개할 것을 찾습니다.&lt;/b&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;br /&gt;'자기소개' 라고하면 학부생 2학년 시절 사회학 세미나에서 적었던 자기소개가 가장 먼저 떠오릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 자기소개보다 더 심도있게 어떤 성장 환경과 사회적 맥락에서 지금의 내가 되었는가 솔직하게 작성하고 다른 사람들로부터 피드백을 받는 시간이었습니다. 그러니까 지금 작성하고 있는 글과 굉장히 흡사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 시간 다른 학우들의 &quot;흥미진진한&quot; 이야기를 많이 들어왔었는데요. 막상 제가 주인공인 주간이 되자, A4용지 한 장도 채우기 힘들 만큼 소개할 것들이 부족하다고 느꼈습니다. 여차저차 뭔갈 말하기는 했는데 그게 참 추상적이었나봐요. 그 자리에서 사람들로부터 &quot;어쩌다보니, 그러다가&quot;라는 접두사가 매우 많다는 피드백을 받기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;지금 돌이켜보면 당시 막 성인이 되었던 저는 나의 상처를 누군가에게 보여줄 용기도 없었고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 적당히 어느정도 보여줄만큼 내 과거를 곱씹어 소화시킨 적도 없었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 정작 내가 어떤 사람인지 전혀 몰랐던 것이지요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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;대해&amp;nbsp;묘하게&amp;nbsp;집착하게&amp;nbsp;되었어요.&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;&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;나 혼자만 보는 글이 아니라 누군가에게 도움이 되는 글이자 나를 표현할 수 있는 글을 쓰고 싶다는 욕심도 있고요.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;경험이 가장 큰 가치라고 믿는 사람 은 정말&amp;nbsp;이것저것 해봅니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구나 동료들로부터 &lt;i&gt;&quot;새로운 것을 두려워하지 않는다&quot;&lt;/i&gt;는 말을 자주 듣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식당에 가서 메뉴를 고를 때도 뜬금없이 새로운 것을 골라서 이름 앞에 '기미' 라는 호가 붙기도 했을 정도에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  먼저 먹고 같이 간 친구들에게 내 평가를 전달하고 음식을 권하는 역할을 했었거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마도 어릴 적부터 보수적인 기독교집안에 가정형편이 부유하지 못했던 탓인지 늘 새로운 경험에 대한 갈급이 있었던 것 같아요. 그런 마음이 있어서인지 하고싶은 것은 거의 다 하고 살았다고 자부합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성인이 되고서는 해외 봉사나 여행을 다니는 것을 좋아했어요. (진짜 자극적인 경험의 총집합체) 관련하여 창업팀에서 잠깐 활동하기도 했고. 여름방학 하나를 바쳐 악기를 배우러 시골에 가있기도. 한국을 한 번쯤은 떠나보고 싶어서 워홀을 다녀오기도 했어요.&lt;/p&gt;
&lt;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;이런 다양한 경험을 바탕삼아 첫번째 직업으로 문화예술 기획자를 가질 수 있었어요.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;생각을 현실로 만드는 것을 즐거워 하는 사람 은 기술을 좋아합니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기획자로서 몇 년을 일하다보니 이런 저런 고충이 생겼는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에 심각했던 것 중 하나는 내가 창의력을 쓰는 일에서 소진된다고 느낀다는 것이었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전시, 행사, 교육 등 아무리 새로운 경험을 하러 다녀도 그것에서 계속 더 새로운 아웃풋을 내야 한다는 생각을 하니 무엇을 하든 즐겁지가 않았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일을 하며 가장 괴로웠던 때는 내가 그닥 창의력이 좋은 사람이 아니라는 것을 인정해야 했던 때였던 것 같아요.&lt;br /&gt;&lt;br /&gt;반대로 내가 가장 몰입하며 했던 일은 무엇이었나?&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;것.&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;현실로&amp;nbsp;이루어진다니?!&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;&lt;i&gt;기획자에서 어떻게 개발자로 이직을 생각하게 되었는지는 다른 글에서 확인해주세요..!&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://new-pow.tistory.com/23&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.02.01 - [  Smalltalk] - 2022년 회고 (1) 커뮤니티 기획자가 개발자를 꿈꾸게된 이야기&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이 글을 마무리하며&lt;/b&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;a href=&quot;https://0juuu.tistory.com/25&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;완벽한 글을 작성해야 한다는 생각에 글쓰는 것에 막연한 두려움이 있는 것은 저도 가지고 있던 고민이었거든요.&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;아직도 글로 쓰고 싶은 소재는 참 많은데... 임시보관함에 있는 채로 거의 1달이 지나가고 있는 것들이 있는데.....  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두려움을 떨치고 차차 글을 마무리를 해나가고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글또 안되더라도 다음에 또 다른 글로 올게요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/125</guid>
      <comments>https://new-pow.tistory.com/125#entry125comment</comments>
      <pubDate>Wed, 18 Sep 2024 15:59:11 +0900</pubDate>
    </item>
    <item>
      <title>Test: Test Container 도입 이후 겪은 문제들   (느려진 테스트, 동시성 문제 등 해결하기 at: Kotlin Exposed + Kotest)</title>
      <link>https://new-pow.tistory.com/124</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;테스트 컨테이너를 도입하여 실제 서비스 DB인 postgresql로 테스트를 할 수 있도록 하는 일이었습니다.&lt;/p&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가 h2 인메모리로 사용 중이었는데요. 도입하기 매우 가볍고 빠르다는 장점이 있었지만, 여러 단점들도 많았습니다.&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;H2에서 테스트할 수 있는 쿼리만 사용할 수 있음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`ON CONFLICT` 등의 쿼리는 사용할 수 없음.&lt;/li&gt;
&lt;li&gt;`DISTINCT`을 사용할 때 H2에서는 동작하던 쿼리가 Postgresql에서는 오류를 일으키기도 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Postgresql에서 사용하던 설정이나 컬럼 등을 사용할 수 없음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-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;419&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JLqxG/btsKmQElt6s/1bPzElkVs9WlzKuHJk5Fuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JLqxG/btsKmQElt6s/1bPzElkVs9WlzKuHJk5Fuk/img.png&quot; data-alt=&quot;출처 : https://billykorando.com/2019/03/27/testcontainers-bringing-sanity-to-integration-testing/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JLqxG/btsKmQElt6s/1bPzElkVs9WlzKuHJk5Fuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJLqxG%2FbtsKmQElt6s%2F1bPzElkVs9WlzKuHJk5Fuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;419&quot; height=&quot;499&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://billykorando.com/2019/03/27/testcontainers-bringing-sanity-to-integration-testing/&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;매번 이미 테스트를 했는데도 클라우드 환경에서 동작이 문제가 있는지 확인해야하고 특정 쿼리를 사용하면 간단하게 풀릴 수 있는 문제를 해결하기 위해 성능이 저하되거나 코드가 복잡해지는 등의 문제를 겪어서 테스트 컨테이너를 도입하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 공식문서와 다른 분들이 작성해주신 블로그를 가장 많이 참고하며 구현하였습니다. &lt;span style=&quot;text-align: start;&quot;&gt;이에 대한 설명은 이미 다른 블로그에서 많이 다루었기 때문에 저는 새로운 인사이트를 얻게된다면 추후&lt;/span&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;a href=&quot;https://java.testcontainers.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Testcontainers for Java&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저는 Kotlin 코드로 바꾸어 진행하였습니다. 이전에 H2 DB를 연결시켜주었던 대신 정의한 Test container 의 url, port 등을 설정해주면 되었습니다.&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 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고: 테스트 환경&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;Kotlin/Spring&lt;/li&gt;
&lt;li&gt;Kotest 단위 테스트를 위해 사용하고 있는 프레임워크&lt;/li&gt;
&lt;li&gt;Cucumber API 테스트를 위해 사용하고 있는 프레임워크&lt;/li&gt;
&lt;li&gt;Exposed 프레임워크&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;테스트 컨테이너를 도입하며 발생한 문제...&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 도입하는 것은 어렵지 않았기 때문에 빠르게 `Postgresql` 과 `Redis` 에 적용하였는데요.&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;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; : 하나의 DB 컨테이너를 띄우고 그 안에서 계속 테스트를 하다보니 데이터의 일관성이 깨지는 문제점이 있었습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러건의 데이터를 조회하는 경우 기대 데이터가 유효하지 않아졌습니다. (데이터 5개를 기대하고 페이지를 조회했을 때, 훨씬 많은 데이터가 조회될 수 있음)&lt;/li&gt;
&lt;li&gt;난수로 생성하는 데이터들에서 충돌이 발생할 확률이 증가하였습니다.&lt;/li&gt;
&lt;li&gt;매번 테이블이 초기화 되는 것을 상정하고 상수로 생성했던 데이터들의 수정이 필요했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 시간 지연&lt;/b&gt; : H2 로 테스트하는 것과는 달리 Postgresql로 테스트를 했을 때, 테스트 시간이 훨씬 지연되었습니다.
&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;연쇄적으로 Github workflow 가 자동으로 취소기도함 (기본적으로 Github workflow timeout은 30분입니다)&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;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;&lt;b&gt;테스트를 빠르게 만드는 방법들&lt;/b&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;&lt;b&gt;테스트 컨테이너 스펙 조정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 컨테이너도 도커 컨테이너기 때문에 띄울때 커맨드로 추가로 옵션을 줄 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 예시 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729932582300&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public PostgreSQLContainer container = new PostgreSQLContainer&amp;lt;&amp;gt;()
            .withCommand(&quot;postgres -c max_prepared_transactions=10&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;fsync=off&lt;/b&gt;: 파일 동기화를 비활성화하여 데이터베이스의 쓰기 성능을 높입니다. (일부 데이터 손실 가능성 있습니다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;synchronous_commit=off&lt;/b&gt;: 커밋 시 동기화를 비활성화하여 성능을 높입니다. (일부 데이터 손실 가능성 있습니다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;full_page_writes=off&lt;/b&gt;: 모든 페이지를 다시 기록하는 것을 비활성화하여 성능을 높입니다. (일부 데이터 손실 가능성 있습니다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;shared_buffers=1024MB&lt;/b&gt;: 공유 버퍼 크기를 1024MB로 설정하여 메모리 성능을 조정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;work_mem=1024MB&lt;/b&gt;: 쿼리 정렬 및 해시 연산에 사용할 메모리를 1024MB로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;max_connections=100&lt;/b&gt;: 최대 연결 수를 100개로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;autovacuum=off&lt;/b&gt;: autovacuum 을 비활성화합니다.&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;/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;max_worker_processes=5&lt;/b&gt;: 최대 워커 프로세스 수를 5개로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;max_parallel_workers=N&lt;/b&gt;: 최대 병렬 작업 프로세스 수를 N개로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;max_wal_size=128MB&lt;/b&gt;: WAL(Write-Ahead Logging) 최대 크기를 128MB로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;checkpoint_timeout=30min&lt;/b&gt;: 체크포인트 간격을 30분으로 설정합니다.&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 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대량 데이터 입력 개선을 위한 Exposed `batchInsert` 도입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 속도를 개선하기 위해 리팩토링했던 부분 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트의 Given 부분에서 기존에는 반복문을 돌며 DB에 insert를 해주었는데요. 이에 대해서 insert를 한꺼번에 하도록 수정하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 테스트를 위한 라이브러리로 &lt;a href=&quot;https://github.com/JetBrains/Exposed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JetBrain의 Exposed&lt;/a&gt; 를 사용하고 있었는데, 여기서 batchInsert 라는 함수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730034360967&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SomeTable.batchInsert(dataList) { data -&amp;gt;
    this[SomeTable.column1] = data.value1
    this[SomeTable.column2] = data.value2
    this[SomeTable.column3] = data.value3
}&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;다만 주의해야할 것은 exposed에서 지원하는 이 함수는, &lt;u&gt;&lt;b&gt;실제로 DB에 여러건을 insert하는 쿼리를 실행시키는 것이 아니라 한 트랜젝션에서 실행되는 것이라는 점&lt;/b&gt;&lt;/u&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;NOTE: The batchInsert function will still create multiple INSERT statements when interacting with your database. You most likely want to couple this with the rewriteBatchedInserts=true (or rewriteBatchedStatements=true) option of your relevant JDBC driver, which will convert those into a single bulkInsert. You can find the documentation for this option for MySQL here and PostgreSQL here.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처 : &lt;a href=&quot;https://stackoverflow.com/questions/57612163/kotlin-exposed-batch-insert-not-working-as-documented&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/57612163/kotlin-exposed-batch-insert-not-working-as-documented&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 생성시 `&lt;span style=&quot;text-align: left;&quot;&gt;rewriteBatchedInserts` 옵션을 켜면 batchInsert를 효율적으로 할 수 있습니다. &lt;a href=&quot;http://%20 Database.connect(PGSimpleDataSource().apply {     setURL(&amp;quot;jdbc:postgresql://localhost:5432/postgres&amp;quot;)     user = &amp;quot;pguser&amp;quot;     password = &amp;quot;pgpass&amp;quot;     reWriteBatchedInserts = true   })&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고링크&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 `shouldReturnGeneratedValues` 옵션을 false 로 변경하여 반환되는 데이터를 받지 않음으로서 테스트 시간을 단축할 수 있었습니다. (UUID를 사용하였음) &lt;a href=&quot;https://cheese10yun.github.io/exposed/&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평균 19%가량 개선됨을 확인했습니다.&lt;/li&gt;
&lt;li&gt;특정 테스트의 경우 다음의 변화를 보였습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GetAllXXXByXXXSpec :&amp;nbsp;615 ms &amp;rarr;&amp;nbsp;&amp;nbsp;498 ms&lt;/li&gt;
&lt;li&gt;XXXUpdateXXXSpec : 492 ms &amp;rarr; 401 ms&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 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;테스트 환경 설정을 통해 테스트를 병렬적으로 실행&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;Kotest에서 제공하는 병렬 실행 기능을 최대한 활용해 테스트 시간을 줄일 수 있습니다.&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;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;
&lt;pre id=&quot;code_1730035938238&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object TestProjectConfig : AbstractProjectConfig() {

    // 각 Spec 마다 독립적인 인스턴스 생성 (테스트 간 간섭 최소화)
    override val isolationMode: IsolationMode = IsolationMode.InstancePerSpec

    // 병렬 실행 시 최대 10개의 테스트를 동시에 실행하도록 설정
    override val parallelism: Int = 10

    // 동시에 실행 가능한 테스트 스펙의 최대 개수를 제한
    @ExperimentalKotest
    override val concurrentSpecs: Int = 10

    // 각 스펙 내에서 동시에 실행 가능한 테스트를 제한
    @ExperimentalKotest
    override val concurrentTests: Int = 5
}&lt;/code&gt;&lt;/pre&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;isolationMode:&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;InstancePerLeaf&lt;/b&gt; 옵션은 각 &quot;leaf&quot;(테스트 케이스)마다 독립적인 인스턴스를 생성하여 실행하므로 테스트 간에 영향을 주지 않는 것이 특징입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SingleInstance&lt;/b&gt; 는 모든 인스턴스를 싱글톤으로 생성합니다. deprecated 되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;InstancePerSpec&lt;/b&gt; 으로 설명하면 전테 스펙(테스트 클래스) 내의 모든 테스트에 대해 하나의 인스턴스를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;InstancePerTest&lt;/b&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;유리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;parallelism&lt;/b&gt; 병렬 실행시 최대 몇개의 테스트를 동시에 실행하도록 설정할 것인지 결정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;concurrentSpecs&lt;/b&gt; 동시에 실행 가능한 테스트 스펙의 최대 개수를 제한합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;concurrentTests&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 DB데이터를 조회하거나 삽입하는 테스트에서는 보수적으로 설정하는 것이 좋다는 것입니다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;기타 진행했던/고려했던 방법들&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;위에 작성한 방법 외에도 리팩토링을 굉장히 많이 했습니다. 반복되는 코드를 정리하고, 속도를 위해 &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;이에 대해서는 확실히 개선된다는 테스트 해보지 못하였기 때문에 메모로만 남깁니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;transaction 블록 안에서 데이터 추가, 테스트 진행 후 rollback을 호출하여 데이터를 초기화합니다.&lt;/li&gt;
&lt;li&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;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;&lt;b&gt;마무리하며...&lt;/b&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;p data-ke-size=&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;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>  Practice</category>
      <category>exposed</category>
      <category>Kotlin</category>
      <category>spring</category>
      <category>test</category>
      <category>test_container</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/124</guid>
      <comments>https://new-pow.tistory.com/124#entry124comment</comments>
      <pubDate>Sat, 31 Aug 2024 09:37:29 +0900</pubDate>
    </item>
    <item>
      <title>K8s : Master node에 Pod 가 스케줄링 되지 않는 이유</title>
      <link>https://new-pow.tistory.com/123</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 처음 공부하게 되면 Master node와 Worker node에 대한 개념부터 배우기 시작합니다.&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;쿠버네티스에서 Master Node에 Pod가 스케줄링되지 않는 이유는 여러 가지가 있지만, 가장 주된 이유는 Master Node의 안정성과 성능을 유지하기 위해서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구현하기 위해 쿠버네티스는 `Taints`와 `Tolerations`라는 메커니즘을 사용합니다.&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;b&gt;Taints와 Tolerations&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Taints와 Tolerations는 노드와 파드 간의 특정 조건을 설정하여 특정 파드가 특정 노드에 스케줄링될 수 없도록 하거나, 특정 조건을 만족하는 경우에만 스케줄링될 수 있도록 합니다.&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;Taints&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Taints 는 더러움, 얼룩, 흔적이라는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드에 Taints 즉, 얼룩이 있다면 파드에는 이를 견딜수있는 tolerations 가 있어야 해당 노드에 할당될 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl taint node ip-172-31-30-28.us-west-2.compute.internal alicek106/iirin-taint=dirty:NoSchedule&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 노드에 &lt;code&gt;alicek106/iirin-taint=dirty:NoSchedule&lt;/code&gt; 라는 taint를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;kubectl taint node ip-172-31-30-28.us-west-2.compute.internal alicek106/iirin-taint=dirty:NoSchedule-&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전에 설정된 taint를 제거하여 해당 노드가 다시 스케줄링 가능하도록 만듭니다.&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Taint 효과 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`NoSchedule` 해당 노드에 새로운 파드가 스케줄링되지 않습니다.&lt;/li&gt;
&lt;li&gt;`PreferNoSchedule` 가능한 해당 노드에 파드가 스케줄링되지 않도록 합니다.&lt;/li&gt;
&lt;li&gt;`NoExecute` 해당 노드에서 파드가 실행되지 않도록 하며, 이미 실행 중인 파드도 퇴거시킵니다.&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;b&gt;Tolerations&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드가 특정 taint를 무시하고 해당 노드에 스케줄링될 수 있도록 하는 설정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tolerations를 통해 특정 파드가 특정 노드에 스케줄링될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod, Reflicaset 등에 적용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722039325813&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations: # pod에 정의하는 방법
  - key: &quot;example-key&quot;
    operator: &quot;Exists&quot;
    effect: &quot;NoSchedule&quot;&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_1722039506776&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kind: Runner
spec:
  replicas: 2
    spec:
      labels:
        - test
      tolerations:
        - key: capacity
          value: spot
          operator: Equal
        - key: kind
          value: runner
          operator: Equal&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;&lt;b&gt;마스터 노드의 Taints&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마스터 노드에 파드가 할당되지 않는 주된 이유는 마스터 노드에 taint가 설정되어 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`:NoExcute` 효과의 taint를 허용할 수 있는 파드가 마스터 노드에서 실행되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;Cordon과 Drain&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Cordon&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;kubectl cordon &amp;lt;node_name&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 노드를 cordon하여 더 이상 새로운 파드가 해당 노드에 스케줄링되지 않도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이미 실행 중인 파드에는 영향을 미치지 않습니다. &lt;code&gt;uncordon&lt;/code&gt; 명령어를 사용하여 cordon 상태를 해제할 수 있습니다.&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;Drain&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;kubectl drain &amp;lt;node_name&amp;gt; --force&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;노드 유지보수 등의 이유로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 파드로 생성된 파드가 있다면 drain이 실패할 수 있으므로&amp;nbsp;&lt;code&gt;--force&lt;/code&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;b&gt;PodDisruptionBudget (PDB)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PodDisruptionBudget은 특정 파드의 개수를 유지하기 위한 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod&amp;nbsp;퇴거가&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: pdb
spec:
  minAvailable: 80%
  selector:
    matchLabels:
      app: my-app&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;&lt;b&gt;주의사항&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;kubectl apply -f 06-simple-pdb-ex.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;error: resource mapping not found for name: &quot;iirin-simple-pdb-example&quot; namespace: &quot;&quot; from &quot;06-simple-pdb-ex.yaml&quot;: no matches for kind &quot;PodDisruptionBudget&quot; in version &quot;policy/v1beta1&quot;
ensure CRDs are installed first&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;혹시 위와 같은 에러가 발생한다면, `policy/v1beta1` 버전을 더이상 지원하지 않기 때문입니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;PodDisruptionBudget의 베타 API 버전(policy/v1beta1)은 버전 1.25를 기준으로 더 이상 제공되지 않습니다. 이 API는 버전 1.21에서 지원 중단되었습니다.&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs/deprecations/apis-1-25?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고링크&amp;nbsp;https://cloud.google.com/kubernetes-engine/docs/deprecations/apis-1-25?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&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;</description>
      <category>☁️ Infra</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/123</guid>
      <comments>https://new-pow.tistory.com/123#entry123comment</comments>
      <pubDate>Sat, 27 Jul 2024 09:26:38 +0900</pubDate>
    </item>
    <item>
      <title>Inline 함수, 왜 쓸까?</title>
      <link>https://new-pow.tistory.com/121</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 코틀린을 계속 사용하며 아직까지 익숙하지 않은 키워드가 몇가지 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 하나가 `Inline`, `noInline` 키워드였습니다.&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;b&gt;Kotlin 인라인 함수로 성능과 가독성 향상시키기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin에서 인라인 함수는 코드의 성능과 가독성을 향상시켜 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출의 오버헤드를 제거함으로써, 인라인 함수는 더 빠른 실행과 더 나은 최적화를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;고차 함수와 인라인 키워드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차 함수는 각 호출 시마다 새롭게 메모리를 할당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 디컴파일하여 바이트 코드를 보면, 각 호출 시 새로운 객체가 생성되고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 함수가 계속 반복되는 for 루프에서 사용된다면 성능에 큰 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;fun main() {
    noinlineFun { println(&quot;Hello, World!&quot;) }
}

// 고차함수
fun noinlineFun(function: () -&amp;gt; Unit) {
    function()
}&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;아래 코드가 GPT에게 요청한 예상되는 디컴파일 결과입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1720220108034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

public final class MainKt {
    public static final void main() {
        noinlineFun((Function0)(new Function0() {
            @NotNull
            public final Unit invoke() {
                System.out.println(&quot;Hello, World!&quot;);
                return Unit.INSTANCE;
            }
        }));
    }

    public static final void noinlineFun(@NotNull Function0&amp;lt;Unit&amp;gt; function) {
        Intrinsics.checkNotNullParameter(function, &quot;function&quot;);
        function.invoke();
    }
}&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;그래서 `inline` 키워드를 통해 함수 호출이 아니라 컴파일 단계에 함수 자체를 같이 컴파일하여 최적화할 수 있는 것입니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;fun main() {
    inlineFun { println(&quot;Hello, World!&quot;) }
}

// 고차함수
inline fun inlineFun(function: () -&amp;gt; Unit) {
    function()
}&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_1720219177091&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    // inlineFun 함수 호출부가 다음과 변경됩니다.
    println(&quot;Hello, World!&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인라인 함수의 특징&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;인라인 함수는 로컬 반환뿐만 아니라 비로컬 반환도 허용합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;인라인 함수는 public 또는 internal로 선언되지 않은 한 주변 클래스나 Scope의 private 변수 및 메서드에 접근할 수 없습니다.&lt;/li&gt;
&lt;li&gt;인라인 함수는 주로 2-3줄 정도의 짧은 함수에 사용합니다.
&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;/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;b&gt;&lt;code&gt;crossinline&lt;/code&gt; 및 &lt;code&gt;noinline&lt;/code&gt; 키워드&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Crossinline&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`crossinline` 키워드는 람다를 인라인 함수에 전달할 때 비로컬 반환을 방지하기 위해 사용됩니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    performAction {
        println(&quot;Start of action&quot;)
        nestedAction {
            println(&quot;Inside nested action&quot;)
            // return // 비로컬 반환을 할 수 없습니다.
        }
        println(&quot;End of action&quot;)
    }
}

inline fun performAction(action: () -&amp;gt; Unit) {
    println(&quot;Performing action&quot;)
    action()
}

inline fun nestedAction(crossinline nested: () -&amp;gt; Unit) {
    println(&quot;Starting nested action&quot;)
    nested()
    println(&quot;Ending nested action&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Noinline&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`inline` 키워드로 표시된 함수는 기본적으로 모든 함수 매개변수가 인라인되지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 반복문에서 사용하거나 프로젝트의 여러 부분에서 사용할 때 인라인이 되지 않기를 바라는 때도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    performActions({
        println(&quot;inlined 합니다.&quot;)
    }, {
        println(&quot;noinline 합니다.&quot;)
    })
}

// 고차 함수
inline fun performActions(inlinedAction: () -&amp;gt; Unit, noinline noinlineAction: () -&amp;gt; Unit) {
    println(&quot;Starting actions&quot;)
    inlinedAction()
    noinlineAction()
    println(&quot;Ending actions&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;이럴 때는 `noinline` 키워드로 컴파일시 함수를 복사하여 넣어주지 않고 함수를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인라인 함수와 리턴&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 함수의 중요한 특징 중 하나는 비로컬 반환을 허용한다는 점입니다. 일반 함수에서는 람다 내에서 &lt;code&gt;return&lt;/code&gt;을 사용하면 로컬 반환만 가능합니다. 하지만 인라인 함수에서는 다음과 같이 비로컬 반환이 가능합니다:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;inline fun inlineFunction(lambda: () -&amp;gt; Unit) {
    lambda()
}

fun main() {
    inlineFunction {
        println(&quot;Before return&quot;)
        return  // 비로컬 반환
    }
    println(&quot;This line will not be executed&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;code&gt;crossinline&lt;/code&gt; 키워드를 사용하여 이를 방지할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;inline fun crossinlineFunction(crossinline lambda: () -&amp;gt; Unit) {
    lambda()
}

fun main() {
    crossinlineFunction {
        println(&quot;Before return&quot;)
        // return  // 컴파일 오류 발생
    }
    println(&quot;This line will be executed&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;</description>
      <category>  Languages/✔ Kotlin</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/121</guid>
      <comments>https://new-pow.tistory.com/121#entry121comment</comments>
      <pubDate>Sat, 29 Jun 2024 09:42:29 +0900</pubDate>
    </item>
    <item>
      <title>Coroutine이 취소되는 기본 동작 방식</title>
      <link>https://new-pow.tistory.com/119</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;코루틴의 취소&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 실행 중에 취소 요청에 의해 취소되거나 예외가 발생하여 취소될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴이 취소되는 것이 중요한 이유는 필요하지 않은 코루틴을 적절하게 취소하여 컴퓨팅 자원을 효율적으로 사용하는 것이 중요하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 바로 스레드를 죽이는 것은 좋지 않은 해결방법입니다. 스레드에서 활용중이던 리소스 연결을 먼저 해제한 후 취소하는 것이 더 우아한 방법이겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;취소하는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴을 취소하는 기본적인 방법은 `cancel` 함수를 사용하는 방법입니다.&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;취소 시점으로부터 첫번째 중단 지점(suspend 함수 사용 지점)부터 취소가 이루어집니다.&lt;/li&gt;
&lt;li&gt;하위 job이 있는 경우 모두 취소합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1717804519446&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun main(): Unit = coroutineScope {
   val job = launch {
       // job work
   }
   job.cancel()
   job.join() // 혹은 cancelAndJoin()
}&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;위의 특징에서도 보이듯이 `cancel()` 함수만으로 코루틴을 중단할 수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취소 여부 체크를 하기 위해 중단 지점으로 인위적으로 `delay()`, `yield()` 같은 coroutines 패키지의 `suspend` 함수를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런 suspend 패키지의 함수를 사용하지 않았다면 취소가 이루어지지 않습니다. 해당 코루틴의 동작이 모두 수행되고나서야 취소됩니다.&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;  어떻게 취소되는 거지? `Cancellation&amp;nbsp;Exception`&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 코루틴이 멈추는 모든 로직은 이 예외를 발생시켜 멈춥니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 `Cancellation Exception` 을 try-catch 한다면 코루틴은 멈추지 않습니다. 혹은 `Cancellation Exception` 를 상속받은 다른 예외를 통해 코루틴을 멈출 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 중단지점마다 본인의 상태를 확인해 취소 요청을 받으면 그냥 중지되는 것이 아니라&amp;nbsp; 내부적으로 `Cancellation Exception` 를 던집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717805290518&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val job = lanch(Dispatchers.Default) {
	if (isActive) { // 취소 신호가 있는지 확인할 수 있습니다.
		throw CancellationException()
	}
}

// ---

job.cancel()&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코루틴이 `CancellationException`과 일반 예외를 처리하는 차이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발생한 예외가 `CancellationException`이라면&lt;b&gt; 취소 요청&lt;/b&gt;으로 간주하고 부모 코루틴에게 전파하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 그 이외의 예외가 발생한 경우 코루틴 실행 &lt;b&gt;실패&lt;/b&gt;로 간주하고 부모 코루틴에게 전파합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로는 두 가지 케이스 모두 '취소됨'으로 관리합니다. 즉, `CancellationException` 을 던집니다.&lt;/p&gt;
&lt;p data-ke-size=&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고자료
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;인프런 강의 : 2시간으로 끝내는 코루틴&lt;/li&gt;
&lt;li&gt;코틀린 코루틴의 취소 &lt;a href=&quot;https://dgahn.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dgahn.tistory.com/19&lt;/a&gt;&amp;nbsp;&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;</description>
      <category>  Languages/✔ Kotlin</category>
      <category>coroutine</category>
      <category>Kotlin</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/119</guid>
      <comments>https://new-pow.tistory.com/119#entry119comment</comments>
      <pubDate>Sat, 25 May 2024 01:15:10 +0900</pubDate>
    </item>
    <item>
      <title>Coroutine의 개념 이해한대로 정리하기</title>
      <link>https://new-pow.tistory.com/118</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가기 전에&lt;/b&gt;&lt;/h2&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;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;컨텍스트 스위치 context switch&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;/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;b&gt;Coroutine&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;code&gt;co&lt;/code&gt;는 '협력하는'이라는 의미가 있는 접두사입니다. &lt;code&gt;routine&lt;/code&gt;은 컴퓨터 공학에서 이야기하는 루틴입니다. 즉, 협력하는 함수라는 의미입니다.&lt;/li&gt;
&lt;li&gt;Coroutine은 Kotlin 언어를 개발한 Jetbrain 이 개발자들이 겪는 스레딩 문제를 직관적인 방식으로 해결할 수있도록 추가한 기능입니다.&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;b&gt;Routine 과 Coroutine&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;일반적인 Routine은 다음과 같은 시퀀스 다이어그램을 가집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;    +--------------+          +-------------+
    | Main Routine |          | sub Routine |
    +--------------+          +-------------+
         |                           |
         |        routine 시작        |
         |--------------------------&amp;gt;|
         |                           |
         |                           |
         |                           |
         |                           |
         |                           |
         |                           | 
         |&amp;lt;--------------------------|
         |        routine 종료       초기화
         |&lt;/code&gt;&lt;/pre&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;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;반면 아래는 coroutine 에 대한 시퀀스 다이어그램입니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    +----------------+          +---------------+
    | Main coroutine |          | sub coroutine |
    +----------------+          +---------------+
         |                              |
         |         routine 시작          |
         |-----------------------------&amp;gt;|
         |             중단              |
         |&amp;lt;-----------------------------|
         |                              |
         |                              |
         |                              |
         |             재개              |
         |-----------------------------&amp;gt;|
         |&amp;lt;-----------------------------|
         |         routine 종료
         |&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;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;/li&gt;
&lt;li&gt;코루틴이 완전히 종료되기 전까지 중단된 코루틴 함수 안에 있는 정보는 메모리에서 제거되지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 코루틴이 완전히 종료되기 전까지 메모리에서 해당 코루틴에 대한 정보가 계속 남아있다는 점이 매우 중요합니다. 왜 중요한지는 스레드와 코루틴의 관계와 차이점을 보면 명확히 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Thread 와 Coroutine&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급하였듯이 코루틴은 스레드보다 더 작은 작업단위이기도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:1400/format:webp/1*fg9-h6CWPz8p_pmHGktpyg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이미지 출처 : &lt;a href=&quot;https://medium.com/nerd-for-tech/a-journey-from-callback-hell-to-kotlin-coroutines-episode-1-98b52821b323&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/nerd-for-tech/a-journey-from-callback-hell-to-kotlin-coroutines-episode-1-98b52821b323&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 코드 종류 중 하나이기 때문에 이를 실행하려면 스레드가 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루틴을 실행하려면 스레드가 배정되어 있어야 하는 것처럼요.&lt;/p&gt;
&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;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;시퀀스 다이어그램에서 코루틴은 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;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;      A thread
    +----------------+          +---------------+
    | Main coRoutine |          |  sub coRoutine |
    +----------------+          +---------------+
         |                              |
         |    routine 시작(B thread)     |
         |-----------------------------&amp;gt;|
         |             중단              |
         |&amp;lt;-----------------------------|
         |                              |
         |                              |
         |                              |
         |         재개(C thread)        |
         |-----------------------------&amp;gt;|
         |&amp;lt;-----------------------------|
         |         routine 종료
         |&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;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;     A thread                    B thread                    C thread
  +----------------+          +-----------------+          +-----------------+
  | Main coroutine |          | Sub coroutine-1 | 동일 코루틴 | Sub coroutine-2 |
  +----------------+          +-----------------+          +-----------------+
       |                              |                            
       |     routine 시작              |                            
       |-----------------------------&amp;gt;|                            
       |                              |                            
       |             중단              |                            
       |&amp;lt;-----------------------------|                            
       |                                                           
       |                                                           
       |                                                           
       |                                       재개                 
       |----------------------------------------------------------&amp;gt;|
       |                                                           |
       |&amp;lt;----------------------------------------------------------|
       |         routine 종료                                       
       |                                                           
&lt;/code&gt;&lt;/pre&gt;
&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;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;OS에 의해서 컨텍스트 스위칭이 일어납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;코드를 통해 개발자가 코루틴이 다른 코루틴에게 실행을 양보하게 할 수 있습니다. (`yield()` 함수 등 subroutine 간의 상호작용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비동기 프로그래밍을 더 쉽고, 더 가볍게&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 효율적인 애플리케이션 성능을 위해 스레드간 컨텍스트 스위칭을 이용하여 비동기 프로그래밍을 구현할 수 있습니다.&lt;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;점.&amp;nbsp;그리고&amp;nbsp;코드가&amp;nbsp;복잡해진다는&amp;nbsp;단점이&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yafju/btsHuePuVMY/wUMGo63yiPMJSo7b6au870/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yafju/btsHuePuVMY/wUMGo63yiPMJSo7b6au870/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yafju/btsHuePuVMY/wUMGo63yiPMJSo7b6au870/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/yafju/btsHuePuVMY/wUMGo63yiPMJSo7b6au870/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;396&quot; height=&quot;288&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 지옥은 불시에 찾아올 수 있습니다   (이미지 출처 :&amp;nbsp;&lt;a href=&quot;https://medium.com/nerd-for-tech/a-journey-from-callback-hell-to-kotlin-coroutines-episode-1-98b52821b323)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/nerd-for-tech/a-journey-from-callback-hell-to-kotlin-coroutines-episode-1-98b52821b323)&lt;/a&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;예를 들어 아래는 GPT와 함께 짜본 스레드 카페 예시입니다. 동기로 짜는 것보다 `Callback`, `Future` 등 주문을 기다리거나 커피 만드는 것을 기다리는 간단한 비동기 함수인데 코드의 복잡도가 쉽게 올라감을 알 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Cafe {
    fun takeOrder(order: Order) {
        synchronized(orderQueue) {
            orderQueue.add(order)
        }
    }

    fun makeCoffeeAsync(): CompletableFuture&amp;lt;Order&amp;gt; {
        return CompletableFuture.supplyAsync {
            var order: Order? = null
            synchronized(orderQueue) {
                if (orderQueue.isNotEmpty()) {
                    order = orderQueue.removeAt(0)
                }
            }
            if (order != null) {
                Thread.sleep(2000L) // 커피 만드는 시간
                order
            } else {
                null
            }
        }
    }

    fun serveCoffeeAsync(order: Order) {
        CompletableFuture.runAsync {
            Thread.sleep(500L) // 커피 서빙 시간
            synchronized(completedOrders) {
                completedOrders.add(order)
            }
        }
    }
}

fun main() {
    val cafe = Cafe()
    val executor = Executors.newFixedThreadPool(3)

    val orderTask = CompletableFuture.runAsync {
        cafe.takeOrder(Order(&quot;Alice&quot;, &quot;Latte&quot;))
        Thread.sleep(1000L)
        cafe.takeOrder(Order(&quot;Bob&quot;, &quot;Espresso&quot;))
        Thread.sleep(1000L)
        cafe.takeOrder(Order(&quot;Charlie&quot;, &quot;Cappuccino&quot;))
    }

    val coffeeMakerTask = CompletableFuture.supplyAsync {
        while (true) {
            cafe.makeCoffeeAsync().thenAcceptAsync { order -&amp;gt;
                if (order != null) {
                    cafe.serveCoffeeAsync(order)
                }
            }.join()
        }
    }

    orderTask.thenRunAsync(coffeeMakerTask, executor)

    try {
        Thread.sleep(10000L)
    } catch (e: InterruptedException) {
        e.printStackTrace()
    }

    executor.shutdown()
}&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;Kotlin 에서는 Coroutine 이 해결책으로 등장한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;그리고 OS에게 작업 스케줄링을 넘기지 않고, 코드를 통해 개발자가 직접 작업을 스케줄링할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Refs.&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.wonderwall.kr/articles/coroutinedeepdive/&quot;&gt;https://tech.wonderwall.kr/articles/coroutinedeepdive/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://seunghyun.in/android/7/&quot;&gt;https://seunghyun.in/android/7/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;인프런 강의 : 2시간으로 끝내는 코루틴&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/coroutines-guide.html&quot;&gt;https://kotlinlang.org/docs/coroutines-guide.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Languages/✔ Kotlin</category>
      <category>coroutine</category>
      <category>Kotlin</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/118</guid>
      <comments>https://new-pow.tistory.com/118#entry118comment</comments>
      <pubDate>Sat, 18 May 2024 08:09:56 +0900</pubDate>
    </item>
    <item>
      <title>  Error: EKS 워크 노드 생성에 실패했다... NodeCreationFailure : Unhealthy nodes in the kubenetes cluster</title>
      <link>https://new-pow.tistory.com/116</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;발생 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS EKS 클러스터를 만드는 도중, 워커 노드 그룹 생성에 실패한 상황.&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;노드로 쓰일 컴퓨팅 리소스는 잘 생성이 되었으나 한참을 생성중인 상태더니 결국 이런 상태가 뜬 겁니다.&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;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CPw8O/btsHk0MaU44/kf1SKQm05RnH5QvxCsGQE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CPw8O/btsHk0MaU44/kf1SKQm05RnH5QvxCsGQE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CPw8O/btsHk0MaU44/kf1SKQm05RnH5QvxCsGQE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCPw8O%2FbtsHk0MaU44%2Fkf1SKQm05RnH5QvxCsGQE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;260&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;260&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;color: #333333; text-align: start;&quot;&gt;관리형 노드 그룹의 노드가 15분 내에 클러스터에 연결되지 않으면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;NodeCreationFailure 라는 상태 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔 상태가 Create failed로 설정됩니다.&lt;/p&gt;
&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/eks/latest/userguide/troubleshooting.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS 사용자 가이드&lt;/a&gt;에서 NodeCreationFailure를 찾아보면 이렇게 설명되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;NodeCreationFailure&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작된 인스턴스를 Amazon EKS 클러스터에 등록할 수 없습니다. 이러한 오류의 일반적인 원인은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/create-node-role.html&quot;&gt;노드 IAM 역할&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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&gt;퍼블릭 IP 주소를 사용하여 인터넷에 액세스할 수 있습니다. 노드가 있는 서브넷에 연결된 보안 그룹은 통신을 허용해야 합니다. 자세한 내용은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/network_reqs.html#network-requirements-subnets&quot;&gt;서브넷 요구 사항 및 고려 사항&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/sec-group-reqs.html&quot;&gt;Amazon EKS 보안 그룹 요구 사항 및 고려 사항&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단원을 참조하세요.&lt;/li&gt;
&lt;li&gt;노드와 VPC는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/private-clusters.html&quot;&gt;프라이빗 클러스터 요구 사항&lt;/a&gt;의 요구 사항을 충족해야 합니다.&lt;/li&gt;
&lt;/ul&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;대처&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;AmazonEKSWorkerNodePolicy
&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: #16191f; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;VPC의 Amazon EC2 리소스를 설명할 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;kubelet&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;권한.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;이 정책은 Amazon EKS Pod Identity Agent에 대한 권한도 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;AmazonEC2ContainerRegistryReadOnly&lt;/span&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: #16191f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;Amazon ECR 의 컨테이너 이미지를 사용할 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;kubelet&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;권한&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;네트워킹용 내장 애드온이 Amazon ECR의 컨테이너 이미지를 사용하는 포드를 실행하기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;/span&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;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;ipv4 주소 체계를 사용하는 EKS이기 때문에 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;추가로 아래 역할을 연결해주었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;(IPv6 주소체계는 다른 방법을 사용해야 하는 모양입니다. AWS 사용자 가이드를 참고하세요   &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-iam-role.html#cni-iam-role-create-ipv6-policy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;link&lt;/a&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;b&gt;AmazonEKS_CNI_Policy&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;&lt;/span&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;span style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;Amazon VPC CNI 플러그인(amazon-vpc-cni-k8s)에 EKS 워커 노드에서 IP 주소 구성을 수정하는 데 필요한 권한을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 권한이 없었기 때문에 워커노드를 생성하며 IP주소 구성을 노드-파드 간 통신이 불가능하게 구성되어 `Unhealthy nodes in the kubenetes cluster` 오류가 나지 않았을까 싶습니다.&lt;/p&gt;
&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: #16191f; text-align: left;&quot;&gt;IRSA(IAM Roles for Service Account) 또는 EKS Pod Identity를 사용하여 &lt;a href=&quot;https://velog.io/@kubernetes/AWS-VPC-CNI&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;VPC CNI&lt;/a&gt; 포드에 권한을 부여하지 않는 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;인스턴스 역할에서 VPC CNI에 대한 권한을 제공해야 합니다.&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;이&amp;nbsp;역할에&amp;nbsp;정책을&amp;nbsp;연결하는&amp;nbsp;것보다&amp;nbsp;Amazon&amp;nbsp;VPC&amp;nbsp;CNI&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon&amp;nbsp;EKS&amp;nbsp;노드&amp;nbsp;IAM&amp;nbsp;역할에&amp;nbsp;현재&amp;nbsp;AmazonEKS_CNI_Policy&amp;nbsp;IAM(IPv4)&amp;nbsp;정책&amp;nbsp;또는&amp;nbsp;IPv6&amp;nbsp;정책이&amp;nbsp;연결되어&amp;nbsp;있고&amp;nbsp;별도의&amp;nbsp;IAM&amp;nbsp;역할을&amp;nbsp;생성한&amp;nbsp;경우,&amp;nbsp;정책을&amp;nbsp;대신&amp;nbsp;연결한&amp;nbsp;다음&amp;nbsp;aws-node&amp;nbsp;Kubernetes&amp;nbsp;서비스&amp;nbsp;계정에&amp;nbsp;할당한&amp;nbsp;후&amp;nbsp;클러스터의&amp;nbsp;IP&amp;nbsp;패밀리와&amp;nbsp;일치하는&amp;nbsp;AWS&amp;nbsp;CLI&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;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;참고 : AWS VPC CNI&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Amazon EKS implements cluster networking through the Amazon VPC Container Network Interface(VPC CNI) plugin. The CNI plugin allows Kubernetes Pods to have the same IP address as they do on the VPC network. More specifically, all containers inside the Pod share a network namespace, and they can communicate with each-other using local ports.&lt;br /&gt;&lt;br /&gt;아마존 EKS는 아마존 VPC 컨테이너 네트워크 인터페이스(VPC CNI) 플러그인을 통해 클러스터 네트워킹을 구현한다. CNI 플러그인을 사용하면 쿠버네티스 파드가 VPC 네트워크에서와 동일한 IP 주소를 가질 수 있다. 더 구체적으로, 파드 내부의 모든 컨테이너는 네트워크 네임스페이스를 공유하며, 로컬 포트를 사용하여 서로 통신할 수 있다.&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;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/65807859/nodecreationfailure-unhealthy-nodes-in-the-kubernetes-cluster&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/65807859/nodecreationfailure-unhealthy-nodes-in-the-kubernetes-cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/create-node-role.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/create-node-role.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/troubleshooting.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/troubleshooting.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  bugs</category>
      <category>EKS</category>
      <category>K8s</category>
      <category>kubernates</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/116</guid>
      <comments>https://new-pow.tistory.com/116#entry116comment</comments>
      <pubDate>Sat, 11 May 2024 07:09:16 +0900</pubDate>
    </item>
    <item>
      <title> 우아한 종료Graceful shutdown을 위하여</title>
      <link>https://new-pow.tistory.com/114</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정상 종료란 무엇일까요?&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;OS로부터 종료 시그널을 받으면 새로운 요청을 받지 않고 처리되고 있던 요청이 모두 끝난 후 모든 자원을 릴리즈한 뒤 프로세스를 종료하는 것입니다.&lt;/li&gt;
&lt;li&gt;다른 말로는 그레이스풀 셧다운 Graceful shutdown이라고 하기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down. If the provided context expires before the shutdown is complete, Shutdown returns the context's error, otherwise it returns any error returned from closing the [Server](&lt;a href=&quot;https://pkg.go.dev/net/http#Server)'s&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pkg.go.dev/net/http#Server)'s&lt;/a&gt; underlying&amp;nbsp;Listener(s).&lt;br /&gt;&lt;br /&gt;종료는 활성 연결을 중단하지 않고 서버를 정상적으로 종료합니다. 셧다운은 먼저 열려 있는 모든 수신기를 닫은 다음 유휴 연결을 모두 닫은 다음 연결이 유휴 상태로 돌아갈 때까지 무한정 기다린 다음 종료하는 방식으로 작동합니다. 종료가 완료되기 전에 제공된 컨텍스트가 만료되면 종료는 컨텍스트의 오류를 반환하고, 그렇지 않으면 서버의 기본 리스너를 닫을 때 반환된 모든 오류를 반환합니다.&lt;br /&gt;&lt;br /&gt;- [참고링크](&lt;a href=&quot;https://pkg.go.dev/net/http#Server.Shutdown)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pkg.go.dev/net/http#Server.Shutdown)&lt;/a&gt;&lt;/blockquote&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;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;/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 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;우아한 종료를 위해 처리해야 하는 OS 시그널&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급하였듯 우아한 종료는 프로그램 실행 중에 OS 시그널을 받으면, 이를 catch하여 처리로직을 구현하는 형태로 애플리케이션의 우아한 종료를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS마다&amp;nbsp;지원&amp;nbsp;시그널이&amp;nbsp;다를&amp;nbsp;수&amp;nbsp;있으며&amp;nbsp;리눅스&amp;nbsp;기준&amp;nbsp;`kill&amp;nbsp;-l`&amp;nbsp;명령어를&amp;nbsp;통해&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있습니다.&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;간섭 시그널 `SIGINT`&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령줄에서&amp;nbsp;ctrl&amp;nbsp;+&amp;nbsp;c&amp;nbsp;를&amp;nbsp;누른&amp;nbsp;경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;종료 시그널 `SIGTERM`&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 외부로부터 어플리케이션&amp;nbsp;컨테이너&amp;nbsp;종료&amp;nbsp;시그널을&amp;nbsp;받는&amp;nbsp;경우&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;pre class=&quot;angelscript&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;kill -15 {프로세스의 PID}
&lt;/code&gt;&lt;/pre&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;프로세스 종료 시그널 `SIGKILL`&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;시그널을 catch 하는 것이 불가능합니다.&lt;/li&gt;
&lt;li&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;아래는 직접 실행중인 서버에서 시그널을 받았을 때 golang으로 작성된 처리 예시입니다. (참고 : Go로 배우는 웹 애플리케이션 개발 예제)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여담이지만 golang은 종종 모든 것을 직접 구현해줘야할 때가 있어서 동작 원리예제에 대한 예시를 찾기 상대적으로 쉬운 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1714777216833&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import (
	&quot;context&quot;
	&quot;log&quot;
	&quot;net&quot;
	&quot;net/http&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;syscall&quot;

	&quot;golang.org/x/sync/errgroup&quot;
)

func (s *Server) Run(ctx context.Context) error {
	ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
	defer stop()
	eg, ctx := errgroup.WithContext(ctx)
	eg.Go(func() error { // 다른 고루틴에서 HTTP 서버를 실행
		// http.ErrServerClosed 는 정상적인 종료를 의미한다.
		// http.Server.Shutdown() 이 호출되면 http.ErrServerClosed 가 반환되기 때문
		if err := s.srv.Serve(s.l); err != nil &amp;amp;&amp;amp;
			err != http.ErrServerClosed {
			log.Printf(&quot;failed to close: %+v&quot;, err)
			return err
		}
		return nil
	})

	// 채널로부터의 알림(종료 알림)을 기다린다. 시그널이나 취소 요청에 의해 취소될 때까지 블록된 상태로 있다.
	&amp;lt;-ctx.Done()
	// 모든 요청을 처리한 후 서버를 종료한다.
	if err := s.srv.Shutdown(context.Background()); err != nil {
		log.Printf(&quot;failed to shutdown: %+v&quot;, err)
	}
	// 정상 종료를 기다린다.
	return eg.Wait()
}&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;Springboot 2.3부터 properties `server.shutdown` 설정을 통해 간단하게 설정해줄 수 있습니다  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ibm.com/docs/ko/adi/5.1.0?topic=administration-shutting-down-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고링크&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1714777367208&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server.shutdown=graceful //default: immediate
spring.lifecycle.timeout-per-shutdown-phase=1m //default timeout: 30s&lt;/code&gt;&lt;/pre&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Go로 배우는 웹 애플리케이션 개발&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-kr.org/ch20-03-graceful-shutdown-and-cleanup.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://doc.rust-kr.org/ch20-03-graceful-shutdown-and-cleanup.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Practice</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/114</guid>
      <comments>https://new-pow.tistory.com/114#entry114comment</comments>
      <pubDate>Fri, 12 Apr 2024 22:51:28 +0900</pubDate>
    </item>
    <item>
      <title>  Error: MacOS에서 Kind로 구축한 k8s 클러스터의 Deployment external IP에 접근을 하지 못합니다</title>
      <link>https://new-pow.tistory.com/113</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 상황&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1712361885701&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;```
☁  _Lecture_k8s_starter.kit [main] ⚡  kubectl get services
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
chk-hn         LoadBalancer   10.96.253.167   192.168.1.11   80:30941/TCP   12m
```&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;쿠버네티스 실습 중에 LoadBalancer service를 만들고 클러스터 내부와 외부에서 External IP로 각각 접근을 실행하였습니다.&lt;/li&gt;
&lt;li&gt;클러스터 내부에서는 문제없이 접근할 수 있었으나, 클러스터 외부에서는 timeout이 발생하였습니다.&lt;/li&gt;
&lt;li&gt;외부에서 ping을 날려봤지만, timeout이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712358663535&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl 192.168.247.5:30623
// 응답없음...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Network 설정 중 누락된 것이 있는가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클러스터를 띄울 때, 실습 가이드를 따라한 것이 아니라 yml 파일을 작성하여 클러스터를 띄웠기 때문에 default 값이 없거나  적절하게 설정되어 있지 않을 수도 있다는 추측이었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712362116580&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 클러스터 설정 yml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  ipFamily: ipv6
  apiServerAddress: 127.0.0.1
nodes:
  - role: control-plane
  - role: worker
  - role: worker
  - role: worker&lt;/code&gt;&lt;/pre&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;Kind의 특징인가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MacOS에서 쿠버네티스를 연습할 수 있어서&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Minikube는 명령어가 조금 다를 수 있는 반면, 명령어가 같아서 Kind를 사용했는데요.&lt;/li&gt;
&lt;li&gt;아무래도 VM을 Docker container 위에 띄우는 Kind 특성상 네트워크 설정에 달라지는 부분이 있을까 싶었습니다.&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;그래서 좀 찾아보니 해당 이슈는 알려진 문제점으로 공식 문서에도 이렇게 적혀있었습니다.&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;On macOS and Windows, docker does not expose the docker network to the host. Because of this limitation, containers (including kind nodes) are only reachable from the host via port-forwards, however other containers/pods can reach other things running in docker including loadbalancers. You may want to check out the Ingress Guide as a cross-platform workaround. You can also expose pods and services using extra port mappings as shown in the extra port mappings section of the&amp;nbsp;Configuration Guide.&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;Docker 가 Linux VM에서 컨테이너를 실행하고 있지만, Docker Network가 호스트에 노출되지 않았기 때문에 발생하는 오류입니다.&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;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HvBYC/btsGqep4hyO/gtvE8O5RhKHIU4ttKcqgkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HvBYC/btsGqep4hyO/gtvE8O5RhKHIU4ttKcqgkk/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;nbsp;https://medium.com/groupon-eng/loadbalancer-services-using-kubernetes-in-docker-kind-694b4207575d&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HvBYC/btsGqep4hyO/gtvE8O5RhKHIU4ttKcqgkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHvBYC%2FbtsGqep4hyO%2FgtvE8O5RhKHIU4ttKcqgkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1400&quot; height=&quot;768&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;nbsp;https://medium.com/groupon-eng/loadbalancer-services-using-kubernetes-in-docker-kind-694b4207575d&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;LoadBalancer 타입 특성상 external IP로 접근 가능해야하지만, Docker container에서 노출하지 않으므로 이 external IP로는 접근이 불가능한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 노출 방식을 NordPort로 한다면 별 문제 없이 접근 가능합니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일단 실습과정이기 때문에 `{NodeIP}:{Service Port}`로 요청을 보내 우회하는 전략으로 해결하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712364019466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 노드 정보
kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE     IP           NODE                 NOMINATED NODE   READINESS GATES
deploy-nginx-7f979874cf-4kqjx   1/1     Running   0          24s     10.244.3.3   del-deploy-worker    &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
deploy-nginx-7f979874cf-hn22v   1/1     Running   0          24s     10.244.2.3   del-deploy-worker2   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
deploy-nginx-7f979874cf-t48jt   1/1     Running   0          4m58s   10.244.1.2   del-deploy-worker3   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx                           1/1     Running   0          7h26m   10.244.2.2   del-deploy-worker2   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;

# 서비스 정보
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
chk-hn         LoadBalancer   10.96.253.167   192.168.1.11   80:30941/TCP   12m

# 요청결과
curl 192.168.247.5:30623
chk-hn-6cb6cdf8f8-68xsn&lt;/code&gt;&lt;/pre&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;다른 해결방법으로는 NGINX 수신 컨트롤러를 설정하는 방법이 있습니다. [&lt;a href=&quot;https://medium.com/groupon-eng/loadbalancer-services-using-kubernetes-in-docker-kind-694b4207575d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고링크&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712364099874&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: &quot;/test(/|$)(.*)&quot;
        backend:
          service:
            name: test-service
            port:
              number: 80&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;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Refs.&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kind.sigs.k8s.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/groupon-eng/loadbalancer-services-using-kubernetes-in-docker-kind-694b4207575d&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/groupon-eng/loadbalancer-services-using-kubernetes-in-docker-kind-694b4207575d&lt;/a&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;</description>
      <category>  bugs</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/113</guid>
      <comments>https://new-pow.tistory.com/113#entry113comment</comments>
      <pubDate>Sat, 6 Apr 2024 07:51:43 +0900</pubDate>
    </item>
    <item>
      <title>공변성과 반공변성과 Generics</title>
      <link>https://new-pow.tistory.com/112</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;그러며 새로이 깨달은 것이 `공변성(covariance)` 과 반공변성(Contravariance)`에 대해 제대로 이해하고 있지 않기도 하고.&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;이미 Java로 코드를 구현할 때, 이 개념을 자연스럽게 사용하고 있었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새삼 개념과 객체지향 리스코프 치환 원칙 LSP까지 짚어보니 좀 복잡했지만 왜 이렇게 사용해야했는지 이해할 수 있어서 유의미했던 공부였습니다.&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;b&gt;공변성과 반공변성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 변성(variance)은 타입 시스템에서 서로 다른 타입 간의 관계를 설명하는 개념입니다. 다시말해 상속관계에 있는 A, B 타입이 있을 때, 이를 Type Argument로 가지고 있는 Base Type이 어떤 관계에 있는지 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 타입의 상속 관계가 아래와 같을 때&lt;/p&gt;
&lt;pre id=&quot;code_1713555697390&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Animal {
	public void walk()
}
class Cat extends Animal {
	public void grooming()
}&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;List라는 Base type을 이렇게 정의한다면, `List&amp;lt;Cats&amp;gt;` 는 `List&amp;lt;Animals&amp;gt;` 를 상속받은 것일까요?&lt;/p&gt;
&lt;pre id=&quot;code_1713555789759&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Animal&amp;gt; animals;
List&amp;lt;Cat&amp;gt; cats;&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;&quot;`Cats`와 `Animals` 가 상속 관계를  라고, `List&amp;lt;Cats&amp;gt;` , `List&amp;lt;Animals&amp;gt;` 간의 상속 관계를 보장하지 않는다.&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;자바에서는 무변성의 특징을 갖기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type Argument의 관계를 그대로 Base Type간의 관계가 유지된다면 &lt;b&gt;공변성(Convariance)&lt;/b&gt;라고 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대 방향의 상속 관계를 가진다면 &lt;b&gt;반공변성(Contravariance)&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;이와는 달리 Type Argument 타입 관계가 Base Type 간의 관계에 영향을 미치지 않는다면 &lt;b&gt;무변성(Invariance)&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_1713564480448&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Animal a = new Cat(); // ok
List&amp;lt;Animal&amp;gt; as = new List&amp;lt;Cat&amp;gt; // error&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 변성은 제네릭, 다형성 구현에 매우 중요한 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 프레임워크에 따라 제네릭 변성 처리 방식이 다를 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한 번 말하지만 &lt;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_1713563872276&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// java

List&amp;lt;Object&amp;gt; objects = new ArrayList&amp;lt;String&amp;gt;(); // compile error 제네릭은 무공변성을 가집니다.
Object[] objectArray = new String[]; // ok, 배열은 공변성을 가집니다.&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;&lt;b&gt;변성을 잘 써야하는 이유?&lt;/b&gt;&lt;/h3&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;변성을 잘 사용한다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;다형성을 이용하여 보다 유연한 코드를 작성&lt;/u&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;Object는 String의 상위 타입이지만, List&amp;lt;Object&amp;gt;는 List&amp;lt;String&amp;gt; 의 상위 타입이 아니기 때문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713565186957&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Counter {
	public void countTotalLength(List&amp;lt;Object&amp;gt; objects) {...}
}

Counter counter = new Counter();
List&amp;lt;String&amp;gt; strings = new ArrayList&amp;lt;&amp;gt;();

counter.coutTotalLength(strings) // error!!&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Java에서 공변성과 반공변성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 공편성과 반공변성을 각각 `extends` 와 `super` 를 사용하여 구현이 가능합니다.&lt;/p&gt;
&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;대신 전달받은 `strings` 의 타입을 바로 활용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 확실할 수 있는 것은 List 제네릭으로 Object의 하위타입인 것인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 전달된 strings는 메서드 내부에 타입 전달이 되지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713565358808&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Counter {
	public void countTotalLength(List&amp;lt;? extends Object&amp;gt; objects) {...} // 하위 타입에 대해 수용 가능
}

Counter counter = new Counter();
List&amp;lt;String&amp;gt; strings = new ArrayList&amp;lt;&amp;gt;();

counter.coutTotalLength(strings) // ok!&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 바꾼다면 String의 상위 타입에 있는 것도 할당 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713566002045&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Counter {
	public void countTotalLength(List&amp;lt;? super String&amp;gt; objects) {...} // 상위 타입에 대해 수용 가능
}

Counter counter = new Counter();
List&amp;lt;String&amp;gt; strings = new ArrayList&amp;lt;&amp;gt;();
List&amp;lt;Object&amp;gt; objects = new ArrayList&amp;lt;&amp;gt;();

counter.coutTotalLength(strings) // ok!
counter.coutTotalLength(objects) // ok!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 공변과 반공변을 사용할 때는 구현하다 자연스럽게 사용하게 되기 마련입니다. 어차피 컴파일 에러로 빠르게 확인할 수 있으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 하위 타입을 꺼내서 사용한다면(소비한다고 표현하기도 합니다.) 반공변처리를. 상위 타입으로만 사용한다면 공변처리를 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 read / write, producer/consumer 로 표현하기도 하는데, 이에 대한 이야기는 이펙티브 자바(Effective Java) 규칙 28에 자세히 언급되어 있습니다.&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;&quot;For maximum flexibility, use wildcard types on input parameters that represent producers or consumers.&quot;&lt;br /&gt;&quot;유연성을 극대화하려면 생산자 또는 소비자를 나타내는 입력 매개변수에 와일드카드(? extends, ? super) 유형을 사용하세요.&quot;&lt;br /&gt;&lt;br /&gt;Joshua Bloch, in his book Effective Java, 3rd Edition, explains the problem well (Item 31: &quot;Use bounded wildcards to increase API flexibility&quot;). He gives the name Producers to objects you only read from and Consumers to those you only write to. He recommends:&lt;br /&gt;조슈아 블로흐는 그의 저서 Effective Java, 3판에서 이 문제를 잘 설명합니다(항목 31: &quot;바운딩 와일드카드를 사용하여 API 유연성 높이기&quot;). 그는 읽기만 하는 객체에는 생산자라는 이름을, 쓰기만 하는 객체에는 소비자라는 이름을 붙입니다. 그는 이렇게 추천합니다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://kotlinlang.org/docs/generics.html#declaration-site-variance&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;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/generics.html#type-erasure&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kotlinlang.org/docs/generics.html#type-erasure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/generics.html#type-projections&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kotlinlang.org/docs/generics.html#type-projections&lt;/a&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;p data-ke-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>  Languages/☕️ Java</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/112</guid>
      <comments>https://new-pow.tistory.com/112#entry112comment</comments>
      <pubDate>Sat, 30 Mar 2024 02:23:09 +0900</pubDate>
    </item>
    <item>
      <title>  Bug: 로컬에서 잘 실행되던 Unit Test가 parallel test 실행에서 오류가 났다 : 잠깐  아까 설정했던 Mock을 어떻게 했나요...?</title>
      <link>https://new-pow.tistory.com/111</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 상황&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring boot gradle test&lt;/li&gt;
&lt;li&gt;kotest, spoke 테스트&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;SpringBoot 프로젝트의 테스트를 로컬에서 실행했을 때 모든 단위 테스트와 API 테스트가 통과했으나, Github workflow 실행시 갑자기 단위 테스트 중 몇 개가 깨져버렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬과 origin 코드가 동일한 상황에서 로컬에서는 계속 잘 통과하는 데 무엇이 문제일까 헤매었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 예외코드가 약간 이상합니다. 분명 실패한 테스트는 수정 로직인데, 예외 스택에 삭제 테스트가 함께 포함되어 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1710949517905&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// 예외 로그 중 시그니처가 살짝 수정되어있습니다

com.backend.domain.test.Modify &amp;gt; 수정 &amp;gt; 작성자가 수정할 경우 성공한다 FAILED
    java.lang.AssertionError at ModifySpec.kt:78
        Caused by: java.lang.AssertionError at ModifySpec.kt:78
            Caused by: java.lang.AssertionError at ModifySpec.kt:78
                Caused by: com.backend.domain.AccessException at DeleteSpec.kt:181 //뜬금없이 다른 파일 코드&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;해당 코드 라인으로 찾아가보니 `mockkObject` 를 설정했던 코드가 있었습니다.&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;b&gt;발생 원인&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬과 workflow의 가장 큰 차이점은 `gradlew test` 로 하였나 `gradlew test -parallel` 로 실행하였느냐였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github workflow는 생산성과 비용의 문제로 병렬 실행하고 있었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련해서 찾아보니 `mockkObject`, `mockkStatic`과 같이 static이나 object를 모킹하는 경우 스레드 세이프하지 안다는 이슈가 있더라고요. 2019년(제가 찾아본 제일 오래된 것)부터 지속적으로 제기되어 왔었던 이슈였습니다. 특히 모킹된 동작을 지워주는 `clearAllMocks`, 모킹된 요소를 지워주는 `unmockkAll` 가 스레드 세이프하지 않아 병렬 실행시 테스트가 깨지는 경우가 왕왕 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 mockk을 해제 안해줘도 다음 테스트에 영향이 가서 문제이고, 해제해도 스레드세이프하지 못하면 예상과 다른 동작을 할 수 있으므로 주의해야 합니다.&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; &amp;zwj;♀️ 잠깐, `clearAllMocks` 와 `unmockkAll` 의 차이점 더 자세히&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;`clearAllMocks`&lt;br /&gt;- mock객체와 관련된 객체 내부 상태를 삭제하여 빈 객체로 되돌려 둡니다. (access count, mock된 동작 등)&lt;br /&gt;- `every { sth } returns sth` `coEvery { sth } returns sth` 와 같은 상태를 취소합니다.&lt;br /&gt;&lt;br /&gt;`unmockkAll`&lt;br /&gt;- 객체를 mockking 하기 이전처럼 MockK's internal collection에서 mock객체를 삭제합니다.&lt;br /&gt;- `mockkStatic(sth::class)` 등을 이전으로 되돌립니다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/mockk/mockk/issues/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[관련 이슈 : Doc&amp;nbsp;change:&amp;nbsp;clarify&amp;nbsp;difference&amp;nbsp;between&amp;nbsp;clear&amp;nbsp;and&amp;nbsp;unmock]&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;대처&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 메서드 실행 이후에 `unmockkObject` 를 통해 명시적으로 mockk을 제거해주었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1711153162373&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    override suspend fun afterEach(testCase: TestCase, result: TestResult) {
        unmockkObject(Access.Companion)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mockk/mockk/issues/764&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mockkStatic&amp;nbsp;is&amp;nbsp;not&amp;nbsp;threadsafe&amp;nbsp;#764&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mockk/mockk/issues/860&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Suggestion:&amp;nbsp;Recommendations&amp;nbsp;for&amp;nbsp;running&amp;nbsp;parallel&amp;nbsp;tests&amp;nbsp;#860&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mockk/mockk/issues/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Doc&amp;nbsp;change:&amp;nbsp;clarify&amp;nbsp;difference&amp;nbsp;between&amp;nbsp;clear&amp;nbsp;and&amp;nbsp;unmock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Languages/✔ Kotlin</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/111</guid>
      <comments>https://new-pow.tistory.com/111#entry111comment</comments>
      <pubDate>Sat, 23 Mar 2024 07:09:58 +0900</pubDate>
    </item>
    <item>
      <title>  Bugs: MacOS에서 Sonoma 업데이트 이후 Embedded Redis  시작되지 않음</title>
      <link>https://new-pow.tistory.com/110</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`EmbeddedRedis` 가 적용된 테스트를 실행시키려고 할 때, 다음의 에러 로그가 뜬 후 프로세스가 죽는 현상이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Can't start redis server. Check logs for details. Redis process log:`&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 process 가 이미 죽어버려서 로그를 볼 수도 없는 상황.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제에 대해 사수님께 질문하여 'OS 문제인 것 같다' 는 결론을 얻었습니다.&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;b&gt;발생 원인&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전에 MacOS를 Sonoma 14.x로 업데이트 했는데, 그로 인해 OS security policy가 변경되며 `EmbeddedRedis` 실행에 실패했던 것입니다. &lt;a href=&quot;https://github.com/kstyrc/embedded-redis/issues/135#issuecomment-1810598861&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[  related issue]&lt;/a&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;Most likely security policies forbid the Redis binary from starting. The security policies seems to have been tightened in Sonoma. You can confirm that yourself with the&amp;nbsp;Console&amp;nbsp;app. Learn a bit more about it&amp;nbsp;over here. In a nutshell, you start recording messages, filter for&amp;nbsp;redis&amp;nbsp;and run whatever code uses&amp;nbsp;embedded-redis. Eventually you might see something like this:&lt;br /&gt;&lt;br /&gt;`ASP: Security policy would not allow process &amp;lt;pid&amp;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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;대처&lt;/b&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;p data-ke-size=&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;886&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyJsLw/btsFuGs4r9z/h7JECyJKGe0faNj2siGCA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyJsLw/btsFuGs4r9z/h7JECyJKGe0faNj2siGCA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyJsLw/btsFuGs4r9z/h7JECyJKGe0faNj2siGCA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyJsLw%2FbtsFuGs4r9z%2Fh7JECyJKGe0faNj2siGCA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;311&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;394&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;기존 Embedded Redis 를 포크하여 해당 문제를 해결한 레포지토리가 있습니다. 이것으로 대체할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/codemonstur/embedded-redis&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/codemonstur/embedded-redis&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709342420670&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 - codemonstur/embedded-redis: Redis embedded server&quot; data-og-description=&quot;Redis embedded server. Contribute to codemonstur/embedded-redis development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/codemonstur/embedded-redis&quot; data-og-url=&quot;https://github.com/codemonstur/embedded-redis&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJ19JQ/hyVufNfrnc/6EGbMIpFcAKx9kxWlw5Fd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/codemonstur/embedded-redis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/codemonstur/embedded-redis&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJ19JQ/hyVufNfrnc/6EGbMIpFcAKx9kxWlw5Fd0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - codemonstur/embedded-redis: Redis embedded server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis embedded server. Contribute to codemonstur/embedded-redis development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Refs.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kstyrc/embedded-redis/issues/135#issuecomment-1810598861&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kstyrc/embedded-redis/issues/135#issuecomment-1810598861&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709342240694&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;Unable to run on macOS Sonoma &amp;middot; Issue #135 &amp;middot; kstyrc/embedded-redis&quot; data-og-description=&quot;This worked before I upgraded to macOS Sonoma. Now I get this error message: java.lang.RuntimeException: Can't start redis server. Check logs for details. Redis process log: at redis.embedded.Abstr...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kstyrc/embedded-redis/issues/135#issuecomment-1810598861&quot; data-og-url=&quot;https://github.com/kstyrc/embedded-redis/issues/135&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/coYwpJ/hyVqvc8glO/QlYuik5y4aRoIZeimFrVgK/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_118_1064_219&quot;&gt;&lt;a href=&quot;https://github.com/kstyrc/embedded-redis/issues/135#issuecomment-1810598861&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kstyrc/embedded-redis/issues/135#issuecomment-1810598861&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/coYwpJ/hyVqvc8glO/QlYuik5y4aRoIZeimFrVgK/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_118_1064_219');&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;Unable to run on macOS Sonoma &amp;middot; Issue #135 &amp;middot; kstyrc/embedded-redis&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This worked before I upgraded to macOS Sonoma. Now I get this error message: java.lang.RuntimeException: Can't start redis server. Check logs for details. Redis process log: at redis.embedded.Abstr...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  bugs</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/110</guid>
      <comments>https://new-pow.tistory.com/110#entry110comment</comments>
      <pubDate>Sat, 2 Mar 2024 00:37:24 +0900</pubDate>
    </item>
    <item>
      <title>2024-M01~02 회고 : 신입 개발자의 성공적인 랜딩을 위하여</title>
      <link>https://new-pow.tistory.com/109</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;b&gt;&lt;/b&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;2024년이 시작한지도 벌써 두 달이나 지났습니다. 진짜 뭐했다고 이렇게 시간이 빨리 흘러가는 지 참..!&lt;/p&gt;
&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;&lt;i&gt;아래 그림은 티타임에서 정리한 월간 회고  &lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;사실 이 글에서는 더 잡다한 얘기가 있을 수 있습니다.....&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;10528&quot; data-origin-height=&quot;5160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxXwHa/btsFF33hc5q/zgoktrwRNhyNvUmCLadaiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxXwHa/btsFF33hc5q/zgoktrwRNhyNvUmCLadaiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxXwHa/btsFF33hc5q/zgoktrwRNhyNvUmCLadaiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxXwHa%2FbtsFF33hc5q%2FzgoktrwRNhyNvUmCLadaiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;10528&quot; height=&quot;5160&quot; data-origin-width=&quot;10528&quot; data-origin-height=&quot;5160&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;b&gt;신입개발자의 성공적인 랜딩을 위하여&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nXFNj/btsFkTO1Bx8/aOlz4qHolmJQXfPZOeU560/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nXFNj/btsFkTO1Bx8/aOlz4qHolmJQXfPZOeU560/img.gif&quot; data-alt=&quot;성공적인... 랜딩... 하고 있는거....... 맞지?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nXFNj/btsFkTO1Bx8/aOlz4qHolmJQXfPZOeU560/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/nXFNj/btsFkTO1Bx8/aOlz4qHolmJQXfPZOeU560/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;270&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;270&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;올해 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;아침에 일어나 출근을 하고, 직장에 갔다가 퇴근을 하는 이 루틴이 너무 오랜만이어서 많이 긴장되더라고요. 그래서 첫 한달은 일을 하고오면 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;처음 입사 OT 이후 약 한달 반동안 OJT를 거쳤습니다. 이거 생각보다 꽤 긴 거 아닌가 싶기는 했는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 그동안 쓰던 Java + Spring MVC + JPA 기반의 프로젝트와 달리, Kotlin + Spring WebFlux + R2dbc 로 스택이 많이 달라지기도 했고 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;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;신입 개발자라면 응당 하게되는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&quot;1인분을 제대로 하고 싶다&quot;&lt;/i&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;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 data-ke-size=&quot;size16&quot;&gt;2월 중반부터는 OJT의 `on job training` 시작하여 백오피스 기능을 구현하는 일을 시작했는데요.&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;b&gt;고민했던 것&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;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;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 달의 우선 목표는 &quot;파악&quot; 하는 것
&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;/li&gt;
&lt;li&gt;나의 장점은 무엇인가? 약간 잃어버리는 느낌. 나 답게 일하는 방법을 더 생각해보자.&lt;/li&gt;
&lt;li&gt;심리적으로 경직된 하루하루를 살다보니 피곤이 더 누적된다. 심리적인 이완이 필요한데, 이걸 어떻게 하지?&lt;/li&gt;
&lt;li&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;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;실력을 기르는 일은 돌 하나씩을 쌓아오리는 식으로 이루어지지 않는다. 수많은 점을 찍고, 그 점들을 이리저리 연결하고, 때로 찍었던 점을 잃어 애써 연결에 성공한 선분이 함께 사라지고, 그러면서도 거듭 점찍기와 연결하기를 시도하면서 커다란 그림을 완성해가는 일에 가깝다. ... 어제 들인 노력을 고스란히 쌓아서 다음 단계로 가져가야 한다고 생각하면, 운동을 꾸준히 할 수가 없다. &lt;i&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/66903691&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;일하는 마음 p.121&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;다른 Insight&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;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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;질문을 부담없이 하자&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGlDtv/btsFGU5Qdp6/3LgWP54UjAhfD1T1OSjWxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGlDtv/btsFGU5Qdp6/3LgWP54UjAhfD1T1OSjWxk/img.png&quot; data-alt=&quot;질문을 어떻게 할 지 모르겠다는 질문에 조언받은 답변을 메모해놨어요.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGlDtv/btsFGU5Qdp6/3LgWP54UjAhfD1T1OSjWxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGlDtv%2FbtsFGU5Qdp6%2F3LgWP54UjAhfD1T1OSjWxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;243&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;420&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;i&gt;'어떻게 정확히 원하는 답을 얻을 수 있는 질문을 할까?'&lt;/i&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;특히 첫달에 이런 고민이 많이 되었어요. 그런 저에게 가장 많이 도움 받았던 말은 &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;사람마다 편한 커뮤니케이션 방식은 다르기 마련입니다.&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;i&gt;&quot;질문할 것이 생기면 어떻게 물어보는 것이 가장 편하세요? 바로 물어봐도 될까요?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;질문을 하기 전 미리 작성해 볼것&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;상대방이 알아듣게 명료하게 질문하는 데 큰 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;추상적으로 얘기하는 것을 방지하기 위해 질문에 해당하는 코드, 링크 등 활용하기&lt;/li&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;/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;&lt;b&gt;나만 안되는   문제를 맞이했을 때 : 예외 디버깅 등&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 구현하다가 알 수 없는 오류를 만났을 때. 구글링으로 해결을 못했거나 어떤 것이 문제(에러)인지 막막할 때에 바로 질문을 해야하나 고민했던 적이 있습니다. 바쁜 팀원에게 어이없는 오류로 질문을 해도 될까 엄청 망설였거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;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;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;&quot;어? 갑자기 왜 되는거지?&quot; 의 상황 방지&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;루틴이 주는 안정감&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 2달 동안 많이 발전한 것이 있다면 바로 루틴을 세우고 지키는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 취준 기간보다 더 알차게 루틴을 만들고 지켰습니다. 엄청 거창한 루틴은 없고요. 몇시에 기상하고 몇시에 집을 나서는 지, 영양제는 챙겨 먹는지, 책은 30분정도 읽기. 이런 것들이에요. 안 지켜지는 날이 더 많은 루틴도 있습니다  &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;300&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clFT4h/btsFM3V8ETb/yJEnVK9tsgKzRKRy09Hu20/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clFT4h/btsFM3V8ETb/yJEnVK9tsgKzRKRy09Hu20/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clFT4h/btsFM3V8ETb/yJEnVK9tsgKzRKRy09Hu20/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclFT4h%2FbtsFM3V8ETb%2FyJEnVK9tsgKzRKRy09Hu20%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;250&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰고 기록하는 것이 나의 존재감을 안정적으로 만드는 효과가 있더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 보낸 하루가 영 헛되지 않았다고 위로받기도 하고. 내가 못한 것들이 너무 나를 괴롭히지 않게 딱 그만큼만 보이니까요.&lt;/p&gt;
&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://myroutine.today/ko&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;iOS 단축어를 이용해 간단한 아침 루틴과 뽀모도로 기록 시스템을 갖추어 사용하고 있는데요. 이 방법도 매달 조금씩 바꿔가며 적용시키고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;다른 언어를 배워 본다는 것 : &lt;b&gt;Golang&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 WWGo 에 참여한 이후로 Golang에 관심이 급 생겨서 틈틈히 책 &lt;a href=&quot;https://m.yes24.com/Goods/Detail/124624242&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;'Go 언어로 배우는 웹 애플리케이션 개발'&lt;/a&gt;을 읽고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go로 굳이 웹 애플리케이션을? 이라고 생각하신다면 ㅎㅎ 제가 웹 애플리케이션에 가장 익숙하기 때문에 이렇게 시작하는 것이 가장 편안한 길이라고 생각했어요.&lt;/p&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나 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;예를 들자면, Go는 매우 실용적으로 구현하기 위한 언어라서 Java의 객체지향과는 달리 객체지향의 일부분만 차용해서 사용한다든가. Kotlin Koroutine과 Go의 Goroutine과 Java Virtual thread의 공통점과 차이점이라든가? 무엇때문에 이 언어가 등장했는지 알게되면 깨지는 고정관념이 있더라고요. 반면에 생기는 단점들도 있고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710337856253&quot; class=&quot;go&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;// 별거 아니지만, go를 좋아하게 된 이유 중 하나는 쓰고나면 귀여워보여서입니다;

package main

import &quot;fmt&quot;

func main() {

    var a [5]int
    fmt.Println(&quot;emp:&quot;, a)

    a[4] = 100
    fmt.Println(&quot;set:&quot;, a)
    fmt.Println(&quot;get:&quot;, a[4])

    fmt.Println(&quot;len:&quot;, len(a))

    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println(&quot;dcl:&quot;, b)

    var twoD [2][3]int
    for i := 0; i &amp;lt; 2; i++ {
        for j := 0; j &amp;lt; 3; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println(&quot;2d: &quot;, twoD)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 생긴 욕심이 있어요. 무언가 만들 때 개발자로서 언어에 구애받지 않아야겠다는 것이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어에 상관없이 가장 문제해결에 필요한 언어를 사용할 수 있었으면 좋겠어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 시작점으로 올해 Go로 작은 애플리케이션을 하나 만들어보고 싶습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지속가능한 토이프로젝트를 위하여&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 입사한 회사에 적응하다보니 토이프로젝트나 개인 공부 시간이 확 줄어버려서 어색한 한해를 보내고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 부트캠프처럼 목적지를 정해놓고 전속력으로 달리는 듯한 공부에서 이제 지친 일상 속 새로운 자극과 자아실현이 되어줄   목적으로 토이프로젝트를 운용하고 공부하려고 하니 이 리듬의 전환이 생각보다 쉽지 않은 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 매일 꾸준히 업무 외 코드를 짜는 것이 가장 어려운 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;더불어 스터디 운영을 위한 플랫폼을 계속해서 팀원들과 같이 구축해나가보려고 합니다.&lt;/p&gt;
&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-origin-width=&quot;1794&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EYVRY/btsFQsvmoI0/o4waTCCxYjGpFNGtYcihiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EYVRY/btsFQsvmoI0/o4waTCCxYjGpFNGtYcihiK/img.png&quot; data-alt=&quot;기본 기능은 다 구현했으나, 아직 리팩토링과 고칠 것들이 많이 남았습니다  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EYVRY/btsFQsvmoI0/o4waTCCxYjGpFNGtYcihiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEYVRY%2FbtsFQsvmoI0%2Fo4waTCCxYjGpFNGtYcihiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1794&quot; height=&quot;1004&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;1004&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;아쉬웠던 점과 3월에 해보고 싶은 것들...&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;회사에서는&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;전반적으로 1~2월은 새로운 환경에 적응하느라 나답지 않았던 순간도 너무 많았고, 눈치도 보고 조급하고 (상황에) 반응하는 순간들이 많았다는 생각이 들더라고요.&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;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 data-ke-size=&quot;size16&quot;&gt;꾸준히  부족한 점과 강점을 피드백 받고, 신입으로서 할 수 있는 피드백을 주며 팀에 자산을 남기고 싶어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 팀의 문화를 더 좋게 하기 위한 방법은 무엇이 있을까 고민해보고 싶어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 일하는 곳이 나에게 더 편안하고 적합한 곳이었으면 좋겠어요. 일이 재미있으면 더 좋고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인의 장점이 팀의 장점으로 이어질 수 있는 것들이 무엇이 있을까요? 반대로 저의 단점이 팀 안에서 보완되었으면 좋겠어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개발자로서는&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일에 적응하는 것 외에 따로 특별히 한 것이 없다는 생각을 했는데, 그 이유는 아마 시작한 것은 많은데 끝낸 것은 별로 없어서 그런가봐요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(특히 책은 읽기 시작을 많이했는데 끝을 내지 못한게 마구 쌓여있어요 ㅎㅎㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3월부터는 새로운 것을 시작하기 보다는, 시작했던 것을 잘 마무리하는 데에 힘쓰고 싶어요.&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;Kotlin과 Coroutine에 더 친숙해지도록 배움을 이어나가려고해요.&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;토이프로젝트를 통해 회사 밖에서의 욕망을 잘 풀 것!&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;b&gt;개인적으로&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루종일 노트북과 스마트 기기만 끼고 살아서 저의 목/어깨와 눈에 많이 미안해졌어요ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출퇴근시간이 전체적으로 늦어서 저녁 식사 시간도 많이 망가졌고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 컨디션이 너무 많이 안좋아져서 감기를 달고 살기도 했어요.&lt;/p&gt;
&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;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;미뤄두었던 치과치료를 3월에 다시 시작했습니다. 다들 정기검진과 스케일링 잊지말고 하시길...!&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;여기까지입니다.&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;</description>
      <category>  Smalltalk</category>
      <category>log</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/109</guid>
      <comments>https://new-pow.tistory.com/109#entry109comment</comments>
      <pubDate>Fri, 1 Mar 2024 22:41:12 +0900</pubDate>
    </item>
    <item>
      <title>테스트 코드 작성 시 주의해야 하는 것들</title>
      <link>https://new-pow.tistory.com/107</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;사실 작년까지는 테스트 코드 작성을 정말 최소한으로만 해왔었는데요. 짜고나서 동작을 확인할 때, 테스트하고 싶은 것만 테스트하고 (정말 로직만 확인하는 테스트) 그나마도 구현이 계속되어 테스트를 고쳐야할 때면 가끔 코드를 들어내기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD나 테스트가 좋다고는 하지만 오히려 구현에 발목을 잡히는 것이 아닌가 하는 생각도 들었고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 테스트를 열심히 짜보며 이런 생각을 고치게 되었습니다. &quot;&lt;b&gt;그동안 내가 테스트가 유효할 만큼의 테스트를 짜보지 않아서 잘모르고 있었다, 변경에 취약한 테스트를 짜고 있었다&quot;&lt;/b&gt;는 생각이 들어 중간 소결차 글을 써봐야겠다는 생각이 들었습니다. 특히 회사에서 온보딩 교육을 거치며 테스트 관련 조언과 배움을 얻었던 것에서 많이 기반하였습니다. `BAD EXAMPLE`의 경우 실제로 제가 잘못사용한 예시입니다. (그대로 복사하기 좀 그런 경우는 적당히 수정하였습니다 ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 나름대로 내용을 소화했기 때문에 옳지 않은 이야기가 있을 수 있습니다. 그럴 경우 부디 댓글로 알려주세요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문득 작년 말에 했던 간단한 외주 프로젝트가 떠오르는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 간단하다고 생각되어 부끄럽지만 테스트 코드에 매우 소홀했었는데, 기능 하나가 추가되면서 연쇄적으로 다른 예외들이 터져서 프론트엔드 개발자님이 고생을 많이 하셨어요. 아마 테스트를 차곡차곡 쌓아가기 시작했으면 그렇게까지 연쇄적인 예외는 사전에 방지할 수 있었을 것 같더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 때를 반성하며...   간략하게 작성해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  테스트 작성시 주의할 점&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트도 관리 대상입니다&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 촘촘히 있으면 가장 힘들었던 부분이 이 주제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 바뀌거나, 뭐하나를 바꿀 때마다 모든 테스트를 수정해주어야 한다는 점이 스트레스였는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 지금 내가 테스트를 짜는 방법이 잘못되었나 우려가 많이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &lt;b&gt;기능이 바뀌면 연관된 테스트가 바뀐다는 것은 너무나 자연스러운 구현&lt;/b&gt;의 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 테스트를 바꾸는 과정에서 구현의 문제점을 발견하는 경험을 하기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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 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: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이름을 잘 지어야 합니다&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;테스트를 대표하는 이름(ex. `DisplayName`)은 비즈니스 내용을 포함하도록 작성합니다.
&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;/li&gt;
&lt;li&gt;객체의 역할도 잘 표현할 수 있도록 변수 이름을 짓습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 `Mock` 과 `Dummy`, `Fake`와 같은 PostFix 도 한눈에 테스트를 읽고 해당 로직을 파악하는 데에 도움을 많이 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트에는 성공 케이스도 같이 작성합니다&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위테스트에서 간혹 기능 수정에 의해 가장 기본적인 기능이 깨지거나, 예상치 못한 데이터에서 실패가 날 수 있습니다. 성공 케이스도 엣지 케이스를 생각하여 여러개 작성하면 좋은 이유가 여기서 `예상치 못한`의 범주를 많이 줄여주기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;가장 간단한 테스트를 작성합니다&lt;/b&gt;&lt;/h3&gt;
&lt;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;&lt;b&gt;하나의 테스트 안에 제어하는 변수는 최소로 하여 가장 작은 단위로 테스트&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Given-when-then 코드는 서로 섞지 않습니다&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;/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;b&gt;BAD EXAMPLE&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708839725636&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// given
val data = &quot;sth&quot;

// when
val expect = // sth

// then
assert(expect).isEquals() // sth

// when
val anotherExpect = // sth

// then
assert(expect).isEquals() // sth&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;이는 AAA패턴(Arrange/Act/Assert)에서도 마찬가지입니다. &lt;a href=&quot;https://medium.com/plain-and-simple/arrange-act-assert-vs-given-when-then-c22da421bf75&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(  Given/When/Then 과 AAA 비교 link)&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;b&gt;각 테스트끼리는 관계가 없이 독립적으로 작동할 수 있게 합니다&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;/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;b&gt;BAD EXAMPLE&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708840136338&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Order(1)
fun &quot;할 일을 등록합니다&quot; () {}

@Order(2)
fun &quot;할 일을 조회합니다&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트 구성 요소는 테스트 안에 작성합니다&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;p data-ke-size=&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;BAD EXAMPLE&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708841859827&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val memberKey
    val authorization
    val memberId
    val todoId

    @BeforeEach
    fun setup() {
        restClient = WebTestClient
                .bindToApplicationContext(context)
                .build()

        memberKey = UUID.randomUUID().toString()
        def token = // token 생성
        authorization = &quot;Bearer $token&quot;

        memberId = // member 생성
        todoId = // todo 생성
    }
    
    @Test
    fun test() {
    	// test 내용
    }&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트 환경은 테스트 결과에 영향을 미치지 않아야 합니다&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;/ul&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;테스트하는 조건이 `테스트 하고자하는 변수`라면 환경은 애플리케이션의 context, properties 등을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;미리 주입하는 데이터에 테스트가 의존하지 않아야 합니다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 미리 주입하는 데이터란, sql query 등으로 insert 해두는 더미 데이터를 뜻합니다.&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;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;/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;b&gt;빠른 테스트를 할 수 있도록 합니다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에 불필요한 context는 제거하여 테스트 실행 및 검증이 빨라지도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 구현&amp;amp;리팩토링 시마다 테스트를 실행시켜야 하기 때문에 특히나 &lt;b&gt;단위 테스트는 빠르게 결과를 확인&lt;/b&gt;할 수 있으면 경제적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 주제 관련해서 우연찮게 괜찮았던 영상이 있어 같이 첨부합니다. &lt;a href=&quot;https://toss.im/slash-21/sessions/1-6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;  토스 | 테스트 커버리지 100%&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;매직 넘버, 매직 문자열을 지양합니다&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;/ul&gt;
&lt;p data-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;i&gt;&lt;b&gt;&quot;테스트만 통과하도록 데이터를 바꾸는 것은 쉬워요&quot;&lt;/b&gt;&lt;/i&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BAD EXAMPLE&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709191129568&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//given
def tooShortId = &quot;a&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; BETTER EXAMPLE&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709191178616&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//given
def tooShortId = Generator.nextString(MIN_LENGTH - 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;버그에 대한 테스트를 남겨둡니다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 테스트를 잘 해서 배포해도 결국 버그가 일어나기 쉽습니다.&lt;/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;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  테스트를 작성해서 좋았던 점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;해당 로직이 어떤 역할을 하는지, 어떤 변수가 있는지 파악하기 좋습니다.&lt;/li&gt;
&lt;li&gt;테스트를 실행시켜 해당 로직의 호출을 즉시 할 수 있어 이해가 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;해당 도메인의 변수와 도메인간 관계를 파악하기가 좋습니다.&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;버그 배포를 방지할 수 있습니다.&lt;/li&gt;
&lt;/ul&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;파악해야 할 코드도 많고, 복잡하게 얽인 도메인 로직에서 사이드 이펙트가 많이 일어날 수 있기 때문입니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  TDD를 해야할까?&lt;/b&gt;&lt;/h2&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;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.incodom.kr/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C&quot;&gt;TDD&lt;/a&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&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;테스트할 로직이 매우 간단한 경우&lt;/b&gt;가 많았기 때문이에요.&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;테스트를 끊임없이 다시 수정&lt;/b&gt;하게 되더라고요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;API 테스트의 경우 문서처럼 작성하면 된다하더라도 단위 테스트의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&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;이렇게 되다보니 아무것도 없는 상태에서 테스트를 짜는 것이 너무 시간이 많이 걸려서 차라리 먼저 기능을 간단하게 구현해보고 실패 사례에 대한 테스트를 계속 추가하면 되지 않을까? 라는 생각이 많이 들었어요.&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;먼저 짜는 것의 좋은 점도 분명 있습니다.&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;어디까지 짜야할지&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;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 TDD의 방식으로 테스트를 미리 짜고 구현을 한다면, 좀 더 객체지향적이고  간결한(SRP) 코드를 짜는데 도움이 되는 것 같습니다.&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;u&gt;&lt;b&gt;테스트 작성 시간&lt;/b&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;더 견고하고 객체지향적인 초기 코드&lt;/b&gt;의 트레이드 오프&lt;/u&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;학습에 도움이 되었던 컨텐츠들&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.kingbbode.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테스트를 작성하는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/core/testing/unit-testing-best-practices&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;.NET Core 및 .NET 표준을 사용하는 단위 테스트 모범 사례&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/mock-test-code/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;실무에서 적용하는 테스트 코드 작성 방법과 노하우 Part 1: 효율적인 Mock Test&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/XSkz0kO7J3w?si=EltFqi0dNj_qJU6a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;영상&lt;/a&gt;도 있습니다.&lt;/li&gt;
&lt;li&gt;같은 블로그에 테스트 관련 좋은 글이 많았습니다.&lt;/li&gt;
&lt;li&gt;맨 마지막에 있던 문구가 공감이 가서 같이 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/jdlBu2vFv58?si=YUMND8_CfoYxqrFg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;토스ㅣSLASH&amp;nbsp;21&amp;nbsp;-&amp;nbsp;테스트&amp;nbsp;커버리지&amp;nbsp;100%&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Practice</category>
      <category>test</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/107</guid>
      <comments>https://new-pow.tistory.com/107#entry107comment</comments>
      <pubDate>Sat, 17 Feb 2024 10:00:44 +0900</pubDate>
    </item>
    <item>
      <title>왜 Array Index는 0부터 시작하는 것일까?</title>
      <link>https://new-pow.tistory.com/106</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 의문이 든 것은 배열의 인덱스와 N번째 요소를 페어링하여 코드를 작성할 때 문득 든 것입니다.&lt;br /&gt;아마 프로그래밍을 처음 배우는 사람들은 한번쯤 거쳐가는 질문이었을 것입니다.&lt;br /&gt;&lt;br /&gt;사실 간단히 어떤 이유에서이겠거니라는 추측은 하고 있었는데, 이것 저것 찾다가 가장 설득이 되었던 글을 모아보았습니다.&lt;/blockquote&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;a href=&quot;https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html&quot;&gt;Why numbering should start at zero&lt;/a&gt; (1982)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수열을 표시할 때, 왜 0부터 시작하는 것이 좋은지 서술하는 글입니다.&lt;/li&gt;
&lt;li&gt;어떻게 수열을 표시할 수 있는지 예시를 제시합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;길이가 N인 시퀀스를 표시할 때 &lt;code&gt;0&amp;lt;=i&amp;lt;N&lt;/code&gt; 로 표시하는 편이 `1&amp;lt;=i&amp;lt;N+1` 보다 알아보기 좋습니다.&lt;/li&gt;
&lt;li&gt;그러므로 가장 작은 자연수 시작점은 0이 되는 것이 자연스럽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;So let us let our ordinals start at zero: an element's ordinal (subscript) equals the number of elements preceding it in the sequence. And the moral of the story is that we had better regard &amp;mdash;after all those centuries!&amp;mdash; zero as a most natural number.&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;a href=&quot;https://developeronline.blogspot.com/2008/04/why-array-index-should-start-from-0.html&quot;&gt;Why The Array Index Should Start From 0&lt;/a&gt; (2008)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 C/C++에서 index를 0부터 시작했는지에 대한 글입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C/C++을 근거로하여 Java 등 다양한 언어에서도 0부터 인덱스를 시작하길 채택하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결론은 Index란 디스크 메모리와 긴밀한 연관성을 가지고 있습니다. 배열의 `시작요소부터 n 요소 떨어진` 메모리 위치를 뜻하는 것이 index 이기 때문입니다. 따라서 시작지점의 요소는 &lt;code&gt;array[0]&lt;/code&gt; 로 표시할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉 Index는 오프셋으로 사용됩니다.&lt;/li&gt;
&lt;li&gt;0부터 표기하기 시작하면 주소를 계산하기도 편합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;  리스트에서 &lt;u&gt;데이터 길이가 8bit인 데이터&lt;/u&gt;에 대해&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt;3 요소 떨어진&lt;/u&gt; 메모리 위치(3번째 요소 위치)를 컴퓨터가 계산하는 방법&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. 0부터 시작할 때&lt;/b&gt;&lt;br /&gt;&amp;nbsp; `list[2]` = 원점으로부터 `8*2 = 16bit` 떨어진 곳 부터 시작 (  편안~)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 1부터 시작할 때&lt;/b&gt;&lt;br /&gt;`list[3]` = 원점으로부터 `8*(3-1) = 16bit` 떨어진 곳부터 시작 (  매번 -1을 해주어야함)&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더불어 가장 작은 자연수인 0을 표현하고, 요소가 0개인 `빈 배열`도 표기할 수 있기 위해서 다음과 같은 코드 포맷이 유용하다고 정리하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;      for(i=0;i&amp;lt;N;i++)  
      {  
          sum+= a[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;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  Computer Science</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/106</guid>
      <comments>https://new-pow.tistory.com/106#entry106comment</comments>
      <pubDate>Sat, 17 Feb 2024 09:37:08 +0900</pubDate>
    </item>
    <item>
      <title>Github action 에서 예외 발생시 디버깅을 해보자 (feat. gradle)</title>
      <link>https://new-pow.tistory.com/102</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;local에서는 build가 되는 상황.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 github action에서 이런 에러가 났습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706587354108&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
        Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
            Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
                Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
                    Caused by: org.springframework.beans.factory.BeanCreationException at BeanDefinitionValueResolver.java:342
                        Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
                            Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
                                Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
                                    Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
                                        Caused by: java.lang.IllegalArgumentException at ConnectionUrlParser.java:67&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UiGjS/btsD5xy6ApO/ISs90KtnNkUOhKwi6cw6T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UiGjS/btsD5xy6ApO/ISs90KtnNkUOhKwi6cw6T1/img.png&quot; data-alt=&quot;역대급 어떻게 대처해야 할지 막막했던 에러.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UiGjS/btsD5xy6ApO/ISs90KtnNkUOhKwi6cw6T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUiGjS%2FbtsD5xy6ApO%2FISs90KtnNkUOhKwi6cw6T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;360&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;360&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;대충 db connection을 위한 url을 못찾고 있는 것 같고, 의존성을 만들지 못하는 것 같고....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 로컬에서는 별 문제 없이 모든 테스트와 빌드가 통과하니 정말 뭘해야할 지 모르겠더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬과 달리 action flow 중에 정보를 얻기가 쉽지 않았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때는 빌드 옵션에 로그가 찍힐 수 있도록 이렇게 추가해주면 github action console에서도 충분한 정보를 얻을 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #eff1f5; color: #4c4f69;&quot;&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt; --stacktrace --info&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706587674333&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# git workflow
	- name: Build with Gradle
        uses: gradle/gradle-build-action@v2
        with:
          arguments: --build-cache --parallel :application:test --stacktrace --info
          cache-read-only: false&lt;/code&gt;&lt;/pre&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;984&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGlA82/btsEaOmBI9F/7rKmyzKXAb434XTcVvvpEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGlA82/btsEaOmBI9F/7rKmyzKXAb434XTcVvvpEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGlA82/btsEaOmBI9F/7rKmyzKXAb434XTcVvvpEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGlA82%2FbtsEaOmBI9F%2F7rKmyzKXAb434XTcVvvpEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;106&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;232&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;13875 line이나..! 이렇게 로그가 많이 찍히니 마음이 든든하네요  &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;yml 파일에서 누락된 부분이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우에는 test profile로 스프링 애플리케이션을 실행할 때, `spring.cloud.vault.enabled: false` 설정을 누락해서 test 시 vault 설정을 찾다가 실패하는 에러가 나고 있었던 것이었습니다.&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;yml 파일을 모두 정리하며 각 active profile마다 누락되는 부분이 없는지 체크해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706588189400&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring: # test 시에는 vault를 꺼줍시다 
  cloud:
    vault:
      enabled: false&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;</description>
      <category>  bugs</category>
      <category>BUG</category>
      <category>Error</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/102</guid>
      <comments>https://new-pow.tistory.com/102#entry102comment</comments>
      <pubDate>Tue, 30 Jan 2024 22:00:45 +0900</pubDate>
    </item>
    <item>
      <title>POST, PUT 등 메서드로 서버에 API 호출시 GET으로 요청이 도달하는데요...  </title>
      <link>https://new-pow.tistory.com/101</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tbXJm/btsD4iU6x1E/eXwGWxrJqUw02gtdQb6QB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tbXJm/btsD4iU6x1E/eXwGWxrJqUw02gtdQb6QB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tbXJm/btsD4iU6x1E/eXwGWxrJqUw02gtdQb6QB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtbXJm%2FbtsD4iU6x1E%2FeXwGWxrJqUw02gtdQb6QB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;200&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;server를 띄운 후, Postman으로 요청보냈을 때 GET요청은 물론 POST, PUT, PATCH 등의 HTTP 메서드 요청이 모두 GET으로 호출되는 현상을 발견했습니다.... 처음보는 현상이라 당황하고 어디서부터 디버깅을 시작해야할지 막막했는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api 서버의 프록시에도 GET요청으로 가득한 것을 보며 이것은 요청부터가 GET으로 출발했다는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;발생 원인&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`HTTPS`를 지원하는 서버에 `http`로 접근하게 되면 `HTTPS` 로 리다이렉트될 때 메서드가 변환되어 호출되는 현상이었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhoCh8/btsD2HIekCs/9R9ABLB7ftfT3hqLCQljFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhoCh8/btsD2HIekCs/9R9ABLB7ftfT3hqLCQljFK/img.png&quot; data-alt=&quot;http 프로토콜로 요청한 것이 이유였습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhoCh8/btsD2HIekCs/9R9ABLB7ftfT3hqLCQljFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhoCh8%2FbtsD2HIekCs%2F9R9ABLB7ftfT3hqLCQljFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;126&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http 프로토콜로 요청한 것이 이유였습니다.&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;b&gt;  왜 변환이 되는 것일까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 포스트맨의 `Automatically follow redirects` 옵션을 끈 뒤, 다시 `http` 프로토콜로 요청 보내보면 301응답을 통해 리다이렉트가 유도되는 것을 확인할 수 있습니다.&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;edited_blob&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t0b6i/btsDZ4RKvC4/RH1kxzAOV3CifM4G1q1Agk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t0b6i/btsDZ4RKvC4/RH1kxzAOV3CifM4G1q1Agk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t0b6i/btsDZ4RKvC4/RH1kxzAOV3CifM4G1q1Agk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft0b6i%2FbtsDZ4RKvC4%2FRH1kxzAOV3CifM4G1q1Agk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;381&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;1188&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;서버측에서 `HTTP` 를 `HTTPS`로 리다이렉트를 하면서 3xx 응답으로 브라우저에게 다시 요청하게끔 하는 것인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 `301 Moved Permanently` 로 리다이렉션을 넘겨줄 때 `GET`, `HEAD`가 아닌 요청은 변경될 수 있으므로 주의해야 합니다.&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;If the 301 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.&lt;br /&gt;&lt;br /&gt;Note: When automatically redirecting a POST request after receiving a 301 status code, some existing HTTP/1.0 user agents will erroneously change it into a GET request.&lt;br /&gt;&lt;br /&gt;301 상태 코드가 GET 또는 HEAD 이외의 요청에 대한 응답으로 수신되는 경우, 사용자 에이전트는 요청이 발행된 조건이 변경될 수 있으므로 사용자가 확인할 수 없는 한 요청을 자동으로 리디렉션해서는 안 됩니다.&lt;br /&gt;&lt;br /&gt;참고: 301 상태 코드를 수신한 후 POST 요청을 자동으로 리디렉션할 때, 일부 기존 HTTP/1.0 사용자 에이전트는 해당&amp;nbsp;요청을 GET 요청으로 잘못 변경할 수 있습니다.&lt;br /&gt;&lt;br /&gt;- 출처 : https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;대처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 API를 호출할 때 `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;만약 `HTTP` 요청도 `GET`, `HEAD` 외의 메서드에 대해서 리다이렉션해야 한다면 308 응답을 사용하는 것을 추천합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;참고:&lt;/b&gt;&lt;br /&gt;&amp;nbsp;명세에서는 리디렉션를 수행할 때 메서드와 본문이 변경되지 않아야 한다고 하지만, 모든 사용자 에이전트가 이 요구사항을 충족하지 않습니다.&amp;nbsp;&lt;br /&gt;`301` 코드는 `GET`과 `HEAD` 메서드의 응답으로만 사용하고,&amp;nbsp;`POST` 메서드에 대해서는 메서드 변경이 명시적으로 금지된&amp;nbsp;308 Permanent Redirect&amp;nbsp;사용이 바람직합니다.&lt;br /&gt;&lt;br /&gt;- 출처 : https://developer.mozilla.org/ko/docs/Web/HTTP/Status/301&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;단 주의할 사항은 일부 브라우저에서 308 코드를 비표준 방식으로 사용한다는 것입니다.&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;Note:&amp;nbsp;This status code is much younger (June 2014) than its sibling codes and thus might not be recognized everywhere. See&amp;nbsp;Section 4&amp;nbsp;of&amp;nbsp;[RFC7538]&amp;nbsp;for deployment considerations.&lt;br /&gt;&lt;br /&gt;- 출처 : https://httpwg.org/specs/rfc9110.html#status.300&lt;/blockquote&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/MCh6p/btsD28sb31X/5dwbtc3YgUyydmMK63Kf0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MCh6p/btsD28sb31X/5dwbtc3YgUyydmMK63Kf0k/img.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;548&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/MCh6p/btsD28sb31X/5dwbtc3YgUyydmMK63Kf0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMCh6p%2FbtsD28sb31X%2F5dwbtc3YgUyydmMK63Kf0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wzvql/btsD3IT3XAn/H6uqN0LN48QnApo44vcpa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wzvql/btsD3IT3XAn/H6uqN0LN48QnApo44vcpa0/img.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;548&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/wzvql/btsD3IT3XAn/H6uqN0LN48QnApo44vcpa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwzvql%2FbtsD3IT3XAn%2FH6uqN0LN48QnApo44vcpa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;브라우저 호환성 비교 [출처 : https://developer.mozilla.org/ko/docs/Web/HTTP/Status]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;301&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;308&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;영구적으로 리다이렉션&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;메소드 유지&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;`GET`, `HEAD` only&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 17px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706306962692&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;HTTP/1.1: Status Code Definitions&quot; data-og-description=&quot;part of Hypertext Transfer Protocol -- HTTP/1.1 RFC 2616 Fielding, et al. 10 Status Code Definitions Each Status-Code is described below, including a description of which method(s) it can follow and any metainformation required in the response. 10.1 Inform&quot; data-og-host=&quot;www.w3.org&quot; data-og-source-url=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2&quot; data-og-url=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2&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;HTTP/1.1: Status Code Definitions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;part of Hypertext Transfer Protocol -- HTTP/1.1 RFC 2616 Fielding, et al. 10 Status Code Definitions Each Status-Code is described below, including a description of which method(s) it can follow and any metainformation required in the response. 10.1 Inform&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.w3.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Status/301&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Status/301&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706307999390&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;301 Moved Permanently - HTTP | MDN&quot; data-og-description=&quot;HTTP 301 Moved Permanently 리디렉션 상태 응답 코드는 요청한 리소스가 Location (en-US) 헤더에 주어진 URL로 완전히 옮겨졌다는 것을 나타냅니다. 브라우저는 이 페이지로 리디렉트하고, 검색 엔진은 해&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Status/301&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Status/301&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dD82sW/hyU82bbsVg/QnS9ewoX13wNWmMNGtvDD1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Status/301&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Status/301&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dD82sW/hyU82bbsVg/QnS9ewoX13wNWmMNGtvDD1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&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;301 Moved Permanently - HTTP | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTP 301 Moved Permanently 리디렉션 상태 응답 코드는 요청한 리소스가 Location (en-US) 헤더에 주어진 URL로 완전히 옮겨졌다는 것을 나타냅니다. 브라우저는 이 페이지로 리디렉트하고, 검색 엔진은 해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://httpwg.org/specs/rfc9110.html#status.300&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://httpwg.org/specs/rfc9110.html#status.300&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706308819530&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;RFC9110&quot; data-og-description=&quot;HTTP Semantics&quot; data-og-host=&quot;httpwg.org&quot; data-og-source-url=&quot;https://httpwg.org/specs/rfc9110.html#status.300&quot; data-og-url=&quot;https://httpwg.org/specs/rfc9110.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/daWKOd/hyVb55b8au/FUdvJnyKnYxzZLRZmhP6lk/img.png?width=180&amp;amp;height=180&amp;amp;face=0_0_180_180&quot;&gt;&lt;a href=&quot;https://httpwg.org/specs/rfc9110.html#status.300&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://httpwg.org/specs/rfc9110.html#status.300&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/daWKOd/hyVb55b8au/FUdvJnyKnYxzZLRZmhP6lk/img.png?width=180&amp;amp;height=180&amp;amp;face=0_0_180_180');&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;RFC9110&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTP Semantics&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;httpwg.org&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>  bugs</category>
      <category>BUG</category>
      <category>HTTP</category>
      <category>https</category>
      <category>web</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/101</guid>
      <comments>https://new-pow.tistory.com/101#entry101comment</comments>
      <pubDate>Sat, 27 Jan 2024 06:45:24 +0900</pubDate>
    </item>
    <item>
      <title>2024-W04 : WWG 참여 후기</title>
      <link>https://new-pow.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 주에 한 것&lt;/b&gt;&lt;/h2&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;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmeFwy/btsEjU9fXMc/84hMrU748fqTKhB9WXKqBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmeFwy/btsEjU9fXMc/84hMrU748fqTKhB9WXKqBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmeFwy/btsEjU9fXMc/84hMrU748fqTKhB9WXKqBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmeFwy%2FbtsEjU9fXMc%2F84hMrU748fqTKhB9WXKqBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;331&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;940&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;What is New?!&lt;/b&gt;&lt;/h3&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;직장에서 인프라 구축을 찍먹해보는 기간을 가졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아.. 정말 너무 어려웠습니다. docker container와 docker-compose 정도가 컨테이너 오케스트레이션 경험의 다였거든요.&lt;/p&gt;
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`k8s`   kubernetes 의 중간 글자를 줄여 부르는 말. 자동 배포, 스케일링, 관리를 편하게 해주는 오픈 소스. 더 경량화한 오픈소스는 k3s 라고 하는 것 같다. (.....)&lt;/li&gt;
&lt;li&gt;`ArgoCD`   k8s 애플리케이션의 배포를 자동화할 수 있는 오픈 소스. UI도 지원해서 직관적으로 구조를 파악하기 좋다.&lt;/li&gt;
&lt;li&gt;`Helm`   k8s 애플리케이션의&amp;nbsp; 스펙을 정의하는데 사용한다. yml 파일 형식으로, 템플릿과 각 정의가 서로 override되어 배포 설정을 간편하게 할 수 있도록 돕는다.&lt;/li&gt;
&lt;li&gt;`Declarative 선언형` &lt;b&gt;  &lt;/b&gt;원하는 상태를 먼저 정의하고 실행을 시작하면. 그 선언에 맞게 수행하는 것은 k8s의 역할이다. 즉, 필요한 것을 하기 위해 명령어를 직접 입력하지 않아도 된다.&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;/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;b&gt;Woman Who Go 참여한 후기&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_IMG_2475.jpeg&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;1687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yNyAv/btsEkfrLIg1/P8gm6hA3gimzmjDWVDZYt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yNyAv/btsEkfrLIg1/P8gm6hA3gimzmjDWVDZYt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yNyAv/btsEkfrLIg1/P8gm6hA3gimzmjDWVDZYt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyNyAv%2FbtsEkfrLIg1%2FP8gm6hA3gimzmjDWVDZYt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;337&quot; data-filename=&quot;edited_IMG_2475.jpeg&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;1687&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;평소에 점점 사용사례가 늘어나는 Golang도 관심이 많았고 이런 개발 행사에 한번쯤 가보고 싶어서 신청했습니다.&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;그리고 Go를 엄청 영업당하고 왔어요 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 보기에 Go는 최적화가 잘 되어있고, Goroutine이라는 경량 스레드로 비동기 처리를 하는데 유리하다는 것이 가장 매력적이더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점은 다른 언어에서는 당연한 것들이 Go에는 없는 경우가 더러 있다는 것. 풍부한 오픈소스와 리소스 환경인 자바와는 다르게 직접 환경을 조성해 나가야만하는구나 하는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 그게 오히려 매력적이기도 하더라고요. 아직 할 것들이 엄청 많다는 거니까!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Golang 개발자들은 엄청 적극적이겠구나 싶기도 하고요. (실제로 ㅎㅎ 행사에서 그런 기운이 잘 느껴졌던 것 같아요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;좋았던 점&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; 비기너들을 환영해주는 분위기. Golang에 대해 잘 모르고 갔지만 전혀 소외되지 않는 내용들이었다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Golang의 장점. 다른 언어와 뭐가 다른지. 그 언어에만 있는 고유 개념들이 자바만 알던 나에게는 신세계였다.&lt;/li&gt;
&lt;li&gt;그러나 시간이 갈수록 언어는 서로 닮아가는 것 같기도....&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;잔망스러웠던 Gopher(캐릭터) 너무 귀여움 ㅜㅜ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caVTcD/btsEneygbI9/5anqkYtLinTu8k88PEEBbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caVTcD/btsEneygbI9/5anqkYtLinTu8k88PEEBbk/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;nbsp;https://medium.com/@pushplaybang/getting-going-with-go-golang-e396589c8273&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caVTcD/btsEneygbI9/5anqkYtLinTu8k88PEEBbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaVTcD%2FbtsEneygbI9%2F5anqkYtLinTu8k88PEEBbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;600&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;nbsp;https://medium.com/@pushplaybang/getting-going-with-go-golang-e396589c8273&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;아쉬웠던 점&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장비 등 딜레이 문제로 핸즈온, 스피치 시간이 많이 줄어들었다. 특히 핸즈온은 절대 따라갈 수가 없는 속도가 되었다. 분명 초급 예제였는데  &lt;/li&gt;
&lt;li&gt;Golang의 개요에 대한 같은 이야기의 반복
&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Golang은 당장 메인 언어로서는 힘들지만, 간결하고 쉽게 배울 수 있는 언어인만큼 Golang도 계속 관심 가지고 이것저것 시도해봐야겠다 하는 생각이 들었습니다.&lt;/p&gt;
&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;IMG_2489.jpeg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NSrLC/btsElJFWgqc/hhoJy0dRgfX82WvcvrP8V0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NSrLC/btsElJFWgqc/hhoJy0dRgfX82WvcvrP8V0/img.jpg&quot; data-alt=&quot;핸즈온 때 질문해서 책도  받아왔습니다   히히&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NSrLC/btsElJFWgqc/hhoJy0dRgfX82WvcvrP8V0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNSrLC%2FbtsElJFWgqc%2FhhoJy0dRgfX82WvcvrP8V0%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;600&quot; data-filename=&quot;IMG_2489.jpeg&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;What I Leanred&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내가 무엇이 필요한지 잘 아는 힘이 필요하다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 새로 배운 것은 아니고 그냥 고민입니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이 시기에만 할 수 있는 생각인 것 같아서 기록해둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 계속 질문하는 것이 일인 사람(= 신입....)이 되니까 이런 생각이 많이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'질문은 대체 어떻게 잘 하는가?'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'어떻게 해야 내가 궁금한 것을 잘 전달할 수 있나?'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'어떻게 해야 내가 원하는 답을 얻을 수 있나?'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'어떻게 해야 내게 지금 필요한 것을 알 수 있나?'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이 고민의 소결을 해보자면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 경험을 쌓으면 쌓을수록 더 잘 질문할 수 있게 된다는 생각이 들더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나에 대해 아는 것도 중요하고, 문제의 핵심을 파악할 수 있는 능력도 중요한데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그게 (천부적인 재능에서 나올 수도 있지만) 결국 경험이 가져다 주는 통찰이 가장 현실적인 방법이 아닐까...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 경험이 쌓일 때까지 질문을 계속 못하고 있을 수는 없잖아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경험이 보다 더 정확한 질문을 할 수 있게하는 것은 맞지만.. 완벽한 질문을 하기 위해 아둥바둥 하지 않기로 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이순간 내가 인지하고 있는 수준에서 질문하는 것이 최선&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다음 주에 할 것&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGC7i/btsEnOlSFNl/8fdTvQEfgboxtPV3Epc41k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGC7i/btsEnOlSFNl/8fdTvQEfgboxtPV3Epc41k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGC7i/btsEnOlSFNl/8fdTvQEfgboxtPV3Epc41k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGC7i%2FbtsEnOlSFNl%2F8fdTvQEfgboxtPV3Epc41k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;288&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/100</guid>
      <comments>https://new-pow.tistory.com/100#entry100comment</comments>
      <pubDate>Sat, 27 Jan 2024 06:16:11 +0900</pubDate>
    </item>
    <item>
      <title>2024-W03 : 코틀린을 학습했던 방법</title>
      <link>https://new-pow.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 주까지 한 것&lt;/b&gt;&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린과 spring webFlux을 비롯한 새로운 프레임워크와 테스트 툴 등을 학습했습니다.&lt;/li&gt;
&lt;/ul&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;What I Leanred&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내가 코틀린을 배웠던 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 언어를 학습하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트나 그냥 책 예제를 따라하는 것이 아니라 프로젝트에서 쓰기위한 언어를 학습하는 것은 처음이라 초반에 어떻게 학습을 할 지 막막했었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 코틀린을 배우기 시작하면서 일종의 배우는 노하우가 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기록겸 누군가 도움이 될 수 있으니 가볍게 적어둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 공식 튜토리얼을 따라합니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 자바는 공식문서가 약한 편이지만, 다른 언어들은 공식문서가 참 잘되어있습니다. 튜토리얼을 따라하며 가장 기본적인 문법을 익혔습니다. 이 때 중간에 궁금한 것들이 생기면 바로 공식문서 내에서 찾아볼 수 있어서 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;example을 쭉보며 문법을 눈에 익히고, concept를 이해하는 것도 추천합니다.&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;a href=&quot;https://kotlinlang.org/docs/kotlin-tour-welcome.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kotlinlang.org/docs/kotlin-tour-welcome.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1705708248230&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;Welcome to our tour of Kotlin! | Kotlin&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/docs/kotlin-tour-welcome.html&quot; data-og-url=&quot;https://kotlinlang.org/docs/kotlin-tour-welcome.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2blaA/hyU5PiE1xA/Q62OxIYE7DsSaDoKLvGedk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/kotlin-tour-welcome.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/docs/kotlin-tour-welcome.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2blaA/hyU5PiE1xA/Q62OxIYE7DsSaDoKLvGedk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400');&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;Welcome to our tour of Kotlin! | Kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinlang.org&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://play.kotlinlang.org/byExample/overview?_gl=1*1a0nbzr*_ga*NDg0MTQ0MDg4LjE3MDQzNTk5NTY.*_ga_9J976DJZ68*MTcwNTcwODEwMi4xMy4xLjE3MDU3MDgzMzkuMC4wLjA.&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://play.kotlinlang.org/byExample/overview?_gl=1*1a0nbzr*_ga*NDg0MTQ0MDg4LjE3MDQzNTk5NTY.*_ga_9J976DJZ68*MTcwNTcwODEwMi4xMy4xLjE3MDU3MDgzMzkuMC4wLjA.&lt;/a&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;b&gt;2. POCO 프로젝트를 작게 만들어봅니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 프레임워크를 사용하지 않은 프로젝트를 만들어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 로또게임 이라든지, 야구게임, 사다리게임 같은 것들이요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 해당 언어로 돌아가게만 만들어 보는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 앞에서 배웠던 기본 문법들을 조합해서 쓰는 방법을 많이 익혔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 간단한 프로젝트를 마이그레이션 해봅니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 아이디어가 필요없는 게시판 글쓰기, 회원가입과 로그인 같은 간단한 프로젝트를 마이그레이션 해보며 언어와 프레임워크에 익숙해집니다. 이 언어가 두 번째 언어라면 이미 어떤 것이 필요한지 알고 있을 겁니다. 그걸 공식문서에서 찾거나 구글링하면서 채워나가는 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 찾는 도중에 기존 언어와 차이점을 많이 학습하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 여기 중간에 멈추고 회사 온보딩 과정을 따르게 되었습니다만, 만약 더 학습할 시간이 주어진다면 조금 더 큰 규모의 토이 프로젝트를 해볼 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배우면서 느낀점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 공부를 시작한 이래로 계속 자바 스프링 환경에서만 개발을 해보았는데요.&lt;/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;동료분께서 이런 글을 공유해주셨는데요. 제가 느꼈던 바와 비슷해 공감이 많이 가서 공유합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://milkymilky0116.github.io/posts/thoughts_on_learning_programming_language/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://milkymilky0116.github.io/posts/thoughts_on_learning_programming_language/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1705708739717&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;내가 끊임없이 새로운 것을 배우는 이유&quot; data-og-description=&quot;시작하기 전에, 이 글은 내가 배웠던 언어들에 대한 소개를 하려는 목적으로 쓰인 것이 아니다. 혹은 어떤 방법론에 대해서 설교하고자 쓰인 글은 더더욱 아니다. 이 글은 그저 호기심이 많은 주&quot; data-og-host=&quot;milkymilky0116.github.io&quot; data-og-source-url=&quot;https://milkymilky0116.github.io/posts/thoughts_on_learning_programming_language/&quot; data-og-url=&quot;https://milkymilky0116.github.io/posts/thoughts_on_learning_programming_language/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://milkymilky0116.github.io/posts/thoughts_on_learning_programming_language/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://milkymilky0116.github.io/posts/thoughts_on_learning_programming_language/&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;시작하기 전에, 이 글은 내가 배웠던 언어들에 대한 소개를 하려는 목적으로 쓰인 것이 아니다. 혹은 어떤 방법론에 대해서 설교하고자 쓰인 글은 더더욱 아니다. 이 글은 그저 호기심이 많은 주&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;milkymilky0116.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다음 주에 할 것&lt;/b&gt;&lt;/h2&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;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/00VLe/btsDJl6dKNq/eDXfRMzEACWibAVQnG3CZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/00VLe/btsDJl6dKNq/eDXfRMzEACWibAVQnG3CZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/00VLe/btsDJl6dKNq/eDXfRMzEACWibAVQnG3CZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F00VLe%2FbtsDJl6dKNq%2FeDXfRMzEACWibAVQnG3CZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;268&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <category>log</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/99</guid>
      <comments>https://new-pow.tistory.com/99#entry99comment</comments>
      <pubDate>Thu, 18 Jan 2024 15:54:26 +0900</pubDate>
    </item>
    <item>
      <title>멀티 모듈을 설정하며 겪었던 예외들...(feat. Gradle, Springboot)</title>
      <link>https://new-pow.tistory.com/98</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 MSA에 대해서 학습할 때 키워드로만 접해봤습니다만, 본격적으로 이를 학습하고 구축해보는 것은 처음이었는데요.&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;&lt;b&gt;멀티모듈이 무엇인가?&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모놀리식 Monolithic, 마이크로 서비스 MicroService Archtecture 그리고 멀티모듈..?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 개념을 접하며  가장 먼저 들었던 질문은 &lt;b&gt;&quot;MSA와 멀티모듈이 무슨 차이점이 있고 왜 같이 많이 언급되는가?&quot;&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;모놀리식 Monolithic 아키텍처&lt;/b&gt;입니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;... the communication in Monolithic applications are inter-process communication. So that means it is working on single process that invoke one to another by using method calls.&amp;nbsp;You just create class and call the method inside of target module. All running the same process.&lt;br /&gt;&lt;br /&gt;모놀리식 애플리케이션의 통신은 프로세스 내부의 통신입니다. 즉, 메서드 호출을 사용하여 다른 프로세스를 호출하는&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단일 프로세스에서 작동&lt;/b&gt;한다는 의미입니다. 클래스를 생성하고 대상 모듈 내부에서 메서드를 호출하기만 하면 됩니다. 모두 동일한 프로세스를 실행합니다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;a href=&quot;https://medium.com/design-microservices-architecture-with-patterns/monolithic-architecture-is-still-worth-at-2021-98bfc112dc24&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;- Monolithic Architecture Is Still Worth at 2021?&lt;/a&gt;&lt;/i&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;/p&gt;
&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;i&gt;&lt;b&gt;[ 아래 : 모놀리식 아키텍처의 발전과정 ]&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIAbo/btsDqhQxYJ2/FBnKY4ERgMCCKdi3vf9ZNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIAbo/btsDqhQxYJ2/FBnKY4ERgMCCKdi3vf9ZNk/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;nbsp;https://medium.com/design-microservices-architecture-with-patterns/monolithic-architecture-is-still-worth-at-2021-98bfc112dc24&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIAbo/btsDqhQxYJ2/FBnKY4ERgMCCKdi3vf9ZNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIAbo%2FbtsDqhQxYJ2%2FFBnKY4ERgMCCKdi3vf9ZNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;302&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;nbsp;https://medium.com/design-microservices-architecture-with-patterns/monolithic-architecture-is-still-worth-at-2021-98bfc112dc24&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;앞서 언급한 모놀리식 아키텍처의 단점으로 인해 좀 더 확장가능성 있고, 유연하게 리팩터링을 할 수 있는 개발 아키텍처로 많이 선택되는 것이 &lt;b&gt;마이크로 서비스 아키텍처 MSA&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스를 모듈화하여 커다란 소프트웨어 기능을 작게 쪼개어 제공하는 방식인데요. 이들이 각자 개별적인 작업을 담당하며 독립적으로 움직이고, API를 통해 다른 서비스와 통신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pNieO/btsDp5v4pKs/6Jaoj9fcqJBaFLxQjB0mvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pNieO/btsDp5v4pKs/6Jaoj9fcqJBaFLxQjB0mvk/img.png&quot; data-alt=&quot;이미지 출처 :&amp;amp;amp;nbsp;https://giljae.medium.com/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-microservices-architecture-%EC%9D%98-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EB%8B%A8%EC%A0%90-7c45615cfe1a&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pNieO/btsDp5v4pKs/6Jaoj9fcqJBaFLxQjB0mvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpNieO%2FbtsDp5v4pKs%2F6Jaoj9fcqJBaFLxQjB0mvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;510&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 :&amp;amp;nbsp;https://giljae.medium.com/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-microservices-architecture-%EC%9D%98-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EB%8B%A8%EC%A0%90-7c45615cfe1a&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;각 기능의 코드가 물리적으로 많지 않기 때문에 다른 개발자가 읽기에 어렵지 않고, 단일 모듈의 장애가 전체 어플리케이션에는 크게 영향을 받지 않는 장점이 있습니다. 특 각 모듈별로 새로운 기술을 적용하고 리팩터링하는 데에 유연합니다. 필요에 의해 일부 모듈에 리소스를 더 할당할 수 있기도 하고요.&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;b&gt;왜 멀티모듈과 MSA는 같이 언급되는 것일까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분을 학습할 때는 우아한테크세미나 &lt;a href=&quot;https://youtu.be/nH382BcycHc?si=tACXaTCfsLlASzBu&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;(작성중....)&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;1280&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci0Wy4/btsDnDNSYbO/yDcnDn7MVoaX1YtXkKzUT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci0Wy4/btsDnDNSYbO/yDcnDn7MVoaX1YtXkKzUT0/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;nbsp;https://youtu.be/nH382BcycHc?si=e0elGtIZWM7H3YAc&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci0Wy4/btsDnDNSYbO/yDcnDn7MVoaX1YtXkKzUT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci0Wy4%2FbtsDnDNSYbO%2FyDcnDn7MVoaX1YtXkKzUT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;236&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;605&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;nbsp;https://youtu.be/nH382BcycHc?si=e0elGtIZWM7H3YAc&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;b&gt;겪었던 에러 상황들과 고민했던 것들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 멀티모듈을 구성할 때 튜토리얼은 &lt;a href=&quot;https://docs.gradle.org/current/userguide/declaring_dependencies_between_subprojects.html#header&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gradle 공식문서&lt;/a&gt;와 &lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-09-06-multi-module/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;멀티 모듈 적용하기 with Gradle&lt;/a&gt;, &lt;a href=&quot;https://youtu.be/PdofVTuM-tE?si=g56g6183u_bKJfTt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SpringBoot + Kotlin 멀티 모듈 구성 - 단일모듈에서 멀티모듈로 변경해보기&lt;/a&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;1280&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nHWAC/btsDnwBoMOq/Kk6qbJEKIkyuNPKylnVpw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nHWAC/btsDnwBoMOq/Kk6qbJEKIkyuNPKylnVpw1/img.png&quot; data-alt=&quot;Gradle multi project build. 출처 :&amp;amp;nbsp;https://docs.gradle.org/current/userguide/intro_multi_project_builds.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nHWAC/btsDnwBoMOq/Kk6qbJEKIkyuNPKylnVpw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnHWAC%2FbtsDnwBoMOq%2FKk6qbJEKIkyuNPKylnVpw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;756&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gradle multi project build. 출처 :&amp;nbsp;https://docs.gradle.org/current/userguide/intro_multi_project_builds.html&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;그리고 구축과정, 테스트와 run해보며 겪었던 오류에 대해 시간순서대로 정리하였습니다.&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;/b&gt;&lt;br /&gt;Springboot, Gradle, Kotlin&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;&lt;b&gt;파일 구조를 잘 못 설정했을 때 `Execution failed for task ':bootRun'.`&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;에러코드&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 모듈을 세팅하고 하위 서브 모듈을 설정했을 때 발생한 예외코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705132993750&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Execution failed for task ':bootRun'.
&amp;gt; Failed to calculate the value of task ':bootRun' property 'mainClass'.
   &amp;gt; Main class name has not been configured and it could not be resolved

* Try:
&amp;gt; Run with --stacktrace option to get the stack trace.
&amp;gt; Run with --info or --debug option to get more log output.
&amp;gt; Run with --scan to get full insights.
&amp;gt; Get more help at https://help.gradle.org.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.4/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 1s
3 actionable tasks: 2 executed, 1 up-to-date&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;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;/ul&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;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTr7je/btsDpb4DMV1/haMjpc0oQrbtaWaJsn0410/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTr7je/btsDpb4DMV1/haMjpc0oQrbtaWaJsn0410/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTr7je/btsDpb4DMV1/haMjpc0oQrbtaWaJsn0410/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTr7je%2FbtsDpb4DMV1%2FhaMjpc0oQrbtaWaJsn0410%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;220&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 파일 구성에서 root module에 src가 남아있기 때문이었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705133323232&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;├── buildSrc
│   ...
├── api
│   ├── src
│   │   └──...
│   └── build.gradle
├── services
│   └── person-service
│       ├── src
│       │   └──...
│       └── build.gradle
├── shared
│   ├── src
│   │   └──...
│   └── build.gradle
├── src # 이부분이 있어서....
└── settings.gradle&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;root module에 있는 src 파일을 삭제해 주었습니다.&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;`build.gradle`dependency configuration 시 `api`, `implementation`의 차이점은 무엇일까?&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1705207317369&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gadle.kts
dependencies {
    implementation(&quot;org.hibernate:hibernate-core:3.6.7.Final&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;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://docs.gradle.org/current/userguide/dependency_management_for_java_projects.html#sec:configurations_java_tutorial&quot;&gt;공식 문서&lt;/a&gt;에 따르면 이런 차이점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`implementation`
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`api`
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`testImplementation`
&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: #292c32; text-align: start;&quot;&gt;프로젝트의 테스트 소스를 컴파일하고 실행하는 데 필요한 종속성&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bde5fR/btsDrHgW5Yn/g87DFEfykZJQAy1sD8AYX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bde5fR/btsDrHgW5Yn/g87DFEfykZJQAy1sD8AYX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bde5fR/btsDrHgW5Yn/g87DFEfykZJQAy1sD8AYX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbde5fR%2FbtsDrHgW5Yn%2Fg87DFEfykZJQAy1sD8AYX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&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;가장 큰 차이점은 `api`의 경우 의존성 받는 모듈의 하위 의존성까지 참조할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 이 경우 의존성의 전파로 상위 모듈 오염이 발생할 수 있는 가능성이 늘어나므로 가급적 지양하는 것이 좋습니다. (물론 정답이라기 보다 한 관점임) &lt;a href=&quot;https://geminikims.medium.com/%EC%A7%80%EC%86%8D-%EC%84%B1%EC%9E%A5-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EB%B0%A9%EB%B2%95-97844c5dab63&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;`org.junit.platform.commons.JUnitException` 발생시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spock api test를 작성하고 실행시켰을 때 다음과 같은 에러가 발생하였습니다.&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;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1705215155661&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/Users/berapt/Library/Java/JavaVirtualMachines/azul-17.0.9/Contents/Home/bin/java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=65269:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit5-rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar:/Users/berapt/Documents/GitHub/backend-ojt/app/out/test/classes:/Users/berapt/Documents/GitHub/backend-ojt/app/out/test/resources:/Users/berapt/Documents/GitHub/backend-ojt/app/out/production/classes:/Users/berapt/Documents/GitHub/backend-ojt/app/out/production/resources:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-reflect/1.9.20/34eeb4eed5f493cdbb760ef50a5653ec414006bf/kotlin-reflect-1.9.20.jar:/Users/berapt/Documents/GitHub/backend-ojt/domain/command/out/production/classes:/Users/berapt/Documents/GitHub/backend-ojt/domain/command/out/production/resources:/Users/berapt/Documents/GitHub/backend-ojt/domain/query/out/production/classes:/Users/berapt/Documents/GitHub/backend-ojt/domain/query/out/production/resources:/Users/berapt/Documents/GitHub/backend-ojt/domain/common/out/production/classes:/Users/berapt/Documents/GitHub/backend-ojt/infrastructure/out/production/classes:/Users/berapt/Documents/GitHub/backend-ojt/infrastructure/out/production/resources:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.9.20/e58b4816ac517e9cc5df1db051120c63d4cde669/kotlin-stdlib-1.9.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/jakarta.inject/jakarta.inject-api/2.0.1/4c28afe1991a941d7702fe1362c365f0a8641d1e/jakarta.inject-api-2.0.1.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-webflux/2.6.8/746f6f3e5df1aa38846b5283e721734b6132089b/spring-boot-starter-webflux-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-test/2.6.8/fc0744a41a4366a1507b4137ebc8f44db92d0847/spring-boot-starter-test-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.spockframework/spock-spring/2.2-M1-groovy-4.0/fd3b978ab984e81333afd7af939556779e3d29ec/spock-spring-2.2-M1-groovy-4.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.spockframework/spock-core/2.2-M1-groovy-4.0/3b001b8365c2703fd2dfb509da22b63144dbe3f0/spock-core-2.2-M1-groovy-4.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-runner-junit5-jvm/5.7.2/ac0bf80b9970b7c5b835454acb80f4bc07b378e5/kotest-runner-junit5-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-assertions-core-jvm/5.7.2/b684e98ebc3ead2ad779e78ebb00c52fccc3709e/kotest-assertions-core-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-property-jvm/5.7.2/f3daf8de510335662205f12df4243aa2335728f9/kotest-property-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-json/2.6.8/ea3311052a96a1bf0f975334cda285769d40a6b/spring-boot-starter-json-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/2.6.8/d6ad2d708e9bf7ebc71f85e0b001a68d591ba2d0/spring-boot-starter-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-reactor-netty/2.6.8/647038dc085a4006b624841b71682a9be472a2b/spring-boot-starter-reactor-netty-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-webflux/5.3.20/4f93011f4c62be81463fe4d944c115989d465465/spring-webflux-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-web/5.3.20/3c2fe9363760d62d5b7c9f087bb4255e3377a0b2/spring-web-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-test-autoconfigure/2.6.8/da8747b50d70ad643527c845044266cf851205d9/spring-boot-test-autoconfigure-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-test/2.6.8/7607c8f01a4b9e51697276304ab1bebec0956113/spring-boot-test-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.jayway.jsonpath/json-path/2.6.0/67f565b424f7903a12d4f5b9361b11462ecacdac/json-path-2.6.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/jakarta.xml.bind/jakarta.xml.bind-api/2.3.3/48e3b9cfc10752fba3521d6511f4165bea951801/jakarta.xml.bind-api-2.3.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.assertj/assertj-core/3.21.0/27a14d6d22c4e3d58f799fb2a5ca8eaf53e6942a/assertj-core-3.21.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest/2.2/1820c0968dba3a11a1b30669bb1f01978a91dedc/hamcrest-2.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter/5.8.2/5a817b1e63f1217e5c586090c45e681281f097ad/junit-jupiter-5.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.mockito/mockito-junit-jupiter/4.0.0/b76de25bd6e5d8f7924d0536729c0076e37e9396/mockito-junit-jupiter-4.0.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.mockito/mockito-core/4.0.0/f5195e0c4a45716bbd2d1d29173adbd148acce3a/mockito-core-4.0.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.skyscreamer/jsonassert/1.5.0/6c9d5fe2f59da598d9aefc1cfc6528ff3cf32df3/jsonassert-1.5.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-test/5.3.20/33a92d5066fb810023969a0d70fac96387962769/spring-test-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/5.3.20/4b88aa3c401ede3d6c8ac78ea0c646cf326ec24b/spring-core-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.xmlunit/xmlunit-core/2.8.4/35be57989ca80eefa03161b211630e319a8f36c6/xmlunit-core-2.8.4.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-engine/1.8.2/b737de09f19864bd136805c84df7999a142fec29/junit-platform-engine-1.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.apache.groovy/groovy/4.0.0/6856ea9e77c50c52429d996a9ce194db8497e682/groovy-4.0.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.9.20/e2b4d1f475ae0606d063a84fce4dccdb45c7e12a/kotlin-stdlib-jdk8-1.9.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-launcher/1.8.2/c334fcee82b81311ab5c426ec2d52d467c8d0b28/junit-platform-launcher-1.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-suite-api/1.8.2/a148ffd359cac121fcba000ad7f5a75b5e2ac2b4/junit-platform-suite-api-1.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.8.2/4c21029217adf07e4c0d0c5e192b6bf610c94bdc/junit-jupiter-api-5.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.github.curious-odd-man/rgxgen/1.4/8d5947bd00bd8e12313c56b5e6f5f9f2f0e34433/rgxgen-1.4.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.13.3/ad2f4c61aeb9e2a8bb5e4a3ed782cfddec52d972/jackson-datatype-jsr310-2.13.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-parameter-names/2.13.3/f71c4ecc1a403787c963f68bc619b78ce1d2687b/jackson-module-parameter-names-2.13.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.13.3/d4884595d5aab5babdb00ddbd693b8fd36b5ec3c/jackson-datatype-jdk8-2.13.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.13.3/56deb9ea2c93a7a556b3afbedd616d342963464e/jackson-databind-2.13.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/2.6.8/cd1a9b61a270219ba6964fc8f4212fc80990be6/spring-boot-autoconfigure-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/2.6.8/2bfaf71bfba3d7dcbc0ad0301a0c0aa3af8513af/spring-boot-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-logging/2.6.8/8b473f1982ebd19f2fd16f7e3f65b6e8c070d0ea/spring-boot-starter-logging-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/1.3.5/59eb84ee0d616332ff44aba065f3888cf002cd2d/jakarta.annotation-api-1.3.5.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.29/6d0cdafb2010f1297e574656551d7145240f6e25/snakeyaml-1.29.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.projectreactor.netty/reactor-netty-http/1.0.19/bcb2d93714306e8d1235e16cc953ac2bf88ac93c/reactor-netty-http-1.0.19.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/5.3.20/ab88bd9e3a8307f5c0516c15d295c88ec318659/spring-beans-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.projectreactor/reactor-core/3.4.18/29f4f3a4876a65861deffc0f7f189029bcaf7946/reactor-core-3.4.18.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/net.minidev/json-smart/2.4.8/7c62f5f72ab05eb54d40e2abf0360a2fe9ea477f/json-smart-2.4.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.36/6c62681a2f655b49963a5983b8b0950a6120ae14/slf4j-api-1.7.36.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/jakarta.activation/jakarta.activation-api/1.2.2/99f53adba383cb1bf7c3862844488574b559621f/jakarta.activation-api-1.2.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-params/5.8.2/ddeafe92fc263f895bfb73ffeca7fd56e23c2cce/junit-jupiter-params-5.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy/1.11.22/8b4c7fa5562a09da1c2a9ab0873cb51f5034d83f/byte-buddy-1.11.22.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy-agent/1.11.22/2fbcf3210dfc09b42242e3b66a5281cc5b9adb80/byte-buddy-agent-1.11.22.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.vaadin.external.google/android-json/0.0.20131108.vaadin1/fa26d351fe62a6a17f5cda1287c1c6110dec413f/android-json-0.0.20131108.vaadin1.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/5.3.20/35119231d09863699567ce579c21512ddcbc5407/spring-jcl-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.8.2/32c8b8617c1342376fd5af2053da6410d8866861/junit-platform-commons-1.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.opentest4j/opentest4j/1.2.0/28c11eb91f9b6d8e200631d46e20a7f407f2a046/opentest4j-1.2.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.apiguardian/apiguardian-api/1.1.2/a231e0d844d2721b0fa1b238006d15c6ded6842a/apiguardian-api-1.1.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-framework-engine-jvm/5.7.2/50d6dc19ba13259fe121cc44a00068634d0e39fd/kotest-framework-engine-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-framework-api-jvm/5.7.2/c7a563e04b5ddc008ffb237f066504e1d0ec5025/kotest-framework-api-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-framework-discovery-jvm/5.7.2/9a13b703f1cf45e5c7d63fd393cf446428e0802b/kotest-framework-discovery-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-common-jvm/5.7.2/508fd4ecd453d987171a1297f9580c211fb02462/kotest-common-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-extensions-jvm/5.7.2/3badce4a9576705e043fbe548d85ffe2ca30488d/kotest-extensions-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-framework-concurrency-jvm/5.7.2/dd63871080d2f739eb1937754f48abbf9a14b091/kotest-framework-concurrency-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.5.2/f4cc07a50437659e0043e7da762809a46932b6a0/kotlinx-coroutines-core-jvm-1.5.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.9.20/8b4b73f4e08efaae93fc01d8c6eaab58a72d2ab3/kotlin-stdlib-jdk7-1.9.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-assertions-shared-jvm/5.7.2/1329f64da3b88621e8cf1c7d29d6cc90761e3751/kotest-assertions-shared-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.13.3/7198b3aac15285a49e218e08441c5f70af00fc51/jackson-annotations-2.13.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.13.3/a27014716e4421684416e5fa83d896ddb87002da/jackson-core-2.13.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/5.3.20/517a42165221ea944c8b794154c10b69c0128281/spring-context-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.11/4741689214e9d1e8408b206506cbe76d1c6a7d60/logback-classic-1.2.11.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.17.2/17dd0fae2747d9a28c67bc9534108823d2376b46/log4j-to-slf4j-2.17.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/1.7.36/ed46d81cef9c412a88caef405b58f93a678ff2ca/jul-to-slf4j-1.7.36.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.projectreactor.netty/reactor-netty-core/1.0.19/adb58ba62d297b56d6b7915a50f048eddcfc81a6/reactor-netty-core-1.0.19.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-http2/4.1.77.Final/9e58eeeacc74f8ad2b2acb240b1f01d2c40159d7/netty-codec-http2-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-http/4.1.77.Final/c5ac5afa9af5b4dc0e8bdbfd686979af77ebdb3c/netty-codec-http-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-resolver-dns-native-macos/4.1.77.Final/ba23bed7fd221158b5064096f9f8e286b190250c/netty-resolver-dns-native-macos-4.1.77.Final-osx-x86_64.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-resolver-dns/4.1.77.Final/aad506ab6804e2720771634e2de2a065fa678126/netty-resolver-dns-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-epoll/4.1.77.Final/8d10e9e138dac52172dd83229bdc89197100c723/netty-transport-native-epoll-4.1.77.Final-linux-x86_64.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.reactivestreams/reactive-streams/1.0.3/d9fb7a7926ffa635b3dcaa5049fb2bfa25b3e7d0/reactive-streams-1.0.3.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/net.minidev/accessors-smart/2.4.8/6e1bee5a530caba91893604d6ab41d0edcecca9a/accessors-smart-2.4.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.github.ajalt/mordant/1.2.1/6cbab1a74ab6dafbf81b7706733d4c2fbaff2e0b/mordant-1.2.1.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.github.classgraph/classgraph/4.8.162/85bc1625bc8aac51ad32971ebb26a3e35cb97356/classgraph-4.8.162.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-test/1.5.2/afa586e67fc5213fbea0d4c075af56b36a73da94/kotlinx-coroutines-test-1.5.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/5.3.20/c82f17997ab18ecafa8d08ce34a7c7aa4a04ef9e/spring-aop-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/5.3.20/20e179f0dfabf0a46428f22c2150c9c4850fd15d/spring-expression-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.11/a01230df5ca5c34540cdaa3ad5efb012f1f1f792/logback-core-1.2.11.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.17.2/f42d6afa111b4dec5d2aea0fe2197240749a4ea6/log4j-api-2.17.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-handler-proxy/4.1.77.Final/d1ac0d95b770098c46b6679fbfd417ae277012d4/netty-handler-proxy-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-handler/4.1.77.Final/47a81089de03635a27f509f3e4e13386ae1db275/netty-handler-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec/4.1.77.Final/4efc5f59335301d6ba0d7cd31dd10651119b03c8/netty-codec-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport/4.1.77.Final/2a3373bbd20d520c821f210bd5ee886788512043/netty-transport-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-buffer/4.1.77.Final/d97571f99e5e739d86824d0df99f35d295276b5f/netty-buffer-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-common/4.1.77.Final/ea0fc20f4e6178966b9d62017b7fcb83dfe0e713/netty-common-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-resolver-dns-classes-macos/4.1.77.Final/60a6b7a3d81982bcf98db89c20b04f870d2d5ea0/netty-resolver-dns-classes-macos-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-dns/4.1.77.Final/a0a9bc85703efbab626fb8642e08e221b59dc604/netty-codec-dns-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-resolver/4.1.77.Final/4a239dbf8d8bb5f98aa51462c35011c0516395fd/netty-resolver-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-classes-epoll/4.1.77.Final/dd70dbccbcf98382223a59044f3c08d8e9920cad/netty-transport-classes-epoll-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-unix-common/4.1.77.Final/c95d53486414b3270d08057957c5da8e0c37e4eb/netty-transport-native-unix-common-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.1/a99500cf6eea30535eeac6be73899d048f8d12a8/asm-9.1.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.github.ajalt/colormath/1.2.0/c62f49b31f34588dbbfb477c08fd56bc3026d202/colormath-1.2.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.kotest/kotest-assertions-api-jvm/5.7.2/38eb14f47f6add08ae2f9927adb1d3af6abb4aad/kotest-assertions-api-jvm-5.7.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-socks/4.1.77.Final/17bb510aa545fc73a18ab804c594593e32de1a1d/netty-codec-socks-4.1.77.Final.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-reactor/1.5.2/43700d2146eb36140428bae27755c0243650e75b/kotlinx-coroutines-reactor-1.5.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-data-r2dbc/2.6.8/2282f2d3a1215be80f3fc35b0937d78d6fa461f4/spring-boot-starter-data-r2dbc-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-data-redis-reactive/2.6.8/ae04d87ee2ca19c1012bbaad0aa41639e4307dfc/spring-boot-starter-data-redis-reactive-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.projectreactor.kotlin/reactor-kotlin-extensions/1.1.6/5ad285e06ba04edad1ec372a38010984425cc352/reactor-kotlin-extensions-1.1.6.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.r2dbc/r2dbc-h2/0.8.5.RELEASE/a694edf4436d91db2c3a982969642f0018c398bc/r2dbc-h2-0.8.5.RELEASE.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/20.1.0/2fcd1f3225bca0c4a7bc931142076f8c1e80993f/annotations-20.1.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-testkit/1.8.2/43c593ad99a975588d56b501fd4353065facebfc/junit-platform-testkit-1.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/9.2/81a03f76019c67362299c40e0ba13405f5467bff/asm-9.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/cglib/cglib-nodep/3.3.0/87271c95d5bc9e37e4981c9593ff14d470b6684b/cglib-nodep-3.3.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.objenesis/objenesis/3.2/7fadf57620c8b8abdf7519533e5527367cb51f09/objenesis-3.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-reactive/1.5.2/6ab6c941fa2cd480a89490b438ba27e6033a30ec/kotlinx-coroutines-reactive-1.5.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-r2dbc/1.4.4/14b7fc538c8a194a8b132a20844626ab61cbfad2/spring-data-r2dbc-1.4.4.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.r2dbc/r2dbc-pool/0.8.8.RELEASE/a979c923133ae0e7afdee36da951bb3ebea2670c/r2dbc-pool-0.8.8.RELEASE.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.r2dbc/r2dbc-spi/0.8.6.RELEASE/9940d38ab7ea27657a4477f50c496451e09b01f2/r2dbc-spi-0.8.6.RELEASE.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-data-redis/2.6.8/37e8659feb933c627d6876e3b5f83cefa79c0a08/spring-boot-starter-data-redis-2.6.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/com.h2database/h2/1.4.200/f7533fe7cb8e99c87a43d325a77b4b678ad9031a/h2-1.4.200.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-jdk8/1.5.2/9c62137ef8ed62459f7a412bfc9884e2c6ca15d6/kotlinx-coroutines-jdk8-1.5.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.github.java-diff-utils/java-diff-utils/4.12/1a712a91324d566eef39817fc5c9980eb10c21db/java-diff-utils-4.12.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-engine/5.8.2/c598b4328d2f397194d11df3b1648d68d7d990e3/junit-jupiter-engine-5.8.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.opentest4j/opentest4j/1.3.0/152ea56b3a72f655d4fd677fc0ef2596c3dd5e6e/opentest4j-1.3.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-relational/2.3.4/a10399ef878628c27b8b265e70debdae31b6a16b/spring-data-relational-2.3.4.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-commons/2.6.4/8bf118f8f249e1d9b7cd5410b7dc6482531b173b/spring-data-commons-2.6.4.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-r2dbc/5.3.20/be93e2006010af38cebb60cedb7ed9bd2358f32c/spring-r2dbc-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-tx/5.3.20/9a4ec2249dc3523ac70e0710a64288c14fc3ff78/spring-tx-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.projectreactor.addons/reactor-pool/0.2.8/6ad5eca1908b59fc5d160a8f3a9cd17367901918/reactor-pool-0.2.8.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-redis/2.6.4/596b51391389d8451708756dc4892da05fb363b6/spring-data-redis-2.6.4.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/io.lettuce/lettuce-core/6.1.8.RELEASE/a68e451255221a2e3b4dd774b521ba8ddb994255/lettuce-core-6.1.8.RELEASE.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-keyvalue/2.6.4/c17dde1da8c182f32c271c6f33c8d849f998a240/spring-data-keyvalue-2.6.4.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context-support/5.3.20/67c501ee0f6a0a93c1a1791fb9a176e1351de538/spring-context-support-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.springframework/spring-oxm/5.3.20/c20a0baf7237d4d79939ccfe7765e1186df6488a/spring-oxm-5.3.20.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-debug/1.5.2/d9676e67fd62e0f7ac0e6489b19224ce4cd327d3/kotlinx-coroutines-debug-1.5.2.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-platform/5.5.0/af38e7c4d0fc73c23ecd785443705bfdee5b90bf/jna-platform-5.5.0.jar:/Users/berapt/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna/5.5.0/e0845217c4907822403912ad6828d8e0b256208/jna-5.5.0.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 TodoSpec
Internal Error occurred.
org.junit.platform.commons.JUnitException: TestEngine with ID 'spock' failed to discover tests
	at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverEngineRoot(EngineDiscoveryOrchestrator.java:160)
	at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverSafely(EngineDiscoveryOrchestrator.java:134)
	at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discover(EngineDiscoveryOrchestrator.java:80)
	at org.junit.platform.launcher.core.DefaultLauncher.discover(DefaultLauncher.java:110)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: org.junit.platform.commons.JUnitException: ClassSelector [className = 'TodoSpec'] resolution failed
	at org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener.selectorProcessed(AbortOnFailureLauncherDiscoveryListener.java:39)
	at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolveCompletely(EngineDiscoveryRequestResolution.java:102)
	at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.run(EngineDiscoveryRequestResolution.java:82)
	at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.resolve(EngineDiscoveryRequestResolver.java:113)
	at org.spockframework.runtime.SpockEngine.discover(SpockEngine.java:28)
	at org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.discoverEngineRoot(EngineDiscoveryOrchestrator.java:152)
	... 13 more
Caused by: org.junit.platform.commons.PreconditionViolationException: Could not load class with name: TodoSpec
	at org.junit.platform.engine.discovery.ClassSelector.lambda$getJavaClass$0(ClassSelector.java:75)
	at org.junit.platform.commons.function.Try$Failure.getOrThrow(Try.java:335)
	at org.junit.platform.engine.discovery.ClassSelector.getJavaClass(ClassSelector.java:74)
	at org.spockframework.runtime.ClassSelectorResolver.resolve(ClassSelectorResolver.java:26)
	at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.lambda$resolve$2(EngineDiscoveryRequestResolution.java:134)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1602)
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
	at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolve(EngineDiscoveryRequestResolution.java:185)
	at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolve(EngineDiscoveryRequestResolution.java:125)
	at org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.resolveCompletely(EngineDiscoveryRequestResolution.java:91)
	... 17 more
Caused by: java.lang.ClassNotFoundException: TodoSpec
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
	at org.junit.platform.commons.util.ReflectionUtils.lambda$tryToLoadClass$9(ReflectionUtils.java:829)
	at org.junit.platform.commons.function.Try.lambda$call$0(Try.java:57)
	at org.junit.platform.commons.function.Try.of(Try.java:93)
	at org.junit.platform.commons.function.Try.call(Try.java:57)
	at org.junit.platform.commons.util.ReflectionUtils.tryToLoadClass(ReflectionUtils.java:792)
	at org.junit.platform.commons.util.ReflectionUtils.tryToLoadClass(ReflectionUtils.java:748)
	... 32 more

Process finished with exit code 254&lt;/code&gt;&lt;/pre&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;원인과 문제해결&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;groovy plugin이 root module에 있고 정작 사용하는 submodule에는 없었습니다. spock 프레임워크를 사용하는 모듈에 `build.gradle`에 groovy 플러그인을 정의해 주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705219646148&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    kotlin(&quot;plugin.spring&quot;) version &quot;1.9.20&quot;
    id(&quot;groovy&quot;) // for spock
}&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MA, MSA에 대한 이해
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/design-microservices-architecture-with-patterns/monolithic-architecture-is-still-worth-at-2021-98bfc112dc24&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/design-microservices-architecture-with-patterns/monolithic-architecture-is-still-worth-at-2021-98bfc112dc24&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/design-microservices-architecture-with-patterns/microservices-communications-f319f8d76b71&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/design-microservices-architecture-with-patterns/microservices-communications-f319f8d76b71&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.wikipedia.org/wiki/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&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>  bugs</category>
      <category>gradle</category>
      <category>multimodule</category>
      <category>spring</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/98</guid>
      <comments>https://new-pow.tistory.com/98#entry98comment</comments>
      <pubDate>Sat, 13 Jan 2024 04:05:14 +0900</pubDate>
    </item>
    <item>
      <title>2023년을 보내며...</title>
      <link>https://new-pow.tistory.com/97</link>
      <description>&lt;p data-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;2023년이 끝나고 2024년이 시작한지도 벌써 며칠이 지났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 2023년을 정리하며 간단하게 글을 남겨보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;2023년 동안 한 것&lt;/b&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;중간중간 회고를 적었기 때문에 이번 글에서는 전체적인 소회와 못다적은 11월, 12월 이슈들을 적어보려고 합니다.&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; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;1~2월 : &lt;u&gt;&lt;b&gt;코드스쿼드&lt;/b&gt;&lt;/u&gt; CS16, 재택 공부를 병행하며 CS 스터디&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;3~4월 : 코드스쿼드 Java, Spring, WAS 미션. 객체지향스터디 Object 읽기. 운영체제  학습&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;5월 : 코드스쿼드 첫번째 프로젝트 &lt;b&gt;Issue Tracker&lt;/b&gt; 시작. DB, 네트워크 스터디   &lt;a href=&quot;https://new-pow.tistory.com/45&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1월~5월 회고 link&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;6월 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;secondhand&lt;/b&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;프로젝트 시작,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;코드스쿼드&lt;/b&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수료&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;7월 : DB 스터디 사작, JPA 스터디 시작,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;데이터 크롤링 파트타임&lt;/b&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;시작&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;8월 : 데이터 크롤링 파트타임 종료, 원티드 프리온보딩 백엔드 인턴십(6주) 진행&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;9월 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;u&gt;데이터 라벨링을 위한 API 개발&lt;/u&gt;&lt;/b&gt;, DB 스터디 종료, JPA 스터디 종료&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;10월 : 본격적인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;취준&lt;/b&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기간 시작   &lt;a href=&quot;https://new-pow.tistory.com/81&quot;&gt;6월~10월 회고 link&lt;/a&gt; / &lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;취준컴퍼니 시작. 이력서 피드백 기간. 면접 스터디 시작&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;11월 : &lt;u&gt;&lt;b&gt;취업준비 모임&lt;/b&gt;&lt;/u&gt; 시작,&lt;a href=&quot;https://flytrap-dev.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt; Rss reader 프로젝트&lt;/b&gt;&lt;/a&gt; 시작&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;12월 : 입사 결정. 취업준비 모임 외 스터디 종료 혹은 방학. 코틀린을 배우기 시작.&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;/p&gt;
&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전반적인 소회&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지난 2023년은 되돌아보면 제 평생에 가장 격렬했던 해 중 하나였을 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;올해는 불안으로 시작했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 불안했던 것과 달리 예상 외로 정말 열심히 공부했고, 함께 할 사람을 찾았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;좋은 분께 도움을 많이 받았고, 취업 목표 기간보다 빠르게 취업을 결정해서 일을 시작할 수 있었습니다.&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: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;무엇보다 새로운 사람들을 많이 만났습니다.&lt;/b&gt;&lt;/h3&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;늘 &quot;사람으로부터 배운다&quot; 라는 모토를 가지고 함께 하는 사람의 장점과 함께하는 경험에서 인사이트를 많이 얻었었는데요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;올해는 이 인사이트가 너무 쏟아져서 소화하기 힘들정도로 좋은 사람들을 많이 만났던 것 같아요. 그것도 같은 동료 학습자 뿐만이 아니라 스승, 멘토, 선배 등 다양하게 만나게 되었고 좋은 사람들과 함께해서 더 행운이었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 올해는 학습의 방식, 방향이나 태도에서 많은 배움을 얻을 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&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: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해는 끊임없이 나의 상태를 돌아보았던 한해였습니다.&lt;/b&gt;&lt;/h3&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;취업준비를 하고, 학습을 하는동안 최대한 열심히 일상을 몰아치게 살고 있으니 스트레스 관리를 잘 해주는 것이 중요해서 끊임없이 나를 돌아볼 수밖에 없었습니다.&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;i&gt;&quot;이것을 하는게 좋은가?&quot;, &quot;지금 내게 무엇이 가장 중요한가?&quot;, &quot;지금 자존감이 너무 떨어져있지는 않은가?&quot;, &quot; 불안해서 할 것들을 미루고 있지 않은가?&quot;, &quot;내가 이 일을 할만한 상태인가?&quot;&lt;/i&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;/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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxj5S0/btsC345YRKM/C7PzRoPB6OniwLnc5m8Wx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxj5S0/btsC345YRKM/C7PzRoPB6OniwLnc5m8Wx1/img.png&quot; data-alt=&quot;매년 하는 '올해의 OO'의 일부. 생각을 비우고 그냥 적당히 꾸준히 하기를 매우매우 강조했다 (...)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxj5S0/btsC345YRKM/C7PzRoPB6OniwLnc5m8Wx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxj5S0%2FbtsC345YRKM%2FC7PzRoPB6OniwLnc5m8Wx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;408&quot; height=&quot;198&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;매년 하는 '올해의 OO'의 일부. 생각을 비우고 그냥 적당히 꾸준히 하기를 매우매우 강조했다 (...)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;루틴이 가장 중요했던 한해였습니다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;올해 저에게도 루틴이 가장 중요한 한해였습니다. 언제 끝날지 모르는 힘든 기간 중에 나를 지탱해주는 것은 갑자기 찾아오는 기쁨이나 성과보다는 내가 지쳐도 하루를 버티는 습관이 중요하다고 여겼거든요.&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;또 루틴이 제게 중요한 이슈였던 이유는 두려움을 이겨내는 방식 중에 하나가 된다는 것입니다.&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;b&gt;그럼에도 많이 흔들렸던 한해였습니다.&lt;/b&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;s&gt; &lt;i&gt;(심지어 면접에서 '자신감을 가지라'라는 피드백을 듣기도.... )&lt;/i&gt;&lt;/s&gt;&lt;/p&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb1Epl/btsC9nQm9F2/LReF5qvB7x8UfSanbEtQt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb1Epl/btsC9nQm9F2/LReF5qvB7x8UfSanbEtQt1/img.png&quot; data-alt=&quot;저에게는 비밀 노션페이지가 하나 있습니다.. 종종 주변에서 해주는 애정어린 말이 여기 들어가 있어요.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb1Epl/btsC9nQm9F2/LReF5qvB7x8UfSanbEtQt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb1Epl%2FbtsC9nQm9F2%2FLReF5qvB7x8UfSanbEtQt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;343&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;800&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;큰 이슈들..!&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;취업 준비 모임&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 가장 잘 한 일이라면 이 모임에 신청했던 일이지 않았을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 취업 준비하면서 가장 늘어지고 힘든 시기에 시작하게 된 모임이었는데요. 운이 좋게 합류하게 된 이후로 벌써 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;모여서 하는 것은 한 주동안 어땠는지 얘기를 나누는 것인데, 다른분들의 고민이나 한주동안 얻은 레슨을 듣고있다보면 비슷하게 고민을 하고 있거나 한걸음 더 나아가서 고민하고 계신다는 생각이 많이 들었어요. 제가 앞으로 고민하게 될 것들을 먼저 하고계신 것처럼요. 그래서 나만 하는 고민이 아니구나 혹은 이건 고민할 일도 아니구나 하는 생각에 안심이되기도 하고. 이런 고민까지 할 수 있구나 하며 시야가 더 넓어지기도 했습니다. 사실상 불안한 제 멘탈을 가장 많이 케어해주던 커뮤니티 중 하나였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 모임이 그냥 공감과 응원에서만 끝나는 것이 아니라 모임이 거듭될 수록 제가 같이 단단해져가는게 느껴졌어요. 매일 SoD, EoD를 작성하고 매주 회고를 하는 틀은 물론이고. 모임에서 고민 해결을 위해 제안받은 방법이나 옆에서 보면서 좋아보이는 방법들도 따라해보면서 일상에 점점 기반이 생겨서 단단해져가고 있더라고요.&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/c26LJ5/btsC6V737Yx/dXL0wsoJqQhB9bB6rNrTy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c26LJ5/btsC6V737Yx/dXL0wsoJqQhB9bB6rNrTy1/img.png&quot; data-origin-width=&quot;1392&quot; data-origin-height=&quot;914&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; width=&quot;500&quot; height=&quot;328&quot; style=&quot;width: 52.2982%; margin-right: 10px;&quot; data-widthpercent=&quot;52.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c26LJ5/btsC6V737Yx/dXL0wsoJqQhB9bB6rNrTy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc26LJ5%2FbtsC6V737Yx%2FdXL0wsoJqQhB9bB6rNrTy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1392&quot; height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0qkbz/btsC511PRk5/bkMkIfxEtY48UsMIeTXaLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0qkbz/btsC511PRk5/bkMkIfxEtY48UsMIeTXaLK/img.png&quot; data-origin-width=&quot;2060&quot; data-origin-height=&quot;1520&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; width=&quot;600&quot; height=&quot;443&quot; data-widthpercent=&quot;47.09&quot; style=&quot;width: 46.539%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0qkbz/btsC511PRk5/bkMkIfxEtY48UsMIeTXaLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0qkbz%2FbtsC511PRk5%2FbkMkIfxEtY48UsMIeTXaLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2060&quot; height=&quot;1520&quot;/&gt;&lt;/span&gt;&lt;/div&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;그리고 함께 하시는 분들의 캐릭터가 모두 달라 배울 점도 배가 되는 것 같아요. 이 모임에 함께하게 된 것이 2023년 최대 업적 중 하나가 아니었을까요...  &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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b data-stringify-type=&quot;bold&quot;&gt;RSS Reader 프로젝트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;11월 중 취준기간동안 개발 공부가 제일 재미없어졌을 때 다시 한 번 불을 지펴준 토이 프로젝트입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;이전에 가볍게 언급했다시피 취준기간동안 개발에 대한 흥미가 조금씩 떨어져갈 때 쯤 SOS를 외치자 흔쾌히 합류해준 분들 덕분에 시작할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;시작은 가볍게 Todo list처럼 구현해보자였는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;다만 컨셉이 &quot;우리가 직접 쓸 것을 만들자&quot; 였기 때문에 좀 장기 프로젝트가 될 것 같았고 이 때문에 따로 블로그를 파고 organization을 구성하는 노력을 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1d1c1d; 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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;이에 대한 자세한 이야기는 다른 블로그의 포스팅으로 갈음합니다. &lt;a href=&quot;https://flytrap-dev.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;자세히&lt;/a&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;b&gt;스터디 주도 개발&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번해는 단연 스터디 주도 개발에 내 몸을 맡겼다고 할 정도로 부족함을 채우는 스터디를 굉장히 많이 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB, JPA, 네트워크를 중심으로 책을 한 권씩 떼기도 했었고 잠깐 면접스터디도 했습니다.&lt;/p&gt;
&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;블로그&lt;/b&gt;&lt;/h3&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;지금 확인해보니 2023년 작성된 글로만 83개 포스트가 공개되어 있습니다. 매주 1개~2개의 포스트를 발행한 꼴입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/73KHl/btsC7oWydjB/MeCFu7Z2hKhkknZ0gH32SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/73KHl/btsC7oWydjB/MeCFu7Z2hKhkknZ0gH32SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/73KHl/btsC7oWydjB/MeCFu7Z2hKhkknZ0gH32SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F73KHl%2FbtsC7oWydjB%2FMeCFu7Z2hKhkknZ0gH32SK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;573&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&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;블로그 초반부터 주간 회고를 중심으로 포스트를 작성했는데요. 이 블로그의 목적이 왕초보 개발자 지망생부터의 '나'를 기록하고 겪었던 경험들을 기록하는 것에 있었기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기술적으로 뛰어나거나 훌륭한 인사이트가 있는 블로그는 아닙니다. 하지만 읽는 사람들이 부담없이 함께  생각하는 듯한 경험이 들었으면 싶기도 하고.. 아니면 콘솔에 찍힌 에러메시지를 구글링하다가 우연찮게 발견한 글을 보고 문제 해결에 도움이 되었으면 싶은 마음입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 성장할 시기에는 정작 바빠서 글을 못쓰고 한템포 쉬고나서 개선 경험을 우르르 올린 것을 보면 약간 민망하기는 하지만 ... 그래도 나름 초기의 목적을 잘 지키고 있다고 생각합니다. 취준 모임에 합류하고나서 쓰기 시작한 주간회고도 꽤 재밌게 쓰고 있습니다. 하지만 역시 분기 주기로 내맘대로 작성한 회고가 제일 재밌다는 생각이 들어 앞으로 회고 방식은 천천히 고민해보려고요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2024년에도 꾸준히 회고를 작성하고, 그때의 내가 경험한 문제들을 정리해두는 방향으로 운영할 예정입니다.&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;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2024년을 맞이하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매주 회고모임에서는 &lt;a href=&quot;https://techblog.woowahan.com/2677/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KPT&lt;/a&gt;를 통해 지난 한 주를 진단하는데요. 1년을 떠올리며 간단하게 정리함으로써 이 글을 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 새해 복 많이 받으세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Keep&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;학습하는 습관 지속하기&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;하지만 결국은 기능을 하는 것이 먼저! 야생의 습관을 놓지 않기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;li&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;b&gt;Problem&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;나를 잘 못믿는 것&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;/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;늘 하다가 멈추는 것같아서 그것들이 나를 괴롭힌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1년 반동안 개발 외 이슈와 사람들에게 너무 무지했던 것&lt;/b&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;/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;&lt;b&gt;Try&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;✨ 새로운 직장에서 주니어 엔지니어로서 1인분을 할 수 있도록 적응하고 기술적 신뢰 쌓기 &lt;b&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;/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;/ul&gt;
&lt;/li&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;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;</description>
      <category>  Smalltalk</category>
      <category>2023</category>
      <category>log</category>
      <category>회고</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/97</guid>
      <comments>https://new-pow.tistory.com/97#entry97comment</comments>
      <pubDate>Sat, 6 Jan 2024 06:12:32 +0900</pubDate>
    </item>
    <item>
      <title>`Postman` 으로 WebSocket STOMP 테스트 해보기</title>
      <link>https://new-pow.tistory.com/96</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 &lt;a href=&quot;https://github.com/masters2023-2nd-project-05/second-hand-BE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Secondhand&lt;/a&gt; 프로젝트를 정리하며 그동안의 학습을 돌아보고 디버깅과 성능개선을 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 거의 반년동안 계속 유지보수하고 있는 프로젝트가 되어서 이정도면 반려 프로젝트(...)라고 할 수 있을 정도로 애증이 담긴 프로젝트가 되어버렸는데요. &lt;a href=&quot;http://fivehands-but-iiiiii.site/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;web site&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;웹소켓 통신을 구현했지만, STOMP 프로토콜을 테스트 하기 쉽지 않아서 난항을 겪었던 때가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소에는 `Postman`이나 간단한 API라면`curl`로 테스트를 하는데 Postman에서 `STOMP`를 지원하지 않았거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 인터넷을 찾다가 `APIC`이라는 api테스트 툴을 애용하고 있었습니다. 근데 언제부턴가 이 사이트에 접속할 수 없게 되었어요  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.apic.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.apic.app/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1702202706049&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;About APIC - apic docs&quot; data-og-description=&quot;APIC provides a complete end to end solution for APIs; staring from design to documentation to testing. With a simplistic UI for Designing APIs and a feature rich platform for testing them, APIC provides a common platform for your architects, developers an&quot; data-og-host=&quot;docs.apic.app&quot; data-og-source-url=&quot;https://docs.apic.app/&quot; data-og-url=&quot;https://docs.apic.app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.apic.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.apic.app/&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;About APIC - apic docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;APIC provides a complete end to end solution for APIs; staring from design to documentation to testing. With a simplistic UI for Designing APIs and a feature rich platform for testing them, APIC provides a common platform for your architects, developers an&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.apic.app&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;그래서 팀원과 논의 끝에 계속 꾸준히 쓰고 있던 `Postman`으로 테스트를 작성해두기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`STOMP`도 웹소켓 통신 위에 있는 프로토콜이니 직접 테스트를 작성하면 되지 않을까? 해서요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Postman으로 Websocket 테스트를 하는 법&lt;/b&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/kAzC9/btsB7eNrXbW/mZBlGg4rtI0irTRPi3SEw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAzC9/btsB7eNrXbW/mZBlGg4rtI0irTRPi3SEw1/img.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;644&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;32.44&quot; data-filename=&quot;blob&quot; style=&quot;width: 31.6883%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAzC9/btsB7eNrXbW/mZBlGg4rtI0irTRPi3SEw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAzC9%2FbtsB7eNrXbW%2FmZBlGg4rtI0irTRPi3SEw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;644&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EqD3m/btsB3bRX4Rk/inxWwKkFnkKVrYjUN5Sjc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EqD3m/btsB3bRX4Rk/inxWwKkFnkKVrYjUN5Sjc0/img.png&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;680&quot; data-is-animation=&quot;false&quot; style=&quot;width: 34.2979%; margin-right: 10px;&quot; data-widthpercent=&quot;35.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EqD3m/btsB3bRX4Rk/inxWwKkFnkKVrYjUN5Sjc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEqD3m%2FbtsB3bRX4Rk%2FinxWwKkFnkKVrYjUN5Sjc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;912&quot; height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctdFlb/btsB0f8UYcG/UiA4y22uUAtIeDe7wfq3OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctdFlb/btsB0f8UYcG/UiA4y22uUAtIeDe7wfq3OK/img.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;644&quot; data-is-animation=&quot;false&quot; width=&quot;450&quot; height=&quot;363&quot; style=&quot;width: 31.6883%;&quot; data-widthpercent=&quot;32.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctdFlb/btsB0f8UYcG/UiA4y22uUAtIeDe7wfq3OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctdFlb%2FbtsB0f8UYcG%2FUiA4y22uUAtIeDe7wfq3OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;644&quot;/&gt;&lt;/span&gt;&lt;/div&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;포스트맨에서 요청을 생성할 때, 기본으로 생성하면 `HTTP` Rest API 생성을 하는데요. 저 위에 New를 누르고 `WebSocket` 요청을 생성해주고 이를 저장할때 새로운 콜렉션을 만들어주면 다른 HTTP 프로토콜 외 프로토콜 요청을 위한 콜렉션을 따로 만들 수 있었습니다.&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;2162&quot; data-origin-height=&quot;1416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kF6Sj/btsB1Myxj1x/c38phK0VZqRFODOyY5tGi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kF6Sj/btsB1Myxj1x/c38phK0VZqRFODOyY5tGi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kF6Sj/btsB1Myxj1x/c38phK0VZqRFODOyY5tGi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkF6Sj%2FbtsB1Myxj1x%2Fc38phK0VZqRFODOyY5tGi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;295&quot; data-origin-width=&quot;2162&quot; data-origin-height=&quot;1416&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;지정한 엔드포인트로 connect를 요청하니 잘 연결이 됩니다  &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STOMP의 이해와 메시지 보내기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;STOMP를 사용했던 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 STOMP를 도입했던 이유는 1Spring에서 적용하기 쉽기 때문이었습니다. 더불어 간결하고 쉬운 프로토콜이라 메시지 기반으로 통신하기 용이할 것 같다는 판단이었습니다.&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;메시지를 보면 헤더와 바디를 전달할 때 HTTP 와 비슷한 형태라 직접 읽기도 쉽습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1702793662507&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SEND // command
destination:/queue/a // headers
receipt:message-12345

hello queue a^@ //body,null octect&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;또한 스케일 아웃과 서버 안정성을 위해 다른 웹소켓 통신 브로커(RabbitMQ 등)와 함께 작동하도록 구성하기도 용이합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EiXcW/btsB3udsNaZ/E4bsTXsDKQDg2oBDyLbGF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EiXcW/btsB3udsNaZ/E4bsTXsDKQDg2oBDyLbGF0/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;nbsp;https://docs.spring.io/spring-framework/reference/web/websocket/stomp/message-flow.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EiXcW/btsB3udsNaZ/E4bsTXsDKQDg2oBDyLbGF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEiXcW%2FbtsB3udsNaZ%2FE4bsTXsDKQDg2oBDyLbGF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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&quot; height=&quot;415&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;nbsp;https://docs.spring.io/spring-framework/reference/web/websocket/stomp/message-flow.html&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;당시 저희는 Redis cache를 이용했기 때문에, Redis의 pub/sub을 활용해서 외부 브로커로 사용하기로 했습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;별도의 인프라 구축을 하지 않아도 되며 Pub/sub을 지원하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, Redis는 STOMP를 지원하지 않기 때문에 위 다이어그램에서처럼 STOMP TCP로 메시지를 주고받지는 않았습니다.&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;b&gt;사용 예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서는 간편하게`spring-boot-starter-websocket` 의존성을 추가한 후, `config`와 `controller`를 정의하여 사용을 시작할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702793894392&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@RequiredArgsConstructor
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final StompMessageProcessor stompMessageProcessor;
    private final StompErrorHandler stompErrorHandler;

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint(&quot;/chat&quot;, &quot;/notification&quot;)
                .setAllowedOrigins(&quot;*&quot;);
        registry.setErrorHandler(stompErrorHandler);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker(&quot;/sub&quot;); // 구독 요청
        registry.setApplicationDestinationPrefixes(&quot;/pub&quot;); // 발행
//        registry.setPathMatcher(new AntPathMatcher(&quot;.&quot;));
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(stompMessageProcessor);
    }
}&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_1702793973605&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
class ChatController {

    private final RedisMessagePublisher redisMessagePublisher;
    private final ChannelTopic chatTopic;

    @MessageMapping(&quot;/message&quot;)
    public void message(ChatbubbleRequest message) {
        redisMessagePublisher.publish(chatTopic.getTopic(), message.toDomain());
    }

}&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;특히 이번 프로젝트를 하면서 유용하게 썼던 것은 STOMP의 커맨드를 기준으로 시행되어야하는 비즈니스 로직을 나눈 것이었습니다. STOMP를 사용함으로써 로직 분기점을 위해 별도 규칙을 만들지 않아도 되어서 편리했습니다. 단순히 프로토콜 형식에 따라 로직을 구현함으로써 구조상으로 단순해질 수 있기 때문입니다.&lt;/p&gt;
&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://new-pow.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.10.16 - [  Spring] - 안 읽은 채팅 구현기 : STOMP의 ChannelInterceptor 를 사용하여&lt;/a&gt;]&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;SessionConnectEvent&lt;/b&gt;: Published when a new STOMP CONNECT is received to indicate the start of a new client session. The event contains the message that represents the connect, including the session ID, user information (if any), and any custom headers the client sent. This is useful for tracking client sessions. Components subscribed to this event can wrap the contained message with&amp;nbsp;SimpMessageHeaderAccessor&amp;nbsp;or&amp;nbsp;StompMessageHeaderAccessor.&lt;br /&gt;&lt;br /&gt;- 출처 : &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp/application-context-events.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring docs&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;&lt;b&gt;Postman으로 테스트하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postman으로 테스트를 하기 위해서는 웹소켓 통신을 `connect`하고 STOMP 의 형식에 맞게 메시지만 보내주면 되었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 테스트는 같은 팀원이 전담해서 짜주었고, 생기는 트러블 슈팅을 같이 열심히 했습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postman에서 message를 저장할 수가 있어서 한번 써두면 반복해서 테스트하기 편했어요.&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;2152&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MGwSq/btsCyyy8G7W/ofGONJ8z09THxWkoiKBLF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MGwSq/btsCyyy8G7W/ofGONJ8z09THxWkoiKBLF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MGwSq/btsCyyy8G7W/ofGONJ8z09THxWkoiKBLF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMGwSq%2FbtsCyyy8G7W%2FofGONJ8z09THxWkoiKBLF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;200&quot; data-origin-width=&quot;2152&quot; data-origin-height=&quot;862&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;pre id=&quot;code_1703347353773&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SEND // command
destination:/queue/a // headers
receipt:message-12345

hello queue a^@ //body,null octect&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;프로토콜에 맞추어 커맨드, 헤드, 한줄 공백 후 바디(optional), 그리고 메시지가 끝났다는 것을 표시하는 Null octect까지 보내주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주의사항 &amp;amp; Trouble shooting&lt;/b&gt;&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;`null octetct`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null octect을 아스키코드나 `^@`로 넣으면 잘 먹히지 않습니다. (아마 텍스트 그 자체로 인식하는 듯) 그래서 실제로 웹서비스에서 주고받는 메시지에서 null octect을 복사해와서 사용했습니다   뭔가 잘못해서 그런가...&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;body가 있는 경우, `content-length`, `content-type`을 정확하게 넣어주어야합니다.&lt;/li&gt;
&lt;/ul&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;공식 문서에 각 커맨드마다 필수 헤더들이 있으니 빠지지 않게 작성해주고,&amp;nbsp; 경우에 따라 optional 헤더도 활용해줍니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`CONNECT`&lt;span&gt;&amp;nbsp;&lt;/span&gt;or&lt;span&gt; `&lt;/span&gt;STOMP`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRED:&lt;span&gt;&amp;nbsp;&lt;/span&gt;accept-version,&lt;span&gt;&amp;nbsp;&lt;/span&gt;host&lt;/li&gt;
&lt;li&gt;OPTIONAL:&lt;span&gt;&amp;nbsp;&lt;/span&gt;login,&lt;span&gt;&amp;nbsp;&lt;/span&gt;passcode,&lt;span&gt;&amp;nbsp;&lt;/span&gt;heart-beat&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`CONNECTED` response
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRED:&lt;span&gt;&amp;nbsp;&lt;/span&gt;version&lt;/li&gt;
&lt;li&gt;OPTIONAL:&lt;span&gt;&amp;nbsp;&lt;/span&gt;session,&lt;span&gt;&amp;nbsp;&lt;/span&gt;server,&lt;span&gt;&amp;nbsp;&lt;/span&gt;heart-beat&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`SEND`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRED:&lt;span&gt;&amp;nbsp;&lt;/span&gt;destination&lt;/li&gt;
&lt;li&gt;OPTIONAL:&lt;span&gt;&amp;nbsp;&lt;/span&gt;transaction&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`SUBSCRIBE`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRED:&lt;span&gt;&amp;nbsp;&lt;/span&gt;destination,&lt;span&gt;&amp;nbsp;&lt;/span&gt;id&lt;/li&gt;
&lt;li&gt;OPTIONAL:&lt;span&gt;&amp;nbsp;&lt;/span&gt;ack&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`UNSUBSCRIBE`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRED:&lt;span&gt;&amp;nbsp;&lt;/span&gt;id&lt;/li&gt;
&lt;li&gt;OPTIONAL: none&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`DISCONNECT`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRED: none&lt;/li&gt;
&lt;li&gt;OPTIONAL:&lt;span&gt;&amp;nbsp;&lt;/span&gt;receipt&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`ERROR`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REQUIRED: none&lt;/li&gt;
&lt;li&gt;OPTIONAL:&lt;span&gt;&amp;nbsp;&lt;/span&gt;message&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;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;pre id=&quot;code_1703392865411&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
		registration.setMessageSizeLimit(128 * 1024);
	}

	// ...

}&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`heart-beat` 헤더를 통해 네트워크 연결이 살아있는지, 확인하는 heartbeat 주기(interval)를 설정할 수 있습니다. 서버가 죽었거나, 연결 종료를 빠르게 감지하기 위해 사용합니다. `CONNECT` 시에 헤더로 같이 설정해줍니다. 그렇지 않으면 기본적으로 0,0 으로 설정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8ff; color: #666666; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;CONNECT // 클라이언트쪽 요청
heart-beat:&amp;lt;cx&amp;gt;,&amp;lt;cy&amp;gt;

CONNECTED // 서버쪽 응답
heart-beat:&amp;lt;sx&amp;gt;,&amp;lt;sy&amp;gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리하며&lt;/b&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;(`CONNECT` -&amp;gt; `SUBSCRIBE` -&amp;gt; `SEND` -&amp;gt; `UNSUBSCRIBE` -&amp;gt; `DISCONNECT`)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 STOMP 테스트를 짜보려고하니 해당 프로토콜에 대해 더 깊게 이해하게 되어 오히려 APIC 등 다른 STOMP 테스트 도구에 의존하는 것보다 배울 수 있는 것들이 많았어서 유익했습니다.&lt;/p&gt;
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹소켓 프로토콜로 통신시 bean scope는 어떻게 될까? 그리고 이를 어떻게 활용할 수 있을까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp/scope.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;참고할 링크 : https://docs.spring.io/spring-framework/reference/web/websocket/stomp/scope.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;STOMP 외에 다른 메시징 프로토콜은 뭐가 있을까? Spring 프레임워크에서는 STOMP를 사용하는 것이 가장 편한 것일까?&lt;/li&gt;
&lt;li&gt;클라이언트 메시지 사이즈, heart-beat 외에 서버사이드에서 설정해주어 최적화할 것들은 뭐가 있을까?&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고링크&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1702792702166&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;STOMP :: Spring Framework&quot; data-og-description=&quot;The WebSocket protocol defines two types of messages (text and binary), but their content is undefined. The protocol defines a mechanism for client and server to negotiate a sub-protocol (that is, a higher-level messaging protocol) to use on top of WebSock&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/reference/web/websocket/stomp.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;STOMP :: Spring Framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The WebSocket protocol defines two types of messages (text and binary), but their content is undefined. The protocol defines a mechanism for client and server to negotiate a sub-protocol (that is, a higher-level messaging protocol) to use on top of WebSock&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;</description>
      <category>  Tools</category>
      <category>spring</category>
      <category>Stomp</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/96</guid>
      <comments>https://new-pow.tistory.com/96#entry96comment</comments>
      <pubDate>Sat, 30 Dec 2023 03:46:47 +0900</pubDate>
    </item>
    <item>
      <title>2023-W50 회고 :  코어타임 이후에도 프로젝트를  시야에 두는 방법?</title>
      <link>https://new-pow.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 주에 한 것&lt;/b&gt;&lt;/h2&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;1420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cghEI5/btsCg09qfeL/4qVWzhq9TJlDcSCrhf0cuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cghEI5/btsCg09qfeL/4qVWzhq9TJlDcSCrhf0cuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cghEI5/btsCg09qfeL/4qVWzhq9TJlDcSCrhf0cuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcghEI5%2FbtsCg09qfeL%2F4qVWzhq9TJlDcSCrhf0cuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;500&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;What is New?!&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;한 주동안 활동했던 스터디 혹은 취준 모임을 1개만 빼고 모두 정리했습니다.&lt;/li&gt;
&lt;li&gt;그동안 코어타임을 가졌던 `RSS-Project`의 코어타임을 끝나고 이제 운영 및 유지보수의 단계를 생각해보게 되었습니다.&lt;/li&gt;
&lt;li&gt;프로젝트에서 이미지가 90도 돌아가는 에러를 잡길 시도했으나 실패... 살짝 지쳐서 쉬었다가 다음주에 다시 시도하기로  &lt;/li&gt;
&lt;li&gt;2023년도의 올해의 OOO을 작성하기 시작함&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;What I Leanred&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코어 타임 이후의 프로젝트는 어떻게 운영하지?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;지난 한 주는 정리의 연속이었습니다. 아무래도 직장이라는 집중해야할 일이 생기니 하루 중 가장 큰 시간을 할애할 필요가 생겼습니다. 가장 크게는 스터디들. 하고 있던 프로젝트를 정리하는 것이었는데요.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;문제는 이 프로젝트를 어떻게 잘 '정리'하느냐 였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;최근 코어타임을 하고 있었던 프로젝트는 &lt;a href=&quot;https://github.com/Flytrap-Ware/RSS-Reader&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RSS-Reader&lt;/a&gt;&amp;nbsp; 프로젝트였어요. 여기저기 플랫폼에 산재된 동료들의 블로그 새 글을 피드 형태로 보고, slack이나 discord에서 알람을 받을 수 있는 서비스를 기획하고 개발했는데요. 이 프로젝트의 사실 거대한(...) 목적은 현재 함께 공부하고 있는 스터디의 관리 플랫폼을 만드는 데에 가장 작은 유닛이었어요. 그러니까 계속해서 개발하고 발전시키는 데 목적이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것에 대한 코어타임을 끝내고 유지보수 개선 단계로 넘어가려니 사실 어떻게 해야할 지 잘 몰랐어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 어플리케이션에서 오류가 생기거나 꺼지지 않게 잘 모니터링하고 있다가 문제가 생기면 대처하고 하는 식이어야 하는데, 늘 문제가 터진 다음에야 알게되었고 오류를 발견했을 때 공유체계도 잘 잡혀있지 않아서 발견한 사람이 고치는 일도 더러 있었거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이렇게 모니터링과 로깅에 관심을 가지게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 학습 자료나 어떻게 해야 좋다는 식의 정보가 하나도 없었고, 현업에서는 DataDog 등 모니터링 툴을 구매해서 사용하는 것이 일반적이라고해서 막막했었는데요. 앞서 학습한 동료분들께 여쭤보아 한땀한땀 개선하는 수밖에 없었습니다.&lt;/p&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 CloudWatch`에 RDS 로깅을 모으고 그 중 slow query가 발생했을 때, 별도 로그 그룹으로 모으는 것. 그리고 그것을 트리거로 Lambda를 실행시켜 슬랙 알람을 받는 것이었습니다. (이것을 discord 알람으로 받을 수 있도록 수정이 필요해졌습니다.)&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;1280&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lo4Nr/btsCAMiSoar/QUd27xk09MlxVkbxawOtnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lo4Nr/btsCAMiSoar/QUd27xk09MlxVkbxawOtnk/img.png&quot; data-alt=&quot;대충 이런 흐름...!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lo4Nr/btsCAMiSoar/QUd27xk09MlxVkbxawOtnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flo4Nr%2FbtsCAMiSoar%2FQUd27xk09MlxVkbxawOtnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;894&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;894&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;이것을 시작으로 점차 '지속가능한 프로젝트 운영' 을 위해서 조금씩 공부하고 보완해보려고 합니다.&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;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다음 주에 할 것&lt;/b&gt;&lt;/h2&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;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t7Rmz/btsCAhXj5oR/vUjB5Tq0RSj2K7hkfvhcb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t7Rmz/btsCAhXj5oR/vUjB5Tq0RSj2K7hkfvhcb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t7Rmz/btsCAhXj5oR/vUjB5Tq0RSj2K7hkfvhcb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft7Rmz%2FbtsCAhXj5oR%2FvUjB5Tq0RSj2K7hkfvhcb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;331&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;940&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;</description>
      <category>  Smalltalk</category>
      <category>ㅣㄱ</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/95</guid>
      <comments>https://new-pow.tistory.com/95#entry95comment</comments>
      <pubDate>Sat, 23 Dec 2023 10:28:57 +0900</pubDate>
    </item>
    <item>
      <title>MySQL에서 `Process`의 의미 : 단일 프로세스의 프로세스를 종료할 수 있다고요?</title>
      <link>https://new-pow.tistory.com/94</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;궁금했던 것&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RealMySQL에서 다음과 같은 구절이 나옵니다. (Lock 챕터 중...)&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;강제&amp;nbsp;잠금을&amp;nbsp;해제하려면&amp;nbsp;KILL&amp;nbsp;명령을&amp;nbsp;통해&amp;nbsp;MySQL&amp;nbsp;서버의&amp;nbsp;프로세스를&amp;nbsp;강제로&amp;nbsp;종료하면&amp;nbsp;됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그런데,&amp;nbsp;Process를&amp;nbsp;죽인다는&amp;nbsp;것이&amp;nbsp;가능한&amp;nbsp;것일까요?&lt;br /&gt;제가 아는 바로는 MySQL은 단일 프로세스이기 때문에 Process를 죽이면 MySQL이 종료되지 않을까 생각했습니다.&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;1280&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O2w22/btsCshjFMUZ/kRqGKaYHKuqwqKtKkzvCI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O2w22/btsCshjFMUZ/kRqGKaYHKuqwqKtKkzvCI0/img.png&quot; data-alt=&quot;.. 하나도 몰랐던 사람의 리액션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O2w22/btsCshjFMUZ/kRqGKaYHKuqwqKtKkzvCI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO2w22%2FbtsCshjFMUZ%2FkRqGKaYHKuqwqKtKkzvCI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;168&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;429&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;그런데 이것은 `Process`라는 용어가 OS와 MySQL DBMS 내부에서 다르게 쓰인다는 것을 모를 때 생각한 의문이었습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MySQL은 스레드 기반으로 작동합니다.&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;MySQL 서버는 프로세스 기반이 아니라 스레드 기반으로 작동하며, 크게 포그라운드(Foreground) 스레드와 백그라운드(Background) 스레드로 구분할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;*Real&amp;nbsp;MySQL&amp;nbsp;8.0&amp;nbsp;80page*&lt;/i&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;동일 책에서도 언급되다 시피 MySQL은 스레드 기반으로 작동합니다. 단일 프로세스로 작동되며 이 프로세스의 내붕에서 여러 스레드들이 메모리 자원을 공유하며 동작하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://charlezz.medium.com/process%EC%99%80-thread-%EC%9D%B4%EC%95%BC%EA%B8%B0-5b96d0d43e37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고링크 : 프로세스와 스레드의 차이&lt;/a&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;그래서 이를 기반으로 MySQL 8.0의 공식 문서는 어떻게 언급하고 있는지 찾아보았습니다.&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;`mysqld` 실제 서비스 데몬. 데이터베이스 엔진과 상호작용하며 SQL 쿼리를 처리합니다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;MySQL 설치 시 대부분의 작업을 수행하는 단일 다중 스레드 프로그램입니다. 추가 프로세스를 생성하지 않습니다. MySQL 서버는 데이터베이스와 테이블이 포함된 MySQL 데이터 디렉터리에 대한 액세스를 관리합니다.&lt;/blockquote&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;`mysqld_safe` 서버 시작 스크립트 : MySQL이&amp;nbsp;비정상적으로&amp;nbsp;종료되는&amp;nbsp;경우,&amp;nbsp;다시&amp;nbsp;기동합니다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;오류 발생 시 서버를 다시 시작하고 런타임 정보를 오류 로그에 기록하는 등 일부 안전 기능을 추가합니다. &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/mysqld-safe.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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;`mysql.server`&amp;nbsp;서버&amp;nbsp;시작&amp;nbsp;스크립트&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Unix 및 Unix 계열 시스템의 MySQL 배포판에는 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/mysqld-safe.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mysqld_safe&lt;/a&gt;를 사용하여 MySQL 서버를 시작하는 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/mysql-server.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mysql.server&lt;/a&gt;라는 스크립트가 포함되어 있습니다 .&lt;/blockquote&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;`mysqld_multi`&amp;nbsp;여러&amp;nbsp;MySQL&amp;nbsp;서버&amp;nbsp;관리&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;다양한 Unix 소켓 파일과 TCP/IP 포트에서 연결을 수신하는 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/mysqld.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;multi mysqld&lt;/a&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;결국 `mysqld` 만이 유일한 SQL 쿼리를 처리하는 프로세스라는 문서 내용이었습니다. &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/mysqld.html](https://dev.mysql.com/doc/refman/8.0/en/mysqld.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;프로세스&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;이루어집니다.&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;1280&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxalaI/btsCpenJ9mC/2y9oQuBUQmkOQDFZ7BHf91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxalaI/btsCpenJ9mC/2y9oQuBUQmkOQDFZ7BHf91/img.png&quot; data-alt=&quot;참고 : 80 페이지 MySQL 스레딩 모델&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxalaI/btsCpenJ9mC/2y9oQuBUQmkOQDFZ7BHf91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxalaI%2FbtsCpenJ9mC%2F2y9oQuBUQmkOQDFZ7BHf91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;305&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참고 : 80 페이지 MySQL 스레딩 모델&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;b&gt;그럼&amp;nbsp;`processlist`의&amp;nbsp;정체는&amp;nbsp;무엇일까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 쿼리로 MySQL의 processlist를 조회할 수 있습니다. 대체&amp;nbsp;이&amp;nbsp;process는&amp;nbsp;무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703164900954&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SHOW FULL PROCESSLIST;

+----+---------------+-------------------+-------------+-------+------+----------------------+-----------------------------------------------------------------------------------------+
|ID  |USER           |HOST               |DB           |COMMAND|TIME  |STATE                 |INFO                                                                                     |
+----+---------------+-------------------+-------------+-------+------+----------------------+-----------------------------------------------------------------------------------------+
|1364|root           |192.168.228.1:38667|secondhand-db|Query  |0     |executing             |/* ApplicationName=IntelliJ IDEA 2022.1.3 */ SELECT * FROM information_schema.PROCESSLIST|
|5   |event_scheduler|localhost          |NULL         |Daemon |643686|Waiting on empty queue|NULL                                                                                     |
|1303|root           |192.168.228.1:38417|rss_reader   |Sleep  |17744 |                      |NULL                                                                                     |
+----+---------------+-------------------+-------------+-------+------+----------------------+-----------------------------------------------------------------------------------------+&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;언급한&amp;nbsp;OS의&amp;nbsp;프로세스와는&amp;nbsp;다르게&amp;nbsp;이&amp;nbsp;`processlist`는&amp;nbsp;MySQL&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;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  `processlist`란 무엇일까?&lt;br /&gt;&lt;br /&gt;MySQL 프로세스 목록은 서버 내에서 실행 중인 스레드 집합에 의해 현재 수행되고 있는 작업을 나타냅니다.&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;그래서 process kill 이 가능한 것이지요. OS kill 구문과 같아서 헷갈릴 수 있지만 이 &quot;쿼리&quot;로 진행중인 프로세스를 강제 종료시킬 수 있으며, 이 때 해당 process 수행 중에 획득했던 lock을 다시 반납하게 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703164959254&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kill [process id];&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;OS의 작업단위인 thread와는 무슨 관계가 있나요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 쿼리를 통해 현재 MySQL 프로세스 내부에서 작동중인 스레드 리스트를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703165004267&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select * From performance_schema.threads;

+---------+-------------------------------------------+----------+--------------+----------------+----------------+--------------+-------------------+----------------+--------------------------+-------------------------------------------------------------------------------------+----------------+----+------------+-------+---------------+------------+--------------+----------------+-----------------+---------------------+------------+----------------+----------------+
|THREAD_ID|NAME                                       |TYPE      |PROCESSLIST_ID|PROCESSLIST_USER|PROCESSLIST_HOST|PROCESSLIST_DB|PROCESSLIST_COMMAND|PROCESSLIST_TIME|PROCESSLIST_STATE         |PROCESSLIST_INFO                                                                     |PARENT_THREAD_ID|ROLE|INSTRUMENTED|HISTORY|CONNECTION_TYPE|THREAD_OS_ID|RESOURCE_GROUP|EXECUTION_ENGINE|CONTROLLED_MEMORY|MAX_CONTROLLED_MEMORY|TOTAL_MEMORY|MAX_TOTAL_MEMORY|TELEMETRY_ACTIVE|
+---------+-------------------------------------------+----------+--------------+----------------+----------------+--------------+-------------------+----------------+--------------------------+-------------------------------------------------------------------------------------+----------------+----+------------+-------+---------------+------------+--------------+----------------+-----------------+---------------------+------------+----------------+----------------+
|1        |thread/sql/main                            |BACKGROUND|NULL          |NULL            |NULL            |mysql         |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |1           |SYS_default   |PRIMARY         |480              |66992                |1228598     |1249003         |NO              |
|3        |thread/innodb/io_ibuf_thread               |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |84          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|4        |thread/innodb/io_read_thread               |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |85          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|5        |thread/innodb/io_read_thread               |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |86          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|6        |thread/innodb/io_read_thread               |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |87          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|7        |thread/innodb/io_read_thread               |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |88          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|8        |thread/innodb/io_write_thread              |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |89          |SYS_default   |PRIMARY         |0                |0                    |0           |36              |NO              |
|9        |thread/innodb/io_write_thread              |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |90          |SYS_default   |PRIMARY         |0                |0                    |0           |76              |NO              |
|10       |thread/innodb/io_write_thread              |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |91          |SYS_default   |PRIMARY         |0                |0                    |0           |76              |NO              |
|11       |thread/innodb/io_write_thread              |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |92          |SYS_default   |PRIMARY         |0                |0                    |0           |76              |NO              |
|12       |thread/innodb/page_flush_coordinator_thread|BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |93          |SYS_default   |PRIMARY         |240              |240                  |1978        |1978            |NO              |
|14       |thread/innodb/log_checkpointer_thread      |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |95          |SYS_default   |PRIMARY         |0                |0                    |0           |1728            |NO              |
|15       |thread/innodb/log_flush_notifier_thread    |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |96          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|16       |thread/innodb/log_flusher_thread           |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |97          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|17       |thread/innodb/log_write_notifier_thread    |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |98          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|18       |thread/innodb/log_writer_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |99          |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|19       |thread/innodb/log_files_governor_thread    |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |100         |SYS_default   |PRIMARY         |0                |0                    |0           |120             |NO              |
|24       |thread/innodb/srv_lock_timeout_thread      |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |105         |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|25       |thread/innodb/srv_error_monitor_thread     |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |106         |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|26       |thread/innodb/srv_monitor_thread           |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |107         |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|27       |thread/innodb/buf_resize_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |108         |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|28       |thread/innodb/srv_master_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |109         |SYS_default   |PRIMARY         |240              |240                  |1978        |2082            |NO              |
|29       |thread/innodb/dict_stats_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |110         |SYS_default   |PRIMARY         |240              |240                  |3354        |32434           |NO              |
|30       |thread/innodb/fts_optimize_thread          |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |111         |SYS_default   |PRIMARY         |240              |240                  |2962        |2962            |NO              |
|31       |thread/mysqlx/worker                       |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |1               |NULL|YES         |YES    |NULL           |112         |USR_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|32       |thread/mysqlx/worker                       |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |1               |NULL|YES         |YES    |NULL           |113         |USR_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|33       |thread/mysqlx/acceptor_network             |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |1               |NULL|YES         |YES    |NULL           |114         |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|37       |thread/innodb/buf_dump_thread              |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |118         |SYS_default   |PRIMARY         |0                |0                    |0           |14256           |NO              |
|38       |thread/innodb/clone_gtid_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |waiting for handler commit|NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |119         |SYS_default   |PRIMARY         |108512           |108512               |585233      |623781          |NO              |
|39       |thread/innodb/srv_purge_thread             |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |120         |SYS_default   |PRIMARY         |240              |240                  |2266        |43832           |NO              |
|40       |thread/innodb/srv_worker_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |121         |SYS_default   |PRIMARY         |240              |240                  |3610        |85282           |NO              |
|41       |thread/innodb/srv_worker_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |122         |SYS_default   |PRIMARY         |240              |240                  |2890        |93178           |NO              |
|42       |thread/innodb/srv_worker_thread            |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |643829          |NULL                      |NULL                                                                                 |NULL            |NULL|YES         |YES    |NULL           |123         |SYS_default   |PRIMARY         |240              |240                  |2074        |43644           |NO              |
|43       |thread/sql/event_scheduler                 |FOREGROUND|5             |event_scheduler |localhost       |NULL          |Daemon             |643829          |Waiting on empty queue    |NULL                                                                                 |1               |NULL|YES         |YES    |NULL           |124         |SYS_default   |PRIMARY         |0                |0                    |16665       |16665           |NO              |
|44       |thread/sql/signal_handler                  |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |1               |NULL|YES         |YES    |NULL           |125         |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|46       |thread/mysqlx/acceptor_network             |BACKGROUND|NULL          |NULL            |NULL            |NULL          |NULL               |NULL            |NULL                      |NULL                                                                                 |1               |NULL|YES         |YES    |NULL           |127         |SYS_default   |PRIMARY         |0                |0                    |0           |0               |NO              |
|47       |thread/sql/compress_gtid_table             |FOREGROUND|7             |NULL            |NULL            |NULL          |Daemon             |643829          |Suspending                |NULL                                                                                 |1               |NULL|YES         |YES    |NULL           |128         |SYS_default   |PRIMARY         |8240             |8240                 |14432       |14496           |NO              |
|1404     |thread/sql/one_connection                  |FOREGROUND|1364          |root            |192.168.228.1   |secondhand-db |Query              |0               |executing                 |/* ApplicationName=IntelliJ IDEA 2022.1.3 */ select * From performance_schema.threads|NULL            |NULL|YES         |YES    |SSL/TLS        |244         |USR_default   |PRIMARY         |1094784          |1107120              |1339154     |1374270         |NO              |
|1343     |thread/sql/one_connection                  |FOREGROUND|1303          |root            |192.168.228.1   |rss_reader    |Sleep              |17887           |NULL                      |NULL                                                                                 |1               |NULL|YES         |YES    |SSL/TLS        |245         |USR_default   |PRIMARY         |1067328          |1107360              |1160552     |1200413         |NO              |
+---------+-------------------------------------------+----------+--------------+----------------+----------------+--------------+-------------------+----------------+--------------------------+-------------------------------------------------------------------------------------+----------------+----+------------+-------+---------------+------------+--------------+----------------+-----------------+---------------------+------------+----------------+----------------+&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;위 조회 결과에서 `THREAD_OS_ID` 가 OS 상에서 할당된 thread에 대한 id 값입니다.&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;MySQL 스레드가 수명 기간 동안 동일한 운영 체제 스레드와 연결되어 있는 경우 THREAD_OS_ID에는 운영 체제 스레드 ID가 포함됩니다.&lt;/li&gt;
&lt;li&gt;Windows의 경우 THREAD_OS_ID는 프로세스 탐색기([&lt;a href=&quot;https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx](https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx))에&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx](https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx))에&lt;/a&gt; 표시되는&amp;nbsp;스레드&amp;nbsp;ID에&amp;nbsp;해당합니다.&lt;/li&gt;
&lt;li&gt;Linux의 경우 THREAD_OS_ID는 gettid() 함수의 값에 해당합니다. 이 값은 예를 들어 perf 또는 ps -L 명령을 사용하거나 proc 파일 시스템(/proc/[pid]/task/[tid])에서 노출됩니다.&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;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;&lt;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[27.12.21.8 The threads Table](&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/performance-schema-threads-table.html)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/performance-schema-threads-table.html)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[MySQL Architecture - 3. Thread](&lt;a href=&quot;https://blog.ex-em.com/1681)&quot;&gt;https://blog.ex-em.com/1681)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Database</category>
      <category>mysql</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/94</guid>
      <comments>https://new-pow.tistory.com/94#entry94comment</comments>
      <pubDate>Thu, 21 Dec 2023 22:25:48 +0900</pubDate>
    </item>
    <item>
      <title>Error: Permission to {repository} denied to github-actions[bot]</title>
      <link>https://new-pow.tistory.com/93</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`README.md` 를 자동으로 생성해주는 Action을 시행하다 이와 같은 Github action 에러를 맞이했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703147359284&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# update 관련 내용 생략

git add README.md &amp;amp;&amp;amp; git commit -m &quot;Updated README&quot;
[main 88ac2d7] Updated README
 1 file changed, 25 insertions(+), 2 deletions(-)
git push
remote: Permission to new-pow/bookshelf.git denied to github-actions[bot].
fatal: unable to access 'https://github.com/new-pow/bookshelf/': The requested URL returned error: 403&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 원인&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소 세팅에서 `Workflow`의 권한이 제한되어있었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 수정하여 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6mYtv/btsCtbXgjDX/VkNqCF864TSNZclta39JR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6mYtv/btsCtbXgjDX/VkNqCF864TSNZclta39JR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6mYtv/btsCtbXgjDX/VkNqCF864TSNZclta39JR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6mYtv%2FbtsCtbXgjDX%2FVkNqCF864TSNZclta39JR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;280&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;652&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;b&gt;대처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;`Settings -&amp;gt; Action -&amp;gt; General -&amp;gt; Workflow permissions` 에서 `Read and write permissions` 를 선택합니다.&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;1640&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buJATc/btsCo4ZBgeq/yhKk7hv7QHqkGjvGCkOvXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buJATc/btsCo4ZBgeq/yhKk7hv7QHqkGjvGCkOvXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buJATc/btsCo4ZBgeq/yhKk7hv7QHqkGjvGCkOvXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuJATc%2FbtsCo4ZBgeq%2FyhKk7hv7QHqkGjvGCkOvXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;241&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;790&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;text-align: left;&quot;&gt;바로 성공  &lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Refs.&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/73687176/permission-denied-to-github-actionsbot-the-requested-url-returned-error-403&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/73687176/permission-denied-to-github-actionsbot-the-requested-url-returned-error-403&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1703147553261&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;Permission denied to github-actions[bot]. The requested URL returned error: 403&quot; data-og-description=&quot;I want to push files into the current repository using Github Actions. I've written a basic configuration that uses the official actions/checkout@v3 action. My configuration is almost the same as i...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/73687176/permission-denied-to-github-actionsbot-the-requested-url-returned-error-403&quot; data-og-url=&quot;https://stackoverflow.com/questions/73687176/permission-denied-to-github-actionsbot-the-requested-url-returned-error-403&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8Y4aW/hyUPKOzQlu/qwHsKmvSrHUio5ojJuRDkK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/73687176/permission-denied-to-github-actionsbot-the-requested-url-returned-error-403&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/73687176/permission-denied-to-github-actionsbot-the-requested-url-returned-error-403&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8Y4aW/hyUPKOzQlu/qwHsKmvSrHUio5ojJuRDkK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;Permission denied to github-actions[bot]. The requested URL returned error: 403&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I want to push files into the current repository using Github Actions. I've written a basic configuration that uses the official actions/checkout@v3 action. My configuration is almost the same as i...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.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;</description>
      <category>  bugs</category>
      <category>action</category>
      <category>git</category>
      <category>github</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/93</guid>
      <comments>https://new-pow.tistory.com/93#entry93comment</comments>
      <pubDate>Thu, 21 Dec 2023 17:33:39 +0900</pubDate>
    </item>
    <item>
      <title>2023-W49 회고 : 저 곧 출근해요</title>
      <link>https://new-pow.tistory.com/91</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 주에 한 것&lt;/b&gt;&lt;/h2&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;1239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1eh32/btsBDKOzssC/wfGNh3WlqR8z1gPn8C9wcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1eh32/btsBDKOzssC/wfGNh3WlqR8z1gPn8C9wcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1eh32/btsBDKOzssC/wfGNh3WlqR8z1gPn8C9wcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1eh32%2FbtsBDKOzssC%2FwfGNh3WlqR8z1gPn8C9wcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;436&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1239&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;What is New?!&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;RSS Reader 프로젝트가 막바지를 향해 가고 있습니다. 이제 배포하면 기본 기능은 끝났어요.&lt;/li&gt;
&lt;li&gt;Postman으로 STOMP를 테스트할 수 있도록 개선하였습니다. (Apic 사이트가 접속 불가하거든요...)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;`github package`를 처음 사용해 보았습니다. 와우 정말 편리합니다!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;`SSE emitter` 테스트를 1주일 내내 실패하고 있습니다; 이럴 일이야?&lt;/span&gt;&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqtX7U/btsBFLzkgBS/6tiScq1X3t49fn2FEBL2L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqtX7U/btsBFLzkgBS/6tiScq1X3t49fn2FEBL2L0/img.png&quot; data-alt=&quot;정성스런 코멘트들이 많았던 이력서 피드백...  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqtX7U/btsBFLzkgBS/6tiScq1X3t49fn2FEBL2L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqtX7U%2FbtsBFLzkgBS%2F6tiScq1X3t49fn2FEBL2L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;369&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;737&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;What I Leanred&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;저 면접 합격했어요.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 한주간 저에게는 정말 오래간만에 혼란스러운 한 주였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 내년 봄까지 취업 준비를 하며 계속 이력서를 수정하고, 지원하고를 반복하려고했거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;기술면접과 인성면접을 함께한 자리였습니다.&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;그런데 예상 외로 약 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;그런데 괜히 입사 결정을 하기 직전에 기분이 싱숭생숭해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사는 정말 가고 싶어 고민하고 지원했고, 가면 내가 원하는 기술 스택과 좋은 분위기에서 일을 시작할 수 있을 것 같았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그동안 나름 지원하는 회사에 대한 기준이 있다고 생각했는데.. 아니요; 너무 모호한 기준이었나봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 기업의 문제가 아니라 저의 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이런 고민들을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&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;너무 빠르게 결정을 내리는 순간이 오니까 &quot;정말 이게 최선이야?&quot; 하는 의심이 자꾸 들더라고요.&lt;/li&gt;
&lt;/ul&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;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;같은 시간 내에 나 혼자 공부하는 것보다 현업에서 직접 부딪히며 배우는 생동감 넘치는 배움을 경험하고 싶다는 것이 가장 첫 번째 이유였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 개발 문화와 개발자 커뮤니티, 코드 퀄리티를 중요하게 여기는 개발팀.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 회사가 가진 기술력과 나를 끌어줄 수 있는 선배들이 존재한다는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정한 스트레스와 압박감 속에서 한 사람, 한 사람의 역할과 존재감이 중요한 환경. (저는 일하는 것에서 제 존재감을 찾는 사람이더라고요  )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 완벽하게 일치해서 당황스러울 정도였던 원래부터 관심있었던 기술스택들.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에 대한 광범위한 이해와 몰입이 필요한 스타트업 환경이 나에게 동기부여를 줄 수 있는 환경이라는 것도 부가적인 이유였고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 따지고보면 초기 스타트업이라는 것과 도메인을 제가 잘 모른다는 것 외에는, 모두 제가 너무나 원했던 환경이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 하루라도 빨리 엔지니어로서의 정체성을 가지고 싶다는 욕심도 한 몫했고요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이 글을 작성하기까지 고민했던 치열한 시간들이 있었거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이제 주사위는 던져졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최선을 다해서 회사에 적응하고 서비스에 몰입하여 저의 몫을 해낼 수 있는 엔지니어가 되는 것이 새로운 목표가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 앞으로의 커리어를 잘 설계해야겠다는 욕심도 내게 되었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 1월 2일부터 신입 개발자로서 출근하게 되었습니다  &lt;/p&gt;
&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;zwj;♀️&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;IMG_1944.png&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v4KgN/btsBCl9vOrn/i0W03WxAMwyI8AFoQe7Eu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v4KgN/btsBCl9vOrn/i0W03WxAMwyI8AFoQe7Eu0/img.png&quot; data-alt=&quot;너무 귀여웠던 응원...  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v4KgN/btsBCl9vOrn/i0W03WxAMwyI8AFoQe7Eu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv4KgN%2FbtsBCl9vOrn%2Fi0W03WxAMwyI8AFoQe7Eu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;379&quot; height=&quot;184&quot; data-filename=&quot;IMG_1944.png&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;184&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다음 주에 할 것&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취준생으로서는 마침표를 앞두고 있지만, 저는 평소처럼 규칙적으로 학습하며 연말을 보내려고 합니다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;939&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byKRsd/btsBCxvm91n/BCaJZ2wrRK6ViBRzLwVKsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byKRsd/btsBCxvm91n/BCaJZ2wrRK6ViBRzLwVKsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byKRsd/btsBCxvm91n/BCaJZ2wrRK6ViBRzLwVKsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyKRsd%2FbtsBCxvm91n%2FBCaJZ2wrRK6ViBRzLwVKsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;331&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;939&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <category>log</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/91</guid>
      <comments>https://new-pow.tistory.com/91#entry91comment</comments>
      <pubDate>Sat, 9 Dec 2023 18:10:42 +0900</pubDate>
    </item>
    <item>
      <title>Cache를 적용하여 API 기능을 개선해보자 (2)  Redis repository와 Redis Template</title>
      <link>https://new-pow.tistory.com/90</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 당근마켓을 모티브로 한 프로젝트 &lt;a href=&quot;https://github.com/masters2023-2nd-project-05/second-hand-BE&quot;&gt;Secondhand&lt;/a&gt; 구현시 이슈 사항을 정리한 글입니다.&lt;/p&gt;
&lt;/blockquote&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;a href=&quot;https://new-pow.tistory.com/86&quot;&gt;2023.11.24 - [  Spring] - Redis Cache를 적용하여 Read API 기능을 개선해보자 (1) 이론학습&lt;/a&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;앞서 기본적으로 Cache에 대해서 학습하고 어노테이션을 적용해서 캐시 저장을 해보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 학습과정 중에 아쉬웠던 점은 캐시 추상화 어노테이션을 사용하는 것만으로는 팀원과 구현해보기로 한 `write-back` 을 구현하기 힘들었다는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`write-through`는 이런 방식으로 구현할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
public class MyService {

    @Cacheable(value = &quot;myCache&quot;, key = &quot;#id&quot;)
    public String getDataFromDatabase(String id) {
        // 조회로직
    }

    @Caching(evict = {
        @CacheEvict(value = &quot;myCache&quot;, key = &quot;#id&quot;),
        @CacheEvict(value = &quot;myCache&quot;, key = &quot;'all'&quot;)
    })
    public void updateDataInDatabase(String id, String newData) {
        // 업데이트 로직
        // 레포지토리 접근
    }
}&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;/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;img src=&quot;https://mermaid.ink/img/pako:eNqtVcFu2kAQ_ZWVT62a_ACHSFHpoVJ7KYl64WLsTWsJbGqbSlUUiSZ2lAJRqBoaigxyGgKKRCSHUglV6Q95x__Q9RpjG-PkUi7YO288b2bezO5zgiJiLsdp-EMNywLOS_w7la8UZUR_VV7VJUGq8rKOdjWspk_f4lIBqx_XmrYL6cM3WJS09HGe1_kSr-GiHNio7-bWFgPnENwZXstEulKVBOT-npAzM0D5lChsySGHvFabjIfuvA4XbTCshavX6bpzB3mfZ2R0uwgQ-vj-24UwCHFuEIwNGJgIeucwvY3TCUkmwHaH_JohuHS8XisAK1U9AkCfGo_6YNyRdhfGdQSdBvTryJ3-hZ4TOEQRVpkwx1XQQzTsOgyuVx0SZWRoZw4XExi0Ebk5947qa7yxLAYPYbTNOL2jTiwocbrej25UqGRHUhwT8OwuPFdkGQt6qg0hMrS70zqM-gl7mK8xB7OLoDH0jm0ymtDqU1UgOJxA78ZPPgSks0_xKtRKmqBKJYyeRBRf5p9m0st0eIDvY_1xnTto2oFrWVGq8VSYb9T49GQshoh-EJoWE-LAJPY1bccZoh8nhzM6IjHlpCpAFZHINtLXs-ScxkKcdClhFiI-w2Ddk0uLHlve9y-PqRtRZjTJlLRZTL96mzHhLL7fNxZrwuvQFWBYZGqwgPMW_SPfLPjT9lXRmCeDZwiXieZyBoMJ3SOkcR6VnVrcaSuesG2Q5mR9Ff2WrKvRhUlOHbomlnSWo5fVxGCYvsLxKYKfJrlqZYh2V9aWKgw6h9JCjYP-kzYjImGQvKQJ2fP84LyyLZ41soe3YFsoNhE7O6_g6t5f9mR8QprDNXpJC4zlgbKWILfBVbBa4SWR3pL7vqHI6e9xBRe5HH0U8R5fK-tFrigfUChf05XCJ1ngcnt8WcMbXK0q8np4q66cvhAlXVGXh5i9vg7uY3YtH_wDNbt7xw?type=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;&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;그런데 이렇게 구현해보기 위해서는 직접 Redis에 데이터를 넣고 확인하는 과정이 필요했기 때문에 꽤 골치아픈 요구사항이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redis에 데이터를 저장하는 두 가지 방법과&amp;nbsp;&lt;br /&gt;CRUD Repository를 사용하지 않은 이유&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1701859323340&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-data-redis'&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; Spring data Redis 를 주입하면 두 가지 방식으로 Redis의 데이터에 접근할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`CRUD Repository`를 extends 받는 Interface를 구현합니다.&lt;/li&gt;
&lt;li&gt;`RedisTemplate` 을 사용하여 operations 를 꺼내 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 후자의 방식을 택했습니다. 아무래도 직접 구현하는 것이 목적에 맞는 구현을 할 수 있는 것도 있었지만 `CRUD Repository`를 사용했을 때 어떻게 Redis에 저장되는지 확인하고 나서 정한 것이었는데요.&lt;/p&gt;
&lt;p data-ke-size=&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;CRUD Repository를 사용하면 기존 Spring data Repository를 사용했던것과 같은 방식으로 간편하게 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701860110375&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@ToString
@RedisHash(&quot;chat-bubble&quot;)
public class ChatBubble implements Serializable {
    @Id @JsonIgnore
    private final String id;
    @Indexed
    private final String roomId;
    // 기타 등등 ...
    @TimeToLive
    private Integer expirationSeconds;
}&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;총 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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;ChatBubble의 `@Id` 로 이루어진 Set을 구성합니다.&lt;/span&gt;&lt;/li&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;ChatBubble의 `@Id`를 Key로, 객체를 Value로 갖는 Hash를 여러개 구성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO6qhT/btsBtJP1UaD/ktn1CTQlTlLaLkWMaqwr91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO6qhT/btsBtJP1UaD/ktn1CTQlTlLaLkWMaqwr91/img.png&quot; data-alt=&quot;이미지 출처 :&amp;amp;amp;nbsp;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO6qhT/btsBtJP1UaD/ktn1CTQlTlLaLkWMaqwr91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO6qhT%2FbtsBtJP1UaD%2Fktn1CTQlTlLaLkWMaqwr91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;380&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 :&amp;amp;nbsp;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&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;이렇게 구현되어있는 이유는 Redis가 Key-Value 데이터베이스이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key를 조회하기 위해 모든 키를 순회하기보다 해당 set을 순회하는 편이 더 빠르기 때문에 같은 entity의 경우 하나의 set에 entity의 모든 id를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 `@Indexed`를 지정해주게 되면 이또한 다른 자료구조인 Hash set을 구성하는 것을 확인할 수 있었습니다. 마치 Secondary Index처럼요.&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;Spring Data Redis Repository에서는 앞서 말씀드린 연산을 빠르게 수행하기 위해 key 값만 저장하는 set과 secondary index&lt;br /&gt;를 추가로 이용해 primary key access만 제공하는 Key-Value 데이터베이스의 한계를 극복했습니다. [&lt;a href=&quot;https://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.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;그러나 이러한 방식이면 &lt;b&gt;중복된 데이터를 너무 많이 저장&lt;/b&gt;한다는 고민이 들게 합니다. 특히 In memory로 동작하는 Redis의 특성상 메모리를 불필요하게 차지하게 하고 싶지 않았습니다.&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;TTL을 적용하는데에도 추가적인 로직을 구현해야 했는데요. 엔티티에 설정된 TTL이 0 이하로 떨어지면 Redis `&lt;span style=&quot;color: #555555; text-align: left;&quot;&gt;activeExpireCycle` 에 의해서 삭제되는 등 저장된 데이터는 삭제되지만 &lt;b&gt;Set에서는 삭제가 되지 않습니다.&lt;/b&gt; Set에서 삭제되는 로직을 별도로 구현해주지 않으면 계속 메모리가 누적되게 되는 치명적인 오류가 생긱는 것이죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: left;&quot;&gt;그리고 `size()` 등의 메서드를 호출할 때 데이터 정합성이 맞지 않게 됩니다. 실제로 Redis에 존재하지 않는 데이터임에도 계수에 포함되게 되는 것입니다.&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&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/cYhn3F/btsBxTEPOOQ/fVb06tq2kYb0SucqljQPv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYhn3F/btsBxTEPOOQ/fVb06tq2kYb0SucqljQPv1/img.png&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;788&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/cYhn3F/btsBxTEPOOQ/fVb06tq2kYb0SucqljQPv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYhn3F%2FbtsBxTEPOOQ%2FfVb06tq2kYb0SucqljQPv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2234&quot; height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byBEu4/btsByzsyMTi/FqrYJ2lUAIWl5hN4MWEBck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byBEu4/btsByzsyMTi/FqrYJ2lUAIWl5hN4MWEBck/img.png&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;788&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/byBEu4/btsByzsyMTi/FqrYJ2lUAIWl5hN4MWEBck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyBEu4%2FbtsByzsyMTi%2FFqrYJ2lUAIWl5hN4MWEBck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2234&quot; height=&quot;788&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드에 대해서 당시 리뷰어에게 코멘트를 받고 Redis Repository를 쓰는 것을 포기하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접적으로 `Redis Template` 을 사용하는 것이 원하는 타입으로 저장할 수 있고, 추후에 조회시에도 훨씬 유리할 것이라는 판단이었습니다.&lt;/p&gt;
&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://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고링크 : Spring Data Redis 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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;과도기 : RedisTemplate을 사용하여 Redis에 List 타입으로 저장해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급한 Redis Repository를 포기하고 `RedisTemplate`을 사용하기로 하면서 좋았던 점은 타입에 대한 자유가 생겼다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis Repository는 Value와 이에 대한 조회를 뒷받침하는 자료구조를 사용했다면, 이제 직접 Operation을 꺼내 사용하게 된 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis에는 다양한 타입이 있습니다. 채팅 메시지를 저장하기 위해 고려했던 것은 `Streams`, `Lists`, `Hashes` 등 연속된 key-value를 저장할 수 있는 형태들이었는데요. (채팅방 정보(참여자 접속 정보)는 여러개가 연속할 필요는 없었기 때문에 `opsForValue` 메서드를 사용해 Strings 타입으로 저장하기로 했어요.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 선택했던 것은 List였습니다. 채팅 메시지를 순서대로 저장하고 있어 조회할 때도 편리할 것이라는 예상이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Redis에는 다양한 타입이 있는데, 어떨 때 어떻게 쓰면 좋은지 알아두면 좋겠죠..?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[&lt;a href=&quot;https://redis.io/docs/data-types/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Redis 공식문서 : 타입에 대한 부분&lt;/a&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;1280&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNq2vj/btsBCXZNexC/JgnD1LWbzIJYj2kR2caRs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNq2vj/btsBCXZNexC/JgnD1LWbzIJYj2kR2caRs0/img.png&quot; data-alt=&quot;이렇게 저장하는 것으로 로직을 수정하였습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNq2vj/btsBCXZNexC/JgnD1LWbzIJYj2kR2caRs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNq2vj%2FbtsBCXZNexC%2FJgnD1LWbzIJYj2kR2caRs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;824&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;824&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;다음은 관련해서 구현했던 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701942029981&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories
public class RedisConfig {

    @Value(&quot;${spring.cache.redis.host}&quot;)
    private String host;

    @Value(&quot;${spring.cache.redis.port}&quot;)
    private int port;

    private final SimpMessageSendingOperations messagingTemplate;
    private final ObjectMapper objectMapper;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    // 생략

    @Bean
    public RedisTemplate&amp;lt;String, ChatBubble&amp;gt; redisChatBubbleTemplate() {
        final RedisTemplate&amp;lt;String, ChatBubble&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(redisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        Jackson2JsonRedisSerializer&amp;lt;ChatBubble&amp;gt; jsonRedisSerializer = new Jackson2JsonRedisSerializer&amp;lt;&amp;gt;(ChatBubble.class);
        jsonRedisSerializer.setObjectMapper(objectMapper);
        template.setValueSerializer(jsonRedisSerializer);

        return template;
    }&lt;/code&gt;&lt;/pre&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;처음 채팅 메시지를 발송하면, 채팅방 Id를 뒷쪽에 붙인 Key를 가진 리스트를 생성합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름을 왜 이렇게 짓냐면, Redis에서는 Key로 모든 데이터가 정렬되기 때문에 앞에 Prefix를 붙여 조회의 일관성을 주고 싶었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;채팅 메시지를 다음부터 발송할 때 해당 리스트에 하나씩 추가됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1701942199046&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ChatLogService {
    @Value(&quot;${const.chat.bucket}&quot;)
    private String chatBucketPrefix;
    @Value(&quot;${const.chat.page-size}&quot;)
    private int chatLoadSize;

    private final RedisTemplate&amp;lt;String, ChatBubble&amp;gt; redisChatBubbleTemplate;

	// 채팅 로그를 조회합니다.
    public Slice&amp;lt;ChatBubble&amp;gt; getChatBubbles(int page, String roomId) {
        ListOperations&amp;lt;String, ChatBubble&amp;gt; listOperations = redisChatBubbleTemplate.opsForList();

        String key = chatBucketPrefix + roomId;
        long startIndex = getStartIndex(page);
        long endIndex = startIndex - chatLoadSize;

        List&amp;lt;ChatBubble&amp;gt; messages = listOperations.range(key, endIndex, startIndex);
        Pageable pageable = PageRequest.ofSize(chatLoadSize);
        return getSlice(messages, pageable);
    }

	// 목록 조회시 offset을 계산합니다.
    private long getStartIndex(int page) {
        return (-1L * chatLoadSize * page) - 1;
    }

	// 목록 조회시 slice형태로 만들어줍니다.
    private Slice&amp;lt;ChatBubble&amp;gt; getSlice(List&amp;lt;ChatBubble&amp;gt; messages, Pageable pageable) {
        boolean hasNext = false;

        if (messages.size() &amp;gt; pageable.getPageSize()) {
            hasNext = true;
            messages.remove(pageable.getPageSize());
        }

        return new SliceImpl&amp;lt;&amp;gt;(messages, pageable, hasNext);
    }


	// 채팅 로그를 저장합니다.
    public void saveChatBubble(ChatBubble chatBubble) {
        String key = chatBucketPrefix + chatBubble.getRoomId();
        redisChatBubbleTemplate.opsForList().rightPush(key, chatBubble);
    }
}&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y36rA/btsBBmML8dR/mtVkyM59serCfruwQfWkk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y36rA/btsBBmML8dR/mtVkyM59serCfruwQfWkk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y36rA/btsBBmML8dR/mtVkyM59serCfruwQfWkk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy36rA%2FbtsBBmML8dR%2FmtVkyM59serCfruwQfWkk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1442&quot; height=&quot;674&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;674&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;리스트라며... 근데 Redis에서는 리스트가 linked 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;&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;이렇게되면 페이징을 offset 방식으로 한 것처럼 페이지 조회를 할때마다 앞의 메시지 조회하게 되어서 중간 요소 탐색시 O(n)의 시간복잡도가 소요되게 됩니다.&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;Redis lists are implemented via Linked Lists.&lt;/b&gt; This means that even if you have millions of elements inside a list, the operation of adding a new element in the head or in the tail of the list is performed in constant time. The speed of adding a new element with the&amp;nbsp;LPUSH&amp;nbsp;command to the head of a list with ten elements is the same as adding an element to the head of list with 10 million elements.&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;`sorted set` 을 사용하는 것이 더 나을 수 있다고 판단했지만 결국 바꾸지 않았습니다.&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;중고거래 채팅 특성상 채팅이 길게 이어지거나 옛날 기록을 조회하기 위해 거슬러 올라가는 일은 드물 것이라는 판단이었습니다. 하지만 다음에 할 때는 List 타입으로 하진 않을 것 같아요  &lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;그리고 지금의 코드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현재는 코드를 어느정도 추상화시켰습니다. 반복되는 코드가 많아 역할별로 메서드를 묶기로 해습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그전에 `RedisConfig`에 저장 타입마다 RedisTemplate을 정의하던 코드가 있었는데요. 이를 `Object` 로 통일시켜 등록된 `@Bean`을 줄였습니다. Object mapper를 통해 타입 변환해주는 역할을 해주는 클래스를 별도로 만들어주기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701951102629&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories
public class RedisConfig {

    private final RedisConnectionFactory redisConnectionFactory;
    private final ObjectMapper objectMapper;

    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisObjectTemplate() {
        objectMapper.registerModule(new JavaTimeModule());

        RedisTemplate&amp;lt;String, Object&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new Jackson2JsonRedisSerializer&amp;lt;&amp;gt;(Object.class)); // Use Jackson2JsonRedisSerializer

        return template;
    }
}&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;그리고 이 타입변환을 하게 해줄 `Redis Operations` 와 이를 상속받는 `RedisListOperationsHelper`, `RedisOperationsHelper`를 구현하였습니다.&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;1946&quot; data-origin-height=&quot;1438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xNuVT/btsBGWNE5vV/UwPIYIfGYv5mBUJWLVrTS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xNuVT/btsBGWNE5vV/UwPIYIfGYv5mBUJWLVrTS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xNuVT/btsBGWNE5vV/UwPIYIfGYv5mBUJWLVrTS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxNuVT%2FbtsBGWNE5vV%2FUwPIYIfGYv5mBUJWLVrTS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;369&quot; data-origin-width=&quot;1946&quot; data-origin-height=&quot;1438&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;그리고 각자의 `cache` 레포지토리를 구현할 때 자료구조에 맞춰 이 operationsHelper들을 포함하도록 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701953749040&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public abstract class RedisOperations {

    protected final RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate;
    protected final ObjectMapper objectMapper;

	// 원하는 타입으로 반환할 수 있도록합니다. 제네릭으로 리팩토링할 수 있을 것 같은데 아직 안했어요.
    protected  &amp;lt;T&amp;gt; T getT(Class&amp;lt;T&amp;gt; clazz, Object object) throws JsonProcessingException {
        String s = objectMapper.writeValueAsString(object);
        return objectMapper.readValue(s, clazz);
    }

    public void deleteAll() { // 모든 데이터를 삭제합니다.
        redisTemplate.execute((RedisCallback&amp;lt;Object&amp;gt;) connection -&amp;gt; {
            connection.flushAll();
            return null;
        });
    }
}&lt;/code&gt;&lt;/pre&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;List 타입 operationsHelper를 구현한 코드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1701953781821&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class RedisListOperationsHelper extends RedisOperations {

    public RedisListOperationsHelper(RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate,
            ObjectMapper objectMapper) {
        super(redisTemplate, objectMapper);
    }

    /**
     * Redis List에 데이터를 추가합니다.
     * @param key
     * @param value
     */
    public void add(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * Redis List에 데이터를 가져옵니다. (RPOP)
     * @param key
     * @param T
     * @return List&amp;lt;T&amp;gt;
     */
    public &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt; popAll(String key, Class&amp;lt;T&amp;gt; clazz) {
        Set&amp;lt;String&amp;gt; keys = redisTemplate.keys(key+&quot;*&quot;);
        List&amp;lt;T&amp;gt; result = new ArrayList&amp;lt;&amp;gt;();

        for (String k : keys) {
            Long size = redisTemplate.opsForList().size(k);
            List&amp;lt;Object&amp;gt; objects = redisTemplate.opsForList().rightPop(k, size);
            result.addAll(objects.stream().map(e -&amp;gt; {
                try {
                    return getT(clazz, e);
                } catch (JsonProcessingException ex) {
                    throw new RuntimeException(ex);
                }
            }).collect(Collectors.toList()));
        }
        return result;
    }

    /**
     * Redis List 개수를 확인합니다.
     * @param key
     * @return size (long)
     */
    public long size(String key) {
        return redisTemplate.opsForList().size(key);
    }

    public &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt; getList(String key, long start, long end, Class&amp;lt;T&amp;gt; clazz) {
        List&amp;lt;Object&amp;gt; range = redisTemplate.opsForList().range(key, start, end-1);

        return range.stream().map(e -&amp;gt; {
            try {
                return getT(clazz, e);
            } catch (JsonProcessingException ex) {
                throw new RuntimeException(ex);
            }
        }).collect(Collectors.toList());
    }

    /**
     * Redis List에서 데이터를 삭제합니다.
     * @param key
     * @param value
     */
    public void trim(String key, int size) {
        redisTemplate.opsForList().trim(key, 0, size);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

}&lt;/code&gt;&lt;/pre&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;그리고 이를 활용하여 cache를 구현하였습니다.&lt;/li&gt;
&lt;li&gt;현재 언급하지 않지만, String 타입으로 저장하는 `ChatMetaInfoCache` 도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1701954336014&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class ChatBubbleCache {

    private final RedisListOperationsHelper operationsHelper;
    private final ChatCacheProperties chatBubbleProperties;

    private long getStartIndex(Pageable page) {
        return (-1L * page.getPageSize() * page.getPageNumber());
    }

    private Slice&amp;lt;ChatBubble&amp;gt; getSlice(List&amp;lt;ChatBubble&amp;gt; messages, Pageable pageable) {
        if (messages.isEmpty()) {
            throw new IndexOutOfBoundsException(&quot;빈 페이지 입니다.&quot;);
        }

        if (messages.size() &amp;gt; pageable.getPageSize()) {
            messages.remove(pageable.getPageSize());
        }

        return new SliceImpl&amp;lt;&amp;gt;(
                messages.subList(0, Math.min(pageable.getPageSize(), messages.size())), pageable,
                pageable.getPageSize() &amp;lt; messages.size());
    }

    public Slice&amp;lt;ChatBubble&amp;gt; findAllByRoomId(String key, Pageable pageable) {

        long startIndex = getStartIndex(pageable);
        long endIndex = startIndex - pageable.getPageSize();

        List&amp;lt;ChatBubble&amp;gt; messages = operationsHelper.getList(generateChatLogKey(key), endIndex - 1, startIndex,
                ChatBubble.class);

        return getSlice(messages, pageable);
    }

    public List&amp;lt;ChatBubble&amp;gt; findAllByRoomId(String key) {
        long size = operationsHelper.size(generateChatLogKey(key));
        return operationsHelper.getList(generateChatLogKey(key), 0, size, ChatBubble.class);
    }

    public ChatBubble save(String key, ChatBubble chatBubble) {
        operationsHelper.add(generateChatLogKey(key), chatBubble);
        return chatBubble;
    }

    public int getLastPage(String key, int pageSize) {
        Long size = operationsHelper.size(generateChatLogKey(key));
        return (int) (size / pageSize);
    }

    public void clear(String key) {
        operationsHelper.delete(generateChatLogKey(key));
    }

    public List&amp;lt;ChatBubble&amp;gt; findAllBubbles() {
        return operationsHelper.popAll(chatBubbleProperties.getKey(), ChatBubble.class);
    }

    private String generateChatLogKey (String roomId) {
        return String.format(&quot;%s%s&quot;, chatBubbleProperties.getKey(), roomId);
    }
}&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;이제 이 구현체를 이용하여 service에서 Redis를 이용하여 쓰기 버퍼를 사용하기 위해 로직을 직접 구현해 주었습니다. 타입을 다르게하니 어노테이션으로는 안되기 때문에 직접 구현한 것입니다.&lt;/li&gt;
&lt;li&gt;캐싱하기로 한 다른 데이터들도 비슷하게 구현해보았습니다.&lt;/li&gt;
&lt;li&gt;그리고 주기적으로 캐시에 있는 데이터를 동기화 시킴으로써 `write-back`을 구현해보았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702049083558&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ChatBubbleService {

    private final ChatBubbleRepository bubbleRepository;
    private final ChatBubbleCache bubbleCache;
    private final ChatCacheProperties chatBubbleProperties;

	// 채팅 목록 조회
    @Transactional(readOnly = true)
    public Slice&amp;lt;ChatBubble&amp;gt; getChatBubbles(int page, String roomId) {
        String key = generateChatLogKey(roomId);
        Pageable pageable = PageRequest.of(page, chatBubbleProperties.getPageSize(), Sort.by(&quot;createdAt&quot;).descending());
        try {
            return bubbleCache.findAllByRoomId(key, pageable);
        } catch (IndexOutOfBoundsException e) { // 캐시에서 존재하지 않는 데이터 페이지를 조회하려고 하면 예외가 발생합니다.
        	page -= bubbleCache.getLastPage();
            Pageable pageable = PageRequest.of(page, chatBubbleProperties.getPageSize(), Sort.by(&quot;createdAt&quot;).descending());
            Slice&amp;lt;BubbleEntity&amp;gt; list = bubbleRepository.findAllByChatroomId(roomId, pageable);
            return list.map(BubbleEntity::toDomain);
        }
    }

    @Transactional
    public ChatBubble saveChatBubble(ChatBubble chatBubble) {
        String key = generateChatLogKey(chatBubble.getChatroomId());
        return bubbleCache.save(key, chatBubble); // 캐시에만 저장
    }

    private String generateChatLogKey (String roomId) {
        return String.format(&quot;%s%s&quot;, chatBubbleProperties.getKey(), roomId);
    }
}&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_1702049132734&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class ChatBubbleScheduledWorker {

    private final ChatBubbleCache chatBubbleCache;
    private final ChatBubbleRepository chatBubbleRepository;

    @Scheduled(cron = &quot;0 * * * * *&quot;) // 매 1분마다
    public void clearChatBubbleCache() throws JsonProcessingException {
        log.info(&quot; START : {} Thread clear chat bubble cache&quot;, Thread.currentThread().getId());
        List&amp;lt;ChatBubble&amp;gt; all = chatBubbleCache.findAllBubbles();

        if (all.isEmpty()) return;

        List&amp;lt;BubbleEntity&amp;gt; entities = all.stream().map(BubbleEntity::from)
                .collect(toList());
        chatBubbleRepository.saveAll(entities);
        log.info(&quot;  END : clear chat bubble cache = {}&quot;, entities.size());
    }
}&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;p data-ke-size=&quot;size16&quot;&gt;여기까지 실제로 캐싱을 공부하고 전략을 세워 어노테이션과 `RedisTemplate`을 통해 직접 구현해보니 이해가 빠르게 될 수 있었습니다.&lt;/p&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 data Redis, Spring Cache로 추상화 되어있는 것이 얼마나 소중한지(...)도 깨달을 수 있었습니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 포스트에서 언급되고 있는 전체 코드는 &lt;a href=&quot;https://github.com/masters2023-2nd-project-05/second-hand-BE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github&lt;/a&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 링크/자료&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1702082736009&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;Lessons Learned using Spring Data Redis - Salesforce Engineering Blog&quot; data-og-description=&quot;A low-code solution for CRUD operations with Redis when certain caching patterns exist.&quot; data-og-host=&quot;engineering.salesforce.com&quot; data-og-source-url=&quot;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&quot; data-og-url=&quot;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/U86VP/hyUIxvqBU4/kvBhV1nlzsjBNCJlTBqCS0/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/doUunr/hyUIvqRlx8/4FrVkdDKno5hFZfCWARM8K/img.png?width=1024&amp;amp;height=418&amp;amp;face=0_0_1024_418,https://scrap.kakaocdn.net/dn/bAzmIj/hyUIsgCAs9/snprQDZh5V5LkqbbSLvrl0/img.png?width=1024&amp;amp;height=411&amp;amp;face=0_0_1024_411&quot;&gt;&lt;a href=&quot;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://engineering.salesforce.com/lessons-learned-using-spring-data-redis-f3121f89bff9/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/U86VP/hyUIxvqBU4/kvBhV1nlzsjBNCJlTBqCS0/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/doUunr/hyUIvqRlx8/4FrVkdDKno5hFZfCWARM8K/img.png?width=1024&amp;amp;height=418&amp;amp;face=0_0_1024_418,https://scrap.kakaocdn.net/dn/bAzmIj/hyUIsgCAs9/snprQDZh5V5LkqbbSLvrl0/img.png?width=1024&amp;amp;height=411&amp;amp;face=0_0_1024_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;Lessons Learned using Spring Data Redis - Salesforce Engineering Blog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A low-code solution for CRUD operations with Redis when certain caching patterns exist.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;engineering.salesforce.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1702082758280&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 Data Redis Repository 미숙하게 사용해 발생한 장애 극복기&quot; data-og-description=&quot;Spring Data Repository를 성숙하지 못하게 사용하면서 발생한 장애를 공유합니다.&quot; data-og-host=&quot;hyperconnect.github.io&quot; data-og-source-url=&quot;https://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.html&quot; data-og-url=&quot;https://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/MEMi5/hyUIyHRyJf/mqMN15ODr9l7MhXrYk4Cy1/img.jpg?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631,https://scrap.kakaocdn.net/dn/dGxGAv/hyUIGeR3cE/kLvswIZEnDzbm6kTvCvxbK/img.jpg?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631,https://scrap.kakaocdn.net/dn/15hMi/hyUIFtuyH4/c3mSE34p5yXaLb0R8KkT6K/img.png?width=3654&amp;amp;height=1062&amp;amp;face=0_0_3654_1062&quot;&gt;&lt;a href=&quot;https://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hyperconnect.github.io/2022/12/12/fix-increasing-memory-usage.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/MEMi5/hyUIyHRyJf/mqMN15ODr9l7MhXrYk4Cy1/img.jpg?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631,https://scrap.kakaocdn.net/dn/dGxGAv/hyUIGeR3cE/kLvswIZEnDzbm6kTvCvxbK/img.jpg?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631,https://scrap.kakaocdn.net/dn/15hMi/hyUIFtuyH4/c3mSE34p5yXaLb0R8KkT6K/img.png?width=3654&amp;amp;height=1062&amp;amp;face=0_0_3654_1062');&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 Data Redis Repository 미숙하게 사용해 발생한 장애 극복기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data Repository를 성숙하지 못하게 사용하면서 발생한 장애를 공유합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hyperconnect.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://redis.io/docs/data-types/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redis.io/docs/data-types/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1702082868662&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;Understand Redis data types&quot; data-og-description=&quot;Overview of data types supported by Redis&quot; data-og-host=&quot;redis.io&quot; data-og-source-url=&quot;https://redis.io/docs/data-types/&quot; data-og-url=&quot;https://redis.io/docs/data-types/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://redis.io/docs/data-types/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://redis.io/docs/data-types/&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;Understand Redis data types&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Overview of data types supported by Redis&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;redis.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>⚙️ Frameworks, Libraries/  Spring</category>
      <category>cache</category>
      <category>redis</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/90</guid>
      <comments>https://new-pow.tistory.com/90#entry90comment</comments>
      <pubDate>Fri, 8 Dec 2023 23:28:44 +0900</pubDate>
    </item>
    <item>
      <title>2023-W48 회고 : 좋아하는 것을 찾는 방법</title>
      <link>https://new-pow.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 주에 한 것&lt;/b&gt;&lt;/h2&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;1359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y3wtA/btsBjdW9f6O/ghaJQcGqH4aj7UbQc4p3kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y3wtA/btsBjdW9f6O/ghaJQcGqH4aj7UbQc4p3kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y3wtA/btsBjdW9f6O/ghaJQcGqH4aj7UbQc4p3kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy3wtA%2FbtsBjdW9f6O%2FghaJQcGqH4aj7UbQc4p3kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;479&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1359&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;What is New?!&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;실시간 채팅 WIKI 작성 완료하였습니다. 드디어...! 그리고 동시에 리팩토링과 테스트를 다시 짜보고 있습니다.&amp;nbsp;&lt;a href=&quot;https://github.com/masters2023-2nd-project-05/second-hand/wiki/BE-%E2%80%90-%EC%A3%BC%EC%9A%94%EA%B8%B0%EB%8A%A5-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Link]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Spring AOP를 사용하여 Event publish 의 관심사를 수평으로 분리해보았습니다. 생각보다 신경쓸게 많아서 스트레스 받았어요... 관련된 글은 조만간 comming soon...!&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;What I Leanred&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;`후니의 쉽게 쓴 시스코 네트워킹` 1권 후기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 공부를 하기 위해 읽기 시작한 후니의 쉽게 쓴 시스코 네트워킹 책을 1권으로 마무리하고 다음 책으로 넘어가기로 했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/113485068&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;책 정보 : https://www.yes24.com/Product/Goods/113485068&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1701695952399&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;후니의 쉽게 쓴 시스코 네트워킹 - 예스24&quot; data-og-description=&quot;네트워크를 이렇게 쉽게 설명한 책은 없다! 20만 독자가 선택한 유일무이한 네트워크 최고의 입문서!2002년 출간 이후 16년 동안 네트워크 분야 베스트셀러 1위를 지키고 있는 『후니의 쉽게 쓴 시&quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;https://www.yes24.com/Product/Goods/113485068&quot; data-og-url=&quot;https://www.yes24.com/Product/Goods/113485068&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b5VGOb/hyUIEGVwMR/O0Mu4v7O8a9uKVC0jpRncK/img.jpg?width=931&amp;amp;height=1200&amp;amp;face=58_579_106_633,https://scrap.kakaocdn.net/dn/oQEVU/hyUEZzb9Kx/dG3HKfDqO5vUvnPLrjqLUk/img.jpg?width=931&amp;amp;height=1200&amp;amp;face=58_579_106_633,https://scrap.kakaocdn.net/dn/cit2cg/hyUIrOmvGq/Okw5AZMFJ5OYaPIKasOew1/img.jpg?width=931&amp;amp;height=1200&amp;amp;face=58_579_106_633&quot;&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/113485068&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yes24.com/Product/Goods/113485068&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b5VGOb/hyUIEGVwMR/O0Mu4v7O8a9uKVC0jpRncK/img.jpg?width=931&amp;amp;height=1200&amp;amp;face=58_579_106_633,https://scrap.kakaocdn.net/dn/oQEVU/hyUEZzb9Kx/dG3HKfDqO5vUvnPLrjqLUk/img.jpg?width=931&amp;amp;height=1200&amp;amp;face=58_579_106_633,https://scrap.kakaocdn.net/dn/cit2cg/hyUIrOmvGq/Okw5AZMFJ5OYaPIKasOew1/img.jpg?width=931&amp;amp;height=1200&amp;amp;face=58_579_106_633');&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;후니의 쉽게 쓴 시스코 네트워킹 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;네트워크를 이렇게 쉽게 설명한 책은 없다! 20만 독자가 선택한 유일무이한 네트워크 최고의 입문서!2002년 출간 이후 16년 동안 네트워크 분야 베스트셀러 1위를 지키고 있는 『후니의 쉽게 쓴 시&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10월 19일부터 11월 30일까지 약 5주동안 매주 1챕터씩 &lt;a href=&quot;https://flytrap.notion.site/fe3592afef3a473f88947519fd001f31?pvs=4&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;정들었던 이 파란 책을 떠나보내며 간단하게 후기를 남겨볼까합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 주로 OSI 7 layers 중 2~3 계층의 장비와 라우팅에 집중한 책입니다. 예시 그림이 풍부하고 최대한 쉬운 어휘를 사용한 작가의 노력 덕분에 네트워크 구성에 대해 잘 모르고 row level 네트워크에 대해 겁부터 나는 사람들이 읽기 시작하면 딱 좋겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 대부분의 내용을 실제 네트워크, 장비 설정이 차지하고 있기 때문에 실제로 네트워크 구성을 해보겠다는 니즈가 없이 가볍게 넘어간다면 굉장히 빠르게 읽을 수 있습니다. 다만 저는 동시에 여러 CS 서적들을 함께 읽고 스터디하느라 천천히 읽었습니다.&lt;/p&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 style=&quot;color: #333333; text-align: start;&quot;&gt;4계층 이후부터의 이야기가 너무 없어서&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;a href=&quot;https://m.yes24.com/Goods/Detail/45543957&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://m.yes24.com/Goods/Detail/45543957&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1701697121804&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;컴퓨터 네트워킹 하향식 접근 - 예스24&quot; data-og-description=&quot; &quot; data-og-host=&quot;m.yes24.com&quot; data-og-source-url=&quot;https://m.yes24.com/Goods/Detail/45543957&quot; data-og-url=&quot;https://m.yes24.com/Goods/Detail/45543957&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bilLic/hyUIymo6Hc/WbnlSvHafOLC9zMMh5NlK1/img.jpg?width=887&amp;amp;height=1200&amp;amp;face=0_0_887_1200,https://scrap.kakaocdn.net/dn/i28ns/hyUE3O7IvC/0HkAuJZzbvnaLRtaFdVMW1/img.jpg?width=887&amp;amp;height=1200&amp;amp;face=0_0_887_1200,https://scrap.kakaocdn.net/dn/IUDoj/hyUE3hkaj0/xe2xeRZwUyY0WWNzvJeWlk/img.jpg?width=887&amp;amp;height=1200&amp;amp;face=0_0_887_1200&quot;&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/45543957&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m.yes24.com/Goods/Detail/45543957&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bilLic/hyUIymo6Hc/WbnlSvHafOLC9zMMh5NlK1/img.jpg?width=887&amp;amp;height=1200&amp;amp;face=0_0_887_1200,https://scrap.kakaocdn.net/dn/i28ns/hyUE3O7IvC/0HkAuJZzbvnaLRtaFdVMW1/img.jpg?width=887&amp;amp;height=1200&amp;amp;face=0_0_887_1200,https://scrap.kakaocdn.net/dn/IUDoj/hyUE3hkaj0/xe2xeRZwUyY0WWNzvJeWlk/img.jpg?width=887&amp;amp;height=1200&amp;amp;face=0_0_887_1200');&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;컴퓨터 네트워킹 하향식 접근 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;m.yes24.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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;좋아하는 것을 찾는 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력서를 작성하고 지원하고 피드백하고의 반복인 취준생의 일상에서 정말 수많은 질문과 자기 의심들이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 '이걸 해봤어요' 하고 어필할 기술을 작성하고&amp;nbsp; 모난 곳이 없이 둥글둥글 이력서를 수정하다보면 어느새 평범하기 그지없는 이력서 A가 되는 것 같아 문득 불안감이 들더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동료 중에 &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;저는 객체지향과 처음만났던 국비교육 과정 5일차의 강렬한 기억을 얘기하며 &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;사실 원인과 결과가 모호한 관계라고 생각해요. 좋아하기 때문에 시간을 투여하기도 하지만 반대로 시간을 투여하기 때문에 좋아하게 되기도 하는 것이죠.&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;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;객체지향은 &amp;lsquo;재미&amp;rsquo;가 있습니다. 글쓰는 느낌같아요. 그리고 짜는 사람마다 결과가 달라진다는 것도요.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;친절한 코드 작성에 대하여&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 읽은 질답에 대해서 짧게 단상이 들어 작성합니다. &lt;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;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; href=&quot;https://www.inflearn.com/questions/1082553/configuration-%EA%B3%BC-componentscan?re_comment_id=297665&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@Configuration 과 @ComponentScan&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701771655917&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;@Configuration 과 @ComponentScan - 인프런 | 질문 &amp;amp; 답변&quot; data-og-description=&quot;@Configuration@ComponentScanpublic class HelloApplication { public static void main(String[] args) { AnnotationConfigServletWebApplicationCont...&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/questions/1082553/configuration-%EA%B3%BC-componentscan?re_comment_id=297665&quot; data-og-url=&quot;https://www.inflearn.com/questions/1082553/configuration-%EA%B3%BC-componentscan?re_comment_id=297665&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YAzGA/hyUFbfuAnU/EgCNHrlrLhVhTkOvnNk4M1/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_462,https://scrap.kakaocdn.net/dn/dmv0Vs/hyUE3V14cX/VaPYuxqh7VBKfV466DtGL0/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_462,https://scrap.kakaocdn.net/dn/ckw2GG/hyUIvcg8li/YTc5ZglWRAXtRSOykPzzok/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/questions/1082553/configuration-%EA%B3%BC-componentscan?re_comment_id=297665&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/questions/1082553/configuration-%EA%B3%BC-componentscan?re_comment_id=297665&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YAzGA/hyUFbfuAnU/EgCNHrlrLhVhTkOvnNk4M1/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_462,https://scrap.kakaocdn.net/dn/dmv0Vs/hyUE3V14cX/VaPYuxqh7VBKfV466DtGL0/img.jpg?width=1200&amp;amp;height=628&amp;amp;face=751_416_794_462,https://scrap.kakaocdn.net/dn/ckw2GG/hyUIvcg8li/YTc5ZglWRAXtRSOykPzzok/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781');&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;@Configuration 과 @ComponentScan - 인프런 | 질문 &amp;amp; 답변&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;@Configuration@ComponentScanpublic class HelloApplication { public static void main(String[] args) { AnnotationConfigServletWebApplicationCont...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.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;짧게 요약하자면 `@ComponentScan` 에는 `@Configuration` 을 붙이지 않아도 스스로 구성 요소 스캔을 하는데, 왜 붙이는 것일까? 에 대한 질문이었고 이에 대한 토비님의 답변이었는데요. 답변에서 그의 철학을 느낄 수 있었던 부분이 있어 발췌하였습니다.&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;첫째는 애노테이션을 기능이 아니라 주석으로 생각한다면 이 클래스는 스프링의 빈이고 그 중에서도 구성정보를 자바 코드로 다루는 것임을 나타내기 위해서입니다. 애플리케이션 구조 전반에 적용되는 @ComponentScan을 일반 컨트롤러 클래스 같은데 붙이지 않죠. 자연스럽게 @Configuration 빈으로 정의된 클래스에 나오게 됩니다. 그래서 스프링부트의 애노테이션도 부트스트래핑으로 직접 등록되는 빈 클래스라고 하더라도 @Configuration을 붙이게 됩니다.&lt;br /&gt;&lt;br /&gt;두 번째는 이게 멀티 모듈로 나눠지는 경우 해당 모듈의 루트가 되는 클래스이고 하위 패키지에서 빈 클래스를 찾아서 등록시키기 위해 @ComponentScan가 붙어있더라도 부트의 부트스트래핑 클래스가 아니게 될 수 있습니다. 물론 이때도 @Import나 자동구성에 의해서 등록될 수도 있긴하지만 register로 등록되는 건 아니겠죠. 설명을 적고 보니, 결국 질문하신 내용에 포함이 될 수도 있겠네요.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다음 주에 할 것&lt;/b&gt;&lt;/h2&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;1119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPLJ0d/btsBitzhqzf/hAKDDcW4vKkvyJKlGfPk0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPLJ0d/btsBitzhqzf/hAKDDcW4vKkvyJKlGfPk0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPLJ0d/btsBitzhqzf/hAKDDcW4vKkvyJKlGfPk0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPLJ0d%2FbtsBitzhqzf%2FhAKDDcW4vKkvyJKlGfPk0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;377&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <category>log</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/89</guid>
      <comments>https://new-pow.tistory.com/89#entry89comment</comments>
      <pubDate>Wed, 6 Dec 2023 19:40:17 +0900</pubDate>
    </item>
    <item>
      <title>2023-W47 회고</title>
      <link>https://new-pow.tistory.com/87</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;/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;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfJ74c/btsAZ90IvIg/nL6wtqlR22j7FdeEMgSLM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfJ74c/btsAZ90IvIg/nL6wtqlR22j7FdeEMgSLM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfJ74c/btsAZ90IvIg/nL6wtqlR22j7FdeEMgSLM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfJ74c%2FbtsAZ90IvIg%2FnL6wtqlR22j7FdeEMgSLM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;469&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;What is New?!&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;Spring 의 특징인 PSA에 대해서 공부했습니다.&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; &amp;zwj;  What I leared&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일은 작게 하자, 커밋은 한 단위를 다 포함시키게 하자.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 작업기간이 모두 끝나고 문서만 정비하고 있는 프로젝트가 있는데 정말 오랜만에 팀원과 만나서 문서화 겸 코드를 수정하여 브랜치를 합칠 일이 생겼습니다. 그런데 그 중간중간 저는 한 브랜치에다 주마다 1개씩 코드 수정사항을 쌓고 있었거든요. 함께 하는 백엔드 팀원은 프로젝트를 진행하지 않아서 피드백이 없었고, 합치기 뭐해서 그냥 옆에다가 쌓아둔 것이죠. 그렇게 커진 브랜치는 본 브랜치와 합치기 매우 힘들었습니다. 엄청난 충돌이 났거든요. 그래서 그냥 hard push로 origin을 모두 덮어 써 버렸어요.&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;&lt;b&gt;한 커밋에는 작업 단위에 대한 모든 변경사항을 다 넣어야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 내가 파일의 이름을 바꾸었다면, 바꾸기 전과 후를 한 커밋에 넣습니다. pull을 받는 입장에서는 커밋 단위로 작업을 비교하기 때문에 이름변경에 대해 1회 처리할 것을 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;커밋과 PR은 작은 단위로 해보자.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업하는 동료나 미래의 내가 작업의 흐름을 따라올 수 있을 정도로 커밋을 작성합니다. 그리고 프로젝트에는 약 400줄 내외의 PR을 올립니다. 근데 다른 현업 팀의 예시를 들어보니 200줄 내외의 PR단위를 쓰기도 하시더라고요. 코드 리뷰 받기도 쉽고, 변화를 빠르게 적용하여 내 코드에서 사용하기 편해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 주제와 비슷하게 취업 준비 스터디에서 추천받은 글 있습니다. &lt;a href=&quot;https://dora.dev/devops-capabilities/process/working-in-small-batches/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Working in small batches&amp;nbsp; (&lt;/a&gt;그리고 이 아티클을 바로 번역해준 동료의 블로그 글도 링크합니다 &lt;a href=&quot;http://%20DORA | DevOps Capabilities: Working in Small Batches  dora.dev&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(번역 및 정리) Working In Small Batches&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;소규모 단위 작업했을 때의 장점&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;1. 변경 사항에 대한 피드백을 받는 데 걸리는 시간이 줄어듭니다. 문제를 더 쉽게 분류하고 해결할 수 있습니다.&lt;br /&gt;2. 효율성과 동기부여가 향상됩니다.&lt;br /&gt;3. 소속한 팀이 &lt;b&gt;매몰 비용 오류&lt;/b&gt;*에 빠지는 것을 방지합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;* 매몰비용 오류란, 지금 여기까지 했으니 이대로 하자! 라는 태도이겠죠? 그 길이 정답이 아닐지라도 종종 그렇게 의사결정을 하기도합니다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;소규모 단위 작업하는 방법&lt;br /&gt;&lt;/b&gt;INVEST 원칙의 Agile 개념을 따릅니다.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;- &lt;b&gt;Independent &lt;/b&gt;&lt;/b&gt;작업 배치를 최대한 독립적으로 만들어 구현/배포 순서에 독립적으로 배포하고 검증할 수 있도록 합니다.&lt;b&gt;&lt;b&gt;&lt;br /&gt;- &lt;b&gt;Negotiable &lt;/b&gt;&lt;/b&gt;&lt;/b&gt;각 작업 배치는 반복 가능하고, 피드백을 받으며 재협상(수정)할 수 있습니다.&lt;b&gt;&lt;b&gt;&lt;br /&gt;- &lt;b&gt;Valuable &lt;/b&gt;&lt;/b&gt;&lt;/b&gt;개별 작업 배치는 유의미하며, 이해 관계자들에게 가치를 제공합니다. (무의미한 작업을 나누지 않습니다.)&lt;b&gt;&lt;b&gt;&lt;br /&gt;- &lt;b&gt;Estimable &lt;/b&gt;&lt;/b&gt;&lt;/b&gt;쉽게 추정할 수 있는 작업 배치에 대한 충분한 정보가 있습니다.&lt;b&gt;&lt;b&gt;&lt;br /&gt;- &lt;b&gt;Small &lt;/b&gt;&lt;/b&gt;&lt;/b&gt;작은 시간 단위로 일괄 작업을 완료할 수 있어야 합니다.&lt;b&gt;&lt;b&gt;&lt;br /&gt;- &lt;b&gt;Testable &lt;/b&gt;&lt;/b&gt;&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;핵심은 UI레이어가 아닌 서비스나 API레이어에서 개발을 시작하는 것입니다. UI 레이어는 작업 단위를 나누기에 너무 크기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 주의할 점이 있습니다. 작업을 충분히 작은 조각으로 나누는 것과 이 작은 단위를 모아서 릴리즈 단위로 테스트를 실행하기 위해 그룹화 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일의 방식을 바꾸는 것은 물론 비용이 많이 듭니다. 구성원들의 공감대도 필요하고요. 특히나 저처럼 지금 리더가 따로 없는 프로젝트를 할 때는 그 공감대가 굉장히 중요합니다. 다음 프로젝트를 진행할 때 미리 공유해보고 소규모로 문제를 쪼개고 해결해보는 경험을 해보는 것도 좋을 것 같다는 생각을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제의 엣지 케이스를 찾아내자&lt;/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;h3 data-ke-size=&quot;size23&quot;&gt;재능 있는 척 하지 않기&amp;nbsp;&lt;br /&gt;그리고 대충 아는 척 하지 않기 BUT&amp;nbsp;아는 것은 나누기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 인상깊게 읽은 블로그 글이 있습니다. &lt;a href=&quot;https://jojoldu.tistory.com/743&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;글의 요지는 '노력하지 않아도 잘 하는 척'을 했을 때의 단점에 대해서 였습니다.&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;예전 동료 중에 약 20년간 검도를 꾸준히 해온 사람이 있었는데, 그를 보고 꾸준함이 가장 큰 재능이라는 생각이 들었거든요. 제가 성실하지 않다고 생각했기 때문에 늘 그런 사람들을 존경하고 부러워하기 시작했던 것 같아요.&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;여기에 더해서 생각한 것은 &quot;대충 아는 척 덜하기&quot;입니다.&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;부트캠프를 다닐 때는 제 말버릇이 &quot;저 잘 모르는 거에요. 어떤거에요? 알려주세요.&quot; 였는데요. 물론 진짜 모르는 개념을 만났을 때 이런 이야기를 하기도 했지만, 어설프게 알고 있을 때도 &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;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;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfw7rv/btsASSTPzXt/ir7pXhqHhCSUd3SuCC61i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfw7rv/btsASSTPzXt/ir7pXhqHhCSUd3SuCC61i1/img.png&quot; data-alt=&quot;취준 모임에서 하는 것. 다들 보드가 하나씩 있어서 구경하는 재미가 있습니다 ✨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfw7rv/btsASSTPzXt/ir7pXhqHhCSUd3SuCC61i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdfw7rv%2FbtsASSTPzXt%2Fir7pXhqHhCSUd3SuCC61i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;1448&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1448&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;매일 SoD와 EoD를 하고 매주 블로그 글을 작성하고 공유하고 매주 토요일 아침에 만나 한주간의 회고를 하 곤 합니다.&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;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;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiHjmQ/btsASqDpLrq/gbu9YTSM3wi5eO8YxKB1Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiHjmQ/btsASqDpLrq/gbu9YTSM3wi5eO8YxKB1Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiHjmQ/btsASqDpLrq/gbu9YTSM3wi5eO8YxKB1Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiHjmQ%2FbtsASqDpLrq%2Fgbu9YTSM3wi5eO8YxKB1Ok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;398&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <category>log</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/87</guid>
      <comments>https://new-pow.tistory.com/87#entry87comment</comments>
      <pubDate>Sun, 26 Nov 2023 21:46:09 +0900</pubDate>
    </item>
    <item>
      <title>Cache를 적용하여 Read API 기능을 개선해보자 (1) 이론학습</title>
      <link>https://new-pow.tistory.com/86</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 글은 당근마켓을 모티브로 한 프로젝트 &lt;a href=&quot;https://github.com/masters2023-2nd-project-05/second-hand-BE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Secondhand&lt;/a&gt;&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;사실 처음 시작은 조회수 구현을 어떻게 효율적으로 할 수 있을까에서 출발하였습니다. &lt;a href=&quot;https://webisfree.com/2017-11-13/redis%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-view-count-%EB%B0%A9%EB%AC%B8%EC%9E%90-%EC%88%98-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%ED%9A%A8%EA%B3%BC%EC%A0%81%EC%9D%B8-%EB%B0%A9%EB%B2%95%EC%9D%80&quot;&gt;[조회수 관련해서 참고한 블로그 글]&lt;/a&gt; 세션별로 중복되지 않게 일정 시간동안 1회씩 카운팅하고 싶은 욕심이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 학습을 하다보니 레디스 Cache를 읽기 API에서도 사용해보고 싶더라고요  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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Cache에 대하여&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 Cache는 자주 사용하는 연산에 대하여 속도가 빠른 임시 공간에 저장해두어 애플리케이션 연산 속도를 높일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 연산 속도는 DB I/O작업을 얼마나 줄이느냐에 비례하는데요. 캐시를 사용해서 DB는 물론, 어플리케이션 연산도 skip할 수가 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Cache는 나중에 요청을 위해 결과를 미리 저장해두었다가 빠르게 서비스 해주는 것을 의미합니다.&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?si=LEAY_WRG-07JMvPm&amp;amp;v=mPB2CZiAkKM&amp;amp;feature=youtu.be&amp;amp;themeRefresh=1&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;애플리케이션이 Database(Disk)에 접속하여 데이터를 읽어오는 것보다는 자주 읽는 데이터를 애플리케이션이 빠르게 접근할 수 있는 메모리에 올려두고 빠르게 가져와 사용하는 것이지요. 창고에 있는 데이터를 책상에 올려두는 것과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비단 읽는 것뿐만 아니라 쓰기작업 혹은 연산 작업에도 적용됩니다. 다이나믹 프로그래밍을 생각해보세요. 중간 연산 결과를 계속 캐싱하여 다음 연산을 실행한다면 매번 처음부터 연산을 하는 것보다 훨씬 빠를 겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3qfzU/btsAUn51eci/ujXN4U2FyJxW7CZyKipoAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3qfzU/btsAUn51eci/ujXN4U2FyJxW7CZyKipoAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3qfzU/btsAUn51eci/ujXN4U2FyJxW7CZyKipoAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3qfzU%2FbtsAUn51eci%2FujXN4U2FyJxW7CZyKipoAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;230&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Redis를 이용해서 cache를 구현합니다. 하지만 캐시 구현에 가장 손쉬운 방법은 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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;헷갈리는 Cache와 Buffer의 차이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 두 용어의 차이점을 정확하게 알지 못하기 때문에 종종 이 용어를 횬용해서 쓰기도 해서 간단하게 짚고 넘어가겠습니다. (제 얘기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 엄밀히 말하자면 다른 의미를 갖고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;캐시와 버퍼는 둘 다 데이터를 임시로 저장하는 데 사용됩니다.&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;Buffer는 전통적으로 빠른 속도의 장치와 느린 속도의 장치 사이에서 데이터를 일시적으로 보관하는 데 사용합니다. 빠른 속도의 장치에서 느린 속도의 장치로 데이터를 보낼 때 데이터의 유실, 손실 상황을 막기 위함입니다. 느린 속도의 장치가 이 버퍼 데이터를 받을 때는 한번에 받도록 하여 이 속도 차이를 완화시킵니다. 그러다보니 데이터는 버퍼에서 한 번만 쓰거나 읽을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 컴퓨터에서 입력장치인 키보드의 데이터를 CPU가 받는다고 할 때, 키보드 버퍼에 데이터를 잠시 저장하거나 네트워킹에서 다른 장치로 데이터가 이동할 때 잠시 저장해두는 용도로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Cache` 의 경우, 성능 향상에 목적이 있습니다. 캐시는 자주 사용하는 정보, 데이터(값비싼 연산결과나 자주 참조되는 데이터)를 메모리에 올려두고 사용할 수 있는 저장소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 I/O보다 캐시 메모리에 올라간 데이터를 읽는 것이 훨씬 빠르기 때문입니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;언제 사용해야 좋을까요?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시는 애플리케이션 전반에서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 캐시가 없는 애플리케이션 서버가 동일한 API 요청을 N번 받으면 매번 DB 조회 후에 모든 로직을 거쳐 반환하는 과정을 겪어야 겠지요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oMFGq/btsARjxiwSG/rxMEbmgt3Yab3Cn12pRqLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oMFGq/btsARjxiwSG/rxMEbmgt3Yab3Cn12pRqLk/img.png&quot; data-alt=&quot;대표적인 사용 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oMFGq/btsARjxiwSG/rxMEbmgt3Yab3Cn12pRqLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoMFGq%2FbtsARjxiwSG%2FrxMEbmgt3Yab3Cn12pRqLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;301&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1002&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;캐시의 장점은 데이터베이스에 직접 조회하는 것보다 빠른 것뿐만 아니라 데이터베이스의 부하를 줄일 수 있다는 점도 있습니다. 같은 API로직을 10번 요청받았을 때, 최초 1회만 DB에 접근하​면 되니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;캐시에 유리한 데이터&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시에 들어갈 데이터는 자주 사용되지만 변경은 자주 일어나지 않는 것이 유리합니다.&lt;/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;다만 조회는 잦으면서 변함이 별로 없는 캐시라면 TTL(Time To Live)을 길게해서 메모리에 올려두면 더 좋습니다.&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;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HgVM2/btsAQcrDzjd/KMDHDvJ9Yh7b5xeCCUrmDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HgVM2/btsAQcrDzjd/KMDHDvJ9Yh7b5xeCCUrmDk/img.png&quot; data-alt=&quot;아무것도 모를 때 채팅을 캐싱해보겠다며 팀 메이트와 그려본 플로우 차트  &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HgVM2/btsAQcrDzjd/KMDHDvJ9Yh7b5xeCCUrmDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHgVM2%2FbtsAQcrDzjd%2FKMDHDvJ9Yh7b5xeCCUrmDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;450&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;캐시를 사용하기 전에 선택할 것들&lt;/b&gt;&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Local cache와 Global Cache&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시를 WAS(Web Application Server) 에 저장하는 방식인 Local cache와 별도 캐시 서버를 구축하는 Global Cache 방식 두가지가 있는데요. &lt;a href=&quot;https://medium.com/uplusdevu/%EB%A1%9C%EC%BB%AC-%EC%BA%90%EC%8B%9C-%EC%84%A0%ED%83%9D%ED%95%98%EA%B8%B0-e394202d5c87&quot;&gt;  Local cache 참고링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트의 경우에는 Scale out 가능성이 있다는 가정을 하고 있으며, 이미 Redis pub/sub으로 채팅 서비스를 구현한 상태이기 때문에 자연스럽게 Redis cache를 이용하여 Global Cache 를 사용하기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local cache의 경우, WAS 인스턴스 메모리에 데이터를 저장하므로 속도가 매우 빠르고 별도 인프라를 구축할 필요가 없어서 선택하는 경우도 있습니다.&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;캐시 읽기/쓰기 전략과 TTL&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 읽기 전략과 쓰기 전략에는 여러가지 종류가 있었는데요. 데이터베이스와 캐시에서 어떻게 데이터를 가져올지에 대한 전략입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱할 API의 성격과 환경에 따라 선택해야 했습니다. 해당 전략에 대해서는 이 &lt;a href=&quot;https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG200&quot;&gt;링크&lt;/a&gt;를 참고하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 프로젝트는 Read API에 대해서 Read through 정책을, Write API에 대해서는 Write back정책을 선택했습니다. API 성능 개선도 목표였지만 결국 가장 큰 목표는 캐시에 대한 학습과 구현에 있었기 때문에 코드를 직접 쓰기도 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UnKHa/btsAQzNKfI0/Qaks07B4op96ampC5Cv6Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UnKHa/btsAQzNKfI0/Qaks07B4op96ampC5Cv6Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UnKHa/btsAQzNKfI0/Qaks07B4op96ampC5Cv6Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUnKHa%2FbtsAQzNKfI0%2FQaks07B4op96ampC5Cv6Tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2388&quot; height=&quot;1668&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&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;b&gt;Read API에 적용해보자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 우선적으로 캐싱 할 API로는 다음을 염두에 두고 있었는데요. 이유는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 상품판매 목록 조회&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;/li&gt;
&lt;li&gt;여러 데이터를 조회해야하므로 쿼리가 복잡한 편&lt;/li&gt;
&lt;li&gt;다만 데이터의 수정이 잦은 편이기 때문에 TTL을 짧게 해두는 것이 좋을 것 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 지역 목록 조회&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;키워드 검색 결과 (key를 키워드를 붙여서 적용)는 프론트에서 수시로 보내는 요청 (사용자가 타이핑을 하는 동안 여러번 요청이 온다.)&lt;/li&gt;
&lt;li&gt;데이터가 변함이 없는 편&lt;/li&gt;
&lt;li&gt;TTL을 조금 길게 해두어도 좋을 것 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 채팅 로그와 채팅방 메타 데이터&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;/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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Cache Abstraction&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 적용하려고 자료를 찾아보니 Spring Framework는 캐시 추상화가 되어있기 때문에 어노테이션을 기반으로 손쉽게 구현할 수 있었습니다. 스프링 3.1 버전부터 &lt;span style=&quot;text-align: start;&quot;&gt;기존 Spring 애플리케이션에 캐싱을 투명하게 추가할 수 있는 지원을 제공해왔는데,&amp;nbsp; &lt;span style=&quot;text-align: start;&quot;&gt;캐싱 추상화를 통해 코드에 미치는 영향을 최소화하면서 다양한 캐싱 솔루션을 일관되게 사용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #fafafa; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 인터페이스를 제공하고 다른 구현체들이 구체적인 로직을 수행하는 것입니다.
org.springframework.cache.*&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;text-align: start;&quot;&gt;트랜잭션이나 Async처럼 선언적인 어노테이션으로 코드를 바꾸지 않고도 사용할 수 있었습니다.&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;b&gt;@Cacheable&lt;/b&gt;: Triggers cache population.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@CacheEvict&lt;/b&gt;: Triggers cache eviction.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@CachePut&lt;/b&gt;: Updates the cache without interfering with the method execution.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Caching&lt;/b&gt;: Regroups multiple cache operations to be applied on a method.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@CacheConfig&lt;/b&gt;: Shares some common cache-related settings at class-level.&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;b&gt;설정하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1700873428785&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
@RequiredArgsConstructor
public class CacheConfig {

    private final RedisConnectionFactory redisConnectionFactory;
    private final ObjectMapper objectMapper;

    @Bean
    public CacheManager redisCacheManager() {
        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory)
                .cacheDefaults(defaultCacheConfiguration())
                .withInitialCacheConfigurations(CacheConfigurations())
                .build();
    }

    @Bean(name = &quot;defaultCacheConfiguration&quot;)
    public RedisCacheConfiguration defaultCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofSeconds(3))
                .disableCachingNullValues();
    }

    @Bean
    public Map&amp;lt;String, RedisCacheConfiguration&amp;gt; CacheConfigurations() {
        return Map.of(
                CacheKey.MEMBER, defaultCacheConfiguration().entryTtl(Duration.ofSeconds(5)),
                CacheKey.ITEM, defaultCacheConfiguration().entryTtl(Duration.ofSeconds(3)),
                CacheKey.REGION, defaultCacheConfiguration().entryTtl(Duration.ofSeconds(100))
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인별로 용도가 조금씩 달라서 TTL을 다르게 설정해주기 위해 CacheConfiguration Map을 Cache manager에게 전달하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이름붙인 것은 예를 들어 이런식으로 적용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700872099011&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@CacheConfig(cacheNames = CacheKey.REGION)
@RequiredArgsConstructor
public class RegionService implements GetValidRegionsUsecase {

    private final RegionRepository regionRepository;

	// 생략

    @Override
    @Transactional(readOnly = true)
    @Cacheable(key = &quot;#id&quot;)
    public Region getRegion(Long id) throws NotValidRegionException {
        return regionRepository.findById(id).orElseThrow(() -&amp;gt; new NotValidRegionException(&quot;해당하는 지역이 없습니다.&quot;));
    }

    @Transactional(readOnly = true)
    @Cacheable(key = &quot;#address&quot;)
    public List&amp;lt;Region&amp;gt; findRegionByAddress (String address) {
        return regionRepository.findAllByAddress(address);
    }
}&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;'강남'으로 주소 검색을 했을 때, 다음과 같이 Redis에 저장되는 것을 확인했습니다. DB access log도 1회만 찍히고 응답 속도도 100ms에서 10ms로 개선된 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2348&quot; data-origin-height=&quot;1612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Xcnq/btsAThekW5J/3RA7bvwc1Rcf94YDWHJmFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Xcnq/btsAThekW5J/3RA7bvwc1Rcf94YDWHJmFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Xcnq/btsAThekW5J/3RA7bvwc1Rcf94YDWHJmFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Xcnq%2FbtsAThekW5J%2F3RA7bvwc1Rcf94YDWHJmFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2348&quot; height=&quot;1612&quot; data-origin-width=&quot;2348&quot; data-origin-height=&quot;1612&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션을 사용하는 것으로는 채워지지 않은(..) 요구사항들이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Write back을 구현한다거나, redis 저장 타입을 바꾸고 싶다거나 하는 것들이요.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고링크&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;로컬 캐시 사용 사례 참고링크&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;/span&gt; &lt;a href=&quot;https://dev.gmarket.com/16&quot;&gt;https://dev.gmarket.com/16&lt;/a&gt;&lt;span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/uplusdevu/%EB%A1%9C%EC%BB%AC-%EC%BA%90%EC%8B%9C-%EC%84%A0%ED%83%9D%ED%95%98%EA%B8%B0-e394202d5c87#0083&quot;&gt;&lt;span&gt;로컬 캐시 선택하기&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;캐시 전략에 대한 참고링크&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG199&quot;&gt;&lt;span&gt;Selecting a Cache Strategy&lt;/span&gt;&lt;/a&gt;Spring docs
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/cache.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/cache.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html#cache-annotations-config&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html#cache-annotations-config&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-boot-ehcache&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.baeldung.com/spring-boot-ehcache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-cache-tutorial&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.baeldung.com/spring-cache-tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/redis/reference/redis.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-data/redis/reference/redis.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;FreeStarVideoAdContainer&quot;&gt;
&lt;div id=&quot;freestar-video-parent&quot;&gt;
&lt;div id=&quot;freestar-video-child&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>⚙️ Frameworks, Libraries/  Spring</category>
      <category>cache</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/86</guid>
      <comments>https://new-pow.tistory.com/86#entry86comment</comments>
      <pubDate>Fri, 24 Nov 2023 23:42:28 +0900</pubDate>
    </item>
    <item>
      <title>Error: Sqlite3 와 Hibernate 연동 문제</title>
      <link>https://new-pow.tistory.com/85</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생 상황&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 토이 프로젝트 세팅 중, DB를 무엇으로 쓸 지 정해지지 않은 상태에서 가볍게 SQLite를 써보자고 하는 중이었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(SQLite는 단일 파일 DB로서 h2와 경량화된 데이터베이스이며 인메모리로 활성화할 수 있다는 공통점이 있습니다. 학습의 용도로 SQLite를 시도해보기로했어요.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite와 Spring JPA가 연동되지 않는 문제가 있었습니다.&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;당시 yml&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1700310390218&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    driver-class-name: org.sqlite.JDBC
    url: jdbc:sqlite:sqlite-sample.db
  jpa:
    database-platform: org.hibernate.community.dialect.SQLiteDialect&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;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1700310415082&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.hibernate.HibernateException: Unable to determine Dialect for SQLite 3.43 (please set 'hibernate.dialect' or register a Dialect resolver)
	at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.determineDialect(DialectFactoryImpl.java:202) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.buildDialect(DialectFactoryImpl.java:86) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$1.execute(JdbcEnvironmentInitiator.java:358) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$1.execute(JdbcEnvironmentInitiator.java:280) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.jdbc.WorkExecutor.executeReturningWork(WorkExecutor.java:56) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.jdbc.AbstractReturningWork.accept(AbstractReturningWork.java:34) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:70) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:279) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:193) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:69) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:119) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:264) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:239) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:216) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.boot.model.relational.Database.&amp;lt;init&amp;gt;(Database.java:45) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:231) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.&amp;lt;init&amp;gt;(InFlightMetadataCollectorImpl.java:199) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:169) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1383) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1454) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]&lt;/code&gt;&lt;/pre&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;JPA와 DB가 연동 되려면 Dialect가 필요한데 기존에 지원하던 SQLite용 Dialect가 Hibernate 6 에서는 더이상 지원하지 않았기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Dialect 패키지에 대한 참고링크 : &lt;a href=&quot;https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/dialect/package-summary.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/dialect/package-summary.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700310638806&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;org.hibernate.dialect (Hibernate JavaDocs)&quot; data-og-description=&quot;Package org.hibernate.dialect Description This package abstracts the SQL dialect of the underlying database. A concrete Dialect may be specifed using hibernate.dialect.&quot; data-og-host=&quot;docs.jboss.org&quot; data-og-source-url=&quot;https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/dialect/package-summary.html&quot; data-og-url=&quot;https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/dialect/package-summary.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/dialect/package-summary.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/dialect/package-summary.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;org.hibernate.dialect (Hibernate JavaDocs)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Package org.hibernate.dialect Description This package abstracts the SQL dialect of the underlying database. A concrete Dialect may be specifed using hibernate.dialect.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.jboss.org&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;h2 data-ke-size=&quot;size26&quot;&gt;대처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hibernate-community-dialects 의존성을 build.gradle 에 추가해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700310790765&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    implementation 'org.xerial:sqlite-jdbc:3.43.2.2'
    implementation('org.hibernate.orm:hibernate-community-dialects:6.1.7.Final') // 이부분을 추가해 주었습니다.&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;The&amp;nbsp;hibernate-community-dialects&amp;nbsp;module comes with a SQLite dialect:&amp;nbsp;&lt;a href=&quot;https://github.com/hibernate/hibernate-orm/blob/main/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteDialect.java&quot; data-token-index=&quot;3&quot;&gt;hibernate-orm/SQLiteDialect.java at main &amp;middot; hibernate/hibernate-orm &amp;middot; GitHub&amp;nbsp;124&amp;nbsp;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Refs.&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://discourse.hibernate.org/t/sqlite-not-working-with-hibernate-6-2/8174/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://discourse.hibernate.org/t/sqlite-not-working-with-hibernate-6-2/8174/2&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gwenn/sqlite-dialect&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/gwenn/sqlite-dialect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discourse.hibernate.org/t/how-to-integrate-sqlite-with-spring-boot-having-hiberate-6/7538&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://discourse.hibernate.org/t/how-to-integrate-sqlite-with-spring-boot-having-hiberate-6/7538&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://calcite.apache.org/javadocAggregate/org/apache/calcite/sql/SqlDialect.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://calcite.apache.org/javadocAggregate/org/apache/calcite/sql/SqlDialect.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  bugs</category>
      <category>hibernate</category>
      <category>JPA</category>
      <category>sqlite</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/85</guid>
      <comments>https://new-pow.tistory.com/85#entry85comment</comments>
      <pubDate>Sat, 18 Nov 2023 21:40:25 +0900</pubDate>
    </item>
    <item>
      <title>2022년 회고 (3) 백엔드 개발자가 되겠어 ✊</title>
      <link>https://new-pow.tistory.com/84</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;케케묵은 지난글  &lt;br /&gt;&lt;a href=&quot;https://new-pow.tistory.com/23?ref=localhost&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2022년 회고 (1) 커뮤니티 기획자가 개발자를 꿈꾸게된 이야기&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://new-pow.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2022년 회고 (2) 풀스택 국비교육 수강기&lt;/a&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;매우 늦었고 내용이 별거 없어서... 조금 부끄럽지만...&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;시작한 글을 마무리해보겠다는 의지로 gitpage에 숨겨져있던 글을 끌고 왔습니다.&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;백엔드 개발자가 되어야겠다!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 즈음부터 백엔드 개발자가 되어야겠다는 결심을 했다. 왜 백엔드인가...&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 처음에는 나는 프론트엔드가 더 잘 맞는다고 느끼기도 했다. 이전에 기획자로서 했던 일도 이용자와 직접 접하며 생기는 인터액션에 대한 풍부한 상상이 필요했었기 때문에. 프론트엔드가 다루는 부분이 연장선으로 여겨져서 익숙했었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아무래도 처음 웹 개발을 접하다보니 가장 자주 마주하는 웹페이지가 편한 이유도 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 공부를 거듭할수록 보여지는 것에 구애받지 않고 온전히 비즈니스 로직에 집중할 수 있는 백엔드의 매력에 푹 빠졌다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;복잡한 문제를 잘 나누어서 효율적으로 처리할 수 있다는 점. 구현을 하는 방법이 매우 다양한데 그 중에서 적절한 방법을 찾는 것이 퍼즐을 푸는 것처럼 즐겁게 느껴졌다. 어플리케이션 외에도 데이터베이스나 네트워크 등 환경마다 효율적인 방식을 측정하고 선택하는 것도 매우 매력적이었다. 무엇보다 광범위한 부분을 이해하고 컨트롤 할 수 있다는 점도 성격에 잘 맞았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금도 이 결정은 내게 잘 맞는 선택을 했다는 생각이 든다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;그런데 왜 이렇게 알아야 하는 것이 많죠..  ?&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2022년 8월~12월 : CS 스터디 : 환멸의 계곡&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한편 8월부터 교육과정에서 만난 동료와 함께 스터디를 시작했다. 짧은 국비교육과정에서 다루지 않는 것들이 많다보니 그 부족함을 채우기 위함이었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6EMVV/btsAxZk0bUn/KwPQLS4ZCkMQYt1imUoHN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6EMVV/btsAxZk0bUn/KwPQLS4ZCkMQYt1imUoHN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6EMVV/btsAxZk0bUn/KwPQLS4ZCkMQYt1imUoHN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6EMVV%2FbtsAxZk0bUn%2FKwPQLS4ZCkMQYt1imUoHN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;325&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1247&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&gt;스터디 이름이 환멸의 계곡인 이유는... 환멸의 계곡에라도 빠졌으면 좋겠다는 의미였다   우리는 우매함의 봉우리를 올라가지도 못하는 실력이었으니까.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;(가트너의 하이프 사이클에서 Trough of Disillusionment 단계는 &quot;환멸의 계곡&quot; 이라고도 불린다.&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;이미지 출처는 위키백과 &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/하이프_사이클&quot;&gt;https://ko.wikipedia.org/wiki/하이프_사이클)&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;처음에는 자료구조와 알고리즘을 공부하며 코딩테스트를 대비해보자고 시작했지만 컴퓨터 공학을 전공하였던 동료의 리드에 맞추어 영역을 CS로 넓혀 공부했고 2명이라는 적은 인원에도 주 2회씩 총 45회차나 진행했으니 꽤 길게 이어나간 것이다. (&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/new-pow/cs-study?ref=localhost&quot;&gt;repository&lt;/a&gt;) 매주 각각 2~4개 주제를 공부해와서 하나씩 쌓아갔다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1065&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sFQ9M/btsAxGTABCn/4yue6kZR4IWtSKpN23k170/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sFQ9M/btsAxGTABCn/4yue6kZR4IWtSKpN23k170/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sFQ9M/btsAxGTABCn/4yue6kZR4IWtSKpN23k170/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsFQ9M%2FbtsAxGTABCn%2F4yue6kZR4IWtSKpN23k170%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;320&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1065&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 style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 이런 전문 지식을 잘 설명해줄 수 있는 누군가와 함께 공부한다는 그 자체만으로 많은 도움이 되었고, 의지가 되었던 스터디였다. 이 때 처음 배웠던 개념들이 이후에도 반복적으로 등장하면서 점점 이해도가 높아져가는 기반이 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 스터디는 아쉽지만 내가 다른 부트캠프를 듣기로 결정하면서 마무리하게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;코드스쿼드 프리코스&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2022년 11월 : 코드스쿼드 마스터즈 BE 프리코스&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 이때만해도 바로 취업을 하고 싶었다. 5.5개월동안 열심히 했으니 바로 취업할 수 있지않을까? 생각을 했는데. 실제로 취업한 사람들도 있었고.. 그런데 공부를 하면 할 수록 나의 부족한 점만 크게 느껴졌다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 이때쯤 코드스쿼드 프리코스​를 모집한다는 정보를 입수했다. 예전에 홈페이지에 방문해서 모집 알람신청을 해두었는데, 잊어버릴 때 즈음 알람이 온 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어차피 취준하느라 공백일 한 달. 가이드라인을 받아 다시 방향성도 잡고 루틴하게 공부시간도 가질겸 신청했다. 이때는 몰랐다 이 과정이 얼마나 많은 에너지를 필요로 하는지...&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당시 선택에 큰 도움이 되었던 블로그 글&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;figure id=&quot;og_1700307832280&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;[후기] 코드스쿼드 2022 마스터즈 코스 수료 (웹 백엔드)&quot; data-og-description=&quot;후 길다면 길었고, 짧다면 짧은 6개월이 금방 지나가 버렸다~ (위 닌자 로고의 마지막 기수 ㅎㅎ 2022 과정 중간에 매듭? 같은 걸로 변경됐는데... 개인적으로 별로.... 뭐랄까... 코드스쿼드 멤버들&quot; data-og-host=&quot;blogshine.tistory.com&quot; data-og-source-url=&quot;https://blogshine.tistory.com/459?ref=localhost&quot; data-og-url=&quot;https://blogshine.tistory.com/459&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/17NQH/hyUyz7Dq5G/ZLarq21VvKRXkIAebRkEgK/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/ilVIj/hyUyAMe2aB/roczUGVIJnS27f1BMWMAr1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/bnjqDs/hyUywwilEQ/1AryPxJzwtgaLPBdkjJhE1/img.png?width=2200&amp;amp;height=1216&amp;amp;face=0_0_2200_1216&quot;&gt;&lt;a href=&quot;https://blogshine.tistory.com/459?ref=localhost&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blogshine.tistory.com/459?ref=localhost&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/17NQH/hyUyz7Dq5G/ZLarq21VvKRXkIAebRkEgK/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/ilVIj/hyUyAMe2aB/roczUGVIJnS27f1BMWMAr1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/bnjqDs/hyUywwilEQ/1AryPxJzwtgaLPBdkjJhE1/img.png?width=2200&amp;amp;height=1216&amp;amp;face=0_0_2200_1216');&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;[후기] 코드스쿼드 2022 마스터즈 코스 수료 (웹 백엔드)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;후 길다면 길었고, 짧다면 짧은 6개월이 금방 지나가 버렸다~ (위 닌자 로고의 마지막 기수 ㅎㅎ 2022 과정 중간에 매듭? 같은 걸로 변경됐는데... 개인적으로 별로.... 뭐랄까... 코드스쿼드 멤버들&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blogshine.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;figure id=&quot;og_1700307891887&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;(2021) 1. 비전공자로 자바 백엔드 개발자 시작하기&quot; data-og-description=&quot;저는 개인적으로 이런 이야기를 하는 것을 썩 좋아하진 않습니다. 어떤 사람의 커리어나, 그 사람의 현재 위치는 운이 굉장히 큰 영향을 끼쳤다고 믿기 때문입니다. 그 사람이 했던 방식, 했던 &quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/505?ref=localhost&quot; data-og-url=&quot;https://jojoldu.tistory.com/505&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HO7nD/hyUyrIxbdd/y30fJj0zkV1f99C7zS8eG0/img.png?width=800&amp;amp;height=418&amp;amp;face=0_0_302_330,https://scrap.kakaocdn.net/dn/QKQB2/hyUysHqcBg/tVJNMk3y7ysylYyY918sU0/img.png?width=800&amp;amp;height=418&amp;amp;face=0_0_302_330,https://scrap.kakaocdn.net/dn/c1qfBa/hyUytTQcKc/whoFnKfGfUB0Q7iiTWLvL1/img.png?width=1676&amp;amp;height=876&amp;amp;face=0_0_633_691&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/505?ref=localhost&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jojoldu.tistory.com/505?ref=localhost&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HO7nD/hyUyrIxbdd/y30fJj0zkV1f99C7zS8eG0/img.png?width=800&amp;amp;height=418&amp;amp;face=0_0_302_330,https://scrap.kakaocdn.net/dn/QKQB2/hyUysHqcBg/tVJNMk3y7ysylYyY918sU0/img.png?width=800&amp;amp;height=418&amp;amp;face=0_0_302_330,https://scrap.kakaocdn.net/dn/c1qfBa/hyUytTQcKc/whoFnKfGfUB0Q7iiTWLvL1/img.png?width=1676&amp;amp;height=876&amp;amp;face=0_0_633_691');&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;(2021) 1. 비전공자로 자바 백엔드 개발자 시작하기&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;jojoldu.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;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4%EC%97%90%EC%84%9C-%EB%B0%B0%EC%9B%A0%EB%8D%98-%EA%B2%83%EB%93%A4&quot; style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프리코스에서 배웠던 것들&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한참 코로나로 인한 거리두기 중일 때라 프리코스는 온라인으로 진행되었다. 클래스의 개념, JVM의 메모리 구조, 힙 메모리와 스택 메모리부터 차근차근 배우기 시작했는데 그동안 기능 구현을 중심으로 학습해왔던 나에게는 신세계이자 이해하기 어려운 내용들이었다. 이런 개념들을 어떻게 학습해야 할 지 학습 방법부터 새로 배워야 하는 것들이 많았다  &lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음이 프리코스 4주 과정동안 에서 주로 배웠던 것들이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&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;특히 지금도 심취해있는 객체지향에 대한 짝사랑이 이때부터 본격적으로 시작되었던 것 같다 ... 하하&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;1224&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3nV7N/btsAAM55bTB/t6ipoWbBHA0C5hYIVbaQs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3nV7N/btsAAM55bTB/t6ipoWbBHA0C5hYIVbaQs1/img.png&quot; data-alt=&quot;코드스쿼드 프리코스 1주차 회고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3nV7N/btsAAM55bTB/t6ipoWbBHA0C5hYIVbaQs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3nV7N%2FbtsAAM55bTB%2Ft6ipoWbBHA0C5hYIVbaQs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;464&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;1136&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 위의 내용과 함께 얻을 수 있었던 것은 학습하는 방향과 습관이다. 어떤 것을 더 잘 알아야하는지에 대한 학습 방향 가이드라인이 있었고 오후부터 시작하는 코어타임에 동료들과 학습한 내용을 공유하는 것도 방향을 잡는데 큰 도움이 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드스쿼드는 수강생들은 자유롭게 방목한 채로 스스로 성장하게 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이게 이전 국비교육을 들었을 때와 가장 큰 차이점이었다. 하루 코어타임이 8시간이라고 해도 짜여진 스케줄은 별로 없고 코어타임동안 자유롭게 공부하고, 필요할 때 동료들과 대화하며 부족한 부분을 채워나갔다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 방법은 함께하는 동료들이 매우 중요하기 때문에 운이 따르고, 이 방식이 별로 안맞는 사람들도 많은데다 기본적으로 학습에 대한 자신만의 방법이 있어야 가능한 방법이라 모든 사람에게 적합한 교육 방식일지는 잘 모르겠지만. &lt;span style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot;&gt;나에게는 이 스타일이 꽤 잘맞았다  &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%8D%94-%EA%B3%B5%EB%B6%80%EB%A5%BC-%ED%95%98%EA%B8%B0%EB%A1%9C-%ED%96%88%EB%8B%A4&quot; style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;그리고 더 공부를 하기로 했다.&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 영역이든 이런 어려움이 따를테지만, 개발을 배우면서 가장 괴로운 점은 분명히 나는 공부를 하고 있다고는 하는데 모르는 것이 지수적으로 증가한다는 것이다. 심지어 이미 공부를 한 내용도 사실 정확하게 알고 기억하기란 어려웠다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 아이러니하게도 동시에 이것이 가장 즐거운 점이기도 하다. 내가 모르는 것이 계속 존재하고 개념과 기술의 꼬리를 물며 학습하고, 이전에 학습했던 것을 기반으로 직접 구현할 수 있다는 것이 새로운 세계를 탐험하는 것 같았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 때 당시 내게 필요했던 것은 이 탐험을 어떻게 스스로 할 수 있느냐에 대한 연습이었던 것 같다. 내게는 그 연습시간이 더 많이 필요했고 당시에 바로 취업하기보다는 더 공부하고 싶었다. 스스로 부족함을 많이 느꼈다. 이 생각 속에서 고민고민하다 코드스쿼드 마스터즈 본과정까지 듣기로 결정하고 입과시험을 보았다. 그리고 운이 좋게 마스터즈 코스(오프라인 과정)에 합격할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1700308523530&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
ㅤㅤㅤㅤㅤㅤ﹡ㅤㅤㅤㅤㅤㅤㅤㅤㅤ＋ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．．ㅤ．．ㅤㅤ＋．．ㅤㅤ＋ㅤ．ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤ＊ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．ㅤ．ㅤㅤㅤ．．ㅤㅤ＊ㅤ＋ㅤ．ㅤ＋．ㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ＋ㅤㅤㅤㅤ．．ㅤ．ㅤㅤㅤ．ㅤㅤㅤ＋ㅤ．．ㅤ＋ㅤ．ㅤㅤㅤ．
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．ㅤㅤ．ㅤㅤ．ㅤㅤ＋ㅤㅤㅤ．．＋ㅤㅤㅤㅤ．．．ㅤ
ㅤㅤㅤㅤㅤ．ㅤㅤㅤㅤㅤㅤ＋ㅤㅤㅤㅤㅤㅤㅤㅤㅤ．ㅤ．ㅤㅤㅤㅤㅤ＋．ㅤ＋ㅤㅤ．ㅤ．ㅤㅤㅤ．．ㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．．．ㅤㅤ．ㅤㅤㅤㅤ．．＋ㅤㅤㅤㅤ＋．．．ㅤㅤ．ㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&quot;You Are Here!&quot;．ㅤ．＊．ㅤㅤ＋ㅤㅤ＋ㅤㅤ．ㅤㅤㅤ．ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ |ㅤ．ㅤㅤㅤ．＋ㅤ．＋ㅤ．ㅤ．ㅤ．ㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．ㅤㅤ゜ㅤ\|/ㅤ＋ㅤ．＋．＊ㅤㅤㅤㅤㅤㅤㅤㅤㅤ． ㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．．ㅤㅤㅤVㅤㅤ＋ㅤ．ㅤ＋ㅤ．ㅤ．ㅤㅤㅤ．ㅤ．ㅤ＊ㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ＋ㅤㅤㅤ ．ㅤㅤㅤㅤ．＋＋ㅤㅤ．ㅤㅤ．ㅤㅤㅤㅤㅤㅤㅤ．ㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．ㅤㅤㅤ．＊．ㅤㅤㅤㅤㅤㅤ＋ㅤㅤㅤ＋．ㅤㅤㅤㅤㅤ．ㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤ．ㅤㅤㅤㅤㅤ．．．．．ㅤ．ㅤㅤㅤㅤ＋ㅤ＋ㅤㅤ．．ㅤㅤㅤㅤㅤ．ㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．．ㅤㅤ．ㅤㅤ．．ㅤ．＋．ㅤ．ㅤㅤ＋ㅤㅤㅤㅤ．ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ＋．ㅤㅤ．ㅤㅤ．ㅤㅤ．ㅤㅤㅤ．＋＋．ㅤ．．＊ㅤ．ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤ＋ㅤㅤㅤㅤㅤㅤㅤㅤㅤ．＋．ㅤㅤ．ㅤㅤ．． ㅤ．ㅤㅤ．．ㅤㅤ．ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．ㅤㅤ＊ㅤㅤ．ㅤ．ㅤ．ㅤ＋．．＋ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤ．．．ㅤㅤㅤ．．＋ㅤㅤㅤ．＋．ㅤ．ㅤㅤ．ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ．ㅤ．ㅤㅤ．ㅤㅤ＋＋ㅤㅤㅤ．ㅤㅤ．．．＊ㅤㅤㅤ＊ㅤㅤㅤ．ㅤㅤㅤㅤㅤ
＋ㅤㅤㅤㅤ．．．ㅤㅤㅤㅤㅤ．ㅤ＋．ㅤ．ㅤㅤ＋．ㅤㅤㅤㅤㅤ．ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span&gt;입과시험이었던 '멋있게' 출력한 우주. 우주를 안그래도 좋아했는데... 입과시험이 내 취향 저격이어서 엄청엄청 즐거웠던 기억이 난다. 화면에는 없지만 행성과 로켓 등 나름 즐겁게 구현했던 요소가 몇 개 더 있다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1700308580840&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void putMoon(String[][] space, int x, int y, double angle){
    double radian=Math.toRadians(angle);
    float nx = Math.round(x+(space.length/10*Math.cos(radian)));
    float ny = Math.round(y+(space.length/10*Math.sin(radian)));
    
    if(space.length/2&amp;lt;nx &amp;amp;&amp;amp; nx&amp;lt;x){       // 달이 지구와 태양 사이에 있다면 반달로 저장
        space[ny][nx]=&quot; &quot;;
    } else if (x&amp;lt;nx &amp;amp;&amp;amp; nx&amp;lt;space.length/2){
        space[ny][nx]=&quot; &quot;;
    } else {
        space[ny][nx]=&quot; &quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span&gt;지금보면 부족함 투성이지만, 입과시험에서 썼던 코드 중 가장  혼자 뿌듯했던 코드. 달 위치를 표시할 때 태양과 지구와의 나란히 선 순서를 고민해서 반달을 출력하도록 넣었다ㅎㅎ&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span&gt;그리고 역시.... 합격 기준은 잘 모르겠다...;&amp;nbsp;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2022년을 마무리하며&lt;/b&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;1년으로 끝날 줄 알았던 학습과 적응 기간은 '좀 더 제대로 알고 싶다'라는 욕심하에 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;2022년 고생했다. 시작한 달리기에 지치지 말고 2023년을 보내자&lt;/p&gt;</description>
      <category>  Smalltalk</category>
      <category>log</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/84</guid>
      <comments>https://new-pow.tistory.com/84#entry84comment</comments>
      <pubDate>Sat, 18 Nov 2023 20:57:41 +0900</pubDate>
    </item>
    <item>
      <title>2022년 회고 (2) 풀스택 국비교육 수강기</title>
      <link>https://new-pow.tistory.com/83</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;케케묵은 지난글  &lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;https://new-pow.tistory.com/23?ref=localhost&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.02.01 - [ /회고] - 2022년 회고 (1) 커뮤니티 기획자가 개발자를 꿈꾸게된 이야기&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;매우 늦었고 내용이 별거 없어서... 조금 부끄럽지만... gitpage에 있던 글을 끌고 왔습니다.&lt;/i&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;b&gt;국비 교육 과정을 듣기로 한다&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2022년 6월~10월 : 국비교육 수강&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; 1년정도 쉬기로 한거, 돈도 더 들지 않겠다 주말에는 지인의 카페에서 계속 알바를 하고 평일에는 9시부터 6시까지 풀스택 국비교육과정을 듣기로 한 선택말이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;선택의 기준은 아주 간단했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시작하기에 부담이 없을 것, 곧 언제든 다시 돌아갈 수 있을 것&lt;/li&gt;
&lt;li&gt;교육과정은 취업을 하기 유리할 것 (범용적일 것)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기다가 개발자인 친구가 &amp;ldquo;너는 프론트엔드, 백엔드 개념도 모르니까 일단 다 들어보고 나중에 심화해서 공부해봐&amp;rdquo; 라고 조언해준 것을 토대로 결정한 것이 멀티캠퍼스에서 진행하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 플랫폼을 활용한 웹 서비스 개발&lt;span&gt;&amp;nbsp;&lt;/span&gt;과정을 들었다. (지금 이 과정이 없어진 것 같다.)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 배웠던 것은 다음과 같다. 스택들은 매우 기초적이고 핵심적인 스택들이고 이것들을 모두 훑었다는 것이 이 과정의 가장 큰 장점이었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;커리큘럼&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 3주&lt;/li&gt;
&lt;li&gt;MySQL 기본 쿼리 문법과 Java와 연동(JDBC) 1주&lt;/li&gt;
&lt;li&gt;HTML5, CSS, JavaScript, jQuery 2주&lt;/li&gt;
&lt;li&gt;Sevlet &amp;amp; JSP, Spring, Sptring boot 와 MyBatis 3주&lt;/li&gt;
&lt;li&gt;Jenkins, Naver Cloud Platform을 이용한 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8&quot; style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자율 기획 프로젝트 2주
&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;/li&gt;
&lt;li&gt;자율 기획 프로젝트 및 배포 약 8주
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 플랫폼 : Naver cloud platform&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 id=&quot;%EA%B8%B0%ED%83%80-%ED%8A%B9%EA%B0%95&quot; style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기타 특강&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Git, Github 사용 1회&lt;/li&gt;
&lt;li&gt;Notion 사용 1회&lt;/li&gt;
&lt;li&gt;React 체험 3회&lt;/li&gt;
&lt;li&gt;Oracle DB 사용해보기 1회&lt;/li&gt;
&lt;li&gt;기타 취업 관련 특강&amp;hellip;.&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;AI 서비스이고 플랫폼이고 뭐고 사실 그때 제대로 알지 못했고, 나중에 생각해보니 Open API 중에서 AI 기술을 접목한 API를 사용해본다는 뜻이었다   이때 API라는 개념이 없었는데..  지금 생각해보니 구현을 한 것 자체가 정말 대단했다. 같은 반에 거의 30명에 달하는 사람들 중에 개발 초심자가 아닌 사람이 고작 3명 정도였다. Java를 처음배우고 Github가 무엇인지도 모르는 사람들이 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;b&gt;배웠던 것들에 대해&lt;/b&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;정규 수업시간은 8시간동안 계속 수업을 듣고 실습 코드 작성을 했고, 진도는 미친듯이 빨랐다. 몰입해서 이 시간을 보내고 나면 나머지 시간은 지쳐서 쓰러져 자기 일쑤였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 약 3개월동안 위의 기술을 모두 배우고 프로젝트를 2.5개월간 하려다보니 정말 필수적인 것만 속성으로 배울 수 있었다. 이때 당시의 기록들을 좀 들추어 각 기술에 대한 이해도를 살펴보니 꽤 재밌다.&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; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 교육과정 중, 변수나 연산자, 조건문과 반복문은 사실 수학 함수나 다름없어서 큰 놀라움은 없었다. 그저 콘솔에서 입력과 출력으로 어플리케이션이 돌아간다는 자체가 가장 신기했다. 자판기 콘솔 프로그램을 짜고 신기해했던 나...ㅋㅋ&lt;/li&gt;
&lt;li&gt;교육 내용은 확실히 자바 기초에 부합하는 정확한 내용들이었으나 학습하고 공부하는 입장에서 너무 빠른 input 으로 &amp;lsquo;그렇구나&amp;rsquo; 하면서 넘어가는 지식이 굉장히 많았다. 언어 &lt;span style=&quot;background-color: #ffffff; color: #15171a; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;자체를&lt;/span&gt; 이해하기보다는 어떻게 사용하는 지에 집중하여서 학습하였던 것 같다.&lt;/li&gt;
&lt;li&gt;학습 내용 중 가장 충격적이고 바로 받아들이기 힘들었던 개념은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;객체지향언어&lt;/b&gt;의 특성이었다. 패키지와 클래스를 분리하고 이를 import 하거나 인스턴스를 만들어서 사용할 수 있다는 것을 배웠을 때 한동안 멘붕을 느꼈었다. 왜 이것을 나누는지에 대한 이해가 부족했던 것 같다. 지금은 객체지향언어가 재밌다.&lt;/li&gt;
&lt;li&gt;그리고 바로 그 다음주 2주차에 연달아 Java 메모리 사용과 참조타입을 공부하며 두번째 충격을 받게된다   이게 무슨 세계지&lt;/li&gt;
&lt;li&gt;DB에 대한 첫 인상이 최악이었나보다. 추후 JDBC 를 사용해보며 자바 교육기간동안 3차 멘붕을 맞게 된다.&lt;/li&gt;
&lt;li&gt;HTML, CSS, JavaScript 를 좋아했다. 다만 코드가 늘어날 수록 코드를 주체하지 못해 고민했다. 정리하지 못한 코드가 통째로 한 파일에 얽혀있었다.&lt;/li&gt;
&lt;li&gt;자바스크립트는 jQuery보다는 바닐라로 사용하는 것을 훨씬 좋아했다. ajax로 비동기 요청을 하고 받는 것을 하면서도 비동기에 대한 이해가 부족했던 것 같다.&lt;/li&gt;
&lt;li&gt;배포에 대해서.. 당시 네트워크에 대한 지식이 전무하였지만, 수업자료가 꽤 잘되어있어서 할 수 있었다. 아마 이 짧은 시간 안에 수강생들을 이해시킬만한 네트워크 설명을 하기 힘들었을 것 같다.&lt;/li&gt;
&lt;li&gt;controller&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;service&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt; DAO&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 이어지는 3 layers 계층형 아키텍처를 이해하는 데 힘들었다. 이것은 함께한 수강생 중 대부분이 비슷했는데 그냥 구조에 '익숙해진다' 에 더 가까운 학습이었던 것 같다. 각 레이어의 역할을 크게 고민하지 않았었다. 이런 아키텍처에 대한 이해는 본격적으로 프로젝트를 시작하고 구현을 반복하면서 점점 더 익혀갔다.&lt;/li&gt;
&lt;li&gt;개발 공부를 시작한 것에 즐거워하고 만족했다.&lt;/li&gt;
&lt;li&gt;기본 CRUD 외에 로직을 효율적으로 처리하는 방법을 숙지하는 데에 험난함을 느끼고 더 학습하고 싶어져 백엔드로 진로를 결정하게 된다.&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;&lt;b&gt;프로젝트에 대하여&lt;/b&gt;&lt;/h2&gt;
&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;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lrtb4/btsAzAkvmIs/nkVkrtQX4keyRbQ4TBSQmK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lrtb4/btsAzAkvmIs/nkVkrtQX4keyRbQ4TBSQmK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lrtb4/btsAzAkvmIs/nkVkrtQX4keyRbQ4TBSQmK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Lrtb4/btsAzAkvmIs/nkVkrtQX4keyRbQ4TBSQmK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;443&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;443&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;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용 기술 스택 : Java11, SpringBoot 2.7, MySQL, MyBatis, JavaScript (Vanila)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 2달정도를 잡고 있었던 이 프로젝트 Artchive. 참 무에서 유로 간다고 생각하고 비전공자 5명이서 한땀한땀 만들었다. HTML 태그부터 시작해서 처음부터 만드느라 구현자마다 미묘하게 페이지가 달라져서 나중에 통일시키느라 고생했던 기억이난다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금 보면 AWS에 배포하긴 했지만 인프라나 설계면에서 부족한점이 많다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 속 개인적인 역할로서 HTML, CSS 쪽이나 바닐라 JS를 이용하여 지도 API를 사용하고, 비동기 요청을 하는 등 프론트 역량을 더 많이 발휘했었다. 스크린샷 화면에 보이는 로직 중 여행 코스를 기획하고 저장하는 페이지 view부터 crud까지. 댓글, 좋아요와 리뷰로 연결될 수 있도록 구현했다. 아래 ERD 에서는 노란색으로 표시된 코스에 해당한다.&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;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VPBR9/btsAyNq7n4x/4gFMknKkRokkjIAX4QYaKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VPBR9/btsAyNq7n4x/4gFMknKkRokkjIAX4QYaKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VPBR9/btsAyNq7n4x/4gFMknKkRokkjIAX4QYaKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVPBR9%2FbtsAyNq7n4x%2F4gFMknKkRokkjIAX4QYaKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;513&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때 구현하며 가장 큰 이슈는 두가지였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째, &quot;각 코스의 순서를 view에서 어떻게 수정할 것인가, 그리고 이것을 DB에 어떻게 저장해야 좋을까&quot; 였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프론트에서 코스 순서와 내용을 변동하는 것은 JQuery의 라이브러리 도움을 받았다. 그리고 백엔드로 넘길 때는 순서-내용으로 key-value 형태의 JSON을 넘겨 DB에는 그대로 String 직렬화하여 스냅샷처럼 저장했다.   그니까 코스가 바뀔 때마다 한번의 Insert만 일어나지만 매번 내용을 새로 덮어쓰는 식이었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;링크드 리스트나 우선순위를 주는 등의 다양한 방법이나 테이블을 어느정도 정규화+비정규화하고 인덱스를 생성하여 최적화할 수 있는 방법들이 있었는데.. 당시에는 알지 못했다. 생각해보면 게시판 페이지 로딩부터 느렸던 이유가 다 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두번째, AI학습을 위한 데이터 수집과 학습의 자동화였다. 사용자의 이용패턴을 DB에 저장하고 이를 데이터로 추천해주는 Aitems를 적용했었는데. (담당은 내가 아니었다.) 이부분은 스스로 이해도가 너무 낮았고, 더미 데이터를 대량으로 만들 수 있을만한 여유도 기술도 없었다. 이 Ai 학습 패턴 업데이트를 자동화하지 못했던 것도 아쉽다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금 보면 아이디어 자체는 여러가지 시도하기 좋았는데 아쉬움이 많이 남는 프로젝트이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;%EA%B5%90%EC%9C%A1%EA%B3%BC%EC%A0%95%EC%9D%98-%EC%A2%8B%EC%95%98%EB%8D%98-%EC%A0%90&quot; style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;교육과정의 좋았던 점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;view부터 어플리케이션, DB, 인프라 배포까지 전체를 훑는 경험해 볼 수 있었다. 그래서 내가 어디에 더 흥미있는지 고민하기에 좋은 환경이었다.&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;h2 id=&quot;%EA%B5%90%EC%9C%A1%EA%B3%BC%EC%A0%95%EC%9D%98-%EC%95%84%EC%89%AC%EC%9B%A0%EB%8D%98-%EC%A0%90&quot; style=&quot;background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;교육과정의 아쉬웠던 점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #15171a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;너무 급하게 배우려다보니 무엇하나 어떻게 학습해야하는지 알지 못한채로 5.5개월이 끝나버렸다. 특히 프로젝트 기간이 길어서 교육과정이 더 급격하게 짧게 느껴졌다.&lt;/li&gt;
&lt;li&gt;이건 우리반의 특성인 것 같은데 먼저 공부해봤거나 전공을 했거나 프로젝트 방법을 아는 사람이 극 소수인데다 초반에 자율 조구성을 할 때 일부 그룹에만 편성되어서 어떻게 프로젝트를 해야하는지 모르는채로 시작해야 했다.&lt;/li&gt;
&lt;li&gt;프로젝트에 대한 평가가 기획과 발표 스킬에 집중되어 있었다. 코드는 구현하는 것이 목적이었고, 어떻게 해야 좋은 코드인지 학습하기는 힘들었다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Smalltalk</category>
      <category>log</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/83</guid>
      <comments>https://new-pow.tistory.com/83#entry83comment</comments>
      <pubDate>Sat, 18 Nov 2023 20:28:54 +0900</pubDate>
    </item>
    <item>
      <title>S3 업로드를 비동기로 처리하고 싶어요✊ : 반환이 있는 @Async를 사용할 때 주의할 것들....</title>
      <link>https://new-pow.tistory.com/82</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 글은 Secondhand 프로젝트를 하며 트러블 슈팅하고 학습한 내용을 정리한 글입니다.&lt;/blockquote&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;b&gt;시작하며&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Bn0A/btszM1J35HI/kIvAYxXFL7W24k1IIB7SOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Bn0A/btszM1J35HI/kIvAYxXFL7W24k1IIB7SOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Bn0A/btszM1J35HI/kIvAYxXFL7W24k1IIB7SOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Bn0A%2FbtszM1J35HI%2FkIvAYxXFL7W24k1IIB7SOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;446&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;1016&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;Secondhand에 글을 작성할 때는 최대 10장의 이미지를 업로드할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 구현하고 나서 가장 마음에 걸렸던 부분이 있는데요, 바로 이 10장의 이미지가 하나의 스레드에서 동기적으로 처리된다는 것입니다. 꽤 고화질인 이미지를 10장 한번에 처리를 하면 3초정도 소요되기도 합니다  ....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 마음에 걸렸던 코드라 이참에 리팩토링을 해보기로 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dr7TFP/btszLpLkQhR/087vNuKRMFQEkyFqFZbeR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dr7TFP/btszLpLkQhR/087vNuKRMFQEkyFqFZbeR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dr7TFP/btszLpLkQhR/087vNuKRMFQEkyFqFZbeR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdr7TFP%2FbtszLpLkQhR%2F087vNuKRMFQEkyFqFZbeR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;230&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;474&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;b&gt;개선 해보자&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기존 로직 및 문제점 분석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 코드와 시퀀스 다이어그램을 먼저 공유하자면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1699185918830&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Override
    public List&amp;lt;ItemDetailImage&amp;gt; uploadItemDetailImages(List&amp;lt;MultipartFile&amp;gt; request) throws ImageHostException {
        checkFilesSize(request);

        List&amp;lt;ItemDetailImage&amp;gt; images = new ArrayList&amp;lt;&amp;gt;();

        for (MultipartFile multipartFile : request) {
            ImageInfo imageInfo = uploadItemDetailImage(multipartFile);
            images.add(ItemDetailImage.create(imageInfo.getImageUrl()));
        }

        return images;
    }
    
    @Override
    public ImageInfo uploadItemDetailImage(MultipartFile file) throws ImageHostException {
        String imageUrl = &quot;&quot;;

        try {
            imageUrl = upload(file, Directory.ITEM_DETAIL);
        } catch (IOException e) {
            throw new ImageHostException(&quot;물품 사진 업로드에 실패하였습니다.&quot;);
        }

        return ImageInfo.create(imageUrl);
    }
    
    public String upload(MultipartFile file, Directory directory) throws IOException, TooLargeImageException, NotValidImageTypeException {
        checkFileSize(file);
        checkFileType(file);

        String newFileKey = generateKey(file.getOriginalFilename(), directory.getPrefix());
        amazonS3.putObject(new PutObjectRequest(properties.getBucket(), newFileKey, file.getInputStream(), getMetadata(file)));
        return amazonS3.getUrl(properties.getBucket(), newFileKey).toString();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mmFTF/btszQDB2AqO/8QN1vwqPWrHbYHXSUzxQqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mmFTF/btszQDB2AqO/8QN1vwqPWrHbYHXSUzxQqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mmFTF/btszQDB2AqO/8QN1vwqPWrHbYHXSUzxQqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmmFTF%2FbtszQDB2AqO%2F8QN1vwqPWrHbYHXSUzxQqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2388&quot; height=&quot;1668&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 하나에서 최대 10개의 이미지를 개미처럼 천천히 하나씩 처리하고 있습니다.  ...&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;그리고 이것을 모두 다 처리한 뒤에 섬네일을 처리하므로 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;테스트로 로컬에서 스레드 로그도 찍어보았는데요. 역시나 한개의 스레드로 직렬 처리하고 있었습니다. 깔끔한 이 로직..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699189328069&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: 232, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: 232, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: 232, image: 115064144.jpeg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 115064144.jpeg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: 232, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: 232, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg
DEBUG 9717 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg&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;&lt;b&gt;마법의 어노테이션 @Async을 붙이면 어떻게 될까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히들 동기 작업을 하기 위해서는 어노테이션 @Async를 붙여주곤 합니다. 그럼 해결 될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 `upload()`에 @Async 를 붙이고 AsyncConfig 를 만들어줍니다. 그리고 기대하며 다시 테스트를 해보았지만...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 동일한 스레드에서 동기로 실행됩니다. 심지어 스레드 그룹을 지정해주었음에도 해당 그룹에서 스레드를 가져오지도 않고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1699189845271&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2023-11-05 22:18:39.177 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: main 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:18:41.300 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:18:41.364 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: main 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:18:41.443 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:18:41.448 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: main 232, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg
2023-11-05 22:18:42.105 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg
2023-11-05 22:18:42.113 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: main 232, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
2023-11-05 22:18:42.169 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
2023-11-05 22:18:42.170 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: main 232, image: 115064144.jpeg
2023-11-05 22:18:42.270 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: 115064144.jpeg
2023-11-05 22:18:42.273 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: main 232, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
2023-11-05 22:18:42.360 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
2023-11-05 22:18:42.365 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread upload work start: main 232, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg
2023-11-05 22:18:42.569 DEBUG 10387 --- [nio-8080-exec-2] c.t.s.a.image.service.ImageHostService   : Thread work end: 232, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고용으로 수정한 코드를 추가합니다)&lt;/p&gt;
&lt;pre id=&quot;code_1699195295926&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EnableAsync
@Configuration
public class AsyncConfig {

    @Bean(name = &quot;imageUploadExecutor&quot;)
    public Executor imageUploadExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadGroupName(&quot;imageUploadExecutor&quot;);
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(50);
        executor.initialize();
        return executor;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1699195340867&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Async(&quot;imageUploadExecutor&quot;)
    public String upload(MultipartFile file, Directory directory) throws IOException, TooLargeImageException, NotValidImageTypeException {
        checkFileSize(file);
        checkFileType(file);

        String newFileKey = generateKey(file.getOriginalFilename(), directory.getPrefix());
        amazonS3.putObject(new PutObjectRequest(properties.getBucket(), newFileKey, file.getInputStream(), getMetadata(file)));
        return amazonS3.getUrl(properties.getBucket(), newFileKey).toString();
    }&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⭐️ @Async 를 사용할 때 주의할 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제점은 제가 그동안 Async 에 대해 잘못 사용하고 있었다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Async는 어떻게 동작할까요?&lt;/p&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의 큰 특징인 AOP 의 기능으로 구현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드에 @Async 어노테이션을 붙이면 proxyTargetClass 를 기반으로 해당 객체의 프록시가 만들어집니다. 그 후 Spring은 context에 연결된 스레드 풀을 찾아 해당 메서드의 로직을 별도 스레드로 실행하려고 합니다. 만약 명명된 빈을 찾을 수 없으면 기본 SimpleAsyncTaskExecutor를 사용합니다. (&lt;a href=&quot;https://dzone.com/articles/effective-advice-on-spring-async-part-1&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;Proxy란 Target을 감싸 Target의 요청을 대신 받아주는 Wrapping Object인데요. 호출자에서 타겟을 호출하게되면 타겟이 아닌 프록시가 호출되어, 타겟 메소드 실행전 선처리&amp;rarr;타겟 메소드&amp;rarr;후처리를 실행합니다. (  &lt;a href=&quot;https://dahye-jeong.gitbook.io/spring/spring/2020-04-09-aop&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;이러한 동작 방식 때문에 구현시 주의할 점이 있는데요. 주의사항을 지키지 않으면 어노테이션이 무시되고 동기 방식으로 동작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;@EnableAsync 어노테이션&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션의 메인 설정 클래스나 configuration 파일에서 해당 어노테이션을 사용해주어야 합니다. 이 어노테이션으로 스프링의 @Async 어노테이션과 EJB 3.1 javax.ejb.Asynchronous 를 감지합니다. (&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Bean으로 관리되고 있어야 합니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ComponentScan 어노테이션에 의해 스캔되거나 @Configuration 클래스 내부에 빈으로 정의되어야 합니다. Spring 에서 프록시를 생성하기 위해서는 Spring IoC 컨테이너에 의해 관리되는 Bean이어야 하기 때문입니다.&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;b&gt;Private 메서드에는 동작하지 않습니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임에 프록시를 생성할 수 없으므로 동작하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;호출하는 메서드와 비동기 메서드가 같은 클래스에 있으면(즉, Self-invocation 이면) 안됩니다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분이 제가 실수했던 부분이었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;The default is&amp;nbsp;AdviceMode.PROXY.&amp;nbsp;Please note that proxy mode allows for interception of calls through the proxy only. &lt;b&gt;Local calls within the same class cannot get intercepted that way&lt;/b&gt;; an&amp;nbsp;Async&amp;nbsp;annotation on such a method within a local call will be ignored since Spring's interceptor does not even kick in for such a runtime scenario. For a more advanced mode of interception, consider switching this to&amp;nbsp;AdviceMode.ASPECTJ.&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;이렇게 메서드를 호출하는 경우 스프링의 인터셉터가 작동하지 않기 때문입니다. 프록시를 우회하고 메서드를 직접 호출하게 되어 별도 Thread에서 동작하지 않아 Async 어노테이션은 무시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dzone.com/articles/effective-advice-on-spring-async-part-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(이미치 출처)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T72Hg/btszKyoTfYB/dttaK3bBasf7JLw2evqZPk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T72Hg/btszKyoTfYB/dttaK3bBasf7JLw2evqZPk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T72Hg/btszKyoTfYB/dttaK3bBasf7JLw2evqZPk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT72Hg%2FbtszKyoTfYB%2FdttaK3bBasf7JLw2evqZPk%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;668&quot; height=&quot;305&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;305&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;학습한 내용을 적용해보았습니다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고보니 @Async 어노테이션이 붙은 메서드를 내부 함수에서 호출해주었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 분리해주고, AsyncConfig도 다시 점검하여 수정해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 따로 구현한 `ImageUploader` 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699205418515&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ImageUploader {

    private final AwsProperties properties;
    private final AmazonS3 amazonS3;

    @Async(&quot;imageUploadExecutor&quot;)
    public String upload(MultipartFile file, Directory directory) throws IOException {
        log.debug(&quot;Thread upload work start: {}, image: {}&quot;, Thread.currentThread().getId(), file.getOriginalFilename());

        String newFileKey = generateKey(file.getOriginalFilename(), directory.getPrefix());
        amazonS3.putObject(new PutObjectRequest(properties.getBucket(), newFileKey, file.getInputStream(), getMetadata(file)));
        log.debug(&quot;Thread work end: {}, image: {}&quot;, Thread.currentThread().getId(), file.getOriginalFilename());
        return amazonS3.getUrl(properties.getBucket(), newFileKey).toString();
    }

    private String generateKey(String originFileKey, String prefix) {
        return String.format(&quot;%s%s-%s&quot;, prefix, UUID.randomUUID(), originFileKey);
    }

    private ObjectMetadata getMetadata(MultipartFile file) {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(file.getSize());
        objectMetadata.setContentType(file.getContentType());
        return objectMetadata;
    }
}&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;/p&gt;
&lt;pre id=&quot;code_1699191855639&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2023-11-05 22:39:33.688 DEBUG 11379 --- [ploadExecutor-3] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 249, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg
2023-11-05 22:39:33.688 DEBUG 11379 --- [ploadExecutor-2] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 248, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:39:33.689 DEBUG 11379 --- [ploadExecutor-4] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 250, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
2023-11-05 22:39:33.688 DEBUG 11379 --- [ploadExecutor-1] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 247, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:39:33.688 DEBUG 11379 --- [ploadExecutor-7] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 253, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg
2023-11-05 22:39:33.688 DEBUG 11379 --- [ploadExecutor-6] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 252, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
2023-11-05 22:39:33.688 DEBUG 11379 --- [ploadExecutor-5] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 251, image: 115064144.jpeg
Hibernate: 
    insert 
    into
        item_contents
        (contents, detail_image_url, is_deleted) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        item_counts
        (chat_counts, hits, is_deleted, like_counts) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        item
        (created_at, updated_at, category, item_contents_id, item_counts_id, is_deleted, price, region_id, seller_id, status, thumbnail_url, title) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2023-11-05 22:39:34.986 DEBUG 11379 --- [ploadExecutor-1] c.t.s.api.image.service.ImageUploader    : Thread work end: 247, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:39:34.986 DEBUG 11379 --- [ploadExecutor-5] c.t.s.api.image.service.ImageUploader    : Thread work end: 251, image: 115064144.jpeg
2023-11-05 22:39:34.986 DEBUG 11379 --- [ploadExecutor-6] c.t.s.api.image.service.ImageUploader    : Thread work end: 252, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
2023-11-05 22:39:34.986 DEBUG 11379 --- [ploadExecutor-4] c.t.s.api.image.service.ImageUploader    : Thread work end: 250, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
2023-11-05 22:39:34.986 DEBUG 11379 --- [ploadExecutor-2] c.t.s.api.image.service.ImageUploader    : Thread work end: 248, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 22:39:35.006 DEBUG 11379 --- [ploadExecutor-7] c.t.s.api.image.service.ImageUploader    : Thread work end: 253, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg
2023-11-05 22:39:35.117 DEBUG 11379 --- [ploadExecutor-3] c.t.s.api.image.service.ImageUploader    : Thread work end: 249, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MgNIp/btszI2qqUHB/hmicWcJMK3Izw1aCDOLcjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MgNIp/btszI2qqUHB/hmicWcJMK3Izw1aCDOLcjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MgNIp/btszI2qqUHB/hmicWcJMK3Izw1aCDOLcjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMgNIp%2FbtszI2qqUHB%2FhmicWcJMK3Izw1aCDOLcjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;198&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;510&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&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/bhzmG6/btszKAfWTec/ZOOeiO7uLmuKGCii99MV00/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhzmG6/btszKAfWTec/ZOOeiO7uLmuKGCii99MV00/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhzmG6/btszKAfWTec/ZOOeiO7uLmuKGCii99MV00/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhzmG6%2FbtszKAfWTec%2FZOOeiO7uLmuKGCii99MV00%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;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;쿼리가 왜 중간에 나오는거야? 그리고 왜 이렇게 빠른거야?? I/O작업인데 200ms 대가 가능한거야? 정말로...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;새로운 문제의 등장: 반환값이 있는 비동기 메서드 처리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나.. 쿼리가 중간에 나오는게 이상해서 DB를 확인해보니 역시나 null 파티가 났더라고요. null null 한 이미지 url들...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pOU4Y/btszLpLmOfs/bnhg3I6kfJf0QP5EW3c2KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pOU4Y/btszLpLmOfs/bnhg3I6kfJf0QP5EW3c2KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pOU4Y/btszLpLmOfs/bnhg3I6kfJf0QP5EW3c2KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpOU4Y%2FbtszLpLmOfs%2Fbnhg3I6kfJf0QP5EW3c2KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1796&quot; height=&quot;104&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜? 비동기로 처리하다보니 `upload()` 의 반환값인 url을 기다리지 않고 후다닥 다음 로직을 처리해버린 탓입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(부족하지만 이해를 돕기위한 시퀀스 다이어그램..)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNak8p/btszLjrL8lP/FT2cO9kFKJoPh2HwKWEck0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNak8p/btszLjrL8lP/FT2cO9kFKJoPh2HwKWEck0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNak8p/btszLjrL8lP/FT2cO9kFKJoPh2HwKWEck0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNak8p%2FbtszLjrL8lP%2FFT2cO9kFKJoPh2HwKWEck0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2388&quot; height=&quot;1668&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 비동기 처리의 반환값을 기다렸다가 처리할 수 있도록 해주어야 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억 저편에 있던 동기-비동기, 블락킹-넌블락킹을 꺼내와 키워드를 찾기 시작했습니다. 해당 개념은 지금 여기서 다루지 않으므로 &lt;a href=&quot;https://inpa.tistory.com/entry/%F0%9F%91%A9%E2%80%8D%F0%9F%92%BB-%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%B8%94%EB%A1%9C%ED%82%B9%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;반환값이 있는 비동기 메서드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;void 반환 유형을 사용하는 메서드의 경우, 간단하게 메서드를 비동기적으로 실행하도록 할 수 있습니다. 하지만 반환값이 있는 경우가 문제였는데요. 일반적으로 반환하면 저의 경험처럼 스택 메모리에서 pop되며 공중으로 흩날리게 되겠죠...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환 유형의 경우 아래와같은 선택지가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqMix/btszNQBFHo2/rG5CRUtkKhyNrI5JC8wYNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqMix/btszNQBFHo2/rG5CRUtkKhyNrI5JC8wYNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqMix/btszNQBFHo2/rG5CRUtkKhyNrI5JC8wYNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqMix%2FbtszNQBFHo2%2FrG5CRUtkKhyNrI5JC8wYNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;114&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;234&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Future&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future 를 사용하여 비동기 프로세스의 결과를 받아 호출했던 스레드에서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`get()` 을 사용하면 결과값이 반환될 때까지 블로킹하고 기다립니다. 주의할 점은 get() 메서드의 파라미터로 time out 시간을 정해주지 않으면 무한정 대기하게 된다는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1699207065450&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future&amp;lt;Integer&amp;gt; future = executorService.submit(() -&amp;gt; {
            Thread.sleep(1000);
            return 42;
        });

        Integer result = future.get();
        System.out.println(&quot;Result: &quot; + result);

        executorService.shutdown();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future 내부적으로 스레드 세이프하게 구현되어 있어서 따로 syncronized를 작성해주지 않아도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&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;b&gt;CompletableFuture&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java8부터 도입되었습니다. Future 인터페이스와 함께 CompletionStage 인터페이스를 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 완료를 기다리는 Future와는 달리, 콜백을 추가하거나 작업을 조합하여 사용하는 비동기 파이프라인을 만들거나 여러 CompletableFuture 들을 모아 모두 완료되면 다음 작업을 하거나 예외를 발생시키는 완료를 할 때, 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순한 Future로 사용하는 방법 get())&lt;/li&gt;
&lt;li&gt;비동기 계산 결과 처리 (thenApply(), thenAccept(), thenRun())&lt;/li&gt;
&lt;li&gt;작업을 결합해서 사용하기 (thenCompose())&lt;/li&gt;
&lt;li&gt;여러 Future 병렬로 실행 (allOf())&lt;/li&gt;
&lt;li&gt;오류에 대한 처리 (&lt;span style=&quot;background-color: #fafafa; text-align: start;&quot;&gt;completeExceptionally(), CompleteOnTimeout()&lt;/span&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 메서드는 여기를 참고했습니다. &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.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;각 메서드에 대한 예시는 여기를 참고했습니다. &lt;a href=&quot;https://www.baeldung.com/java-completablefuture&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ListenableFuture&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 메서드에서 성공, 실패시 콜백할 함수를 `addCallback()`으로 추가하여 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1699207682029&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ListenableFutureExample {
    public static void main(String[] args) {
        SettableListenableFuture&amp;lt;Integer&amp;gt; future = new SettableListenableFuture&amp;lt;&amp;gt;();
        future.addCallback(new ListenableFutureCallback&amp;lt;Integer&amp;gt;() {
            @Override
            public void onSuccess(Integer result) {
                System.out.println(&quot;Result: &quot; + result);
            }

            @Override
            public void onFailure(Throwable ex) {
                System.err.println(&quot;Error: &quot; + ex.getMessage());
            }
        });

        // 비동기 작업 완료 후 결과 설정
        new Thread(() -&amp;gt; {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            future.set(42);
        }).start();

        // 비동기 작업이 완료될 때까지 대기하지 않고 계속 진행
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}&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;Spring framework 6.0부터 deprecate 되었습니다. (&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/concurrent/ListenableFuture.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;deprecate된 정확한 원인은 찾지 못했는데요. 관련해서 알고계시다면 댓글 부탁드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;반환값을 받아 사용할 수 있도록 수정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 선택지 중 반환값을 CompletableFurure로 정했습니다. Future보다 예외처리나 반복 작업을 모두 모아서 처리할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기처리할 메서드의 반환값을 CompletableFurure로 바꾸어 주고 호출 메서드에서 이를 처리할 수 있도록 수정해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1699194560370&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Async(&quot;imageUploadExecutor&quot;)
    public CompletableFuture&amp;lt;String&amp;gt; upload(MultipartFile file, Directory directory) throws IOException, TooLargeImageException, NotValidImageTypeException {
        log.debug(&quot;Thread upload work start: {}, image: {}&quot;, Thread.currentThread().getThreadGroup().getName()+ &quot; &quot; + Thread.currentThread().getId(), file.getOriginalFilename());
        CompletableFuture&amp;lt;String&amp;gt; future = new CompletableFuture&amp;lt;&amp;gt;();

        String newFileKey = generateKey(file.getOriginalFilename(), directory.getPrefix());
        amazonS3.putObject(new PutObjectRequest(properties.getBucket(), newFileKey, file.getInputStream(), getMetadata(file)));
        log.debug(&quot;Thread work end: {}, image: {}&quot;, Thread.currentThread().getId(), file.getOriginalFilename());
        future.complete(amazonS3.getUrl(properties.getBucket(), newFileKey).toString());
        return future;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1699239599542&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Override
    public List&amp;lt;ItemDetailImage&amp;gt; uploadItemDetailImages(List&amp;lt;MultipartFile&amp;gt; request) throws ImageHostException {
        checkFilesCount(request);

        List&amp;lt;ItemDetailImage&amp;gt; images = new ArrayList&amp;lt;&amp;gt;();
        List&amp;lt;CompletableFuture&amp;lt;String&amp;gt;&amp;gt; uploadFutures = new ArrayList&amp;lt;&amp;gt;(); // 결과 future

        for (MultipartFile multipartFile : request) {
            CompletableFuture&amp;lt;String&amp;gt; upload = null;
            try {
                upload = imageUploader.upload(multipartFile, Directory.ITEM_DETAIL);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            uploadFutures.add(upload);
        }
		
        uploadFutures.forEach(upload -&amp;gt; images.add(ItemDetailImage.create(
                upload.exceptionally(e -&amp;gt; {
                            log.error(&quot;이미지 업로드에 실패하였습니다.&quot;, e);
                            throw new CompletionException(e);
                        })
                        .join()))); // 완료된 반환값

        return images;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctS9oj/btszSHSvQAj/4K8OmXfOu4D2gGKKStQGm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctS9oj/btszSHSvQAj/4K8OmXfOu4D2gGKKStQGm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctS9oj/btszSHSvQAj/4K8OmXfOu4D2gGKKStQGm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctS9oj%2FbtszSHSvQAj%2F4K8OmXfOu4D2gGKKStQGm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2388&quot; height=&quot;1668&quot; data-origin-width=&quot;2388&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실행 결과는 의도했던 대로 비동기로 진행되고 있음을 확인할 수 있었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1699194197637&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2023-11-05 23:27:52.294 DEBUG 13264 --- [ploadExecutor-6] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 282, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 23:27:52.341 DEBUG 13264 --- [ploadExecutor-6] c.t.s.api.image.service.ImageUploader    : Thread work end: 282, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 23:27:52.342 DEBUG 13264 --- [ploadExecutor-5] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 281, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 23:27:52.342 DEBUG 13264 --- [loadExecutor-10] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 286, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg
2023-11-05 23:27:52.342 DEBUG 13264 --- [ploadExecutor-3] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 279, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
2023-11-05 23:27:52.342 DEBUG 13264 --- [ploadExecutor-8] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 284, image: 115064144.jpeg
2023-11-05 23:27:52.342 DEBUG 13264 --- [ploadExecutor-4] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 280, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
2023-11-05 23:27:52.344 DEBUG 13264 --- [ploadExecutor-2] c.t.s.api.image.service.ImageUploader    : Thread upload work start: imageUploadExecutor 278, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg
2023-11-05 23:27:52.414 DEBUG 13264 --- [ploadExecutor-4] c.t.s.api.image.service.ImageUploader    : Thread work end: 280, image: a9b9f3e6-2fa0-4737-9d42-25dc31a82a2c-image0.jpeg
2023-11-05 23:27:52.419 DEBUG 13264 --- [ploadExecutor-8] c.t.s.api.image.service.ImageUploader    : Thread work end: 284, image: 115064144.jpeg
2023-11-05 23:27:52.440 DEBUG 13264 --- [ploadExecutor-3] c.t.s.api.image.service.ImageUploader    : Thread work end: 279, image: 68f27e6f-a951-441e-99fc-03cfe37d89c2-2023-05-11-bunny.jpg
2023-11-05 23:27:52.451 DEBUG 13264 --- [ploadExecutor-5] c.t.s.api.image.service.ImageUploader    : Thread work end: 281, image: 7cdd2eb2-6aa9-42cd-88a9-eb9dbc8e9811-IMG_7857.jpg
2023-11-05 23:27:52.515 DEBUG 13264 --- [ploadExecutor-2] c.t.s.api.image.service.ImageUploader    : Thread work end: 278, image: maxim-abramov-s7vgZ1Prn2M-unsplash.jpg
2023-11-05 23:27:52.815 DEBUG 13264 --- [loadExecutor-10] c.t.s.api.image.service.ImageUploader    : Thread work end: 286, image: 8ec282b9-a2c6-46ab-8fe1-ed6499fd6f37-image0.jpeg
Hibernate: 
    insert 
    into
        item_contents
        (contents, detail_image_url, is_deleted) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        item_counts
        (chat_counts, hits, is_deleted, like_counts) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        item
        (created_at, updated_at, category, item_contents_id, item_counts_id, is_deleted, price, region_id, seller_id, status, thumbnail_url, title) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s8uN9/btszNRtPya5/5mpTKnEdkSavaoFBUDVkR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s8uN9/btszNRtPya5/5mpTKnEdkSavaoFBUDVkR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s8uN9/btszNRtPya5/5mpTKnEdkSavaoFBUDVkR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs8uN9%2FbtszNRtPya5%2F5mpTKnEdkSavaoFBUDVkR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;198&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;510&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;a href=&quot;https://github.com/masters2023-2nd-project-05/second-hand-BE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Secondhand 레포지토리&lt;/a&gt;에서 확인 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글에 대해 잘못된 부분이 있다면 댓글로 알려주시면 감사하겠습니다  &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;&lt;b&gt;참고링크&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;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-async&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.baeldung.com/spring-async&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dzone.com/articles/effective-advice-on-spring-async-part-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Effective&amp;nbsp;Advice&amp;nbsp;on&amp;nbsp;Spring&amp;nbsp;Async:&amp;nbsp;Part&amp;nbsp;1&amp;nbsp;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mangkyu.tistory.com/175&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mangkyu.tistory.com/175&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dahye-jeong.gitbook.io/spring/spring/2020-04-09-aop&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dahye-jeong.gitbook.io/spring/spring/2020-04-09-aop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>⚙️ Frameworks, Libraries/  Spring</category>
      <category>async</category>
      <category>Future</category>
      <category>spring</category>
      <author>Iirin</author>
      <guid isPermaLink="true">https://new-pow.tistory.com/82</guid>
      <comments>https://new-pow.tistory.com/82#entry82comment</comments>
      <pubDate>Mon, 6 Nov 2023 12:14:05 +0900</pubDate>
    </item>
  </channel>
</rss>