<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Gatsby Starter Blog RSS Feed]]></title><description><![CDATA[Gatsby Starter Blog RSS Feed]]></description><link>https://jdalma.github.io</link><generator>GatsbyJS</generator><lastBuildDate>Sun, 29 Mar 2026 16:07:46 GMT</lastBuildDate><item><title><![CDATA[2025년 회고]]></title><description><![CDATA[들어가며 올해 1월에 회사를 새로 입사하고 수습 기간을 보내느라 이제야 늦은 2025년 회고를 작성한다. 어쩌다보니 회고를 적을 때 마다 퇴사로 시작하고 있지만.. 2025년에 배우고 느낀 점을 정리해보자. 퇴사 입사한 지 9개월 만인 2025년…]]></description><link>https://jdalma.github.io/2025y/retrospect/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/retrospect/</guid><pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;들어가며&quot; style=&quot;position:relative;&quot;&gt;들어가며&lt;a href=&quot;#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0&quot; aria-label=&quot;들어가며 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;올해 1월에 회사를 새로 입사하고 수습 기간을 보내느라 이제야 늦은 2025년 회고를 작성한다.&lt;br&gt;
어쩌다보니 회고를 적을 때 마다 퇴사로 시작하고 있지만.. 2025년에 배우고 느낀 점을 정리해보자.&lt;/p&gt;
&lt;h1 id=&quot;퇴사&quot; style=&quot;position:relative;&quot;&gt;퇴사&lt;a href=&quot;#%ED%87%B4%EC%82%AC&quot; aria-label=&quot;퇴사 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;입사한 지 9개월 만인 2025년 8월, 더스윙에서 퇴사했다.&lt;br&gt;
작년 회고에 작성한 것 처럼 입사할 때 기대했던 스윙 서비스가 아닌 스윙 바이크를 혼자 담당하게 되어 아쉬운건 사실이였지만, 퇴사 사유는 아니다.&lt;br&gt;
오히려 내가 맡은 스윙 바이크 서비스를 궤도에 올리겠다는 목표를 갖게 됐다.&lt;/p&gt;
&lt;p&gt;당시 스윙 바이크는 엑셀 시트만으로 200억이 넘는 매출을 내고 있었다. 기존에 개발된 어드민이 있긴 했지만 실무자들의 요구사항에 맞게 개발되어 있지 않아 오히려 엑셀과 어드민에 이중으로 작업해야 하는 상황이었다.&lt;br&gt;
필요한 기능이 정확하게 개발되어 있지 않고 에러가 발생해도 이를 처리해 줄 개발 담당자가 없었다. 기존 개발자들도 이미 퇴사한 뒤라 인수인계를 받을 수 있는 상황도 아니였다.&lt;/p&gt;
&lt;p&gt;이런 상황을 처음 들었을 때 막막하긴 했지만, 오히려 도전 욕구가 샘솟았다. 아무리 힘들어도 이 어드민을 실무자들이 실제로 사용할 수 있는 수준으로 만들고 나서 퇴사하겠다는 마음을 먹게 됐다.&lt;/p&gt;
&lt;p&gt;쉽지 않은 과정이었다. 실제로 어드민을 사용하는 담당자들은 대구에서 근무했기에 협업이 어려웠고, 바이크 구독,계약 관리라는 도메인 자체도 생소해서 이해하는 데 시간이 많이 걸렸다.
그럼에도 결국 실무자들이 사용할 수 있는 수준의 어드민을 만들어냈고, 스윙 바이크가 업계 1위를 달성하는 데 기여할 수 있었다.&lt;/p&gt;
&lt;p&gt;맡은 서비스에만 머무르지 않으려 했다. 스윙, 디어 (공유 모빌리티 서비스), IoT 파이프라인, 인프라에도 꾸준히 관심을 가졌다.&lt;br&gt;
해당 업무를 경험할 기회를 달라고 팀장님께 자주 어필했고, 이슈나 장애가 발생하면 새벽에도 나타나 어깨너머로 배우며 도움을 주기도 했다.&lt;br&gt;
다행히 바이크에 신규 IoT 모델을 추가하는 작업을 맡게 되면서 IoT 인프라를 경험하고 이해할 수 있었다.&lt;/p&gt;
&lt;p&gt;바이크 어드민을 궤도에 올리고 IoT 작업까지 경험하면서부터 다른 서비스와 회사에 조금씩 눈이 갔다.&lt;br&gt;
더스윙에서는 기술적 의사결정 대부분을 혼자 진행하다 보니, 협업의 밀도가 높고 도메인 복잡도가 큰 서비스를 경험하고 싶다는 갈증이 생겼다.&lt;br&gt;
이전부터 돈을 다루는 커머스나 핀테크 서비스에 관심이 있었기에 이직을 준비하기로 마음먹었다.&lt;br&gt;
회사를 다니면서 면접을 여러 차례 봤지만, 실무와 면접 준비를 같이 하기에는 어느 한 개에도 집중할 수 없었다.&lt;br&gt;
결국 퇴사 후 내가 원하는 도메인과 서비스에 온전히 집중하며 준비하기로 결심했다.&lt;/p&gt;
&lt;h1 id=&quot;스타트업에서-성장한-것&quot; style=&quot;position:relative;&quot;&gt;스타트업에서 성장한 것&lt;a href=&quot;#%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%97%90%EC%84%9C-%EC%84%B1%EC%9E%A5%ED%95%9C-%EA%B2%83&quot; aria-label=&quot;스타트업에서 성장한 것 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;더스윙은 근무 강도가 높고 직설적인 피드백이 오가는 회사였다. 쉬운 환경은 아니었지만, 좋은 동료들 덕분에 힘든 만큼 값진 경험을 할 수 있었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;사업적 이해&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;실무자들과 협업하면서 지금 당장 필요한 기능이 무엇인지 파악하고, 가장 적은 비용으로 가장 큰 효과를 줄 수 있는 기능을 개발하는 게 나의 임무였다.&lt;/li&gt;
&lt;li&gt;이런 환경에서 일하다 보니 내가 개발하는 기능이 어떤 문제를 해결하는지, 지금 가장 급한 문제가 맞는지를 자연스럽게 따지게 됐고, 실무자들의 업무 흐름을 더 깊이 이해하게 됐으며 내가 누구를 위해 존재하는지 다시 생각하게 된 계기였다.&lt;/li&gt;
&lt;li&gt;이 경험을 통해 &lt;strong&gt;결국 사업이 존재해야 개발이 존재할 수 있다&lt;/strong&gt;는 걸 깨달았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문제를 해결하는 방법&lt;/strong&gt;
&lt;ul&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;strong&gt;몰입의 가치&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;주 5일 기준 60~70시간 정도를 9개월간 일했다. 자연스럽게 집,회사를 반복하면서 문제를 해결하기 위한 고민을 많이 했다.&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;strong&gt;책임감&lt;/strong&gt;
&lt;ul&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;/ol&gt;
&lt;p&gt;더스윙에서 좋은 인연도 만나게 됐고, 개발자로서 직업을 대하는 자세도 배울 수 있었던 시간이었다. 퇴사하면서도 감사한 마음이 컸다.&lt;/p&gt;
&lt;h1 id=&quot;취업-준비&quot; style=&quot;position:relative;&quot;&gt;취업 준비&lt;a href=&quot;#%EC%B7%A8%EC%97%85-%EC%A4%80%EB%B9%84&quot; aria-label=&quot;취업 준비 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;퇴사하고 본격적으로 취업 준비를 시작했다. 세 번의 이직을 하면서 항상 최종 면접을 통과한 첫 번째 회사에 입사해왔기에, 이번에는 여러 회사에 붙어보고 직접 골라서 가보고 싶었다.&lt;/p&gt;
&lt;p&gt;가장 먼저 한 일은 실무에서 장애를 해결하면서 깊게 파보지 못했던 부분들을 딥다이브하는 것이었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;코루틴 딥다이브&lt;/li&gt;
&lt;li&gt;네트워크 통신 에러 딥다이브&lt;/li&gt;
&lt;li&gt;DBCP 풀사이즈 튜닝하기&lt;/li&gt;
&lt;li&gt;MySQL 실험해보기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 정리한 내용들은 기술 면접에서 실제로 질문을 많이 받았고, 딥다이브하는 습관과 능력을 긍정적으로 평가해주시는 분들이 많았다.&lt;br&gt;
2025년 4월부터 꾸준히 지원하면서 총 171개를 지원했다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/56288197f7dd9874d7f103c6776e7feb/e8950/support_results.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABoklEQVR42p2Sf2vbMBCG8/2h/5W2nyNQRjdKYLAuWxeTrDA3yZzEVmwH24lsz5l/+5nssNBBCukOhDgkPbp73+vxIpqmoSh+I4SJaZq4rkscx1RVxbnRe5mURUGS5Xh+QCjD9gs8z8P3ve6zdp0NbC/H8S9sZ00U2SqXbDaCIPD+eVDXDVFaECd7pNyRZtlp4D5J2O12NHVNXRVd60EQYFmGWk9Y6zWzpeDJjnlYhhhOQOD77KOtup+Tpil5nh+A8/mcr4+P2LaNKQT68zOTyXd0XccwFghrhnBtNqsp1fKByF3iiZ800gBfZytm6t0aqQrqgJqm8f7uDm08ZqrAodKtbI04pVm5J40lYeQTJA5e6mE6VlftseXRaNQBh5MJ45sbSsv6K+zRjFOmtFlWZeRFrqQ6nPV8Re73+wzu7/mkKv12eUm6WJAqLVvXX4/mdZeHwyEfVIUtULu6YjoY8EPpGkfRcQLeNIdjpV3b8me1a9fXRMqU80f5BLA15d3tLR+VIV8uLihXq0Nlqu3/AkopcdScmY6DqdqtttujKW8F/gE2YJWGsG8eXgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;support results&quot;
        title=&quot;&quot;
        src=&quot;/static/56288197f7dd9874d7f103c6776e7feb/1cfc2/support_results.png&quot;
        srcset=&quot;/static/56288197f7dd9874d7f103c6776e7feb/3684f/support_results.png 225w,
/static/56288197f7dd9874d7f103c6776e7feb/fc2a6/support_results.png 450w,
/static/56288197f7dd9874d7f103c6776e7feb/1cfc2/support_results.png 900w,
/static/56288197f7dd9874d7f103c6776e7feb/21482/support_results.png 1350w,
/static/56288197f7dd9874d7f103c6776e7feb/d61c2/support_results.png 1800w,
/static/56288197f7dd9874d7f103c6776e7feb/e8950/support_results.png 2000w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;11월 말부터 12월 초까지는 면접이 몰려서 체력적으로 굉장히 힘든 기간이었다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a8fed7f5bc85f245c510bdd7f7d8f08b/ae3b6/calendar.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 83.55555555555554%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAABc0lEQVR42o1UC3bDIAzj/rfdAuGXwCxjUejSt9Hn2KRFlmVTF2PsIYQOz3Xfd7+uS+MqHvsmH3iu+b41PXuep5rDw3uvG3wJuwxQY/HTDIDvAQjDWZCCbYArw1KrHbx7zrmXUoTVAMRiQtgjIDYAAVgVj0UmjGvdWTP5R0Bqwuw8gFh1NQ25Z/yvkqf4xhorSekpJU2Yc5kaEuwXIDUhK2q1Ml8ZqydDq3Ir+ZnhZRLcm25PGgLHEd1bU/DDUqp2l91madU6jzij6/Dy7pCzagQcDE/TZozIYDk6u5bJRmRJiuGGgQzNAfX7ODRDEDBkDSn3KKIXiaMcjIVx6UkYIYYBHr/3QoosnTeq2ICZztuHRpARR4ist7EB4JcxfGrKK246NpDkU1MUMC4tn6NijXi/KQT/s8tPgz3+YXa2TMh5XW8KR8/hcUjJ6xxiVKaedq9l032q/Syth4JxuubgA4zmcJWQAX5l0t5YDRVfti7oSowfYBM5p8SWKvQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;calendar&quot;
        title=&quot;&quot;
        src=&quot;/static/a8fed7f5bc85f245c510bdd7f7d8f08b/1cfc2/calendar.png&quot;
        srcset=&quot;/static/a8fed7f5bc85f245c510bdd7f7d8f08b/3684f/calendar.png 225w,
/static/a8fed7f5bc85f245c510bdd7f7d8f08b/fc2a6/calendar.png 450w,
/static/a8fed7f5bc85f245c510bdd7f7d8f08b/1cfc2/calendar.png 900w,
/static/a8fed7f5bc85f245c510bdd7f7d8f08b/21482/calendar.png 1350w,
/static/a8fed7f5bc85f245c510bdd7f7d8f08b/d61c2/calendar.png 1800w,
/static/a8fed7f5bc85f245c510bdd7f7d8f08b/ae3b6/calendar.png 2836w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;최종적으로 5개 회사에 합격했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A회사, B회사&lt;/strong&gt; : 도메인이 흥미로워 지원했지만, 1차·2차 면접에서 의미 있는 대화를 많이 나누지 못해 입사를 포기했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C회사&lt;/strong&gt; : 3차 면접까지 있는 회사였는데, 1차를 통과한 뒤 2·3차를 하루에 몰아서 5시간 정도 봤다. (도중에 식사 시간이 있어서 김밥도 사주셨다.)
&lt;ul&gt;
&lt;li&gt;협업할 모든 팀의 인원과 면접을 봐야 했다. 그런데 마지막 3차에서 인사 팀장님과의 면접이 굉장히 불쾌해서, 최종 합격 전화를 받았을 때 입사를 포기하겠다고 말씀드렸다.&lt;/li&gt;
&lt;li&gt;전화를 주신 개발 팀장님이 나를 많이 마음에 들어해주셔서 아쉬웠고, 나중에 생각이 바뀌면 다시 연락 달라고 해주셔서 감사했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D회사&lt;/strong&gt; : 마지막까지 정말 고민한 회사다. 관심 있던 도메인이었고 트래픽이나 기술적 복잡도도 높았다.
&lt;ul&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;strong&gt;E회사&lt;/strong&gt; : 평소 관심 있던 도메인이었고, 면접에서부터 도전적인 업무를 진행하고 있다고 말씀해주셔서 입사를 결정했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;퇴사라는 선택이 무모할 수도 있었지만, 4개월간 열심히 준비한 보람이 있었다.&lt;/p&gt;
&lt;h1 id=&quot;런닝&quot; style=&quot;position:relative;&quot;&gt;런닝&lt;a href=&quot;#%EB%9F%B0%EB%8B%9D&quot; aria-label=&quot;런닝 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;올해는 회사 일이 바빠서 많이 못 뛰었는데, 퇴사 이후에 자주 뛰면서 평균 주 2회까지 끌어올렸다.
회사에서 주말 사내 스터디를 만들었는데, 이때 팀원들을 런닝으로 꼬셔서 주말에 1번이라도 꾸준히 뛴 게 도움이 됐다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/da8ba3e003b1ec288a74bf04deb47176/ad68d/running1.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 97.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAATABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAMCAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB9ezKVcLPIlgv/8QAGxAAAgIDAQAAAAAAAAAAAAAAABEBEgIQIiP/2gAIAQEAAQUC9LYuooObCFGv/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8BIf/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABkQAAEFAAAAAAAAAAAAAAAAABEAIjAxQv/aAAgBAQAGPwLIKdcH/8QAHRAAAgEEAwAAAAAAAAAAAAAAAAERECFBUWGh0f/aAAgBAQABPyFxdpdqCyknlFPQVzexDSWcVP/aAAwDAQACAAMAAAAQPAf8/8QAFxEBAQEBAAAAAAAAAAAAAAAAARARUf/aAAgBAwEBPxBQmOT/xAAWEQADAAAAAAAAAAAAAAAAAAABEBH/2gAIAQIBAT8QhVK//8QAHxABAAIBAwUAAAAAAAAAAAAAAQARMSFBURBxgZHx/9oACAEBAAE/EA0qwBO485hA1HUKL6LtoZ9uYrFAW7bxWeYzNre7H6IaT//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;running1&quot;
        title=&quot;&quot;
        src=&quot;/static/da8ba3e003b1ec288a74bf04deb47176/8e1fc/running1.jpg&quot;
        srcset=&quot;/static/da8ba3e003b1ec288a74bf04deb47176/863e1/running1.jpg 225w,
/static/da8ba3e003b1ec288a74bf04deb47176/20e5d/running1.jpg 450w,
/static/da8ba3e003b1ec288a74bf04deb47176/8e1fc/running1.jpg 900w,
/static/da8ba3e003b1ec288a74bf04deb47176/ad68d/running1.jpg 1170w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dc45d94c311edb6dcabe29846474880e/ad68d/running2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 147.55555555555554%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAeABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAIBAwX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAH2+kWYCbizAf/EABYQAQEBAAAAAAAAAAAAAAAAABBBIf/aAAgBAQABBQKmtKf/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAXEAEBAQEAAAAAAAAAAAAAAAAQMQEy/9oACAEBAAY/AprXlr//xAAbEAACAwEBAQAAAAAAAAAAAAABEQAQIVExgf/aAAgBAQABPyHBAqOsAfKMOMxPRRhevHFHX//aAAwDAQACAAMAAAAQsMYw/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAGxABAQEBAQADAAAAAAAAAAAAAREAMUEhwdH/2gAIAQEAAT8QfnVe0qaMkcF5TjX3iwrX13WRNA5H7hpebrBgMjwdGWu//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;running2&quot;
        title=&quot;&quot;
        src=&quot;/static/dc45d94c311edb6dcabe29846474880e/8e1fc/running2.jpg&quot;
        srcset=&quot;/static/dc45d94c311edb6dcabe29846474880e/863e1/running2.jpg 225w,
/static/dc45d94c311edb6dcabe29846474880e/20e5d/running2.jpg 450w,
/static/dc45d94c311edb6dcabe29846474880e/8e1fc/running2.jpg 900w,
/static/dc45d94c311edb6dcabe29846474880e/ad68d/running2.jpg 1170w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;마라톤 대회는 5번 정도 나갔는데, 기록 사진을 캡처해놓지 않아서 그나마 캡쳐해놓은 하프 대회 기록을 올린다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dc140bbc233551e9be6fa8895012fb65/105d8/half.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 216.44444444444443%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAArCAIAAAD3xz8iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGs0lEQVR42pVW628bxxE/RyLvtbt3vMfukRTF95uUSIkiJT5E0pJKS7VQqw/UhVDUKFzUVtIWrgoEleG6kAw0DpIPRVIbtgMn+Tv6JW0RIF/6IUCD9s/p7+4kWzCKFv1xudidndmZ252ZHSkSiUSjUdu2E4kEY2x+fj7ynwC6pmmWZYGnUCg4jjM3Nyfhj7VyubzeW4c8NgKTGkBRFPSY6rouy/LCwkKj0VhcXNza2ioUchIghOABsJnneckAiQDVarVWq2HwiggG8DNmUCrALuXzeXBks1kohz0Y5HI59KCPRqPxeAx6Op2+IKLP16q5nx7ytdW0BFKn04Ex9XodHOBLpVLFYhEWdgIsLy9j03SATAarueVm6rM/Rne3PQl8UIIeHOjBAUNAwXaglEolfCqm4SqsKJWKAXsJvVQOgNnS0tLp6enh4eGdO3fu3bt3dHR080c3T05Ojo+P9/f3T89OT+6fPHjwYHV1FYrK5QqkpEIA7A0lOMaNjY1GszHoD2Zbs9X26rA/vLZ7rd1u70x3diY7k9GkWquGhgCSEcA0TMqoK9yij4LJTZlHdU+nnKaSKS/hyQtRJS+rrsooMy4gkQC4+sh8ZNgZHv387p0f3y1czSvfjqrbSqIdf/vWO9PdiXIzSn+h0RYhMiU0FCISDUEotShpEbpIWJOyKKMZQnOEFIiaUnRFp2VCk4TUCY0FzAECYdz5PCN7qv6urB8p2m+j+ram/0zRHkf1+zK5q5LvaNqTKJlo+kOZbGv0LeaLvBaeY/oPVP03iv6RTO9pbEzIL1XtaUQ7i+rHMt3RyR8U8o6qn8j691QqMWq+EmaUapQuEOwqr0TnV+YiakRva2RNJx2dLhGFKHInotRlsqXBeJ+ZvRIO5IlKjKh5tX91d7J7bXs3u5hVN2S6q2ttda3V3Rpuj9cn+luQZKHkJWHqn6DDnfF03B/2V7ur7oqj9zQy0sySCf8ZjUfN5SYzGb0EXxj3hEtD3ME3b9y4sb+3nywl5B9GjBlVBnJ/bXBwcLC3twc/g8+CDcwQ8YWhUL4ExLOCQJYVNaLKERktil8AhPdlTv+eY7FY1Q/caq1eg3OFMQwUioVKtQJ6SEEPf6xWz6eVSgWCEuL70aNHn7789Pmz57dv3z7+9fHLly9fPH9x6ye3zs7OPv/s88fvPf7kxSdPnzxFwDx79uzJx08+/OBDhBAiV0L26va6wwlCoI8A6Kx3BpuDwXTQWmmtra0Np4P14Xp/c8NfbbUHw8FgMugNelhC5pEMYlhFi+0RY4vZ3DanhjFmxi61mjHbsdl1Yk6YsU1jJdMmjpU1fcrYgEqcmWQwQ6SFqHOvKIQneI2LBhfLXCxyL+6JJS6qQjS5lxPCEV7KEy0uKtwTnh9VBjV4AffrOFObC+4MbAycHdstuCIhnE3bXXecmcVrLjcFz3GMQRGuOBcWBc5bLl9xRZrzhsvrnHddnvE1+4MW58uuCDSLjHDXHDDjmANhYvCK6w4ct+u4i67bduBb7tDmaWjmPnHNgSpYxy2O3d2RDQoS8IXwEuebLt90RJbzvuNuO+41h1f97UH3RlzM3HgtHrfjIs/db9m8f6EZbm6nbbtqWQ3LTth2EYOYjXHSdlwnVja1BY0UdeL6weQkHH+1ZOGe/NP2HRwhplAqU6pTqmLAMGWE4aGJO/GH9x8++t3Z+++9f/DdA3gqUxh44JvngYFLi1nWG82ybbxRS63lb/75ry/++rev//HNRx//CQ+XFTDTMDD8RMIMoilUky8axgrTFXn+SqWQ/cuXX/35iy+/+vvXcMnonMR0lahRQvRAmBDXdRfqXbPcM0vdsKXaE6uybhQ6bn0wO3z78N0Pbv7q90tb36f5VaPU9RoDx0sSXYNmYpqmk8yYibyZyJnxrBnPOAs5f5zIGYlsxOBGqqq5i6qdjCXzRiJnLxRMyz5PvX7G1zVGXjd6aWwyYhDVoDra+arun9j5NyMyzf+BNxlep6EwrRj/D14L+1f1X4GLe4NynsOQ0GAGSgYMlABYCL0vzFugYxU3Eq7Cc+BeKEN8YWyDgiCTySAthW81yg8IFy+A1IWMg7wJBjzD6HMB/ByGLfFeI61iARUEqL1eD7kWMvUAeI3xbqO+AANqDVCuX7+ONISP9XMYNpvNZtgJxuATsAWyN+iYIvTwwf1+P0yXoKDf3NzEFHT/cQ9ring8DlZUIM1mE1MUOv6jb5qwudVqwZyQAT1WYZcfkgAqNYzC2g9TCEMzTIUGlFLT6RR2gnjlypWwFMRR4eQkSfo3F01M0LjSuswAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;half&quot;
        title=&quot;&quot;
        src=&quot;/static/dc140bbc233551e9be6fa8895012fb65/1cfc2/half.png&quot;
        srcset=&quot;/static/dc140bbc233551e9be6fa8895012fb65/3684f/half.png 225w,
/static/dc140bbc233551e9be6fa8895012fb65/fc2a6/half.png 450w,
/static/dc140bbc233551e9be6fa8895012fb65/1cfc2/half.png 900w,
/static/dc140bbc233551e9be6fa8895012fb65/105d8/half.png 1170w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;확실히 체중이 안 줄어서 그런지 기록이 크게 좋아지지 않는 것 같다. 그래도 3년 동안 꾸준히 달리고 있는 것에 만족한다.&lt;/p&gt;
&lt;h1 id=&quot;총평&quot; style=&quot;position:relative;&quot;&gt;총평&lt;a href=&quot;#%EC%B4%9D%ED%8F%89&quot; aria-label=&quot;총평 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;2024년에 작성한 2025년 목표를 얼마나 달성했을까&lt;/p&gt;
&lt;ol class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 풀코스 완주하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 러닝 마일리지 총 1000km 쌓기 → 913km로 87km 부족하다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 책 30권 읽기 → 5권 밖에 못 읽었다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 인프런 강의 5편 이상 보기 → 필요한 부분만 뛰엄뛰엄 봐서 완강은 한 개도 못했다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 사람들 앞에서 기술 발표 해보기 → 딱히 발표한 적은 없는 것 같다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 월에 1개씩 기술 블로깅하기 (총 12개) → 총 8개 작성해서 4개 부족하다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 회사에서 내가 맡은 서비스는 무조건 해내고, 다른 서비스도 업무 처리하기 → 완수했다!&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; (개인적으로) 사내 개선 태스크 10개 만들어서 해내기 → 10개는 아니고 3,4개 정도 한 것 같다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 도전을 두려워 하지 말기 → 퇴사와 취업준비에 도전하여 취업하였으니 완수했다!&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; (간단한) 서비스 만들기 → 생각만하고 실천을 못 했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;느낀점&quot; style=&quot;position:relative;&quot;&gt;느낀점&lt;a href=&quot;#%EB%8A%90%EB%82%80%EC%A0%90&quot; aria-label=&quot;느낀점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;요즘 AI의 발전에 따라 개발자의 역할이 변하고 있다는 걸 체감하고, FOMO도 함께 느끼고 있다.&lt;br&gt;
IDE를 사용하는 시간이 줄어들고 직접 코드를 작성하는 비중이 줄면서, 개발자의 무게중심이 더 거시적인 방향으로 이동하고 있다고 본다.&lt;/p&gt;
&lt;p&gt;아직은 개발 지식이 없는 사람이 일정 수준 이상의 복잡도를 가진 애플리케이션을 만드는 데 한계가 있다.&lt;br&gt;
코드베이스를 이해하지 않아도 되고, 고가용성과 내결함성에 대한 지식 없이도 적절한 트레이드오프를 알아서 잡아주는 AI가 나온다면 개발 지식이 정말 필요 없어질 수도 있다.&lt;br&gt;
하지만 아직까지는 개발자의 업무 범위가 달라지고 있을 뿐, 역할 자체가 사라질 단계는 아니다.&lt;/p&gt;
&lt;p&gt;겁먹고 움츠러들기보다는 더 열심히 뛰어들고 많이 경험해봐야 한다고 생각한다. (꽤 재밌다고 생각한다.)&lt;br&gt;
회사에서 에이전틱 코딩 TF 리드를 맡게 됐는데, 이 기회에 더 열심히 할 명분도 생겼다.&lt;/p&gt;
&lt;p&gt;하던 대로 꾸준히 노력하자.&lt;/p&gt;
&lt;h2 id=&quot;2026년-목표&quot; style=&quot;position:relative;&quot;&gt;2026년 목표&lt;a href=&quot;#2026%EB%85%84-%EB%AA%A9%ED%91%9C&quot; aria-label=&quot;2026년 목표 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;러닝 마일리지 총 1000km 쌓기&lt;/li&gt;
&lt;li&gt;책 20권 읽기&lt;/li&gt;
&lt;li&gt;사람들 앞에서 기술 발표 해보기&lt;/li&gt;
&lt;li&gt;올해 6개 기술 블로깅하기&lt;/li&gt;
&lt;li&gt;회사에서 내가 맡은 서비스는 무조건 해내고, 다른 서비스도 업무 처리하기&lt;/li&gt;
&lt;li&gt;사내 개선 태스크 10개 만들어서 해내기&lt;/li&gt;
&lt;li&gt;도전을 두려워 하지 말기&lt;/li&gt;
&lt;li&gt;(간단한) 서비스 만들기&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[2026년 기록]]></title><description><![CDATA[Amazon VPC 네트워킹 원리와 보안 데이터 중심 애플리케이션 설계 2판 2025-02-13: 요즘 개발자를 위한 시스템 설계 수업 시스템 설계는 AI…]]></description><link>https://jdalma.github.io/2026y/review/</link><guid isPermaLink="false">https://jdalma.github.io/2026y/review/</guid><pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3 id=&quot;amazon-vpc-네트워킹-원리와-보안&quot; style=&quot;position:relative;&quot;&gt;Amazon VPC 네트워킹 원리와 보안&lt;a href=&quot;#amazon-vpc-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%82%B9-%EC%9B%90%EB%A6%AC%EC%99%80-%EB%B3%B4%EC%95%88&quot; aria-label=&quot;amazon vpc 네트워킹 원리와 보안 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;데이터-중심-애플리케이션-설계-2판&quot; style=&quot;position:relative;&quot;&gt;데이터 중심 애플리케이션 설계 2판&lt;a href=&quot;#%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A4%91%EC%8B%AC-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%84%A4%EA%B3%84-2%ED%8C%90&quot; aria-label=&quot;데이터 중심 애플리케이션 설계 2판 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;2025-02-13-요즘-개발자를-위한-시스템-설계-수업&quot; style=&quot;position:relative;&quot;&gt;2025-02-13: 요즘 개발자를 위한 시스템 설계 수업&lt;a href=&quot;#2025-02-13-%EC%9A%94%EC%A6%98-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%88%98%EC%97%85&quot; aria-label=&quot;2025 02 13 요즘 개발자를 위한 시스템 설계 수업 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;시스템 설계는 AI 시대에 개발자로서 관심가져야 할 부분이기도 하고 &lt;a href=&quot;https://johngrib.github.io/wiki/review/2025/#book&quot;&gt;종립님의 리뷰&lt;/a&gt;에서도 좋은 평가를 받았길래 읽어보았다.&lt;br&gt;
데이터 중심 애플리케이션 설계 2판과 같이 읽으면서 겹치는 부분도 있어 상호보완적으로 읽기 좋았다.&lt;/p&gt;
&lt;p&gt;이 책은 시스템 설계의 정의부터 분산 시스템을 이루는 특징과 필요성 그리고 분산 시스템의 근본적 한계인 CAP, (CAP의 확장인) PACELC, 비잔티움 장군 문제, FLP까지 설명하여 시스템 설계 입문으로 읽기 굉장히 친절하다고 느꼈다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;FLP&lt;/code&gt;: 합의가 끝날 수 있나? &lt;strong&gt;보장 불가 (비동기 + crash)&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;비잔티움&lt;/code&gt;: 배신자가 있어도 합의 가능한가? &lt;strong&gt;n &gt; 3f 일 때만 가능&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;CAP&lt;/code&gt;: 네트워크 파티션 단절 발생시 무엇을 포기하나? &lt;strong&gt;C 또는 A&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;PACELC&lt;/code&gt;: 정상일 때도 트레이드오프가 있나? &lt;strong&gt;L 또는 C&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;설계 단계에서는 데이터 흐름을 이해하는 것이 제일 중요하다.
&lt;ul&gt;
&lt;li&gt;데이터 구조, 수집, 저장, 처리, 검색 (DDAI에서와 동일한 강조였다.)&lt;/li&gt;
&lt;/ul&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&gt;
&lt;/li&gt;
&lt;li&gt;일관된 해싱을 보장하기 위한 방법
&lt;ul&gt;
&lt;li&gt;링 모양 해싱 + 가상 노드 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DNS의 재귀적 쿼리와 반복적 쿼리 방법&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분산 캐싱의 장점과 한계점&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;가상 면접 사례로 배우는 대규모 시스템 설계 기초 1,2권을 읽기 전에 이 책을 통해 분산 시스템에 대한 내용을 이해하는 것이 도움될 것 같다.&lt;/p&gt;
&lt;h3 id=&quot;2025-01-29-엔터프라이즈-애플리케이션-아키텍처-패턴&quot; style=&quot;position:relative;&quot;&gt;2025-01-29: 엔터프라이즈 애플리케이션 아키텍처 패턴&lt;a href=&quot;#2025-01-29-%EC%97%94%ED%84%B0%ED%94%84%EB%9D%BC%EC%9D%B4%EC%A6%88-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4&quot; aria-label=&quot;2025 01 29 엔터프라이즈 애플리케이션 아키텍처 패턴 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;회사에서 애플리케이션 아키텍처를 고민하는 상황이 있어서 고전인 이 책이 떠올라 읽어보았다.
지금 읽기에는 조금 주저하게 되긴 했지만, 오래전부터 많이 들었던 단어들과 패턴에 대해 더 자세하게 이해할 수 있는 계기가 되어서 좋았다.&lt;/p&gt;
&lt;p&gt;이 책에서 크게 두 가지 관점을 얻었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;도메인 논리의 구조를 이해하고 무엇이 적절한가?&lt;/strong&gt; -&gt; 트랜잭션 스크립트, 도메인 모델, 테이블 모듈&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 접근 전략의 트레이드오프&lt;/strong&gt; -&gt; 테이블/행 데이터 게이트웨이, 활성 레코드, 데이터 매퍼&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그리고 이 패턴들이 배치되는 두 계층의 책임을 명확히 구분한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain Layer&lt;/strong&gt; : 시스템이 존재하는 이유, 즉 &quot;비즈니스가 요구하는 것&quot;을 표현한다. 계산, 유효성 검증, 비즈니스 규칙 판단을 담당하며, 데이터가 어디에 저장되는지 화면에 어떻게 보이는지 모른다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Source Layer&lt;/strong&gt; : 도메인 객체와 외부 자원(DB, 파일, 외부 API) 사이의 통신을 담당한다. SQL 실행, 커넥션 관리, 결과를 도메인 객체로 변환하며, 비즈니스 규칙을 모른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 책을 읽고 멀티모듈과 순수 도메인 모듈에 대해 더 우호적이게 된 것 같다.
트랜잭션 스크립트와 데이터 게이트웨이를 이용해서 데이터 원본을 호출하는 경우도 어느 정도의 복잡성을 해결할 순 있지만, 복잡한 도메인에서는 도메인 모델과 데이터 매퍼 패턴을 이용해서 영속성에 대한 의존을 끊는 것이 더 적절하다고 설명한다.
인메모리 객체가 영속성 데이터베이스 구조에 대해 알고 있으면 한 쪽의 변화가 다른 쪽에 영향을 미치게 된다. 이 두 계층은 변경의 이유가 서로 다르기 때문이다.&lt;/p&gt;
&lt;p&gt;그렇다고 시스템 복잡도가 낮은 도메인에 적용하게 되면 불필요한 복잡도를 야기한다. 이 책을 통해 이런 트레이드오프를 간접적으로 경험할 수 있어 좋았다.
백엔드 팀에서도 순수 도메인 모듈에 대한 찬반이 대립 중인데, 계속 고민해 볼 영역인 것 같다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;고민거리&lt;/strong&gt;&lt;br&gt;
클래스 간 참조와 테이블 간 참조는 서로 방식이 다르다.&lt;br&gt;
팀과 팀원이 1:N으로 존재하는 경우, 클래스에서는 팀이 팀원을 컬렉션으로 보유할 수 있다. 하지만 테이블에서는 팀원이 팀을 참조해야 한다.&lt;br&gt;
이런 차이를 데이터 매퍼 영역에서 숨겨주는 것이 RDB 관점으로 개발하는 사고를 줄일 수 있지 않을까 생각해본다.&lt;br&gt;
성능을 위한 비정규화나 테이블 분할처럼 도메인 로직과 무관하게 스키마가 바뀌는 경우도 있다. 이런 변경이 도메인까지 전파되지 않으려면 결국 두 계층의 분리가 필요하다.&lt;br&gt;
한편으론, 테이블이 수정되는 경우는 도메인이 변경되는 것이 당연한 것 아닌가 싶기도 하다. 한번 직접 부딪혀봐야 감이 잡힐 것 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2025-01-15-일론-머스크&quot; style=&quot;position:relative;&quot;&gt;2025-01-15: 일론 머스크&lt;a href=&quot;#2025-01-15-%EC%9D%BC%EB%A1%A0-%EB%A8%B8%EC%8A%A4%ED%81%AC&quot; aria-label=&quot;2025 01 15 일론 머스크 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;현재 다니고 있는 회사의 CTO님에게 추천받아서 읽어보았다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;&quot;요즘은 개발자가 개발만 잘해서 경쟁력을 가질 수 없다. 사업에 대한 아이디어와 공감, 그리고 생각의 전환에 대한 고민이 필요하다.&quot;&lt;/code&gt; 라는 주제로 이야기를 나누다가 관련해서 추천해주실 만한 책이 있는지 여쭤보게 됐고, 그렇게 이 책을 알게 됐다.&lt;br&gt;
&lt;em&gt;이전 회사의 대표님도 일론 머스크의 팬이었는데, 책을 읽으면서 이전 회사와 대표님의 업무 방식이 떠올랐다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;이 책은 일론 머스크의 어린 시절부터 Zip2, 페이팔, 테슬라, 스페이스X, 뉴럴링크, 보링 컴퍼니, 그리고 트위터 인수까지의 여정을 다룬다.&lt;br&gt;
복잡한 가족 관계, 경영 스타일, 극단적인 리스크 감수, 피포위 심리 등 머스크의 성격을 깊이 있게 들여다볼 수 있다.&lt;/p&gt;
&lt;p&gt;대단한 사람이라는 사실은 자명하다. 하지만 직원들에게 가혹하고, 주변 사람들에게 상처를 주는 모습, 그리고 트위터 인수 과정에서 퇴직금과 스톡옵션 지급을 피하기 위해 인수 일정을 앞당긴 행동을 보면서 &lt;strong&gt;존경과 불편함을 동시에 느꼈다.&lt;/strong&gt;&lt;br&gt;
돌이켜보면 나는 후광효과에 빠져 있었던 것 같다. 부, 업적, 명성이 곧 그 사람의 인격이나 도덕성까지 보증한다고 무의식적으로 생각했기 때문이다.&lt;br&gt;
그럼에도 확실하게 배울 점은 &lt;strong&gt;제1원칙 사고&lt;/strong&gt;다. &lt;strong&gt;&quot;원래 그렇게 해왔으니까&quot;라는 유추적 사고를 거부하고, &quot;이것이 정말 필요한가? 왜 그런가?&quot;를 끝까지 파고드는 것이다.&lt;/strong&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 요구사항에 의문을 제기하라. 타당한 이유인가? 관행이나 약속, 규정 때문인가?&lt;/li&gt;
&lt;li&gt;이 요구사항을 준 사람의 이름이 누구인가?&lt;/li&gt;
&lt;li&gt;부품이든 프로세스든 최대한 제거할 수 있는가? 자동화할 수 있는가?&lt;/li&gt;
&lt;li&gt;빠르게 움직이고, 날려버리고, 반복하라.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;또한 곱씹어보게 되는 말들도 있었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;제거한 부분의 10퍼센트 이상을 복원할 필요가 없다면 충분히 제거하지 않은 것이다.&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;틀려도 괜찮다. 다만 잘못된 것을 옳다고 우겨서는 안 된다.&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;모든 기술 관리자는 실무 경험을 갖춰야 한다.&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;돌아보면, 나도 항상 최대한 정확하게, 리스크를 최대한 줄이기 위해 시간과 비용을 과하게 들인 경험이 있다.&lt;br&gt;
완벽한 설계를 위해 트레이드오프를 피하려 했던 순간들. 이 책을 읽으면서 때로는 빠르게 시도하고, 부수고, 다시 반복하는 것도 필요하다는 걸 느꼈다.&lt;/p&gt;
&lt;p&gt;다 읽고나니 CTO님이 왜 이 책을 추천해주셨는지 알 것 같다.&lt;br&gt;
개발만 잘하는 것을 넘어서, 당연하게 여겨왔던 것들에 의문을 던지고, 더 본질적인 문제를 파고드는 사고방식.&lt;br&gt;
존경과 불편함을 동시에 느끼게 한 책이지만, 일과 삶을 대하는 자세와 생각하는 방법을 배울 수 있었던 것 같다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[DBCP maximum-pool-size 튜닝하기]]></title><description><![CDATA[들어가며 HikariCP About Pool Sizing을 통해 Pool Sizing에 대한 가이드를 읽을 수 있다. 풀 사이즈 관련 설정들(maximumPoolSize, minimumIdle, idleTimeout…]]></description><link>https://jdalma.github.io/2025y/stress/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/stress/</guid><pubDate>Sat, 20 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;들어가며&quot; style=&quot;position:relative;&quot;&gt;들어가며&lt;a href=&quot;#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0&quot; aria-label=&quot;들어가며 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot;&gt;HikariCP About Pool Sizing&lt;/a&gt;을 통해 Pool Sizing에 대한 가이드를 읽을 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;풀 사이즈 관련 설정들(&lt;strong&gt;maximumPoolSize&lt;/strong&gt;, minimumIdle, idleTimeout)&lt;/li&gt;
&lt;li&gt;커넥션 생명주기 관리(maxLifetime, &lt;strong&gt;keepaliveTime&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;커넥션 요청 대기(&lt;strong&gt;connectionTimeout&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;난 DBCP 튜닝을 해본적이 없다. 기본값을 그대로 사용하거나, 막연히 큰 값을 설정하는 것을 여러 프로젝트에서 봐왔다.&lt;br&gt;
하지만 최적의 설정은 애플리케이션의 특성, 쿼리 실행 시간, 동시 사용자 수에 따라 달라지기 때문에 튜닝을 연습해보려 한다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;strong&gt;의도적으로 병목 상황을 만들고&lt;/strong&gt;, k6 부하 테스트와 Grafana 모니터링을 통해 &lt;strong&gt;임계점을 직접 확인&lt;/strong&gt;하면서 최적의 pool size를 찾아보려 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing&quot;&gt;HikariCP GitHub - About Pool Sizing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP#gear-configuration-knobs-baby&quot;&gt;HikariCP Configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;부하-테스트-기준&quot; style=&quot;position:relative;&quot;&gt;부하 테스트 기준&lt;a href=&quot;#%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B8%B0%EC%A4%80&quot; aria-label=&quot;부하 테스트 기준 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e097ac830ab6beafb96d3f4e6ef6cb4e/ac522/api.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.22222222222222%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABcklEQVR42nWR247TQBBE/a088Ts88xO8IfHGaqVlb0pWIhIJeGM7viT2jB3fYif2+NA2GARSWir1jDRdXVNl8buGYeD/quuaJEk4HnPpijhWKJXS9wPj87KqUTpFZzlaelEUWF3XTcNj3+/3MhQTBAFhGE73vjcUmU+R/uBc+3RNJAjpmwBzDtH7FZF9RxJ9Z5RkLZYvk7q6qnAcB9/3sW17gtZa1B1x7HvWq49U6YJSPQmeyeMHTuoLt/efePvuAzcPN5Mw69t6LSp6DocDaZqSZdnUUyFLRO2oMnAeidzPVPqZJlvQHF+osyVdvuDp6x1v3t/yuFr+IhzJ5i9fLpd/YXqMqE+Vh4rXtJVLU3p/0FaC0qHNN3IOJ18trpUxcDoxjGhaTFFiLt00NMOYgV5ghr+hXiXs25bS89Cvr+jtlnSzIYsP5IUkqtRky5jqHOpcVwnP8uVIPHSEdDemLl56ux2u67KVBVorWlk6WzYT/gTQWWEf4s4jfwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;api&quot;
        title=&quot;&quot;
        src=&quot;/static/e097ac830ab6beafb96d3f4e6ef6cb4e/1cfc2/api.png&quot;
        srcset=&quot;/static/e097ac830ab6beafb96d3f4e6ef6cb4e/3684f/api.png 225w,
/static/e097ac830ab6beafb96d3f4e6ef6cb4e/fc2a6/api.png 450w,
/static/e097ac830ab6beafb96d3f4e6ef6cb4e/1cfc2/api.png 900w,
/static/e097ac830ab6beafb96d3f4e6ef6cb4e/21482/api.png 1350w,
/static/e097ac830ab6beafb96d3f4e6ef6cb4e/d61c2/api.png 1800w,
/static/e097ac830ab6beafb96d3f4e6ef6cb4e/ac522/api.png 2941w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;javascript&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// k6/scripts/phase1-read-test.js
export const options = {
  scenarios: {
    ramping_load: {
      executor: &amp;#39;ramping-vus&amp;#39;,
      startVUs: 1,
      stages: [
        { duration: &amp;#39;20s&amp;#39;, target: 10 },   // Warm-up
        { duration: &amp;#39;30s&amp;#39;, target: 50 },   // Tomcat 스레드 포화 (max: 100)
        { duration: &amp;#39;30s&amp;#39;, target: 100 },  // Tomcat 초과
        { duration: &amp;#39;30s&amp;#39;, target: 125 },  // 고부하
        { duration: &amp;#39;30s&amp;#39;, target: 150 },  // 최대 부하
        { duration: &amp;#39;20s&amp;#39;, target: 0 },    // Cool-down
      ],
    },
  },
};&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tomcat 최대 스레드를 100개로 제한&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DBCP 최대 커넥션 수를 5개로 제한&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 상황에서 점진적으로 부하를 줘보자&lt;/p&gt;
&lt;h2 id=&quot;정상적인-상황&quot; style=&quot;position:relative;&quot;&gt;정상적인 상황&lt;a href=&quot;#%EC%A0%95%EC%83%81%EC%A0%81%EC%9D%B8-%EC%83%81%ED%99%A9&quot; aria-label=&quot;정상적인 상황 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/78d4c62c1cb2a50f71ffb012578fe5df/10b63/baseline.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACjUlEQVR42j1TWXbbMAzUHVpbC0lxp/bYSpPWr69N/nv/+0wHdF8/YJgQCGAGw2bdTxgbcWkHXDqFr/TXVsENGmOvYGmjGqGZY1xE2+uad2n/5fYG7WBqrNcOjbYBY8pYP35h+/yAXVf0yuBtWXFfN5zTjKPMMLFAjw7KecTzFfF+YrCO8Yx5v8GEhGm7oxl9rEXK90e1dEqiZbcRg7HoWFy7QIvoeB7ZwC47/PYCk0v9LvGr0rBs2qRpg88zhtHzg68XZQpJ7Al7oFf8FsqCwiJxWtkwoGNsYKOOdPS0jrCtT2hcjOTHkh+LpXgGPXqeN8bP9UAilI2dDS8Plt9YyNMWNrakoGWxIwRsqeB9O9AoJjle+v36hj+Pdyz7G4Z8wxQidkIK5GmltyFXGiKn/1YyfqxPixzAMyc5h528NyHNyIScOUWKE/0EHxfyx+79UGHLFg3hlMwFlIJI77mMwGUWnq+c8ks3kLaAZtlu5HCBEl54+cr1i28ph4tYp8kTl+FznVK7jOswVvn8Nza80ivDgsLLQC4eLwmf9wAfHC7saLj9NO91SdLQcqKeeYaQSyK8yeGgLZmUHCfycmCiphtJmBl8XT0tcDGBl54XR8IU3kRajpQochWCR04BMQZSFHgOlR5RhWPT5jh23G8H9v3APM+YSPg8cQksepEXwETxilsWnWlCV4Qt3vhSeRNKrsKzo2w0f0QKVVvaVrhiQrSjLhfGVz478Yl5ltNo5kmuCLplw5EqsWl6vhTjA4XLCexYtei9Q4keLyXifU6URsLPvVR7bJQK7bEWSor6Zb6Y0CPySxR9U9cfE6UjUJ+yEMhZ5EH4QbSYEjxN8uS/xOe5cIGpPsue08qWBfJfAxeGFyZtTXgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;baseline&quot;
        title=&quot;&quot;
        src=&quot;/static/78d4c62c1cb2a50f71ffb012578fe5df/1cfc2/baseline.png&quot;
        srcset=&quot;/static/78d4c62c1cb2a50f71ffb012578fe5df/3684f/baseline.png 225w,
/static/78d4c62c1cb2a50f71ffb012578fe5df/fc2a6/baseline.png 450w,
/static/78d4c62c1cb2a50f71ffb012578fe5df/1cfc2/baseline.png 900w,
/static/78d4c62c1cb2a50f71ffb012578fe5df/21482/baseline.png 1350w,
/static/78d4c62c1cb2a50f71ffb012578fe5df/d61c2/baseline.png 1800w,
/static/78d4c62c1cb2a50f71ffb012578fe5df/10b63/baseline.png 1897w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tomcat 스레드보다 많이 요청됐지만 정상 처리됨&lt;/li&gt;
&lt;li&gt;초당 700개의 요청을 처리&lt;/li&gt;
&lt;li&gt;99%의 요청이 200ms 이내 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;각-조회-api-300ms-지연-상황&quot; style=&quot;position:relative;&quot;&gt;각 조회 API 300ms 지연 상황&lt;a href=&quot;#%EA%B0%81-%EC%A1%B0%ED%9A%8C-api-300ms-%EC%A7%80%EC%97%B0-%EC%83%81%ED%99%A9&quot; aria-label=&quot;각 조회 api 300ms 지연 상황 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;처리를 300ms 지연시켜서 DB Connection 반환을 지연시켜보자&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;부하 테스트를 하면 메트릭 집계 안되는 경우&lt;/strong&gt;&lt;br&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bb5cd49e74c797d08c2828cfdfdfc2d3/6c68b/disappear.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABQElEQVR42n2R226DMBBE+Yk0BF/wBQwYQkJzaapITfvS//+j6dj0oZWqPhytLfDMzm7RdBOaMCJV10ZsK41NqfC0+59NKTPrXWY2pUChbYC2LbRroVi30qLSFju91nxWvyllzf8HKB/zuTIBwvaQpkPhQuSjmkKKVeMwODyeHYytsZMaQpHafGP5KIkajNMFl9sD7TCzkQDDdJ4pC5tiCosxLvg4HXGfLd6PHof9CSqcYfsrFJ0raWjg2BE7FElwwXg4Q5gGKaVp+jyyQrsOodvj8/UNj2XBFAzOQ4Pr8QWqv8GMd9QUlX7iGNwamR1WpKTJVtSZdE5GhedC5jhTKKJrAqwx8NZiHwY694xDXITmvCqKVVnMckSr4E+ScCFrnzcs6K5Mi2SQPsR+QsgGHtMwwXA5TdujpVHFRZTib74AxBfCrOuIHN4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;disappear&quot;
        title=&quot;&quot;
        src=&quot;/static/bb5cd49e74c797d08c2828cfdfdfc2d3/1cfc2/disappear.png&quot;
        srcset=&quot;/static/bb5cd49e74c797d08c2828cfdfdfc2d3/3684f/disappear.png 225w,
/static/bb5cd49e74c797d08c2828cfdfdfc2d3/fc2a6/disappear.png 450w,
/static/bb5cd49e74c797d08c2828cfdfdfc2d3/1cfc2/disappear.png 900w,
/static/bb5cd49e74c797d08c2828cfdfdfc2d3/21482/disappear.png 1350w,
/static/bb5cd49e74c797d08c2828cfdfdfc2d3/d61c2/disappear.png 1800w,
/static/bb5cd49e74c797d08c2828cfdfdfc2d3/6c68b/disappear.png 1888w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
부하 테스트를 진행하면서 메트릭이 안보이는 경우가 있었는데 &lt;code class=&quot;language-text&quot;&gt;/actuator/prometheus&lt;/code&gt; API도 8080 포트로 조회하고 있어서 이 API 요청을 처리할 스레드가 없어서 메트릭이 조회가 안되었다.&lt;br&gt;
다른 포트로 할당해주면 된다. &lt;code class=&quot;language-text&quot;&gt;management.server.port&lt;/code&gt; 설정을 8080 포트와 분리해주면 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c9ce19f5b404148390d5a8538f517c12/6c68b/300ms-delay.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAChklEQVR42lWT6XLUMBCE/Qwp1pcs65blY8+wSyAJKX7x/k/UtORQBT+6ZJ0z0/O5ui5HRGUh6w6qFVDdsKvt0TQ9Du2ATiiM2kNSTSdRc22X+E+HRqB6NgoXKXDzFj9/fMfH+/uu1wecd2jEiNF6DMahVwadVGiHES3HTumingkJE9FwvRqMR28s7OkKc77BnC6f4vfxDOkcBm2hecZYAxM83DTBp0RxnDmuG9dmDAxSLccL4rzBuAliZESpmQGjcnPIVvAxyaD53MoAKwNrFzEy47KnDZJWCMbwjkYlteZDIycSkunHYHGdmRGzEaMpe/nhjt9tKVeh7mVR9naTIzYmcuW5RkiWnLOgrJ/wYMm/7g/8/vbA5XSHmh/QfmE2vpQt6eGY7XEWx+gZOOItedyXCR+Lh8glh3REmDb4OMPzUalcyUhrB0Vpmt1Lw70Az+ZEz/JTwBQDm+axeYML9ZWSIx+clhNsmFmaRU1cmk4UHTIGROcL0Wlzp01gIMdzDl+IzFPd46nZz/QZsb/YZBRamtnTh8us8XayeDtbeKdRi92zgZkKltxnREZTGqaJ2+Q11qgojS1pGDanikx9ZvvPW8T9FPF8zF5yvkYE+hQnzwYRFZ/nExJxyefnedeUdi3EJ6NVrWTuxGasxEK5UHDIUpb+fSo3w8ZE1hayt8AywMzLyVLEJfI7N65g0426lJWRyKTXYkfi8I/yvKVERqUfMQ8SiWc97yy0YZMDfZblbiW4kDMoIqTSZDRMST/wL5n4+2VEbikWXVjea9rnZ5b8cky4k92B7+S3qom/UWCpgWV4GxBjpB8R28KRl1JRXvdFnp5fo8VLNLhxvEWHR8hY0Sbj8Ac93YiE06VDYwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;300ms delay&quot;
        title=&quot;&quot;
        src=&quot;/static/c9ce19f5b404148390d5a8538f517c12/1cfc2/300ms-delay.png&quot;
        srcset=&quot;/static/c9ce19f5b404148390d5a8538f517c12/3684f/300ms-delay.png 225w,
/static/c9ce19f5b404148390d5a8538f517c12/fc2a6/300ms-delay.png 450w,
/static/c9ce19f5b404148390d5a8538f517c12/1cfc2/300ms-delay.png 900w,
/static/c9ce19f5b404148390d5a8538f517c12/21482/300ms-delay.png 1350w,
/static/c9ce19f5b404148390d5a8538f517c12/d61c2/300ms-delay.png 1800w,
/static/c9ce19f5b404148390d5a8538f517c12/6c68b/300ms-delay.png 1888w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tomcat 스레드보다 많이 요청됐지만 정상 처리됨.&lt;/li&gt;
&lt;li&gt;DB Connection 대기 시간도 30초 직전까지 보임&lt;/li&gt;
&lt;li&gt;그렇기에 응답 시간도 최악 30초 직전&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;일단 이것을 보고 떠오른 속성은 &lt;code class=&quot;language-text&quot;&gt;db connection timeout&lt;/code&gt;이다.&lt;br&gt;
일반적인 상황에서 DB Connection을 얻기 위해 30초 동안 대기한다는 것은 부하가 몰리는 경우 심각한 장애를 일으킬 확률이 높다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tomcat Thread Pool 고갈&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용자가 너무 많은 재시도를 할 확률이 높음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재시도를 많이하면 죽은 요청이 많이 발생함&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;빠르게 실패하도록 (Fail Fast) 타임아웃을 지정하고 테스트해보자.&lt;/p&gt;
&lt;h2 id=&quot;connection-timeout-지정&quot; style=&quot;position:relative;&quot;&gt;connection-timeout 지정&lt;a href=&quot;#connection-timeout-%EC%A7%80%EC%A0%95&quot; aria-label=&quot;connection timeout 지정 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;hikari:
   pool-name: StockHikariPool
   maximum-pool-size: 5             # 병목 시뮬레이션용 (기본값: 10)
+  connection-timeout: 3000         # 3초로 지정 (기본값: 30,000)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/76970ac563ea6a4a9775b98d060ee9a6/44d2c/300ms-stress-test.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 85.77777777777779%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADF0lEQVR42lVUyZLcRBTUJ0Dg6dZW+6qtJXW3x+MYGwJzBBwcuBNEcOXMlStfnaQkgz2HjKd69bbKylLh04Dcz6iFQVXW0OcK4tzAVS1MWaE513goW2RlsFiLQSpE+mNZIrftjtQ0yERTNii0T2i0xVkoeK2xriu+ffuM3z68w6/PdwxGs5lCZrGkWExKBGLsOqzXK5YN68K8BdYYFnQRxjkoJth+wriseMPNx3XGm+uC5TJBaIPWMMZZKO/g+oGxPVRK0CnvMGzQsnlhY8YwzXC0xoU9+dRKQqHkZIJTN1Jzv0fPuH7iJLFDw31JGj7DomReIdj5SJKohIC2Dkuf8HTJsKSjNREtmzTS7AmnRkKz0VMf8H5OL6BISSG0YwInMwEuZFzHGc/LFe/XK7p+hYwLhE2QbGRJTfAWj2PC60vCjbhPtMTjnOEdOXShwzzfkdK4F2yVw0OjOImG4FSSqLmWxsP4DMUBJE9VCV5Wq2nN/zjXPLLkZNqlPWFzlk0LZySsljg3Aqf6gGQh5/1+kppNTpTVFttQNkJsEGi4LpRlQCvIj+BEEvfekA+DdxfDIyg2UZxak1uP5DkR42pO563BGC2mZHc70kbHW5YsmPtMdBjHDsOQyV3GOHToaUOmNMidoLxCHuBTj0SJxJzhKJdNHZ6IuYNh02LwATdq6jaMmLZNThT4Ghxt5K11nC4riYETXULATIzU4+brtDos46w8KCoGqjvz/Ik8RNpITgKf0cKA74PBj4PDxyXipx3hwBrx8yds/o+0o1F4Ra53HbpN/c4jkvQcPKYu4jZmzEPChRRcqMsXGL78PvZ9pPR4msLzCIHKr1i95rV/c5JoKeCSAi65Litx2Fp8wvFdNS/99RbPm9/f8oYHJr5qG3z3WuDrut51locZmwocZWX8Z1hqd7N691Ob7vAJSq+o2u1vIvEVu/z94Qr8+Qt+fxq55kXw1cSel9Vd0I8LOiLT101ffjNmmHafowIoGw9LCWTir8e3+Of2A/643lGSj9CNu0x6Fkz8E/2HjoU2u+3n4YK4Wfq2n8a/2k3nZvyzjdsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;300ms stress test&quot;
        title=&quot;&quot;
        src=&quot;/static/76970ac563ea6a4a9775b98d060ee9a6/1cfc2/300ms-stress-test.png&quot;
        srcset=&quot;/static/76970ac563ea6a4a9775b98d060ee9a6/3684f/300ms-stress-test.png 225w,
/static/76970ac563ea6a4a9775b98d060ee9a6/fc2a6/300ms-stress-test.png 450w,
/static/76970ac563ea6a4a9775b98d060ee9a6/1cfc2/300ms-stress-test.png 900w,
/static/76970ac563ea6a4a9775b98d060ee9a6/21482/300ms-stress-test.png 1350w,
/static/76970ac563ea6a4a9775b98d060ee9a6/d61c2/300ms-stress-test.png 1800w,
/static/76970ac563ea6a4a9775b98d060ee9a6/44d2c/300ms-stress-test.png 1899w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;정상 처리량은 이전과 비슷하지만 DB Connection을 얻기 위해 3초까지만 대기하기 때문에 (예외 응답이긴 하지만) 3초로 유지되고 있는 것을 확인할 수 있다.&lt;br&gt;
그럼에도 빠르게 응답을 반환하여 서버 자원을 빠르게 반환하고 예외를 통한 모니터링이나 서킷브레이커에 빠르게 전파하는게 좋다고 생각된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;java.sql.SQLTransientConnectionException: StockHikariPool - Connection is not available, request timed out after 3001ms (total=5, active=5, idle=0, waiting=94)
	at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:714)
	at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:184)
   ...&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;maximum-pool-size-튜닝&quot; style=&quot;position:relative;&quot;&gt;maximum-pool-size 튜닝&lt;a href=&quot;#maximum-pool-size-%ED%8A%9C%EB%8B%9D&quot; aria-label=&quot;maximum pool size 튜닝 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 글의 핵심인 pool size를 튜닝해보자. 각 API는 300ms 지연은 불가피한 상황에서 pool size를 튜닝해서 처리량을 증가시키고 에러율을 낮출 수 있을지, 그리고 pool size가 크면 클수록 이득인건지 확인해보자.&lt;/p&gt;
&lt;h3 id=&quot;pool-size-5개인-현재-상황-분석&quot; style=&quot;position:relative;&quot;&gt;pool size 5개인 현재 상황 분석&lt;a href=&quot;#pool-size-5%EA%B0%9C%EC%9D%B8-%ED%98%84%EC%9E%AC-%EC%83%81%ED%99%A9-%EB%B6%84%EC%84%9D&quot; aria-label=&quot;pool size 5개인 현재 상황 분석 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;지표&lt;/th&gt;
&lt;th&gt;관찰 결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request Rate&lt;/td&gt;
&lt;td&gt;정상 응답률은 피크 시 &lt;code class=&quot;language-text&quot;&gt;8~9 req/s&lt;/code&gt;로 제한됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response Time (avg)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3초로 고정&lt;/strong&gt; (db connection-timeout에 의해 제한됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection Acquire p99&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3초에서 평탄화&lt;/strong&gt; (커넥션 획득 대기가 db connection-timeout에 의해 제한됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pending Connections&lt;/td&gt;
&lt;td&gt;최대 &lt;strong&gt;90개 이상&lt;/strong&gt; 대기 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error Count&lt;/td&gt;
&lt;td&gt;VU 25명 이상부터 &lt;strong&gt;에러&lt;/strong&gt; 발생 (최대 2K)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;5개의 커넥션이 모두 active 상태이고, 대기 큐가 계속 쌓이고 있는 상황이다.&lt;br&gt;
현재 환경 기준으로 단계별 테스트를 진행해보자.&lt;/p&gt;
&lt;h3 id=&quot;테스트-결과&quot; style=&quot;position:relative;&quot;&gt;테스트 결과&lt;a href=&quot;#%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B2%B0%EA%B3%BC&quot; aria-label=&quot;테스트 결과 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;Pool Size&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;그라파나&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;초당 처리량&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;평균 응답 시간&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;응답 시간 (99% 기준)&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;커넥션 대기 요청 수&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;에러 수&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;MySQL 초당 쿼리&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;10개&lt;/strong&gt; (기본값, 현재 대비 2배)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/pool-size-10.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;16 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;2.3s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;3.22s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;90&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;211&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;20개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/pool-size-20.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;32 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.4s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;3.17s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;80&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;560&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;418&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;30개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/pool-size-30.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;48 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.0s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;3.15s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;70&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;228&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;623&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;50개&lt;/strong&gt; (Tomcat 스레드의 50%)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/pool-size-50.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;80 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;0.5s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;2.40s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;62&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.04K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;70개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/pool-size-70.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;112 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;0.45s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.07s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;30&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.45K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;100개&lt;/strong&gt; (Tomcat 스레드와 동일)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/pool-size-100.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;159 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;0.36s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;0.62s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;2.06K&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+ 선형적 처리량 증가 (이 테스트에서는 Pool Size가 병목의 유일한 원인이었기 때문에 선형 증가가 나타난 것 같음)
Pool 10 → 20 → 30 → 50 → 70 → 100
TPS  16 → 32 → 48 → 80 → 112 → 159 (거의 비례 증가)

+ 응답 시간 급격한 개선
Pool 10: p99 = 3.22초
Pool 50: p99 = 2.40초
Pool 70: p99 = 1.07초
Pool 100: p99 = 0.62초

+ Pool 70부터 에러 0 달성
Pool 100에서 Pending 0

+ MySQL 부하 비례 증가
Pool 10: Connected 10, QPS 211
Pool 100: Connected 100, QPS 2.06K (10배 증가)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;pool-size를-최대로-잡는-게-최선일까&quot; style=&quot;position:relative;&quot;&gt;Pool Size를 최대로 잡는 게 최선일까?&lt;a href=&quot;#pool-size%EB%A5%BC-%EC%B5%9C%EB%8C%80%EB%A1%9C-%EC%9E%A1%EB%8A%94-%EA%B2%8C-%EC%B5%9C%EC%84%A0%EC%9D%BC%EA%B9%8C&quot; aria-label=&quot;pool size를 최대로 잡는 게 최선일까 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;병목 상황을 만들기 위해 조회 API에서 300ms 지연된다고 가정했는데, 이 상황에서는 에러율이 0인 70개가 적절할 수 있다.&lt;br&gt;
만약 실제 운영 환경이라면 어떤 의사결정을 해야할까?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DB Connection 반환 지연을 줄일 수 있나?&lt;/li&gt;
&lt;li&gt;목표로하는 TPS가 몇인가?&lt;/li&gt;
&lt;li&gt;에러를 일부 허용할 수 있는 상황인가?&lt;/li&gt;
&lt;li&gt;DB 서버 리소스 부담은 괜찮은가?
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;(서버 수 × pool size) &amp;lt; db max_connections&lt;/code&gt; 충분한가?&lt;/li&gt;
&lt;li&gt;pool size만큼 한 번에 connection을 맺기에 커넥션 생성 시간은 괜찮은가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;목표-tps-150으로-다시-테스트&quot; style=&quot;position:relative;&quot;&gt;목표 TPS 150으로 다시 테스트&lt;a href=&quot;#%EB%AA%A9%ED%91%9C-tps-150%EC%9C%BC%EB%A1%9C-%EB%8B%A4%EC%8B%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; aria-label=&quot;목표 tps 150으로 다시 테스트 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;커넥션 점유(API 지연)를 300ms에서 50ms로 개선에 성공하였고, 목표 TPS는 150이라고 가정한 상황에서 최적의 pool size를 찾아보자&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;Pool Size&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;그라파나&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;초당 처리량&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;평균 응답 시간&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;응답 시간 (99% 기준)&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;커넥션 대기 요청 수&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;에러 수&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;MySQL 초당 쿼리&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;목표 달성&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;5개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-5.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;42 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.17s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;2.50s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;95&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;94&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;546&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;10개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-10.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;82 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;608ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;2.58s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;90&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;88&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.06K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;15개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-15.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;125 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;398ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.43s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;85&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.62K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;20개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-20.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;165 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;306ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;1.39s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;80&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;2.14K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;25개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-25.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;216 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;115ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;814ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;75&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;2.80K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;30개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-30.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;257 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;200ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;765ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;70&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;3.33K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;35개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-35.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;303 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;168ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;635ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;65&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;3.93K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;50개&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;/stress/50ms-pool-size-50.png&quot; target=&quot;_blank&quot;&gt;보기&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;427 req/s&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;116ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;374ms&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;50&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;5.55K&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;15개 부터 에러가 발생하진 않았지만 목표한 TPS에 도달하진 못 했다.&lt;br&gt;
내가 원하는 성능은 pool size가 20개일 때, 사용 가능하다는 것을 확인했다.&lt;/p&gt;
&lt;h2 id=&quot;정리해보면&quot; style=&quot;position:relative;&quot;&gt;정리해보면&lt;a href=&quot;#%EC%A0%95%EB%A6%AC%ED%95%B4%EB%B3%B4%EB%A9%B4&quot; aria-label=&quot;정리해보면 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DB 자원이 허용된다면 Pool Size를 크게 잡아서 당장의 문제를 해결할 수 있다.&lt;br&gt;
하지만 Pool Size를 늘려야 하는 상황 자체가 이미 어딘가에 문제가 있다는 신호다. 대부분은 커넥션을 오래 점유하는 &lt;strong&gt;롱 트랜잭션&lt;/strong&gt;이 원인이다.&lt;/p&gt;
&lt;p&gt;HikariCP에서 일반적으로 추천하는 공식이 있다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;connections = (core_count × 2) + effective_spindle_count

core_count: 하이퍼스레딩 제외한 물리 코어 수
effective_spindle_count: 디스크 동시 처리 가능 수 (SSD면 0~1)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;4코어 서버라면 &lt;code class=&quot;language-text&quot;&gt;(4 × 2) + 1 = 9~10개&lt;/code&gt; 정도가 적정하다는 이야기다. 그런데 단순히 이 공식대로 설정한다고 끝이 아니다.&lt;br&gt;
핵심은 &lt;strong&gt;&quot;소수의 커넥션이 빠르게 회전하면서 포화 상태를 유지하는 것&quot;&lt;/strong&gt; 이다. 커넥션 수가 많은 게 중요한 게 아니라, 점유 시간이 짧아야 한다.&lt;/p&gt;
&lt;p&gt;결국 판단 기준은 &lt;strong&gt;&quot;시간&quot;&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;acquire_time (커넥션 획득 대기 시간)&lt;/strong&gt;: 이게 길어지면 Pool이 부족하거나 점유 시간이 길다는 의미&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;usage_time (커넥션 점유 시간)&lt;/strong&gt;: 이게 길면 쿼리 최적화가 필요하다는 신호&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 Pool Size를 결정할 때 이런 순서로 접근하는 게 맞는 것 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;모니터링으로 현재 상태 파악&lt;/strong&gt; - acquire_time, usage_time, pending 확인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;점유 시간이 길다면 쿼리 최적화 먼저&lt;/strong&gt; - 인덱스, N+1, 트랜잭션 범위 점검&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;그래도 목표 TPS가 안 나오면 부하 테스트&lt;/strong&gt; - 에러가 0이 되는 최소 Pool Size 찾기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상한선 확인&lt;/strong&gt; - &lt;code class=&quot;language-text&quot;&gt;(max_connections × 0.8) ÷ 인스턴스 수&lt;/code&gt;를 넘지 않는지, DB에 설정된 최대 커넥션 수를 초과하지 않는지&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;신경써야하는-다른-속성도-있나&quot; style=&quot;position:relative;&quot;&gt;신경써야하는 다른 속성도 있나?&lt;a href=&quot;#%EC%8B%A0%EA%B2%BD%EC%8D%A8%EC%95%BC%ED%95%98%EB%8A%94-%EB%8B%A4%EB%A5%B8-%EC%86%8D%EC%84%B1%EB%8F%84-%EC%9E%88%EB%82%98&quot; aria-label=&quot;신경써야하는 다른 속성도 있나 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;maximumPoolSize (idle + in-use connection)와 연관된 속성&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;minimumIdle&lt;/strong&gt; (default maximumPoolSize와 동일) : 풀에서 유지하는 최소 idle 커넥션 갯수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;idleTimeout&lt;/strong&gt; (최소 10초, default 10분) : 풀에서 커넥션이 idle 상태로 유지되는 최대 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, idle connection 수가 minimulIdle보다 작고, 전체 connection 수도 maximumPoolSize보다 작다면 신속하게 추가로 connection을 만든다.&lt;br&gt;
기본적으로 minimumIdle과 maximumPoolSize는 동일한 값으로 지정되어 있다.&lt;br&gt;
minimumIdle을 maximumPoolSize보다 작게 설정한 경우 예상하지 못한 트래픽이 몰려오면 connection을 생성하는 비용 때문에 병목이 생길 수 있기 때문에 기본값을 활용하는 것이 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;서비스 환경의 네트워크와 DB 속성을 확인하여 아래의 설정도 확인하는 것이 좋다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;maxLifetime&lt;/strong&gt; (default 30분) : DB 혹은 네트워크 infra에서 설정된 &quot;최대 커넥션 생존 시간(wait_timeout 등)&quot;보다 수 초 짧게 설정 필요. pool로 반환이 안되면 connection을 제거할 수 없기 때문에 pool로 반환을 잘 시켜주는 것이 중요하다.
&lt;ul&gt;
&lt;li&gt;데이터베이스 서버의 최신 변경사항을 반영하고 커넥션 유지 동안 발생할 수 있는 리소스의 누수를 막기 위한 선택이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;keepaliveTime&lt;/strong&gt; (default 2분) : idle 커넥션 생존을 위해 상태 확인 주기, db/network timeout보다 짧게&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;connectionTimeout&lt;/strong&gt; (default 30초) : 풀에서 커넥션을 가져올 때 기다리는 최대 시간, 기본 값은 너무 커 트래픽이 몰리는 경우 서버 스레드가 커넥션을 획득하기 위해 대량으로 블로킹될 가능성이 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 DB 서버 설정은?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;connect_timeout&lt;/code&gt; (default 10초) : MySQL 서버가 TCP 연결 수락 이후 클라이언트로부터 연결 패킷(인증 정보)을 받기까지 기다리는 시간&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;max_connections&lt;/code&gt; : client와 맺을 수 있는 최대 connection 수이다. 스케일 아웃에 대비하여 클라이언트들의 최대 사용 수를 확인하여 설정하는게 좋다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;wait_timeout&lt;/code&gt; (default 480분) : connection이 inactive할 때 다시 요청이 오기까지 얼마의 시간을 기다린뒤에 close 할 것인지를 결정한다. (애플리케이션의 keepaliveTime보다 길게 설정되어야 한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[하네스를 만들며 정리한 생각들]]></title><description><![CDATA[에이전틱 프로그래밍을 위해 내가 사용하는 방법을 기록하고 개선하기 위해 작성한다. 끝난 건 타이핑이지, 엔지니어링이 아니다. 좋은 설계의 레버리지는 커졌고, 나쁜 설계의 피해도 커졌다. 쇼의 주인공은 AI가 아니라 AI…]]></description><link>https://jdalma.github.io/wiki/agentic/</link><guid isPermaLink="false">https://jdalma.github.io/wiki/agentic/</guid><pubDate>Wed, 17 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;에이전틱 프로그래밍을 위해 내가 사용하는 방법을 기록하고 개선하기 위해 작성한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;끝난 건 타이핑이지, 엔지니어링이 아니다. 좋은 설계의 레버리지는 커졌고, 나쁜 설계의 피해도 커졌다. 쇼의 주인공은 AI가 아니라 AI를 잘 다루는 엔지니어다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;2026-03-29&quot; style=&quot;position:relative;&quot;&gt;2026-03-29&lt;a href=&quot;#2026-03-29&quot; aria-label=&quot;2026 03 29 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;팀-플러그인&quot; style=&quot;position:relative;&quot;&gt;팀 플러그인&lt;a href=&quot;#%ED%8C%80-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8&quot; aria-label=&quot;팀 플러그인 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;현재 팀 플러그인에서는 크게 다섯 가지 유형의 기능을 제공하고 있다.&lt;/p&gt;
&lt;h4&gt;1. 도메인 지식 관리&lt;/h4&gt;
&lt;p&gt;처음에는 각 프로젝트마다 도메인 지식을 파일 형태로 세팅하고, 이 파일들이 연결된 프로젝트들을 기반으로 도메인 오케스트레이터를 구성하는 방식을 구상했다.&lt;br&gt;
이 오케스트레이터를 통해 여러 프로젝트와 여러 소스(노션, 슬랙 등)를 참고하여 도메인 질문에 답변하도록 만들고 싶었다.&lt;/p&gt;
&lt;p&gt;주변에 공유했을 때는 대체로 “효용성이 있을 것 같다”는 반응을 들었지만, 실제 업무에 바로 도움이 되는 수준은 아니었다.&lt;br&gt;
오히려 구축과 유지 비용이 더 크게 느껴졌다.&lt;/p&gt;
&lt;p&gt;가장 큰 문제는 동기화 피로도였다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;코드베이스가 변경될 때마다 각 프로젝트의 도메인 파일을 함께 동기화해야 한다.&lt;/li&gt;
&lt;li&gt;도메인 파일이 바뀌면, 이를 참조하는 도메인 오케스트레이터 레이어도 다시 동기화해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;결국 각 프로젝트의 도메인 세팅, 도메인 오케스트레이터 세팅, 그리고 이 둘의 동기화를 지원하는 커맨드까지 각각 관리해야 했다.&lt;br&gt;
구조가 조금만 바뀌어도 플러그인을 다시 배포하고, 이해관계자들에게 변경 사항을 공유해야 하는 피로도 역시 컸다.&lt;/p&gt;
&lt;p&gt;이 방식은 내가 만들고 싶었던 AI-TPM의 방향과는 다소 거리가 있다고 느꼈다.&lt;br&gt;
여기에 더해 brand-specific MCP에 대한 요구사항까지 확인되면서, 플러그인 기반 접근의 한계도 점점 분명해졌다.&lt;/p&gt;
&lt;p&gt;그래서 지금은 AI-TPM + MCP per brand 구조를 Claude Agent SDK로 구현해보고 있다.&lt;br&gt;
이 내용은 아래의 AI-TPM 항목에서 조금 더 정리해보겠다.&lt;/p&gt;
&lt;h4&gt;2. 디버깅 · 분석 · 개발&lt;/h4&gt;
&lt;p&gt;현재 플러그인에는 다음과 같은 디버깅·분석·개발 흐름이 포함되어 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/graylog&lt;/code&gt;: 에러 로그를 분석한 뒤 수정 방안을 제안&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/bottleneck&lt;/code&gt;: 로그 기반으로 API 성능 병목을 분석&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/jira&lt;/code&gt;: JIRA 티켓 이슈를 분석하고 수정 방안을 제안&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/graylog-trace&lt;/code&gt;: 로그 기반으로 플로우 타임라인을 복원해 정상 흐름과 에러 흐름을 비교 분석&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/dev&lt;/code&gt;: JIRA 티켓 또는 개발 기획 문서를 입력받아 TPM 멀티 프로젝트/소스 라우팅 → 분석/설계 → 사용자 승인 → worktree 구현 및 검증 → PR생성까지 수행&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/review&lt;/code&gt;: 생성된 PR을 승인하거나, 코멘트를 기반으로 수정 사항을 반영한 뒤 다시 커밋하고 푸시&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;3. 테스트 자동화&lt;/h4&gt;
&lt;p&gt;단위 테스트와 통합 테스트의 코드 컨벤션, 그리고 테스트의 목적을 어느 정도 통일해보고 싶어서 관련 기능도 플러그인에 배포했다.&lt;br&gt;
하지만 아직 프로젝트별, 개인별 기준이 충분히 정립되지 않은 상태라 실제로는 거의 사용되지 않는 것을 확인했다.&lt;/p&gt;
&lt;p&gt;그래서 이 기능은 일단 플러그인에서 제거하고, 먼저 개인 차원에서 컨벤션을 더 축적한 뒤 팀원들과 논의해 다시 적용할 계획이다.&lt;/p&gt;
&lt;h4&gt;4. 세션 관리&lt;/h4&gt;
&lt;p&gt;팀원들이 Claude Code와 나눈 대화의 세션 요약을 노션에 축적하고, 이를 바탕으로 피드백 루프를 만들고 싶어 관련 기능을 배포해 함께 사용하고 있다.&lt;/p&gt;
&lt;p&gt;요약 항목은 크게 다음 세 가지다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;무엇을 했는가&quot;&lt;/code&gt;: 작업 요약, 변경 파일 목록(git diff 기반), 의사결정 로그(상황 → 선택지 → 결정 → 근거), 시행착오 로그(시도 → 실패 → 전환 → 결과)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;팀이 가져갈 것&quot;&lt;/code&gt;: 코드 컨벤션(규칙 + 이유 + Before/After + 적용 범위), 재사용 가능한 TIL(배경 + 핵심 + 왜 중요한가 + 팀 적용 방법)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;사용자가 개선할 것&quot;&lt;/code&gt;: 도구 활용 리뷰, AI 활용 성숙도 3축 평가(프롬프트 품질 / 도구 활용도 / 자율 위임도), 프롬프트 개선 제안(Before → After), 마찰 기록(증상 → 원인 → 영향 → 해결 → 재발 방지)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 요약본을 계속 누적하면서 주 1회 정도 보고서를 만들어 보고 있는데, 생각보다 유의미한 지점들이 보이고 있다.&lt;br&gt;
그래서 요약 생성과 보고서 생성 모두 계속 고도화하는 중이다.&lt;/p&gt;
&lt;h4&gt;5. 자산화&lt;/h4&gt;
&lt;p&gt;외부 아티클이나 공식 문서 링크를 입력받아, 먼저 공신력을 검증한 뒤 핵심 원칙을 추출하여 자산화하는 기능도 제공하고 있다.&lt;br&gt;
이 기능의 기준은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Iron Law: 검증 없이 자산화하지 않는다.&lt;/li&gt;
&lt;li&gt;컨텍스트 격리 검증: 별도의 Agent를 생성해 확증 편향 없이 출처 등급(A/B/C)과 내용 품질을 평가한다.&lt;/li&gt;
&lt;li&gt;복수 소스 교차 검증: 원문, HN 토론, 요약글 등 여러 소스를 함께 검토할 수 있다.&lt;/li&gt;
&lt;li&gt;검증 FAIL 시 즉시 중단하고, CONDITIONAL이면 한계를 명시한 뒤 사용자 확인을 받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 기능은 실제로 스킬이나 커맨드를 만들 때 꽤 많은 도움을 주고 있어서 개인적으로 만족도가 높다.&lt;/p&gt;
&lt;h3 id=&quot;ai-tpm-to-agent&quot; style=&quot;position:relative;&quot;&gt;AI-TPM to Agent&lt;a href=&quot;#ai-tpm-to-agent&quot; aria-label=&quot;ai tpm to agent permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;기존 플러그인 안에 있던 AI-TPM을 이제는 Agent SDK 기반으로 다시 구현하고 있다.&lt;br&gt;
어떻게 보면 Claude Desktop이나 ChatGPT 웹 인터페이스를 우리 회사 상황에 맞게 커스텀하는 작업에 가깝다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/91fee11c00014028c2e369abd83abe7a/52ab5/domain-agent.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 109.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEHUlEQVR42nWU+3PTRhSF/b932pn+UjqdaaEd2lDaUCCUBFNCCYaEPBycl+PYDo5jR/IzfsqyHpZkrfbr2g6epMDOHO3u1d0zd++ee2NIbgwpZ4ZGo0GpXKJSqVAsFmm322iaRj6fx7atG77XKWITYxTNID/OysP3fUajEZ7n4TgOQRAwHA7pdruIMFS+EVzDdD8hbNcEuaTkeMvlcGNIbjfk9H2EZURXUdwIfzZ1CogPKwzTT7Eyy9jZOLL4AjkyiF3kQnafw9rjFsv3iiRfeKRWJd1GOOeYX01G0+tF+jZkfqH+5hanz77BSP4I6dtEwwYx34VBE1paRDkT0KtLzBaIMV8c0rOQhk7Uv8AvbCGaOeSgOj0U8yOPEQ5O4FLWTZzQxcVmLL/MKMcjpO8Q+TZCTyOdvtq7yDAg9naQZLG1zN0Pi9wrPeJ+aYmHvTgZ68PVC96UgRQRohCH3D1627epJb7HO1qAk1+JavvE1vqb/K49ZrH6lIXyQ+5fLLFQeciRlf0CoUCerxJmfkNP/ED53+9obv7EOH0X2TggZgQmml2j4jQ46uY5aJ9wYdUYBs6UQExkNeecLSKrg13LY+knOKV9zOI+TiU7zW1s4hAGgigUUy2NVX7s0YDwM68yl9DVIrL7CIUompwVV8K+8sl1Qjaqgp1KwMppk+eFNusllw09YKs6xh5P1c44p15UVYufzeLsvsPdSyLOisqWI9T1WYRCRfb3Wcjt4zG3Ni/5On7KV08zfLumcedwxJ10yEk7pFms4r96RXd1ldryMpWVFWrP4jTjcYYvXxLt7c0IJ8JNaIKFQ5M/jwwWMxY/J+s8ODb547DPg6xH05WIgYm3uUl7QqpgvH5NX6GjYCQSCBV17GN+vHGE6UXYQcQolDQsm0KnS9v21L+rtKmPdF1VEUPGRp/QMGYwTYSyRSolsc8JN1IpEFFIzx2imx2alkGgkh6IEF/NY/l/MV3rNjcFofKuDqWsBu9HLfYU1rtlnlSOWbs8Y6N3QcptsquQs1rTQ3JW7HN8Quiq8nll62zKznReLOyypLR2P7vFIy3NuuiSCC95N6x80gvnEcqPmIpYUnR77A3q7PR00qaKqKtzMGhwWk+R6+XJ2pdUXPNaJck59Y0cTn66Yw9rZOP4o6mcJqPrmFhqr0pANQEDbHVd1fsQwacdexKRNgg574XslBqslxsU1NpVr251AtqlgPRelbOTDl1N2Sfd32+o1qXaVitBZB4i7WPVXGcpiAVCslMNiB9qPHh7zD8HmqoOj7qheuOFIJMasL12rlDiPO3TV/2S4T6D2hJaflHZFjAqfyH62zNCoSr/tDMmpVskywNSmslR3VOaFJiNgFJ6oGBQPOpRydvY/YkWS7jNN1jNdUatdbV+rbSYnxL+BwDobbr7GYeNAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;domain agent&quot;
        title=&quot;&quot;
        src=&quot;/static/91fee11c00014028c2e369abd83abe7a/1cfc2/domain-agent.png&quot;
        srcset=&quot;/static/91fee11c00014028c2e369abd83abe7a/3684f/domain-agent.png 225w,
/static/91fee11c00014028c2e369abd83abe7a/fc2a6/domain-agent.png 450w,
/static/91fee11c00014028c2e369abd83abe7a/1cfc2/domain-agent.png 900w,
/static/91fee11c00014028c2e369abd83abe7a/21482/domain-agent.png 1350w,
/static/91fee11c00014028c2e369abd83abe7a/52ab5/domain-agent.png 1420w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
대략적인 구조는 위와 같다.&lt;/p&gt;
&lt;p&gt;현재 통합 커머스 작업을 진행하면서 서버 간 의존성을 OAS 명세 기반으로 전환하고 있는데, 이 명세 파일들을 MCP로 활용할 수 있을 것 같다는 생각이 들었다.&lt;br&gt;
즉, 정적인 도메인 파일을 수동으로 동기화하는 대신, 시스템이 이미 가지고 있는 명세를 더 직접적으로 활용하는 방향이다.&lt;/p&gt;
&lt;p&gt;무엇보다 이 작업 자체가 꽤 재미있어서, 당분간은 개인 시간도 계속 투자해볼 생각이다.&lt;/p&gt;
&lt;h3 id=&quot;느낀-점&quot; style=&quot;position:relative;&quot;&gt;느낀 점&lt;a href=&quot;#%EB%8A%90%EB%82%80-%EC%A0%90&quot; aria-label=&quot;느낀 점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;최근 들어 서비스의 도메인 지식과 프로젝트 이해도가 AI 활용도와 거의 비례한다는 생각을 자주 하게 된다.&lt;/p&gt;
&lt;p&gt;우리 회사는 일반적인 이커머스 플랫폼이라기보다, 브랜드가 제품을 직접 생산하고 판매하는 엔드투엔드 운영 회사에 가깝다.&lt;br&gt;
그래서 단순한 온라인 판매 도메인만 이해해서는 부족하고, 제품의 생명주기와 공급망, 운영 구조까지 함께 이해해야 한다.&lt;/p&gt;
&lt;p&gt;문제는 내가 아직 그 부분에 꽤 무지하다는 점이다.&lt;br&gt;
그리고 이 무지함이 업무 자동화 워크플로우를 구상하고 실제로 밀어붙이는 데 분명한 브레이크로 작용하고 있다고 느낀다.&lt;/p&gt;
&lt;p&gt;예를 들어, JIRA 이슈를 자동으로 분석하고 front + back + OMS 프로젝트를 함께 훑어 수정 범위를 파악한 뒤 PR까지 생성하는 워크플로우를 상상할 수는 있다.&lt;br&gt;
하지만 지금의 내가 그것을 만들었을 때, 과연 실제로 얼마나 높은 효용을 낼 수 있을지는 확신하기 어렵다.&lt;/p&gt;
&lt;p&gt;그래도 일단은 플러그인에 다음 흐름을 추가했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;이슈 및 기획 이해 → 멀티 프로젝트 기반 분석 → 수정/개발 → PR 생성&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;생성된 PR에 피드백을 반영하여 재작업&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;만약 내가 이 서비스를 오랫동안 직접 개발하고 운영해온 사람이라면, 아마 이런 자동화를 훨씬 더 공격적으로 구축했을 것 같다.&lt;br&gt;
그만큼 결국 자동화의 품질은 도메인 이해도와 운영 감각에 크게 의존한다.&lt;/p&gt;
&lt;p&gt;한편으로는, 완성도 높은 워크플로우 자동화에 가까워지려면 결국 내가 직접 0 to 1로 만든 서비스를 오래 운영해보는 경험도 필요하겠다는 생각이 든다.&lt;br&gt;
그런 의미에서 사내 플러그인을 관리하고, Agent SDK 기반 하네스를 다듬는 일은 지금의 나에게 꽤 중요한 훈련이다.&lt;/p&gt;
&lt;p&gt;요즘 하네스 엔지니어링과 에이전틱 프로그래밍에 많은 시간을 쓰고 있는데, 이 동기는 단순히 회사 생활을 더 잘하기 위한 것만은 아니다.&lt;br&gt;
장기적으로는 스스로 자립할 수 있는 역량을 만들기 위한 과정이라고 확신하고 있다.&lt;/p&gt;
&lt;h2 id=&quot;2026-03-16&quot; style=&quot;position:relative;&quot;&gt;2026-03-16&lt;a href=&quot;#2026-03-16&quot; aria-label=&quot;2026 03 16 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;업무적으로는 AI-TPM을 계속해서 만들고 있고, TPM 모드에서 의도한대로 프로젝트와 소스(노션, 슬랙 등)들을 정상적으로 참조하는지 모니터링 툴을 이용해서 확인하고 의도한대로 호출되지 않으면 다시 교정하는 작업을 반복 중이다.&lt;br&gt;
호출할 때 마다 지시한대로 움직이지 않을 확률이 존재하기 때문에, 이걸 정량적으로 확인하기 위한 시나리오 실행 툴도 만들고 있다.&lt;/p&gt;
&lt;p&gt;사용 흐름은 아래와 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;플러그인의 &lt;code class=&quot;language-text&quot;&gt;domain-setup&lt;/code&gt; 커맨드로 참조할 프로젝트들의 도메인들을 세팅한다.&lt;/li&gt;
&lt;li&gt;AI-TPM 모드를 실행할 위치에서 project-paths.json을 작성한다.&lt;/li&gt;
&lt;li&gt;project-paths.json에 프로젝트의 절대경로와 참조할 소스 링크(노션, 슬랙 링크 등)을 작성한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;domain-enrich&lt;/code&gt; 커맨드를 이용해 project-paths.json에 작성한 프로젝트와 소스에 대한 description을 작성한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;domain-scenario-create&lt;/code&gt; 커맨드와 세팅된 도메인 정보를 이용해서 시나리오를 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;domain-scenario-run&lt;/code&gt; 커맨드로 작성된 시나리오를 N회 실행하여 보고서를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;시나리오를 생성하려면 도메인과 코드베이스에 대한 이해가 필요해서, 다른 분들에게 사용 가이드를 전달하고 피드백을 적용하면서 발전시킬 예정이다.&lt;br&gt;
최종적인 목표로는 회의에 참석시켜서 궁금한 것에 대한 빠르고 (꽤) 정확한 대답과 회의에 나온 컨텍스트를 적용해서 적용 가능 여부 &amp;#x26; 변경 범위 파악 &amp;#x26; 작업 진행을 시키는 것이 목표다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2dcb7774506b059870b3d87e4797801d/d7ceb/ai-tpm.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 216.44444444444443%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAArCAYAAAB4pah1AAAACXBIWXMAAAsTAAALEwEAmpwYAAAJQUlEQVR42l2WB3Mb1xWF+euTSeIWj+PEkiKbalSxbHWKYhElEiRFkRLRCIBEIUBg0RbbF4u2++UAlCeZ7MzBK3h7975bzr1LCZAkV4jnP3p6vT6npwXq9QafP59QrdYolUraO6XZbJHN5uh0uouzyZd3/niWtMV/AaNhSBh4C9iWuYBjWwu4ro1p9nEcG8saEIYB///+kjNMaNrQ9qCh8cKClgOGldDoTbkcQF2j5U0JxhCOE8JRwljzkTCewOQPTCXQ67pYOx/pvtun//4D1mGOXsmgb47ouGAOPALzM1PnI4EnTUOZRJIzVpuN/jFPjS2et7Z50XpPK/RZGvZsRnv72NvbhO/eM/54jNcy8Z2AqZfHvXxGOb1MJXsHq/6EqZtl4HmcezavuikeXa7yqPWaX5trEuhJwwhcwZf63khzXcfV3PUjRoNPTAevMc4ecVl8yMR8xdjcw/d8Qr0zjObX1/gFgdZLc5v4gidB/gLxFeZ7EjwcyrDDI5Jwn1HYXdhxfs6LYubKXI0xrnwxl7UUBRLSi2Unjf1kMQYa3fbV3OuBZcwYtCdYihS7q3PuPBoSRsGEaKjvhVeYjOdOsWOOLz12zgx25Yz9isFe2SB13iZVbfNRbm9fzrCaMWZjyqAxJOy1cZspvOYGfjvDyHEZOjMiXwI7wZTNscGzbo6nnZzGPM80Pu+e8qSZZm3Y4CIYEet6Iy9gZu5L5eey613OTpYZ9X4nMVeJuqUrgS1vwruow+PyEb+Vj9mJ2hzEA/biPvuzPlvjDjljSKeYYHUUf7rm1O8R+2liN8XMP2csjw/dmEhxvtSUS3enPdbMc1ayu6zkUryxKmw4NXbGbbYlsDSIdE05w05o1+AsnVD8NCN/OKZ0MqN2qv+UEAuBLWfCamjo2i1u51PcPNrkfulA183wVnurQ4OqHTHRdYZ6oZUdkN84J7Ne5OPLDMerRSqpJm5/diVwJC/VlVY12ekinFDVlcoKynOh7I+o+1MieXAazr0qAmnuMcreZFR6SJBbIVDQJ+Vf5ZghE9l5yZ8GDIZdoUcnaNMODNyJRT/qL9aDqIuhGzSCDt2Jh8cUNx7hecqmUIhD3GS8wEhY6o9d3tT7vK7bvK70ealweV3p8vKszav5WLNItTtc+lWc5hZR+x1+Yws784RB7ilufZPI2Cbo7OJNTDklCLldSLhTgpsnQ3764PDTocu1jx7LuTG3ivC0qrQKKnB6G4r3mORu4x3fxBUm2VtQWiHJ/UzoFFi68EOW81O+26jyw/sW325U+G69wrdvynyzdsaPeyaPK4k4sACFOwSffqHx9geMdz9S3/qByvr32lvWx5YJ7FMF9ijkSTXmXtbnYSFS2PjcObG5l3YEl/uFMe+7c9JoMa0+w83/TvfkIe7pY5zsA8z0Q5zcr0xrL/AVw3KKreSu6IW6WKeOGdZoBj0xThMnusDS2ovKeNOO1g1d/ZRgVJbGn8Q6GRFFjSDMYfkFvdNlKRg18RoPGJTv4F48ILi4h2t+UK6+wDq7jVN9gFO5y9DaJbT3dfYRgTjSrT7CNw8wm28JG78R1LW2jkUOM1eSW4LBQFoa0Tmt0SXt0QU1Oy/SPFcI6QbKJmc6wFHmNLSu61x9XKcSlDl3TrVXZjAZSGCsOIxdgiQirUM3Lx5zt/GUX2q/cUPEerP2mBf2DlbiIVqlnzisiAyu1x5wvXyfa+UVzR+y3HvKuRRZGkqQr3ANmQse0Jg2ac5aNBODiuyXG+jrOuhJYE8JXTcv+HyZphbWaU7bNCYK+kmLuuDGqimRHzMQgVqdGbYRL4jV64DTmhOsClJlRjpVJ31UptPyRbJDpkrXSXiF6f9iTrChl2B34gWs+dgVkbYkXKWzlknYfRbwdLnMq3sV3r/okN2ZK5Aw9FQmRM6BExOKukKNwzkfjr58aTa8gtJ0AaUsjXLC6YeYwoHH5x2P3MGUs8Mhjj42EhGMv9D/vN7PsWCbsTYaqhOZZkKuFfOxZLNfVIn8ZHJYG5MVqdbFddMgYiZWnoryh7U9rNNNke4xfj3HxCgxahWJ3JClmQTu1GJu5OHaB4uvXpf4ZuOCv66e8/e3Ta5nYn5XnoftlvL1F6XfXTUD12huf0/h+Z+53PoG8j+TZG+oHNRZSmZqQwYxVWlZlWNq3Rl92XUg/uu5yWJu6yrT0ZRh3yAw6vituhi8o1LgiwfVKNhXmKkXWZo3OEf1MY8/O7zMBTxXTr8peGyWXDbPfNaLHh05YP5El6pwxVdY2Vd4xU36+S2swjbT+i7jdoE4Tq4EHqhMXjsw+XfK4OZBh2u7BjcO+vy02+aGzFCzrgROGimi9E2M/Rv0D2/Q2fsnXWGUvsGktk08mwtUf/exiQrUlLtHDrc/DLhzaGu0WEkPeZSfUbevWrXpRYok8zPB8TK93Z8Y7F0nOPoZZNvZxbsvGurjB90xd8suK4U+d3JtbmUNfkk3uXdmcb/iU3Nni/bPaJ9SKa9RKq6p6XxFOv2cYmGVamUNw8iSzDVMJPA0GLJlttiUwddl8DVh3bjkjdZv1X+05BB0OOMNVW572jf0v9Bq8LplsNbvLf5jFitsJHEym0ndeIFoPMNUOs5NkegKiQ4lsTREiCU4vrKn3EqiwrV4ZlPZb6JLSENzZFFSUS97DU5V4DMq+Lu9CvtmWWORg0FFLGJypipYVGU807ykeVWV0FIjYM0cbMGKnUUFXPJFX36iZocv0Px43FdvU+JRJc3vjRy/Xea5X8sJWVa0d/+yxJbaPHtePomw50gCsY0azkCT+tAlbwUUHJ+i0uekb7OhfNsxfE603xx79FUiLTuH2dnHVNkcWDmcQY5BPyMmz+OFFxIYiA/FheuNiH+cwNdv2/xptcZf1hr8+WWZHw99rmUUFfZQia8GJnON8efrGJtf03/7Fa2Nv6kv/xbS/2JUfaJiH8419NUve3zoRuw3LfbqPd5W+2yWTfaaIZ/NCaaILppZaoePGLV38Wur2LU3DC5T6g/3iaS15xauNPTEsm5ii7VtcbYr5vZky4D8uMy6c8S7/h7rl9u8aaXYNjZIOe/IhHkcnXJ02hYsnZ+XiIUNI5HfwjHykC/q8WYB4SziMMzyxHrDrfyv6ihW1EU84NbpIx73X5MKTwhE0YHO/gFfikXJiP8AQFxTDMKeT6IAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ai tpm&quot;
        title=&quot;&quot;
        src=&quot;/static/2dcb7774506b059870b3d87e4797801d/1cfc2/ai-tpm.png&quot;
        srcset=&quot;/static/2dcb7774506b059870b3d87e4797801d/3684f/ai-tpm.png 225w,
/static/2dcb7774506b059870b3d87e4797801d/fc2a6/ai-tpm.png 450w,
/static/2dcb7774506b059870b3d87e4797801d/1cfc2/ai-tpm.png 900w,
/static/2dcb7774506b059870b3d87e4797801d/21482/ai-tpm.png 1350w,
/static/2dcb7774506b059870b3d87e4797801d/d7ceb/ai-tpm.png 1446w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이렇게 만들면서 느낀 점과 보완해야 할 점들이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;플러그인을 운영하면서 수정하고 테스트하는 사이클을 만들기가 어렵다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;배포한 뒤 다시 업데이트 받고 테스트하기에는 너무 번거롭다.. 아니면 로컬 캐시를 직접 수정해야 하는데, 로컬 캐시의 플러그인과 로컬 플러그인의 내용이 동기화되지 않을 수 있지 않을까 하는 마음에 꺼려했지만, 로컬 캐시 플러그인을 빠르게 수정하고 테스트하는게 최선인 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;바이브코딩에 대한 가드레일이 부족하다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;제일 시급한건 내가 생각하는 클린 코드, 클린 아키텍처, 트랜잭션 분리 등 나의 지식을 그대로 담은 에이전트(또는 컨벤션)를 만들어야한다.&lt;/li&gt;
&lt;li&gt;superpowers를 제외한 나만의 워크플로가 없다. 스스로 작업 단위를 어떤 순서대로 나누는지 잘 확인해서 이걸 본따 스킬로 만들어야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내가 바이브코딩을 진행하는 습관을 되돌아봐야 한다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;작업을 태스크별로 쪼개서 병렬로 진행하는 것, 그러면서 내가 검수해야 할 내용들은 꼼꼼히 읽어보는 습관을 들여야 한다.&lt;/li&gt;
&lt;li&gt;한번씩 읽기에 지치면 그냥 진행할 때가 있는데 그런 경우 잘못된 방향을 다시 고치는게 비용이 더 크다.&lt;/li&gt;
&lt;li&gt;작업 단위를 잘 나누고 구현전 스펙을 자세하게 작성하고 구현 계획을 꼼꼼하게 읽는 것. 그리고 구현 계획 검수에 대한 비용을 줄이고 싶다면 제 2의 정현준을 만들어서 에이전트로 검증해야 한다. 이때 훅을 사용하는 방법을 고안해봐야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;2026-03-02&quot; style=&quot;position:relative;&quot;&gt;2026-03-02&lt;a href=&quot;#2026-03-02&quot; aria-label=&quot;2026 03 02 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://news.hada.io/topic?id=27104&quot;&gt;에이전틱 엔지니어링 시대의 생존 스킬 9가지&lt;/a&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;요구사항을 명확히 쪼개고 엣지 케이스를 정리하는 능력&lt;/li&gt;
&lt;li&gt;아키텍처와 컨텍스트 설계 능력&lt;/li&gt;
&lt;li&gt;AI 기준의 &apos;완료&apos;가 아니라 개발자, 지시하는 사람으로서의 &apos;완료&apos;를 정의하는 능력&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://channel.io/ko/team/blog/articles/ai-native-ddd-refactoring-98c23cdb&quot;&gt;AI가 규칙을 &quot;알잘딱&quot; 지키는 백엔드 레포 만들기&lt;/a&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;AI는 지시사항과 문서를 잘 안지킨다.&lt;/li&gt;
&lt;li&gt;자연어의 모호성, 규칙 충돌 등&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결정론적 코드 제어&lt;/strong&gt; : 아키텍처 강제, 린트 (의존성 규칙, 네이밍, 인터페이스 패턴, 구조), 테스트&lt;/li&gt;
&lt;li&gt;대칭성 (모든 계층이 동일한 패턴을 따르도록)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;규칙을 문서로 적지말고 DDD를 활용하여 결정론적 검증 시스템을 구축하라&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2026-02-17&quot; style=&quot;position:relative;&quot;&gt;2026-02-17&lt;a href=&quot;#2026-02-17&quot; aria-label=&quot;2026 02 17 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;사내 에이전틱 코딩 TF를 맡게 됐다. 개인적으로 주최한 스터디가 회사의 방향과 맞아떨어지면서 자연스럽게 TF로 전환됐다.
요즘은 &lt;code class=&quot;language-text&quot;&gt;AI TPM&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;에이전틱 코딩 컨벤션&lt;/code&gt;에 관심을 두고 있다.&lt;/p&gt;
&lt;p&gt;AI 관련 기술이 쏟아지는 상황에서 백엔드 개발자로서 가장 의미 있는 작업이 뭘까 고민하다 보니 에이전틱 코딩에 관심을 가지게 됐다.
현재 풀고 싶은 문제는 크게 세 가지다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;오케스트레이션 모니터링&lt;/strong&gt; : 클로드 코드가 에이전트/스킬/훅을 의도대로 호출하는지 확인할 수 있는 도구&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도메인 컨텍스트 관리&lt;/strong&gt; : 도메인 지식과 코드베이스의 상호 의존성을 계층화하여 AI에게 전달하는 방법
&lt;ul&gt;
&lt;li&gt;CLAUDE.md 중첩, 라우팅 레이어, 서브에이전트 전용 컨텍스트 삽입, 상황별 에이전트 팀즈 활용 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검증 가능성 확보&lt;/strong&gt; : 에이전트 동작을 테스트할 수 있는 방법
&lt;ul&gt;
&lt;li&gt;중복된 CLAUDE.md를 읽었는가?&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;/ol&gt;
&lt;p&gt;위 문제를 해결하기 위해 아래의 문서를 차근차근 읽어가고 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Codex 팀의 Ryan Lopopolo는 이걸 &quot;Harness Engineering&quot;이라고 부릅니다.&lt;br&gt;
엔지니어의 역할이 코드를 작성하는 사람에서, 에이전트가 일할 수 있는 환경을 설계하고 의도를 명세하고 피드백 루프를 만드는 사람으로 바뀌는 겁니다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;Humans steer, agents execute&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://openai.com/ko-KR/index/unlocking-the-codex-harness/&quot;&gt;OpenAI Harness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openai.com/index/harness-engineering/&quot;&gt;OpenAI Harness Engineering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tonylee.im/ko/blog/openai-harness-engineering-five-principles-codex/&quot;&gt;OpenAI Harness 5원칙 - toneylee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/cookbook/articles/codex_exec_plans&quot;&gt;PLANS.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://architecture.md/&quot;&gt;ARCHITECTURE.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bits.logic.inc/p/ai-is-forcing-us-to-write-good-code&quot;&gt;AI는 좋은 코드를 강제한다&lt;/a&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;strong&gt;100% 테스트 커버리지&lt;/strong&gt; : 에이전트가 건드린 모든 코드 라인의 동작을 반드시 예시(테스트)로 검증하게 만드는 장치로 100% 커버리지를 요구&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;파일/디렉터리 구조와 작은 모듈&lt;/strong&gt; : 에이전트는 결국 파일 시스템을 보며 탐색하므로, 의미 있는 디렉터리/파일 이름과 잘게 쪼개진 파일이 중요한 인터페이스가 된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타입과 자동화된 베스트 프랙티스&lt;/strong&gt; : 린터/포매터를 최대한 엄격히 돌리고, 작업 종료나 커밋 시 자동으로 고치게 만드는 등 가급적 많은 규칙을 자동으로 강제한다.&lt;/li&gt;
&lt;li&gt;API는 OpenAPI 기반 타입 세이프 클라이언트, 데이터는 타입,체크,트리거, ORM은 Kysely 같은 타이핑 좋은 툴을 통해 끝단까지 타입을 관통시키는 식으로 설계한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2026-02-10&quot; style=&quot;position:relative;&quot;&gt;2026-02-10&lt;a href=&quot;#2026-02-10&quot; aria-label=&quot;2026 02 10 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;처음에는 아키텍처, 클린 코드 같은 &quot;사람을 위한 고민&quot;이 AI에게 도움이 될지 의문이었다.&lt;br&gt;
하지만 사람에게 인지부하가 낮다는 건 AI 컨텍스트도 작게 차지한다는 의미라는 걸 깨달았다.&lt;/p&gt;
&lt;p&gt;멀티 모듈의 특정 모듈, 아키텍처의 특정 레이어, 특정 패키지에는 우리가 목적과 책임을 부여한다.&lt;br&gt;
이 목적과 책임을 에이전트별로 구분할 수 있다면, 에이전트의 컨텍스트 윈도우도 효율적으로 사용할 수 있다.&lt;br&gt;
Claude Code의 Agent Teams(실험적 기능)를 사용해보면서 결국 &lt;strong&gt;에이전트를 어떻게 배치하고 활용하느냐&lt;/strong&gt;가 핵심이라는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;테스트 코드, 클린 코드 같은 기술적 컨벤션은 이미 많은 SKILL이 대중화되어 적용하기 쉽다.&lt;br&gt;
그런데 &lt;strong&gt;도메인 지식은 어떻게 계층화하여 AI 컨텍스트에 삽입할지&lt;/strong&gt;가 고민이다.&lt;/p&gt;
&lt;p&gt;부문장님의 &lt;a href=&quot;https://github.com/JSON-OBJECT/claude-code/blob/main/plugins/deep-thinking/commands/forge-prompt.md&quot;&gt;forge-prompt&lt;/a&gt;를 활용해서 도메인 에이전트를 설계해볼 생각이다.&lt;br&gt;
핵심 비즈니스의 도메인 정보를 자산화하는 것이 중요한 과제처럼 느껴진다.&lt;br&gt;
꾸준한 자산화를 통해 결국 도메인 정보도 AI에게 전적으로 의지할 수 있지 않을까?&lt;/p&gt;
&lt;p&gt;비즈니스별로 AI 컨텍스트를 잘 관리할 수 있다면, AI TPM(Technical Project Manager)을 만들 수 있지 않을까?&lt;br&gt;
프론트, 이커머스, OMS 프로젝트에 AI 컨텍스트 구조를 설계하여 TPM을 구현하는 것이 현재 목표다.&lt;/p&gt;
&lt;h2 id=&quot;2026-02-03&quot; style=&quot;position:relative;&quot;&gt;2026-02-03&lt;a href=&quot;#2026-02-03&quot; aria-label=&quot;2026 02 03 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;요즘 애플리케이션 아키텍처에 대한 고민을 하다가 문득, 이런 아키텍처 작업은 사람을 위한 행위인데 AI를 위한 고민이 중심이 되어야 하지 않나?&lt;br&gt;
사람이 코드를 언제까지 읽을 수 있을까? 아직 검수는 필수이지만 대부분 코드에 대한 이해를 AI에게 의존하고 있는데 애플리케이션 아키텍처가 중요할까?&lt;br&gt;
AI친화 아키텍처를 고민해야하지 않을까? 라는 생각을 했다.&lt;br&gt;
결국 아키텍처도 책임과 목적을 분리하고 인지부하를 낮추는 행위이기 때문에 AI의 토큰 절약이나 컨텍스트를 너무 많이 차지하지 않게 하여 해결이 필요한 부분의 적절한 크기만큼 메모리에 로딩하게 하는것도 전략이기 때문에 결국 아키텍처 고민도 적절하다고 생각하긴 한다.&lt;br&gt;
하지만 에이전틱 프로그래밍을 이루기 위해 어떤 문서를 어디에 위치시키고 어떤 전략을 가져야할까를 고민하게 됐다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/overview&quot;&gt;프롬프트 엔지니어링 개요&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.c2.com/?IntentionRevealingNames&quot;&gt;의도를 드러내는 이름&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/oms-claude-ai-workflow/&quot;&gt;OMS에서 Claude AI를 활용하여 변화된 업무 방식&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=3HzELVptsb4&quot;&gt;&quot;배민은 이제 노가다 안 합니다&quot; 1시간 업무를 1분만에 끝내는 AI 자산화의 비밀(우아한형제들 임동준님)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;ADR.md : 의사결정 문서&lt;/li&gt;
&lt;li&gt;TODO.md : 큰 작업을 한 번에 끝내기 힘든 경우&lt;/li&gt;
&lt;li&gt;CLAUDE.md : 모든 프롬프팅에서 공통적으로 주입하기 위한 내용
&lt;ul&gt;
&lt;li&gt;추가로 주입하고 싶은 정보가 있으면 다른 파일들 링크 추가해도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SKILLS : 한 번에 너무 많은 정보를 전달하기 보다는 필요한 작업에 필요한 내용을 주입하기&lt;/li&gt;
&lt;li&gt;커스텀 커맨드 : 예를 들어, 세션에서 나눈 기술적 의사결정을 ADR.md에 정리하도록 &lt;code class=&quot;language-text&quot;&gt;/adr&lt;/code&gt; 커맨드 추가&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[MySQL Lock 실험실]]></title><description><![CDATA[InnoDB 행 잠금의 2원 2규칙 원칙 1: InnoDB의 기본 잠금 단위는 넥스트 키 락이며, 넥스트 키 락의 잠금 범위는 좌측으로는 개구간, 우측으로는 폐구간이다.  InnoDB…]]></description><link>https://jdalma.github.io/2025y/labs/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/labs/</guid><pubDate>Sun, 26 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://devocean.sk.com/blog/techBoardDetail.do?ID=167948&amp;#x26;boardType=techBlog&quot;&gt;InnoDB 행 잠금의 2원 2규칙&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;원칙 1&lt;/strong&gt;: InnoDB의 기본 잠금 단위는 넥스트 키 락이며, 넥스트 키 락의 잠금 범위는 좌측으로는 개구간, 우측으로는 폐구간이다. &lt;code class=&quot;language-text&quot;&gt;ex) (R1, R10]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;InnoDB 잠금의 시작은 넥스트 키 락이다. 이후에 갭 락과 레코드 락을 따져보는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;원칙 2&lt;/strong&gt;: 잠금은 쿼리를 수행하는 과정에서 접근한 객체에만 걸린다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;규칙 1&lt;/strong&gt;: 인덱스(고유, 비고유)를 사용하는 동등 조건의 쿼리를 수행할 때 레코드 스캔 방향은 오른쪽이며, 마지막 레코드가 동등 조건을 만족하지 않으면 넥스트 키 락은 갭 락으로 강등된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;규칙 2&lt;/strong&gt;: 고유 인덱스를 사용하는 동등 조건의 쿼리를 수행할 때, 레코드가 동등 조건을 만족하면 넥스트 키 락은 레코드 락으로 강등된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;details&gt;
&lt;summary&gt;🔐 잠금 상태 조회 쿼리&lt;/summary&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- 잠금 상태 조회하기
SELECT 
    dl.object_name AS `table`,
    dl.lock_type,
    dl.index_name,
    dl.lock_mode,
    CASE 
        WHEN dl.lock_mode LIKE &amp;#39;%REC_NOT_GAP%&amp;#39; THEN &amp;#39;Record Lock&amp;#39;
        WHEN dl.lock_mode LIKE &amp;#39;%GAP%&amp;#39; AND dl.lock_mode NOT LIKE &amp;#39;%REC_NOT_GAP%&amp;#39; THEN &amp;#39;Gap Lock&amp;#39;
        WHEN dl.lock_mode NOT LIKE &amp;#39;%GAP%&amp;#39; AND dl.lock_mode NOT LIKE &amp;#39;%REC_NOT_GAP%&amp;#39; THEN &amp;#39;Next-Key Lock&amp;#39;
        ELSE dl.lock_mode
    END AS lock_type_detail,
    dl.lock_data AS locked_data
FROM performance_schema.data_locks dl
ORDER BY dl.object_name, dl.lock_data;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/details&gt;
&lt;h2 id=&quot;갭-락으로-인한-insert-실패&quot; style=&quot;position:relative;&quot;&gt;갭 락으로 인한 INSERT 실패&lt;a href=&quot;#%EA%B0%AD-%EB%9D%BD%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%9C-insert-%EC%8B%A4%ED%8C%A8&quot; aria-label=&quot;갭 락으로 인한 insert 실패 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
| 20 |   20 |   20 |
| 25 |   25 |   25 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt; 존재하지 않는 id = 7를 업데이트 
BEGIN;
UPDATE t SET b=b+1 WHERE id=7;

+-----------+------------+-----------+------------------+-------------+
| lock_type | index_name | lock_mode | lock_type_detail | locked_data |
+-----------+------------+-----------+------------------+-------------+
| TABLE     | NULL       | IX        | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,GAP     | Gap Lock         | 10          |
+-----------+------------+-----------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
INSERT INTO t VALUES (8, 8, 8);     -- BLOCKED
INSERT INTO t VALUES (9, 9, 9);     -- BLCOKED

INSERT INTO t VALUES (4, 4, 4);     -- 성공
INSERT INTO t VALUES (11, 11, 11);  -- 성공
UPDATE t SET b=b+1 WHERE id=5;      -- 성공
UPDATE t SET b=b+1 WHERE id=10;     -- 성공&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;먼저 넥스트 키 락에 의해 &lt;code class=&quot;language-text&quot;&gt;(5, 10]&lt;/code&gt;으로 잠금되고, 마지막 레코드 10이 id=7 조건에 만족하지 않기 때문에 넥스트 키 락은 갭 락으로 강등되어 &lt;code class=&quot;language-text&quot;&gt;(5, 10)&lt;/code&gt;의 잠금이 발생한다.&lt;br&gt;
그리하여 마지막 업데이트 쿼리는 둘 다 성공한다.&lt;/p&gt;
&lt;h2 id=&quot;non-unique-세컨더리-인덱스-동등-잠금-for-share&quot; style=&quot;position:relative;&quot;&gt;non-unique 세컨더리 인덱스 동등 잠금 (FOR SHARE)&lt;a href=&quot;#non-unique-%EC%84%B8%EC%BB%A8%EB%8D%94%EB%A6%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EB%8F%99%EB%93%B1-%EC%9E%A0%EA%B8%88-for-share&quot; aria-label=&quot;non unique 세컨더리 인덱스 동등 잠금 for share permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
| 20 |   20 |   20 |
| 25 |   25 |   25 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
select id from t where a = 5 for share;

+-----------+------------+-----------+------------------+-------------+
| lock_type | index_name | lock_mode | lock_type_detail | locked_data |
+-----------+------------+-----------+------------------+-------------+
| TABLE     | NULL       | IS        | Next-Key Lock    | NULL        |
| RECORD    | ix_a       | S,GAP     | Gap Lock         | 10, 10      | &amp;lt;- (5, 10)
| RECORD    | ix_a       | S         | Next-Key Lock    | 5, 5        | &amp;lt;- (0, 5]
+-----------+------------+-----------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
INSERT INTO t VALUES (-1, -1, -1);  -- 성공
INSERT INTO t VALUES (0,0,0);   -- BLOCKED
INSERT INTO t VALUES (3,3,3);   -- BLOCKED
INSERT INTO t VALUES (7,7,7);   -- BLOCKED

UPDATE t SET b=b+1 WHERE id=5;  -- 성공
UPDATE t SET b=b+1 WHERE id=10; -- 성공

+----+------+------+
| id | a    | b    |
+----+------+------+
| -1 |   -1 |   -1 |
|  0 |    0 |    0 |
|  5 |    5 |    6 |
| 10 |   10 |   11 |
+----+------+------+

UPDATE t SET a=a+1 WHERE id=10; -- 성공
UPDATE t SET a=a+1 WHERE id=5;  -- BLOCKED

+----+------+------+
| id | a    | b    |
+----+------+------+
| -1 |   -1 |   -1 |
|  0 |    0 |    0 |
|  5 |    5 |    6 |
| 10 |   11 |   11 |
+----+------+------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Q: 왜 5미만의 INSERT 쿼리는 실행하지 못할까?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;원칙 1에 따라 기본 잠금 단위는 넥스트 키 락이기 때문에 &lt;code class=&quot;language-text&quot;&gt;(0,5]&lt;/code&gt;에 넥스트 키 락이 설정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q: 왜 &lt;code class=&quot;language-text&quot;&gt;id = (5 &amp;lt; N &amp;lt; 10)&lt;/code&gt;의 INSERT 쿼리는 실행하지 못할까?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;a 컬럼 인덱스는 유니크하지 않은 세컨더리 인덱스이기 때문에 &lt;code class=&quot;language-text&quot;&gt;a = 5&lt;/code&gt;에 해당하는 레코드만 찾아서 바로 종료하는 것이 아니라 오른쪽으로 계속 스캔하여 레코드 10을 찾을 때까지 이동하기에 &lt;code class=&quot;language-text&quot;&gt;(5,10]&lt;/code&gt;에 넥스트 키 락이 설정된다. &lt;strong&gt;이 과정에서 접근한 모든 객체에 잠금을 걸게 되기 때문이다&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q: 왜 &lt;code class=&quot;language-text&quot;&gt;id=10&lt;/code&gt;에 대한 a,b 컬럼 UPDATE는 성공할까?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;10은 &lt;code class=&quot;language-text&quot;&gt;a = 5&lt;/code&gt; 조건을 만족하지 않기 때문에 넥스트 키 락이 갭 락으로 강등되어 &lt;code class=&quot;language-text&quot;&gt;(5,10)&lt;/code&gt;으로 잠금 범위가 변경되기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q: FOR SHARE로 &lt;code class=&quot;language-text&quot;&gt;a = 5&lt;/code&gt;에 대한 명시적 공유 잠금을 걸었는데 b 컬럼에 대한 업데이트는 왜 실행될까?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;A 세션의 FOR SHARE 쿼리는 커버링 인덱스를 활용하고 있고 조회 가능 잠금이기 때문에 프라이머리 키 인덱스에 어떠한 잠금도 설정되지 않는다.&lt;/li&gt;
&lt;li&gt;잠금에 대한 정보를 통해 &lt;code class=&quot;language-text&quot;&gt;ix_a 인덱스의 (5,10) 구간에 걸린 Gap Lock&lt;/code&gt;으로 인해 a 컬럼은 UPDATE가 실행되지 않았고, b 컬럼에 대한 UPDATE는 실행 가능한 것이다.&lt;/li&gt;
&lt;li&gt;만약 &lt;code class=&quot;language-text&quot;&gt;a = 5&lt;/code&gt;에 대한 조회 가능 잠금을 통해 레코드가 업데이트 되는 것을 원하지 않으면 FOR SHARE 쿼리를 수정하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;select * from t where a = 5 for share; -- 또는
select id, a, b from t where a = 5 for share;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IS            | Next-Key Lock    | NULL        |
| RECORD    | ix_a       | S,GAP         | Gap Lock         | 10, 10      |
| RECORD    | PRIMARY    | S,REC_NOT_GAP | Record Lock      | 5           | -&amp;gt; 레코드 락을 확인할 수 있다!
| RECORD    | ix_a       | S             | Next-Key Lock    | 5, 5        |
+-----------+------------+---------------+------------------+-------------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;non-unique-세컨더리-인덱스-동등-장금-for-update&quot; style=&quot;position:relative;&quot;&gt;non-unique 세컨더리 인덱스 동등 장금 (FOR UPDATE)&lt;a href=&quot;#non-unique-%EC%84%B8%EC%BB%A8%EB%8D%94%EB%A6%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EB%8F%99%EB%93%B1-%EC%9E%A5%EA%B8%88-for-update&quot; aria-label=&quot;non unique 세컨더리 인덱스 동등 장금 for update permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
| 20 |   20 |   20 |
| 25 |   25 |   25 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
select id from t where a = 5 for update;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | ix_a       | X,GAP         | Gap Lock         | 10, 10      |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 5           |
| RECORD    | ix_a       | X             | Next-Key Lock    | 5, 5        |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;

INSERT INTO t VALUES (7,7,7);   -- BLOCKED
UPDATE t SET b=b+1 WHERE id=5;  -- BLOCKED
UPDATE t SET b=b+1 WHERE id=10; -- 성공

UPDATE t SET a=a+1 WHERE id=5;   -- BLOCKED
UPDATE t SET a=a+1 WHERE id=10;  -- 성공&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;프라이머리-키-인덱스-범위-잠금&quot; style=&quot;position:relative;&quot;&gt;프라이머리 키 인덱스 범위 잠금&lt;a href=&quot;#%ED%94%84%EB%9D%BC%EC%9D%B4%EB%A8%B8%EB%A6%AC-%ED%82%A4-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EB%B2%94%EC%9C%84-%EC%9E%A0%EA%B8%88&quot; aria-label=&quot;프라이머리 키 인덱스 범위 잠금 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;조회 결과는 같지만 잠금 범위가 서로 다른 케이스를 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
SELECT * FROM t WHERE id=10 FOR UPDATE;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 10          |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
UPDATE t SET b=b+1 WHERE id=5;  -- 성공
INSERT INTO t VALUES (7,7,7);   -- 성공
INSERT INTO t VALUES (11,11,11);    -- 성공&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;처음엔 &lt;code class=&quot;language-text&quot;&gt;(5, 10]&lt;/code&gt; 넥스트 키 락이 설정되고, id는 프라이머리 키 인덱스이고 &lt;code class=&quot;language-text&quot;&gt;id = 10&lt;/code&gt; 조건에 해당하는 레코드가 존재하므로 레코드 락으로 강등된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;A 세션&amp;gt;
BEGIN;
SELECT * FROM t WHERE id &amp;gt;= 10 AND id &amp;lt; 11 FOR UPDATE;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 10          |
| RECORD    | PRIMARY    | X,GAP         | Gap Lock         | 15          |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
INSERT INTO t VALUES (8, 8, 8);     -- 성공
INSERT INTO t VALUES (9, 9, 9);     -- 성공
INSERT INTO t VALUES (11, 11, 11);  -- BLOCKED

UPDATE t SET b=b+1 WHERE id = 15;   -- 성공
UPDATE t SET a=a+1 WHERE id = 15;   -- 성공
UPDATE t SET a=a+1 WHERE id = 10;   -- BLOCKED&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;id &gt;= 10 AND id &amp;lt; 11&lt;/code&gt; 조건에 해당하는 레코드를 찾게 되는데, 먼저 &lt;code class=&quot;language-text&quot;&gt;id &gt;= 10&lt;/code&gt;에 대한 &lt;code class=&quot;language-text&quot;&gt;(5, 10]&lt;/code&gt; 넥스트 키 락이 설정된다. 하지만 &lt;code class=&quot;language-text&quot;&gt;id = 10&lt;/code&gt;인 레코드가 존재하므로 레코드 락으로 강등된다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;id &amp;lt; 11&lt;/code&gt; 같은 범위 검색은 조건을 만족하는지 안하는지 다음 첫 번째 레코드까지 접근해야만 알 수 있기 때문에 오른쪽으로 범위 검색을 계속해서 수행하며 &lt;code class=&quot;language-text&quot;&gt;id=15&lt;/code&gt; 레코드를 찾게된다.&lt;br&gt;
이미 &lt;code class=&quot;language-text&quot;&gt;id=10&lt;/code&gt;레코드를 찾았으므로 &lt;code class=&quot;language-text&quot;&gt;(10, 15)&lt;/code&gt;에 갭락이 설정된다.&lt;/p&gt;
&lt;p&gt;즉, 최종 잠금 범위는 &lt;code class=&quot;language-text&quot;&gt;10, (10, 15)&lt;/code&gt;가 된다.&lt;/p&gt;
&lt;h2 id=&quot;non-unique-세컨더리-인덱스-범위-잠금&quot; style=&quot;position:relative;&quot;&gt;non-unique 세컨더리 인덱스 범위 잠금&lt;a href=&quot;#non-unique-%EC%84%B8%EC%BB%A8%EB%8D%94%EB%A6%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EB%B2%94%EC%9C%84-%EC%9E%A0%EA%B8%88&quot; aria-label=&quot;non unique 세컨더리 인덱스 범위 잠금 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
SELECT * FROM t WHERE a &amp;gt;= 10 AND a &amp;lt; 11 FOR UPDATE;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 10          |
| RECORD    | ix_a       | X             | Next-Key Lock    | 10, 10      |
| RECORD    | ix_a       | X             | Next-Key Lock    | 15, 15      |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
INSERT INTO t VALUES (6, 6, 6);     -- BLOCKED
INSERT INTO t VALUES (8, 8, 8);     -- BLOCKED
INSERT INTO t VALUES (4, 4, 4);     -- 성공

UPDATE t SET b=b+1 WHERE a = 15;    -- BLOCKED
UPDATE t SET b=b+1 WHERE a = 10;    -- BLOCKED
UPDATE t SET b=b+1 WHERE a = 5;     -- 성공
UPDATE t SET b=b+1 WHERE a = 4;     -- 성공&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;먼저 &lt;code class=&quot;language-text&quot;&gt;a=10&lt;/code&gt;인 레코드를 찾아 &lt;code class=&quot;language-text&quot;&gt;(5, 10]&lt;/code&gt; 넥스트 키 락이 설정된다. &lt;code class=&quot;language-text&quot;&gt;ix_a&lt;/code&gt; 인덱스는 유니크 인덱스가 아니기 때문에 레코드 락으로 끝나지 않는다. (규칙 2를 적용할 수 없다.)&lt;br&gt;
그렇기에 동등 조건을 만족하지 않는 다음 레코드까지 범위 검색을 수행하며, &lt;code class=&quot;language-text&quot;&gt;a=15&lt;/code&gt;에 해당하는 레코드를 찾아 검색이 중단되고 &lt;code class=&quot;language-text&quot;&gt;(10, 15]&lt;/code&gt;에 넥스트 키 락이 설정된다.&lt;br&gt;
동등 조건이 아닌 범위 검색에 해당하기 때문에 갭 락으로 강등되지 않는다.&lt;/p&gt;
&lt;p&gt;즉, 최종 잠금 범위는 &lt;code class=&quot;language-text&quot;&gt;(5, 10], (10, 15]&lt;/code&gt;이다.&lt;/p&gt;
&lt;h2 id=&quot;unique-세컨더리-인덱스-업데이트-잠금&quot; style=&quot;position:relative;&quot;&gt;unique 세컨더리 인덱스 업데이트 잠금&lt;a href=&quot;#unique-%EC%84%B8%EC%BB%A8%EB%8D%94%EB%A6%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EC%9E%A0%EA%B8%88&quot; aria-label=&quot;unique 세컨더리 인덱스 업데이트 잠금 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t2 (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  UNIQUE ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
| 20 |   20 |   20 |
| 25 |   25 |   25 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
UPDATE t2 set b = b + 1 where a = 10;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 10          |
| RECORD    | ix_a       | X,REC_NOT_GAP | Record Lock      | 10, 10      |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;

insert into t2 values (9, 9, 9);    -- 성공
insert into t2 values (11, 11, 11); -- 성공
update t2 set b = 15 where a = 10;  -- BLOCKED&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;a&lt;/code&gt; 컬럼의 인덱스는 유니크함을 보장하기 때문에 &lt;code class=&quot;language-text&quot;&gt;a = 10&lt;/code&gt;을 찾게되면 넥스트 키 락은 레코드 락으로 강등되기 때문에 왼쪽과 오른쪽에 대한 갭락 또는 넥스트 키 락이 발생하지 않는다.&lt;/p&gt;
&lt;h2 id=&quot;non-unique-세컨더리-인덱스-중복된-행-잠금&quot; style=&quot;position:relative;&quot;&gt;non-unique 세컨더리 인덱스 중복된 행 잠금&lt;a href=&quot;#non-unique-%EC%84%B8%EC%BB%A8%EB%8D%94%EB%A6%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%A4%91%EB%B3%B5%EB%90%9C-%ED%96%89-%EC%9E%A0%EA%B8%88&quot; aria-label=&quot;non unique 세컨더리 인덱스 중복된 행 잠금 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
| 20 |   20 |   20 |
| 25 |   25 |   25 |
| 30 |   10 |   30 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
SELECT * FROM t WHERE a=10 FOR UPDATE;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 10          |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 30          |
| RECORD    | ix_a       | X             | Next-Key Lock    | 10, 10      |
| RECORD    | ix_a       | X             | Next-Key Lock    | 10, 30      |
| RECORD    | ix_a       | X,GAP         | Gap Lock         | 15, 15      |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
INSERT INTO t VALUES (4, 4, 4);      -- 성공
INSERT INTO t VALUES (6, 6, 6);      -- BLOCKED
INSERT INTO t VALUES (9, 9, 9);      -- BLOCKED
INSERT INTO t VALUES (12, 12, 12);   -- BLOCKED
INSERT INTO t VALUES (17, 17, 17);   -- 성공
INSERT INTO t VALUES (24, 24, 24);   -- 성공
INSERT INTO t VALUES (27, 27, 27);   -- 성공

UPDATE t SET b=b+1 WHERE a = 5;     -- 성공
UPDATE t SET b=b+1 WHERE a = 10;    -- BLOCKED
UPDATE t SET b=b+1 WHERE a = 15;    -- 성공
UPDATE t SET b=b+1 WHERE a = 20;    -- 성공
UPDATE t SET b=b+1 WHERE a = 25;    -- 성공

UPDATE t SET a=a+1 WHERE id = 0;
UPDATE t SET a=a+1 WHERE id = 5;     -- BLOCKED
UPDATE t SET a=a+1 WHERE id = 10;    -- BLOCKED
UPDATE t SET a=a+1 WHERE id = 15;
UPDATE t SET a=a+1 WHERE id = 20;
UPDATE t SET a=a+1 WHERE id = 25;
UPDATE t SET a=a+1 WHERE id = 30;    -- BLOCKED

UPDATE t SET b=b+1 WHERE id = 0;
UPDATE t SET b=b+1 WHERE id = 5;
UPDATE t SET b=b+1 WHERE id = 10;    -- BLOCKED
UPDATE t SET b=b+1 WHERE id = 15;
UPDATE t SET b=b+1 WHERE id = 20;
UPDATE t SET b=b+1 WHERE id = 25;
UPDATE t SET b=b+1 WHERE id = 30;    -- BLOCKED&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;먼저 &lt;code class=&quot;language-text&quot;&gt;a=10&lt;/code&gt; 조건에 해당하는 레코드를 찾아 &lt;code class=&quot;language-text&quot;&gt;(5, 10]&lt;/code&gt; 넥스트 키 락을 설정한다.&lt;br&gt;
정확히는 &lt;code class=&quot;language-text&quot;&gt;(a=5, id=5)부터 (a=10, id=10)&lt;/code&gt;까지 넥스트 키 락이 설정된다.&lt;/p&gt;
&lt;p&gt;그리고 넌유니크 세컨더리 인덱스이기 때문에 다음 레코드 &lt;code class=&quot;language-text&quot;&gt;a=15&lt;/code&gt;를 찾을 때 까지 오른쪽으로 검색을 수행하며, 이 과정에서 &lt;code class=&quot;language-text&quot;&gt;a=10, id=30&lt;/code&gt; 레코드를 거치기 때문에 &lt;code class=&quot;language-text&quot;&gt;(a=10, id=10) ~ (a=10, id=30)&lt;/code&gt;까지 넥스트 키 락이 설정된다.&lt;/p&gt;
&lt;p&gt;계속 오른쪽으로 검색하며 &lt;code class=&quot;language-text&quot;&gt;a=15, id=15&lt;/code&gt; 레코드를 만나면 검색 루프는 종료되고 &lt;code class=&quot;language-text&quot;&gt;(a=10, id=30) ~ (a=15, id=15)&lt;/code&gt; 까지 넥스트 키 락이 설정된다.&lt;br&gt;
하지만 검색 조건 &lt;code class=&quot;language-text&quot;&gt;a=10&lt;/code&gt; 조건에 해당되지 않으므로 최종적으로 갭 락으로 강등된다.&lt;/p&gt;
&lt;p&gt;즉 최종 잠금 범위는&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- ix_a, X (Next-Key Lock)
( (a=5, id=5), (a=10, id=10) ],

-- ix_a, X (Next-Key Lock)
( (a=10, id=10), (a=10, id=30) ], 

-- ix_a, X,GAP (Gap Lock)
( (a=10, id=30), (a=15, id=15) )&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;non-unique-세컨더리-인덱스-중복된-행-잠금-추가-케이스&quot; style=&quot;position:relative;&quot;&gt;non-unique 세컨더리 인덱스 중복된 행 잠금 (추가 케이스)&lt;a href=&quot;#non-unique-%EC%84%B8%EC%BB%A8%EB%8D%94%EB%A6%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%A4%91%EB%B3%B5%EB%90%9C-%ED%96%89-%EC%9E%A0%EA%B8%88-%EC%B6%94%EA%B0%80-%EC%BC%80%EC%9D%B4%EC%8A%A4&quot; aria-label=&quot;non unique 세컨더리 인덱스 중복된 행 잠금 추가 케이스 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE employees (
    id int NOT NULL AUTO_INCREMENT,
    first_name varchar(255) DEFAULT NULL,
    last_name varchar(255) DEFAULT NULL,
    PRIMARY KEY (id),
    KEY idx_first_name (first_name)
) ENGINE=InnoDB

+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
| 34 | E          | E1        |
| 35 | E          | E2        |
| 36 | E          | E3        |
| 37 | B          | B1        |
| 38 | B          | B2        |
+----+------------+-----------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;A 세션&amp;gt;
BEGIN;
update employees SET last_name = &amp;#39;Updated E&amp;#39; where first_name = &amp;#39;E&amp;#39; and last_name = &amp;#39;E2&amp;#39;;

+-----------+----------------+---------------+------------------+------------------------+
| lock_type | index_name     | lock_mode     | lock_type_detail | locked_data            |
+-----------+----------------+---------------+------------------+------------------------+
| TABLE     | NULL           | IX            | Next-Key Lock    | NULL                   |
| RECORD    | idx_first_name | X             | Next-Key Lock    | &amp;#39;E&amp;#39;, 34                |
| RECORD    | idx_first_name | X             | Next-Key Lock    | &amp;#39;E&amp;#39;, 35                |
| RECORD    | idx_first_name | X             | Next-Key Lock    | &amp;#39;E&amp;#39;, 36                |
| RECORD    | PRIMARY        | X,REC_NOT_GAP | Record Lock      | 34                     |
| RECORD    | PRIMARY        | X,REC_NOT_GAP | Record Lock      | 35                     |
| RECORD    | PRIMARY        | X,REC_NOT_GAP | Record Lock      | 36                     |
| RECORD    | idx_first_name | X             | Next-Key Lock    | supremum pseudo-record |
+-----------+----------------+---------------+------------------+------------------------+

-- &amp;lt;B 세션&amp;gt;
insert into employees (first_name, last_name) values (&amp;#39;A&amp;#39;, &amp;#39;A1&amp;#39;);    -- 성공
insert into employees (first_name, last_name) values (&amp;#39;B&amp;#39;, &amp;#39;B1&amp;#39;);    -- BLOCKED
insert into employees (first_name, last_name) values (&amp;#39;C&amp;#39;, &amp;#39;C1&amp;#39;);    -- BLOCKED
insert into employees (first_name, last_name) values (&amp;#39;D&amp;#39;, &amp;#39;D1&amp;#39;);    -- BLOCKED
insert into employees (first_name, last_name) values (&amp;#39;F&amp;#39;, &amp;#39;F1&amp;#39;);    -- BLOCKED
insert into employees (first_name, last_name) values (&amp;#39;Z&amp;#39;, &amp;#39;Z1&amp;#39;);    -- BLOCKED

update employees set last_name = &amp;#39;updated B1&amp;#39; where id = 37;   -- 성공
update employees set last_name = &amp;#39;updated B2&amp;#39; where id = 38;   -- 성공
update employees set last_name = &amp;#39;updated B2&amp;#39; where first_name = &amp;#39;B&amp;#39;;    -- 성공
update employees set first_name = &amp;#39;C&amp;#39; where id = 37;   -- BLOCKED
update employees set first_name = &amp;#39;A&amp;#39; where id = 37;   -- 성공&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;먼저 &lt;code class=&quot;language-text&quot;&gt;first_name = &apos;E&apos;&lt;/code&gt;를 찾기 위해 오른쪽으로 검색하지만 &lt;code class=&quot;language-text&quot;&gt;E&lt;/code&gt;보다 큰 데이터는 없기에 supremum 락이 걸린다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;(&amp;#39;B&amp;#39;, 37)
(&amp;#39;B&amp;#39;, 38) ← 마지막 B 레코드
    ↓
  [갭1] ← (&amp;#39;B&amp;#39;, 38) ~ (&amp;#39;E&amp;#39;, 34)
    ↓
(&amp;#39;E&amp;#39;, 34) ← Next-Key Lock = 레코드 + 갭1
(&amp;#39;E&amp;#39;, 35) ← Next-Key Lock = 레코드 + 갭2
(&amp;#39;E&amp;#39;, 36) ← Next-Key Lock = 레코드 + 갭3
    ↓
  [갭4] ← (&amp;#39;E&amp;#39;, 36) ~ supremum
    ↓
supremum  ← Next-Key Lock = supremum + 갭4&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;strong&gt;실제 락 범위&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;(&apos;E&apos;, 34)&lt;/code&gt; &lt;strong&gt;Next-Key Lock&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;레코드: &lt;code class=&quot;language-text&quot;&gt;(&apos;E&apos;, 34)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;갭: &lt;code class=&quot;language-text&quot;&gt;((&apos;B&apos;, 38) ~ (&apos;E&apos;, 34))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;(&apos;E&apos;, 35)&lt;/code&gt; &lt;strong&gt;Next-Key Lock&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;레코드: &lt;code class=&quot;language-text&quot;&gt;(&apos;E&apos;, 35)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;갭: &lt;code class=&quot;language-text&quot;&gt;((&apos;E&apos;, 34) ~ (&apos;E&apos;, 35))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;(&apos;E&apos;, 36)&lt;/code&gt; &lt;strong&gt;Next-Key Lock&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;레코드: &lt;code class=&quot;language-text&quot;&gt;(&apos;E&apos;, 36)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;갭: &lt;code class=&quot;language-text&quot;&gt;((&apos;E&apos;, 35) ~ (&apos;E&apos;, 36))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;supremum Next-Key Lock&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;supremum 자체&lt;/li&gt;
&lt;li&gt;갭: &lt;code class=&quot;language-text&quot;&gt;((&apos;E&apos;, 36) ~ supremum)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;non-unique-세컨더리-인덱스-limit-잠금&quot; style=&quot;position:relative;&quot;&gt;non-unique 세컨더리 인덱스 LIMIT 잠금&lt;a href=&quot;#non-unique-%EC%84%B8%EC%BB%A8%EB%8D%94%EB%A6%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-limit-%EC%9E%A0%EA%B8%88&quot; aria-label=&quot;non unique 세컨더리 인덱스 limit 잠금 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
| 20 |   20 |   20 |
| 25 |   25 |   25 |
| 30 |   10 |   30 |
| 35 |   10 |   35 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
select * from t where a = 10 limit 2 FOR UPDATE;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 10          |
| RECORD    | ix_a       | X             | Next-Key Lock    | 10, 10      |
| RECORD    | ix_a       | X             | Next-Key Lock    | 10, 30      |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 30          |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
insert into t values (9, 9, 9);       -- BLOCKED
insert into t values (13, 13, 13);    -- 성공
insert into t values (14, 9, 14);     -- BLOCKED
insert into t values (16, 16, 16);    -- 성공

UPDATE t SET b=b+1 WHERE id = 15;     -- 성공&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 712px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0fa3f7bc2ed3df21f40adff80ae95151/c4538/not_gray_gaplock.webp&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 13.777777777777779%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/webp;base64,UklGRkgAAABXRUJQVlA4IDwAAACwAwCdASoUAAMAPtFUo0uoJKMhsAgBABoJaQDGfB7HiqstkCsGAAD+8eBgRKEvjjsZCYemeID/2OF4AAA=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;not gray gaplock&quot;
        title=&quot;&quot;
        src=&quot;/static/0fa3f7bc2ed3df21f40adff80ae95151/c4538/not_gray_gaplock.webp&quot;
        srcset=&quot;/static/0fa3f7bc2ed3df21f40adff80ae95151/d7e55/not_gray_gaplock.webp 225w,
/static/0fa3f7bc2ed3df21f40adff80ae95151/8626f/not_gray_gaplock.webp 450w,
/static/0fa3f7bc2ed3df21f40adff80ae95151/c4538/not_gray_gaplock.webp 712w&quot;
        sizes=&quot;(max-width: 712px) 100vw, 712px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;처음으로 좌측 개구간 &lt;code class=&quot;language-text&quot;&gt;(a=5, id=5)부터 (a=10, id=10)&lt;/code&gt; 까지 넥스트 키 락이 설정된다.&lt;/li&gt;
&lt;li&gt;우측으로 탐색하며 &lt;code class=&quot;language-text&quot;&gt;(a=10, id=30)&lt;/code&gt; 레코드에 접근학데 되므로 &lt;code class=&quot;language-text&quot;&gt;(a=10, id=10)부터 (a=10, id=30)&lt;/code&gt;까지 넥스트 키 락이 설정된다.&lt;/li&gt;
&lt;li&gt;LIMIT 2에 대한 조건을 만족했기 때문에 더 이상 검색을 진행하지 않고 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;넥스트-키-락갭-락--레코드-락으로-인한-데드락-발생-케이스&quot; style=&quot;position:relative;&quot;&gt;넥스트 키 락(갭 락 + 레코드 락)으로 인한 데드락 발생 케이스&lt;a href=&quot;#%EB%84%A5%EC%8A%A4%ED%8A%B8-%ED%82%A4-%EB%9D%BD%EA%B0%AD-%EB%9D%BD--%EB%A0%88%EC%BD%94%EB%93%9C-%EB%9D%BD%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%9C-%EB%8D%B0%EB%93%9C%EB%9D%BD-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4&quot; aria-label=&quot;넥스트 키 락갭 락  레코드 락으로 인한 데드락 발생 케이스 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE t (
  id int NOT NULL,
  a int NULL,
  b int NULL,
  PRIMARY KEY (id),
  KEY ix_a (a)
) ENGINE=InnoDB;

+----+------+------+
| id | a    | b    |
+----+------+------+
|  0 |    0 |    0 |
|  5 |    5 |    5 |
| 10 |   10 |   10 |
| 15 |   15 |   15 |
| 20 |   20 |   20 |
| 25 |   25 |   25 |
+----+------+------+

-- &amp;lt;A 세션&amp;gt;
BEGIN;
select * from t where a = 10 for update;

+-----------+------------+---------------+------------------+-------------+
| lock_type | index_name | lock_mode     | lock_type_detail | locked_data |
+-----------+------------+---------------+------------------+-------------+
| TABLE     | NULL       | IX            | Next-Key Lock    | NULL        |
| RECORD    | PRIMARY    | X,REC_NOT_GAP | Record Lock      | 10          |
| RECORD    | ix_a       | X             | Next-Key Lock    | 10, 10      |
| RECORD    | ix_a       | X,GAP         | Gap Lock         | 15, 15      |
+-----------+------------+---------------+------------------+-------------+

-- &amp;lt;B 세션&amp;gt;
UPDATE t SET b=b+1 WHERE a=10;   -- BLOCKED

-- &amp;lt;A 세션&amp;gt;
insert into t values (8,8,8);    -- 데드락을 감지하여 B 세션의 트랜잭션을 재시작해버리면서 성공한다.

-- &amp;lt;B 세션&amp;gt;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ol&gt;
&lt;li&gt;A 세션이 &lt;code class=&quot;language-text&quot;&gt;a=10 FOR UPDATE&lt;/code&gt;를 실행하면, 인덱스 ix_a 기준 &lt;code class=&quot;language-text&quot;&gt;(5,10], (10,15)&lt;/code&gt;에 넥스트 키 락(갭 락+레코드 락)이 설정된다.&lt;/li&gt;
&lt;li&gt;B 세션이 &lt;code class=&quot;language-text&quot;&gt;UPDATE t SET b=b+1 WHERE a=10;&lt;/code&gt;을 실행하면, 이미 A 세션이 소유한 레코드 락 때문에 세션 2는 대기한다.&lt;/li&gt;
&lt;li&gt;이후 A 세션이 커밋/롤백 없이 &lt;code class=&quot;language-text&quot;&gt;INSERT INTO t VALUES (8,8,8);&lt;/code&gt;을 실행하면, 해당 값은 ix_a 인덱스의 &lt;code class=&quot;language-text&quot;&gt;(5,10) 갭&lt;/code&gt;에 삽입되어야 하므로, B 세션이 이미 &lt;code class=&quot;language-text&quot;&gt;(5,10) 구간의 갭 락&lt;/code&gt;을 소유하고 있을 경우 세션 1의 INSERT도 대기(블로킹)하게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;결과적으로, A 세션은 B 세션의 갭 락을 기다리고, B 세션은 A 세션의 레코드 락을 기다리는 데드락이 성립하여 MySQL이 둘 중 하나의 트랜잭션을 강제 롤백시키면서 B 세션에 에러가 발생한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;핵심&lt;/strong&gt;&lt;br&gt;
InnoDB는 팬텀 리드 등 트랜잭션 고립성을 위해 넥스트 키 락(갭 락+레코드 락) 구조를 채택&lt;br&gt;
갭 락과 레코드 락이 분리되어 동작하기 때문에, 세션 간 대기가 서로 꼬이면 데드락이 자주 발생함&lt;br&gt;
데드락 발생 시 애플리케이션에서 트랜잭션 재시도 처리를 해주는 것이 필요함&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;넥스트-키-락은-왜-양-쪽으로-락을-걸까&quot; style=&quot;position:relative;&quot;&gt;넥스트 키 락은 왜 양 쪽으로 락을 걸까?&lt;a href=&quot;#%EB%84%A5%EC%8A%A4%ED%8A%B8-%ED%82%A4-%EB%9D%BD%EC%9D%80-%EC%99%9C-%EC%96%91-%EC%AA%BD%EC%9C%BC%EB%A1%9C-%EB%9D%BD%EC%9D%84-%EA%B1%B8%EA%B9%8C&quot; aria-label=&quot;넥스트 키 락은 왜 양 쪽으로 락을 걸까 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;MVCC(멀티 버전 동시성 제어)만으로 팬텀 리드를 완벽히 방지할 수 있는 게 아니기 때문에, InnoDB는 넥스트 키 락(Next-Key Lock)을 추가로 도입했다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MVCC(멀티 버전 동시성 제어)만으로는 &lt;strong&gt;&apos;조회&apos; 팬텀 리드 방지&lt;/strong&gt; 만 가능하다.&lt;br&gt;
내 트랜잭션이 시작됐을 때의 스냅샷을 쿼리할 수 있기 때문에, 트랜잭션 도중 누가 새로 데이터를 넣든 &quot;내가 바라보는 데이터셋&quot;에는 영향이 없어서 &quot;조회 팬텀 리드&quot;는 막을 수 있다.&lt;/p&gt;
&lt;p&gt;하지만, &lt;strong&gt;UPDATE/DELETE/SELECT ... FOR UPDATE 등 &apos;쓰기/락 기반&apos; 쿼리에서는 MVCC만으로는 한계가 있다&lt;/strong&gt;&lt;br&gt;
내가 트랜잭션 중 특정 조건으로 UPDATE를 수행하면 동시에 다른 트랜잭션에서 그 조건에 부합하는 새 데이터를 INSERT할 수 있다.&lt;br&gt;
이 경우 &apos;조작(UPDATE/DELETE)&apos;의 대상이 되는 레코드 집합이 트랜잭션 중에 변동 되기 때문에 &lt;strong&gt;쓰기 팬텀 리드&lt;/strong&gt;가 발생할 수 있다.&lt;br&gt;
&lt;strong&gt;단순 스냅샷(읽기 일관성)만으로는 &quot;조작 대상의 집합&quot;을 동적으로 제약할 수 없다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE employees (
    id int NOT NULL,
    first_name varchar(255) DEFAULT NULL,
    last_name varchar(255) DEFAULT NULL,
    PRIMARY KEY (id),
    KEY idx_first_name (first_name)
) ENGINE=InnoDB;

+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
|  1 | John       | Doe1      |
|  2 | John       | Doe2      |
|  3 | John       | Doe3      |
|  4 | John       | Doe4      |
|  5 | Jane       | Ann1      |
|  6 | Jane       | Ann2      |
|  7 | Jane       | Ann3      |
|  8 | Jack       | Tim1      |
|  9 | Jack       | Tim2      |
| 10 | Jack       | Tim3      |
+----+------------+-----------+

-- &amp;lt;트랜잭션 A&amp;gt;
set transaction_isolation = &amp;#39;READ-COMMITTED&amp;#39;;

select * from employees where id &amp;gt;= 8 for update;
-- 3 rows in set (0.01 sec)

+-------------+------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+------------+-----------+---------------+-------------+-----------+
| employees   | NULL       | TABLE     | IX            | GRANTED     | NULL      |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 8         |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 9         |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 10        |
+-------------+------------+-----------+---------------+-------------+-----------+

-- &amp;lt;트랜잭션 B&amp;gt;
set transaction_isolation = &amp;#39;READ-COMMITTED&amp;#39;;

insert into employees(id, first_name, last_name) values (11, &amp;#39;Test&amp;#39;, &amp;#39;Test1&amp;#39; );
commit;
-- Query OK, 1 row affected (0.03 sec)
-- &amp;lt;/트랜잭션 B&amp;gt;

-- &amp;lt;트랜잭션 A&amp;gt;
select * from employees where id &amp;gt;= 8 for update;
-- 4 rows in set (0.00 sec)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;넥스트 키 락으로 인해 &lt;strong&gt;&quot;현재 읽은 쿼리 결과에 포함되지 않았던 행이 이후 트랜잭션에서 추가&quot;&lt;/strong&gt; 되는 현상을 차단하는 것이다.&lt;br&gt;
자세한 테스트 내용은 &lt;a href=&quot;https://jdalma.github.io/2024y/transaction/#phantom-read-%EB%AC%B8%EC%A0%9C&quot;&gt;팬텀 리드 문제&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 넥스트 키 락은 &lt;strong&gt;MVCC의 한계를 보완&lt;/strong&gt; 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션이 조회,조작한 인덱스의 &quot;사이 공간&quot;에도 락을 걸어 &lt;strong&gt;추가적인 INSERT(팬텀 레코드 삽입)&lt;/strong&gt; 을 막는다.&lt;/li&gt;
&lt;li&gt;실행한 트랜잭션 동안 UPDATE/DELETE/SELECT ... FOR UPDATE 조건에서 조작한 레코드뿐 아니라, 조건 사이 갭에도 갭락을 걸어 &lt;strong&gt;&quot;어떤 트랜잭션도 이 구간에 새 레코드 못 넣게&quot;&lt;/strong&gt; 강제하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;결론&quot; style=&quot;position:relative;&quot;&gt;결론&lt;a href=&quot;#%EA%B2%B0%EB%A1%A0&quot; aria-label=&quot;결론 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MVCC만으로는 &quot;조회 일관성&quot;만 보장되고, 같은 조건 대상으로 UPDATE/DELETE/SELECT ... FOR UPDATE 등에서는 &apos;팬텀 리드&apos; 발생 가능(쓰기 팬텀).&lt;/strong&gt;
&lt;strong&gt;InnoDB 넥스트 키 락은 이러한 상황까지 커버해서 &quot;조작 대상 집합이 트랜잭션 내에서 변하지 않도록&quot; 보장&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;즉, MVCC + Next-Key Lock이 합쳐져서 완전한 팬텀 리드 차단 메커니즘이 되는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자세한 내용은 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html#innodb-next-key-locks&quot;&gt;MySQL docs: innodb-locking&lt;/a&gt;을 참고하자!&lt;/p&gt;
&lt;h1 id=&quot;테스트-필요&quot; style=&quot;position:relative;&quot;&gt;테스트 필요!&lt;a href=&quot;#%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%84%EC%9A%94&quot; aria-label=&quot;테스트 필요 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;범위 조건 이후 컬럼은 스캔 범위를 좁히는데 사용되지 않고, 필터 조건으로만 동작한다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;-- 인덱스를 바꾼다면
(contract_id, valid_to, valid_from)&lt;/p&gt;
&lt;p&gt;-- 쿼리
WHERE contract_id = ?
AND valid_to &gt; ?      -- 범위 조건 (여기서 스캔 멈춤)
AND valid_from &amp;#x3C;= ?   -- 필터링만&lt;/p&gt;
&lt;p&gt;결론: 둘 다 범위 조건이면 어느 쪽을 앞에 두든 하나만 인덱스 스캔에 활용됩니다. 순서를 바꿔도 본질적으로 같습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;그래서 현재 인덱스가 적절한가?&lt;/p&gt;
&lt;p&gt;(contract_id, valid_from, valid_to)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;contract_id = ? → 효율적 (등호 조건)&lt;/li&gt;
&lt;li&gt;valid_from &amp;#x3C;= ? → 범위 스캔&lt;/li&gt;
&lt;li&gt;valid_to &gt; ? → 필터링 (인덱스에 포함되어 있어서 커버링 인덱스로 동작 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;세 번째 컬럼(valid_to)이 인덱스에 포함되어 있으면, 테이블을 다시 읽지 않고 인덱스만으로 필터링할 수 있어서 여전히 이점이 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;요약&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상황&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;/td&gt;
&lt;td&gt;인덱스 스캔에는 사용 안 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인덱스에 포함되면&lt;/td&gt;
&lt;td&gt;필터링은 인덱스 내에서 가능 (커버링)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;현재 인덱스&lt;/td&gt;
&lt;td&gt;적절함, contract_id로 좁히고 나머지는 필터링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;인덱스: (contract_id, valid_from, valid_to)&lt;/p&gt;
&lt;p&gt;정렬 순서:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;contract_id&lt;/th&gt;
&lt;th&gt;valid_from&lt;/th&gt;
&lt;th&gt;valid_to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;  A       | 2025-01-01 | 2025-06-01
  A       | 2025-01-01 | 2025-12-31
  A       | 2025-01-15 | 2025-03-01
  A       | 2025-02-01 | 9999-12-31
  B       | 2025-01-01 | 2025-06-01
  ...&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;첫 번째 컬럼으로 정렬 → 같은 값 내에서 두 번째로 정렬 → 같은 값 내에서 세 번째로 정렬&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;범위 조건의 문제&lt;/p&gt;
&lt;p&gt;WHERE contract_id = &apos;A&apos;
AND valid_from &amp;#x3C;= &apos;2025-01-15&apos;
AND valid_to &gt; &apos;2025-01-15&apos;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;contract_id = &apos;A&apos; → A 블록으로 바로 이동 (등호)&lt;/li&gt;
&lt;li&gt;valid_from &amp;#x3C;= &apos;2025-01-15&apos; → A 내에서 범위 스캔 시작&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 시점에서 valid_to가 연속되어 있지 않음:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;contract_id&lt;/th&gt;
&lt;th&gt;valid_from&lt;/th&gt;
&lt;th&gt;valid_to&lt;/th&gt;
&lt;th&gt;valid_to &gt; &apos;2025-01-15&apos;?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;  A       | 2025-01-01 | 2025-06-01    | ✓
  A       | 2025-01-01 | 2025-12-31    | ✓
  A       | 2025-01-15 | 2025-03-01    | ✓&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;valid_from이 같아도 valid_to는 다 다름 → 범위를 더 좁힐 수 없음&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;그래서 정확히 말하면&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;용어&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Index Range Scan&lt;/td&gt;
&lt;td&gt;범위 조건 이후 컬럼은 스캔 범위를 좁히는 데 사용 안 됨 ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index Filter&lt;/td&gt;
&lt;td&gt;인덱스 내에서 필터링은 가능 (ICP, 커버링 인덱스)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;스캔 범위: contract_id = &apos;A&apos; AND valid_from &amp;#x3C;= &apos;2025-01-15&apos;
필터링:   valid_to &gt; &apos;2025-01-15&apos; (스캔한 결과에서 걸러냄)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[WebClient PrematureCloseException 원인 분석하기]]></title><description><![CDATA[문제 발견 모니터링을 통해 보름 간격으로 한 번씩  예외가 발생하는 것을 확인했다. 외부 서비스로 초당 400~50…]]></description><link>https://jdalma.github.io/2025y/webclient/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/webclient/</guid><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;문제-발견&quot; style=&quot;position:relative;&quot;&gt;문제 발견&lt;a href=&quot;#%EB%AC%B8%EC%A0%9C-%EB%B0%9C%EA%B2%AC&quot; aria-label=&quot;문제 발견 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;모니터링을 통해 보름 간격으로 한 번씩 &lt;code class=&quot;language-text&quot;&gt;PrematureCloseException&lt;/code&gt; 예외가 발생하는 것을 확인했다.&lt;br&gt;
외부 서비스로 초당 400~500회 요청을 보내고, 응답을 기다리지 않는 비동기 방식으로 처리하는 중 부하가 몰릴 때 발생하는 것을 추가로 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;예외의 원인을 확실하게 이해하기 위해 WebClient의 커넥션 풀이 어떻게 관리되는지 정상적인 케이스를 먼저 확인해보자.&lt;/p&gt;
&lt;h1 id=&quot;webclient-구조-이해하기&quot; style=&quot;position:relative;&quot;&gt;WebClient 구조 이해하기&lt;a href=&quot;#webclient-%EA%B5%AC%EC%A1%B0-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; aria-label=&quot;webclient 구조 이해하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 773px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/303fabc729c5e02668cf3a163c98d1dd/612f7/webClient.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.22222222222221%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAADxUlEQVR42k1UaXPiRhDVP0gC2MYCCRAghIRA94UuEBhf69jeeHN98KY2qd3KtUn+f9XLG+xU5cPTzGh6err7vR6p1Wrh5OQUnc4JWq0O0Ua73ea6c/z/HzrtzhFiT6AlQNv/2whfkpjI/SHMlY28yZHXW5TbHZw4hKyNcD4YoasoMMIUYblFXm1QNntUze4IfW7hnOf7A+0YlNTiRxuNEdorbOMYhyzHdV5iF8VY20tkC8KyUbkBmjDGPkrQEBvOqyCCa5iYjyaYT3R8xQwkrdfDr4yqpsPd0sWdH+KRhm/DBE9xindJhic6eH9xhT/ePuKvh0f8SXz6+h4/v7nD8/UNvr+6xlNRYkpfkj0c4Q1vG/CWM2XI9IY4FegPjujIKtrdHhTDxpx2th8QERxm4HDtBDFWDGBl2ognUzpUh9hvczQXGxwuLnHN264OHC+vsds1MCwDYxpqoxFUZQBFfcVgAPlcgSwrODnrwZ3NURkGJH88hslFfzhmlFOo2hQK5wojFjhXRzjlId2Lke33KDcbFJstqh2JIXnVpkHO9YHz2rIgmbxtu3JhTWdwZiYc3UDpx2jiNWISEs0XSIjSC7FP1zi8Ys/abpn2LZ3tSOI+SuGMNEj9bhePLP6PaY5nMvyRsvhE/E7Dz5TF380F/tkd8JnE/VZU+MjDv6wLfMgKPNPxe65/4NkPHEeyDCmeTPAuzuC5DpIixVZojM7cMIMyWaDPMvQGE8yYcrAukZcV1lWNNC+oyRpL12dJVGY4RyJISfUZ7siUbVqwnCVsSsenZGbWErI6JkaQybpGls2Vj+XKg0f7iCm7lJhxFPaApE0QT3VIy+EQNZ0EhoWICEhQTGfJYoWE2ow4hoTPOnoCxgJrirymU0+fH8+F5gINL7LIvPQle3IxMZBTR9WruA+OhxuScMsI7qi1W+KG8yO8Fzyw3vesZcXLMp61pga+EL2s9/r4rirRpCmZJXNMZUPU7I4iDOGYJmwaR4xqzajE/ybNcCmYFYzT8ZYMf5usoQlScuZd5gmyOkclWpC6qsnunuJOixwqdSqzRqafoD7cHPdLklaKkfaCxCBbY+v4WIiU6/kc3/AGU9PROe/jjBBECHF3zmR0eyq6dHjC9hszUrE+o9C7Aj12Ctct7hW28+LQYUv9RH1FrMOCRfbImiCg8AJcUGv7JGdqOQ5CMkzbIwkubVYzCzbtxRl9rOOG5dD7Cp8vPpKGqiKiHkUvNpTPwVrgdrnCQxDgwX/BPedX/Cf2N8yqpO2akhMPgtCyq2nHB/ZfFRgz2ZfltGYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;webClient&quot;
        title=&quot;&quot;
        src=&quot;/static/303fabc729c5e02668cf3a163c98d1dd/612f7/webClient.png&quot;
        srcset=&quot;/static/303fabc729c5e02668cf3a163c98d1dd/3684f/webClient.png 225w,
/static/303fabc729c5e02668cf3a163c98d1dd/fc2a6/webClient.png 450w,
/static/303fabc729c5e02668cf3a163c98d1dd/612f7/webClient.png 773w&quot;
        sizes=&quot;(max-width: 773px) 100vw, 773px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Bean
fun webClient(): WebClient {
    // ConnectionProvider 설정
    val connectionProvider = ConnectionProvider.builder(&amp;quot;custom&amp;quot;)
        .maxConnections(100)                   // 최대 연결 수
        .pendingAcquireMaxCount(50)            // 대기 큐 크기
        .maxIdleTime(Duration.ofSeconds(20))   // 유휴 연결 유지 시간
        .maxLifeTime(Duration.ofMinutes(5))    // 연결 최대 생존 시간
        .evictInBackground(Duration.ofSeconds(30)) // 백그라운드 정리
        .build()

    // HttpClient 설정
    val httpClient = HttpClient.create(connectionProvider)      // ConnectionProvider 주입
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)     // Connection Timeout
        .responseTimeout(Duration.ofSeconds(10))                // Response Timeout
        .doOnConnected { conn -&amp;gt;
            conn.addHandlerLast(ReadTimeoutHandler(10, TimeUnit.SECONDS))   // Read &amp;amp; Write Timeout
                .addHandlerLast(WriteTimeoutHandler(10, TimeUnit.SECONDS))
        }

    return WebClient.builder()
        .clientConnector(ReactorClientHttpConnector(httpClient))    // HttpClient 주입
        .baseUrl(&amp;quot;https://api.example.com&amp;quot;)
        .build()
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ClientHttpConnector&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;WebClient와 실제 HTTP 클라이언트 라이브러리 사이의 어댑터 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HttpClient&lt;/strong&gt; : 어떻게 HTTP 통신을 할지 결정
&lt;ul&gt;
&lt;li&gt;HTTP 프로토콜 레벨의 설정 담당&lt;/li&gt;
&lt;li&gt;ConnectionProvider를 주입받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ConnectionProvider&lt;/strong&gt; : Connection을 어떻게 관리할지 결정
&lt;ul&gt;
&lt;li&gt;TCP 연결의 물리적 관리를 위함&lt;/li&gt;
&lt;li&gt;TCP 연결 생성/해제 비용이 크므로 재사용 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;webclient-connection-상태-변화&quot; style=&quot;position:relative;&quot;&gt;WebClient Connection 상태 변화&lt;a href=&quot;#webclient-connection-%EC%83%81%ED%83%9C-%EB%B3%80%ED%99%94&quot; aria-label=&quot;webclient connection 상태 변화 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;8080포트의 서버에서 9090포트의 서버로 요청을 보내고 1초 후에 응답하는 API를 테스트해보았다.&lt;br&gt;
(keep-alive timeout = 3000ms)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5ea0ecd5cd827df7dd23f51fa247aae4/0ad08/success_network_log.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAABP0lEQVR42iWP3UvCcBiF+/8v+4IuIkuzkbp0mzO/m/v4mW6OzMwsJCuhAiXSUst6mvbC4XmuDuddIzi/2UbRi5QMgeW4FMo2tvCoWDV0SUY/kNDDx2Qiy8RWfhqwcCRT1/Is7vssngb8zL9YWxbWm5fI2RxFR2C73oqi4SM8n0Q0hrR7QCx0SCIcRY5IxAMuXTuWsTMFZrc95r0+s+my8Bc6vS4VX9C4a9F+7OIH7AzuuB7ccuZaFEQ5oIm4FFRbVawLC+fCxu24dB9ueBt9MB1/BtN+g8LXV/xsFiUaJWcYGOdVchUDs17DrNVQ0ipyUkZRUyhaEk1XV1TTKbL5DFdNn+/3MYsgTCb/L3uNNknFoFT2MM0mxZKH41zh2C3Se3GUzX3U7QjyeojUVpiTjdDK9R2JtmbAyzMMhzAa8Qe64k2+vcc34QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;success network log&quot;
        title=&quot;&quot;
        src=&quot;/static/5ea0ecd5cd827df7dd23f51fa247aae4/1cfc2/success_network_log.png&quot;
        srcset=&quot;/static/5ea0ecd5cd827df7dd23f51fa247aae4/3684f/success_network_log.png 225w,
/static/5ea0ecd5cd827df7dd23f51fa247aae4/fc2a6/success_network_log.png 450w,
/static/5ea0ecd5cd827df7dd23f51fa247aae4/1cfc2/success_network_log.png 900w,
/static/5ea0ecd5cd827df7dd23f51fa247aae4/21482/success_network_log.png 1350w,
/static/5ea0ecd5cd827df7dd23f51fa247aae4/d61c2/success_network_log.png 1800w,
/static/5ea0ecd5cd827df7dd23f51fa247aae4/0ad08/success_network_log.png 2978w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;WebClient를 생성할 때 metrics를 활성화하여 로그를 확인해볼 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;val httpClient = HttpClient.create(connectionProvider)
    .metrics(true) { uriTagValue -&amp;gt; uriTagValue }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+ 1. 커넥션 풀 생성
Creating a new [my-provider] client pool [PoolFactory{evictionInterval=PT0S, leasingStrategy=fifo, maxConnections=1, maxIdleTime=-1, maxLifeTime=-1, metricsEnabled=false, pendingAcquireMaxCount=2, pendingAcquireTimeout=45000}] for [localhost/&amp;lt;unresolved&amp;gt;:9090]
[1965a96f] Created a new pooled channel, now: 0 active connections, 0 inactive connections and 0 pending acquire requests.

+ 2. TCP 연결을 완료하고 커넥션 풀에 등록
[1965a96f] REGISTERED
[1965a96f] CONNECT: localhost/127.0.0.1:9090
[1965a96f] Registering pool release on close event for channel
[1965a96f] Channel connected, now: 1 active connections, 0 inactive connections and 0 pending acquire requests.
[1965a96f] ACTIVE
[1965a96f] onStateChange(PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 - R:localhost/127.0.0.1:9090]}, [connected])

+ 3. 요청 준비
[1965a96f-1] onStateChange(GET{uri=null, connection=PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 - R:localhost/127.0.0.1:9090]}}, [configured])
[1965a96f-1] Handler is being applied: {uri=http://localhost:9090/internal/delay/1, method=GET}
[1965a96f-1] onStateChange(GET{uri=/internal/delay/1, connection=PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 - R:localhost/127.0.0.1:9090]}}, [request_prepared])

+ 4. 요청 전송
[1965a96f-1] WRITE: 102B GET /internal/delay/1 HTTP/1.1
user-agent: ReactorNetty/1.1.22
host: localhost:9090
accept: */*
[1965a96f-1] FLUSH
[1965a96f-1] WRITE: 0B
[1965a96f-1] FLUSH
[1965a96f-1] onStateChange(GET{uri=/internal/delay/1, connection=PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 - R:localhost/127.0.0.1:9090]}}, [request_sent])

+ 5. 응답 헤더 수신
[1965a96f-1] READ: 142B HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 28
Date: Tue, 23 Sep 2025 06:09:17 GMT

+ 6. 응답 바디 수신
delay api success. seconds=1
[1965a96f-1] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)
HTTP/1.1 200 
Content-Type: &amp;lt;filtered&amp;gt;
Content-Length: &amp;lt;filtered&amp;gt;
Date: &amp;lt;filtered&amp;gt;
[1965a96f-1] onStateChange(GET{uri=/internal/delay/1, connection=PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 - R:localhost/127.0.0.1:9090]}}, [response_received])
[1965a96f-1] [terminated=false, cancelled=false, pending=0, error=null]: subscribing inbound receiver

+ 6-1. 응답 바디 수신 완료
[1965a96f-1] Received last HTTP packet
[1965a96f] onStateChange(GET{uri=/internal/delay/1, connection=PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 - R:localhost/127.0.0.1:9090]}}, [response_completed])

+ 7. 연결 해제 및 커넥션을 풀에 반환하여 idle 상태로 전환
[1965a96f] onStateChange(GET{uri=/internal/delay/1, connection=PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 - R:localhost/127.0.0.1:9090]}}, [disconnecting])
[1965a96f] Releasing channel
[1965a96f] Channel cleaned, now: 0 active connections, 1 inactive connections and 0 pending acquire requests.
[1965a96f] READ COMPLETE

-- 3초 이후 --

+ 8. 커넥션 최종 연결 종료
[1965a96f] READ COMPLETE
[Connection Closed] : GET{uri=/internal/delay/1, connection=PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 ! R:localhost/127.0.0.1:9090]}}
[1965a96f] INACTIVE
[1965a96f] onStateChange(PooledConnection{channel=[id: 0x1965a96f, L:/127.0.0.1:59768 ! R:localhost/127.0.0.1:9090]}, [disconnecting])
[1965a96f] UNREGISTERED&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;┌─────────────┐    ┌──────────────┐    ┌─────────────┐    ┌──────────────┐
│ 1. 풀 생성    │ →  │ 2. TCP 연결   │ →  │ 3. HTTP 요청 │ →  │ 4. HTTP 응답  │ →
└─────────────┘    └──────────────┘    └─────────────┘    └──────────────┘
  Pool Init         REGISTERED            configured          response
                    CONNECT               request_prepared    received
                    ACTIVE                request_sent        completed
                    (1 active)            (WRITE/FLUSH)       (200 OK)

┌──────────────┐    ┌─────────────┐    ┌──────────────┐
│ 5. 연결 해제   │ →  │ 6. 유휴 대기   │ →  │ 7. 최종 종료   │
└──────────────┘    └─────────────┘    └──────────────┘
  disconnecting     Keep-Alive          INACTIVE
  Release           (재사용 대기)        UNREGISTERED
  (0 active,        (~3초)              (Pool에서 제거)
   1 inactive)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;로그와 다이어그램을 통해 Connection의 상태가 변화되는 주요 단계를 확인할 수 있다.&lt;br&gt;
이제 PrematureCloseException이 발생하는 원인에 대해 더 자세하게 확인해보자.&lt;/p&gt;
&lt;h1 id=&quot;prematurecloseexception이-발생하는-케이스&quot; style=&quot;position:relative;&quot;&gt;PrematureCloseException이 발생하는 케이스&lt;a href=&quot;#prematurecloseexception%EC%9D%B4-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%BC%80%EC%9D%B4%EC%8A%A4&quot; aria-label=&quot;prematurecloseexception이 발생하는 케이스 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;즉, &lt;code class=&quot;language-text&quot;&gt;PrematureCloseException&lt;/code&gt;은 HTTP 통신 중 예상하지 못한 시점에 연결이 종료될 때 발생하는 예외다.&lt;br&gt;
자세히는 네트워크의 입출력과 생명주기, 이벤트, 상태 관리 등을 관리하는 ChannelOperations를 상속한 &lt;code class=&quot;language-text&quot;&gt;HttpClientOperations&lt;/code&gt;에서 채널을 정리할 때 실행되는 &lt;code class=&quot;language-text&quot;&gt;onInboundClose()&lt;/code&gt;함수에서 EOF 이벤트를 감지하고 현재의 상태를 확인하여 예외를 생성한다.&lt;/p&gt;
&lt;h4&gt;연결 종료 감지 과정&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;(테스트 환경이 mac이라서) KQueueEventLoop에서 FIN 패킷을 EV_EOF 이벤트로 감지&lt;/li&gt;
&lt;li&gt;EV_EOF 이벤트로 인해 EOF 처리, Half-closure 설정에 따라 입력만 종료 또는 전체 종료 결정&lt;/li&gt;
&lt;li&gt;Channel inputShutdown 플래그 설정 + 시스템 레벨 소켓 수신 부분 종료&lt;/li&gt;
&lt;li&gt;실제 소켓 fd 닫기 + closeInitiated 플래그 설정&lt;/li&gt;
&lt;li&gt;channelInactive 이벤트가 파이프라인 통해 전파 EventLoop에서 채널 등록 해제&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ChannelHandler.channelInactive() 프로토콜별 정리 작업에서 onInboundClose() 호출&lt;/strong&gt; → HTTP 프로토콜 레벨에서 어떤 단계에서 문제가 발생했는지 구분해서 적절한 예외를 발생시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e0e7f9a29c48c31fdd0971949c15ba2f/fe7a3/onInboundClose.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABy0lEQVR42m1S7W7bMAz0W2xtYsu2vizHi/xtt07cBh2QAkP/7P3f5XZyAgTY9uNAiiKPPEqRVhq+cihdhaLqYH+0sEUJbUooUyBXBlmmN6SZQn73hciR3CFSiafnBE07IEp4yKWBP3q4soINxMUBh6qGox9IlS4gld3ItSxgpCO5/D+h4EWYxrBIa0ewwDooNpG5vsWMg1UlrCw3K9LHdP8S8qC0RdsMaOsB3jeUfkTGiVKSplIjodQkfWBPwr14ICbHt10CHwhjTpgxqWVhbyx6yg3wJKtJ6nMJtxd/Ib0hvtkyzqC/77DUHaKKsj6THGcWL/aAM/f2QsKRu5ood2B84O562n6zBoO+x9i0pz9yEM+BfvEdIp8bfJHwQ1qcmHDmxTuJ30l6IfnK5EUbLMbgdPdP1uCVeNMaPyn/lTVTkuGr7REZyp2ExMjH6Jjc8wEGW26de8aC3/GlW5J1/GINFfXK0XeYqaLe9quxexZouhFRwYU2nGzl35sp64UyA2ZKmtg5xGYST8EybyDhGEB/pLqaOQGHWODS8VEuHPUqMlx0ias94rM44kpcSLKycOUnXrmONTSlXXJFSCxSbvbM88rmc5LidzfiD2XNJlv0V0AGAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;onInboundClose&quot;
        title=&quot;&quot;
        src=&quot;/static/e0e7f9a29c48c31fdd0971949c15ba2f/1cfc2/onInboundClose.png&quot;
        srcset=&quot;/static/e0e7f9a29c48c31fdd0971949c15ba2f/3684f/onInboundClose.png 225w,
/static/e0e7f9a29c48c31fdd0971949c15ba2f/fc2a6/onInboundClose.png 450w,
/static/e0e7f9a29c48c31fdd0971949c15ba2f/1cfc2/onInboundClose.png 900w,
/static/e0e7f9a29c48c31fdd0971949c15ba2f/21482/onInboundClose.png 1350w,
/static/e0e7f9a29c48c31fdd0971949c15ba2f/d61c2/onInboundClose.png 1800w,
/static/e0e7f9a29c48c31fdd0971949c15ba2f/fe7a3/onInboundClose.png 8106w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;요청 헤더 전송&lt;/li&gt;
&lt;li&gt;요청 바디 전송   ← 여기서 닫히면 &lt;code class=&quot;language-text&quot;&gt;&quot;while sending request body&quot;&lt;/code&gt; &lt;strong&gt;아직 요청도 완전히 못 보낸 상태&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;요청 완료&lt;/li&gt;
&lt;li&gt;서버 처리 중     ← 여기서 닫히면 &lt;code class=&quot;language-text&quot;&gt;&quot;BEFORE response&quot;&lt;/code&gt; &lt;strong&gt;요청은 보냈지만 응답을 아직 못 받은 상태&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;응답 헤더 수신&lt;/li&gt;
&lt;li&gt;응답 바디 수신   ← 여기서 닫히면 &lt;code class=&quot;language-text&quot;&gt;&quot;DURING response&quot;&lt;/code&gt; &lt;strong&gt;응답을 받기 시작했지만 응답이 완전히 안 온 상태&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;응답 완료&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;1-while-sending-request-body&quot; style=&quot;position:relative;&quot;&gt;1. while sending request body&lt;a href=&quot;#1-while-sending-request-body&quot; aria-label=&quot;1 while sending request body permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;송신 서버의 요청을 수신 서버가 수신 중에 (바디를 완전히 받기 전에) 연결을 종료하는 경우에 발생한다.&lt;br&gt;
&lt;strong&gt;테스트 시나리오&lt;/strong&gt;: 스트리밍 바디를 전송하는 중에 서버가 두 번째 청크를 받으면 즉시 연결을 종료&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// WebClient Body
val chunkData = &amp;quot;X&amp;quot;.repeat(4096) // 4KB per chunk
val streamingBody = Flux.interval(Duration.ofMillis(500))
    .take(20)  // 20개 청크 = 80KB
    .map { index -&amp;gt;
        logger.info(&amp;quot;Preparing to send chunk ${index + 1}/20&amp;quot;)
        chunkData
    }
    .doOnNext {
        logger.info(&amp;quot;Sending chunk of ${it.length} bytes...&amp;quot;)
    }
    .doOnComplete {
        logger.info(&amp;quot;All chunks sent&amp;quot;)
    }

// Netty 서버
fun main() {
    val parentGroup = NioEventLoopGroup()
    val workerGroup = NioEventLoopGroup()

    try {
        ServerBootstrap()
            .group(parentGroup, workerGroup)
            .channel(NioServerSocketChannel::class.java)
            .handler(LoggingHandler(LogLevel.DEBUG))    // 서버 자체 이벤트 로깅 (bind, accept 등)
            .childHandler(object : ChannelInitializer&amp;lt;SocketChannel&amp;gt;() {
                override fun initChannel(ch: SocketChannel) {           // ch는 연결된 클라이언트 채널
                    ch.pipeline().addLast(LoggingHandler(LogLevel.DEBUG))   // 클라이언트 데이터 송수신 체크
                    ch.pipeline().addLast(RudeServerHandler())
                }
            })
            .bind(9090).sync()
            .channel()
            .closeFuture().sync()
    } finally {
        parentGroup.shutdownGracefully()
        workerGroup.shutdownGracefully()
    }
}

private class RudeServerHandler : ChannelInboundHandlerAdapter() {
    private val logger = org.slf4j.LoggerFactory.getLogger(&amp;quot;RudeServer&amp;quot;)
    private var readCount = 0
    private var totalBytesReceived = 0

    override fun channelActive(ctx: ChannelHandlerContext) {
        super.channelActive(ctx)
        logger.info(&amp;quot;Client connected: ${ctx.channel().remoteAddress()}&amp;quot;)
    }

    override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
        val buffer = msg as io.netty.buffer.ByteBuf
        val readableBytes = buffer.readableBytes()
        readCount++
        totalBytesReceived += readableBytes
        
        logger.info(&amp;quot;channelRead #$readCount: Received $readableBytes bytes (total: $totalBytesReceived)&amp;quot;)
        
        // 첫 번째 read에서 헤더가 포함되어 있을 수 있음
        // 두 번째 read는 바디 데이터
        // 두 번째 청크를 받으면 연결 끊기
        if (readCount &amp;gt;= 2) {
            logger.info(&amp;quot;Received 2nd chunk, closing connection WITHOUT sending response!&amp;quot;)
            buffer.release()
            ctx.close().await()
        } else {
            logger.info(&amp;quot;Waiting for more data...&amp;quot;)
            buffer.release()
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;  
&lt;details&gt;
&lt;summary&gt;📋 WebClient와 Netty 로그 자세히보기 (클릭하여 펼치기)&lt;/summary&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+ &amp;lt;Spring WebClient 로그&amp;gt;

[reactor-http-kqueue-7] DEBUG r.n.r.PooledConnectionProvider - [c437bfe8] Created a new pooled channel, now: 0 active connections, 0 inactive connections and 0 pending acquire requests.
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8] REGISTERED
[reactor-http-kqueue-7] DEBUG i.n.r.dns.DnsNameResolverBuilder - resolveCache and TTLs are mutually exclusive. TTLs are ignored.
[reactor-http-kqueue-7] DEBUG i.n.r.dns.DnsNameResolverBuilder - cnameCache and TTLs are mutually exclusive. TTLs are ignored.
[reactor-http-kqueue-7] DEBUG i.n.r.dns.DnsNameResolverBuilder - authoritativeDnsServerCache and TTLs are mutually exclusive. TTLs are ignored.
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8] CONNECT: localhost/127.0.0.1:9090
[reactor-http-kqueue-7] DEBUG r.n.r.DefaultPooledConnectionProvider - [c437bfe8, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] Registering pool release on close event for channel
[reactor-http-kqueue-7] DEBUG r.n.r.PooledConnectionProvider - [c437bfe8, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] Channel connected, now: 1 active connections, 0 inactive connections and 0 pending acquire requests.
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] ACTIVE
[reactor-http-kqueue-7] DEBUG r.n.r.DefaultPooledConnectionProvider - [c437bfe8, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] onStateChange(PooledConnection{channel=[id: 0xc437bfe8, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090]}, [connected])
[reactor-http-kqueue-7] DEBUG r.n.r.DefaultPooledConnectionProvider - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] onStateChange(GET{uri=null, connection=PooledConnection{channel=[id: 0xc437bfe8, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090]}}, [configured])
[reactor-http-kqueue-7] DEBUG r.n.http.client.HttpClientConnect - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] Handler is being applied: {uri=http://localhost:9090/any-path, method=POST}
[reactor-http-kqueue-7] DEBUG r.n.r.DefaultPooledConnectionProvider - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] onStateChange(POST{uri=/any-path, connection=PooledConnection{channel=[id: 0xc437bfe8, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090]}}, [request_prepared])
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] WRITE: 155B POST /any-path HTTP/1.1
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] FLUSH
[parallel-6] INFO  e.s.PrematureCloseExceptionService - Preparing to send chunk 1/20
[parallel-6] INFO  e.s.PrematureCloseExceptionService - Sending chunk of 4096 bytes...
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] WRITE: 6B 1000
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] WRITE: 4096B XXXX... // send data ...
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] WRITE: 2B 
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] FLUSH
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 - R:localhost/127.0.0.1:9090] READ COMPLETE
[reactor-http-kqueue-7] DEBUG r.n.r.PooledConnectionProvider - [c437bfe8-1, L:/127.0.0.1:52226 ! R:localhost/127.0.0.1:9090] Channel closed, now: 0 active connections, 0 inactive connections and 0 pending acquire requests.
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 ! R:localhost/127.0.0.1:9090] INACTIVE
[reactor-http-kqueue-7] DEBUG r.n.r.DefaultPooledConnectionProvider - [c437bfe8-1, L:/127.0.0.1:52226 ! R:localhost/127.0.0.1:9090] onStateChange(POST{uri=/any-path, connection=PooledConnection{channel=[id: 0xc437bfe8, L:/127.0.0.1:52226 ! R:localhost/127.0.0.1:9090]}}, [response_incomplete])
[reactor-http-kqueue-7] WARN  r.n.http.client.HttpClientConnect - [c437bfe8-1, L:/127.0.0.1:52226 ! R:localhost/127.0.0.1:9090] The connection observed an error
reactor.netty.http.client.PrematureCloseException: Connection has been closed BEFORE response, while sending request body
[reactor-http-kqueue-7] INFO  my-webclient - [c437bfe8-1, L:/127.0.0.1:52226 ! R:localhost/127.0.0.1:9090] UNREGISTERED
[http-nio-8080-exec-2] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.reactive.function.client.WebClientRequestException: Connection has been closed BEFORE response, while sending request body] with root cause
reactor.netty.http.client.PrematureCloseException: Connection has been closed BEFORE response, while sending request body&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+ &amp;lt;Netty 서버 로그&amp;gt;

-- 리스너 포트 등록 및 활성화 완료
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xc281fdae] REGISTERED
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xc281fdae] BIND: 0.0.0.0/0.0.0.0:9090
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xc281fdae, L:/[0:0:0:0:0:0:0:0]:9090] ACTIVE

-- 요청 수신
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x5421fff9, L:/[0:0:0:0:0:0:0:0]:9090] READ: [id: 0x57861e0a, L:/127.0.0.1:9090 - R:/127.0.0.1:52226]
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x5421fff9, L:/[0:0:0:0:0:0:0:0]:9090] READ COMPLETE
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 - R:/127.0.0.1:52226] REGISTERED
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 - R:/127.0.0.1:52226] ACTIVE
[nioEventLoopGroup-3-3] INFO  RudeServer - Client connected: /127.0.0.1:52226
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 - R:/127.0.0.1:52226] READ: 155B
[nioEventLoopGroup-3-3] INFO  RudeServer - channelRead #1: Received 155 bytes (total: 155)
[nioEventLoopGroup-3-3] INFO  RudeServer - Waiting for more data...
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 - R:/127.0.0.1:52226] READ COMPLETE
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 - R:/127.0.0.1:52226] READ: 2048B
[nioEventLoopGroup-3-3] INFO  RudeServer - channelRead #2: Received 2048 bytes (total: 2203)
[nioEventLoopGroup-3-3] INFO  RudeServer - Received 2nd chunk, closing connection WITHOUT sending response!
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 - R:/127.0.0.1:52226] CLOSE
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 ! R:/127.0.0.1:52226] READ COMPLETE
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 ! R:/127.0.0.1:52226] USER_EVENT: io.netty.channel.socket.ChannelInputShutdownReadComplete@6a0d7782
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 ! R:/127.0.0.1:52226] INACTIVE
[nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 ! R:/127.0.0.1:52226] UNREGISTERED&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/details&gt;
&lt;br/&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ChannelRegistered&lt;/code&gt; : Channel 이 Event Loop에 등록됨&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ChannelActive&lt;/code&gt; : Channel이 활성화됨, 이제 데이터를 주고받을 수 있음&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Channellnactive&lt;/code&gt; : Channel이 원격 피어로 연결되지 않음&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ChannelUnregistered&lt;/code&gt;: Channel이 생성됐지만 Event Loop에 등록되지 않음&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c89228419bd6fa200e076aae3d9f741b/d7ceb/whileSending.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.555555555555554%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABaklEQVR42i2QS2+bQBSF+f8/oFJXVSNVtgyOU2MMDMObCWADfudhx0m66KqNlEVVZfd1jLq4Oufec+48jrFYHQjzBWWzo2737O+fyatVj+v9iW77iMxr3KggSCtEUiKzSs8u/JYgK/ETRbO5xw9zDJGmjB1HL0R4caIPq7BF0GOslL6sYDxzGH6fYtoOluYTd86N72E6Nteam7MpodI+e4qxuXsjKZ9pd79YHX5zevmg6n7y9PrB4/kvu4f3XpfFkbR6IVJnRPqg8Yno9ky2+KG1E4fTHwqtG8XQRXy9phjMUQOXxgpJv9m0VkRjSsqhj7ya9J7gP86/mAjN3c8jPF2zTwOWI0lypV+4VhWZJ+nyknVRc2w2LJOCY7vlru44VA0qiLVHx+CHZL4kDySLNNN9QKH7S/U+EWEEOnBrHuJfAs9q1HKLEypUs9VfXBGXLRMv1nlJxm7U42gmuBExlnuZhT1GZYeIFf8A6EuHde0hP+QAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;whileSending&quot;
        title=&quot;&quot;
        src=&quot;/static/c89228419bd6fa200e076aae3d9f741b/1cfc2/whileSending.png&quot;
        srcset=&quot;/static/c89228419bd6fa200e076aae3d9f741b/3684f/whileSending.png 225w,
/static/c89228419bd6fa200e076aae3d9f741b/fc2a6/whileSending.png 450w,
/static/c89228419bd6fa200e076aae3d9f741b/1cfc2/whileSending.png 900w,
/static/c89228419bd6fa200e076aae3d9f741b/21482/whileSending.png 1350w,
/static/c89228419bd6fa200e076aae3d9f741b/d7ceb/whileSending.png 1446w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+ Netty 서버 로그
18:09:15.348 [nioEventLoopGroup-3-3] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x57861e0a, L:/127.0.0.1:9090 ! R:/127.0.0.1:52226] USER_EVENT: io.netty.channel.socket.ChannelInputShutdownReadComplete@6a0d7782

+ RST 패킷
UTC Arrival Time: Oct  1, 2025 09:09:15.348334000 UTC
Transmission Control Protocol, Src Port: 9090, Dst Port: 52226, Seq: 2, Ack: 4260, Len: 0
Flags: 0x014 (RST, ACK)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;2-connection-prematurely-closed-before-response&quot; style=&quot;position:relative;&quot;&gt;2. Connection prematurely closed BEFORE response&lt;a href=&quot;#2-connection-prematurely-closed-before-response&quot; aria-label=&quot;2 connection prematurely closed before response permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;while sending request body&lt;/code&gt;와 비슷한 케이스이지만 요청 body가 없는 경우 이 메세지의 예외가 발생한다.&lt;br&gt;
&lt;strong&gt;테스트 시나리오&lt;/strong&gt;: 요청 헤더만 받고 즉시 연결을 종료&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun main() {
    val parentGroup = NioEventLoopGroup()
    val workerGroup = NioEventLoopGroup()

    try {
        ServerBootstrap()
            .group(parentGroup, workerGroup)
            .channel(NioServerSocketChannel::class.java)
            .handler(LoggingHandler(LogLevel.DEBUG))    // 서버 자체 이벤트 로깅 (bind, accept 등)
            .childHandler(object : ChannelInitializer&amp;lt;SocketChannel&amp;gt;() {
                override fun initChannel(ch: SocketChannel) {           // ch는 연결된 클라이언트 채널
                    ch.pipeline().addLast(LoggingHandler(LogLevel.DEBUG))   // 클라이언트 데이터 송수신 체크
                    ch.pipeline().addLast(RudeServerHandler())
                }
            })
            .bind(9090).sync()
            .channel()
            .closeFuture().sync()
    } finally {
        parentGroup.shutdownGracefully()
        workerGroup.shutdownGracefully()
    }
}

private class RudeServerHandler : ChannelInboundHandlerAdapter() {
    private val logger = org.slf4j.LoggerFactory.getLogger(&amp;quot;RudeServer&amp;quot;)

    override fun channelActive(ctx: ChannelHandlerContext) {
        super.channelActive(ctx)
        logger.info(&amp;quot;Client connected: ${ctx.channel().remoteAddress()}&amp;quot;)
    }

    override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
        logger.info(&amp;quot;Received message: $msg&amp;quot;)
        logger.info(&amp;quot;Closing connection !!!&amp;quot;)
        ctx.close().await()
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;details&gt;
&lt;summary&gt;📋 WebClient와 Netty 로그 자세히보기 (클릭하여 펼치기)&lt;/summary&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+ &amp;lt;Spring WebClient 로그&amp;gt;

[reactor-http-kqueue-4] DEBUG r.n.r.PooledConnectionProvider - [ace71497] Created a new pooled channel, now: 0 active connections, 0 inactive connections and 0 pending acquire requests.
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497] REGISTERED
[reactor-http-kqueue-4] DEBUG i.n.r.dns.DnsNameResolverBuilder - resolveCache and TTLs are mutually exclusive. TTLs are ignored.
[reactor-http-kqueue-4] DEBUG i.n.r.dns.DnsNameResolverBuilder - cnameCache and TTLs are mutually exclusive. TTLs are ignored.
[reactor-http-kqueue-4] DEBUG i.n.r.dns.DnsNameResolverBuilder - authoritativeDnsServerCache and TTLs are mutually exclusive. TTLs are ignored.
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497] CONNECT: localhost/127.0.0.1:9090
[reactor-http-kqueue-4] DEBUG r.n.r.DefaultPooledConnectionProvider - [ace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] Registering pool release on close event for channel
[reactor-http-kqueue-4] DEBUG r.n.r.PooledConnectionProvider - [ace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] Channel connected, now: 1 active connections, 0 inactive connections and 0 pending acquire requests.
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] ACTIVE
[reactor-http-kqueue-4] DEBUG r.n.r.DefaultPooledConnectionProvider - [ace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] onStateChange(PooledConnection{channel=[id: 0xace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090]}, [connected])
[reactor-http-kqueue-4] DEBUG r.n.r.DefaultPooledConnectionProvider - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] onStateChange(GET{uri=null, connection=PooledConnection{channel=[id: 0xace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090]}}, [configured])
[reactor-http-kqueue-4] DEBUG r.n.http.client.HttpClientConnect - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] Handler is being applied: {uri=http://localhost:9090/any-path, method=POST}
[reactor-http-kqueue-4] DEBUG r.n.r.DefaultPooledConnectionProvider - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] onStateChange(POST{uri=/any-path, connection=PooledConnection{channel=[id: 0xace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090]}}, [request_prepared])
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] WRITE: 123B POST /any-path HTTP/1.1
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] FLUSH
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] WRITE: 5B 0
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] FLUSH
[reactor-http-kqueue-4] DEBUG r.n.r.DefaultPooledConnectionProvider - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] onStateChange(POST{uri=/any-path, connection=PooledConnection{channel=[id: 0xace71497, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090]}}, [request_sent])
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497-1, L:/127.0.0.1:50390 - R:localhost/127.0.0.1:9090] READ COMPLETE
[reactor-http-kqueue-4] DEBUG r.n.r.PooledConnectionProvider - [ace71497-1, L:/127.0.0.1:50390 ! R:localhost/127.0.0.1:9090] Channel closed, now: 0 active connections, 0 inactive connections and 0 pending acquire requests.
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497-1, L:/127.0.0.1:50390 ! R:localhost/127.0.0.1:9090] INACTIVE
[reactor-http-kqueue-4] DEBUG r.n.r.DefaultPooledConnectionProvider - [ace71497-1, L:/127.0.0.1:50390 ! R:localhost/127.0.0.1:9090] onStateChange(POST{uri=/any-path, connection=PooledConnection{channel=[id: 0xace71497, L:/127.0.0.1:50390 ! R:localhost/127.0.0.1:9090]}}, [response_incomplete])
[reactor-http-kqueue-4] WARN  r.n.http.client.HttpClientConnect - [ace71497-1, L:/127.0.0.1:50390 ! R:localhost/127.0.0.1:9090] The connection observed an error
reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
[reactor-http-kqueue-4] INFO  my-webclient - [ace71497-1, L:/127.0.0.1:50390 ! R:localhost/127.0.0.1:9090] UNREGISTERED
[http-nio-8080-exec-6] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.reactive.function.client.WebClientRequestException: Connection prematurely closed BEFORE response] with root cause
reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+ &amp;lt;Netty 서버 로그&amp;gt;

-- 리스너 포트 등록 및 활성화 완료
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x1828781c] REGISTERED
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x1828781c] BIND: 0.0.0.0/0.0.0.0:9090
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x1828781c, L:/[0:0:0:0:0:0:0:0]:9090] ACTIVE

-- 요청 수신
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x1828781c, L:/[0:0:0:0:0:0:0:0]:9090] READ: [id: 0xb065a578, L:/127.0.0.1:9090 - R:/127.0.0.1:50390]
[nioEventLoopGroup-2-1] DEBUG i.n.handler.logging.LoggingHandler - [id: 0x1828781c, L:/[0:0:0:0:0:0:0:0]:9090] READ COMPLETE
[nioEventLoopGroup-3-2] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xb065a578, L:/127.0.0.1:9090 - R:/127.0.0.1:50390] REGISTERED
[nioEventLoopGroup-3-2] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xb065a578, L:/127.0.0.1:9090 - R:/127.0.0.1:50390] ACTIVE
[nioEventLoopGroup-3-2] INFO  RudeServer - Client connected: /127.0.0.1:50390
[nioEventLoopGroup-3-2] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xb065a578, L:/127.0.0.1:9090 - R:/127.0.0.1:50390] READ: 128B
[nioEventLoopGroup-3-2] INFO  RudeServer - Received message: PooledUnsafeDirectByteBuf(ridx: 0, widx: 128, cap: 2048)
[nioEventLoopGroup-3-2] INFO  RudeServer - Closing connection !!!
[nioEventLoopGroup-3-2] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xb065a578, L:/127.0.0.1:9090 - R:/127.0.0.1:50390] CLOSE
[nioEventLoopGroup-3-2] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xb065a578, L:/127.0.0.1:9090 ! R:/127.0.0.1:50390] READ COMPLETE
[nioEventLoopGroup-3-2] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xb065a578, L:/127.0.0.1:9090 ! R:/127.0.0.1:50390] INACTIVE
[nioEventLoopGroup-3-2] DEBUG i.n.handler.logging.LoggingHandler - [id: 0xb065a578, L:/127.0.0.1:9090 ! R:/127.0.0.1:50390] UNREGISTERED&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/details&gt;
&lt;br/&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f548df5c4bf745e84c88fd244cdd323f/b297c/beforeResponse.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 32.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABn0lEQVR42iWR6Y7aQBCE/f5PkEirTUgWAsthsMEYbPAxNr4Bc0ssLEk2kXK8w5fB+VGqrqlqdbdG+dIboo4shhMHf5HT0Uy8IGUyC/DDrPKe2gMaXb1CUx1WeO4bNHo6Lcn1zoC27BtbHopmWUyFjxtHFNsNpueSb9aEeSZ1iRX4DGRGs220mY3hzZkIFyv0Gfv/a9N3cNOIVPYpmrFg7pUE0ZHd7ie2s2KzfSPLL+yldvyt3D7BmGaM7VxuXkgdV7VuxoymaaVn3hpX7FDGQuDlOVG55vh6ZR4nHK4Xiv2B4/WKXxQYvo8pAuZJLLMps2SBV2SSI5w0xopCYnnN6faKouoCU06fOSVp+oJhJiTxGSH2ZOml8lRNoI0W9PWgQrvnMhiGdFSXbt+reDItZP4FxcmWpPsT5fnKt99/iTZ7br/+cLx956vk7HDCyQrEsuSe9fIV3nIp3w+E8qqwLBGrJclux/ntB8pDrcVTU6XZ0RmN59TqXfSRLaeOK/1Zeu8/NHj89MzDxyb3/LvHOrVGV/60SUvm7mgPJngi5x9pP8eTAzpr6wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;beforeResponse&quot;
        title=&quot;&quot;
        src=&quot;/static/f548df5c4bf745e84c88fd244cdd323f/1cfc2/beforeResponse.png&quot;
        srcset=&quot;/static/f548df5c4bf745e84c88fd244cdd323f/3684f/beforeResponse.png 225w,
/static/f548df5c4bf745e84c88fd244cdd323f/fc2a6/beforeResponse.png 450w,
/static/f548df5c4bf745e84c88fd244cdd323f/1cfc2/beforeResponse.png 900w,
/static/f548df5c4bf745e84c88fd244cdd323f/21482/beforeResponse.png 1350w,
/static/f548df5c4bf745e84c88fd244cdd323f/d61c2/beforeResponse.png 1800w,
/static/f548df5c4bf745e84c88fd244cdd323f/b297c/beforeResponse.png 2844w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;BEFORE response while sending request body&lt;/code&gt; 경우와 다른 점은 Netty 서버에서 USER_EVENT가 발생하지 않는 차이점이 있다.&lt;/p&gt;
&lt;h2 id=&quot;3-connection-prematurely-closed-during-response&quot; style=&quot;position:relative;&quot;&gt;3. Connection Prematurely closed DURING response&lt;a href=&quot;#3-connection-prematurely-closed-during-response&quot; aria-label=&quot;3 connection prematurely closed during response permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;송신 서버가 수신 서버에게 헤더를 정상적으로 응답받고 바디를 받기 위해 대기하는 도중, 송신 서버가 커넥션을 일방적으로 닫는 경우이다.&lt;br&gt;
즉, 송신 서버가 헤더를 정상적으로 받고 바디를 완전히 받기 위해 대기하는 중에 수신 서버가 일방적으로 커넥션을 종료하는 경우이다.&lt;br&gt;
&lt;strong&gt;테스트 시나리오&lt;/strong&gt;: 응답 헤더와 일부 바디를 전송한 후 연결 종료&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 송신 서버의 API
@GetMapping(&amp;quot;/abort-connection&amp;quot;)
fun abortConnection(response: HttpServletResponse) {
    logger.info(&amp;quot;Starting abort connection...&amp;quot;)
    
    try {
        val writer = response.writer
        
        // 응답 데이터를 한 번 전송하여 클라이언트가 정상적으로 읽기 시작하도록 유도
        writer.write(&amp;quot;Starting response...&amp;quot;)
        writer.flush()

        Thread.sleep(100)

        // 일방적으로 종료! FIN 패킷 전달
        response.outputStream.close()

        writer.write(&amp;quot;This should not be sent&amp;quot;)
        
    } catch (e: IOException) {
        logger.error(&amp;quot;IOException during abort: ${e.message}&amp;quot;)
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br/&gt;
&lt;details&gt;
&lt;summary&gt;💡 WebClient Connection Pool 로그 자세히보기&lt;/summary&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Creating a new [my-provider] client pool [PoolFactory{evictionInterval=PT0S, leasingStrategy=fifo, maxConnections=1, maxIdleTime=-1, maxLifeTime=-1, metricsEnabled=false, pendingAcquireMaxCount=2, pendingAcquireTimeout=45000}] for [localhost/&amp;lt;unresolved&amp;gt;:9090]
[551113d8] Created a new pooled channel, now: 0 active connections, 0 inactive connections and 0 pending acquire requests.
[551113d8] REGISTERED
[551113d8] CONNECT: localhost/127.0.0.1:9090
[551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] Registering pool release on close event for channel
[551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] Channel connected, now: 1 active connections, 0 inactive connections and 0 pending acquire requests.
[551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] ACTIVE
[551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] onStateChange(PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090]}, [connected])
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] onStateChange(GET{uri=null, connection=PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090]}}, [configured])
[New Connection] : GET{uri=null, connection=PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090]}}
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] Handler is being applied: {uri=http://localhost:9090/force/abort-connection, method=GET}
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] onStateChange(GET{uri=/force/abort-connection, connection=PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090]}}, [request_prepared])
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] WRITE: 108B GET /force/abort-connection HTTP/1.1
user-agent: ReactorNetty/1.1.22
host: localhost:9090
accept: */*

[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] FLUSH
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] WRITE: 0B
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] FLUSH
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] onStateChange(GET{uri=/force/abort-connection, connection=PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090]}}, [request_sent])
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] READ: 108B HTTP/1.1 200 
Transfer-Encoding: chunked
Date: Sat, 27 Sep 2025 05:28:01 GMT
14
Starting response...    // 첫 번째 청킹 데이터 정상적으로 응답받음
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] onStateChange(GET{uri=/force/abort-connection, connection=PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090]}}, [response_received])
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] [terminated=false, cancelled=false, pending=0, error=null]: subscribing inbound receiver
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] READ COMPLETE
[551113d8-1, L:/127.0.0.1:53169 - R:localhost/127.0.0.1:9090] READ COMPLETE
[551113d8-1, L:/127.0.0.1:53169 ! R:localhost/127.0.0.1:9090] Channel closed, now: 0 active connections, 0 inactive connections and 0 pending acquire requests.
[Connection Closed] : GET{uri=/force/abort-connection, connection=PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 ! R:localhost/127.0.0.1:9090]}}
[551113d8-1, L:/127.0.0.1:53169 ! R:localhost/127.0.0.1:9090] INACTIVE
[551113d8-1, L:/127.0.0.1:53169 ! R:localhost/127.0.0.1:9090] onStateChange(GET{uri=/force/abort-connection, connection=PooledConnection{channel=[id: 0x551113d8, L:/127.0.0.1:53169 ! R:localhost/127.0.0.1:9090]}}, [response_incomplete])
[551113d8-1, L:/127.0.0.1:53169 ! R:localhost/127.0.0.1:9090] UNREGISTERED
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception 
[Request processing failed: org.springframework.web.reactive.function.client.WebClientResponseException: 200 OK from GET http://localhost:9090/force/abort-connection, 
but response failed with cause: reactor.netty.http.client.PrematureCloseException: Connection prematurely closed DURING response] with root cause&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/details&gt;
&lt;br/&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ff6649ca3efa0906969b2bcf54dd3e52/d9ed5/duringResponse.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 32.44444444444445%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABZklEQVR42h2Q2Y7TQBBF/feDeOCBRTzAMNIgFAmJJcRx7MRxvLsd2x07jj1LMgogtr841PTD0a1WVd2+3VZaN3hxQlLXBj0MBIVCjyPbbk8puNK3g5BFFOPnGas8ZV3kLNMEX9QTLdqWuu+x0qrB2UREZWXQhwE/zdG9GLZ7lO6YS3+6WjMX05m/McaPIZwwlMti0Yi4qskajbXdHVlFDaq5o9T3jLe/SdTBaDf8YNef8WONu9maOXdTmdpPNF64NbWzVtTtifvTP6w4vcH2KsJ4IEpGqurMKuio6u+o8oFcHXGWDVNHYbsVs8WWz7PM1N/miqmt+GLnsj9Sy451NT3zctLz5tNJeODa/sPrj7dG39u/ePf1J68mA8+udzK35/mHjou3NU+vOp5c7ri41IYXkzvZ+SsJE/n0ZUkYtQalblgHjdE8H8iyA95KnuUpFjL3iL3IWcjZ8QpJmjN3C/xAS8Ij/wELcYjpVb2JLQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;duringResponse&quot;
        title=&quot;&quot;
        src=&quot;/static/ff6649ca3efa0906969b2bcf54dd3e52/1cfc2/duringResponse.png&quot;
        srcset=&quot;/static/ff6649ca3efa0906969b2bcf54dd3e52/3684f/duringResponse.png 225w,
/static/ff6649ca3efa0906969b2bcf54dd3e52/fc2a6/duringResponse.png 450w,
/static/ff6649ca3efa0906969b2bcf54dd3e52/1cfc2/duringResponse.png 900w,
/static/ff6649ca3efa0906969b2bcf54dd3e52/21482/duringResponse.png 1350w,
/static/ff6649ca3efa0906969b2bcf54dd3e52/d61c2/duringResponse.png 1800w,
/static/ff6649ca3efa0906969b2bcf54dd3e52/d9ed5/duringResponse.png 2880w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 세 가지 다른 예외 메시지는 복잡한 네트워크 통신에서 발생하는 예외를 더 자세하게 표현하기 위해 나뉘어진 것을 확인할 수 있다.&lt;/p&gt;
&lt;h1 id=&quot;커널-tcp-소켓-상태에-따른-처리&quot; style=&quot;position:relative;&quot;&gt;커널 TCP 소켓 상태에 따른 처리&lt;a href=&quot;#%EC%BB%A4%EB%84%90-tcp-%EC%86%8C%EC%BC%93-%EC%83%81%ED%83%9C%EC%97%90-%EB%94%B0%EB%A5%B8-%EC%B2%98%EB%A6%AC&quot; aria-label=&quot;커널 tcp 소켓 상태에 따른 처리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;4-way handshake 단계를 진행 중인 커넥션을 사용하여 요청을 보내면 어떻게 되는지 확인해보자.&lt;br&gt;
실제로는 굉장히 짧은 시간에 이루어지기 때문에 재현하기 힘들어 TCP 커널 코드를 확인해보면서 유추해보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fcb9f339dfde65490a7ecb3b01faf28a/9a1cf/4way-handshake.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 76.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB2klEQVR42m1Sa3ObMBDk//+zfmrS5otjM1OIjYTAoPcTRNdo4glNd2yNpNPe7t1Rrevq7ajlTUyt4o2cG6evwasNyOuWc15z0JoTwpqGU4pV9cxrnbetWpY1eKbFhXWvd/ZGrj+DrWMQ287Gf1nXqLUmHT1f+PXW17WmNBqDtJUxVvBxGq987tRMghq8pEv023BfT3V+rzfKYgiQitbKaXJaO6Wi98hbzfPcM0YI7dmAJI5LL1RUJkm1GRO56P989FcSHker71OCVYg+bOVqX3d7y+L3fAj7GKz3xpqBq9dz87v+0D4gZK1Fj7ZPVPkTuI0xbkes64LLFGHZonDG2LhjmibOefXl3ZOcY0qTUOMsQlqeoRBC4aBSrEqpA7nYBlxMNRl/Xdq70CmGlFJxCz5ocCGlfNj+TsZTbPphxM+h1Jwh65wrziml0O/7Hu8rQsjtdmvbFmcEjDEpRW1sTe4v55ZOwhjtvEfGhyPnDg3DAQE0DGowgw2OeCSEVFoXzhMQx1CeA6pAgDKcdF0HZbQBWcoLzBMNCzGVceAGvg7KyA0CUmAVQsQduMTasOnH23s78K/KB/JzBzXEiqVSi7Z2FlIoFXb8R/n7F/YPSjuA0mrUeDqdyqj+Aia9YZP7zMFVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;4way handshake&quot;
        title=&quot;&quot;
        src=&quot;/static/fcb9f339dfde65490a7ecb3b01faf28a/1cfc2/4way-handshake.png&quot;
        srcset=&quot;/static/fcb9f339dfde65490a7ecb3b01faf28a/3684f/4way-handshake.png 225w,
/static/fcb9f339dfde65490a7ecb3b01faf28a/fc2a6/4way-handshake.png 450w,
/static/fcb9f339dfde65490a7ecb3b01faf28a/1cfc2/4way-handshake.png 900w,
/static/fcb9f339dfde65490a7ecb3b01faf28a/9a1cf/4way-handshake.png 924w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Active Closer의 정상적인 상태 진행&lt;/strong&gt; ESTABLISHED → FIN_WAIT1 → FIN_WAIT2 → TIME_WAIT → CLOSE&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 739px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAA5lAAAOZQHwd0H4AAADeUlEQVR42k2Uy28bVRTG/f+xYo0qdnTLBolKwAoJIaQUQTd0ASpSJVaNlEohTUgRTRw58aOOU+fVJo4Tx4494/F43u8f3zhpYaQ7594593zn+849dypJlDC7sRgOrrno95jP57x/po7L8Pljekv3ufjxPrPGBpeXA447HSaDAfZkgmMYOJaF47pMTZOK60QY4xDDsLi6GjEeW8xmHq4bCjyhf7bO5dkfDPu/MbiskeUQxxFZllEUBVmakgbBBxKVJEllcgXnAik0EkzTF2ioQIjidQbDr5nOvhPIwV1YfvvOc/IS0PcX6zJBpZz0ej02N7dpNOo4zq3kIPDxPAfXi/HDfDEs21ns9QXgOg6d/X22Xr3ioN0mFeMFw/LV6RywvPyM1dU1RqPhwpFlqQJdMi+hiApRLSQtJQzDhT8S/XqjwcrKCs1mkyRJ/gMsqYuwGJXMwJ6HsrlsxO/9pzzqPuKno595MVol92Nc22ZmmKSRavm/+n0ALOm6niuQmFiJwiijVBDGBU+MJzwcLPHD9RLPzVUS18eZW1jGBHtqMrNM1Tq+reF7wCxT68gZODYkqpltYVxfkUcBhZIUKnwpIlB5S2UzK5R0JdV5pklxB3XHMNROO80oidtegB3GTCVjrKLPwohZmmPEOSMv43LqMnN8RqZN35hxZSVcGOoOxc4lyRdWpZcX7Mzb1Ky/qY9f0Eqn7CY5O4FHXYlb8Tu2x2ts9pbZGHd5q5M+nNtsmDe87K+xdvInDa/OXhFzqiatnEtK23vK3tnndMYPqDkd/jLmVHXCu5K07z7j9fAL2jcP2M/aDFXbvgKbRHSsh+y8k2/yvdYep8ISw4wdMdmZBzREuaHmLG1dElqSUtem7fD2e03ra9Wzp1FTTFPrl9Oc9UlAVad5Wko+V7Z6YdKy3/A6OqdVRAughgD3soJmalL3T2hFZ+zhM1BNe/Lt6iDa6TXV6TnNqC/JmQB1U87EoOX8QvX4Hs3BZ7SCDlXd7y3LoGqHNCa/snX0CbW392gkNa4k+VyANUL2ja/YOv5U9kslczkuJQ/kfCM5h5LQ1YcjzbuS1FHTlvMTsT0MQl47IV3NDfXKUKNb5AtfzUy0t1wXXAirUl7oQs5CtdSLUC1T/sI8NbqnjTfrj2l+8xH7336M0foH3T5dSbWIny+sM08lvozPFz+HfwF/IKbnliOK1wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;tcp lifecycle&quot;
        title=&quot;&quot;
        src=&quot;/static/c1757b36c69d0c457e9d022fa8515fe5/f1d1f/tcp-lifecycle.png&quot;
        srcset=&quot;/static/c1757b36c69d0c457e9d022fa8515fe5/3684f/tcp-lifecycle.png 225w,
/static/c1757b36c69d0c457e9d022fa8515fe5/fc2a6/tcp-lifecycle.png 450w,
/static/c1757b36c69d0c457e9d022fa8515fe5/f1d1f/tcp-lifecycle.png 739w&quot;
        sizes=&quot;(max-width: 739px) 100vw, 739px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;
&lt;a href=&quot;https://intronetworks.cs.luc.edu/1/html/tcp.html&quot;&gt;출처: intronetworks&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;커널-레벨-처리-로직&quot; style=&quot;position:relative;&quot;&gt;커널 레벨 처리 로직&lt;a href=&quot;#%EC%BB%A4%EB%84%90-%EB%A0%88%EB%B2%A8-%EC%B2%98%EB%A6%AC-%EB%A1%9C%EC%A7%81&quot; aria-label=&quot;커널 레벨 처리 로직 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_ipv4.c#L1905&quot;&gt;tcp_ipv4.c &lt;code class=&quot;language-text&quot;&gt;tcp_v4_do_rcv(...)&lt;/code&gt;&lt;/a&gt; 함수를 보면 소켓 상태에 따른 처리 방식을 확인할 수 있다.&lt;/p&gt;
&lt;h4&gt;주요 TCP 상태 설명&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;FIN-WAIT-1&lt;/strong&gt;: 자신이 보낸 종료 요청(FIN)에 대한 ACK을 기다리거나, 상대방의 FIN을 기다리는 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLOSE-WAIT&lt;/strong&gt;: 상대방으로부터 FIN을 받고 ACK를 보낸 후, 애플리케이션이 close()를 호출할 때까지 기다리는 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN-WAIT-2&lt;/strong&gt;: 상대방의 FIN을 기다리는 상태 (자신의 FIN에 대한 ACK는 이미 받음)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LAST-ACK&lt;/strong&gt;: 자신이 보낸 FIN에 대한 ACK를 기다리는 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIME-WAIT&lt;/strong&gt;: 상대방이 마지막 ACK를 확실히 받았음을 보장하고, 이전 연결의 지연된 패킷이 새 연결에 영향을 주지 않도록 일정 시간 대기하는 상태 (보통 2*MSL)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLOSED&lt;/strong&gt;: 연결이 완전히 종료된 상태&lt;/li&gt;
&lt;/ol&gt;
&lt;deckgo-highlight-code language=&quot;c&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
    // 1. 수신된 데이터를 사용자 영역에 전달할 수 있는 상태인 경우
    if (sk-&amp;gt;sk_state == TCP_ESTABLISHED) { /* Fast path */
        // ...
        tcp_rcv_established(sk, skb);
        return 0;
    }

    // 2. 연결 요청을 기다리는 상태인 경우
    if (sk-&amp;gt;sk_state == TCP_LISTEN) {
        // ...
    } else
        sock_rps_save_rxhash(sk, skb);

    // 3. 그 외의 상태인 경우
    reason = tcp_rcv_state_process(sk, skb);
    if (reason) {
        rsk = sk;
        goto reset;
    }
    return 0;

// RST 패킷 전송
reset:
    tcp_v4_send_reset(rsk, skb, sk_rst_convert_drop_reason(reason));

tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
    ... (중략) ...
    switch (sk-&amp;gt;sk_state) {
    case TCP_CLOSE:
        ... (중략) ...

    case TCP_LISTEN:
        if (th-&amp;gt;ack)
            return 1;

        if (th-&amp;gt;rst) {
            ... (중략) ...
        }
        if (th-&amp;gt;syn) {
            ... (중략) ...
        }
        goto discard;

    case TCP_SYN_SENT:
        ... (중략) ...
        return 0;
    }
    ... (중략) ...

    switch (sk-&amp;gt;sk_state) {
    case TCP_SYN_RECV:
        ... (중략) ...
    case TCP_FIN_WAIT1:
    case TCP_FIN_WAIT2:
        if (sk-&amp;gt;sk_shutdown &amp;amp; RCV_SHUTDOWN) {
            if (TCP_SKB_CB(skb)-&amp;gt;end_seq != TCP_SKB_CB(skb)-&amp;gt;seq &amp;amp;&amp;amp;
                after(TCP_SKB_CB(skb)-&amp;gt;end_seq - th-&amp;gt;fin, tp-&amp;gt;rcv_nxt)) {
                // 데이터가 포함된 패킷이면
                NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
                tcp_reset(sk, skb);  // ← RST 전송!
                return SKB_DROP_REASON_TCP_ABORT_ON_DATA;
            }
        }
        ... (중략) ...
    case TCP_CLOSING:
        ... (중략) ...
    case TCP_LAST_ACK:
        ... (중략) ...
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;TCP_FIN_WAIT1, TCP_FIN_WAIT2 상태&lt;/strong&gt; : 데이터가 포함되어 있다면 즉시 RST 패킷 전송&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP_TIME_WAIT 상태&lt;/strong&gt; : RST 패킷 전송 &lt;a href=&quot;https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_minisocks.c#L99&quot;&gt;tcp_minisocks.c#L99&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;TIME_WAIT은 마지막 ACK가 유실될 경우를 대비하여 상대방이 FIN을 재전송하는 경우 대응하기 위한 상태이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이는 종료 절차가 진행 중인 연결에 새로운 데이터가 오는 것을 비정상적인 상황으로 간주하여 강제로 연결을 리셋하기 때문이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;중요한 점!&lt;/strong&gt;
SEND_SHUTDOWN: 내가 먼저 데이터를 더 보내지 않을 때(내가 FIN 보냄/송신 종료)&lt;br&gt;
RCV_SHUTDOWN: 상대가 데이터를 더 보내지 않을 때(상대가 FIN 보냄/수신 종료)&lt;br&gt;
이들은 각각의 소켓 방향별로 독립적으로 관리되며, TCP 세션의 종료 과정에서 매우 중요한 역할을 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;원인과-예방-방법&quot; style=&quot;position:relative;&quot;&gt;원인과 예방 방법&lt;a href=&quot;#%EC%9B%90%EC%9D%B8%EA%B3%BC-%EC%98%88%EB%B0%A9-%EB%B0%A9%EB%B2%95&quot; aria-label=&quot;원인과 예방 방법 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;PrematureCloseException&lt;/code&gt; 예외가 무엇인지, 예외의 메세지가 왜 서로 다른지 알아보았다.&lt;br&gt;
이 예외의 원인인 서버가 갑자기 연결을 끊는 경우는 어떤 경우가 있는지, 예방 방법은 무엇인지 알아보자.&lt;/p&gt;
&lt;h2 id=&quot;로드밸런서와-서버간-timeout이-다른-경우&quot; style=&quot;position:relative;&quot;&gt;로드밸런서와 서버간 timeout이 다른 경우&lt;a href=&quot;#%EB%A1%9C%EB%93%9C%EB%B0%B8%EB%9F%B0%EC%84%9C%EC%99%80-%EC%84%9C%EB%B2%84%EA%B0%84-timeout%EC%9D%B4-%EB%8B%A4%EB%A5%B8-%EA%B2%BD%EC%9A%B0&quot; aria-label=&quot;로드밸런서와 서버간 timeout이 다른 경우 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 564px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/555356b46aaaacb57ab39a73ef22b7fd/ba4d9/diff-timeout.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.22222222222223%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7klEQVR42o1Sy4/ScBDu3+tFjRf/iI2evJh4Xh+JHtb14EUJsOtKd8FCX9JSHqVPoNDyaCntZ+dHVyAbEyf50pn5Tef1DZfnGfI8P8GJkH2PUqIohO9O0HUEXA0/w5hJUP1b/PbvwOF/5KhQFEW4/nGNmzqPT403eNV4Ct76io5Xh7nQwA0CGeNQhxX2MJjLmK7sQ5Lik82GiD8+Ryp8Ye75fA5Zlpkeb9ewJiMYIw2mO0BWTMu9qD3CW+EMd/Y3KBMeXasDVVWha13wv0RIF6+BD48Rnj+B1GqgVr9Cq9lkCbfbBOPxGGJHKuJ1pLsUnBUacMIhzKmOcBOwwCwr9lqAJPH6WJw/w6p1yewgCKAoyiEOeTnQngu2Q3Nkgv95C9+flI+nxGRHPhq5WqtCU3uoKpd4336JpllBc/wdLasCjmX+WyV/iLLbPNsxO01TOK4DuaOiolzgnXgGeXoDbdZElAT7Dqn13W73T5IpyYNzKv9zPQeKKqPX17GJN/uEy+USmqYVI/twXRe2bcNxHKZ7ngdRFGFZFtPJR28E23YgSRIjkciJk/hwh0mSsP0sFgsYhsGWT1itVojjmPnDMGRF+/0+a4J8hKwkkIQ7JkHXdQaqSLdGXdMhr9drCILA2KV3Kthut5lNMVTwPuEfIdQ56qr3vvQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;diff timeout&quot;
        title=&quot;&quot;
        src=&quot;/static/555356b46aaaacb57ab39a73ef22b7fd/ba4d9/diff-timeout.png&quot;
        srcset=&quot;/static/555356b46aaaacb57ab39a73ef22b7fd/3684f/diff-timeout.png 225w,
/static/555356b46aaaacb57ab39a73ef22b7fd/fc2a6/diff-timeout.png 450w,
/static/555356b46aaaacb57ab39a73ef22b7fd/ba4d9/diff-timeout.png 564w&quot;
        sizes=&quot;(max-width: 564px) 100vw, 564px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 이미지와 같이 LB와 서버의 timeout 값이 서로 다를 때 RST 패킷으로 인해 PrematureCloseException 예외가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8f7e0090625157b9cdad1b2377d1bf26/dcc98/lb-timeout.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 65.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABq0lEQVR42pWT2W7bMBBF8/+P/ZYCfW+B1q0TBAgQGHFs2ZYlWZIlaiW1kTxlk9gPTtHGAxDDbQ5nBrw3XFjfGw5RxjSObmXIsxpvHdHUEpEX7P2IvutJ4pLlMkYp/RJn7Wv8zSXQGEPX7emHnYPv3HxHnj8hxJKqWrn1Bj2EjMqnV8cz6GTvgVhqXZPKlLiKyVVONZYUfUHpvDLqnJG9pP0daGhMS4tE0dFaV2pXsEm27POAdpJXAq0D6splJTjUAaLPHDCjGDO3l6N0ex3QomnaBVI9ktZ3ROmcTDzQjWvX14XzISfix4B2QHf3yPI7xfEbqvzKIG8x0xZ0iNXH6zLETi5o68ba1b+nSD7jLT5RHr8wygd0710B1O5PqRYrQ0wbMDUBVsWM+Rx5nNGIGWN3Av6r5NNB6xoeHbCHlGEX0KxW1JtbKu8HUxm9XjX2AxmegTWTv6JdLRCP99TPS8TTnMb7iRbhFUDeLnWCJrwj9WYI/xdDHDAcMnRSuscUf1jTpN8UZRlH83+lSGkIgpoXKTvLMoXn5S+aLUuJ76dOkpokaXlepu+0/Bu4ZvLLzmbgdAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;lb timeout&quot;
        title=&quot;&quot;
        src=&quot;/static/8f7e0090625157b9cdad1b2377d1bf26/1cfc2/lb-timeout.png&quot;
        srcset=&quot;/static/8f7e0090625157b9cdad1b2377d1bf26/3684f/lb-timeout.png 225w,
/static/8f7e0090625157b9cdad1b2377d1bf26/fc2a6/lb-timeout.png 450w,
/static/8f7e0090625157b9cdad1b2377d1bf26/1cfc2/lb-timeout.png 900w,
/static/8f7e0090625157b9cdad1b2377d1bf26/21482/lb-timeout.png 1350w,
/static/8f7e0090625157b9cdad1b2377d1bf26/d61c2/lb-timeout.png 1800w,
/static/8f7e0090625157b9cdad1b2377d1bf26/dcc98/lb-timeout.png 2613w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;로드밸런서의 세션 테이블이 갱신되는 경우 직접적으로 FIN 이나 RST를 보내지 않고 조용히 세션 테이블을 갱신하기 때문이다.&lt;br&gt;
송신 서버는 이미 3-way handshake가 끝났다고 기억하고 있기 때문에 바로 바디를 전송하는 경우 로드밸런서는 RST 플래그를 반환하는 케이스가 발생할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s also important to note that almost all firewalls will silently remove idle connections from their state and will not initiate a close (send a TCP FIN or RST) to the client or server.&lt;br&gt;
The NLB has a fixed idle timeout of 350 seconds for TCP flows. Once the idle timeout is reached or a TCP connection is closed, it is removed from NLB’s connection state table.&lt;br&gt;
&lt;a href=&quot;https://aws.amazon.com/ko/blogs/networking-and-content-delivery/introducing-configurable-idle-timeout-for-connection-tracking/&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;AWS&lt;/code&gt; Introducing configurable Idle timeout for Connection tracking&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;webclient-maxidletime-설정-부재&quot; style=&quot;position:relative;&quot;&gt;WebClient maxIdleTime 설정 부재&lt;a href=&quot;#webclient-maxidletime-%EC%84%A4%EC%A0%95-%EB%B6%80%EC%9E%AC&quot; aria-label=&quot;webclient maxidletime 설정 부재 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 582px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f556678baa511f7b90dcfc8b3d7ea39d/7c1cd/timeout.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABwUlEQVR42m2SS4/TQBCE81uR+B9cOCPOHAAhceHEsloOvCSEeBwSEpRk14/YiZ3YjhPs2FFix+vxx4yNs2HZlkr29ExXTVdPByGoypIy8Lmeu5TrEKj4Lypxd/5WdMRmw2FicjA0CtuikF+VazikWFXVaBLVcd1ANKiFGnREHCOcOcL1uJ5NKVyH0vOoks3J5UpEaFLl2zZz+/qISp6R5B21/Gqf8UF/yaX3jcH8E0bQZRsnbLcpqyghNrsUL+6Tv3tEvArYpCn+ImA06/HWeErf+8xo+Z1x+KMhPLt6wqvRY/S4h5NeEW9/M53NcJ0Zl6aN9eU1+fN7pG8eov/qok8m9LsDPv4851n/AT3/PWbcJ9ovG0LlR7bPMEwNz1/Q5trWRJGxG1xwCCZHL9vYZ3scKazLWiH+tqwikZ45jkMsPb0hvAlR3T3jPM9xXbeGqjkSFkWBJ4eRSn+iKDpCCUQSpqETLpds5As43U+ShNVqJf1uBtY5VdvtdoRhyHq9ZiJ9UqrtWgmpQvXv+z6aptWEaj8IAkr5lv8hbFucTqc1ma7rNQzDYLFY1DcZDoeYpollWdi2zXg8rm1S+SzLasI/F9j0hSJXFcYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;timeout&quot;
        title=&quot;&quot;
        src=&quot;/static/f556678baa511f7b90dcfc8b3d7ea39d/7c1cd/timeout.png&quot;
        srcset=&quot;/static/f556678baa511f7b90dcfc8b3d7ea39d/3684f/timeout.png 225w,
/static/f556678baa511f7b90dcfc8b3d7ea39d/fc2a6/timeout.png 450w,
/static/f556678baa511f7b90dcfc8b3d7ea39d/7c1cd/timeout.png 582w&quot;
        sizes=&quot;(max-width: 582px) 100vw, 582px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;실무의 운영환경은 위와 같이 로드밸러서와 수신 서버의 timeout 설정이 동일했기 때문에 세션 테이블 갱신이 원인이 될 확률이 높지는 않아보인다.&lt;br&gt;
&lt;em&gt;그래도 수신 서버의 timeout 설정을 로드밸런서의 timeout보다 짧게 설정해서 수신 서버가 로드밸런서보다 빠르게 끊게 하는게 좋을 것이다.&lt;/em&gt;&lt;br&gt;
또 다른 추정으로는 Reactor Netty 클라이언트가 커넥션 풀을 통해 Connection을 획득했을 때는 열려 있었지만 그 직후 외부 요인(네트워크 구성요소 등)으로 인해 연결이 닫힌 경우이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9728c777cc33f970040b2d8c296358da/7b857/connection-race-condition.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABSklEQVR42p2Ta2/bIBSG+/9/0jR1Hzd1UqN2UpXm0jSzXV8wmYHYYAd4hrZ8bSSbI4R0dHh4X+DccWPI9sRJHBlMjRQ5Z11SFgWXS/x0z90t4MWfObsdonshr55Q/RrjcjxhGdDHQN1LDuJAJo903qCjY0qxCBgS0HhNcVpRiO90/RZhtjivrhVxJjBYxiljsEeU3qH1L+ywSflxrsL/J4dLi7ePWPXAqfqKkd+I0+aWhhsKB0sUktA2THXOWJWM4oX2/Z4w6jmWr0X9GVesGPIVLsvpf6fvUn4wFkdwdoHCBIxVRvP2TPP6SqgbSIpj2yXguMwyQuHKBrnd82e3p39fow/PBGtmWI7XBzECuf5Bs3nAZm9pVkxFxpjvibaff4d+VDTZT8rDF9THE329x7U7ok7W/cJO6TqL6jTaGOpaYoz6t3r/eS//BS6H+CC+SY/lAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;connection race condition&quot;
        title=&quot;&quot;
        src=&quot;/static/9728c777cc33f970040b2d8c296358da/1cfc2/connection-race-condition.png&quot;
        srcset=&quot;/static/9728c777cc33f970040b2d8c296358da/3684f/connection-race-condition.png 225w,
/static/9728c777cc33f970040b2d8c296358da/fc2a6/connection-race-condition.png 450w,
/static/9728c777cc33f970040b2d8c296358da/1cfc2/connection-race-condition.png 900w,
/static/9728c777cc33f970040b2d8c296358da/21482/connection-race-condition.png 1350w,
/static/9728c777cc33f970040b2d8c296358da/d61c2/connection-race-condition.png 1800w,
/static/9728c777cc33f970040b2d8c296358da/7b857/connection-race-condition.png 1941w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트가 요청 완료 후 커넥션을 풀에 반환 (시각 &lt;code class=&quot;language-text&quot;&gt;T0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;서버의 keep-alive timeout은 &lt;code class=&quot;language-text&quot;&gt;60초&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;클라이언트 maxIdleTime은 무제한&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;T0 + 60초 이상&lt;/code&gt;에 클라이언트가 해당 커넥션을 다시 사용하려고 시도&lt;/li&gt;
&lt;li&gt;서버는 이미 커넥션을 닫았거나 닫는 과정에 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Race Condition 발생&lt;/strong&gt;: 클라이언트가 요청을 보내는 시점과 서버의 연결 종료 시점이 겹침&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PrematureCloseException 발생&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 경우 Reactor Netty의 timeout 관련 옵션을 적절히 조정하면 문제 해결에 도움이 될 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// AS-IS
private val client = WebClient.builder()
    .baseUrl({url})
    .build()

// TO-BE
private fun connectionProvider(): ConnectionProvider =
    ConnectionProvider.builder(&amp;quot;client-pool&amp;quot;)
        .maxIdleTime(Duration.ofSeconds(45)) // 서버 keep-alive timeout 보다 짧게 설정
        .build()

private val httpClient = HttpClient.create(connectionProvider())

private val client = WebClient
    .builder()
    .clientConnector(ReactorClientHttpConnector(httpClient))
    .baseUrl(baseURL)
    .build()&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;서버가 커넥션을 닫기 전에 클라이언트가 proactive하게 커넥션을 정리해서 race condition을 방지할 수 있기에 maxIdleTime을 서버의 keep-alive timeout보다 작게 지정하였다.&lt;/p&gt;
&lt;h2 id=&quot;maxidletime-설정-이후-커넥션-풀-예외-발생&quot; style=&quot;position:relative;&quot;&gt;maxIdleTime 설정 이후 커넥션 풀 예외 발생&lt;a href=&quot;#maxidletime-%EC%84%A4%EC%A0%95-%EC%9D%B4%ED%9B%84-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80-%EC%98%88%EC%99%B8-%EB%B0%9C%EC%83%9D&quot; aria-label=&quot;maxidletime 설정 이후 커넥션 풀 예외 발생 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;maxIdleTime을 설정한 이후 커넥션 풀의 커넥션 고갈 예외가 발생한 것을 확인했다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Exception in thread &amp;quot;DefaultDispatcher-worker-5&amp;quot; Exception in thread &amp;quot;DefaultDispatcher-worker-1&amp;quot; Exception in thread &amp;quot;DefaultDispatcher-worker-7&amp;quot; Exception in thread &amp;quot;DefaultDispatcher-worker-10&amp;quot; Exception in thread &amp;quot;DefaultDispatcher-worker-3&amp;quot; org.springframework.web.reactive.function.client.WebClientRequestException: Pending acquire queue has reached its maximum size of 32&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Creating a new [client-pool] client pool 
[
    PoolFactory{
        evictionInterval=PT0S,
        leasingStrategy=fifo, 
+       maxConnections=16, 
        maxIdleTime=45000, 
        maxLifeTime=-1, 
        metricsEnabled=false, 
+       pendingAcquireMaxCount=32, 
        pendingAcquireTimeout=45000
    }
] 
for [{url}]&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h4&gt;maxIdleTime 설정 전&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;연결은 충분히 재사용 가능하기에 불규칙한 트래픽에 유용함.&lt;/li&gt;
&lt;li&gt;하지만 서버가 FIN 패킷을 보낼 때까지 Connection을 보유하기에 PrematureCloseException 발생 가능성 높았음.&lt;/li&gt;
&lt;li&gt;또한 Stale Connection 문제로 Connection reset by peer 예외도 발생 가능성 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;maxIdleTime 설정 후&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;PrematureCloseException 발생 가능성 및 Stale Connection 문제를 예방할 수 있음.&lt;/li&gt;
&lt;li&gt;하지만 idle 시간 초과로 인해 Connection이 자주 제거되어 새 연결을 계속 생성하기에 TCP 오버헤드 증가.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;연결 생성 속도 &amp;lt; 요청 속도&lt;/code&gt; 대기 큐에 요청이 쌓여 Connection 고갈 문제가 발생할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12ded8d31c9f1e0ef99a553a9ff648b3/0eb6d/rate-limiter.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.444444444444443%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAABE0lEQVR42m2QS0vDQBSF84v9O65cuRBBFy7cWFA3glIoLYpQW/Nq05lJkzaZpnknn0nFF3iGy+Hce5lzZgyxs/CTBUmqycuaTFnUo0tyHZJlJTJyWcRvqG4nrTU6THBnHktTEIgtPSzb5fjkFK13GE/ODU44Jdz4rPwQMb5jf3aEmk9wXMnjdMD9+wVz/4WClMVMcHv1wPX5gNfhjCpr8IOA4WjCfp9iCCHYRp9OPdq6os4TfkNJhVLqWxdFgbf0yPP8oM0u4Wj8jLeSGHEc01cYhsRR1PGGYLNlpzXrYN09Qx9mVVX9mHbnC03TdF+Tk3WX92z0jT6laZrYto2UsksksS0Lx3EOvZ7LsvyTum3bf/UH8Pl4Oofp6BwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;rate limiter&quot;
        title=&quot;&quot;
        src=&quot;/static/12ded8d31c9f1e0ef99a553a9ff648b3/1cfc2/rate-limiter.png&quot;
        srcset=&quot;/static/12ded8d31c9f1e0ef99a553a9ff648b3/3684f/rate-limiter.png 225w,
/static/12ded8d31c9f1e0ef99a553a9ff648b3/fc2a6/rate-limiter.png 450w,
/static/12ded8d31c9f1e0ef99a553a9ff648b3/1cfc2/rate-limiter.png 900w,
/static/12ded8d31c9f1e0ef99a553a9ff648b3/0eb6d/rate-limiter.png 913w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;이 상황을 고려해서 설정을 다시 했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;하루에 N번 정기적으로 호출&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;400 ~ 500명에 대한 배치성 부하&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실시간이 보장되지 않아도 좋음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;한 번쯤 전송되지 않아도 큰 문제가 있지 않음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B 서버의 응답은 300ms 이내 응답&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;외부 API 서버에 rate limiter가 존재함&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;deckgo-highlight-code language=&quot;diff&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Creating a new [client-pool] client pool 
[
    PoolFactory{
        evictionInterval=PT0S,
        leasingStrategy=fifo, 
-       maxConnections=16,         
+       maxConnections=10, -- rate limiter가 존재하여 방어적으로 전송
        maxIdleTime=45000, 
        maxLifeTime=-1, 
        metricsEnabled=false, 
-       pendingAcquireMaxCount=32, 
+       pendingAcquireMaxCount=500,  -- 최대 요청에 수용 가능한 정도
-       pendingAcquireTimeout=45000
+       pendingAcquireTimeout=180000  -- 최대 요청이 증가함에 대비하여 여유롭게 대기
    }
] 
for [{url}]&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h1 id=&quot;문제가-진짜-해결된-것일까&quot; style=&quot;position:relative;&quot;&gt;문제가 진짜 해결된 것일까?&lt;a href=&quot;#%EB%AC%B8%EC%A0%9C%EA%B0%80-%EC%A7%84%EC%A7%9C-%ED%95%B4%EA%B2%B0%EB%90%9C-%EA%B2%83%EC%9D%BC%EA%B9%8C&quot; aria-label=&quot;문제가 진짜 해결된 것일까 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;운영에서 발생한 케이스는 Connection prematurely closed BEFORE response 이다. 즉, 수신 서버가 요청을 받았는데 커넥션을 일방적으로 닫은 경우이다.&lt;br&gt;
궁금한 점이 생기는데,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q1.&lt;/strong&gt; Active Closer가 FIN을 전송하고 해당 커넥션의 상태가 &lt;code class=&quot;language-text&quot;&gt;FIN_WAIT_1&lt;/code&gt;인 경우에, 왜 커널 레벨에서 해당 커넥션을 통한 요청에 대해 예외를 직접 처리하지 않고 애플리케이션 레벨까지 전파하는 (Netty의 channelRead가 실행되는) 이유가 뭘까?&lt;br&gt;
&lt;strong&gt;A1.&lt;/strong&gt; Active Closer는 FIN을 송신한 후에도 Passive Closer가 데이터를 보내는 것은 프로토콜상 허용하기 때문이다. &lt;strong&gt;정말로 &quot;양쪽 모두 FIN 송신 후 추가 데이터 수신&quot; 등 명백한 프로토콜 위반 시 커널이 이를 인지하고 오류 처리(RST)&lt;/strong&gt; 를 전송한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q2.&lt;/strong&gt; 이 문제가 발생한 정확한 상황은?&lt;br&gt;
&lt;strong&gt;A2.&lt;/strong&gt; 해당 WebClient의 사용처는 &lt;code class=&quot;language-text&quot;&gt;일시적 부하가 발생하는 배치 로직&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;REST API 여러 곳&lt;/code&gt; 이다. 문제가 발생한 부분은 배치 로직에서 WebClient를 사용했을 때 발생했다. 현재로 유추하는 시나리오는 배치 로직을 제외한 곳에서 낮은 트래픽에 대한 요청을 처리하다 남은 Connection을 배치 로직이 실행되면서 Stale Connection을 이용한 것이라고 추정하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q3.&lt;/strong&gt; maxIdletime과 pendingAcquireMaxCount, pendingAcquireTimeout를 설정하여 해결되었다고 판단했는데 다른 방법은 없는지? 최선이였는지?&lt;br&gt;
&lt;strong&gt;A3.&lt;/strong&gt; PrematureException과 WebClientRequestException을 WebClient 설정으로 해결하였는데 다른 해결 방법도 있어보인다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;배치 로직과 일반 로직에 대한 WebClient 분리&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;특정 시간에 요청이 몰리는 요구사항에 대해 설정 최적화 가능&lt;/li&gt;
&lt;li&gt;대기 큐의 사이즈를 크게, 대기 시간을 넉넉하게, Connection 수는 수신 서버의 상황을 고려하여 적절하게 설정&lt;/li&gt;
&lt;li&gt;하지만 장애 발생 또는 수신 서버에 대한 부하 증가 시 데이터 유실 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재시도 전략&lt;/strong&gt;
&lt;ul&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;strong&gt;RDB 또는 메세지 큐&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;RDB에 알림 요청 데이터와 상태를 함께 기록해서 배치를 통해 주기적으로 전송하거나 메세지 큐에 공급해서 수신 서버가 컨슘하는 일관성을 추가하는 방법은 어떨까 싶다.&lt;/li&gt;
&lt;li&gt;추가적인 비용은 들겠지만 정확한 일관성을 보장하고 싶다면 이 방법이 안전할 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;느낀점&quot; style=&quot;position:relative;&quot;&gt;느낀점&lt;a href=&quot;#%EB%8A%90%EB%82%80%EC%A0%90&quot; aria-label=&quot;느낀점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이번 PrematureCloseException 원인 분석을 통해 많은 것을 배울 수 있었다.&lt;br&gt;
기존에는 WebClient의 기본 설정을 그대로 사용했었는데, 이번 경험을 통해 운영 환경의 특성을 파악하고 적절한 설정을 적용해야함을 깨달았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;기본값 ≠ 최적값&lt;/strong&gt;: 기본 설정은 일반적인 상황을 가정하므로, 특정 환경에서는 적절하지 않을 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상황별 튜닝 필요&lt;/strong&gt;: 트래픽 패턴, 서버 환경, 비즈니스 환경, 네트워크 환경을 고려한 세밀한 조정이 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이번에 적용한 설정으로 문제가 상당히 개선되었지만, &lt;strong&gt;모든 네트워크 예외를 완전히 방지할 수는 없다&lt;/strong&gt;.&lt;br&gt;
우리가 제어할 수 없는 영역인 외부 회사의 서비스 API는 불가피한 네트워크 예외에 대비한 적절한 재시도 전략과 알림 전략이 필요하다.&lt;/p&gt;
&lt;p&gt;실무에서는 시간 압박으로 인해 급하게 처리했지만, 문제를 정확히 파악하기 위해 송신 서버와 수신 서버 모두 TCP dump를 뜨는 것이 더 정확할 것 같다.&lt;br&gt;
(추후 ECS 환경에서의 TCP dump를 뜨기 위한 방법을 정리해야겠다.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;참고&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://brewagebear.github.io/linux-kernel-internal-3/&quot;&gt;[Kernel] 커널과 함께 알아보는 소켓과 TCP Deep Dive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cjlee.io/post/webclient-timeout/&quot;&gt;헷갈리는 WebClient Timeout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9293#name-state-machine-overview&quot;&gt;RFC9293: Transmission Control Protocol (TCP) State Matchine Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://projectreactor.io/docs/netty/release/reference/faq.html#faq.connection-closed&quot;&gt;How can I debug &quot;Connection prematurely closed BEFORE response&quot;?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/spring-webflux-concurrency&quot;&gt;Concurrency in Spring WebFlux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cs.unh.edu/cnrg/people/gherrin/linux-net.html&quot;&gt;Linux IP Networking: A Guide to the Implementation and Modification of the Linux Protocol Stack&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[API가 왜 멈췄을까? 코루틴 이해하기]]></title><description><![CDATA[CS팀에서 특정 기능에 대한 사용자 문의 인입이 증가하고 있다고 백엔드 팀에 문의가 들어왔다. 사용량이 갑자기 늘어나서 병목이 생긴건가? 지연되고 있는 API가 있나? DB…]]></description><link>https://jdalma.github.io/2025y/coroutinescope/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/coroutinescope/</guid><pubDate>Wed, 10 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CS팀에서 특정 기능에 대한 사용자 문의 인입이 증가하고 있다고 백엔드 팀에 문의가 들어왔다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용량이 갑자기 늘어나서 병목이 생긴건가?&lt;/li&gt;
&lt;li&gt;지연되고 있는 API가 있나?&lt;/li&gt;
&lt;li&gt;DB에 부하가 발생해서 지연되는 쿼리가 있나?&lt;/li&gt;
&lt;li&gt;가용할 수 있는 스레드가 없나?&lt;/li&gt;
&lt;li&gt;메모리 또는 CPU로 인해 서버가 다운되었나?&lt;/li&gt;
&lt;li&gt;부하로 인해 ECS 태스크가 스케일링 중인가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위에 해당하는 특이사항은 발견하지 못했지만 데이터독 애플리케이션 로그를 통해 배포가 완료된 뒤에 정상적으로 호출되다가 런타임 예외가 발생한 시점부터 해당 API가 정상 작동하지 않는다는 것을 알아챌 수 있었다.&lt;/p&gt;
&lt;h1 id=&quot;문제-지점&quot; style=&quot;position:relative;&quot;&gt;문제 지점&lt;a href=&quot;#%EB%AC%B8%EC%A0%9C-%EC%A7%80%EC%A0%90&quot; aria-label=&quot;문제 지점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@RestController
class TestController {
    private val scope = CoroutineScope(Dispatchers.IO)
    
    @GetMapping(&amp;quot;/test&amp;quot;)
    fun test() {
        scope.launch {
            // business logic ...
            throw RuntimeException(&amp;quot;비즈니스 로직 런타임 예외 발생!!!&amp;quot;)
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;CoroutineScope를 생성하는 오버헤드를 줄이기 위해 싱글톤 빈 내부에 선언하여 재사용한것으로 보인다.&lt;br&gt;
&lt;strong&gt;문제는 이 scope 자식 코루틴에서 발생한 예외가 적절히 처리되지 않아 &lt;code class=&quot;language-text&quot;&gt;scope&lt;/code&gt;의 Job이 &lt;code class=&quot;language-text&quot;&gt;cancelled&lt;/code&gt; 상태로 전이되었기 때문이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;아래의 코드를 통해 코루틴 내부에서 예외를 어떻게 처리하는지, 취소 상태가 되면 왜 코루틴이 실행되지 않는지 알 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 1. BaseContinuationImpl에서 코루틴 블록 내부에서 발생한 런타임 예외를 failure result로 resume 한다.
public final override fun resumeWith(result: Result&amp;lt;Any?&amp;gt;) {
    var current = this
    var param = result
    while (true) {
        probeCoroutineResumed(current)
        with(current) {
            // fail fast when trying to resume continuation without completion
            val completion = completion!!
            val outcome: Result&amp;lt;Any?&amp;gt; =
                try {
                    val outcome = invokeSuspend(param)
                    if (outcome === COROUTINE_SUSPENDED) return
                    Result.success(outcome)
                } catch (exception: Throwable) {
                    Result.failure(exception)
                }
            releaseIntercepted()
            if (completion is BaseContinuationImpl) {
                current = completion
                param = outcome
            } else {
                completion.resumeWith(outcome)
                return
            }
        }
    }
}

// 2. AbstractCoroutine에서 resume을 실행하면서 전달받은 result를 CompletedExceptionally로 변환하여 전달한다.
public final override fun resumeWith(result: Result&amp;lt;T&amp;gt;) {
    val state = makeCompletingOnce(result.toState())
    if (state === COMPLETING_WAITING_CHILDREN) return
    afterResume(state)
}
internal fun &amp;lt;T&amp;gt; Result&amp;lt;T&amp;gt;.toState(
    onCancellation: ((cause: Throwable) -&amp;gt; Unit)? = null
): Any? = fold(
    onSuccess = { if (onCancellation != null) CompletedWithCancellation(it, onCancellation) else it },
    onFailure = { CompletedExceptionally(it) }
)


// 3. JobSupport에서 예외가 감지된다면 모든 형제,부모 Job에게 예외를 전달하고 상태를 취소,완료 상태로 변경한다.
private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?): Any? {
    val list = getOrPromoteCancellingList(state) ?: return COMPLETING_RETRY
    val finishing = state as? Finishing ?: Finishing(list, false, null)
    var notifyRootCause: Throwable? = null
    synchronized(finishing) {
        ...
        notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }
    }
    notifyRootCause?.let { notifyCancelling(list, it) }
    val child = firstChild(state)
    if (child != null &amp;amp;&amp;amp; tryWaitForChild(finishing, child, proposedUpdate))
        return COMPLETING_WAITING_CHILDREN
    return finalizeFinishingState(finishing, proposedUpdate)
}
private fun notifyCancelling(list: NodeList, cause: Throwable) {
    // first cancel our own children
    onCancelling(cause)
    notifyHandlers&amp;lt;JobCancellingNode&amp;gt;(list, cause)
    cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/880a59dd8ab44d605908b0f9d6d2ccd9/b97f6/scopeexception.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 21.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA6ElEQVR42j3P207CQBSF4b6NkRnowXZ6mENngGIpKIQEkRj1/Z/id2yiF1/Wzr5Zeyd9kXP3mte65KgqDtHFWe7bzezjeeD7MPG1H7n6nts6cA09p66dDXmGl4KwlGyWgsSKBcZYQvDopqVSLZ3pMS7g/Ibtbk/daKq6Q8X8m+UqRywzhExZyBUPIqURksSJx9iSctl6Pn3Ne99ytoZdWc6G8ompqTnF3a8XoxlrxSGWT03DqBTHeOlRxy9VSSJjQ74q2Lmet8Fxi87BMlrN1Bt8p7GdZ72e8MbhOkOZFf+esnzOKirSgh+uvog2/eORdAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;scopeexception&quot;
        title=&quot;&quot;
        src=&quot;/static/880a59dd8ab44d605908b0f9d6d2ccd9/1cfc2/scopeexception.png&quot;
        srcset=&quot;/static/880a59dd8ab44d605908b0f9d6d2ccd9/3684f/scopeexception.png 225w,
/static/880a59dd8ab44d605908b0f9d6d2ccd9/fc2a6/scopeexception.png 450w,
/static/880a59dd8ab44d605908b0f9d6d2ccd9/1cfc2/scopeexception.png 900w,
/static/880a59dd8ab44d605908b0f9d6d2ccd9/b97f6/scopeexception.png 958w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;처음 예외가 발생한 이후 코루틴 스코프의 Job은 취소된 상태이고, 상세 상태에는 예외 정보가 저장된것을 볼 수 있다.&lt;br&gt;
만약 이 취소된 코루틴 스코프를 재호출하면, 내부 상태를 확인하고 비어있는 구현체인 NonDisposableHandle을 반환하기 때문에 코루틴이 실행되지 않는다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;loopOnState { state -&amp;gt;
    when (state) {
        is Empty -&amp;gt; {
            ...
        }
        is Incomplete -&amp;gt; {
            ...
        }
        else -&amp;gt; { // is complete
            if (invokeImmediately) {
                // ChildHandleNode.childJob.parentCancelled(job) 부모 Job을 취소한다.
                handler.invoke((state as? CompletedExceptionally)?.cause)
            }
            return NonDisposableHandle
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;테스트 코드로 확인해보면 더 이해하기 쉬울 것이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;given(&amp;quot;코루틴 빌더별 예외 처리 방식&amp;quot;) {
    `when`(&amp;quot;스코프 내에서 launch 빌더를 사용할 때&amp;quot;) {
        then(&amp;quot;예외가 parentJob에 전파되며, 취소된 스코프는 실행되지 않는다.&amp;quot;) {
            var caughtException = false
            val handler = CoroutineExceptionHandler { _, _ -&amp;gt;
                // 예외 핸들러를 통해 스코프 내의 에외를 잡아도 부모 Job에 그대로 전파된다.
                caughtException = true
            }
            val parentJob = Job()
            val scope = CoroutineScope(parentJob + Dispatchers.IO + handler)

            parentJob.isActive shouldBe true
            parentJob.isCancelled shouldBe false
            parentJob.isCompleted shouldBe false

            val childJob = scope.launch {
                delay(50)
                throw RuntimeException(&amp;quot;launch 내부 예외&amp;quot;)
            }

            childJob.isActive shouldBe true
            childJob.isCancelled shouldBe false
            childJob.isCompleted shouldBe false
            caughtException shouldBe false

            childJob.join()

            parentJob.isActive shouldBe false
            parentJob.isCancelled shouldBe true
            parentJob.isCompleted shouldBe true

            childJob.isActive shouldBe false
            childJob.isCancelled shouldBe true
            childJob.isCompleted shouldBe true
            caughtException shouldBe true

            scope.launch {
                throw RuntimeException(&amp;quot;이 코드는 실행되지 않습니다.&amp;quot;)
            }
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;첫-번째-코루틴의-구조&quot; style=&quot;position:relative;&quot;&gt;첫 번째. 코루틴의 구조&lt;a href=&quot;#%EC%B2%AB-%EB%B2%88%EC%A7%B8-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%98-%EA%B5%AC%EC%A1%B0&quot; aria-label=&quot;첫 번째 코루틴의 구조 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 583px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/84c9a2296b8160a5dbf148c091c3afb7/9fc4b/coroutinescope.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.77777777777778%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACbklEQVR42mWTWW/iWBBG8///Sj+NpjVqqdNZCDgEB7PHGzbGBq9gY9YEkj5dBs1MZvrhU6l0vzq3qnx9dTx9sFy/kZWi9emstDyy3H5geglPXZNW16KpGdSaPQw3otj9PHv+9le1FaNiXZnzHYPgyDB4ZeCVDP31OY4kfldGfKv3+Hrb5q/7Dn/eqNy0DIbT8qLKW0Wp7ftHxtGeK21yYJxIR0mPU6GyS5u8FW3eco0iuGMx/UEe3JP792TeNeW8xmuhyXn74s1b5EkfJz2h2luuOpNXnHjPLqtLoUIaaJRRnfeyxSJQSX2BF62z8rBLEWmcVk22AlvMe6zjltQquPEGxdhcOpyke2b2LbNZgB/EpKGN+vCF65s76kqLZu0PzP4duunQ6Q7w9Fu6zzVG+pj2s0roPjDNdp+BBzzjmuHoBWcSYBldRto3hi+WyGbYucPRH2k8qjypHaZWnZ7W4Pt1jUZDIZwon4GXkTdJjYXfZBmZMvqD7OaR2FOJpx32MtImbbCKdZZhj8OiThkqrLIxRfgstXUZeYtibqod7rHjE1FoUqZtkqBJmfVYpX3yWJP8SS7pnDVz6+e8zAbi7Z29q+SZSJpw0g8ejfICHEuih+/o8yNmdIl6eOLF32JNIkx3hu1FzNMVQbrhZXaQ8/963eznv0A7kUccnbDi939kiMkPF5SrnPV6je9PsSwT29Tp2xF2ivguNVVtBVR0AWruDkeSqsvPsuSSaZQLcCkqmAVTvInDRDR0M5wF4ns/e6uGKqA6lh268kEG8tK73v5/OqA5a7pWTFuf0zZCBk4mf9GKzm9eYfjyUpIDvwAmmGe/St7gsAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;coroutinescope&quot;
        title=&quot;&quot;
        src=&quot;/static/84c9a2296b8160a5dbf148c091c3afb7/9fc4b/coroutinescope.png&quot;
        srcset=&quot;/static/84c9a2296b8160a5dbf148c091c3afb7/3684f/coroutinescope.png 225w,
/static/84c9a2296b8160a5dbf148c091c3afb7/fc2a6/coroutinescope.png 450w,
/static/84c9a2296b8160a5dbf148c091c3afb7/9fc4b/coroutinescope.png 583w&quot;
        sizes=&quot;(max-width: 583px) 100vw, 583px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;간략하게 보면 위의 구조와 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CoroutineScope&lt;/strong&gt; : 코루틴의 실행 범위를 정의하며, 모든 코루틴은 특정 Scope 내에서 실행된다. CoroutineContext를 포함하고 있어 코루틴의 실행 환경을 제공한다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;supervisorScope {}&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;coroutineScope {}&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;CoroutineScope()&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;runBlocking {}&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;withContext {}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CoroutineContext&lt;/strong&gt; : 코루틴 실행에 필요한 &lt;strong&gt;컨텍스트 정보들의 집합&lt;/strong&gt; 이다.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Job&lt;/strong&gt; : 코루틴의 생명주기를 관리할 수 있는 &lt;code class=&quot;language-text&quot;&gt;시작&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;취소&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;완료&lt;/code&gt;등의 상태를 가지며, 부모와 자식 Job을 참조한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dispatcher&lt;/strong&gt; : 코루틴이 어떤 스레드에서 실행될지 결정한다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Dispatchers.Main&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;IO&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Default&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Unconfined&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CoroutineExceptionHandler&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuation&lt;/strong&gt; : 코루틴의 중단, 재개 메커니즘을 담당하며, &lt;code class=&quot;language-text&quot;&gt;suspend&lt;/code&gt; 함수가 호출될 때 현재 실행 상태를 저장하고, 작업 완료 후 해당 지점부터 재개할 수 있게 해준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;코루틴을 중단하면 스레드를 반환해 콜 스택에 있는 정보가 사라지기 때문에 어딘가에 실행 지점을 저장해놓아야 한다.&lt;br&gt;
그 역할이 Continuation이다.&lt;br&gt;
&lt;strong&gt;중단이 되었을 때의 상태(label)와 함수의 지역 변수와 파라미터(필드), 그리고 중단 함수를 호출한 함수가 재개될 위치 정보를 가지고 있다.&lt;/strong&gt;&lt;br&gt;
그렇기에 하나의 Continuation 객체가 다른 Continuation 객체를 참조하는 거대한 양파와 같다.&lt;/p&gt;
&lt;p&gt;아래의 코드를 통해 중단함수가 디컴파일되는 경우 Continuation이 어떻게 상태를 구분하는지, 또한 continuation은 어떻게 resume되는지 확인할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;suspend fun findUserName(token: String): String {
    println(&amp;quot;Before&amp;quot;)
    val userId = getUserId(token) // suspending
    println(&amp;quot;Got userId: $userId&amp;quot;)
    val userName = getUserName(userId)
    println(&amp;quot;After&amp;quot;)
    return userName
}

suspend fun getUserId(token: String): String {
    delay(1000)
    return &amp;quot;test user id&amp;quot;
}

suspend fun getUserName(userId: String): String {
    delay(1000)
    return &amp;quot;test user name&amp;quot;
}

private val executor = Executors.newSingleThreadScheduledExecutor {
    Thread(it, &amp;quot;scheduler&amp;quot;).apply { isDaemon = true }
}

suspend fun delay(timeMillis: Long): Unit = suspendCancellableCoroutine { cont -&amp;gt;
    executor.schedule({
        cont.resume(Unit)
    }, timeMillis, TimeUnit.MILLISECONDS)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7b6b8c1d339c8793085d17b3966d231c/b06fe/continuation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 88.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAADBElEQVR42p2Ua3OaYBCF/QONclc0gkSjgNwEbNR4A7zl0uZD//9fOT2gSZtM2un0w87AwPvsnj27b00URXwWjUYDo0mMZHfEeJ1hvNrA2+7Qcz0I9TpESfr0XO1PQIHAGz8k8IAg2xG2ZeQwHAIb/wFssIqbMMKEwCgr4LJCZ0NgWSGTSbJ8hn6ImiAI+CxkQqNkivnxESFB8SrHhJJLYOPq6hz1q/fPjJqiqlCbTShNDarWhKxpuDVNmK0WhkmE9OEBYUHJeY708IQB26C1dVieh+uRDeMS10MbncEQNcsdIykObHoOb0lZjIihEaz3TXiTEAEh3dsRujzk3Q4RxinWLz8wOz1hSgXJ7oRJwcj3qBm2h3T/yJcTgu2ejWfP1ju0uxbqwhd4ro90OkeY3KHvBmgpCvwower5BfenZ3zdPbAVe0SMNDuwwnGIiDCPFZZV2qsM4XILvduBRtnxYoP7x2dW8YC0OKJ3bWDsuHDvFvDv5ojnS4zTBW4nU/TDpAQGzPTETEfE2wLBOkdwv4GiK+ypDI8/RxybsooJv9kDB1ani25Lh9HqQGe0tBbEi5nsoY8pJXubAtFmh5CHRrMlZJolygLcZIbZ4bGSF68L6NcWDWxXoyVxTFSVRsoK34XzHFpBiuTSw1LyiIaM5megwJ9u4hjBnoblGexlBpUVyYp6BpTjxWdJkn8NtuVF+Hr6TpfOK+awh/Z8VQHL4W52Omj3LOimhbZhXQ4KbwCFct8DS8mnb/AzOrw5wKXLzqqAxMxGv4++F8KwXZi2U+2xbvaqTakkU2qz06ukvwE71g0dijmoIXpeAIsz1w+n/CihH0ScsycE+QE+t8SnCrPcFFZeQoTPJFfuMONriIIITTeqZ3PsI2TlAQ1z2VuXhnVZ7dttw7OKon0AfrwYqqxaBR5wrhI67BPo0BCXU2BWt41QyS2rVOjy34GX3oiSgkHECeD+lsAxt8fLONhsTQlQuPeKqrFC9V/vwzr76ld3ocPNsRdrSs4qg8rb5VVNFb8BfwKUIv8VK530VQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;alt text&quot;
        title=&quot;&quot;
        src=&quot;/static/7b6b8c1d339c8793085d17b3966d231c/1cfc2/continuation.png&quot;
        srcset=&quot;/static/7b6b8c1d339c8793085d17b3966d231c/3684f/continuation.png 225w,
/static/7b6b8c1d339c8793085d17b3966d231c/fc2a6/continuation.png 450w,
/static/7b6b8c1d339c8793085d17b3966d231c/1cfc2/continuation.png 900w,
/static/7b6b8c1d339c8793085d17b3966d231c/21482/continuation.png 1350w,
/static/7b6b8c1d339c8793085d17b3966d231c/d61c2/continuation.png 1800w,
/static/7b6b8c1d339c8793085d17b3966d231c/b06fe/continuation.png 15340w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/678d094323c1155cfccdf1e8181cc5f9/22284/continuation_sequence.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 158.22222222222223%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAYAAAASYli2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAEHUlEQVR42pVWaXPbNhT0//857afOdJJp67R2aseJ3VGcTGqptg5KpESRAHGD2wUpyfIhx8EMRiQELN6x+x6PvNYohkPYWiCN0LYYlgW+FTlU8N1ayzWR36Ga3aD1Fi3XJlLha1lzT4QJFv/VGSorcdRGgdX8DKt8BNloCCFRCQ1tNJxrEJzk8QjvPKyxCb6bznsY24OnYZ3tLj5KL46bvQ8IgdMbaDVBlf+C1fhn1MUx2iAQI7jHYzsiF/bfnXP7gD16756HkiPkiwHqdQ5J15LVjXJwpoS3U/5O4E2xAdxY/BDQ7QC3cTSxpcWxszwd1PUIJS0uxr+imL6DLAdcj4csfAwY4dqA/dG6AnJ9C0GLFa2tpYPRc6j1ALa5IYZGgjhooWvjA0BEXhDcFh7p32U5wnT4O2a377BejtBG+x3Atu0TRffb6BFsss6gkQ0abWHTOo8FT6ZkJ3A6S7Rx0E1F8wU0OamVhmHWDYESTSQPK655XuqZvBTXdEmK635SrN3E0KkxlrO/GOQvuwQki/cp0Vkd4kHatJHczS+6zD/r8mOOPbeW3hPdUjisnqGc/gSvhz8GmNzqvSAnXWTWR5jf/YGqpHRtOEybyIymmGEToTSM+IL5+ARSCJLco6b2rdWdEJI0k9peAHxqodENFrNrmGbIbFMpTr2W2MyizcirbxB1Sdn10pOSdGFF0eIGmtJ0toEPrwIMqJefMbs756Ga2e25+DjzXVgOF4d7wMisCVpTFDVLVB/odkPw52nziuKQbq+qunN1OxvJWrn6l3z9xOxeQVY3ry8OD0kbeytpeTEbYHp7Qu2eYjYZUFkVa2WB6HOqSh4uDs9ludeyZRhYaMlB0g5FeUsqnZKLp1ivJt2+DnBL2K4QbBJgeTjNpOWm0zLBdK/rrjgoBUOXaXg3d1ruOGZIiUZ1hzsAPieibi1Nv0+0nN435A9+DbFMWl68oJTHxYEe1LXsLtPadIVWJGtTYyNfZTXhJc2BehiSbu8tTARupESesUKzxLkUCoIqH3byfEF6bFLVJTN5zCaVMQS6DwU17I3aXeq5PxXY79OGlbquF8iXY7pg9rIcuib/SkD7wOVU3hcrcV9qfhRwG6+UjDQNKZBl19T0Jd2/ZkKaDjB6s2tSnp7Y1P2fSK9VKLIBytUComHRZHuspGfQGTNbEazmxrgBvP/0cDysNxa22ONh21o28bdw8g28+sB5hqDPEA15pT9iXbzHYnqM8eg3LOd/Qon3CIZ71Gbqc86/4ZtPJDgBqSgo9lQv/kG0/BrgLNbnWJRnUM0V3b4g6Adkw7eYD98wBB8R3WeU8gpzPhvDRq8vsRQX0PwKOwoUZZ0byiruenLGr69ZzXa6KaDJFUPaqGrdJ4drBfdPRGq3jB/X5uRp4zT+BxeNwG5mUmQNAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;continuation sequence&quot;
        title=&quot;&quot;
        src=&quot;/static/678d094323c1155cfccdf1e8181cc5f9/1cfc2/continuation_sequence.png&quot;
        srcset=&quot;/static/678d094323c1155cfccdf1e8181cc5f9/3684f/continuation_sequence.png 225w,
/static/678d094323c1155cfccdf1e8181cc5f9/fc2a6/continuation_sequence.png 450w,
/static/678d094323c1155cfccdf1e8181cc5f9/1cfc2/continuation_sequence.png 900w,
/static/678d094323c1155cfccdf1e8181cc5f9/21482/continuation_sequence.png 1350w,
/static/678d094323c1155cfccdf1e8181cc5f9/d61c2/continuation_sequence.png 1800w,
/static/678d094323c1155cfccdf1e8181cc5f9/22284/continuation_sequence.png 2428w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;코루틴은 Continuation의 실행 가능한 단위(블록)라고 볼 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;두-번째-코루틴의-구조적-동시성&quot; style=&quot;position:relative;&quot;&gt;두 번째. 코루틴의 구조적 동시성&lt;a href=&quot;#%EB%91%90-%EB%B2%88%EC%A7%B8-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%A0%81-%EB%8F%99%EC%8B%9C%EC%84%B1&quot; aria-label=&quot;두 번째 코루틴의 구조적 동시성 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;코루틴간 하나의 block으로 감싸져있는 형태로 계층적인 구조를 갖도록하여, 생명주기와 에러 처리, 취소 전파를 통해 예측 가능하도록 하기 위함이다.&lt;br&gt;
그렇기 때문에, Coroutine이 손실되거나 누수되지 않도록 보장하며, 발생하는 오류도 제대로 보고되도록 보장할 수 있다.&lt;br&gt;
CoroutineContext의 Job이 아래와 같은 생명주기를 가지면서 코루틴의 구조적 동시성을 지원하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f7b102779d4c79b1c5e819da077b5173/bb2fd/coroutinehierarchy.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACUklEQVR42oWT227TQBCG+xK8AQ/IBRIXXPIkXMAVUiUkIhUEAiFRoKEFWpK0NGkS1/Epjb0+H9b7MXZbqZUKWBqt15r5/P8zu1tcP8Zg2pam1jRJhnY8stk52emC6ccTPm8fMHp7gvczIDxXaN2im5a20eiupmp6zBY3HtOaPpG2hjIhcVx0HBI5AdZsxdoNEQSJSkmiFG0EKjVGahsB3wYqhVk5aNvGWGPy0x2Wu8/JpwNMrUR50RdalsW34T5Hh7/wJhOK+ZxGasrFEvL8BlBrTFmh0wxTiKVwSj5/LeuxwErqMu/Tcimyzi3slU1TFJ00TFVRqlictXdbbuIp/ukbluMBzuglVTQTa2JPNzjiYjyaMJHw/eCq/YayqK4sS2KnrltN1+CyRPtL1M4L3KdPUK+eUa8dKrHcSo59seT7yVDiK6vNQgRKfi0KxVmndoswBM/rwwRr6sUcsw7wHj5gfv8eweNHYregyEK69g/PPjEYbjPY3+Z4sQeBT+PY0ssFbEIBitzr6C1X8scsoslkEGmIzhV1uiFOE1ReikIPL7rADde40ZqNHLFSjkyeZf84Nv08b3w3mk2ccXjm82Pqo1JNWhj2f7scLQLirKIo8n8Br5jmGtgSpan0zWdv7AqgJisbdg9tjmYbgQsw/xuwuVTYTa6P/ntLKNa+jFw+HCyJ84apHfFub8b74RmWr2TK/1N4y/KlwqUfM3cjAVb4UcZsFcpeEcjNudNyN5iqqCmlJ2Uua171UchehYosSciTlDhSpLG8y0+u99117IB/AEfq4LCDgMqSAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;coroutinehierarchy&quot;
        title=&quot;&quot;
        src=&quot;/static/f7b102779d4c79b1c5e819da077b5173/1cfc2/coroutinehierarchy.png&quot;
        srcset=&quot;/static/f7b102779d4c79b1c5e819da077b5173/3684f/coroutinehierarchy.png 225w,
/static/f7b102779d4c79b1c5e819da077b5173/fc2a6/coroutinehierarchy.png 450w,
/static/f7b102779d4c79b1c5e819da077b5173/1cfc2/coroutinehierarchy.png 900w,
/static/f7b102779d4c79b1c5e819da077b5173/bb2fd/coroutinehierarchy.png 1058w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;suspend fun start() {
    val userData = UserData()
    var parentJob: Job? = null
    var dbJob: Deferred&amp;lt;UserData&amp;gt;? = null
    var cacheJob: Job? = null
    shouldThrow&amp;lt;RuntimeException&amp;gt; {
        coroutineScope {
            parentJob = currentCoroutineContext()[Job]!!
            parentJob.isActive shouldBe true
            parentJob.isCancelled shouldBe false
            parentJob.isCompleted shouldBe false

            cacheJob = launch { saveInCache(userData) }
            dbJob = async { saveInDatabase(userData) }
            dbJob.await()
        }
    }.shouldHaveMessage(&amp;quot;commit 예외 발생&amp;quot;)

    parentJob!!.isActive shouldBe false
    parentJob.isCancelled shouldBe true
    parentJob.isCompleted shouldBe true
    dbJob!!.isActive shouldBe false
    dbJob.isCancelled shouldBe true
    dbJob.isCompleted shouldBe true
    cacheJob!!.isActive shouldBe false
    cacheJob.isCancelled shouldBe true
    cacheJob.isCompleted shouldBe true
}

suspend fun saveInDatabase(userData: UserData): UserData {
    return coroutineScope {
        commit()    // 예외 발생 !!!
        userData
    }
}

suspend fun saveInCache(userData: UserData): UserData {
    coroutineScope {
        val job1 = async { putKey() }
        val job2 = async { updateInMem() }

        try {
            awaitAll(job1, job2)
        } catch (e: Exception) {
            e.shouldBeInstanceOf&amp;lt;CancellationException&amp;gt;()
            e.message shouldBe &amp;quot;Parent job is Cancelling&amp;quot;
            throw e
        }
    }
    return userData
}

suspend fun commit() {
    throw RuntimeException(&amp;quot;commit 예외 발생&amp;quot;)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;commit()&lt;/code&gt; 메소드에서 런타임 예외가 발생하여 관계된 코루틴의 Job 상태들이 &lt;code class=&quot;language-text&quot;&gt;Cancelled&lt;/code&gt;로 전이된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5c40df9d2ee084131068ab736dbb5e81/45662/job_lifecycle.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 30.22222222222222%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABcElEQVR42l2QTUsbURSG84MstIX+AJfuui7Y7opEsLR140Zaav9AoSBoEDSUQg1FF0qkSC3EaEOJgqgBk1FkJpP5yuRzkjtz5+lNAtb0bF7O5Tnvee9JbBzesJa7JvNHx293sGsmRbPFx0KNd/kqu5qPaRhUdYOvpTIz2X3e/DjgzHZxTAvddfisbfLhMsVyJUPi+epvXqQKzH0pYjgNPMclXbzlUfqCB+kS7/MGfsPHsjzeHp/yJPuLyf08O9ot7XqL4o3GVO4Vj39O8/RongSqIhlzv+JeQOTZ2J5LVasgup3BK1GjTteuIYbQv5koEvREQBiKkeGdkYIGmAwCpF8nbLTo2x6h7xL5HrLugFo2ZBmx41EYNySWo43GCe3tJN2tl4jz76PBfo9YCKRSOfhB0MHSdbqmQe3bOnrqE9bmxv+G0VDCyh7N1Yc0VyYIDpaIW8rEtZHqBNJzVFKVWKlQfa9c4mphlsvkM8qLr/kLspevrR+NMTUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;alt text&quot;
        title=&quot;&quot;
        src=&quot;/static/5c40df9d2ee084131068ab736dbb5e81/1cfc2/job_lifecycle.png&quot;
        srcset=&quot;/static/5c40df9d2ee084131068ab736dbb5e81/3684f/job_lifecycle.png 225w,
/static/5c40df9d2ee084131068ab736dbb5e81/fc2a6/job_lifecycle.png 450w,
/static/5c40df9d2ee084131068ab736dbb5e81/1cfc2/job_lifecycle.png 900w,
/static/5c40df9d2ee084131068ab736dbb5e81/21482/job_lifecycle.png 1350w,
/static/5c40df9d2ee084131068ab736dbb5e81/45662/job_lifecycle.png 1410w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h1 id=&quot;해결방법&quot; style=&quot;position:relative;&quot;&gt;해결방법&lt;a href=&quot;#%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95&quot; aria-label=&quot;해결방법 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;1-supervisorjob-적용-&quot; style=&quot;position:relative;&quot;&gt;1. SupervisorJob 적용 👍&lt;a href=&quot;#1-supervisorjob-%EC%A0%81%EC%9A%A9-&quot; aria-label=&quot;1 supervisorjob 적용  permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@RestController
class TestController {
    // SupervisorJob을 사용하여 자식 코루틴의 실패가 부모에 영향을 주지 않도록 함
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    @GetMapping(&amp;quot;/test&amp;quot;)
    fun test() {
        scope.launch {
            // 예외가 발생해도 scope는 계속 활성 상태 유지
            throw RuntimeException(&amp;quot;예외 발생!&amp;quot;)
        }
    }
}

given(&amp;quot;간단한 SupervisorJob 테스트&amp;quot;) {
    `when`(&amp;quot;SupervisorJob을 사용한 스코프에서 자식이 실패하면&amp;quot;) {
        then(&amp;quot;다른 자식과 스코프는 영향받지 않는다&amp;quot;) {
            runBlocking {
                var child2Completed = false
                val scope = CoroutineScope(SupervisorJob())
                
                // 첫 번째 자식 - 실패
                scope.launch {
                    throw RuntimeException(&amp;quot;자식1 실패&amp;quot;)
                }
                
                // 두 번째 자식 - 정상 실행
                scope.launch {
                    delay(100)
                    child2Completed = true
                }.join()
                
                child2Completed shouldBe true
                scope.coroutineContext[Job]?.isActive shouldBe true
                
                scope.cancel()
            }
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;2-supervisorscope-사용&quot; style=&quot;position:relative;&quot;&gt;2. supervisorScope 사용&lt;a href=&quot;#2-supervisorscope-%EC%82%AC%EC%9A%A9&quot; aria-label=&quot;2 supervisorscope 사용 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@RestController
class TestController {
    private val scope = CoroutineScope(Dispatchers.IO)
    
    @GetMapping(&amp;quot;/test&amp;quot;)
    fun test() {
        scope.launch {
            supervisorScope {
                // supervisorScope 내부의 자식 코루틴 실패가 부모에 영향 없음
                throw RuntimeException(&amp;quot;예외 발생!&amp;quot;)
            }
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;코루틴 스코프 안에 코루틴 빌더가 한 개 더 생성되어야 해서 굳이 이렇게 해결하진 않을 것 같다.&lt;/p&gt;
&lt;h2 id=&quot;3-spring-suspend--코루틴-빌더&quot; style=&quot;position:relative;&quot;&gt;3. Spring suspend + 코루틴 빌더&lt;a href=&quot;#3-spring-suspend--%EC%BD%94%EB%A3%A8%ED%8B%B4-%EB%B9%8C%EB%8D%94&quot; aria-label=&quot;3 spring suspend  코루틴 빌더 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@RestController
class TestController {
    @GetMapping(&amp;quot;/test&amp;quot;)
    suspend fun test() {

        // 1. 컨텍스트 전환이 필요한 경우
        withContext(Dispatchers.IO) {
            // ...
        }

        // 2. 모든 자식이 완료될 때까지 대기, 하나라도 실패하면 모두 취소
        return coroutineScope {
            val data1 = async { fetchData1() }
            val data2 = async { fetchData2() }
            Result(data1.await(), data2.await())
        }

        // 3. 자식 실패가 다른 자식에 영향 없음
        supervisorScope {
            launch { .. }
            launch { .. }
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1a7390e309b46b0c93a3ab6807e4e075/960bd/suspendingFunctionInvoke.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABmElEQVR42k2S6XLbMAyE9SARL4AHCFKUbKdp7B6TH0n6/k/Upd1pO/OJA3G4xGKHC3O8HG1sPYmGPLLu2gbF4mMsKedcpfbAKXBmLtjnJNaxdQSW1foMXdu4ZJ5HN227VCWOzJmgiSXQFEMZ7jv49WGyGBdwRwgROsiK9FK66EBddc+iLrD1YTXBGKz+TniwkF5JbyBv3/fL2/jyPl4+Tq+/jq+f+8t7e/5Zzz/68xu1a9DXqNfYv8V+S1jbbTE+wvj0nkRyQbc+zhVt2679lFUTDPWT9kPawaU5SnZKJrDtjSPgA+UYHwkV0VJUpMM2cosZI22UkGKyFv79xPrFumAcz2yILTHyiDPSsvpgPK2rnxj/tDoUGNvYfyz4fIiRYkwFHWA8S4tJPMEY/390ffAnM79OsYOYa23bfh7j3HuX1nrfk1QbCFE7x85H58lhNM/o5O6gnrZXSylVEZWmdevjcm5jb207Dlw3qvZcFMYkI9SSiuY7yGd2RuCER0SYVIr2UlueaWnVgfCgwoMx6EboFp7+2jbhN0PxYzdUUsQlAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;suspendingFunctionInvoke&quot;
        title=&quot;&quot;
        src=&quot;/static/1a7390e309b46b0c93a3ab6807e4e075/1cfc2/suspendingFunctionInvoke.png&quot;
        srcset=&quot;/static/1a7390e309b46b0c93a3ab6807e4e075/3684f/suspendingFunctionInvoke.png 225w,
/static/1a7390e309b46b0c93a3ab6807e4e075/fc2a6/suspendingFunctionInvoke.png 450w,
/static/1a7390e309b46b0c93a3ab6807e4e075/1cfc2/suspendingFunctionInvoke.png 900w,
/static/1a7390e309b46b0c93a3ab6807e4e075/21482/suspendingFunctionInvoke.png 1350w,
/static/1a7390e309b46b0c93a3ab6807e4e075/d61c2/suspendingFunctionInvoke.png 1800w,
/static/1a7390e309b46b0c93a3ab6807e4e075/960bd/suspendingFunctionInvoke.png 6360w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Controller API가 suspend 함수인지 확인하여 &lt;code class=&quot;language-text&quot;&gt;reactor.core.publisher.Mono&amp;lt;T&gt;&lt;/code&gt;가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dc9bad17c701c74ca59ae02478e14599/b54cd/springcoroutine.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 18.666666666666668%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAw0lEQVR42jWPWW4CMRBE5z4RUkLA8b6Mx2Y2BsL971K4G/mjVOpFT1XDkT28dRDK4SotjEuY1wPLdmA/XtjuT6z7o/k/7o8XfMz848LYPML6xKL9z6/AEEpBcgajVdBS4XyRuAiF65+B1A4hTUi5Io4VU12aFxYB6ebj505AmofTWaCuC5LR0No0kGYYqcMoLcG6xunG++65zLyv84ZBhYhSMlyDiVZZKAtpPEJLcVt2rtuB2gZO1isaF7g+zdTq6/SNN6sxjDXLbldmAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;springcoroutine&quot;
        title=&quot;&quot;
        src=&quot;/static/dc9bad17c701c74ca59ae02478e14599/1cfc2/springcoroutine.png&quot;
        srcset=&quot;/static/dc9bad17c701c74ca59ae02478e14599/3684f/springcoroutine.png 225w,
/static/dc9bad17c701c74ca59ae02478e14599/fc2a6/springcoroutine.png 450w,
/static/dc9bad17c701c74ca59ae02478e14599/1cfc2/springcoroutine.png 900w,
/static/dc9bad17c701c74ca59ae02478e14599/21482/springcoroutine.png 1350w,
/static/dc9bad17c701c74ca59ae02478e14599/b54cd/springcoroutine.png 1662w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이때 사용되는 코루틴 스코프는 &lt;code class=&quot;language-text&quot;&gt;SupervisorJob&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;Dispatchers.Default&lt;/code&gt;로 구성되어 있기에, 예외가 발생해도 부모 스코프는 취소되지 않는다.&lt;/p&gt;
&lt;h2 id=&quot;4-예외-처리-&quot; style=&quot;position:relative;&quot;&gt;4. 예외 처리 👍&lt;a href=&quot;#4-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-&quot; aria-label=&quot;4 예외 처리  permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@RestController
class TestController {
    private val scope = CoroutineScope(Dispatchers.IO)
    
    @GetMapping(&amp;quot;/test&amp;quot;)
    fun test() {
        scope.launch {
            try {
                // business logic
                throw RuntimeException(&amp;quot;예외 발생!&amp;quot;)
            } catch (e: Exception) {
                // 예외를 처리하여 스코프 취소 방지
                logger.error(&amp;quot;Error occurred: &amp;quot;, e)
            }
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;Job간 예외가 전파되기 때문에 &lt;code class=&quot;language-text&quot;&gt;scope.launch {}&lt;/code&gt; 밖에서 예외를 잡으면 의미없다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;given(&amp;quot;코루틴 스코프 독립성 테스트&amp;quot;) {
    `when`(&amp;quot;launch 바깥에서 try-catch로 예외를 잡으려고 하면&amp;quot;) {
        then(&amp;quot;예외는 잡히지 않고 Job 계층으로 전파된다&amp;quot;) {
            val tryBlockException = AtomicBoolean(false)
            val handlerException = AtomicBoolean(false)
            
            runBlocking {
                val scope = CoroutineScope(Job() + CoroutineExceptionHandler { _, _ -&amp;gt;
                    handlerException.set(true)
                })
                
                // try-catch로 launch를 감싸도 예외는 잡히지 않음
                try {
                    scope.launch {
                        delay(50)
                        throw RuntimeException(&amp;quot;launch 내부 예외&amp;quot;)
                    }
                    // launch는 즉시 반환되므로 여기서 예외가 발생하지 않음
                } catch (e: Exception) {
                    tryBlockException.set(true)  // 실행되지 않음
                }
                
                delay(100)
                
                // 검증: try-catch는 예외를 잡지 못하고, ExceptionHandler가 처리
                tryBlockException.get() shouldBe false  // try-catch로 잡지 못함
                handlerException.get() shouldBe true    // CoroutineExceptionHandler가 처리
                scope.coroutineContext[Job]?.isCancelled shouldBe true
            }
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h1 id=&quot;마무리&quot; style=&quot;position:relative;&quot;&gt;마무리&lt;a href=&quot;#%EB%A7%88%EB%AC%B4%EB%A6%AC&quot; aria-label=&quot;마무리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이번 장애를 계기로 코루틴을 더 깊게 분석해보았다.&lt;br&gt;
실무에 적용된 코루틴 안티 패턴을 걷어내고 동료들에게 공유할 수 있도록 노력해야겠다.&lt;br&gt;
요즘 AI가 작성해주는 코드를 사용하게 되면서 이런 일이 쉽게 발생하는 것 같다. 여전히 딥 다이브하는 능력은 필수임을 잊지 말자.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[I/O 멀티플렉싱에 대해]]></title><description><![CDATA[멀티플렉싱 먼저 TCP…]]></description><link>https://jdalma.github.io/2025y/multiplexing/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/multiplexing/</guid><pubDate>Tue, 02 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;멀티플렉싱&quot; style=&quot;position:relative;&quot;&gt;멀티플렉싱&lt;a href=&quot;#%EB%A9%80%ED%8B%B0%ED%94%8C%EB%A0%89%EC%8B%B1&quot; aria-label=&quot;멀티플렉싱 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;먼저 TCP 소켓에 대해 이해하자.&lt;br&gt;
소켓은 네트워크에서 서버와 클라이언트, 두 개의 프로세스가 특정 포트를 통해 양방향 통신이 가능하도록 만들어 주는 추상화된 장치이다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;{srcIP, srcPort, destIP, destPort}&lt;/code&gt;로 이루어져 있다.&lt;br&gt;
(사용하는 입장에서는) 위의 정보가 유일함이 보장된다.&lt;br&gt;
하지만 실제 구현에서는 조금 다르다. 외부적으로 알릴 IP와 Port로 소켓을 만들고 해당 소켓을 리스닝 소켓으로 등록한다.&lt;br&gt;
&lt;strong&gt;리스닝 소켓으로 들어오는 클라이언트 연결 요청을 받고 수락한다면 클라이언트와 연결된 새로운 소켓이 생성된다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://vos.line-scdn.net/landpress-content-v2_954/1663603039179.png?updatedAt=1663603039000&quot; alt=&quot;alter&quot;&gt;&lt;/p&gt;
&lt;p&gt;한 개의 서버에 여러 클라이언트가 접속 요청을 보낸다면 서버는 어떻게 수용할 수 있을까?&lt;br&gt;
문제를 해결하기 위해 아래의 방법들이 있다.&lt;/p&gt;
&lt;h2 id=&quot;멀티프로세싱multiprocessing-기반-서버&quot; style=&quot;position:relative;&quot;&gt;멀티프로세싱(multiprocessing) 기반 서버&lt;a href=&quot;#%EB%A9%80%ED%8B%B0%ED%94%84%EB%A1%9C%EC%84%B8%EC%8B%B1multiprocessing-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B2%84&quot; aria-label=&quot;멀티프로세싱multiprocessing 기반 서버 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&apos;프로세스를 다수 생성&apos;하는 방식으로 서비스를 제공한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://vos.line-scdn.net/landpress-content-v2_954/1663603730433.png?updatedAt%253D1663603731000&quot; alt=&quot;Alt text&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;부모 프로세스는 리스닝 소켓으로 accept 함수를 호출해서 연결 요청을 수락한다.&lt;br&gt;
이때 얻는 소켓의 파일 디스크립터(클라이언트와 연결된 연결 소켓)를 자식 프로세스를 생성해 넘겨준다.&lt;br&gt;
자식 프로세스는 전달받은 파일 디스크립터를 바탕으로 서비스를 제공한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;br&gt;
클라이언트가 연결하는 만큼 프로세스를 복사하기 때문에 리소스를 많이 사용한다.&lt;br&gt;
프로세스는 서로 독립적인 메모리 공간을 갖기 때문에 정보 교환이 어렵다.&lt;br&gt;
이 단점을 멀티스레딩으로 처리하면 해결할 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;멀티스레딩multithreading-기반-서버&quot; style=&quot;position:relative;&quot;&gt;멀티스레딩(multithreading) 기반 서버&lt;a href=&quot;#%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%94%A9multithreading-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B2%84&quot; aria-label=&quot;멀티스레딩multithreading 기반 서버 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&apos;스레드를 다수 생성&apos;하는 방식으로 서비스를 제공한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://vos.line-scdn.net/landpress-content-v2_954/1663604015232.png?updatedAt%253D1663604016000&quot; alt=&quot;Alt text&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;메인 스레드는 리스닝 소켓으로 accept 함수를 호출해서 연결 요청을 수락한다.&lt;br&gt;
이때 얻는 소켓의 파일 디스크립터(클라이언트와 연결된 연결 소켓)를 별도 워커 스레드를 생성해 넘겨준다.&lt;br&gt;
워커 스레드는 전달받은 파일 디스크립터를 바탕으로 서비스를 제공한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;br&gt;
한 개의 프로세스 내에 다수의 스레드가 존재하기 때문에 하나의 스레드에서 발생한 문제가 전체에 영향을 미쳐 나머지 다수의 스레드에 영향을 끼칠 수 있다.&lt;br&gt;
일정 크기의 스레드를 생성해 풀로 관리하며 운영할 수 있지만 클라이언트의 요청마다 스레드를 무한정 생성할 수 없기 때문에 많은 수의 요청을 동시에 처리할 수 없다.&lt;br&gt;
또한 스레드들이 idle한 시간동안 불필요하게 블로킹되기에 낭비가 많다.&lt;/p&gt;
&lt;p&gt;I/O 멀티플렉싱 기법을 사용한다면 각 클라이언트마다 별도 스레드를 이용하는게 아니라,&lt;br&gt;
&lt;strong&gt;하나의 스레드에서 다수의 클라이언트에 연결된 소켓(파일 디스크립터)을 관리하면서 소켓에 이벤트 (&lt;code class=&quot;language-text&quot;&gt;read&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;write&lt;/code&gt;)가 발생할 때만 해당 이벤트를 처리하도록 구현해서 더 적은 리소스를 사용하도록 개선할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;멀티플렉싱multiplexing-기반-서버&quot; style=&quot;position:relative;&quot;&gt;멀티플렉싱(multiplexing) 기반 서버&lt;a href=&quot;#%EB%A9%80%ED%8B%B0%ED%94%8C%EB%A0%89%EC%8B%B1multiplexing-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B2%84&quot; aria-label=&quot;멀티플렉싱multiplexing 기반 서버 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&apos;입출력 대상을 묶어서 관리&apos;하는 방식으로 서비스를 제공한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;입출력 다중화&lt;/strong&gt;란?&lt;br&gt;
하나의 프로세스 혹은 스레드에서 입력과 출력을 모두 다룰 수 있는 기술을 말한다.&lt;br&gt;
커널에서는 하나의 스레드가 여러 개의 소켓을 핸들링 할 수 있는 &lt;code class=&quot;language-text&quot;&gt;select&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;poll&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;epoll&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;io_uring&lt;/code&gt;과 같은 시스템 콜을 제공하고 있다.&lt;br&gt;
&lt;strong&gt;그럼에도 지금까지 하나의 클라이언트에 대한 입출력만 처리할 수 있었던 이유는, 입출력 함수가 블록되면 입출력 데이터가 준비될 때까지 무한정 블록돼 여러 클라이언트의 입출력을 처리할 수 없었기 때문이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I/O 멀티플렉싱 기법&lt;/strong&gt;을 사용하면, 비록 입출력 함수 자체는 여전히 블록하는 것으로 작동하지만, 입출력 함수를 호출하기 전에 어떤 파일에서 입출력이 준비됐는지를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;블로킹 I/O 에서는 프로세스(스레드)가 하나의 소켓에 대해 &lt;code class=&quot;language-text&quot;&gt;read&lt;/code&gt; 함수를 호출(시스템 콜)을 하면 데이터가 도착할 때 까지 기다린다.&lt;br&gt;
패킷이 도착하면 커널 내 버퍼에 복사되고, 이 데이터를 사용하기 위해서는 사용자 공간에 다시 복사해야 한다.&lt;br&gt;
&lt;strong&gt;이 작업들이 끝날 때까지 시스템 콜이 반환되지 않는다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://vos.line-scdn.net/landpress-content-v2_954/1663604384989.png?updatedAt%253D1663604385000&quot; alt=&quot;Alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;I/O 멀티플렉싱 시스템 콜들은 여러 파일 디스크립터를 감시하여, 어떤 파일에서 읽기, 쓰기 또는 예외 이벤트가 발생했는지를 확인하는 시스템 콜이다.&lt;br&gt;
즉, 이벤트(입력, 출력, 에러)가 준비된 파일에 대해 입출력을 수행하기 때문에 &lt;strong&gt;무한정 대기해야하는 블록이 발생하지 않을 것이라는게 보장된다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;파일 디스크립터 준비&lt;/strong&gt;: 감시할 파일 디스크립터를 fd_set이라는 구조체에 등록합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 대기&lt;/strong&gt;: select 함수를 호출하여 등록된 파일 디스크립터 중 이벤트가 발생할 때까지 대기합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 처리&lt;/strong&gt;: 이벤트가 발생한 파일 디스크립터에 대해 적절한 입출력 작업을 수행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;아래는 I/O 멀티플렉싱의 select와 epoll의 주요 차이점을 정리한 마크다운 테이블입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;특성&lt;/th&gt;
&lt;th&gt;select&lt;/th&gt;
&lt;th&gt;epoll&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;감시 대상 등록&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;매 호출 시마다 전체 FD 집합 전달&lt;/td&gt;
&lt;td&gt;한 번 등록 후 커널 내에서 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;이벤트 감지 방식&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;전체 파일 디스크립터 순회 검사&lt;/td&gt;
&lt;td&gt;이벤트 발생한 FD만 알림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;최대 파일 디스크립터 수&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;일반적으로 1024개 제한&lt;/td&gt;
&lt;td&gt;사실상 제한 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU 및 메모리 오버헤드&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;매 호출마다 FD 집합 복사 및 전체 검사 발생&lt;/td&gt;
&lt;td&gt;변화 있는 FD만 처리, 복사 및 검사 비용 매우 적음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;지원 운영체제&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;대부분 OS 지원&lt;/td&gt;
&lt;td&gt;리눅스 커널 2.6 이상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;성능 및 확장성&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;제한적, 대규모 연결에 비효율적&lt;/td&gt;
&lt;td&gt;대규모 연결 처리에 매우 효율적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API 복잡도&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;단순&lt;/td&gt;
&lt;td&gt;상대적으로 복잡&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;요약하면, &lt;code class=&quot;language-text&quot;&gt;epoll&lt;/code&gt;은 대규모 소켓 처리와 높은 효율성이 요구되는 리눅스 환경에서 &lt;code class=&quot;language-text&quot;&gt;select&lt;/code&gt; 대비 월등한 성능과 확장성을 제공하도록 설계된 시스템 호출이다.&lt;br&gt;
반면, select는 낮은 이식성과 확장성 문제로 인해 소규모 혹은 범용 환경에서만 주로 사용된다.&lt;/p&gt;
&lt;p&gt;Java NIO가 시스템 수준의 I/O 멀티플렉싱 기술(특히 select, poll, epoll 등)을 추상화하여 자바 개발자가 쉽게 비동기 입출력을 구현할 수 있게 해준다&lt;/p&gt;
&lt;h1 id=&quot;java-nio&quot; style=&quot;position:relative;&quot;&gt;Java NIO&lt;a href=&quot;#java-nio&quot; aria-label=&quot;java nio permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;채널과-버퍼&quot; style=&quot;position:relative;&quot;&gt;채널과 버퍼&lt;a href=&quot;#%EC%B1%84%EB%84%90%EA%B3%BC-%EB%B2%84%ED%8D%BC&quot; aria-label=&quot;채널과 버퍼 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;kernel 버퍼를 직접 핸들링 할 수 없어 복사 오버헤드가 존재했지만, ByteBuffer가 등장하면서 개선됐다. (zero copy)&lt;/li&gt;
&lt;li&gt;FileChannel, DatagramChaneel, SocketChannel, ServerSocketChannel&lt;/li&gt;
&lt;li&gt;채널은 양방향으로 사용하기 때문에 버퍼에 데이터를 쓰다가 이후 데이터를 읽어야 한다면 &lt;code class=&quot;language-text&quot;&gt;filp()&lt;/code&gt;을 호출해서 버퍼를 쓰기 모드에서 읽기 모드로 전환해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;clear()&lt;/code&gt;로 전체 버퍼를 지울 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;논블로킹non-blocking-io&quot; style=&quot;position:relative;&quot;&gt;논블로킹(non-blocking) I/O&lt;a href=&quot;#%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9non-blocking-io&quot; aria-label=&quot;논블로킹non blocking io permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Java NIO에서는 논블로킹 I/O를 사용할 수 있다.&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;h2 id=&quot;셀렉터&quot; style=&quot;position:relative;&quot;&gt;셀렉터&lt;a href=&quot;#%EC%85%80%EB%A0%89%ED%84%B0&quot; aria-label=&quot;셀렉터 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Java NIO에는 여러 개의 채널에서 이벤트(예: 연결 생성, 데이터 도착 등)를 모니터링할 수 있는 셀렉터가 포함돼 있기 때문에 하나의 스레드로 여러 채널을 모니터링할 수 있다.&lt;/li&gt;
&lt;li&gt;내부적으로 SelectorProvider에서 운영체제와 버전에 따라 사용 가능한 &lt;strong&gt;멀티플렉싱 기술을 선택해 사용한다&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하나 이상의 채널을 셀렉터에 등록하고 &lt;code class=&quot;language-text&quot;&gt;select()&lt;/code&gt;를 호출하면, 등록된 채널 중 이벤트 준비가 완료된 하나 이상의 채널이 생길 때까지 블록된다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Java NIO에는 이 외에도 더 많은 클래스와 컴포넌트가 있지만 &lt;strong&gt;채널&lt;/strong&gt;과 &lt;strong&gt;버퍼&lt;/strong&gt;, &lt;strong&gt;셀렉터&lt;/strong&gt;가 API의 핵심이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;ServerSocketChannel channel = ServerSocketChannel.open();
channel.bind(new InetSocketAddress(&amp;quot;localhost&amp;quot;, 8080));
channel.configureBlocking(false); // 논블로킹 모드로 변경

Selector selector = Selector.open();

// 하나의 Selector는 여러 Channel을 관리할 수 있습니다
// 각 Channel마다 독립적으로 관심 이벤트(interest ops)를 설정합니다
// 이 코드는 해당 channel에 대해 OP_READ만 등록한 것입니다
SelectionKey key = channel.register(selector, SelectionKey.OP_READ); // 채널을 셀렉터에 등록&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;ServerSocketChannel을 열고 Channel에 IP와 Port를 바인딩한다.&lt;br&gt;
Selector에 Channel을 등록하고, 준비된 채널의 집합을 받는다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ServerSocketChannel&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;역할: 클라이언트의 연결 요청을 **듣고(listen) 수락(accept)**하는 채널&lt;/li&gt;
&lt;li&gt;생성: 한 번만 생성 (서버당 포트당 1개)&lt;/li&gt;
&lt;li&gt;이벤트: OP_ACCEPT (연결 요청 감지)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SocketChannel&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;역할: 실제 클라이언트와 데이터를 주고받는 채널&lt;/li&gt;
&lt;li&gt;생성: 클라이언트마다 생성 (클라이언트 수만큼)&lt;/li&gt;
&lt;li&gt;이벤트: OP_READ, OP_WRITE (데이터 읽기/쓰기)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SelectionKey&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;역할: Channel과 Selector의 연결 관계 표현, 관심 이벤트 관리, 핸들러 저장&lt;/li&gt;
&lt;li&gt;생성: Channel을 Selector에 등록하는 경우 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Set&amp;lt;SelectionKey&amp;gt; selectedKeys = selector.selectedKeys();
Iterator&amp;lt;SelectionKey&amp;gt; keyIterator = selectedKeys.iterator();
 
while(keyIterator.hasNext()) {
     
    SelectionKey key = keyIterator.next();
 
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
 
    keyIterator.remove();
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;key의 상태에 따라 분기된다.&lt;/p&gt;
&lt;h1 id=&quot;reactor-패턴-이벤트-핸들링-패턴&quot; style=&quot;position:relative;&quot;&gt;Reactor 패턴 (이벤트 핸들링 패턴)&lt;a href=&quot;#reactor-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%95%B8%EB%93%A4%EB%A7%81-%ED%8C%A8%ED%84%B4&quot; aria-label=&quot;reactor 패턴 이벤트 핸들링 패턴 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4/%ED%96%89%EB%8F%99_%EA%B4%80%EB%A0%A8.md#observer-pattern&quot;&gt;Observer 패턴 참고&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reactor: 무한 반복문을 실행해 이벤트가 발생할 때까지 대기하다가 이벤트가 발생하면 처리할 수 있는 핸들러에게 디스패치합니다. 이벤트 루프라고도 부릅니다.&lt;br&gt;
핸들러: 이벤트를 받아 필요한 비즈니스 로직을 수행합니다.&lt;br&gt;
세부적인 구현은 상황에 맞게 변경할 수 있습니다.&lt;br&gt;
따라서 세부 구현 내용에 초점을 맞추기보다는 리소스에서 발생한 이벤트를 처리하기까지의 과정과, 그 과정에서 Reactor와 핸들러가 어떤 역할을 하는지 이해하는 것이 Reactor 패턴을 이해하는 데 더 많은 도움이 됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/reactor/tree/main/src/main/java/org/example&quot;&gt;예제&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Override
public void run() {
    try {
        while (true) {
            selector.select();
            Set&amp;lt;SelectionKey&amp;gt; selected = selector.selectedKeys();
            for (SelectionKey selectionKey : selected) {
                dispatch(selectionKey);
            }
            selected.clear();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h1 id=&quot;eventloop&quot; style=&quot;position:relative;&quot;&gt;EventLoop&lt;a href=&quot;#eventloop&quot; aria-label=&quot;eventloop permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;이벤트 루프(event loop)는 동시성(concurrency)을 제공하기 위한 프로그래밍 모델 중 하나로,&lt;br&gt;
특정 이벤트가 발생할 때까지 대기하다가 이벤트가 발생하면 디스패치해 처리하는 방식으로 작동합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 560px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f35be33cdc63b3fca328dcd390bb4ba7/ac282/netty_eventloop.webp&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 96.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/webp;base64,UklGRlgAAABXRUJQVlA4IEwAAACQAwCdASoUABMAPtFgqE+oJSOiKAgBABoJaQAAPS/0oEGI3aQAAP7xiTh7btuqZqeuzFag3O4IUVk45CoKOyVYhTlUztxE2KUCgAAA&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;netty eventloop&quot;
        title=&quot;&quot;
        src=&quot;/static/f35be33cdc63b3fca328dcd390bb4ba7/ac282/netty_eventloop.webp&quot;
        srcset=&quot;/static/f35be33cdc63b3fca328dcd390bb4ba7/d7e55/netty_eventloop.webp 225w,
/static/f35be33cdc63b3fca328dcd390bb4ba7/8626f/netty_eventloop.webp 450w,
/static/f35be33cdc63b3fca328dcd390bb4ba7/ac282/netty_eventloop.webp 560w&quot;
        sizes=&quot;(max-width: 560px) 100vw, 560px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;em&gt;출처: &lt;a href=&quot;https://mark-kim.blog/netty_deepdive_1/&quot;&gt;Netty Deep Dive&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;내부적으로 &lt;code class=&quot;language-text&quot;&gt;Selector&lt;/code&gt;를 이용해 특정 이벤트가 발생할 때까지 대기하다가 이벤트가 발생하면 적절한 핸들러로 이벤트를 전달(dispatch)해 처리하는 역할을 무한 루프로 실행해 반복하던 Reactor가 바로 이벤트 루프이다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;이벤트가 발생하기를 대기한다.&lt;/li&gt;
&lt;li&gt;이벤트가 발생하면 처리할 수 있는 핸들러에 이벤트를 디스패치한다.&lt;/li&gt;
&lt;li&gt;핸들러에서 이벤트를 처리한다.&lt;/li&gt;
&lt;li&gt;다시 1~3 단계를 반복한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.linecorp.com/ko/blog/do-not-block-the-event-loop-part1&quot;&gt;이벤트 루프를 블록하면 안되는 이유 - 파트1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.linecorp.com/ko/blog/do-not-block-the-event-loop-part2&quot;&gt;이벤트 루프를 블록하면 안되는 이유 - 파트2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mark-kim.blog/understanding-non-blocking-io-and-nio/&quot;&gt;사례를 통해 이해하는 네트워크 논블로킹 I/O와 Java NIO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mark-kim.blog/netty_deepdive_1/&quot;&gt;Netty Deep Dive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/Netty/%EB%84%A4%ED%8B%B0%EC%9D%98%20%ED%95%B5%EC%8B%AC%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8.md&quot;&gt;Netty의 핵심 컴포넌트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/Netty/ChannelHandler%EC%99%80%20ChannelPipeline.md&quot;&gt;ChannelHandler와 ChannelPipeline&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[GraphQL 이해하고 사용하기]]></title><description><![CDATA[GraphQL 기반 서비스를 처음으로 접하면서 다음과 같은 고민을 한적이 있다.      이 글에서는 Netflix DGS Framework를 기준으로 이러한 고민들을 해소해보고자 한다. 이 글을 통해 아래의 내용을 학습할 수 있다. GraphQL…]]></description><link>https://jdalma.github.io/2025y/graphql/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/graphql/</guid><pubDate>Fri, 24 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GraphQL 기반 서비스를 처음으로 접하면서 다음과 같은 고민을 한적이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;스키마를 어떤 기준으로 작성해야 할까?&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;조회 쿼리가 너무 많이 발생하는데 어떻게 개선할 수 있을까?&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;Selection Set의 필드는 어떤 원리로 채워지는 걸까?&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;/graphql은 누가 라우팅할까?&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;예외는 어떻게 처리할까?&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 글에서는 Netflix DGS Framework를 기준으로 이러한 고민들을 해소해보고자 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이 글을 통해 아래의 내용을 학습할 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GraphQL API의 호출 구조 이해&lt;/li&gt;
&lt;li&gt;Query의 &lt;code class=&quot;language-text&quot;&gt;Selection set&lt;/code&gt; 동작 원리&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Data Loaders&lt;/code&gt;를 통한 N+1 문제 해결 방법&lt;/li&gt;
&lt;li&gt;GraphQL의 예외 처리 방식&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;company-employee-예제로-보는-graphql&quot; style=&quot;position:relative;&quot;&gt;Company-Employee 예제로 보는 GraphQL&lt;a href=&quot;#company-employee-%EC%98%88%EC%A0%9C%EB%A1%9C-%EB%B3%B4%EB%8A%94-graphql&quot; aria-label=&quot;company employee 예제로 보는 graphql permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;간단한 회사-직원 관계&lt;code class=&quot;language-text&quot;&gt;(Company 1:N Employee)&lt;/code&gt; 예제를 통해 GraphQL의 데이터 조회와 변경 작업을 살펴보자.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GraphQL에서는 &lt;code class=&quot;language-text&quot;&gt;schema&lt;/code&gt;를 통해 클라이언트와 서버 간의 통신을 명시적으로 정의하여 안전한 타입 체킹, 자동 문서화, 코드 생성 등을 가능하게 한다.&lt;br&gt;
아래의 코드 블록에서 &lt;code class=&quot;language-text&quot;&gt;# schema&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;*.graphql&lt;/code&gt;에 작성된 schema 정보임을 뜻한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;dgsquery와-dgsdata를-이용한-조회&quot; style=&quot;position:relative;&quot;&gt;@DgsQuery와 @DgsData를 이용한 조회&lt;a href=&quot;#dgsquery%EC%99%80-dgsdata%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%A1%B0%ED%9A%8C&quot; aria-label=&quot;dgsquery와 dgsdata를 이용한 조회 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6d805fe4e752719e6a93498fae60b617/c23ad/schema.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABfElEQVR42mWS647aMBBG8/7P1H+V0BYWrQoESIhawobESQi5Oiabyyk2pRet5dFItr+ZMzO2pmni05r+MeMm+r6nbmqGYeCpGceRPM+ZzWYsl0uKosDSF1JK4jg2FkYh3Uf3CPRbKDOJ/dXm9csr3tyjqx735/MZx3HwfZ/b7Wb0ls7cdZ0h0CRDNzDKEe4a4++7/FnytnzDdVxkKUnchCAM2G63bDYb9vu9Ib1er1jz+ZzL5YJs7g8vCWInaOOWLMiQgUTFCuEIXNfF3tokIiH1UpNctcpQ6VKzLCOKIqzD4UDTNKRpSiQiUjc1Jb4f32nihiqoCDYBnucRhiFt09In/aMl42T6eDweEUKYM6uuaxOwqiqKskCVijEfmep7+dkAH1D8KHj59sLO2VHmJf53nyiOjE73cbVaocEM4XMomlBjK6X+TPa5+qJHhYo2bFFnhcqVSa4DLRYLTqeT6Z8mtfQk//s60+dvY/z4157vde9t22a9XhtCHfAXU2dfVS2uoPYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;schema&quot;
        title=&quot;&quot;
        src=&quot;/static/6d805fe4e752719e6a93498fae60b617/1cfc2/schema.png&quot;
        srcset=&quot;/static/6d805fe4e752719e6a93498fae60b617/3684f/schema.png 225w,
/static/6d805fe4e752719e6a93498fae60b617/fc2a6/schema.png 450w,
/static/6d805fe4e752719e6a93498fae60b617/1cfc2/schema.png 900w,
/static/6d805fe4e752719e6a93498fae60b617/c23ad/schema.png 1193w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 이미지의 의미는 아래와 같다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@DgsComponent
class CompanyFetcher {

    @DgsQuery
    fun findAllCompanies(@InputArgument filter: CompanyFilter): Connection&amp;lt;Company&amp;gt; {
        ...
    }

    @DgsData(parentType = &amp;quot;CompanyResponse&amp;quot;, field = &amp;quot;staffs&amp;quot;)
    fun staff(environment: DgsDataFetchingEnvironment): CompletableFuture&amp;lt;List&amp;lt;Staff&amp;gt;&amp;gt; {
        val dataLoader = environment.getDataLoader&amp;lt;String, List&amp;lt;Employee&amp;gt;&amp;gt;(EmployeeDataLoader.EMPLOYEE_LOADER_NAME)
        return dataLoader.load(..)
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;graphql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;# schema
type Query {
    findAllCompanies(filter: CompanyFilter): CompanyConnection!
}

type CompanyConnection {
    totalCount: Int!
    responses: [CompanyResponse!]!
}

type CompanyResponse {
    id: ID!
    alias: String!
    state: CompanyState!,
    employees: [Employee]
}

type Employee {
    id: Int,
    companyId: String
    name: String
}

# request
query {
    findAllCompanies(filter: {
        statusIn: [NORMAL]
    }) {
        totalCount
        responses {
            id
            alias
            state
            employees {
                name
            }
        }
    }
}

# response
{
  &amp;quot;data&amp;quot;: {
    &amp;quot;findAllCompanies&amp;quot;: {
      &amp;quot;totalCount&amp;quot;: 3,
      &amp;quot;responses&amp;quot;: [
        {
          &amp;quot;id&amp;quot;: &amp;quot;별빛기술&amp;quot;,
          &amp;quot;alias&amp;quot;: &amp;quot;STAR&amp;quot;,
          &amp;quot;state&amp;quot;: &amp;quot;NORMAL&amp;quot;,
          &amp;quot;employees&amp;quot;: [
            {
              &amp;quot;name&amp;quot;: &amp;quot;하늘솔&amp;quot;
            },
            {
              &amp;quot;name&amp;quot;: &amp;quot;달빛솔&amp;quot;
            }
          ]
        },
        { ... }
      ]
    }
  }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;주요 특징은 &lt;code class=&quot;language-text&quot;&gt;CompanyResponse&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;@DgsQuery&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;@DgsData&lt;/code&gt;의 응답을 합쳐서 만든다는 것이다.&lt;br&gt;
(이 기능을 지원하는 BatchLoader에 대해서는 뒤에서 자세히 다룬다.)&lt;br&gt;
GraphQL의 가장 큰 특징인 &lt;code class=&quot;language-text&quot;&gt;selection set&lt;/code&gt;을 필요한 것만 선택하여 지정할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;selection set&lt;/code&gt;이란, &lt;strong&gt;GraphQL 쿼리에서 어떤 필드를 선택할지 지정하는 중괄호(&lt;code class=&quot;language-text&quot;&gt;{}&lt;/code&gt;) 내부의 집합이다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;graphql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;query {
    findAllCompanies(filter: {
        ...
    }) {
        # &amp;lt;selections set&amp;gt;
        totalCount
        responses {
            id
            alias
            state
            employees {
                name
            }
        }
        # &amp;lt;/selection set&amp;gt;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;selection set&gt;&lt;/code&gt; 영역에 있는 최상위 필드 또는 중첩된 필드를 필요한 것만 선택하여 응답을 받을 수 있기에 오버페칭과 언더페칭을 해결할 수 있다.&lt;br&gt;
당연히 schema에 type으로 응답 타입의 필드에 작성된 필드를 기준으로 선택해야 한다.&lt;/p&gt;
&lt;h2 id=&quot;dgsmutation을-이용한-조작&quot; style=&quot;position:relative;&quot;&gt;@DgsMutation을 이용한 조작&lt;a href=&quot;#dgsmutation%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%A1%B0%EC%9E%91&quot; aria-label=&quot;dgsmutation을 이용한 조작 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@DgsComponent
class CompanyMutation {
    @DgsMutation
    fun createCompany(createRequest: CreateCompanyRequest): Company {
        val company = ...
        return company
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;graphql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;# schema
type Mutation {
    createCompany(createCompanyRequest: CreateCompanyRequest!): CompanyResponse!
}
input CreateCompanyRequest {
    id: String!
    alias: String!
    employees: [EmployeeInput!]!
}
input EmployeeInput {
    name: String
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 schema를 정의하고 그에 맞게 Mutation을 등록한 후에, Spring Web API 작성하듯이 &lt;code class=&quot;language-text&quot;&gt;@DgsComponent&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;@DgsMutation&lt;/code&gt;을 작성하면 호출할 수 있게 된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;graphql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;# request
mutation {
 createCompany(createCompanyRequest: {
   id: &amp;quot;company-123&amp;quot;
   alias: &amp;quot;신규회사&amp;quot; 
   employees: [
     {
       name: &amp;quot;김철수&amp;quot;
     },
     {
       name: &amp;quot;이영희&amp;quot;  
     }
   ]
 }) {
   id
   alias
   state
   employees {
     id
     name
   }
 }
}

# response
{
  &amp;quot;data&amp;quot;: {
    &amp;quot;createCompany&amp;quot;: {
      &amp;quot;id&amp;quot;: &amp;quot;company-123&amp;quot;,
      &amp;quot;alias&amp;quot;: &amp;quot;신규회사&amp;quot;,
      &amp;quot;state&amp;quot;: &amp;quot;NORMAL&amp;quot;,
      &amp;quot;employees&amp;quot;: [
        {
          &amp;quot;id&amp;quot;: 9,
          &amp;quot;name&amp;quot;: &amp;quot;김철수&amp;quot;
        },
        {
          &amp;quot;id&amp;quot;: 10,
          &amp;quot;name&amp;quot;: &amp;quot;이영희&amp;quot;
        }
      ]
    }
  }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;정리&quot; style=&quot;position:relative;&quot;&gt;정리&lt;a href=&quot;#%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;정리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;각 어노테이션의 주요 특징과 사용 목적을 정리하면 다음과 같다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;어노테이션&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;th&gt;사용 위치&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;code class=&quot;language-text&quot;&gt;@DgsQuery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데이터 조회 작업&lt;/td&gt;
&lt;td&gt;최상위 레벨&lt;/td&gt;
&lt;td&gt;1. Query 타입 구현 (1:1 관계)&lt;br&gt;2. 읽기 전용 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;@DgsData&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;타입의 필드 구현&lt;/td&gt;
&lt;td&gt;중첩 필드&lt;/td&gt;
&lt;td&gt;1. 특정 Query 타입 내부의 필드를 구현 (1:1 또는 1:N 관계)&lt;br&gt;2. &lt;code class=&quot;language-text&quot;&gt;parentType&lt;/code&gt; 지정 필요&lt;br&gt;3. DataLoader와 함께 사용하여 N+1 문제 해결&lt;br&gt;4. DgsDataFetchingEnvironment로 부모 컨텍스트 접근 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;@DgsMutation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데이터 변경 작업&lt;/td&gt;
&lt;td&gt;최상위 레벨&lt;/td&gt;
&lt;td&gt;1. Mutation 타입 구현 및 (1:1 관계)&lt;br&gt;2. 생성/수정/삭제 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;GraphQL의 내부 동작 원리와 최적화 방법에 대해 더 자세히 알아보자.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;graphql-분석하기&quot; style=&quot;position:relative;&quot;&gt;GraphQL 분석하기&lt;a href=&quot;#graphql-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0&quot; aria-label=&quot;graphql 분석하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/6c960/overview.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABtElEQVR42m2Sb5OiMAzG/f7fa1/sK29uOM9zFQRUEAvInxYKlN+VOrpzu5dMppk0efqkyYovoo2m7EvyPn+Z6AT+1ef91ztvP97wfI+0Sf/JWawZGlZPoNnqIkVfoEa1BJjn+RVvu5Z7c2c0I2YyzlzdPL/IpCr9BHxKoQvKqsT3fYqicICT1aRKCM4BvelfBJ4PmvkB7lc+q7ZtkVKitXasFkCpJFmaUcua6/3KqToRNzHJPSFX+VcOL8CgDlhtt1vW6zWe53HLbu5vojgiyzIaq/vLnkAExPeY7X7LSZwwg2HUI7rT7ny279eWoRCCw+HAZrMhPIZEIsLbeAQfASITiFIwTROztvRt3aAHknPC5XThml7p2555etz5uQVsmgallGtzSV6mNZnJ/U0RFlz+XFCpYsxH+rJnyAciLyL4GVAeS0xrqLKKqZz4CD++T3lZkQXQiR32+fcZsRPIoyTaRehQU4c1aZCij5ohtQ/4Ebfwxm6/+z/gYIZnENPZFZGGWc2OzdK6aWystn5nE0ZcnA7iW/x9bdqhdbvYTZ3bRzU9THYS2cvP8+mrh990DUIJ/gKoJACBQRT3iQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;overview&quot;
        title=&quot;&quot;
        src=&quot;/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/1cfc2/overview.png&quot;
        srcset=&quot;/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/3684f/overview.png 225w,
/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/fc2a6/overview.png 450w,
/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/1cfc2/overview.png 900w,
/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/21482/overview.png 1350w,
/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/d61c2/overview.png 1800w,
/static/d4ed9a3f45a5f88a7cd5427f1d1a654c/6c960/overview.png 2135w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;graphql-request는-다른-spring-request와-어떻게-구분될까&quot; style=&quot;position:relative;&quot;&gt;GraphQL Request는 다른 Spring Request와 어떻게 구분될까?&lt;a href=&quot;#graphql-request%EB%8A%94-%EB%8B%A4%EB%A5%B8-spring-request%EC%99%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B5%AC%EB%B6%84%EB%90%A0%EA%B9%8C&quot; aria-label=&quot;graphql request는 다른 spring request와 어떻게 구분될까 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DispatcherServlet은 요청이 들어오면 아래의 과정을 거친다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;API 요청을 처리할 수 있는 &lt;code class=&quot;language-text&quot;&gt;핸들러&lt;/code&gt;를 조회한다.&lt;/li&gt;
&lt;li&gt;핸들러를 실행시킬 수 있는 &lt;code class=&quot;language-text&quot;&gt;핸들러 어댑터&lt;/code&gt;를 조회한다.&lt;/li&gt;
&lt;li&gt;핸들러 어댑터를 이용하여 &lt;code class=&quot;language-text&quot;&gt;핸들러를 실행&lt;/code&gt;한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;GraphQL도 DispatcherServlet을 통해 실행되기에 Spring Boot의 Auto Configuration을 통해 GraphQL의 요청을 처리하기 위해 필요한 구성들을 자동으로 등록한다.&lt;/p&gt;
&lt;h3&gt;1. GraphQL 요청을 처리하는 핸들러 찾기&lt;/h3&gt;
&lt;p&gt;아래는 GraphQL 요청을 처리할 수 있는 &lt;code class=&quot;language-text&quot;&gt;핸들러&lt;/code&gt;를 조회할 때 사용되는 조건이다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;GraphQlHttpRequestPredicate&lt;/code&gt; 구현체는 DispatcherServlet에서 Handler를 조회할 때 사용된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public final class GraphQlRequestPredicates {
    ...
    private static class GraphQlHttpRequestPredicate implements RequestPredicate {

        @Override
        public boolean test(ServerRequest request) {
            return httpMethodMatch(request, HttpMethod.POST)
                    &amp;amp;&amp;amp; contentTypeMatch(request, this.contentTypes)
                    &amp;amp;&amp;amp; acceptMatch(request, this.acceptedMediaTypes)
                    &amp;amp;&amp;amp; pathMatch(request, this.pattern);
        }
        ...
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;POST&lt;/code&gt; 요청&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;content-type&lt;/code&gt; : application/json || applciation/graphql&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;media-type&lt;/code&gt; : application/json || application/graphql-response+json&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/graphql&lt;/code&gt; 경로&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위 4가지 조건에 모두 만족한다면 &lt;code class=&quot;language-text&quot;&gt;GraphQlHttpHandler&lt;/code&gt;를 반환한다.&lt;br&gt;
(GraphQlHttpHandler는 &lt;code class=&quot;language-text&quot;&gt;GraphQlWebMvcAutoConfiguration&lt;/code&gt;에서 자동으로 등록된 빈이다.)&lt;/p&gt;
&lt;h3&gt;2. GraphQlHttpHandler를 이용해 핸들러 어댑터 찾기&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 470px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/992d95b0ba43a2df0fa44094f68c2863/f96db/handlerAdapter.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABvklEQVR42k3R2W7TQBQGYD9OVYkmXmPP6hlvMW6ICmHJoqRuQEBaoYIEEgLUCx7853jShYvPZ86M5veM7Ymyx+LiK968/YHF6+/YX92hv/yD7fY3+YX3+zscPv3FavUTtj6gqK//c3DK5gYVSaa38Po4wGcrcNsVOFQKW6Oxs+aJMdjkGislsZRH74SgfphTri6lwnqYC0N4m/EpZsOmVy+wLiUaxtEJiedCEYmWdLRhprWrZZLARhEMbR5YkpM68LGmLM+kDDse4Vsr8aUW2MiMMGfJJvRm5mwUv0cn4RleBiPnwj/DjHwcnyAJY3gRk4gahWldomkqdG2Dlur8fAppCkSyQKwKJLpyhp7V57DzBUJd4yQWUEEITZ6FKbwgnIBlEpa+R54X4NJCKAupC1JCUphwLLSpIGiu5QJzUkcxOn+Ehq47phyfeCNKzTOODyzAXkzQixRXdO0dS3DJqedDz2icurWe1q6DU1T+GJJONaFrnlGG/xDoRyk4U+joL06NpZMaVNqioGpVjnIYk8dK83leIsyUCxpRSHAfdgykR5IKChVgMseEHyVMO8M4FeaxOlyDUR9ETyd7CPwHCSJABvXPO0EAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;handlerAdapter&quot;
        title=&quot;&quot;
        src=&quot;/static/992d95b0ba43a2df0fa44094f68c2863/f96db/handlerAdapter.png&quot;
        srcset=&quot;/static/992d95b0ba43a2df0fa44094f68c2863/3684f/handlerAdapter.png 225w,
/static/992d95b0ba43a2df0fa44094f68c2863/fc2a6/handlerAdapter.png 450w,
/static/992d95b0ba43a2df0fa44094f68c2863/f96db/handlerAdapter.png 470w&quot;
        sizes=&quot;(max-width: 470px) 100vw, 470px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;DispatcherServlet에서는 4개의 HandlerAdapter를 보유하고 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RequestMappingHandlerAdapter (일반적인 스프링 어노테이션 기반 Web API)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HandlerFunctionAdapter (함수형 스타일)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;HttpRequestHandlerAdapter (HTTP 요청 직접 처리)&lt;/li&gt;
&lt;li&gt;SimpleControllerHandlerAdapter (레거시 Controller 인터페이스 구현체 지원)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GraphQL은 Spring에서 지원하는 &lt;code class=&quot;language-text&quot;&gt;RouterFunction&lt;/code&gt;을 통해 함수형으로 라우팅 기능을 구현해놓았기에 &lt;code class=&quot;language-text&quot;&gt;HandlerFunctionAdapter&lt;/code&gt;를 사용한다.&lt;br&gt;
GraphQL은 4개의 RouterFunction이 존재하며, 일반적인 요청은 &lt;code class=&quot;language-text&quot;&gt;GraphQlHttpHandler&lt;/code&gt;가 처리한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/07efeffeef66478b1559af1e0670f32c/2e92a/routerFunction.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABpElEQVR42o2SW2/aQBCF/fP7kD+Q9zYXQSCplJaU5gYKMdBQaCSwIWZt1tddYwOqEtJInM4uIOUhqTrSkXa+PbPSrI6xWgFvSdVy+YxYpAgTAZlmeM/7Wgb+UY9PT3AYgzUcYcJ9/E8Z+WyObCN1zueLtTYsSnO4fgyZv+/ZajafwwhFhq38JIUfS0wiqVdt2QF2qh4+fHWwW/fh8ghBMgWne04+5d/OKh4kEkYkErgTj1aaIJL0EP1VnEowzwXjHE4g4IQS4yAmnwuPfCKbIpYkIXWvvGFCW2Q5jINyB3sFEx8PTZxd9DF4iFA46eITsb1iC7XGCINRgOLnHvaPWpqXT+/JF+LL9z71Tc32j+7wa8Bh1EwXNZPh+pah2XFhO4Hula6Itbse+jbH5Y2DepN85hi1WwfWA0fjh+qZ5lcNhsGQHkz4GNyxELojzKYCLy9/oNnY1myRS6gU5TKCz4bELWLpOgW/FwgUo/lMhOvYnJ+UUC0XUSkcoP/zDkuKyvlxCd9KBc3s+54e7tzUUSke4oxk9bqa+RSpKvkUa19f6tj8BbF+j0vvI9eFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;routerFunction&quot;
        title=&quot;&quot;
        src=&quot;/static/07efeffeef66478b1559af1e0670f32c/1cfc2/routerFunction.png&quot;
        srcset=&quot;/static/07efeffeef66478b1559af1e0670f32c/3684f/routerFunction.png 225w,
/static/07efeffeef66478b1559af1e0670f32c/fc2a6/routerFunction.png 450w,
/static/07efeffeef66478b1559af1e0670f32c/1cfc2/routerFunction.png 900w,
/static/07efeffeef66478b1559af1e0670f32c/21482/routerFunction.png 1350w,
/static/07efeffeef66478b1559af1e0670f32c/d61c2/routerFunction.png 1800w,
/static/07efeffeef66478b1559af1e0670f32c/2e92a/routerFunction.png 2508w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;todo&quot; style=&quot;position:relative;&quot;&gt;TODO&lt;a href=&quot;#todo&quot; aria-label=&quot;todo permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;executionstrategy&quot; style=&quot;position:relative;&quot;&gt;ExecutionStrategy&lt;a href=&quot;#executionstrategy&quot; aria-label=&quot;executionstrategy permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HandlerFunctionAdapter가 GraphQlHttpHandler를 통하여 GraphQl의 요청을 처리하게 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;중첩 필드가 실행될 때 재귀적으로 실행하는 것을 확인했음. 더 분석 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;batchloader-규칙&quot; style=&quot;position:relative;&quot;&gt;BatchLoader 규칙&lt;a href=&quot;#batchloader-%EA%B7%9C%EC%B9%99&quot; aria-label=&quot;batchloader 규칙 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// DataLoaderHelper

private void assertResultSize(List&amp;lt;K&amp;gt; keys, List&amp;lt;V&amp;gt; values) {
    assertState(keys.size() == values.size(), () -&amp;gt; &amp;quot;The size of the promised values MUST be the same size as the key list&amp;quot;);
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;각 companyId에 대해 빈 리스트라도 반환해야 함&lt;/li&gt;
&lt;li&gt;결과는 반드시 입력 keys와 같은 순서를 유지해야 함&lt;/li&gt;
&lt;li&gt;반환 타입이 List&amp;#x3C;List&lt;Staff&gt;&gt;이어야 함 (각 companyId당 하나의 List)&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 입력 keys
companyIds = [1, 2, 3]

// 올바른 결과 (size = 3)
[
    [staff1, staff2],  // company1의 직원들
    [],                // company2의 직원 (없음)
    [staff3]           // company3의 직원
]

// 잘못된 결과 (size = 4 != keys.size)
[staff1, staff2, staff3, staff4]&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;DgsContext&lt;/li&gt;
&lt;li&gt;DataLoaderRegistry&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[2025년 기록]]></title><description><![CDATA[올리브영 기술 블로그 : 비동기 요청-응답 패턴으로 풀어낸 발주 서비스 개발기 동기적으로 처리되는 API로 인해 너무 느린 문제와 다른 서비스에서 해당 API…]]></description><link>https://jdalma.github.io/2025y/review/</link><guid isPermaLink="false">https://jdalma.github.io/2025y/review/</guid><pubDate>Wed, 01 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;올리브영-기술-블로그--비동기-요청-응답-패턴으로-풀어낸-발주-서비스-개발기&quot; style=&quot;position:relative;&quot;&gt;올리브영 기술 블로그 : 비동기 요청-응답 패턴으로 풀어낸 발주 서비스 개발기&lt;a href=&quot;#%EC%98%AC%EB%A6%AC%EB%B8%8C%EC%98%81-%EA%B8%B0%EC%88%A0-%EB%B8%94%EB%A1%9C%EA%B7%B8--%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9A%94%EC%B2%AD-%EC%9D%91%EB%8B%B5-%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C-%ED%92%80%EC%96%B4%EB%82%B8-%EB%B0%9C%EC%A3%BC-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C%EA%B8%B0&quot; aria-label=&quot;올리브영 기술 블로그  비동기 요청 응답 패턴으로 풀어낸 발주 서비스 개발기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;동기적으로 처리되는 API로 인해 너무 느린 문제와 다른 서비스에서 해당 API를 호출해야 할 상황이 발생했다.&lt;br&gt;
유연하게 해결하기 위해 카프카를 이용한 비동기 아키텍처를 고민해본 내용이다.&lt;br&gt;
(카프카를 썻을 때 장점 &lt;code class=&quot;language-text&quot;&gt;메세지 유실 방지&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;재시도 정책/실패 대처&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;분산 처리로 인한 성능 향상&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;request-reply 아키텍처&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Requestor : 요청 메세지를 보내고, 응답 메시지를 기다린다.&lt;/li&gt;
&lt;li&gt;Supplier : 요청 메시지를 받아서 응답 메세지를 보낸다.&lt;/li&gt;
&lt;li&gt;요청자는 Point-to-Point나 Publish-Subscribe 채널로 구성한다.&lt;/li&gt;
&lt;li&gt;응답자는 대체로 응답 데이터를 브로드캐스팅 할 필요가 없으므로 Point-to-Point로 한정되는 것이 일반적이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;응답 처리 방식&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;동기 블록 방식 : 요청자가 응답을 받을 때까지 대기&lt;/li&gt;
&lt;li&gt;비동기 콜백 방식 : 요청을 보낸 후 콜백을 등록하고, 별도의 스레드가 응답 메시지를 대기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;async-request-reply 아키텍처&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트가 백엔드에 작업을 요청하고, 즉시 응답 받은 뒤 나중에 결과를 확인할 수 있도록 하는 구조&lt;/li&gt;
&lt;li&gt;클라이언트가 요청하면 서버는 작업을 큐 등에서 비동기로 처리하고, 응답(202 Accepted)을 통해 “작업 접수됨”을 안내&lt;/li&gt;
&lt;li&gt;Location 헤더와 Retry-After 헤더를 통해 결과를 조회할 수 있는 엔드포인트와 폴링 주기를 전달하는 방법&lt;/li&gt;
&lt;li&gt;클라이언트의 최초 요청에 자원을 조회할 수 있는 식별자를 반환할 수 없는 구조라면 이 아키텍처는 못함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;request-reply 아키텍처를 지원하는 카프카에서 제공하는 ReplyingKafkaTemplate을 사용하면 쉽게 구현 가능하지만, 요청 정보를 메모리에 올려놓고 사용하기 때문에 유실 가능성이 있다.&lt;/p&gt;
&lt;p&gt;두 아키텍처를 합쳐서 해결했다고 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트(백오피스 등)가 발주 요청을 API로 전송하면,&lt;/li&gt;
&lt;li&gt;서버는 **요청 이력 ID(예: 전표번호 또는 요청키)**를 생성해 DB에 저장한 뒤,&lt;/li&gt;
&lt;li&gt;즉시 요청 이력 ID를 응답으로 반환한다.&lt;/li&gt;
&lt;li&gt;이후, 실제 발주,검증 등 비동기 처리는 내부적으로 Kafka Consumer에서 진행되어, 처리 결과(성공/실패 등)가 DB에 업데이트된다&lt;/li&gt;
&lt;li&gt;클라이언트는 요청 이력 ID를 활용해 &lt;code class=&quot;language-text&quot;&gt;요청 결과 조회 API&lt;/code&gt;를 호출하여, DB에 반영된 처리 결과(진행 상태, 성공여부, 실패 사유 등)를 안전하게 확인한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;발주 처리에 성공하게 되면 Reply Topic에 공급되고, 실패되면 Dead Letter Topic에 공급되어 DLT Listener가 슬랙 알림/실패 이력 적재 등 처리 후 다시 Reply Topic에 공급한다.&lt;br&gt;
의아한 점은 클라이언트는 Reply Topic에 의해 응답을 받는것이 아니라 API Polling으로 결과를 조회하는데 Reply Topic이 왜 존재하는지 의문이다.&lt;/p&gt;
&lt;h1 id=&quot;코틀린-코루틴&quot; style=&quot;position:relative;&quot;&gt;코틀린 코루틴&lt;a href=&quot;#%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4&quot; aria-label=&quot;코틀린 코루틴 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;실무에서 코루틴 장애를 경험하게 되어서 원인을 확실히 이해하고 코루틴을 확실히 짚고 넘어가고 싶어 읽어보았다.&lt;br&gt;
&lt;a href=&quot;https://jdalma.github.io/2025y/coroutinescope/&quot;&gt;API가 왜 멈췄을까? 코루틴 이해하기&lt;/a&gt;글을 참고하라.&lt;/p&gt;
&lt;p&gt;단순히 사용하는 방법만 알려주지 않고 코루틴의 메커니즘까지 설명해줘서 좋았다.&lt;br&gt;
실제로 어떻게 중단되고 재개되는지 알 수 있고, 이 중단 함수를 통해서 스레드를 어떻게 효율적으로 사용하게 되는지 이해할 수 있게 됐다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Continuation 메커니즘&lt;/li&gt;
&lt;li&gt;CoroutineScope, CoroutineBuilder&lt;/li&gt;
&lt;li&gt;CoroutineContext (Job, Dispatchers)&lt;/li&gt;
&lt;li&gt;Job을 통한 예외 전파&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;어떤 원리로 성능이 개선된건지 몰라서 답답했는데 이 책을 읽고 안개가 조금 걷힌 느낌이다.&lt;br&gt;
추가로 Sequnece, Channel, Flow도 설명하기에, 코틀린을 효과적으로 사용하고 싶다면 꼭 읽어야 할 책이다.&lt;/p&gt;
&lt;h1 id=&quot;무신사-기술-블로그-무진장-힘들었지만-무진장-성장한-개발-이야기&quot; style=&quot;position:relative;&quot;&gt;무신사 기술 블로그: 무진장 힘들었지만 무진장 성장한 개발 이야기&lt;a href=&quot;#%EB%AC%B4%EC%8B%A0%EC%82%AC-%EA%B8%B0%EC%88%A0-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%AC%B4%EC%A7%84%EC%9E%A5-%ED%9E%98%EB%93%A4%EC%97%88%EC%A7%80%EB%A7%8C-%EB%AC%B4%EC%A7%84%EC%9E%A5-%EC%84%B1%EC%9E%A5%ED%95%9C-%EA%B0%9C%EB%B0%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0&quot; aria-label=&quot;무신사 기술 블로그 무진장 힘들었지만 무진장 성장한 개발 이야기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;이벤트 피크 타임의 &lt;strong&gt;분당 매출 1억 원, 분당 주문 약 1만 건&lt;/strong&gt;을 안정적으로 처리하는 것&lt;br&gt;
이벤트 전시 페이지에만 &lt;strong&gt;분당 약 400만 건의 요청(RPM&lt;/strong&gt;, Requests Per Minute**)** 초로 환산하면 대략 약 6만 7천 건의 요청(RPS, Requests Per Second)
&lt;a href=&quot;https://medium.com/musinsa-tech/%EB%AC%B4%EC%A7%84%EC%9E%A5-%ED%9E%98%EB%93%A4%EC%97%88%EC%A7%80%EB%A7%8C-%EB%AC%B4%EC%A7%84%EC%9E%A5-%EC%84%B1%EC%9E%A5%ED%95%9C-%EA%B0%9C%EB%B0%9C-%EC%9D%B4%EC%95%BC%EA%B8%B0-e445888579a9&quot;&gt;무진장 힘들었지만 무진장 성장한 개발 이야기&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;첫 번째. 전시 페이지&lt;/h2&gt;
&lt;p&gt;막대한 읽기(Read) 트래픽을 효율적이고 안정적으로 처리하면서, 핵심적인 &lt;strong&gt;쓰기(Write)&lt;/strong&gt; 작업인 주문 시스템을 보호하는 것이 목표&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;읽기(Read)와 쓰기(Write)의 분리, CQRS 아키텍처 도입&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;모놀리식 구조에서는 이벤트가 시작되면 대량의 읽기 요청으로 인해 커넥션 풀이 고갈되어 중요한 쓰기 작업(주문 처리) 불가능해지고 전체 서비스가 마비되는 상황이 반복되었다.&lt;/li&gt;
&lt;li&gt;이 문제를 &lt;code class=&quot;language-text&quot;&gt;CQRS&lt;/code&gt; 패턴으로 해결하려 한다.&lt;/li&gt;
&lt;li&gt;Redis를 이용하여 이벤트에 필요한 모든 상품 데이터(상품명, 가격, 할인가, 이미지 URL 등)를 사전에 가공하여 &lt;strong&gt;머티리얼라이즈드 뷰(Materialized View)&lt;/strong&gt; 형태로 별도의 조회 전용 데이터 저장소를 사용한다.&lt;/li&gt;
&lt;li&gt;읽기 모델의 &lt;strong&gt;최종적 일관성&lt;/strong&gt;은 CDC로 해결. 이로 인해 발생하는 몇 초간의 데이터 지연은, 서비스 전체가 마비될 수 있는 위험을 감수하는 것보다 훨씬 합리적인 비즈니스 &lt;strong&gt;트레이드오프(Trade-off)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis 데이터 압축 최적화&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Redis 도입으로 데이터베이스 병목은 해결했지만, 엄청난 양의 데이터 이동으로 인해 &lt;strong&gt;네트워크 대역폭&lt;/strong&gt; 과 &lt;strong&gt;Redis 메모리&lt;/strong&gt; 사용량 문제가 발생했다.&lt;/li&gt;
&lt;li&gt;그렇기에 압축 최적화를 진행, 압축률과 압축/압축해제 속도, CPU 사용률들을 고려해서 압축률이 약간 낮지만 압도적으로 빠른 압축 해제 속도와 낮은 CPU 점유율 → LZ4 알고리즘&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;캐시 전략&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;콜드 스타트&lt;/strong&gt; : 캐시가 &lt;strong&gt;완전히 비어있는 상태&lt;/strong&gt;에서 시작되어, 초기에는 모든 요청이 캐시 미스(Cache Miss)가 발생하는 문제&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;캐시 스템피드&lt;/strong&gt; : 인기 있는 캐시 키가 &lt;strong&gt;동시에 만료&lt;/strong&gt;되었을 때, 여러 요청이 &lt;strong&gt;동일한 데이터를 동시에 조회&lt;/strong&gt;하면서 모두 DB에 접근하는 현상&lt;/li&gt;
&lt;li&gt;예방 방법 : 캐시 워밍 업, 캐시 만료 시간 분산, 확률적 TTL 갱신, 다단계 캐시, DB 쿼리 비용이 락 대기 비용보다 비싸다면 분산 락도 고려해볼만 함, 멀티 버전 캐시 → 핵심은 일관성을 어느정도로 허용할 수 있는지가 중요하다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 시작 수십분 전, 예열 스크립트를 실행하여 가공된 최종 데이터를 캐시에 미리 채워놓기로 해결&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;두 번째. 주문/재고/쿠폰 시스템 고도화&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/musinsa-tech/%EB%82%98%EC%95%BC-%EC%A3%BC%EB%AC%B8-%EC%A3%BC%EB%AC%B8%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EB%8F%84%EC%A0%84%EA%B3%BC-%EC%84%B1%EC%9E%A5-%EC%9D%B4%EC%95%BC%EA%B8%B0-744b4bece5b8&quot;&gt;&lt;strong&gt;나야, 주문 - 주문시스템의 도전과 성장 이야기&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;문제점&lt;/h3&gt;
&lt;p&gt;하나의 데이터베이스를 공유하는 모놀리식 아키텍처 한계에 부딪혔다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;주요 도메인들이 하나의 어플리케이션 내에 통합되어 있었기 때문에 복잡하고 유지보수하기 어렵다.&lt;/li&gt;
&lt;li&gt;DB 의존도가 높아 이벤트로 인해 쓰기 요청이 몰리면 서버가 다운되는 일이 잦았다.
&lt;ul&gt;
&lt;li&gt;주문 시 재고 처리를 위해 DB Lock으로 인한 병목도 존재함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;개선 내용&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;지속적인 모니터링으로 슬로우 쿼리 개선&lt;/li&gt;
&lt;li&gt;레디스 분산 락으로 DB Lock 방식의 한계를 극복
&lt;ul&gt;
&lt;li&gt;읽기 요청은 스케일 아웃을 통해 문제를 해결할 수 있었지만, 쓰기 요청은 여러 도메인에서 한 데이터베이스를 사용했기에 쓰기 요청에 대한 병목은 계속 존재함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일반 MVC 구조에서 DDD 구조 도입&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;MSA 도입&lt;/h3&gt;
&lt;p&gt;주문 시스템은 여러 비즈니스와 연계되어 있어 API 통신의 안정성과 트랜잭션 일관성을 유지할 수 있는 설계가 필수적이였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;장애 전파 방지 및 SAGA 패턴을 통한 분산 트랜잭션 관리&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;MSA로 전환하면서 많은 기능이 DB 기반에서 API 중심으로 변경되면서, 특정 API 서비스에 장애가 발생하면 다른 서비스로 전파되는 문제가 있었다.&lt;/li&gt;
&lt;li&gt;이때 서킷 브레이커 패턴을 적용하여 장애가 발생한 서비스에 대한 호출을 차단했다.&lt;/li&gt;
&lt;li&gt;데이터 일관성은 SAGA 패턴으로 실패 보상 트랜잭션을 활용하여 서비스간 의존도를 줄이고, 확장성과 안정성을 강화할 수 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 720px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2d0dc81319033281fa78c058ad07f258/bc514/lambda.webp&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 97.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/webp;base64,UklGRogAAABXRUJQVlA4IHwAAADwBACdASoUABQAPtFgqk+oJaOiKAgBABoJaQAALnZhcLJVsbeHoQ15dAb+HTgtiAAA/vYJto4lizaS0z/BXST2xXIahQ8tsnHRTR2H+hLkLYNIFqBD5wwxBh6Ca8i5HzRvgYaT/Pwu4g8u1A9QNek0DAGtjX5Rg/QmAAAA&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sns.webp&quot;
        title=&quot;&quot;
        src=&quot;/static/2d0dc81319033281fa78c058ad07f258/bc514/lambda.webp&quot;
        srcset=&quot;/static/2d0dc81319033281fa78c058ad07f258/d7e55/lambda.webp 225w,
/static/2d0dc81319033281fa78c058ad07f258/8626f/lambda.webp 450w,
/static/2d0dc81319033281fa78c058ad07f258/bc514/lambda.webp 720w&quot;
        sizes=&quot;(max-width: 720px) 100vw, 720px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이벤트 기반 아키텍처로의 도약, 리소스 분산&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;주문 처리의 성능 최적화를 위해 필수 수행 영역과 비필수 수행 영역을 구분하여, 비필수 영역은 SNS, SQS, Lambda 비동기 처리로 전환했다.&lt;/li&gt;
&lt;li&gt;SNS → (팬아웃) SQS 구독 → 람다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메세지 중복 문제 (멱등성 설계 필요)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;람다 메세지 중복 문제는 Redis의 SETNX를 사용하여 분산 락을 통해 중복 메세지를 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오류 처리와 장애 대응을 위해 DLQ를 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결과적으로 주문 처리 시스템의 안정성과 성능은 크게 개선되었지만, 이후 연계된 서버리스 기능들에 대해 모니터링과 트러블슈팅 복잡성이 증가하는 새로운 고민이 생김&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kafka 도입&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;비동기 유즈케이스가 추가될 때 마다 람다가 계속 늘어나서 모니터링과 트러블 슈팅 복잡성이 증가했다.&lt;/li&gt;
&lt;li&gt;카프카를 도입하여 람다 컨슈머를 자바 컨슈머로 변환했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;세 번째. 실시간 재고 시스템의 안정성 및 확장성 강화&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/musinsa-tech/%EC%9E%AC%EA%B3%A0-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%9D%98-%EC%A7%84%ED%99%94%EC%99%80-%ED%98%81%EC%8B%A0-%EC%A7%80%EC%86%8D%EC%A0%81%EC%9D%B8-%EA%B0%9C%EC%84%A0%EC%9D%84-%ED%86%B5%ED%95%9C-%EC%95%88%EC%A0%95%EC%84%B1%EA%B3%BC-%ED%99%95%EC%9E%A5%EC%84%B1-%EA%B0%95%ED%99%94-cb851f1ff782&quot;&gt;&lt;strong&gt;재고 서비스의 진화와 혁신: 지속적인 개선을 통한 안정성과 확장성 강화&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;사용자 인증, 재고확인, 결제완료확인 등 최소한의 필수 검증만 수행한 뒤, 주문완료 이벤트를 Kafka 토픽에 발행&lt;/li&gt;
&lt;li&gt;그리고 즉시 사용자에게는 ‘주문이 완료되었습니다’라는 화면을 보여줍니다. 사용자는 빠른 시간에 쾌적한 응답&lt;/li&gt;
&lt;li&gt;그 후, ‘재고’, ‘쿠폰’, ‘결제’, ‘알림’ 등 각각의 마이크로서비스들이 주문완료 이벤트를 구독(subscribe)하여 각자의 역할을 비동기적으로, 그리고 독립적으로 수행&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;한정된 수량의 상품을 구매하기 위해 수천, 수만 명의 사용자가 동시에 몰릴 때, 단순한 &lt;code class=&quot;language-text&quot;&gt;UPDATE inventory SET quantity = quantity - 1&lt;/code&gt; 쿼리는 데이터베이스의 락 경합(Lock Contention)과 데드락(Deadlock)을 유발하며 시스템 전체를 마비시키는 주범이 된다.&lt;br&gt;
성능과 데이터 정합성을 모두 지키기 위해 &lt;strong&gt;다층 방어&lt;/strong&gt; 전략을 구축했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1차. 캐시 계층 : Redis를 활용한 비관적 재고 검사&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;사용자의 구매의사가 확정되지 않은 상태(장바구니에 담거나 주문서 페이지에 진입하는 경우)는 레디스의 원자적 연산을 통해 관리&lt;/li&gt;
&lt;li&gt;구매 가능성이 낮은 대다수의 트래픽을 효과적으로 걸러냄&lt;/li&gt;
&lt;li&gt;Redis의 데이터 타입은 hash를 이용하였으며 hash를 이용할 경우 장점은 스캔 시 value도 함께 조회하여 데이터 조회에 O(n)으로 처리 가능&lt;/li&gt;
&lt;li&gt;Redis에 적재한 재고 데이터는 데일리 대사를 진행하고 병렬처리 방식으로 전체 재고의 대사를 진행&lt;/li&gt;
&lt;li&gt;INCR/DECR을 활용하여 stock:product_sku_123과 같은 키의 값을 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2차. 분산락 : Redlock을 이용한 임계 영역 보호&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 최종적으로 ‘결제하기’ 버튼을 눌러 구매 의사를 확정한 순간, 이제는 데이터 정합성을 보장해야 한다.&lt;/li&gt;
&lt;li&gt;데이터베이스 재고를 차감하는 코드 블록을 분산 락으로 감싸놓았기에, 사용자가 결제하기 버튼을 눌러 구매 의사를 확정한 순간 특정 상품에 대한 락을 획득한 스레드만이 코드 블록에 진입할 수 있도록 했다.&lt;/li&gt;
&lt;li&gt;이를 통해 동일 상품에 대한 동시 재고 차감 시도를 직렬화하여, 레이스 컨디션(Race Condition)을 원천적으로 방지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3차. 데이터베이스 계층 : 낙관적 락을 통한 최종&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;분산 락이 완벽하게 동작하지 않을 가능성은 언제나 존재하기에 낙관적 락을 사용&lt;/li&gt;
&lt;li&gt;실패한다면 재시도할 수 있도록 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;네 번째. 대용량 트래픽을 위한 쿠폰 시스템 개선&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/musinsa-tech/%EB%AC%B4%EC%8B%A0%EC%82%AC-%EC%84%B1%EC%9E%A5%EA%B3%BC-%ED%95%A8%EA%BB%98-%EA%B1%B0%EB%8C%80%ED%95%B4%EC%A0%B8%EC%98%A8-600%EC%A4%84%EC%A7%9C%EB%A6%AC-%EC%BF%A0%ED%8F%B0-%EC%BF%BC%EB%A6%AC%EC%99%80%EC%9D%98-%EC%95%84%EB%A6%84%EB%8B%A4%EC%9A%B4-%EC%9D%B4%EB%B3%84-e689d7d932b5&quot;&gt;&lt;strong&gt;무신사 성장과 함께 거대해져온 600줄짜리 쿠폰 쿼리와의 아름다운 이별&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 600줄 쿼리는 사용자와 장바구니에 담긴 상품들에 대해 적용 가능한 모든 쿠폰을 실시간으로 계산하는 로직&lt;br&gt;
사용자 정보, 상품, 브랜드, 카테고리, 프로모션 등 수많은 테이블을 &lt;code class=&quot;language-text&quot;&gt;JOIN&lt;/code&gt;하는 이 쿼리는 이벤트 기간에 어김없이 데이터베이스 CPU 사용률을 100%로 치솟게 만드는 주범&lt;br&gt;
상품 상세 페이지에서 최적의 쿠폰을 실시간으로 제공해야하는 요구가 생기면서 이 쿼리를 개선해야 했음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;‘실시간 계산’에서 ‘사전 계산(Pre-computation)’으로의 전환 작업&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;쿠폰 인덱서(Coupon Indexer) 도입&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;쿠폰 발급, 프로모션 생성, 사용자 등급 변경 등 쿠폰 적용 조건에 영향을 미치는 모든 이벤트가 발생할 때마다 이를 감지하여 동작하는 백그라운드 서비스를 만듦&lt;/li&gt;
&lt;li&gt;이 인덱서는 모든 쿠폰의 적용 규칙을 미리 분석하고 인덱싱하는 역할을 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis를 활용한 머티리얼라이즈드 뷰&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;인덱서의 계산 결과는 Redis에 단순하고 비정규화된 데이터 구조로 저장된다.&lt;/li&gt;
&lt;li&gt;예를 들어, 특정 쿠폰을 사용할 수 있는 사용자 ID 목록을 담은 &lt;code class=&quot;language-text&quot;&gt;Set&lt;/code&gt; (&lt;code class=&quot;language-text&quot;&gt;coupons:id:applicable_users&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;특정 상품에 적용 가능한 쿠폰 ID 목록을 담은 &lt;code class=&quot;language-text&quot;&gt;Set&lt;/code&gt; (&lt;code class=&quot;language-text&quot;&gt;products:id:applicable_coupons&lt;/code&gt;) 등을 미리 만들어 둡니다.&lt;/li&gt;
&lt;li&gt;변화가 많지 않고 자주 요청되는 데이터는 캐시된 정보를 사용하도록 변경 (쿠폰 유형, 회원 등급)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;연산의 주체 변경&lt;/strong&gt; : DB 연산을 최소화하고 애플리케이션으로 변경
&lt;ul&gt;
&lt;li&gt;Redis에 몇 번의 간단한 Set 연산을 통해 적용 가능한 모든 쿠폰 목록을 정확하게 찾아낼 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;생각해볼-점&quot; style=&quot;position:relative;&quot;&gt;생각해볼 점&lt;a href=&quot;#%EC%83%9D%EA%B0%81%ED%95%B4%EB%B3%BC-%EC%A0%90&quot; aria-label=&quot;생각해볼 점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;CQRS를 통해 읽기 부하를 분리하였지만 추가적인 고민은 더 있었다.
&lt;ul&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;람다로 EDA와 비슷한 비동기를 구현하였지만 관리 포인트가 너무 많이 늘어났기에 카프카로 변경했다.
&lt;ul&gt;
&lt;li&gt;메세지 중복 문제 해결 → 멱등성 설계(DB 제약조건 등), 분산 락&lt;/li&gt;
&lt;li&gt;메시지 유실 또는 오류, 장애 대응을 위한 추가적인 방법 → 아웃박스 패턴, DLQ, 공급/소비 전략 설정, 사가, 이벤트소싱&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분산 락과 원자적 연산으로 동시성 지키기
&lt;ul&gt;
&lt;li&gt;Lettuce의 SETNX
&lt;ul&gt;
&lt;li&gt;키 존재 여부 확인 → 존재하지 않으면 값 설정&lt;/li&gt;
&lt;li&gt;이 작업은 중간에 다른 작업이 끼어들 수 없도록 원자적으로 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;원자적인 HINCRBY를 활용해도 가능하지 않나?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;레디스의 재고 데이터 유실은 어떻게 대응할 수 있나?
&lt;ul&gt;
&lt;li&gt;RDS? AOF?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;레디스와 RDB에 재고를 양쪽에서 관리하는 경우 동기화를 어떻게 할 것인가?
&lt;ul&gt;
&lt;li&gt;레디스의 재고를 성공적으로 차감한 후에 결제를 완료하지 못 했다면 재고 증가는 어떻게 처리할 것인가?&lt;/li&gt;
&lt;li&gt;결제를 완료하지 못해 재고를 증가시키는 처리와 사용자의 결제 시도 처리가 겹친다면?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CQRS용 머티리얼라이즈드 뷰와 일반적인 캐싱은 목적과 동작 방식이 다르다.
&lt;ul&gt;
&lt;li&gt;CQRS : 쓰기 시점에 읽기용 데이터 미리 준비
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[쓰기 요청] → [Command 처리] → [이벤트 발행] → [뷰 갱신] (Redis에 읽기용 데이터 저장)&lt;/code&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&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[읽기 요청] → [캐시 확인] → [미스 시 DB 조회] → [캐시 저장]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;쉽게 적용 가능하며, TTL로 자동 일관성 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사전 계산을 적용한다면 쿠폰이나 프로모션이 추가되면 모든 사용자의 적용 여부를 계산하여 레디스에 저장해놓는다.&lt;/li&gt;
&lt;li&gt;분산 락으로도 완전한 재고 일관성을 지킬 수 없다.
&lt;ul&gt;
&lt;li&gt;계층별로 락을 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;보상-트랜잭션으로-분산-환경에서도-안전하게-환전하기&quot; style=&quot;position:relative;&quot;&gt;보상 트랜잭션으로 분산 환경에서도 안전하게 환전하기&lt;a href=&quot;#%EB%B3%B4%EC%83%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EC%9C%BC%EB%A1%9C-%EB%B6%84%EC%82%B0-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EB%8F%84-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%ED%99%98%EC%A0%84%ED%95%98%EA%B8%B0&quot; aria-label=&quot;보상 트랜잭션으로 분산 환경에서도 안전하게 환전하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=xpwRTu47fqY&quot;&gt;토스ㅣSLASH 24 - 보상 트랜잭션으로 분산 환경에서도 안전하게 환전하기&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;양방향 무료 환전! MSA 환경에서 서로 데이터베이스도 다르게 보고 있을 때 트랜잭션을 어떻게 지켜내야할까?&lt;/p&gt;
&lt;h3&gt;분산 트랜잭션&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;2PC&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Commit 가능 여부를 질의 (Voting)&lt;/li&gt;
&lt;li&gt;DB가 가능하다고 응답, 한 개라고 불가능하다고 응답한다면 이전 작업들을 모두 롤백한다.&lt;/li&gt;
&lt;li&gt;강한 일관성, 낮은 가용성, 낮은 확장성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사가 패턴&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;각 서비스의 작은 트랜잭션들을 실행하면서 진행&lt;/li&gt;
&lt;li&gt;특정 단계에서 실패하면 보상 트랜잭션 실행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;높은 가용성, 높은 확장성, 중간 상태가 노출되고 보상 트랜잭션 구현을 필요로 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;코레오그래피 사가&lt;/strong&gt;
&lt;ul&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;strong&gt;오케스트레이션 사가&lt;/strong&gt;
&lt;ul&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;/li&gt;
&lt;li&gt;&lt;strong&gt;예외 핸들링&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;정상적인 실패 : 잔액 부족, 계좌해지, 거래제한&lt;/li&gt;
&lt;li&gt;비정상적인 실패 : 서버에러, 타임아웃&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배지 재처리&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;환전 지연 이벤트를 발행하지도 못하고 서버가 죽은 경우 배치를 통해 ‘출금 취소’ → ‘환전 실패’ 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과적 일관성&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;출금 취소에 실패한다면 CDL에 적재하여 지정한 횟수만큼 재시도 한다.&lt;/li&gt;
&lt;li&gt;그래도 실패한다면 개발자가 직접 개입하여 DL 서버를 통해 다시 메시지를 발행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DB: State Path&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;이벤트 조율을 오케스트레이터로 관리하기 때문에 매 상태를 확인하여 중간에 멈춘 환전을 알림으로 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;트랜잭셔널 메세징&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;입금 실패로 인한 환전 실패 처리와 출금 취소 메세지 발행은 항상 같이 이루어져야 한다!&lt;/li&gt;
&lt;li&gt;즉, 로컬 트랜잭션 커밋과 메세지 발행이 원자적으로 이루어져야 한다.&lt;/li&gt;
&lt;li&gt;트랜잭셔널 아웃박스 패턴으로 해결할 수 있지만, 토스는 프로듀서 데드 레터(PDL)로 해결한다.&lt;/li&gt;
&lt;li&gt;출금취소 메시지 발행에 실패한다면 PDL에 메시지를 발행한다. DL서버가 PDL을 컨슘하여 브로커로 다시 메세지를 발행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;사가패턴에서는 중간 상태가 노출되기 때문에 출금부터 진행해야 한다.&lt;br&gt;
입금,출금은 HTTP로 동기처리로 진행하기 때문에 타임아웃 구현이 필요하다.&lt;br&gt;
만약 출금은 성공했지만 입금에 실패한 경우 출금 취소로 처리한다.&lt;br&gt;
상대 계좌 서버나 네트워크 문제로 출금 결과 확인에 실패한다면 어떻게 처리해야 하나?&lt;br&gt;
카프카 메세지를 지연 발송하여 상대 계좌 서버나 네트워크 문제가 회복되는 시간을 벌 수 있다. (30초 → 1분 ..)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;트랜잭셔널 아웃박스 패턴과 PDL에 대한 질문&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Q. 내부적으로 메시징 발행 재처리가 DL 중심으로 잡혀 있는지?&lt;/strong&gt;&lt;br&gt;
A. 토스뱅크는 모든 토픽에 대하여 기본적으로 PDL이 적용되어 있기에 따라서 추가적인 아웃박스 패턴 등을 적용하지 않았다.&lt;br&gt;
&lt;strong&gt;Q. DL 서버에서 메시지 발행에 실패하면 어떻게 되는지?&lt;/strong&gt;&lt;br&gt;
A. DL 서버의 PDL 메시지 처리도 컨슈머로 동작하므로, CDL을 이용하여 재처리할 수 있다.&lt;br&gt;
&lt;strong&gt;Q. PDL이 아웃박스 패턴보다 나은 점&lt;/strong&gt;&lt;br&gt;
A. 모든 서비스에서 일괄 적용하기 쉽다는 장점이 있다. 아웃박스 패턴은 서비스 변경과 아웃박스 데이터 저장을 트랜잭션으로 묶어 처리해야 하므로, 서비스 DB 내에 아웃박스 테이블이 존재해야한다.&lt;br&gt;
토스뱅크에서는 수백개의 MSA 서버가 독립된 스키마(db)를 바라보고 있고, 이 스키마들은 수십개의 분리된 물리 서버 위에 그룹핑되어 존재한다.&lt;br&gt;
또, 사용하는 DB 역시 Oracle, MySQL, Mongo 등으로 다양하기에, 모든 MSA에 일괄적으로 아웃박스 패턴을 적용하려면, DB종류, 물리서버, 스키마마다 아웃박스 테이블들을 만들어줘야하고, 테이블에서 메시지를 발행하는 어플리케이션도 각각 작성해야 한다. 반면 PDL은 모든 서버에서 동일한 메시지 브로커를 바라볼 수 있기 때문에, 라이브러리 형태로 제공되어 일괄 적용하는데 편리한 점이 있다.&lt;br&gt;
&lt;strong&gt;Q. PDL 메시지 브로커도 고가용성이 보장되는지, 그렇다면 불필요한 인프라 비용이 생기는 것은 아닌지?&lt;/strong&gt;&lt;br&gt;
A. PDL 메시지 브로커로 서버에서 로그를 남길때 사용하는 로그 Kafka 클러스터를 사용하고 있다. 즉 고가용성을 위한 세팅이 적용되어 있으며 평상시에도 항상 사용하고 있기 때문에, PDL 메시지가 발행되지 않더라도 메시지 브로커에 문제가 생기면 그 즉시 알게 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;생각할-점&quot; style=&quot;position:relative;&quot;&gt;생각할 점&lt;a href=&quot;#%EC%83%9D%EA%B0%81%ED%95%A0-%EC%A0%90&quot; aria-label=&quot;생각할 점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;데이터 일관성을 아웃박스 패턴으로 해결할 수 있지만, 이 방법의 단점을 이해하고 PDL을 고려할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;선물하기-시스템의-상품-재고는-어떻게-관리되어질까&quot; style=&quot;position:relative;&quot;&gt;선물하기 시스템의 상품 재고는 어떻게 관리되어질까?&lt;a href=&quot;#%EC%84%A0%EB%AC%BC%ED%95%98%EA%B8%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EC%83%81%ED%92%88-%EC%9E%AC%EA%B3%A0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B4%80%EB%A6%AC%EB%90%98%EC%96%B4%EC%A7%88%EA%B9%8C&quot; aria-label=&quot;선물하기 시스템의 상품 재고는 어떻게 관리되어질까 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://techblog.woowahan.com/2709/&quot;&gt;우아한 기술블로그 링크&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;총 4가지 시스템&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;서비스는 상품의 속성을 정의하고, 관리하는 &lt;code class=&quot;language-text&quot;&gt;상품시스템&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;상품 엔티티 : 상품명, 상품 이미지 등 보여지는 요소&lt;/li&gt;
&lt;li&gt;판매상품 엔티티 : 상품이 어떤식 (판매기간)으로 판매될지를 결정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;가격정책 엔티티 : 어떻게 판매될지 결정된 판매상품을 어떠한 가격에 얼만큼 팔지 (원금액, 할인금액, 인당재고, 총재고)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;정의된 상품을 어느 카테고리에 매핑시켜 노출시킬지를 결정하는 &lt;code class=&quot;language-text&quot;&gt;전시시스템&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;상품을 상품권화 시키기 위해 고객님의 구매가 이루어질 수 있도록 하는 &lt;code class=&quot;language-text&quot;&gt;구매시스템&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;상품권을 음식주문시 사용할 수 있도록 하는 &lt;code class=&quot;language-text&quot;&gt;상품권 시스템&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;요구사항&lt;/strong&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설계&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;전체 재고량의 경우 RDB에 저장하여 관리하고, 트랜잭션이 일어나는 재고사용량의 관리는 연산속도가 빠른 in-memory DB를 사용한다.&lt;/li&gt;
&lt;li&gt;연산처리는 단일 스레드에서 처리하는 Redis를 이용하여 동시성 이슈를 해결한다.&lt;/li&gt;
&lt;li&gt;레디스의 데이터 유실이 일어날 수 있으므로, 재고 사용량 데이터를 RDB에 싱크한다.&lt;/li&gt;
&lt;li&gt;구매번호는 유니크한 값이고, Redis의 Set 자료구조는 중복을 허용하지 않기때문에 구매번호를 Set에 저장할 경우 SCARD 오퍼레이션을 통해 손쉽게 사용량을 가져올 수 있다.&lt;/li&gt;
&lt;li&gt;재고량 증가 혹은 감소시점에 (삽입만 발생하는) 재고량 히스토리 정보를 누적한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;구현&lt;/h3&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;`{상품번호}:{권종}:stock:{타입}`

# 전체 재고 관리
S0630000RU:5000:stock:total

# 개인별 재고 관리  
S0630000RU:5000:stock:{회원번호}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;S0630000RU&lt;/strong&gt;: 상품번호 (오늘도 수고했어 상품)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5000&lt;/strong&gt;: 권종 (5000원권)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stock&lt;/strong&gt;: 재고 구분자&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;total&lt;/strong&gt;: 전체 재고 / &lt;strong&gt;201209320003&lt;/strong&gt;: 회원번호&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ADD 구매번호&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;재고 사용량 증가&lt;/h3&gt;
&lt;p&gt;구매 과정에 필요한 타 시스템의 API와 동기 방식으로 진행&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이 플로우는 동기화된 메소드로 실행됨 (배민이지만 상품권 구매는 빈번하지 않다고 판단한듯)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;상품 시스템 플로우&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;트랜잭션 시작&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;구매가 가능한 상태인지 (왜 트랜잭션 열고 유효성 검증하지?)
&lt;ul&gt;
&lt;li&gt;총 재고수량 &amp;#x26; 인당 제한수량 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가능한 경우 Redis에 구매번호 ADD
&lt;ul&gt;
&lt;li&gt;총 재고수량 &amp;#x26; 인당 재고 사용량 증가를 트랜잭션으로 묶음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가능한 경우 RDB에 재고 히스토리 테이블 INSERT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;트랜잭션 커밋&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재고를 차감한 이후에 구매가 실패한 경우&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;재고사용량을 차감시키라는 이벤트를 &lt;code class=&quot;language-text&quot;&gt;재고사용량 감소 큐&lt;/code&gt;에 발행&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;트랜잭션 시작&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;레디스에서 전체 재고, 인당 재고 사용량을 구매번호로 제거&lt;/li&gt;
&lt;li&gt;재고 히스토리 테이블에 재고량 감소&lt;/li&gt;
&lt;li&gt;메세지 ack 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;트랜잭션 커밋&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;생각할 점&lt;/h3&gt;
&lt;ol&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&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&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재고를 확인하는 시점과 실제로 차감하는 시점 사이에 시간 차이가 존재하기 때문에, 두 요청이 거의 동시에 들어와서 같은 재고 상태를 확인한 후, 각각 재고를 차감하는 상황&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;조회와 업데이트가 분리된 두 개의 독립적인 Redis 명령어로 이루어짐&lt;/li&gt;
&lt;li&gt;Synchronized가 JVM 레벨에서만 동작하여 Redis 레벨의 원자성은 보장하지 못함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis 업데이트는 성공했지만 RDB 트랜잭션이 실패하여 롤백되는 경우&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Redis의 MULTI/EXEC와 Spring의 @Transactional은 완전히 독립적인 트랜잭션 시스템이므로, RDB 예외 발생 시 Redis 트랜잭션은 롤백되지 않음&lt;/li&gt;
&lt;li&gt;EXEC를 실행하는 시점에 큐잉되어있던 명령어들 모두 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분산 환경에서 Synchronized가 의미가 없음&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;프롬프트-엔지니어링-논문&quot; style=&quot;position:relative;&quot;&gt;프롬프트 엔지니어링 논문&lt;a href=&quot;#%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-%EB%85%BC%EB%AC%B8&quot; aria-label=&quot;프롬프트 엔지니어링 논문 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.perplexity.ai/page/google-shares-viral-prompt-eng-TEzNkJMNSm2kdu.xiZ8YWQ?login-source=oneTapPage&amp;#x26;login-new=false&quot;&gt;구글, 화제의 프롬프트 엔지니어링 논문 공개&lt;/a&gt;에서 소개하는 &lt;a href=&quot;https://www.kaggle.com/whitepaper-prompt-engineering&quot;&gt;프롬프트 엔지니어링 논문&lt;/a&gt;을 읽어보았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;해피 케이스, 에지 케이스 예제를 제공하여 견고한 출력을 생성할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시스템 프롬프트&lt;/strong&gt; : 언어 모델에 대한 전반적인 컨텍스트와 목적을 설정한다. (모델이 수행해야 하는 작업에 대한 &apos;큰 그림&apos;을 정의)
&lt;ul&gt;
&lt;li&gt;특정 요구 사항을 충족하는 출력을 생성하는데 유용하다. (코드 스니펫 생성 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문맥 프롬프트&lt;/strong&gt; : 현재 상황이나 세부 정보를 제공하여 질문의 뉘앙스를 이해하고 그에 따른 답변을 맞춤화하는데 도움된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;역할 프롬프트&lt;/strong&gt; : 특정 캐릭터 또는 신원을 할당하여 관련 지식 및 핼동과 일치하는 응답을 생성하는데 도움된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스텝백 프롬프트&lt;/strong&gt; : 당면한 문제와 관련된 일반적인 문제를 먼저 고려하도록 유도한 다음, 그 일반적인 질문에 대한 답을 특정 작업에 대한 후속 프롬프트에 제공함으로써 성능을 향상시키는 기법이다.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 1: 추상화&lt;/strong&gt; : &quot;이 문제에 적용되는 일반적인 원칙/개념/배경지식은 무엇인가?&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 2: 구체적 문제 해결&lt;/strong&gt; : &quot;위에서 도출한 원칙을 바탕으로 원래 질문에 답해줘.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;생각의 사슬 (CoT) 프롬프트&lt;/strong&gt; : 중간 추론 단계를 생성하여 추론 능력을 향상시키는 기법이다. 수학적 과제 같은 경우 단계별로 논리적인 내용을 예제로 제공하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-consistency 프롬프트&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ReAct (reason &amp;#x26; act) 프롬프트&lt;/strong&gt; : 인간의 실제 작동 방식을 모방한다. 추론과 행동을 사고-행동 루프로 결합하는 방식으로 작동하기에 문제에 대해 추론하고 행동 계획을 생성한다. 그리고 작업을 수행하고 결과를 관찰한다. 해결책에 도달할 때 까지 반복한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;핵심은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;직관적인 단어를 사용하고 필요한 정보만 제공하라.&lt;/li&gt;
&lt;li&gt;출력에 대해 구체적으로 설명해라.&lt;/li&gt;
&lt;li&gt;제약 조건을 설정하는 것은 유용하긴 하지만 제약 조건이 충돌할 수도 있고 잠재력을 제한시킬 수 있기 때문에 유의해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;도메인-주도-설계-첫걸음&quot; style=&quot;position:relative;&quot;&gt;도메인 주도 설계 첫걸음&lt;a href=&quot;#%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EC%84%A4%EA%B3%84-%EC%B2%AB%EA%B1%B8%EC%9D%8C&quot; aria-label=&quot;도메인 주도 설계 첫걸음 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;현재 회사에서 PM, 기획자없이 혼자 도메인 전문가와 긴밀하게 협업하면서 바이크 계약 어드민을 거의 새로 만드는 수준으로 개발하고 있다.&lt;br&gt;
요구하는 기술 수준이 높은 작업은 아니였지만 계약이라는 도메인 자체가 처음이기도 하고 기존에 구현되어있던 계약 어드민에 대한 데이터 구조, 아키텍처를 스스로 파악해가면서 개발하기란 쉽지 않았다.&lt;br&gt;
문득 이 상황에 도메인주도설계가 도움이 되지 않을까?라는 생각에 읽어보았다.&lt;br&gt;
기술적인 내용도 좋았지만 협업하는 자세와 방법에 대해서 인사이트를 얻을 수 있어 좋았다.&lt;/p&gt;
&lt;p&gt;이 책을 통해 도메인주도설계가 기술적인 문제를 해결하기 보다는 프로젝트를 성공시키기 위한 방법이라는 것을 깨달았다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;우리가 해결하고자 하는 문제가 무엇인지 합의하기 전에 해결책을 얘기하는 것은 의미가 없다.&quot;&lt;br&gt;
&quot;또한 해결책에 대해 합의하기 전에 어떻게 구현하는지 얘기하는 것도 의미가 없다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;무엇?&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;왜?&lt;/code&gt;라는 질문에 대한 정답을 찾는 전략적 설계와 &lt;code class=&quot;language-text&quot;&gt;어떻게?&lt;/code&gt;라는 방법에 대한 정답을 찾는 전술적 설계&lt;/li&gt;
&lt;li&gt;스테이크 홀더(도메인 전문가 등)와 효과적인 협업과 소통을 하는 방법으로 먼저 용어집을 만들자!&lt;/li&gt;
&lt;li&gt;도메인에서 핵심, 일반, 하위 도메인을 구분할 수 있어야 하며, 어떤 것에 집중할지 결정해야한다!&lt;/li&gt;
&lt;li&gt;바운디드 컨텍스트는 MSA가 될 수 없지만, MSA가 바운디드 컨텍스트일 수는 있다.&lt;/li&gt;
&lt;li&gt;애그리게이트는 작업 단위 기준으로 구분되며, 최대한 좁게 설계되어야 한다.&lt;/li&gt;
&lt;li&gt;비즈니스 로직 패턴에 만능은 없다!. 트랜잭션 스크립트, 액티브 레코드, 도메인 모델, 이벤트 소싱 순으로 적절한 복잡도에 적절한 패턴을 선택해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;MSA와 EDA, 이벤트 소싱에 대해서도 더 설명하는데 아직 경험이 부족하여 와닿지는 않은 것 같다.&lt;br&gt;
이 책을 읽고 흥미가 생긴 지점은 비즈니스 로직 복잡도를 도메인 모델로 해결하는 방법이다.&lt;br&gt;
용어집을 만들어 바운디드 컨텍스트를 보호하고, 더 작은 단위인 유즈케이스로 애그리게이트 루트를 만들어 작업 단위와 유즈케이스를 뚜렷하게 경계지어서 복잡도를 낮출 수 있는 방법에 대해 궁금해졌다.&lt;br&gt;
애그리게이트는 외부 기술에 의존하지 않고 순수한 도메인 영억으로 만들어야 하는지? 그렇다면 애그리게이트를 생성할 때 필요한 데이터를 모두 조회해서 다 주입해줘야 하는지? 애그리게이트는 빈이 될 수 없는지?과 같은 의문이 생겼다.&lt;/p&gt;
&lt;p&gt;다음 책으로 실제 구현하는 방법에 대해 학습해보고 실무에 스스로 적용해보아야겠다.&lt;/p&gt;
&lt;h1 id=&quot;개발자를-위한-레디스&quot; style=&quot;position:relative;&quot;&gt;개발자를 위한 레디스&lt;a href=&quot;#%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%A0%88%EB%94%94%EC%8A%A4&quot; aria-label=&quot;개발자를 위한 레디스 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;사내 스터디를 진행하면서 읽어보았다. 내용이 무겁지 않고 글자가 커서 1주일에 한 번씩, 2회만에 완독했다.&lt;/p&gt;
&lt;ol&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;메모리 관리와 maxmemory-policy 설정&lt;/li&gt;
&lt;li&gt;캐시 스탬피드를 완화하는 방법&lt;/li&gt;
&lt;li&gt;레디스의 pub/sub : &lt;code class=&quot;language-text&quot;&gt;fire-and-forget&lt;/code&gt; 패턴&lt;/li&gt;
&lt;li&gt;레디스의 stream : 카프카와 같은 팬아웃 데이터 분산 처리 가능, 소비자 그룹의 장점이 있음&lt;/li&gt;
&lt;li&gt;복제 방식&lt;/li&gt;
&lt;li&gt;클러스터 등&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;레디스 입문서로 적절하다고 생각한다.&lt;/p&gt;
&lt;h1 id=&quot;길-위의-뇌&quot; style=&quot;position:relative;&quot;&gt;길 위의 뇌&lt;a href=&quot;#%EA%B8%B8-%EC%9C%84%EC%9D%98-%EB%87%8C&quot; aria-label=&quot;길 위의 뇌 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;우연하게 &lt;a href=&quot;https://www.youtube.com/watch?v=fU2cp-0vKWU&amp;#x26;ab_channel=%EC%9E%A5%EB%8F%99%EC%84%A0%EC%9D%98%EA%B6%81%EA%B8%88%ED%95%9C%EB%87%8C&quot;&gt;유산소 운동이 뇌에 미치는 놀라운 효과&lt;/a&gt;영상을 접하면서 책을 읽게 되었다.&lt;br&gt;
평소 러닝에 관심이 많기에 러닝에 대한 장점은 대충 알고 있었다. 심페지구력이과 근지구력이 좋아지고 뇌의 노화를 낮추고 뇌 가소성을 높힌다라는 것들 말이다.&lt;br&gt;
하지만 책으로 더 확실하게 알아본다면 나의 러닝 습관에 좋은 영향을 줄 수 있지 않을까 하고 읽어보았다.&lt;/p&gt;
&lt;p&gt;저자는 재활의학과에서 뇌신경질환을 겪는 환자들을 진료하기에 운동이 신체 건강과 뇌 건강에 끼치는 영향에 대해 잘 알고 있다.&lt;br&gt;
그렇기에 이 건강을 지키거나 얻기 위해서는 가만히 노력없이 얻을 수 없다고 설명한다. 전문가가 이렇게 설명하니 꾸준히 달리는 입장에서 기분이 좋았다.&lt;/p&gt;
&lt;p&gt;러닝이 명상같다고 느껴 머리가 복잡하거나 불안할 때 늦은 시간이라도 그냥 뛰러 나가 내 발소리를 들으면서 생각 정리를 한 적이 많다.&lt;br&gt;
부정적 감정을 없애고 머리에 불필요한 찌꺼기들을 지우기에도 좋다고 생각했다.&lt;br&gt;
저자도 &lt;code class=&quot;language-text&quot;&gt;&quot;운동하러 갔다올게&quot;&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;&quot;생각하러 갔다올게&quot;&lt;/code&gt;라고 말할 수 있을 정도로 고요함을 느낀다고 하기에 동질감을 느꼈다.&lt;/p&gt;
&lt;p&gt;이 책은 단순히 &apos;운동하면 건강이 좋아져요~&apos;라는 이야기를 하기보다는 운동을 대하는 생각을 바꿔주기 위해 노력하고 있다고 느껴졌다.&lt;br&gt;
몸과 마음의 힘을 기르기 위해 운동을 통해 컴포트존을 벗어나 스트레스를 관리하는 것을 추천한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;스트레스라고 하면 다들 부정적으로 여기지만, 그렇다고 스트레스 진공 상태에서 지내는 것이 몸과 마음에 좋은 것만은 아니다.&lt;br&gt;
중요한 것은 내가 그 스트레스를 충분히 처리하고 대처할 수 있는가다.&lt;br&gt;
&apos;좋은 스트레스&apos;는 힘들고 도전적이지만 긍정적인 결과를 얻게하는 스트레스다.&lt;br&gt;
결과가 좋지 않았어도 좋은 스트레스가 될 수 있는데, 성장하는 경험이 되었을 때다.&lt;br&gt;
&apos;견딜 만한 스트레스&apos;는 결과는 부정적일지라도 이를 잘 처리할 수 있는 스트레스다.&lt;br&gt;
&apos;나쁜 스트레스&apos;는 신체, 행동, 생각에 부정적인 영향을 미치는 스트레스다.&lt;br&gt;
결국 좋은 스트레스, 나쁜 스트레스를 결정하는 것은 &lt;strong&gt;스트레스 그 자체가 아니라 스트레스를 처리하는 나의 능력치에 달렸다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;돈의-심리학&quot; style=&quot;position:relative;&quot;&gt;돈의 심리학&lt;a href=&quot;#%EB%8F%88%EC%9D%98-%EC%8B%AC%EB%A6%AC%ED%95%99&quot; aria-label=&quot;돈의 심리학 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;부제는 &lt;code class=&quot;language-text&quot;&gt;&quot;당신은 왜 부자가 되지 못했는가&quot;&lt;/code&gt;이다. 책의 제목만 보면 속물들이 읽을 법한 책으로 보일 수 있다.&lt;br&gt;
인문학적 소양은 제쳐두고 돈을 벌기에 혈안이 된 사람들이 좋아할 내용일 것처럼 보이지만, 읽는 동안 &lt;code class=&quot;language-text&quot;&gt;총,균,쇠&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;불안&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;프레임&lt;/code&gt; 같은 책들이 떠오를 만큼 의외로 깊이 있는 통찰과 여러 교훈을 제공한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;투자에 대한 성향과 선호도는 개인의 지능이나 교육과 무관하다.&lt;/strong&gt; 사람의 성향은 그들이 언제, 어디서 태어나고 어떤 경험을 했는지에 따라 결정된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성공에서 행운의 역할을 인정하라.&lt;/strong&gt; 개인의 노력만으로 모든 결과가 이루어진다는 믿음은 착각이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스스로 느끼는 만족의 기준을 이해하고 현실과 타협하라.&lt;/strong&gt; 무조건 더 많은 것을 추구하기보다는 자신에게 진정한 만족감을 주는 것이 무엇인지 알아야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다른 사람에게 잘 보이려 소비하지 마라.&lt;/strong&gt; 부는 당신이 소유한 것이 아니라 보이지 않게 축적된 자산이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;어마어마한 성공은 어마어마한 힘에서 오지 않는다.&lt;/strong&gt; 꾸준히 시간을 들이고 인내하는 과정이 성공의 핵심이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내 시간을 내 뜻대로 사용할 수 있는 자유가 돈이 주는 가장 큰 가치다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;부의 진정한 척도는 소득이 아니라 지출과 만족의 균형이다.&lt;/strong&gt; 연간 5천 달러를 벌면서 4천 달러에 만족하는 사람은, 연간 1만 달러를 벌지만 1만1천 달러에 만족하는 사람보다 부자다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 책은 단기간에 돈을 벌 수 있는 비법을 알려주는 책이 아니다. 그런 목적이라면 이 책을 읽지 않는 것이 좋다.&lt;br&gt;
그러나 돈을 대하는 자세와 철학, 그리고 돈이 줄 수 있는 진정한 가치를 배우고 싶다면 강력히 추천한다. 책은 단순한 투자 전략을 넘어 삶의 방식과 가치관을 돌아보게 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;특히 돈을 통해 얻을 수 있는 가장 중요한 것이 &quot;자유&quot;임을 강조하며, 물질적 풍요가 아닌 내면적 만족을 중시한다는 점이 인상 깊다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;살다 보면 자신이 내린 선택으로 부와 가난이 결정된다고 생각하기가 쉽다.&lt;br&gt;
...&lt;br&gt;
나는 네가 열심히 노력하는 것의 가치와 그 보상을 믿었으면 좋겠다.&lt;br&gt;
그러나 모든 성공이 노력의 결실도 아니고, 모든 가난이 게으름의 결과도 아님을 깨닫기를 바란다.&lt;br&gt;
...&lt;br&gt;
돈이 주는 가장 큰 배당금은 네 시간을 마음대로 할 수 있는 능력이다.&lt;br&gt;
네가 원할 때, 원하는 일을, 원하는 곳에서, 원하는 사람과 함께, 원하는 만큼 오래할 수 있다는 사실은 그 어떤 고가의 물건이 주는 기쁨보다 더 크고 더 지속적인 행복을 준다.&lt;br&gt;
네가 모은 한 푼, 한 푼은 모두 남들 손에 맡겨질 수 있었던 네 미래 한 조각을 소유하는 것과 같단다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&apos;나의 아이들에게 보내는 금융 조언&apos; 중&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[2024년 회고]]></title><description><![CDATA[…]]></description><link>https://jdalma.github.io/2024y/retrospect/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/retrospect/</guid><pubDate>Tue, 31 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;들어가며&quot; style=&quot;position:relative;&quot;&gt;들어가며&lt;a href=&quot;#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0&quot; aria-label=&quot;들어가며 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;매년 회고를 작성할 때마다 스스로에게 묻곤 한다. &lt;code class=&quot;language-text&quot;&gt;&quot;올해를 만족할 정도로 보냈나?&quot;&lt;/code&gt;&lt;br&gt;
항상 스스로 만족하지 못하지만 이전에 작성했던 회고를 다시 읽어보면 성장이 느껴지기도 한다.&lt;br&gt;
조금씩 앞으로 나아가고 있다는 위안을 얻기에 회고가 큰 도움이 된다. 그래서 이번에도 올해 있었던 일들을 돌아보며, 올해를 어떻게 보냈는지 정리해 보려고 한다.&lt;/p&gt;
&lt;h1 id=&quot;퇴사&quot; style=&quot;position:relative;&quot;&gt;퇴사&lt;a href=&quot;#%ED%87%B4%EC%82%AC&quot; aria-label=&quot;퇴사 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;최근, 특허 검색 서비스를 제공하는 회사에서 퇴사했다.&lt;br&gt;
가장 큰 이유는 &lt;code class=&quot;language-text&quot;&gt;도메인&lt;/code&gt;이다. 처음엔 검색 서비스 자체에 흥미를 느꼈지만, 시간이 지날수록 내가 성능을 개선하거나 기능을 추가해도 큰 보람을 느끼기 어려웠다.&lt;br&gt;
기술적으로 해결해야 할 과제는 많았고, 실제로 기술적인 역량을 요구하는 작업에 많이 참여했었지만, 많은 사용자에게 가치를 전달해주는 서비스를 경험해 보고 싶었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;현실 세계의 문제를 확실하게 내가 공감하고 사업적으로 영향을 전달할 수 있는 환경으로 가고 싶다고 생각되었기에 퇴사를 결정하게 됐다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;최종-합격&quot; style=&quot;position:relative;&quot;&gt;최종 합격&lt;a href=&quot;#%EC%B5%9C%EC%A2%85-%ED%95%A9%EA%B2%A9&quot; aria-label=&quot;최종 합격 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/736f1b9e8a7b75758bf7632ac1a261ec/c1b63/submit.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACkUlEQVR42p2U7WvTUBTG9zeLCn4WdOAHRQQRtol+GL5tUJQNkU3RzpdttG7tuiRLbK1NXNc0SXOTm/c8nt66rrWzeznh5hKS/PKc85yTGfw3cnFO/J9QDiXIqgZVPUAcx5gWM5OQ/ATW20PU/YojZsLnHHmei3UBIJBlGTgPEDoaEmsT543jD80MdA0ueNyD9HsL25sraNeLQOoMH06SBFEUi32ayjGFpeYqitozRLGH2C7TKiFlCtivHfzQNDSbTSiKAlmWEUYcSRYhzWO4Xg+tlg7f90+ARu8Aryv3oNuSuE4DA7G1TfISMsYmdRH6whjjME0TLLDhhl1aJjq2ga5pCcMEcLNRwPL3WcjtL2MOR50i8dgwA+ZGlHJ2timf1EVSdxeN7i7arAHuNQYuMxWRvYM+g/MQQRCf2hHiGDVlQ3uOV+VbeFt7iDe1R6iVrwPxwJDA3ketuguj1SFgQnWKYVkuDMOArutwXXdS4UdlUaT8XnmMlf051ErXCGgPEqcvR1Sb1CYTOO1ZDu4Hol59x/ttNgb0nBBLn++jULmDNWl+ACSFGe/A4+QivSQiTpFZ/pn9KBSu7T3B8s4s1qUFrEpzqG5fhVr9BkWtU+1OIBmpzCnlfs2CIKB7fGIUBbBINVwq38a6vDBMOfWP/no90sSULqwArt5BS2+JvrQsa3JSNrQXooZ94CqlvV++IUzJRh4chWZRMj3lD8pTvCzdxJo8j0L1ASpbVwRwQuE/s3vaCAqg7sio6OtoWlXUu1UcGe9ISYDLxHD04jRE262fqeBcwCwf9FKb1eEGnbHxG85DfkGFxypbjkTtYMOgKWCed/mUjyNMfbQP92B36S8dRrBdHw7zYTpM7EmaTgX+AQrc/bzjfbYUAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;submit&quot;
        title=&quot;&quot;
        src=&quot;/static/736f1b9e8a7b75758bf7632ac1a261ec/1cfc2/submit.png&quot;
        srcset=&quot;/static/736f1b9e8a7b75758bf7632ac1a261ec/3684f/submit.png 225w,
/static/736f1b9e8a7b75758bf7632ac1a261ec/fc2a6/submit.png 450w,
/static/736f1b9e8a7b75758bf7632ac1a261ec/1cfc2/submit.png 900w,
/static/736f1b9e8a7b75758bf7632ac1a261ec/c1b63/submit.png 1200w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;운 좋게 한 회사에서 서류를 통과했고, 하루에 1차와 2차 면접을 연달아 진행하게 됐다.
사실 큰 기대를 하지 않았던 회사였지만, 면접관분들의 애티튜드가 긍정적으로 느껴졌고 설명해주시는 서비스들도 흥미로웠기에 내가 경험하고 싶던 도메인을 모두 다룰 수 있을 것이라고 판단했다.&lt;/p&gt;
&lt;p&gt;면접 바로 다음 날 오퍼메일을 받았고, 처우 협상을 마친 뒤 2주 만에 합류했다.&lt;br&gt;
(처우협상 직후 타회사 과제를 1주일 동안 진행해서 여행을 가거나 놀진 못 했다.. 과제도 결국 떨어졌다..)&lt;/p&gt;
&lt;p&gt;합류한 회사는 &lt;a href=&quot;https://swingmobility.co/&quot;&gt;더스윙&lt;/a&gt;이라는 회사이고, 스윙 바이크 어드민 개발을 맡고 있다.&lt;br&gt;
바이크 계약, 결제, 빌링, 정산 등 내가 경험하고 싶었던 도메인에다가 현재 어드민의 문제점과 실무자의 요구사항을 빠르게 이해하여 실무진들에게 도움을 드리는 업무이기에 도전적인 업무라고 느끼고 있다.&lt;/p&gt;
&lt;h1 id=&quot;러닝&quot; style=&quot;position:relative;&quot;&gt;러닝&lt;a href=&quot;#%EB%9F%AC%EB%8B%9D&quot; aria-label=&quot;러닝 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;개발 공부는 꾸준히 해도 스스로 성장했는지 판단하기 굉장히 힘든데 러닝은 하면 할수록 성장이 확실히 느껴져서 재밌는 것 같다. 그리고 스트레스나 불안을 해소하기에도 굉장히 좋기에 꾸준히 하고 있는 운동이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;월&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;거리&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;월&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;거리&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;1월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;31.35&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;7월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;59.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;2월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;30.68&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;8월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;103.98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;3월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;58.83&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;9월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;73.03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;4월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;59.50&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;10월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;154.81&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;5월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;71.92&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;11월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;144.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;6월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;63.31&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;12월&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;77.98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;합계&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;315.59&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;합계&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;614.05&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;총합계&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;929.64&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;총 &lt;code class=&quot;language-text&quot;&gt;929.64km&lt;/code&gt;를 뛰었고 퇴사 이후인 하반기에 확실히 많이 뛰었다.&lt;br&gt;
폼이 금방 올라서 조깅 거리를 &lt;code class=&quot;language-text&quot;&gt;9km&lt;/code&gt;로 늘리고 페이스도 &lt;code class=&quot;language-text&quot;&gt;5분 ~ 6분&lt;/code&gt;으로 안정적으로 뛸 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8a9ad5becb1181b2b570be59994a371d/0931d/marathon.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 84.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAEUElEQVR42jWUWWxUZRTHL4gIc2cpZWY6tQMdhs7STncGrNKFMtNSGwmISDttfVFMUIlLaUwMJb7qq29GEx98MGENJD4QYkklxJgYH4hoClOWdugw086+b/35zW34kpPvfvfk/r5z/uecK/X39+Md8uL1eDg8MMARr5fR0VG6urpwOBwMHxnGc/gww8I/Inx2m43z52fJ5/NkMhlKpRKJRIJisUilUkFS1+gwyzUcbLDQ6WrF3dyCfkctOq2Wqs+pqaXXYqer2cVrze1s3rSZ6elzpFIpBViFhEIh5YLqkmSVjLFBj2mfkYbuXRjaDcg7ZTSyBllWY7bUY+o2Yum2ot9vRJIkvvh8WkRWJplMkk6nicWiFPK5jQg1W8WHPWrko8Jel5EHVMiNArhdg0YnfKNqtg+JdyPCNygjbZGYFsD19XWyuRzrApLIVYhnCkqUklajxWw0Y2210tDWgMvhQqfRodPpUKlU7GncQ11LHY3tjdgsNiXCczMzSjTxeJxsNksqkyORypATF0hWuxVHmwNbp41WdyuuTpdyNpvNNNmbaGlpxtnupHN/J13dXZheNXFh9oKiV7UYVS3zIt28gBUKBSS9RejnMmF0GjE6jBj2Gqi311MjCmJ01WFsq8PgEu9bDZgc9WyvVzEz86UCTKbSpNIZYVnlOZvJIvl8Pqbem2LCN4FvwsfY2BiTU5McP3aMqckJxcbHT+EbHxf7GCffPsZPP3yvAEuFDOVClkohTzGfFSkLDRcWFvAvLrLw8CEPhPkfLeL3+1kKPCOSKxMrVBSL5kukihUW4wVuP8sxv5JjbrnM7UCROXH+K1zYaBu3201v70GGevsY7OvnTc8QPQcOcObsZzxZS/JoaYnlYIiVaIZYNMnP96McuByg70qY0au/4L34G/surfLBXGgDuE1WYdqqpqdhN22uZjqcdl6SNnH0xLuIbhC6pAmHw0TiSVgvc2khzqHrz+m//ITa2a+xfPsjAzcifDQf3gCqVWoMTXpqO3di3G9C697BZtUmjr8zRjWJSqkoGjemVLO6Lj9I0HNlRQAfs+ubm+z+bp6ea0E+vP0CuFVG1SGj7hONLXZtjxapRuL4iTFShRIl0Q5hMVrJ4HK1DFzzJ5i4FeKTOxHO3o0rdmZ+ja/+iGwAa2pqsBgbaWpvwtphFSl38MqWlzkxPkEyV6QoGnclECCwtExeRPk4luPf1Qx/Pw3zOFnmfjDBvZUYgXRlA7jXtRen24nNbcP1hovWnjZ2O3Yx9f5pMmVIi3mNRKLCIsqfZS0nohYtEllapJCpyrAudCnzYkm1llr0zXr0Tj07HUJHuxFV/TZOTk6JkcoTX1tVihIKBik9fcQ/IqJr/iQX7z3j6n+r3Ark+fVplrvB3AZw0DPIyFsjeIe9eIY9yt5/qJdPxTQEIykCz1cJJTIshyKkHzzkuoCcurXKx3einPk9wuk5cb4ZYvbPqAL8H5wJcgz32NIzAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;marathon&quot;
        title=&quot;&quot;
        src=&quot;/static/8a9ad5becb1181b2b570be59994a371d/1cfc2/marathon.png&quot;
        srcset=&quot;/static/8a9ad5becb1181b2b570be59994a371d/3684f/marathon.png 225w,
/static/8a9ad5becb1181b2b570be59994a371d/fc2a6/marathon.png 450w,
/static/8a9ad5becb1181b2b570be59994a371d/1cfc2/marathon.png 900w,
/static/8a9ad5becb1181b2b570be59994a371d/21482/marathon.png 1350w,
/static/8a9ad5becb1181b2b570be59994a371d/0931d/marathon.png 1373w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;마라톤 성적도 조금씩 좋아진 것을 확인할 수 있다.&lt;br&gt;
개인 PB가 &lt;code class=&quot;language-text&quot;&gt;10km : 51분 56초&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;20km : 1시간 54분 26초&lt;/code&gt;로 목표했던 10km 한 시간 이내, 하프 두 시간 이내 뛰기 목표도 이루었다.&lt;/p&gt;
&lt;p&gt;첫 하프가 2시간 27분였지만 세 번째 하프에서 1시간 54분으로 단축한 것이 제일 뿌듯하다.&lt;br&gt;
2025년에도 마일리지 꾸준히 쌓아서 풀코스 완주가 목표다.&lt;/p&gt;
&lt;h1 id=&quot;책-읽기와-스터디&quot; style=&quot;position:relative;&quot;&gt;책 읽기와 스터디&lt;a href=&quot;#%EC%B1%85-%EC%9D%BD%EA%B8%B0%EC%99%80-%EC%8A%A4%ED%84%B0%EB%94%94&quot; aria-label=&quot;책 읽기와 스터디 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://jdalma.github.io/2024y/bookReview/bookReview/&quot;&gt;2024년 기록&lt;/a&gt;에 읽은 책과 강의에 대한 후기를 꾸준히 작성했다.&lt;br&gt;
대부분 출퇴근 시간을 활용해 독서를 하기 때문에, 퇴사 후 취업 준비 기간 동안에는 읽은 책의 양이 크게 줄어들기도 했다. 그럼에도 올해는 총 31권의 책을 읽었다.&lt;br&gt;
그중 기술서적은 17권, 기술 외의 책은 14권이었다.&lt;/p&gt;
&lt;p&gt;퇴사 전, 사내 스터디를 주도하며 다음 3권의 책을 함께 읽었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;네티 인 액션&lt;/li&gt;
&lt;li&gt;Real MySQL 1권&lt;/li&gt;
&lt;li&gt;엘라스틱서치 바이블&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;코드 스피츠와 코드숨 스터디를 통해 다음 6권의 책을 읽었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;이벤트 소싱과 마이크로서비스 아키텍처&lt;/li&gt;
&lt;li&gt;네티 인 액션&lt;/li&gt;
&lt;li&gt;코틀린 디자인 패턴 2/e&lt;/li&gt;
&lt;li&gt;HTTP/2 인 액션&lt;/li&gt;
&lt;li&gt;OAuth2 인 액션&lt;/li&gt;
&lt;li&gt;가상 면접 사례로 배우는 대규모 시스템 설계 기초&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;너무 기술적인 책만 읽지 않으려고, 문학과 비문학을 가리지 않고 흥미가 조금이라도 생기면 여러 종류의 책을 읽으려 노력했다.&lt;br&gt;
그래서 조금 읽다가 덮는 책들이 있긴 하지만, 올해는 특히 두 권의 책이 깊은 인상을 남겼다.&lt;/p&gt;
&lt;p&gt;첫 번째 책은 알랭 드 보통의 &lt;strong&gt;&apos;불안&apos;&lt;/strong&gt; 이다.&lt;br&gt;
이 책은 군대 훈련소 시절 한 번 읽었었는데, 이번에 개정판이 나와 다시 읽었다.&lt;br&gt;
특히 퇴사를 고민하던 시기에 큰 위로와 용기를 준 책이었다. 퇴사 후 내가 원하는 경험을 할 수 있는 회사를 찾기 위한 불확실한 도전을 고민하던 시기에 스트레스와 불안을 덜어주는 데 많은 도움이 되었다.&lt;br&gt;
이 책은 주변 지인이나 가족에게 선물할 만큼 좋아하는 책이며, 매년 초에 읽을 책 리스트에 추가해 두었다.&lt;br&gt;
스트레스나 불안으로 힘들어하는 사람들에게 강력 추천한다.&lt;/p&gt;
&lt;p&gt;두 번째 책은 한강 작가의 &lt;strong&gt;&apos;소년이 온다&apos;&lt;/strong&gt; 이다. 짧은 계엄 기간을 경험한 후, 책장에 꽂아두기만 했던 이 책을 드디어 읽어 보았다.&lt;br&gt;
읽는 내내 감정적으로 매우 힘들었다. 책을 읽으면서 눈물이 날 것 같아 중간중간 멈춰야 할 정도였다.&lt;br&gt;
읽고 느낀 점은 &lt;a href=&quot;https://jdalma.github.io/2024y/bookReview/bookReview/#%EC%86%8C%EB%85%84%EC%9D%B4-%EC%98%A8%EB%8B%A4&quot;&gt;2024년 기록 - 소년이 온다&lt;/a&gt;를 참고할 수 있다. (계엄 관련한 &lt;a href=&quot;https://www.yes24.com/product/goods/103495056&quot;&gt;&apos;작별하지 않는다&apos;&lt;/a&gt;도 읽어볼 예정이다.)&lt;/p&gt;
&lt;p&gt;책을 꾸준히 읽는 습관이 생긴 스스로가 신기하기도 하지만, 책을 읽는다는 게 대수인가 싶기도 하다.&lt;br&gt;
그래도 확실히 책을 읽으면서 내면이 조금씩 단단해지고 있음을 느낀다.&lt;br&gt;
감정과 상황을 다양한 관점에서 해석하는 시각이 생기고, 예민했던 성격이 무던해지고 있다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;내년에도 좋은 책을 많이 접할 수 있으면 좋겠다.&lt;/p&gt;
&lt;h1 id=&quot;todo-리스트&quot; style=&quot;position:relative;&quot;&gt;TODO 리스트&lt;a href=&quot;#todo-%EB%A6%AC%EC%8A%A4%ED%8A%B8&quot; aria-label=&quot;todo 리스트 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c5ffe135b25ffa1ae2a7e18d2398b484/27e9a/todo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACG0lEQVR42mWTjZaiMAyFff+nnHEFhNKW/lBAkLs3URx2Nh6Ocky/JDe3l5wzuq6FfK/rinmesW0bymCxPR6w0WGcCtbHCmcthhA0dxzH15MTUhgg8Xw+cYkpo64rNHWNvjfIKSkwu4oZG2p/xzgXbOuG3ho4R+gwYJom7PxM0whr2h9gGjPa9g7vHEoprJ5Q2FHpO2B7og0GZSmaLJ0JSIrKJBIL38fh1KGPXoExRk1eloWjP1Bsqx12b6DIYfse3jv0xihcYi4L8lBOIwdPDe9omuaTJFFci52jt0OnwI1AAcm4gTpK8aPDwvcPcOGYDTW83f5QH6fjLFxGMTLyhmY4aUiNU4pcRsaDORICtrb/H1jxCayeCNz552juCpSl5HnE/tZQdJYckeYD7H8Bq9tNrXMkSST7zZFXNLplAe6vhcniCPynwzMw54jr9RuG+kjrhqPOBM/0H4n46iz6kLRD5712ue+7HlZgmeCtP/swoa4qBUmXpuuwsrM50go86GyFMibdctu26kOxjEB1y9MM19tzh0FtE+h2OXQklr6hazbM/ornwiVwKVLsuFFi/gMYh3gyttiGQMtrpe7fd13K5Gkb+jDydkzc6nPfP8ZWv76NvdJSufvCSifIuUuIAfd7oyOL4EeHwTX8za5olfC+q1rs9Eg8lhmhJbC83HEprCpbNm/3H4bV68RDhrqG4Uf030DJ9+7n6v0FmnM72993ODcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;todo&quot;
        title=&quot;&quot;
        src=&quot;/static/c5ffe135b25ffa1ae2a7e18d2398b484/1cfc2/todo.png&quot;
        srcset=&quot;/static/c5ffe135b25ffa1ae2a7e18d2398b484/3684f/todo.png 225w,
/static/c5ffe135b25ffa1ae2a7e18d2398b484/fc2a6/todo.png 450w,
/static/c5ffe135b25ffa1ae2a7e18d2398b484/1cfc2/todo.png 900w,
/static/c5ffe135b25ffa1ae2a7e18d2398b484/27e9a/todo.png 1253w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;2023년 말 처음으로 TODO 리스트를 관리하기 시작했고, 올해는 이를 꾸준히 활용하며 학습과 작업에 큰 도움을 받았다.&lt;br&gt;
TODO 리스트를 사용하면서 느낀 장점은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;학습해야 할 내용을 까먹는 일이 줄어들었다. 평소 접한 유용한 게시글도 링크로 정리해두니 쉽게 관리할 수 있었다.&lt;/li&gt;
&lt;li&gt;목록화된 학습 내역을 보며, 지금까지 무엇을 배웠는지 되돌아보기 쉬워졌다.&lt;/li&gt;
&lt;li&gt;태스크의 우선순위를 정리하고, 무엇을 먼저 해야 할지 결정하는 과정이 훨씬 명확해졌다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 관리 방법은 블로그에 10개의 기술 글을 작성하는 학습에 대한 동기부여가 되었다.&lt;/p&gt;
&lt;p&gt;하지만, TODO 리스트를 활용하면서 &lt;code class=&quot;language-text&quot;&gt;하기 싫거나 흥미 없는 태스크는 진행하지 않는다.&lt;/code&gt;는 단점이 있다.&lt;br&gt;
내 관심사가 반영된 태스크만 완료하다 보니, 중요한 작업이 밀리거나 골고루 진행하지 못하는 경우가 있었다.&lt;/p&gt;
&lt;p&gt;이를 보완하기 위해, &lt;code class=&quot;language-text&quot;&gt;&quot;작업을 시작하기 전, 태스크를 랜덤으로 선택해 선택된 태스크는 반드시 완료한다.&quot;&lt;/code&gt; 규칙을 스스로 적용해 볼 생각이다.&lt;br&gt;
이 규칙을 통해 편향된 진행 방식을 개선할 수 있지 않을까 싶다.&lt;/p&gt;
&lt;p&gt;TODO 리스트는 단순한 작업 관리 도구 그 이상이었다.&lt;br&gt;
나의 학습과 작업에 체계성을 더해주고, 동기부여를 강화해주는 중요한 도구라고 느끼고 있다.&lt;/p&gt;
&lt;h1 id=&quot;총평&quot; style=&quot;position:relative;&quot;&gt;총평&lt;a href=&quot;#%EC%B4%9D%ED%8F%89&quot; aria-label=&quot;총평 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;2023년에 작성한 2024년 목표를 얼마나 달성했을까&lt;/p&gt;
&lt;ol class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 하프 마라톤 완주하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 책 20권 읽기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 인프런 강의 5편 이상 보기 → 4개를 완강하고 1개를 아직 보고 있다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 사람들 앞에서 기술 발표 해보기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 도전을 두려워 하지말기 → 퇴사하고 원하는 도메인을 다루고 있으니 이뤘다고 보자&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; (간단한) 서비스 만들기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;3개 밖에 못 이루었다..&lt;/p&gt;
&lt;h2 id=&quot;2025년-목표&quot; style=&quot;position:relative;&quot;&gt;2025년 목표&lt;a href=&quot;#2025%EB%85%84-%EB%AA%A9%ED%91%9C&quot; aria-label=&quot;2025년 목표 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;풀코스 완주하기&lt;/li&gt;
&lt;li&gt;러닝 마일리지 총 1000km 쌓기&lt;/li&gt;
&lt;li&gt;책 30권 읽기&lt;/li&gt;
&lt;li&gt;인프런 강의 5편 이상 보기&lt;/li&gt;
&lt;li&gt;사람들 앞에서 기술 발표 해보기&lt;/li&gt;
&lt;li&gt;월에 1개씩 기술 블로깅하기 (총 12개)&lt;/li&gt;
&lt;li&gt;회사에서 내가 맡은 서비스는 무조건 해내고, 다른 서비스도 업무 처리하기&lt;/li&gt;
&lt;li&gt;(개인적으로) 사내 개선 태스크 10개 만들어서 해내기&lt;/li&gt;
&lt;li&gt;도전을 두려워 하지 말기&lt;/li&gt;
&lt;li&gt;(간단한) 서비스 만들기&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;느낀점&quot; style=&quot;position:relative;&quot;&gt;느낀점&lt;a href=&quot;#%EB%8A%90%EB%82%80%EC%A0%90&quot; aria-label=&quot;느낀점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;어느덧 개발자로서 5년차가 되었다. 내가 생각하는 5년차의 기준에는 아직 부족한 점이 많다고 느끼지만, 그 부족함을 채우기 위해 꾸준히 노력하고 있다.&lt;/p&gt;
&lt;p&gt;더스윙에서 다른 프로덕트 팀원들을 보면 사용자와 가까운 서비스를 개발하고 운영하는 팀원들의 모습을 옆에서 보면서 &lt;code class=&quot;language-text&quot;&gt;&quot;저런 서비스를 개발하는 건 정말 재밌겠다.&quot;&lt;/code&gt; 라는 생각이 들었다.&lt;br&gt;
현재 실시간 사용자 서비스를 직접 개발하고 있지는 않지만, 내가 맡은 서비스가 안정적인 궤도에 오른다면 스윙의 주요 서비스들을 더 깊이 이해하고 직접 개발해보고 싶다는 욕심도 생긴다.&lt;/p&gt;
&lt;p&gt;내가 가장 중요하게 생각하는 것은 많은 시간을 보내는 회사에서의 업무에 대한 흥미다.&lt;br&gt;
만약 회사에서 하는 일이 재미없고 고역처럼 느껴진다면 그 시간은 얼마나 고통스러울까?&lt;br&gt;
다행히도 나는 개발자로서 일과 학습에서 재미를 느끼고 있다는 점에서 큰 행복을 느낀다. 주변 사람들은 이런 점을 잘 이해하지 못하는 경우도 있지만, 나는 그저 내가 하는 일을 즐길 수 있다는 사실에 감사하고 있다.&lt;/p&gt;
&lt;p&gt;최근에는 클린 코드, 테스트 코드, 그리고 개발 문화에 대한 현실과 이상 사이의 괴리감을 절실히 느끼고 있다.&lt;br&gt;
이것들이 중요하지 않다는 뜻은 결코 아니다. 다만 비즈니스가 존재해야 개발이 존재한다는 점에서, 스스로 너무 이상에만 집착했던 것은 아닌지 돌아보게 되었다.&lt;br&gt;
개발이라는 행위는 결국 비즈니스를 완성하기 위한 수단이며, 비즈니스가 우선이라는 사실을 명심하자.&lt;/p&gt;
&lt;p&gt;이제 회사에 빠르게 적응하여 비즈니스와 개발 모두에 기여하고 2025년 목표를 이루기 위해 꾸준히 노력할 예정이다.&lt;br&gt;
2025년에도 사소한 것에 동요하지말고 하는 일에 재미를 느끼면서 보낼 수 있으면 좋겠다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[가시성 문제와 동시성 문제 이해하기]]></title><description><![CDATA[언어 레벨에서 동시성 문제를 해결할 때  키워드나 블록을 사용하게 되는데, 단순히 객체들의 모니터에 대한 잠금을 기준으로 동기화 된다고 이해하고 있다. 이번 글을 통해 조금 더 자세하게 알아보자. JVM 밑바닥까지 파헤치기 12,1…]]></description><link>https://jdalma.github.io/2024y/memory/java/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/memory/java/</guid><pubDate>Wed, 04 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;언어 레벨에서 동시성 문제를 해결할 때 &lt;code class=&quot;language-text&quot;&gt;syncrhonized&lt;/code&gt; 키워드나 블록을 사용하게 되는데, 단순히 객체들의 모니터에 대한 잠금을 기준으로 동기화 된다고 이해하고 있다.&lt;br&gt;
이번 글을 통해 조금 더 자세하게 알아보자. &lt;a href=&quot;https://www.yes24.com/product/goods/126564592&quot;&gt;JVM 밑바닥까지 파헤치기&lt;/a&gt; 12,13장과 &lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard&quot;&gt;김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성&lt;/a&gt;을 참고하였다.&lt;/p&gt;
&lt;h1 id=&quot;공유-메모리-멀티-프로세서-시스템&quot; style=&quot;position:relative;&quot;&gt;공유 메모리 멀티 프로세서 시스템&lt;a href=&quot;#%EA%B3%B5%EC%9C%A0-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%A9%80%ED%8B%B0-%ED%94%84%EB%A1%9C%EC%84%B8%EC%84%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C&quot; aria-label=&quot;공유 메모리 멀티 프로세서 시스템 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;CPU는 프로세서의 처리 속도와 메모리 I/O 속도의 격차 문제를 완화하기 위해 둘 사이에 캐시 계층(하나 이상)을 활용한다.&lt;br&gt;
필요한 데이터를 캐시에 복사해두어 작업을 빠르게 수행하고, 작업이 완료되면 결과 데이터를 캐시에서 메인 메모리로 동기화하기에 프로세서는 메모리의 느린 I/O를 기다릴 필요가 없어진다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;프로세서 &amp;lt;-&amp;gt; 캐시 &amp;lt;-&amp;gt;
프로세서 &amp;lt;-&amp;gt; 캐시 &amp;lt;-&amp;gt;  캐시 일관성 프로토콜  &amp;lt;-&amp;gt; 메인 메모리
프로세서 &amp;lt;-&amp;gt; 캐시 &amp;lt;-&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;이 멀티 프로세서 시스템에서는 &lt;strong&gt;프로세서 각각이 자신만의 캐시를 갖춘 채 똑같은 메인 메모리를 공유한다.&lt;/strong&gt;&lt;br&gt;
그렇기에 &lt;strong&gt;여러 프로세서가 메인 메모리의 같은 영역을 보며 작업하더라도 프로세서별 캐시 데이터는 서로 다를 수 있다.&lt;/strong&gt;&lt;br&gt;
이 경우 데이터를 메인 메모리로 동기화할 때 어느 프로세서의 데이터를 기준으로 삼아야 할까?&lt;/p&gt;
&lt;p&gt;이 일관성 문제를 해결하기 위해서는 메인 메모리를 이용할 때 정해진 프로토콜을 따라야 하며, JVM은 자체 메모리 모델을 가지고 있다.&lt;br&gt;
(&apos;메모리 모델&apos;은 특정 프로토콜을 이용하여 특정 메모리나 캐시를 읽고 쓰는 절차를 말한다.)&lt;/p&gt;
&lt;h1 id=&quot;자바-메모리-모델&quot; style=&quot;position:relative;&quot;&gt;자바 메모리 모델&lt;a href=&quot;#%EC%9E%90%EB%B0%94-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%AA%A8%EB%8D%B8&quot; aria-label=&quot;자바 메모리 모델 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;JVM은 자체 메모리 모델을 가졌기 때문에 플랫폼에 상관없이 메모리를 일관된 방식으로 사용할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;자바 스레드 &amp;lt;-&amp;gt; 작업 메모리 &amp;lt;-&amp;gt;
자바 스레드 &amp;lt;-&amp;gt; 작업 메모리 &amp;lt;-&amp;gt;
자바 스레드 &amp;lt;-&amp;gt; 작업 메모리 &amp;lt;-&amp;gt; 저장, 읽기 &amp;lt;-&amp;gt; 메인 메모리
자바 스레드 &amp;lt;-&amp;gt; 작업 메모리 &amp;lt;-&amp;gt;
자바 스레드 &amp;lt;-&amp;gt; 작업 메모리 &amp;lt;-&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;자바 메모리 모델은 일관성 문제, 동시성 문제를 해결하기 위해 메모리 접근 규칙을 엄격하게 정의해놓았다.&lt;br&gt;
즉, &lt;strong&gt;가상 머신의 메모리에서 변수에 값을 저장하고 가져오는 저수준의 세세한 정보를 정의한것이다.&lt;/strong&gt;&lt;br&gt;
(위에서 말한 변수는 스레드별 고유 지역을 활용하는 지역 변수와 메서드 매개변수를 제외한 변수를 의미한다.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;작업 메모리에는 해당 스레드가 사용하는 변수가 저장된 메인 메모리의 복사본이 담긴다.&lt;/li&gt;
&lt;li&gt;스레드가 변수를 읽고 쓰는 모든 연산은 작업 메모리에서 수행되며 메인 메모리를 직접 읽고 쓸 수 없다.&lt;/li&gt;
&lt;li&gt;스레드간 서로의 작업 메모리에 접근할 수 없으며 반드시 메인 메모리를 거쳐 값을 전송해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그렇다면 메인 메모리에 접근하기 위한 방법은 무엇일까?&lt;/p&gt;
&lt;h1 id=&quot;메모리-간-상호-작용&quot; style=&quot;position:relative;&quot;&gt;메모리 간 상호 작용&lt;a href=&quot;#%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B0%84-%EC%83%81%ED%98%B8-%EC%9E%91%EC%9A%A9&quot; aria-label=&quot;메모리 간 상호 작용 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;메인 메모리와 작업 메모리 사이의 프로토콜, 즉 메인 메모리에서 작업 메모리로 변수를 복사하고 작업 메모리의 내용을 메인 메모리로 다시 동기화하는 구체적인 방법을 자바 메모리 모델은 여덟 가지로 정의했다.&lt;br&gt;
(각 단계의 연산이 원자적으로 이루어지도록 보장해야 한다.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;잠금&lt;/strong&gt; (lock) : 메인 메모리에 존재하는 변수를 특정 스레드만 사용할 수 있는 상태로 만든다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;잠금 해제&lt;/strong&gt; (unlock) : 잠겨 있는 변수를 잠금 해제한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;읽기&lt;/strong&gt; (read) : 뒤이어 수행되는 적재 연산을 위해 메인 메모리의 변숫값을 특정 스레드의 작업 메모리로 전송한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;적재&lt;/strong&gt; (load) : 읽기 연산으로 메인 메모리에서 얻어온 값을 작업 메모리의 변수에 복사해 넣는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용&lt;/strong&gt; (use) : 작업 메모리의 변숫값을 실행 엔진으로 전달한다. 가상 머신이 변숫값을 사용하는 바이트 코드 명령어를 만날 때마다 실행된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;할당&lt;/strong&gt; (assign) : 실행 엔진에서 받은 값을 작업 메모리의 변수에 할당한다. 가상 머신이 변수에 값을 할당하는 바이트코드 명령어를 만날 때마다 실행된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저장&lt;/strong&gt; (store) : 뒤이어 수행되는 쓰기 연산을 위해 작업 메모리의 변숫값을 메인 메모리로 전송한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쓰기&lt;/strong&gt; (write) : 저장 연산으로 작업 메모리에서 얻어온 값을 메인 메모리의 변수에 기록한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 여덟 가지의 기본 연산을 수행할 때 지켜야하는 규칙들도 존재하지만, 여기서는 메모리 간 상호작용을 위해 원자적으로 지원하는 연산이 있다고만 이해하자.&lt;br&gt;
이런 연산이 존재한다는 것을 이해하였으면 가시성 문제를 이해할 수 있다.&lt;/p&gt;
&lt;h1 id=&quot;가시성-문제&quot; style=&quot;position:relative;&quot;&gt;가시성 문제&lt;a href=&quot;#%EA%B0%80%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C&quot; aria-label=&quot;가시성 문제 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;스레드는 특정 변수가 필요하다면 메인 메모리에서 &lt;code class=&quot;language-text&quot;&gt;읽기&lt;/code&gt; 작업 후 &lt;code class=&quot;language-text&quot;&gt;적재&lt;/code&gt; 작업으로 정보를 읽어오고, 변수 갱신이 필요하다면 다른 스레드가 동기화된 변수를 가져가도록 메인 메모리에 저장하기 위해 &lt;code class=&quot;language-text&quot;&gt;저장&lt;/code&gt; 작업 후 &lt;code class=&quot;language-text&quot;&gt;쓰기&lt;/code&gt; 작업이 필요하다는 것을 알아보았다.&lt;br&gt;
하지만 스레드의 작업 메모리간 동기화가 보장되지 않아 문제가 발생하는 경우가 존재하는데, 이 문제를 &lt;strong&gt;가시성 문제&lt;/strong&gt; 라고 한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;/**
 * volatile 키워드 없이 발생하는 메모리 가시성 문제 예제
 * 문제: 메인 스레드가 runnable을 false로 변경해도,
 *      워커 스레드는 CPU 캐시에 저장된 true 값을 계속 읽어서 무한 루프에 빠짐
 */
public class VolatileTest {
    private boolean runnable = true;

    public void work() {
        System.out.println(&amp;quot;[워커 스레드] 작업 시작&amp;quot;);
        long count = 0;
        while (runnable) {
            count++;
        }
        System.out.println(&amp;quot;[워커 스레드] 작업 종료! 총 카운트: &amp;quot; + count);
    }

    public void stop() {
        System.out.println(&amp;quot;[메인 스레드] 중지 신호 전송 (runnable = false)&amp;quot;);
        runnable = false;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileTest test = new VolatileTest();
        Thread worker = new Thread(test::work, &amp;quot;Worker&amp;quot;);
        worker.start();

        Thread.sleep(1000);

        test.stop();

        // 5초 더 대기하며 스레드가 종료되는지 확인
        System.out.println(&amp;quot;[메인 스레드] 5초 동안 대기 중...&amp;quot;);
        Thread.sleep(5000);

        if (worker.isAlive()) {
            System.out.println(&amp;quot;[메인 스레드] 워커 스레드가 여전히 실행 중!&amp;quot;);
        } else {
            System.out.println(&amp;quot;[메인 스레드] ✓ 워커 스레드가 정상 종료됨&amp;quot;);
        }
    }
}

// [워커 스레드] 작업 시작
// [메인 스레드] 중지 신호 전송 (runnable = false)
// [메인 스레드] 5초 동안 대기 중...
// [메인 스레드] 워커 스레드가 여전히 실행 중!&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;메인 스레드는 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt;를 1씩 증가시키는 자식 스레드를 생성하고 500ms후에 자식 스레드가 종료될 수 있도록 &lt;code class=&quot;language-text&quot;&gt;runnable&lt;/code&gt; 상태를 false로 변경하기에 자식 스레드가 종료될 것이라고 예상할 수 있다.&lt;br&gt;
하지만 위의 예제에서 &lt;strong&gt;자식 스레드가 종료되지 않거나 종료되더라도 출력되는 count의 값이 서로 다르다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;task runnable false, count = 535399566
Thread-0 end, count = 600000000&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;이유는 각 스레드가 참조하고 있는 &lt;code class=&quot;language-text&quot;&gt;runnable&lt;/code&gt;변수가 서로 다르기 때문이다. 메인 스레드가 메인 메모리에 반영하는 시점이 언제인지, 자식 스레드가 메인 메모리에서 변수를 재조회할 시점이 언제인지 보장할 수 없는 것이다.&lt;br&gt;
주로 컨텍스트 스위칭이 발생할 때 작업 메모리(캐시)도 함께 갱신되는데, 이 부분도 환경에 따라 달라질 수 있다.&lt;br&gt;
이런 가시성 문제의 경우 &lt;code class=&quot;language-text&quot;&gt;runnable&lt;/code&gt; 변수에 &lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt; 키워드만 작성해주면 해결된다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;task runnable false, count = 419842762
Thread-0 end, count = 419842762&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;그렇다면 &lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt; 키워드가 무슨 역할을 하는 것일까?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 846px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/98336d403ea5248918648d835509ad1f/5b481/volatileBytecode.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 10.222222222222223%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAdElEQVR42lWMwQ7CIBAF+/9/Z28S9WACjYEgaLuFSjvdGi8eJrPvsNOJG/CnnmgdU8q845MmM5QKh6ua4yz4FLH+waBkGZH1g7Tly6QU3d2iTzEEgjHks2G8XMnKKsJWCtsvWNSvWXDB09sb9+Q10DRU/4I7biaZYEnk1C0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;volatileBytecode&quot;
        title=&quot;&quot;
        src=&quot;/static/98336d403ea5248918648d835509ad1f/5b481/volatileBytecode.png&quot;
        srcset=&quot;/static/98336d403ea5248918648d835509ad1f/3684f/volatileBytecode.png 225w,
/static/98336d403ea5248918648d835509ad1f/fc2a6/volatileBytecode.png 450w,
/static/98336d403ea5248918648d835509ad1f/5b481/volatileBytecode.png 846w&quot;
        sizes=&quot;(max-width: 846px) 100vw, 846px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;(바이트 코드에 직접적인 흔적을 찾을 수 있을 줄 알았지만.. 로우 레벨의 영역이라고 한다. &lt;a href=&quot;https://stackoverflow.com/questions/16898367/how-to-decompile-volatile-variable-in-java&quot;&gt;참고&lt;/a&gt;)&lt;br&gt;
&lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5-200-A.1&quot;&gt;field flags&lt;/a&gt;의 내용을 확인하면 &lt;code class=&quot;language-text&quot;&gt;ACC_VOLATILE - Declared volatile; cannot be cached.&lt;/code&gt; 내용을 확인할 수 있다.&lt;br&gt;
즉, &lt;strong&gt;물리적 저장소 관점에서 보면 각 스레드의 작업 메모리 내 volatile 변수들도 일치하지 않을 수 있다. 다만 사용하기 전에 매번 새로 고치므로 일관되지 않은 값을 사용할 일이 없다는 것이다.&lt;/strong&gt;&lt;br&gt;
이렇듯 메모리 가시성 문제는 &lt;strong&gt;멀티스레드 환경에서 한 스레드가 변경한 값이 다른 스레드에서 보지 못하는 문제를 뜻한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하지만 &lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt;을 사용하여도 아래와 같은 동시성 문제는 해결할 수 없다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class Counter {

    volatile int count = 0;

    public void increase() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        final Counter counter = new Counter();
        final ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i = 0 ; i &amp;lt; 10_000; i++) {
            executorService.submit(counter::increase);
        }
        executorService.awaitTermination(2, TimeUnit.SECONDS);
        executorService.shutdown();
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;언뜻 보면 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt;는 정상적으로 증가할 것이라고 보이지만 실제로 10000이 되지 못한다. 그 이유는 바이트 코드를 보면 알 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public void increase();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
    stack=3, locals=1, args_size=1
        0: aload_0
        1: dup
        2: getfield      #7                  // Field count:I
        5: iconst_1
        6: iadd
        7: putfield      #7                  // Field count:I
        10: return&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;2번부터 7번까지 count와 1을 스택에 로드하여 두 개의 정수를 합한 결과를 count 필드에 저장하게 된다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt; 키워드 덕분에 다른 스레드에서 저장한 값은 바로 확인할 수 있지만, 변수를 로드하고 저장하는 사이에 다른 스레드가 값을 저장하여 현재 스레드가 참조하고 있는 값이 변경 전의 값이 될 수 있기 때문이다.&lt;br&gt;
이 문제는 새로운 해결 방법을 적용해야 한다.&lt;/p&gt;
&lt;h1 id=&quot;동시성-문제&quot; style=&quot;position:relative;&quot;&gt;동시성 문제&lt;a href=&quot;#%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C&quot; aria-label=&quot;동시성 문제 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;만약 두 번의 출금이 한 개의 계좌에 동시에 일어난다고 가정해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Account {
    private volatile long balance;

    public Account(long balance) {
        this.balance = balance;
    }

    public void withdraw(long amount) {
        if (this.balance &amp;lt; amount) {
            return;
        }
        this.balance -= amount;
    }
}

class WithdrawTask implements Runnable {
    private final Account account;
    private final long amount;

    public WithdrawTask(Account account, long amount) {
        this.account = account;
        this.amount = amount;
    }

    @Override
    public void run() {
        this.account.withdraw(amount);
    }
}

@Test
void test() throws InterruptedException {
    Account account = new Account(5000);

    Thread thread1 = new Thread(new WithdrawTask(account, 3000));
    Thread thread2 = new Thread(new WithdrawTask(account, 4000));

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();

    Assertions.assertThat(account.getBalance()).isIn(1000L, 2000L);
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;balance&lt;/code&gt;변수에 &lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt; 키워드를 작성하였기에 5000원이 들어있는 계좌에 3000원, 4000원의 출금 태스크를 실행한 결과는  정상적인 결과인 1000원 또는 2000원이 남아있을 것이라고 기대할 수 있다. 하지만 실제로는 두 번의 출금이 모두 실행되어 -2000원이 남게된다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt; 키워드는 한 스레드에서 변수를 수정하였을 때 다른 스레드에서 이 변수의 값을 바로 알아차릴 수 있도록 가시성 문제만을 해결하는 것이다. 위와 같이 두 스레드가 한 변수에 업데이트하는 조건에만 의존하여 변수의 값을 업데이트하는 문제는 다른 문제이다.&lt;br&gt;
즉, &lt;code class=&quot;language-text&quot;&gt;if (this.balance &amp;lt; amount)&lt;/code&gt; 조건이 실행되는 시점에 실제로 5000원이 있었고, 두 스레드가 모두 &lt;code class=&quot;language-text&quot;&gt;balance&lt;/code&gt;를 업데이트하는 로직을 실행하도록 되었기에 문제가 되는 것이다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt; 키워드로 다른 스레드의 업데이트한 값은 실시간으로 참조 가능하기에 -2000원이 되는 것이다.&lt;/p&gt;
&lt;p&gt;한 마디로 &lt;strong&gt;변수를 조회하는 시점과 업데이트 하는 시점을 한 개의 임계영역으로 지정해야 하는 것이다.&lt;/strong&gt;&lt;br&gt;
아래와 같이 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 키워드를 메서드에 작성하면 이 메서드를 실행할 수 있는 메서드는 한 개로 제한되며, 나머지 스레드는 먼저 실행 중인 스레드가 끝날 때까지 기다리게 된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public synchronized void withdraw(long amount) {
    if (this.balance &amp;lt; amount) {
        return;
    }
    this.balance -= amount;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;이 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 키워드를 메서드에 작성하면 아래와 같이 &lt;code class=&quot;language-text&quot;&gt;ACC_SYNCHRONIZED&lt;/code&gt; flag가 추가된다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public synchronized void withdraw(long);

    descriptor: (J)V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    ...&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6&quot;&gt;method flags&lt;/a&gt;를 참조하면 &lt;code class=&quot;language-text&quot;&gt;ACC_SYNCHRONIZED - Declared synchronized; invocation is wrapped by a monitor use.&lt;/code&gt; &lt;strong&gt;메서드 실행 함수가 monitor를 사용하도록 래핑되는 것을 알 수 있다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;block synchronized&lt;/code&gt;를 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public void withdraw(long amount) {
    synchronized (this) {
        if (this.balance &amp;lt; amount) {
            return;
        }
        this.balance -= amount;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 메소드를 역어셈블러로 확인해보면 새로운 intruction이 등장한다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;3: monitorenter
4: aload_0
5: getfield      #7     // Field balance:J
8: lload_1
9: lcmp
10: ifge          16    // 잔액 차감 로직 실행               
13: aload_3
14: monitorexit&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;잔액 차감 로직 전후에 &lt;code class=&quot;language-text&quot;&gt;monitorenter&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;monitorexit&lt;/code&gt;을 볼 수 있다.&lt;br&gt;
&lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-6.html#jvms-6.5.monitorenter&quot;&gt;jvm spec&lt;/a&gt;을 참고해보면 &lt;strong&gt;Object는 모니터와 연결되어 있으며, 모니터는 소유자가 있는 경우에만 잠긴다. 스레드는 이 모니터의 소유권을 얻으려고 시도하며 시도에 성공한다면 모니터 락 카운터 값을 0에서 1로 설정하고 해당 스레드가 모니터의 소유자가 된다. 다른 스레드는 이 모니터 락 카운터 값이 0이 될 때까지 차단된다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;같은 스레드라면 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt;로 동기화된 블록에 다시 진입할 수 있다. 즉, 락을 이미 소유한 스레드는 동기화된 블록에 여러 번 진입해도 블록되지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt;로 동기화된 블록은 락 소유자가 작업을 마치고 락을 해제할 때까지 다른 스레드의 진입을 무조건 차단한다. 그렇기에 락을 소유한 스레드에 문제가 발생해도 강제할 방법이 없다. 또한 락을 기다리는 다른 스레드를 인터럽트해 깨울 방법도 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;핵심은 메모리간 상호 작용에서 알아본 잠금과 잠금 해제 연산을 직접 사용자에게 제공하지 않고, 한 단계 추상화된 바이트코드 명령어인  &lt;code class=&quot;language-text&quot;&gt;monitorenter&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;monitorexit&lt;/code&gt;을 이용하도록 하는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;정리&quot; style=&quot;position:relative;&quot;&gt;정리&lt;a href=&quot;#%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;정리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;무심코 사용하던 &lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt;를 왜 사용하는지, 사용하면 어떻게 되는지에 대해 조금 알게 되었다.&lt;br&gt;
언어 레벨에서 가장 일반적이면서 가장 중요한 동시성 보장을 위해 상호 배제 동기화인 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt;를 제공하긴 하지만 &lt;code class=&quot;language-text&quot;&gt;synchronized&lt;/code&gt; 단점을 개선하기 위해 JDK 1.5부터 &lt;code class=&quot;language-text&quot;&gt;java.util.concurrent&lt;/code&gt;가 추가되었다.&lt;br&gt;
상호 배제 동기화는 비관적 동시성 전략에 속하기에 경합이 실제로 벌어지는지와 상관없이 락을 건다. 그렇게 되면 사용자 모드에서 커널 모드로 전환되고, 락 카운터를 계산하고, 블록된 스레드를 깨워야 하는지 확인하는 작업이 뒤따른다.&lt;/p&gt;
&lt;p&gt;더 유연한 낙관적 동시성 전략도 있다. 이 전략은 잠재적으로 위험할 수 있더라도 일단 작업을 진행하며 충돌이 발생하면 보완 조치를 취하는 것이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;TAS(test-and-set) : 검사와 지정&lt;/li&gt;
&lt;li&gt;FAA(fetch-and-add) : 페치와 증가&lt;/li&gt;
&lt;li&gt;Swap : 교환&lt;/li&gt;
&lt;li&gt;CAS(compare-and-swap) : 비교와 교환&lt;/li&gt;
&lt;li&gt;LL/SC(load-linked/store-conditional) : 적재와 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 내용도 추가로 학습해보자.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[소프트웨어 아키텍처에 대한 고민]]></title><description><![CDATA[참고한 내용들 개발자를 위한 '소프트웨어 아키텍처' 개념과 활용법 만들면서 배우는 클린 아키텍처 Java/Spring…]]></description><link>https://jdalma.github.io/2024y/architecture/architecture/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/architecture/architecture/</guid><pubDate>Mon, 02 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;참고한 내용들
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/2743/&quot;&gt;개발자를 위한 &apos;소프트웨어 아키텍처&apos; 개념과 활용법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.yes24.com/product/goods/105138479&quot;&gt;만들면서 배우는 클린 아키텍처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8/dashboard&quot;&gt;Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;개요&quot; style=&quot;position:relative;&quot;&gt;개요&lt;a href=&quot;#%EA%B0%9C%EC%9A%94&quot; aria-label=&quot;개요 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/f-lab-edu/just-commerce&quot;&gt;토이 프로젝트&lt;/a&gt;를 만들어보면서 아키텍처에 대한 고민이 필요했다.&lt;br&gt;
그리하여 레이어드 아키텍처와 헥사고날 아키텍처에 대해 학습하여 (주관적인) 장,단점을 정리해보고 어떤 결정을 내렸는지 정리해보려 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;소프트웨어 아키텍처란?&lt;/strong&gt;&lt;br&gt;
시스템의 구조와 행동을 정의하는 청사진으로, 소프트웨어 시스템을 구성하는 &lt;strong&gt;컴포넌트(components)&lt;/strong&gt; 와 그들 간의 &lt;strong&gt;관계(relationships)&lt;/strong&gt;, 그리고 설계와 개발을 안내하는 &lt;strong&gt;원칙(principles)&lt;/strong&gt; 을 포함합니다.&lt;br&gt;
그리고 시스템의 기술적 결정을 조직화하고, 개발자가 시스템을 이해, 유지보수, 확장할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;레이어드-아키텍처&quot; style=&quot;position:relative;&quot;&gt;레이어드 아키텍처&lt;a href=&quot;#%EB%A0%88%EC%9D%B4%EC%96%B4%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98&quot; aria-label=&quot;레이어드 아키텍처 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;웹 (컨트롤러) → 비즈니스 (서비스) → 영속성 (엔티티, 리포지토리)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위와 같이 의존성이 단방향, 상위에서 하위 계층으로 흐르는 이 아키텍처의 단점과 특징에 대해 알아보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;데이터베이스 주도 설계를 유도한다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;가장 마지막에 의존하는 영속성 계층을 먼저 설계한 후에 이를 토대로 객체의 상태를 먼저 설계하게 된다.&lt;/li&gt;
&lt;li&gt;이런 데이터베이스 주도 설계는 데이터 중심 설계를 유발하며, 행동보다 데이터를 먼저 결정하게 되어 객체간 협력을 고려하기 어려워진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;계층간 참조 오염이 발생하기 쉽다.&lt;/strong&gt;
&lt;ul&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;strong&gt;유스케이스를 숨긴다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;도메인 로직이 어디에도 존재할 수 있기 때문에 흩어지기 쉽다.&lt;/li&gt;
&lt;li&gt;여러 개의 유스케이스를 담당하는 괴물 서비스가 탄생하기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;데이터베이스 주도 설계 문제는 개발자의 역량이 높다면 문제가 되지 않을 것이라고 생각되며, 계층간 오염 또는 유스케이스를 숨기는 문제는 대부분 철저한 코드 리뷰 또는 팀원간 지식 동기화를 통한 비용을 회사가 부담하는 환경이라면 해결할 수 있다.&lt;br&gt;
하지만 이런 비용을 부담하는 환경이 되지 않는다면 레이어드 아키텍처의 가장 큰 문제점은 &lt;strong&gt;계층간 기술 의존성이 오염되기 쉽다는 것&lt;/strong&gt;이다.&lt;br&gt;
즉, 비즈니스 레이어(또는 도메인 레이어)가 영속성 레이어에 직접 의존하여 영속성 레이어의 기술 의존성에 쉽게 노출되어 결합도가 높아진다는 것이다.&lt;br&gt;
결합도가 높아진다는것은 영속성 레이어에 변경이 일어난다면 비즈니스 로직을 관리하는 비즈니스, 도메인 레이어까지 변경의 영향이 온다는 것이다.&lt;/p&gt;
&lt;p&gt;이 문제를 헥사고날 아키텍처는 &lt;strong&gt;의존성 역전&lt;/strong&gt; 을 통해 해결한다.&lt;/p&gt;
&lt;h2 id=&quot;헥사고날-아키텍처&quot; style=&quot;position:relative;&quot;&gt;헥사고날 아키텍처&lt;a href=&quot;#%ED%97%A5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98&quot; aria-label=&quot;헥사고날 아키텍처 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;포트/어댑터 계층을 통해 의존성을 역전시켜 도메인 코드가 다른 바깥쪽 코드에 의존하지 않게 함으로써 영속성과 UI에 특화된 의존성들을 분리하는 것이다.&lt;/strong&gt;&lt;br&gt;
모든 의존성이 도메인 코드 방향으로 향하도록 하여 도메인 코드 변경의 취약점을 제거하는 것이 핵심이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/83d8e75a7532556c6b6ea437f7c24fcd/387f2/hexagonal.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.88888888888888%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABp0lEQVR42o2T23KjMAyGef83y93uTJOdJC2ZcjA2YJtTEmwOfyW3dDcX24lmhBksf5Kln6gxVxjd43a7YZ5nsK3ryk94N6OuOjTNgGctamwNUwnIokAuC8hSMSuYcw6lzFApgSzPkeQZCopTSqEsSyRZikTkkHSuVjklV4jaxqIzZQC+xjHeyI0xkHRoGAa0tMcupITSGu9Jgt1uh+PxiIK+yaoiUIVWS7RWI/q8HuhaDfb7Pf4cDlB1jaSQocLNJu/hxhGW4tIsg7U2tOh+vz9eeQNycK1raKPRtw1sXdKBKfSTfZqmkIDXhUC85ynJsizfMezRRl4X+rBQJdOMRiuYMsOdBvW1+10BA7wbKc5jK+ahwu3F+wlWD5T5M5NzHn6riiphxz/49X9T3l7G0YWqKppqkgv8fnlBHL9BUeMP1Ndfhz0EDY5jTFVQ4uVnIPem69rgkiAxTft8PuNyueB0OiGmleVyvfbo+y5c/UfgX0EjaEwIETSXkEw4QZqmBLs+xD0N7Ps+wDTpjuXBK0NvX0N6GrgZaysjrXGlDGZYTn/KM7/eBzENoAHP6tShAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;hexagonal&quot;
        title=&quot;&quot;
        src=&quot;/static/83d8e75a7532556c6b6ea437f7c24fcd/1cfc2/hexagonal.png&quot;
        srcset=&quot;/static/83d8e75a7532556c6b6ea437f7c24fcd/3684f/hexagonal.png 225w,
/static/83d8e75a7532556c6b6ea437f7c24fcd/fc2a6/hexagonal.png 450w,
/static/83d8e75a7532556c6b6ea437f7c24fcd/1cfc2/hexagonal.png 900w,
/static/83d8e75a7532556c6b6ea437f7c24fcd/21482/hexagonal.png 1350w,
/static/83d8e75a7532556c6b6ea437f7c24fcd/387f2/hexagonal.png 1518w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;출처 &lt;a href=&quot;https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8/dashboard&quot;&gt;Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아래와 같은 구조가 된다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;src/main/java/com/example/
├── domain/                # 핵심 비즈니스 로직
│   ├── model/             # 엔티티, VO
│   ├── service/           # 도메인 서비스
│   └── exception/         # 도메인 예외
│
├── application/           # 애플리케이션 서비스
│   ├── port/             
│   │   ├── in/            # Inbound Ports Interface (UseCase)
│   │   └── out/           # Outbound Ports Interface (Repository, Client)
│   └── service/           # UseCase 구현체
│
└── adapter/               # 어댑터 구현
    ├── in/                # Inbound Adapters
    │   ├── web/           # REST Controller
    │   └── messaging/     # Message Consumer
    └── out/               # Outbound Adapters
        ├── persistence/   # JPA Repository
        └── messaging/     # Message Producer&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;애플리케이션 코어와 어댑터들 간의 통신이 가능하려면 애플리케이션 코어가 각각의 포트를 제공해야 한다.&lt;br&gt;
&lt;strong&gt;주도하는 어댑터&lt;/strong&gt;에게는 그러한 포트가 코어에 있는 유스케이스 클래스 중 하나에 의해 구현되고 어댑터에 의해 호출되는 인터페이스가 될 것이고,&lt;br&gt;
&lt;strong&gt;주도되는 어댑터&lt;/strong&gt;에게는 그러한 포트가 어댑터에 의해 구현되고 코어에 의해 호출되는 인터페이스가 될 것이다.&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;주도하는 어댑터가 서비스에 의해 구현된 인커밍 포트를 호출하고, 서비스는 어댑터에 의해 구현된 아웃고잉 포트를 호출한다.&lt;/strong&gt;&lt;br&gt;
그렇기에 애플리케이션 계층이 인커밍/아웃고잉 어댑터에 의존하지 않아도 된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;adapter.in.web&lt;/code&gt; (Controller)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;application.port.in&lt;/code&gt; (UseCase)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;application.service&lt;/code&gt; (UseCase Impl)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;application.port.out&lt;/code&gt; (Port)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;adapter.out.persistence&lt;/code&gt; (Port Impl)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;클린 아키텍처, 육각형 아키텍처, 혹은 포트와 어댑터 아키텍처 중 무엇으로 불리든 의존성을 역전시켜 도메인 코드가 다른 바깥쪽 코드에 의존하지 않게 함으로써 영속성과 UI에 특화된 모든 문제로부터 도메인 로직의 결합을 제거하고 코드를 변경할 이유의 수를 줄일 수 있다. 그리고 변경할 이유가 적을수록 유지보수성은 더 좋아진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;내용을 종합하면 어댑터와 포트를 통해 의존성 역전을 적용하여 도메인을 순수한 영역으로 만드는 것이 핵심이다.&lt;br&gt;
도메인은 다른 계층에 의존하지 않기에 변경의 여파를 받지 않을 수 있고 추상화 영역(포트)이 도메인을 감싸고 있기에 계층 구현체를 바꾸기도 수월하다.&lt;/p&gt;
&lt;p&gt;하지만 헥사고날 아키텍처는 작성해야 하는 코드가 많다는 것이 단점이다. 계층간 모델을 독립적으로 관리한다면 그에 맞게 매핑 메서드들도 늘어난다.&lt;br&gt;
또한 레이어드 아키텍처와 마찬가지로 코드 리뷰 또는 지식 동기화에 대한 노력을 하지 않는다면 헥사고날 아키텍처 또한 비즈니스 로직이 흩어질 가능성이 있다.&lt;br&gt;
영속성 프레임워크에 의존하며 개발하던 개발자들은 도메인 영역과 영속성 영역이 분리되게 되면서 불편함을 호소할 수도 있다.&lt;/p&gt;
&lt;h1 id=&quot;결론&quot; style=&quot;position:relative;&quot;&gt;결론&lt;a href=&quot;#%EA%B2%B0%EB%A1%A0&quot; aria-label=&quot;결론 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;정리해보면 레이어드 아키텍처의 단점은 유즈케이스 가시성이 높지 않다는 것, 헥사고날 아키텍처의 단점은 작성해야 할 코드양이 많다는 것과 애플리케이션의 복잡도가 높지 않으면 큰 효과를 보기 힘들 수 있다는 점이다.&lt;br&gt;
공통적인 문제로는 (멀티 모듈을 적용하지 않는 전제) 비즈니스 로직이 흩어지거나 계층 오염이 발생하는 것은 추가적인 노력이 필요한 부분이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;그렇기에 &lt;strong&gt;헥사고날 아키텍처의 장점을 레이어드 아키텍처에 접목하여 최소한의 노력으로 개발자에게 어느정도 가이드 라인을 제공하고 어느정도의 컨벤션을 정의해보았다.&lt;/strong&gt;&lt;br&gt;
구조는 아래와 같다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;─ payment-service
    ├── controller
    │   └── port**
    ├── service
    │   ├── domain
    │   ├── port**
    │   └── exception**
    └── infrastructure
        ├── jpa
        ├── exception**
        ├── mapper**
        └── webclient&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;**&lt;/code&gt; 표시의 패키지를 보자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;포트/어댑터를 in,out으로 구분하지 않고 &lt;strong&gt;port 인터페이스 패키지만 클라이언트 패키지에 위치시켜 의존성 역전 원칙을 통해 도메인 영역을 순수하게 만든다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;자원에 대한 단일 서비스 클래스를 사용하지 않고 &lt;strong&gt;유즈케이스 별로 port 인터페이스와 service 구현체를 작성하여 유즈케이스를 노출시켜 불필요한 메서드에 의존하지 않도록 한다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;또한 유즈케이스별로 요청과 응답을 각각 생성하여 불필요한 정보는 전달되지 않도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;각 계층은 모델을 직접 관리하여 각 계층의 관심사에 맞는 모델의 검증 작업을 통해 의도를 확실히 드러낸다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;예를 들어, controller의 request 모델은 입력 유효성 검증, service의 command 모델은 비즈니스 규칙 검증을 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;각 계층은 예외를 직접 관리하여 각 계층의 관심사에 맞는 예외를 직접 정의하며, 공통 모듈의 예외를 상속받아 계층화하여 사용한다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;예를 들어, service의 예외는 &lt;code class=&quot;language-text&quot;&gt;InvalidCouponException&lt;/code&gt;과 같은 비즈니스 예외를 관리하고 infra의 예외는 &lt;code class=&quot;language-text&quot;&gt;..EntityNotFoundExcepion&lt;/code&gt;과 같은 영속성 예외를 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;계층간 매핑 전략은 커맨드(변경) 요청인 경우 양방향 매핑 전략을 사용하며, 입력 규칙 또는 비즈니스 규칙이 복잡할 경우 커맨드 객체를 추가하여 책임과 목적을 명확히 한다.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;쿼리(읽기) 요청이 간단한 경우 웹 계층과 애플리케이션 계층 간 매핑은 생략하여 불필요한 매핑은 추가하지 않도록 한다.&lt;/li&gt;
&lt;li&gt;기본적으로 도메인 계층과 영속성 계층은 항상 모델(엔티티)를 분리하여 기술적 의존성을 도메인 계층에 침범하지 않도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;service 계층에서 in/out을 구분하여 입력, 출력에 대한 의존성을 관리하지 않아 service 계층에서는 controller/port와 infrastructure에 의존하는 특징이 있다.&lt;br&gt;
&lt;strong&gt;헥사고날 아키텍처의 구조를 그대로 따르기 보다는 레이어드 아키텍처의 기조를 따르되, 헥사고날 아키텍처의 장점 중 현재 필요한 부분만 차용하여 스스로가 생각하는 기준을 정리해 보았다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;느낀점&quot; style=&quot;position:relative;&quot;&gt;느낀점&lt;a href=&quot;#%EB%8A%90%EB%82%80%EC%A0%90&quot; aria-label=&quot;느낀점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;설명한 아키텍처 구조와 컨벤션은 정답이 아니며, 문제를 해결할 당시의 비즈니스 요구 사항과 환경에 따라 아키텍처는 유연하게 변경되어야 한다.&lt;br&gt;
변경에 따라 얻는 점과 잃는 점을 확실히 인식하여 적절한 트레이드오프를 진행할 수 있어야 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;설계를 할 때도 현재 환경에 맞춰 발전시킬 부분과 포기할 부분을 신중히 선택해야 합니다. 모든 것을 동시에 발전시키는 것은 불가능하다는 사실을 받아들여야 합니다.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;잘못된 선택을 하면, 특정 상황에서는 적합하지만 다른 상황에서는 적응하지 못하는 시스템이 만들어지게 됩니다.&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://eternity-object.tistory.com/43&quot;&gt;설계 트레이드오프&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이렇게 학습하고 스스로의 기준을 확립해보면서 아키텍처의 당위성을 설명하는 시도는 아키텍처와 설계에 대한 안목을 키우는 것에 큰 도움이 된다고 느꼈다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[에프랩(F-Lab) Java Backend 2개월 후기]]></title><description><![CDATA[F-Lab…]]></description><link>https://jdalma.github.io/2024y/flab/first/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/flab/first/</guid><pubDate>Wed, 16 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;f-lab을-신청하게-된-계기&quot; style=&quot;position:relative;&quot;&gt;F-Lab을 신청하게 된 계기&lt;a href=&quot;#f-lab%EC%9D%84-%EC%8B%A0%EC%B2%AD%ED%95%98%EA%B2%8C-%EB%90%9C-%EA%B3%84%EA%B8%B0&quot; aria-label=&quot;f lab을 신청하게 된 계기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이때까지를 되돌아보면 공부도 열심히하고 회사에 기여하기 위해 바쁘게 지낸 것 같은데, 스스로를 어필할 수 있는 필살기가 없다고 느꼈다.&lt;br&gt;
내가 가진 지식을 그래프(자료구조)에 비유하자면 기존의 그래프에 새로운 노드들을 견고하게 연결하는 학습 패턴을 가져야 하는데, 기존 학습 그래프랑 연결되지 않고 겉핥기 식으로 동떨어진 새로운 그래프를 계속 만들어 내고 있었다.&lt;br&gt;
(새로운 그래프를 만들더라도 그 그래프들을 연결할 수 있는 기회를 잡거나 노력을 해야 했는데 못한 것 같다.)&lt;br&gt;
그렇게 흥미가 있거나 실무에서 사용되지 않는 새로운 분야를 학습하더라도 금방 잊어먹는 아주 비효율적인 학습을 계속했다.&lt;/p&gt;
&lt;p&gt;어떤 것을 학습해서 어떤 부분을 개선할 수 있는지 또는 이 부분을 개선하기 위해 어떤 학습,분석,모니터링 등이 필요한지를 혼자서는 아는 만큼 밖에 보이지 않기 때문에 이런 학습이 계속 되지 않았나 싶다.&lt;br&gt;
&lt;strong&gt;내가 관심 가지는 도메인에 경험이 많고 학습 방향이나 깊이에 대해 조언을 받을 수 있는 기회를 만들고 싶어 F-Lab을 신청하게 되었다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;첫-번째-달의-과정&quot; style=&quot;position:relative;&quot;&gt;첫 번째 달의 과정&lt;a href=&quot;#%EC%B2%AB-%EB%B2%88%EC%A7%B8-%EB%8B%AC%EC%9D%98-%EA%B3%BC%EC%A0%95&quot; aria-label=&quot;첫 번째 달의 과정 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;이력서와 경력 기술서 피드백&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JVM에서 코드가 실행되기까지 조사하기&lt;/strong&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;JVM 런타임 데이터 영역&lt;/li&gt;
&lt;li&gt;클래스 로딩 메커니즘&lt;/li&gt;
&lt;li&gt;초기화 단계 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;clinit&gt;()&lt;/code&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;strong&gt;가비지 컬렉션&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;GC 대상에 메서드 영역이 포함되어야 하는가?&lt;/li&gt;
&lt;li&gt;대상이 죽었는가? 참조 카운팅 알고리즘, 도당 가능성 분석 알고리즘&lt;/li&gt;
&lt;li&gt;세대 단위 컬렉션 이론에 기반한 마크-스윕,마크-카피,마크-컴팩트 알고리즘&lt;/li&gt;
&lt;li&gt;GC 루트 노드 열거, 안전 지점, 안전 지역&lt;/li&gt;
&lt;li&gt;STW를 줄이기 위한 기법들 (삼색 표시 기법, 증분 업데이트, 시작 단계 스냅숏, 쓰기 장벽을 통한 기억 집합과 카드 테이블)&lt;/li&gt;
&lt;li&gt;클래식 가비지 컬렉터 (신세대 : 시리얼,파뉴,패러렐 스캐빈지, 구세대: 시리얼 올드, CMS, 패러렐 올드)와 G1&lt;/li&gt;
&lt;li&gt;최신 가비지 컬렉터 (셰넌도어, ZGC)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자바 메모리 모델과 스레드&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;작업 메모리와 메인 메모리간 상호 작용&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;volatile&lt;/code&gt;의 원리와 동시성이 지켜지지 않는 케이스&lt;/li&gt;
&lt;li&gt;자바 메모리 모델의 원자성, 가시성, 실행 순서&lt;/li&gt;
&lt;li&gt;스레드 모델 (N:1, 1:1, N:M) 종류와 한계&lt;/li&gt;
&lt;li&gt;가상 스레드와 코루틴을 이해하기 위한 커널 스레드와 사용자 스레드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HashMap 분석&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;tab[(n - 1) &amp;amp; (hash = hash(key))&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;newTab[e.hash &amp;amp; (newCap - 1)]&lt;/code&gt; 의 모듈러 연산에 대한 이해&lt;/li&gt;
&lt;li&gt;resize시 사이즈가 2배씩 증가하는 이유 (사이즈가 2의 제곱수인 이유)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Node&amp;lt;K,V&gt; loHead = null, loTail = null, hiHead = null, hiTail = null;&lt;/code&gt; 네 개의 임시 노드가 존재하는 이유&lt;/li&gt;
&lt;li&gt;resize 진행시 &lt;code class=&quot;language-text&quot;&gt;if ((e.hash &amp;amp; oldCap) == 0)&lt;/code&gt; 코드의 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;포트폴리오 시나리오 작성&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;느낀-점&quot; style=&quot;position:relative;&quot;&gt;느낀 점&lt;a href=&quot;#%EB%8A%90%EB%82%80-%EC%A0%90&quot; aria-label=&quot;느낀 점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;아무리 뛰어난 개발자가 멘토라고 하더라도 학습하고 이해하고 응용할 수 있는 능력은 전적으로 나에게 달려있다.&lt;br&gt;
하지만 학습의 방향성과 깊이에 대한 조언을 받을 수 있다는 것은 학습자 입장에서는 굉장히 중요한 요소라고 느꼈다.&lt;br&gt;
예를 들어, &lt;code class=&quot;language-text&quot;&gt;GC가 시작될 때 사용자 스레드를 STW 하지 말고 커널 스레드를 GC 스레드에 모두 매핑하는 것은 왜 안될까요?&lt;/code&gt; 라는 질문을 통해 스스로 학습할 수 있도록 도와주신다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/128e919a0ce060c2b76cbaf6692e3139/47aef/whystw.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 88.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAADPElEQVR42oVU2XLjOAz0XySpRL5tXZRI3ZTkQ4fjTOIklced//+Q3iblqd23eegCIYIg0A1q5qk9/NqFXxKVi/DoI6h9+I0Hz3zTHuEioG/2jR8ciJZ+ybica+3Dyzzs9jvM3DREcE6ZQEKcc4ihgH9QCLucBzNepmi539KeMiJBeEoRHBNs1Q6L1QLL7RKLzQKbzQYzIXg4TCFlCakqRKKw6zierJQFkkTfUVubpg0UY0NPMF4g8AOEQYj9fo+ZlDE3JYw1SNOESKGS6VsYhhAiRBQJxk2xQgTWj+PYQimFKI6w27HlqqowDAP6vsd4ueBwPNr1kbauTUUJkSLLMh6W9rC50ECwOimljTGJbYVlUeB0Oloc2hYZA/M8s5C81bQklakkwma9xnq9wpZcmfVyubT+9H2N7XaLma5bnPsRpzOrHF9Z5Rv64YLx9RdUWiAImVRSEBHTshLFyiLybiDYfqzgBwLzxV0UlZYomx76eEHZEocLKvpFM+A43qDbDu35ivo4oqE10Iee9g1t/47m9AqZVlit1lPCIs/R1BrKCBD4bDG07RlfxiTekB9F8Dz3vi8sFSL0yV+EhCIZgUz7NqHLcrvxzXLYUYyu69B3PYZxtBjHCz5uN3x+fpKWM4qitCJkWc5E5iIOOMfG931yyIQ7X+D68Y2v2wduPPj9/cXDk/14f8fX1xd+//4HPz8/uF6vvPjEpAXMdBiljcoGZrymwY74QeVIs8ISrpKMyJFQkCTNbQWOs8DLywucFwfz+RwLCrBarayy/4dNGHHG4qxCnFdQRYMkb5FULVRJW2qqzLfubZl4D9ff81X49hKT1MBw9wd2DnVdkpsWw+WMy+uAyzjg3B1w6loUVc7ZzDAeItSavBUZtK7ZrrbVmKrm88Ud82kOzQxVukHTNDZYa20PWFtW5OsPSvtaqjy1MPslLxCBi1hMotxVjnDmILdNjbIsLdmaY1TpypJvkuRFzpdTIM+YKBNEBDNuRSrtuq1Sq7xNaDK7nDHXJciNtYTPmTMcPT8/W0H+g0PMp7Xj4Nn4ziSUoWC2Yv/xhgO74Y9ytUe08+2fw5k7eHh4wOPj41/x9PRkYZT/F1SwLyt8x9kTAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;whystw&quot;
        title=&quot;&quot;
        src=&quot;/static/128e919a0ce060c2b76cbaf6692e3139/1cfc2/whystw.png&quot;
        srcset=&quot;/static/128e919a0ce060c2b76cbaf6692e3139/3684f/whystw.png 225w,
/static/128e919a0ce060c2b76cbaf6692e3139/fc2a6/whystw.png 450w,
/static/128e919a0ce060c2b76cbaf6692e3139/1cfc2/whystw.png 900w,
/static/128e919a0ce060c2b76cbaf6692e3139/47aef/whystw.png 1063w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위와 같이 한 개의 질문에서 여러 개의 질문으로 스스로 확장하며, 질문들을 해결해나가고 다시 질문들을 확장해나가는 과정을 통해 능동적이게 학습할 수 있는 기회를 주었다.&lt;br&gt;
어떤 주제에 대한 스터디나 강의, 책을 통한 학습은 한 개의 태스크를 동기로 진행한다고 비유한다면, 능동적인 학습은 여러 개의 주제에 대한 태스크를 비동기로 진행하는 것과 같다고 느꼈다.&lt;br&gt;
강의나 책을 완강,완독하는 목표가 아니라 근본적인 질문을 이해하기 위해 어떤 것을 학습해야 할지 스스로 판단하고 실행하는 것이 더 좋은 학습인 것 같다.&lt;/p&gt;
&lt;p&gt;그리고 멘토님의 경험에 대한 이야기 덕분에 학습에 살이 더 붙어 이해가 더 잘 되었다. 여러 회사들의 내부 이야기를 간접적으로 들어볼 수 있고, 기술의 히스토리나 키워드들을 던져주시는 것도 좋았다.&lt;/p&gt;
&lt;h1 id=&quot;두-번째-달의-과정&quot; style=&quot;position:relative;&quot;&gt;두 번째 달의 과정&lt;a href=&quot;#%EB%91%90-%EB%B2%88%EC%A7%B8-%EB%8B%AC%EC%9D%98-%EA%B3%BC%EC%A0%95&quot; aria-label=&quot;두 번째 달의 과정 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이론 학습 기간이 끝나고 토이 프로젝트를 시작하는 단계가 진행되면서 실무에서는 생각하지 못 했던 부분들을 고민하게 되었다.&lt;br&gt;
gradle로 멀티 모듈을 구성하는 방법부터 모듈간 의존성은 어떻게 관리할지, 소프트웨어 아키텍처의 여러 가지 방법에 대한 장,단점을 이해하고 최선의 선택은 무엇일지 처음으로 고민해보게 된 것 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;gradle 멀티 모듈 구성 방법과 기준&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;common 모듈 구성&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/geminiKim/dev-practice/issues/16&quot;&gt;구현 레이어의 역할&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=VPzg61njKxw&amp;#x26;list=WL&amp;#x26;ab_channel=%EC%BD%94%EB%94%A9%ED%95%98%EB%8A%94%EC%98%A4%ED%9B%84&quot;&gt;왜 프로젝트를 멀티 모듈로 구성할까&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL8RgHPKtjlBh-LU_yUxFfIq_flizPm_vZ&quot;&gt;멀티 모듈 구성&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cofls6581.tistory.com/274&quot;&gt;멀티모듈이란? 도입기, 모듈 분리 기준 aka.스프링부트, 코틀린&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;레이어드 아키텍처와 헥사고날 아키텍처의 장,단점&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2024y/bookReview/bookReview/#%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98&quot;&gt;만들면서 배우는 클린 아키텍처 후기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/course/%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/dashboard&quot;&gt;지속 성장 가능한 소프트웨어를 만들어가는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;도메인과 인프라간 경계를 명확히하여 순수한 도메인 영역을 만드는 것이 핵심이지 않을까? 자연스럽게 영속성 프레임워크의 기능에 의존하는 것도 떨어지게 된다는 점도 특징이다.&lt;/li&gt;
&lt;li&gt;이 두 아키텍처 중 정답은 없다. 현상황에 대해 이해하고 두 아키텍처를 적절히 혼용하여 단점을 희석시키고 장점을 부각시킬 수 있는 방법을 찾는 것이 정답이다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/community/questions/945855/db-%EC%97%94%ED%8B%B0%ED%8B%B0%EC%99%80-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EB%A6%AC?srsltid=AfmBOopalFese7Hi3bUdVAWZxFzgUFZvfgTfbXLS4PSC-IbWBsosGuDg&quot;&gt;좋은 설계를 만족시키기 위해선 ORM과 작별 인사를 해야 한다&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=18C2A56ialY&amp;#x26;ab_channel=%EC%A0%9C%EB%AF%B8%EB%8B%88%EC%9D%98%EA%B0%9C%EB%B0%9C%EC%8B%A4%EB%AC%B4&quot;&gt;도메인 모듈 분리 시 Transaction + JPA 활용 방안&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;파사드나 애그리게이트가 서로 다른 문제를 해결하는 것인가?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;누군가는 비즈니스의 복잡도를 안고 중개하는 역할을 해야 한다. 그런 관점에서 보면 파사드와 애그리게이트는 서로 같은 문제를 해결하는 것이다.&lt;/li&gt;
&lt;li&gt;복잡도를 누구에게 전가하여 중개를 어느 계층에서 어떤 방법으로 복잡도를 관리할지 스스로 어떤 규칙을 정하고 지키는 것이 핵심 아닐까&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rest docs 적용&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;느낀점&quot; style=&quot;position:relative;&quot;&gt;느낀점&lt;a href=&quot;#%EB%8A%90%EB%82%80%EC%A0%90&quot; aria-label=&quot;느낀점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;실무에서 레이어간 의존성에 대한 고민만 해왔지 멀티 모듈을 사용한 의존성에 대한 고려는 엄청 새로웠다. MSA를 대비한 도메인간 멀티모듈을 적용하는 것과 모놀리식 환경에서 레이어간 멀티모듈을 적용하여 각 모듈이 의존하는 의존성을 완전히 분리하여 서로 침범할 수 없도록 강제하는 것들을 배울 수 있었다.&lt;/p&gt;
&lt;p&gt;소프트웨어 아키텍처와 비즈니스 로직 복잡도에 대한 내용들은 &lt;code class=&quot;language-text&quot;&gt;구조(또는 방법)을 선택하였을 때 얻는 것과 잃는 것은 무엇인가? 당위성을 설명할 수 있는가?&lt;/code&gt;를 생각하게 되는 주제들이였다.&lt;br&gt;
내가 해결해야 할 문제가 무엇인지 이해하지 못하고 특정 기술적인 단어에 집착하여 단편적인 문제 해결 방법들만 고려했던 것은 아닐까?&lt;/p&gt;
&lt;p&gt;문제를 해결하기 위한 정형화된 방법을 찾는것이 아니라 현재 환경에서 맞닥뜨린 문제를 이해하고 발전시킬 부분과 포기할 부분을 신중히 선택해야한다.&lt;br&gt;
이번 학습을 통해 토이 프로젝트에 대해 어떤 아키텍처를 사용하게 되었는지 &lt;a href=&quot;/2024y/architecture/architecture&quot;&gt;소프트웨어 아키텍처에 대한 고민&lt;/a&gt;도 작성하게 되었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://eternity-object.tistory.com/43?fbclid=IwY2xjawGQfTlleHRuA2FlbQIxMQABHSPsDzykKq2BxRJ-WM1EIwKqEhF1G-CRBmz-rQxNdo9IexP1xGKW-mFv0g_aem_cGdvU2_P4v_qNGpZVA3QjQ&quot;&gt;조영호님의 설계 트레이드오프&lt;/a&gt; 참고&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;회사를 구해서 2개월만 진행했다.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[좋은 코드, 고품질 코드란?]]></title><description><![CDATA[…]]></description><link>https://jdalma.github.io/2023y/oop/</link><guid isPermaLink="false">https://jdalma.github.io/2023y/oop/</guid><pubDate>Tue, 16 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;들어가기전에&quot; style=&quot;position:relative;&quot;&gt;들어가기전에&lt;a href=&quot;#%EB%93%A4%EC%96%B4%EA%B0%80%EA%B8%B0%EC%A0%84%EC%97%90&quot; aria-label=&quot;들어가기전에 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;소스 코드를 읽으면서 종종 그것이 어떤 의미인지 이해하지 못하는 경우가 있다. 사실 이런 문제의 원인은 매우 간단하다.&quot;&lt;br&gt;
&quot;코드를 완벽하게 이해하기 위해 &lt;strong&gt;필요한 기본적인 기술적 소양과 능력이 부족하기 때문이다.&lt;/strong&gt;&quot;&lt;br&gt;
디자인 패턴의 아름다움 1장&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;작성되어 있는 코드를 분석하다보면 나에게 생소한 디자인 패턴이 적용되어 있거나, 연속된 추상화 등등 이해가 힘들 때 &lt;code class=&quot;language-text&quot;&gt;&quot;복잡한 도메인,비즈니스를 많이 접해보지 못하고 기술적인 수준이 낮아서 그런건가?&quot;&lt;/code&gt;라는 생각을 한적이 있다.&lt;br&gt;
&lt;strong&gt;좋은 코드를 보고 그 원리를 분석하여 내 것으로 만들고 싶어도 기술의 이해가 없다면 본질을 이해하지 못해 흡수는 커녕 작성자의 의도 조차 파악할 수 없을 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;하지만 내가 좋은 코드를 보고 좋은 코드라고 판단할 수 있을까?&lt;br&gt;
좋은 코드를 판단하지 못하고, 좋은 코드를 작성하지 못하는 이유는 &lt;strong&gt;어떤 코드가 고품질의 코드인지 알지 못하기 때문이다.&lt;/strong&gt;&lt;br&gt;
아래와 같은 질문을 어느정도로 생각하나?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모듈의 구분이 명확하고 코드 구조가 높은 응집도, 낮은 결합도를 충족하는가?&lt;/li&gt;
&lt;li&gt;코드가 고전적인 설계 원칙을 따르고 있는가? (SOLID, DRY, KISS, YAGNI, LoD)&lt;/li&gt;
&lt;li&gt;디자인 패턴이 제대로 적용되었고, 과도하게 설계되지는 않았는가?&lt;/li&gt;
&lt;li&gt;다른 사람이 읽기 편한가?&lt;/li&gt;
&lt;li&gt;코드를 재사용할 수 있는가? 혹시 &apos;바퀴를 재발명&apos;하고 있지는 않은가?&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;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;컴퓨터가 이해할 수 있는 코드는 바보라도 작성할 수 있다. 훌륭한 프로그래머는 사람이 이해할 수 있는 코드를 작성한다.&quot; - 마틴 파울러&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위에서 말하는 좋은 코드, 고품질 코드를 작성하기 위한 방법들에 대해 알아보자.&lt;br&gt;
대부분의 내용은 아래의 책과 영상, 글을 참고하였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/77125987&quot;&gt;모던 자바 인 액션&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000202093794&quot;&gt;디자인 패턴의 아름다움&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UwAoUshVpgM&amp;#x26;ab_channel=SpringCampbyKSUG&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;KSUG&lt;/code&gt; 대규모 엔터프라이즈 시스템 개선 경험기 - 1부 -&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=u_y6UGzOPUk&amp;#x26;ab_channel=SpringCampbyKSUG&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;KSUG&lt;/code&gt; 대규모 엔터프라이즈 시스템 개선 경험기 - 2부 -&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id=&quot;객체지향-프로그래밍&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;객체지향 프로그래밍&lt;/strong&gt;&lt;a href=&quot;#%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot; aria-label=&quot;객체지향 프로그래밍 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;객체지향 프로그래밍의 프로그래밍 단위는 &lt;strong&gt;클래스&lt;/strong&gt;또는 &lt;strong&gt;객체&lt;/strong&gt;이고,&lt;br&gt;
절차지향 프로그래밍의 프로그래밍 단위는 &lt;strong&gt;함수&lt;/strong&gt;이며,&lt;br&gt;
함수형 프로그래밍의 프로그래밍 단위는 &lt;strong&gt;스테이트리스 함수&lt;/strong&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;객체지향 프로그래밍은 &lt;code class=&quot;language-text&quot;&gt;클래스의 관점&lt;/code&gt;에서 생각해야 한다.&lt;br&gt;
처음부터 복잡한 프로세스를 메서드로 분해하는 대신 &lt;strong&gt;어떻게 비즈니스 모델링을 할지 먼저 생각한 후 요구 사항을 클래스로 구성하고 클래스 간의 상호 작용을 설정&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p&gt;객체지향 프로그래밍 언어를 사용하여 모든 코드를 클래스에 넣기만 하면 그것이 바로 객체지향 프로그래밍이라고 생각하지만 절차지향 프로그래밍 스타일로 작성한 경우가 대부분일 것이다.&lt;br&gt;
예를 들어, 많은 비즈니스 로직을 &lt;code class=&quot;language-text&quot;&gt;Service&lt;/code&gt; 계층에 몰아넣고 객체지향이다 라고 말하는 것과 같이 말이다.&lt;br&gt;
대부분의 백엔드 개발자들은 (표현 계층, 논리 계층, 데이터 계층) MVC 아키텍처에 익숙하여 &lt;strong&gt;빈약한 도메인 모델&lt;/strong&gt;을 의식하지 못하며 개발한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/24d4fc16bd45ff5d24a15feaad7571d2/a3ebb/domainModel.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC/klEQVR42iWS+0/TZxjFv8mS7cf9Fe4vMFvUZMsSl2VZ5i1j8zapS0ARxUthA4qdtqKICG51EkWUyq0IpRSERaAXarm1pdIWLXQtKxS1K7cW0GUo/fi2/HBy3vPmzclznvdIKxEX0cAgi2EHsdAw0Sk7C9MjaY4G7MSCQyTmxthYfEZyZQpWAyRTWBGIT5JMTG7qxFT6jbQy6+SpzYDT3MrEcBdhr4lZn4m5ZxaC7sc4TA8JuXtZj3mZdP6Fb6gTr70D/2g3L/0DhD39uAf0zPjMJJeeIyWEocPShqW7gb72WkzG+1geNTDQ04TL2k5/p1awnrfzEwz16hjp02HueoBZ3KeG8A4asQrtd/RAynA+7MJs7WZgsBer5RE2ez+ZJ1Vs2XqQz77YzafbtpNzNAPifv7718P6kp95t5G4Q8e6iMzq3/A6mI6ejvwyOEpjWzONeh0NLY2or15l21cHkD7+nA8++gRJ+pC9334JCT+L08PEI2N4e27j1P7G6oyDd2Kq9ZiPdwsTm4avgg6a9ZuGrfV3OXwok4p798i/oGLXvj0ckB1BnpPBxoKPNfE5b8Quwz1anpeeI3ango2oh+SyP72/FEuRqSHqmrRoW+qpKb/B7q+/4XrtHRRFMlTqPMp+V5N/fA//R8dZDo+wJBAR+wvUXCei+5PXIdEC0ZS1mVHWpu1IAY9FRK2nWXcfTWUpR3/KQGdoQlWSw8UrSspuqCk8sS9tlog4iYuYcWGwKKa1GTQ4HtcRFD8dcnUSMmuR3LYOCuTHURblofg1F3nuEUoKTyHPk1EoP4b8TBaXFLm8EBWZnTAzI/DPeJ/QNkwtGrrqrjE7+QS/oZZAyy2kkb5mFPnZXD6fh6r4JFcunKVUnC8rz1B+8RznC45Re/MS+vo/OLz/O7Jk3/PL6Z8pEMjOzED24y5ysg7yw84d3FUVILmsrfiGjYzbDaLg7YxZ29JFTXFKewY7ROGNdD+spqqsCM01BdVVSqorldRoVAJqblaUUFVejKFRw3tclqDT7tAnIgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;domainModel&quot;
        title=&quot;&quot;
        src=&quot;/static/24d4fc16bd45ff5d24a15feaad7571d2/1cfc2/domainModel.png&quot;
        srcset=&quot;/static/24d4fc16bd45ff5d24a15feaad7571d2/3684f/domainModel.png 225w,
/static/24d4fc16bd45ff5d24a15feaad7571d2/fc2a6/domainModel.png 450w,
/static/24d4fc16bd45ff5d24a15feaad7571d2/1cfc2/domainModel.png 900w,
/static/24d4fc16bd45ff5d24a15feaad7571d2/a3ebb/domainModel.png 1333w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=u_y6UGzOPUk&amp;#x26;ab_channel=SpringCampbyKSUG&quot;&gt;이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;풍부한 도메인 모델&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 클래스는 저장소 계층과 통신하여 도메인 모델을 만들어낸다.&lt;/li&gt;
&lt;li&gt;Service 클래스는 여러 도메인 모델의 비즈니스 논리를 결합한다.&lt;/li&gt;
&lt;li&gt;Service 클래스는 기능과 무관한 타 시스템과의 상호 작용을 담당한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;풍부한 도메인 모델&lt;/strong&gt;은 데이터와 비즈니스 논리가 하나의 클래스에 포함되며 전형적인 객체지향 프로그래밍에 속한다.&lt;br&gt;
즉, &lt;code class=&quot;language-text&quot;&gt;Service&lt;/code&gt;에 비즈니스 로직을 작성하는 것이 아니라 각 &lt;code class=&quot;language-text&quot;&gt;도메인 모델&lt;/code&gt;에 책임을 부여하는 것이다.&lt;br&gt;
이에 기반한 &lt;strong&gt;DDD&lt;/strong&gt; 개발방식은 &lt;strong&gt;비즈니스 시스템을 분리하고, 비즈니스 모듈을 분할하고, 비즈니스 도메인 모델과 상호 작용을 정의하는 방법을 설계할 때 사용된다.&lt;/strong&gt;&lt;br&gt;
DDD를 잘 하기 위한 핵심은 비즈니스에 익숙하지 않으면 합리적인 도메인 설계를 얻을 수 없기 때문에 DDD의 개념을 공부하고 익히는 것이 아니라 &lt;strong&gt;비즈니스에 친숙해지는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;그리고 많은 백엔드 개발자들은 &lt;code class=&quot;language-text&quot;&gt;Setter&lt;/code&gt;는 열심히 막으려 하지만 &lt;code class=&quot;language-text&quot;&gt;Getter&lt;/code&gt;는 합리화한다.&lt;br&gt;
도메인 모델의 속성이 객체일 때 클라이언트 코드가 참조한다면 신경써야할 것이 많다.&lt;br&gt;
예를 들어, &lt;code class=&quot;language-text&quot;&gt;Getter&lt;/code&gt;를 통해 컬렉션 내에있는 객체를 수정한다면? 수정할 수 없는 불변 컬렉션이였지만 가변 컬렉션으로 캐스팅하여 수정한다면?&lt;br&gt;
&lt;strong&gt;객체 전파&lt;/strong&gt;, &lt;strong&gt;방어적 복사&lt;/strong&gt;, &lt;strong&gt;앨리어싱 에러&lt;/strong&gt;들도 고려해야 한다.&lt;/p&gt;
&lt;p&gt;시스템이 복잡할수록 코드 재사용성과 유지 관리 용이성에 대한 요구 사항은 점점 높아지기 때문에 초기 설계에 더 많은 시간과 에너지를 투자하여 &lt;strong&gt;풍부한 도메인 모델에 기반한 DDD 개발 방식에 익숙해져야 할 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;객체지향과 절차지향? 풍부한 도메인 모델과 빈약한 도메인 모델?&lt;/h4&gt;  
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;항상 객체지향 프로그래밍과 풍부한 도메인 모델이 옳지만은 않다.&lt;/code&gt;&lt;br&gt;
요구사항이 간단하고 전체적인 작업 흐름이 가지를 뻗어나가지 않고 고정된 형태를 띄는 경우에는 절차지향 프로그래밍이 적절할 수 있다.&lt;br&gt;
그리고 개발하는 시스템의 비즈니스가 비교적 단순할때는 굳이 복잡하고 풍부한 도메인 모델을 설계하기 보다는 빈약한 도메인 모델이 더 적합하다.&lt;br&gt;
하지만 복잡한 시스템일때 많은 개발자들이 풍부한 도메인 모델을 쉽게 적용할 수 있진 않을 것이다.&lt;br&gt;
&lt;strong&gt;풍부한 도메인 모델은 설계하기가 훨씬 까다롭고, 빈약한 도메인 모델을 기반으로 하는 전통적인 개발 방식은 수년 동안 사용되어 왔고 대부분의 개발자에게 단단하게 박혀 있기 때문에 쉽게 적용하기에는 힘들 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;추상-클래스와-인터페이스&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;추상 클래스와 인터페이스&lt;/strong&gt;&lt;a href=&quot;#%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4&quot; aria-label=&quot;추상 클래스와 인터페이스 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;개발하면서 추상화를 고민할 때 추상 클래스를 쓸지, 인터페이스를 쓸지 잠깐 고민하게 된다.&lt;br&gt;
하지만 항상 인터페이스를 쓰게 되는데 그 이유는&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;인터페이스도 구현 메서드를 가질 수 있다.&lt;/li&gt;
&lt;li&gt;다중 상속이 안되니 정말 강한 관계라고 의도를 표현해야 할 상황인지 판단하지 못 하는 경우가 많다.&lt;/li&gt;
&lt;li&gt;추상 클래스는 부모 클래스를 같이 초기화해야 한다.&lt;/li&gt;
&lt;li&gt;상속 단계가 깊어지는 것이 부담스럽다.&lt;/li&gt;
&lt;li&gt;상속은 자식 클래스가 부모 클래스에게 강하게 결합되기 때문에 캡슐화를 위반한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;클래스 상속의 관점에서 &lt;strong&gt;추상 클래스는 상향식 설계 방식&lt;/strong&gt;이며 &lt;code class=&quot;language-text&quot;&gt;is-a&lt;/code&gt; 관계를 나타낸다.&lt;br&gt;
&lt;strong&gt;인터페이스는 반대로 하향식 설계 방식&lt;/strong&gt;이며 깊어지지 않고 넓어진다. &lt;code class=&quot;language-text&quot;&gt;has-a&lt;/code&gt; 관계를 나타낸다. 그리고 프로토콜 또는 규약의 집합으로 사용자에게 제공되는 &lt;strong&gt;기능의 목록&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;추상 클래스&lt;/h3&gt;  
&lt;ol&gt;
&lt;li&gt;인스턴스화할 수 없다.&lt;/li&gt;
&lt;li&gt;인스턴스 변수와 구현 메서드를 포함할 수 있다.&lt;/li&gt;
&lt;li&gt;하위 클래스는 추상 클래스의 모든 추상 메서드를 구현해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타입 계층 구조를 제공한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 559px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d0059197ae31797013808436478bfa80/a65ce/abstract.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABz0lEQVR42pVSi3LaMBD0//9VHzNliAMYuxRMCMGhhGJj49hYkvXYniTy6mSSqWZ2ZN2dVufdC/DBkr1B1yp0Z422kRBM47MVvBc0xu9lwfD9W4zhcI6vXyJkd+Wb/H8Raq1wzDnuVgzZmmNzy7H/zaGUpLz5jNA8Q0oJxjoIwdELDcENegJntPcWwuXt/vreC6HrxsCQPFJq0ku4b9vEv408xSwYI025hL/v4QhtMokzjK6XuA5TXJFegx8JosmaTHmrKes0wqsU49GNqw/DBQaDhDDDfld7QpIKq/QRi3mFZVrTXjrcUMz+7mvCc6ux+HVCamsXtaufzwo61zjsuSeUvXTit7VBXSmcyh7nhhzOSavuotOFUHCN4iDQ1NqhLISrPVKtHStHKARpZrwASinY85NRigzS+mX2rMNSeR1snHN2+QNF+vt4EE1W9LJyh59JhjhaE5EnmCUbxNO1M4newjhcoshbl9ttK8pvL50rqtugPnEE49E9OesZppMdxuN72ImwjSXxHtNoR6/DGTQc3GL/cHa126whwtzJYc2KJg/0GENQHjQRGCe8/a4KP29aGdKRzgQpjUOZG6edXY+VxpHq7T07p8UfGrlG4y/9ep5IyaY04gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;abstract&quot;
        title=&quot;&quot;
        src=&quot;/static/d0059197ae31797013808436478bfa80/a65ce/abstract.png&quot;
        srcset=&quot;/static/d0059197ae31797013808436478bfa80/3684f/abstract.png 225w,
/static/d0059197ae31797013808436478bfa80/fc2a6/abstract.png 450w,
/static/d0059197ae31797013808436478bfa80/a65ce/abstract.png 559w&quot;
        sizes=&quot;(max-width: 559px) 100vw, 559px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;추상 클래스를 사용하는 위의 다이어그램에서 만약 &lt;code class=&quot;language-text&quot;&gt;노래 부를 수 있는&lt;/code&gt; 타입을 추가해야한다면 어떻게 해야할까?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/64093f70a8db0a86766c6d02caa8a4a7/d9b5d/abstract2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.666666666666668%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAyklEQVR42n2P4W7DIAyE8/4PuCaNqm5romohlUoggMFwc1A69U9mCQEfd/bR4KByLiglIxLj0s8YBy20gJnxXzXvF+aMEAjO+WrcVkqM1bIw/mNEBO+D7LEOPmyoJoeuVbheHmIshyl+7iv684y+m2Ftqqzs8iZSkukkDw7LEhB8hjUJxjjhQZJQbU4hwb90etdJM7N4uNVXXYwJzTgYdKcJ507VqdZEfF2fknSSBArtSYkp4vtTmJy3VJt+0RHDTdcfVfYxYbxp/ALSrYLYUwOjFwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;abstract2&quot;
        title=&quot;&quot;
        src=&quot;/static/64093f70a8db0a86766c6d02caa8a4a7/1cfc2/abstract2.png&quot;
        srcset=&quot;/static/64093f70a8db0a86766c6d02caa8a4a7/3684f/abstract2.png 225w,
/static/64093f70a8db0a86766c6d02caa8a4a7/fc2a6/abstract2.png 450w,
/static/64093f70a8db0a86766c6d02caa8a4a7/1cfc2/abstract2.png 900w,
/static/64093f70a8db0a86766c6d02caa8a4a7/d9b5d/abstract2.png 1224w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이와 같이 경우의 수는 2의 제곱으로 늘어나 복잡도가 기하급수적으로 높아진다.&lt;br&gt;
이 문제는 &lt;strong&gt;인터페이스의 &lt;code class=&quot;language-text&quot;&gt;합성&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;인터페이스&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;위임&lt;/code&gt;을 사용하는게 적절하다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 868px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9a91643c6697ee1dfaf23315b405e49b/748b0/useInterface.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 98.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAADEklEQVR42nVUiXajSAz0/3/XTF4yscfOrOMDHF/c92HAmKu2uhmIvcnyXj8aWi2VSiVNTOMCVXHlss0C4uk6PDxt0yHwGuxUj3YOTscUadziu2diGBl+/nzD09MSjl3+j0PAtkq8vm7x48cci8UBaSItvzr03RbrlYP3pQXf+T5qU3dEXxFdiOU/BrZrH3HUfY9wpwRQNp40FvseYfcF4eEjwfz3CR+7CBs6PB2i0fbefuJ7BY0cppvB1CNUVfUlalGU0M4RDnuXdhc4VkbEMa7X61eEbdvyQn8g9ueziTS9jAYielmWyPMr6rr5z/UOSXKBZbmPDpvm0zCJM1yLcvwWTsS5eN+nN7zLspJ3hn+9w7aRldVOEbkk6RsNUdinLpwJm7quHwtFDNo5ppQseUfZWihyOiyKG0zTkcTv1QS/Z0dMfx3IUY/SMFxSkNPGfUi5uoHcp5hNj1wHzGc6kbaYCMi+HxIFDdSIoo1xPKREK4UG34skx74XElU7clfdOhbpgg0VYhgFFCWCKLBMuf2bsm1dZLqqYiL0i78c1mPq96LXdZvBfdrqlNEZOtNPkxsmEn5VjyRvNio8LxirLs4GG/GEYSKrmue5/A6CCNvt7rMoQ/QBief5JLcYDYb//b5CFMXSyaiKJEU4fBPTZDgorw18t0FGCVpmjre5KqsYhhwMfst0agau76p9L5+7Thn2ZdFB1xry2LIoOZ6ftpREzM4ATKNFHPYFiYKak6clxw17WuMcOMO1rzznGQNP7mWwmB85TRb48+eA9/czXl5WUDmyPA9E2Dt07IaBa64bFuzt6esHzqeCQRu2bv3ocL2yMZspXCqniob5fI/XXxu8PK+Jqm9PQ8/w9naUduu1jt2uv/PyvMJmZWIyyKC8dkwhYDQhHY8RhfYA14GkIYl62RhawUw0dkZIuXh8B/Bcgbwj982nw1vZsUv2mE4VLJc6VmsLtsNC+XTqfqYc+CX2Hy40LaKgUwZOEcfkjxyLdZdyRw4yHPceEWWyrz234iCl9oIOefY4FOS7w8NMFDr8F3FD+hr9f1tHAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;useInterface&quot;
        title=&quot;&quot;
        src=&quot;/static/9a91643c6697ee1dfaf23315b405e49b/748b0/useInterface.png&quot;
        srcset=&quot;/static/9a91643c6697ee1dfaf23315b405e49b/3684f/useInterface.png 225w,
/static/9a91643c6697ee1dfaf23315b405e49b/fc2a6/useInterface.png 450w,
/static/9a91643c6697ee1dfaf23315b405e49b/748b0/useInterface.png 868w&quot;
        sizes=&quot;(max-width: 868px) 100vw, 868px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Flyable { fun fly() }
interface Tweetable { fun tweet() }

class FlyAbility : Flyable { override fun fly() = TODO(&amp;quot;do fly&amp;quot;) }
class TweetAbility : Tweetable { override fun tweet() = TODO(&amp;quot;do tweet&amp;quot;) }

class Pigeon(
    private val flyAbility: Flyable,             // 합성, 의존성 주입
    private val tweetAbility: Tweetable          // 합성, 의존성 주입
) : Flyable, Tweetable {
    override fun fly() = flyAbility.fly()       // 위임
    override fun tweet() = tweetAbility.tweet() // 위임
}

class Penguin(
    private val tweetAbility: Tweetable         // 합성, 의존성 주입
) : Tweetable {
    override fun tweet() = tweetAbility.tweet() // 위임
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;is-a&lt;/code&gt;관계는 합성과 인터페이스의 &lt;code class=&quot;language-text&quot;&gt;has-a&lt;/code&gt;관계로 대체될 수 있고, 다형성은 인터페이스를 사용하여 달성할 수 있으며,&lt;br&gt;
코드 재사용은 합성과 위임으로 대체할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;보통 다단계 클래스 계층을 사용한 경우 위임을 통해 다단계 클래스 계층을 제거하는 편이 좋다.&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;켄트 백의 구현 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;그럼 어떤 경우에 상속이 사용되면 좋을까? &lt;strong&gt;타입 계층 구조가 필요할 때 이다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Flyable { fun fly() }
interface Tweetable { fun tweet() }

abstract class Bird (val name: String) : Flyable, Tweetable

class Pigeon() : Bird(&amp;quot;비둘기&amp;quot;) {
    override fun fly() = TODO()
    override fun tweet() = TODO()
}

class Penguin() : Bird(&amp;quot;펭귄&amp;quot;) {
    override fun fly() = throw OperationNotSupportedException()
    override fun tweet() = TODO()
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;인터페이스로 행위를 일반화하고, 추상 클래스로 계층 구조를 나누었고, 구현 계층에서 행위를 구현하였다.&lt;br&gt;
추상 클래스는 대표적으로 템플릿 메서드 패턴이 필요하거나 상태를 공유해야 할 필요가 있는 경우 사용할 수 있다.&lt;br&gt;
이런 하위 클래스 구조는 &lt;code class=&quot;language-text&quot;&gt;&quot;이 객체는 상위 클래스와 같다. 이 부분만 제외하면..&quot;&lt;/code&gt; 이라고 말하는 것과 같다.&lt;/p&gt;
&lt;h3&gt;합성을 사용할지 상속을 사용할지?&lt;/h3&gt;  
&lt;p&gt;합성을 더 많이 사용하고 상속을 덜 사용하도록 권장하지만, &lt;code class=&quot;language-text&quot;&gt;언제나 합성이 옳은것도 아니고 상속이 항상 쓸모없는 것도 아니다.&lt;/code&gt;&lt;br&gt;
클래스 간의 상속 구조가 안정적이어서 쉽게 변경되지 않고 상속 단계가 2단계 이하로 비교적 얕아 상속 관계가 복잡하지 않다면 과감하게 &lt;strong&gt;상속&lt;/strong&gt;을 사용할 수 있다.&lt;br&gt;
또한, 콘크리트 클래스의 특정 함수를 재정의하거나 (얕은)타입 계층 구조가 필요할때도 &lt;strong&gt;상속&lt;/strong&gt;을 사용할 수 있다.&lt;br&gt;
상속이 구현을 공유하기 위한 기법이므로 공통된 구현을 갖는 경우에 효과적으로 사용될 수 있지만&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;하위 클래스를 사용하면 되돌리기가 쉽지 않다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하위 클래스를 이해하기 위해서는 먼저 상위 클래스를 이해해야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;상속을 올바르게 사용하기 위한 키 포인트는 하위 클래스를 작성할 때 각 메소드를 따로 오버라이딩 할 수 있게 &lt;strong&gt;상위 클래스를 여러 개의 메소드로 잘게 쪼개는 것이다.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;하위 클래스를 사용한 경우와 위임을 사용한 경우를 모두 상상해보고, 특정 전략을 사용한 경우 다른 전략에 비해 상대적 강점이 있는가?를 고민하여 실제로 구현을 해보고 코드가 향상되었는지 확인해봐야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;상속&lt;/strong&gt;은 반드시 하위 클래스가 상위 클래스의 &lt;code class=&quot;language-text&quot;&gt;&quot;진짜&quot; 하위 타입인 상황&lt;/code&gt;에서만 쓰여야 하며 클래스 B가 클래스 A와 is-a 관계일 때만 사용해야 한다. &lt;code class=&quot;language-text&quot;&gt;&quot;B가 정말 A인가?&quot;&lt;/code&gt;&lt;br&gt;
그리고 클래스의 행동을 확장하는 것이 아니라 &lt;strong&gt;정제&lt;/strong&gt; 할 때다.&lt;br&gt;
&lt;strong&gt;확장(합성)&lt;/strong&gt; 이란 새로운 행동을 덧붙여 기존의 행동을 부분적으로 보완하는 것을 의미하고, &lt;strong&gt;정제(상속)&lt;/strong&gt; 란 부분적으로 불완전한 행동을 완전하게 만드는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;시스템이 불안정하고 타입 계층이 깊고 복잡하면 &lt;strong&gt;합성&lt;/strong&gt;을 사용해야 한다.&lt;br&gt;
인터페이스는 &lt;code class=&quot;language-text&quot;&gt;&quot;여기까지가 내가 원하는 것이고, 이외의 내용은 상관하지 않는다.&quot;&lt;/code&gt;라고 이야기하는 것과 같다.&lt;br&gt;
그렇기에 구현을 바꾸는 것은 쉽지만, 인터페이스 자체를 바꾸기는 쉽지 않다.&lt;br&gt;
(기존 인터페이스를 확장하는 버전 인터페이스를 통해 기능을 추가할 수 있긴 하다.)&lt;/p&gt;
&lt;p&gt;추상 클래스는 내부 상태를 공유할 수 있으며 (기본 구현을 사용할 수 있는 길이 열려있는 한,) 기존 설계를 망가뜨리지 않고 새로운 연산을 얼마든지 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;인터페이스 계층과 클래스 계층은 서로 배타적인 것이 아니기에 항상 상속을 배제하고 합성을 사용하려 하지마라.&lt;br&gt;
상속 자체가 문제가 아니라 &lt;code class=&quot;language-text&quot;&gt;잘&lt;/code&gt; 적용하는 것이 문제다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;소프트웨어는 유연해야 하지만, 유연성에는 비용이 들고, 어떤 부분에서 유연성이 필요할지 예측하기란 쉽지 않다.&quot;&lt;br&gt;
&quot;따라서 실제 필요해지는 경우에만 시스템에 유연성을 부여하자.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h1 id=&quot;단일-책임-원칙&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;단일 책임 원칙&lt;/strong&gt;&lt;a href=&quot;#%EB%8B%A8%EC%9D%BC-%EC%B1%85%EC%9E%84-%EC%9B%90%EC%B9%99&quot; aria-label=&quot;단일 책임 원칙 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;클래스와 모듈은 &lt;strong&gt;하나의 책임 또는 기능만을 가지고 있어야 한다&lt;/strong&gt;는 설계 원칙이다.&lt;br&gt;
즉, 거대하고 포괄적인 클래스를 설계하는 대신, &lt;strong&gt;작은 단위와 단일 기능을 가진 클래스를 설계&lt;/strong&gt;해야 한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;책임&lt;/code&gt;이란것은 이 코드가 변화하는 이유가 &lt;strong&gt;한 가지&lt;/strong&gt;이도록 만드는 것이라고 생각하면 쉽다.&lt;/p&gt;
&lt;h4&gt;그럼 책임의 단위는 어떻게 판단해야할까?&lt;/h4&gt;
&lt;p&gt;실제 소프트웨어 개발에서 클래스에 단일책임이 있는지를 판별하기란 쉬운 일이 아니다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class UserInfo (
    val userId: Long,
    val username: String,
    val email: String,
    val telephone: String,
    val provinceOfAddress: String,  // 도
    val cityOfAddress: String,      // 시
    val regionOfAddress: String,    // 구
    val detailedAddress: String     // 상세 주소
)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 코드에서 두 가지 관점이 존재할 수 있다.&lt;br&gt;
&lt;strong&gt;첫 번째.&lt;/strong&gt; 사용자와 관련된 정보가 포함되어 있고 모든 속성과 메서드가 사용자와 같은 비즈니스 모델에 속해 있어 단일책임원칙을 만족한다.&lt;br&gt;
&lt;strong&gt;두 번째.&lt;/strong&gt; 사용자와 주소는 서로 다른 책임이기 때문에 주소 정보를 독립적인 &lt;code class=&quot;language-text&quot;&gt;UserAddress&lt;/code&gt; 클래스로 분할하여 &lt;code class=&quot;language-text&quot;&gt;UserInfo&lt;/code&gt;클래스는 주소 정보를 제외한 다른 정보만 보유하도록 해야한다. 단일책임원칙을 만족하지 않는다.&lt;br&gt;
어느쪽이 맞다고 생각되는가?&lt;/p&gt;
&lt;p&gt;만약 사용자의 주소 정보가 단순 표시에만 사용되며, 다른 사용자 속성과 함께 사용된다면 &lt;strong&gt;첫 번째 관점&lt;/strong&gt;에 납득할 수 있다.&lt;br&gt;
하지만 주소 정보가 표시를 위해 사용될 뿐만 아니라 전자 상거래의 물류 배송 정보로 사용된다면 &lt;strong&gt;두 번째 관점&lt;/strong&gt;이 타당할 것이다.&lt;/p&gt;
&lt;p&gt;위의 예를 통해, &lt;strong&gt;동일한 클래스라 할지라도 다른 응용 시나리오나 다른 단계의 요구 사항에 따라 클래스의 책임이 단일한지 아닌지를 판단하는 것이 다를 수 있다는 점을 알 수 있다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;개인마다 비즈니스 수준의 관점이 다르고, 그 비즈니스 수준은 계속 변화하기 때문이다.&lt;/code&gt;&lt;br&gt;
현재 요구 사항에서는 단일책임원칙을 만족할 수 있지만, 응용 시나리오가 변경되거나 요구사항이 이후 달라질 경우에는 해당 클래스의 설계가 단일책임원칙을 지키지 못할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;개방-폐쇄-원칙&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;개방 폐쇄 원칙&lt;/strong&gt;&lt;a href=&quot;#%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99&quot; aria-label=&quot;개방 폐쇄 원칙 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;고전적인 패턴 22개 중 대부분은 코드 확장성 문제를 해결하기 위해 고안되었고, 이 패턴들의 중요한 설계 원칙이 바로 &lt;strong&gt;개방 폐쇄 원칙&lt;/strong&gt;이다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;확장할 때는 개방, 수정할 때는 폐쇄&lt;/strong&gt; 원칙으로도 불린다.&lt;br&gt;
새로운 기능을 추가할 때 기존의 모듈, 클래스, 함수를 수정하기 보다는 기존 코드를 기반으로 모듈, 클래스, 함수 등을 추가하는 방식으로 코드를 확장해야 한다는 뜻이다.&lt;/p&gt;
&lt;p&gt;아래의 다양한 알림 채널을 지원하는 경고 알림 기능으로 예를 들어보겠다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Alert (
    private val rule: AlertRule,
    private val notification: Notification
) {
    fun check(
        api: String,
        requestCount: Long,
        errorCount: Long,
        duration: Long
    ) {
        val tps = requestCount / duration
        if (tps &amp;gt; rule.getMatchedRule(api).getMaxTps()) {
            notification.notify(NotificationLevel.URGENCY, ...)
        }
        if (errorCount &amp;gt; rule.getMatchedRule(api).getMaxErrorCount()) {
            notification.notify(NotificationLevel.SEVERE, ...)
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 논리는 &lt;code class=&quot;language-text&quot;&gt;check()&lt;/code&gt;에 집중되어 있으며, 초당 트랜잭션 수가 미리 설정한 최댓값을 초과하거나 요청 오류 수가 최대 허용치를 초과하는 경우로 나뉘고 있다.&lt;br&gt;
만약 이때 &lt;code class=&quot;language-text&quot;&gt;초당 인터페이스 요청 횟수가 미리 설정된 최댓값을 초과할 경우, 경고 알림이 설정되며 통지가 발송된다&lt;/code&gt;라는 새로운 규칙을 추가해야 한다면 어떻게 해야할까?&lt;br&gt;
&lt;strong&gt;기존 Alert 도메인의 check 함수 수정은 불가피할 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fba97e4708d704952ba10568613a14c0/17d12/alertHandler.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 65.77777777777779%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACLklEQVR42m1TiXKqQBD0///qlXl5hwdG7sMEURREEBACJkKnXdCYVLZqapfZoenpHgZt2+K6qtcGaXJGnrUIgwLbTY599Cqe00ODumpEXXFskcRv8JaRiPU6wYHvlcwP7gHztEEQtPD9Go+PCobDJ0wmDnNnhCGB8gtgi/2uxdKtMPwl42Go4veDCve5wj5svgIeswaHGHCcPYEszGYL/HmU4b5kZHph1jHcrAvohs+6QERZAjHfS5Me8IqZJu/QlRCWGUGZ+5CfVgi3JyR7YEfmx7wD9Fc5RiOTYYhwnAiWxda9rGfYA+bZmcxs/B9pGI0NTKc2FNWDafEjdkTAd1EXbCtI0kLUzmbPUBQPk7GDjV98BUwPZKjuYOo77gEWdgxV2YhQ5htk6fuN4QVIkhxo2hqy7MIwtpQMX1vOCChNl5hOFvj318B4bGM8suB5R7r6qeFqWWA6dkWtqUeQJi5zvSlN0+BqTBIfKXjC1muOwSsZ1WRdoSzocpAjy8rbeNVVixOjKru9yM8894C3Oawq3D/fr8tH67q+z3yLbg12HGCTI2Do1GL+Ao0mqBT5qtfFeUPv7lXFFWFSryJvf8QdbP0SGkdlPltxTNZil6Ye5/Es6uPozPwWM+qlyBuhlyoH1Lv9sRMyPFHYGLaVQNd2sM2YDkf8G06iIArfYGixyBtaRHYR6yLm616Kb4BxVENnO12rtF9f8byiQVVv1IkAl/uuXV3zGJ/33wE/AK0D0Z35NXr7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;alertHandler&quot;
        title=&quot;&quot;
        src=&quot;/static/fba97e4708d704952ba10568613a14c0/1cfc2/alertHandler.png&quot;
        srcset=&quot;/static/fba97e4708d704952ba10568613a14c0/3684f/alertHandler.png 225w,
/static/fba97e4708d704952ba10568613a14c0/fc2a6/alertHandler.png 450w,
/static/fba97e4708d704952ba10568613a14c0/1cfc2/alertHandler.png 900w,
/static/fba97e4708d704952ba10568613a14c0/21482/alertHandler.png 1350w,
/static/fba97e4708d704952ba10568613a14c0/17d12/alertHandler.png 1666w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위와 같이 책임들을 분리하고 추상화를 한다면 단일 책임 원칙을 지키면서 확장에 유연한 설계가 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;AlertHandler&lt;/code&gt;의 구현체들에서 동일하게 사용되는 &lt;code class=&quot;language-text&quot;&gt;AlertRule&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;Notification&lt;/code&gt;을 부모에서 관리하여 확장시에는 &lt;code class=&quot;language-text&quot;&gt;check()&lt;/code&gt; 함수 구현에 집중할 수 있다.&lt;/li&gt;
&lt;li&gt;구현 클래스를 추가한다면 추가한 클래스에 대한 단위 테스트만 작성하면 된다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;AlertHandler&lt;/code&gt;의 추상화를 통해 알림 기능을 사용하는 클라이언트에서는 &lt;code class=&quot;language-text&quot;&gt;AlertHandler&lt;/code&gt;의 API 목록에만 의존하면 된다.&lt;/li&gt;
&lt;li&gt;기존 &lt;code class=&quot;language-text&quot;&gt;check()&lt;/code&gt; 함수의 파라미터를 &lt;code class=&quot;language-text&quot;&gt;ApiStatInfo&lt;/code&gt;로 묶어버려서 시그니처(파라미터) 수정에 유연해졌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;분기문은 도메인 그 자체라서 제거 자체가 불가능하다.&lt;br&gt;
도메인 기능의 일반화(추상화)를 통해 변화율이 다른 것을 찾아내서 외부로 밀어낼 수 있다.&lt;br&gt;
계속 외부로 밀어내어 의존성 주입을 받는 것이 최선이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;코드를 수정하는 것은 개방 폐쇄 원칙을 위반하는 것일까?&lt;/h3&gt;
&lt;p&gt;코드를 변경할 때 그 결과를 &lt;strong&gt;확장&lt;/strong&gt;으로 보아야 하는지, &lt;strong&gt;수정&lt;/strong&gt;으로 보아야 하는지 명확하게 구분하기 어렵기 때문에 개방 폐쇄 원칙을 이해하기가 어렵다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;TimeoutAlertHandler&lt;/code&gt; 구현 클래스가 추가된다면 이것은 &lt;strong&gt;확장&lt;/strong&gt;이라고 판단할 수 있지만, &lt;code class=&quot;language-text&quot;&gt;ApiStatInfo&lt;/code&gt;에 새 속성과 메서드를 추가하는것은 확장일까, 수정일까?&lt;/p&gt;
&lt;p&gt;클래스에 속성과 메서드가 추가되었기 때문에 분명히 &lt;strong&gt;수정&lt;/strong&gt;되었다고 판단할 수 있다.&lt;br&gt;
하지만 &lt;strong&gt;이 변경 사항이 기존의 속성을 변경하거나 메서드를 수정하지 않았기 때문에 속성이나 메서드 입장에서 보면 확장으로 간주될 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;개방 폐쇄 원칙의 기본적인 목적을 다시 떠올려보면, 코드의 수정이 기존에 작성되었던 코드와 단위 테스트를 깨뜨리지 않는 한, 이는 개방 폐쇄 원칙을 위반하지 않는다고 판단해도 무방하다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;핵심은 우리는 수정을 아예 안 하는 것이 아니라 &lt;strong&gt;수정을 가능한 한 상위 수준의 코드에서 진행&lt;/strong&gt; 하고, 코드의 핵심 부분이나 복잡한 부분, 공통 코드나 기반 코드가 &lt;strong&gt;개방 폐쇄 원칙을 충족하는 방향으로 노력해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;리스코프-치환-원칙&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;리스코프 치환 원칙&lt;/strong&gt;&lt;a href=&quot;#%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84-%EC%B9%98%ED%99%98-%EC%9B%90%EC%B9%99&quot; aria-label=&quot;리스코프 치환 원칙 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;만약 &lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;의 하위 유형인 경우, &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; 유형의 객체는 프로그램을 중단하지 않고도 &lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt;유형의 객체로 대체될 수 있다.&lt;/strong&gt;&lt;br&gt;
하위 유형 또는 파생 클래스의 객체는 프로그램 내에서 &lt;strong&gt;상위 클래스가 나타나는 모든 상황에서 대체 가능&lt;/strong&gt;하며, 프로그램이 원래 가지는 &lt;strong&gt;논리적인 동작이 변경되지 않으며 정확성도 유지&lt;/strong&gt;된다.&lt;br&gt;
한 마디로, 부모 클래스를 사용하는 기존의 클라이언트 코드가 자식 클래스로 대입하여도 문제 없이 작동하도록 하기 위해서 &lt;strong&gt;자식 클래스는 부모 클래스가 따르던 계약 사항을 자식도 따라야한다&lt;/strong&gt;는 것이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Parent (
    val p: Parameter
) {
    open fun doSomething() {
        // do something
    }
}

class Child(
    p: Parameter
): Parent(p) {
    override fun doSomething() {
        // do something
    }
}

class Client {
    fun run(obj: Parent) {
        obj.doSomething()
    }
}

class Main {
    fun test() {
        val p = Parameter()
        Client().run(Child(p))
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 &lt;code class=&quot;language-text&quot;&gt;Child&lt;/code&gt;는 리스코프 치환 원칙을 따르기 때문에, 해당 객체는 상위 클래스 객체가 나타나는 모든 위치에서 대체될 수 있다.&lt;/p&gt;
&lt;h3&gt;리스코프 치환 원칙과 다형성의 차이점&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;리스코프 치환 원칙은 결국 다형성인건가?&lt;/code&gt;라고 생각할 수 있다.&lt;br&gt;
보기에는 비슷하지만 &lt;strong&gt;실제로는 완전히 다른 의미이다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Parent (
    val p: Parameter
) {
    open fun doSomething() {
        if (p == null) {
            throw Exception()
        }
        // do something
    }
}

class Child(
    p: Parameter
): Parent(p) {
    override fun doSomething() {
        if (super.p == null) {
            throw Exception()
        }
        if (!super.p.isOk()) {
            throw ParameterStatusException()
        }
        // do something
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 &lt;code class=&quot;language-text&quot;&gt;Parent&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;p&lt;/code&gt;의 null만 확인하여 &lt;code class=&quot;language-text&quot;&gt;Exception&lt;/code&gt;을 전파하지만, &lt;code class=&quot;language-text&quot;&gt;Child&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;isOk()&lt;/code&gt;를 통해 &lt;code class=&quot;language-text&quot;&gt;ParameterStatusException&lt;/code&gt;을 전파한다.&lt;br&gt;
&lt;strong&gt;하위 클래스가 상위 클래스의 &lt;code class=&quot;language-text&quot;&gt;doSomething()&lt;/code&gt;을 대체하면서 프로그램의 논리적인 동작이 변경되었다.&lt;/strong&gt;&lt;br&gt;
클라이언트 코드에서 호출하는 자체로서는 리스코프 치환 원칙을 어기지 않는 것으로 보이지만, 설계 관점에서 살펴보면 &lt;code class=&quot;language-text&quot;&gt;Child&lt;/code&gt; 클래스의 설계는 원칙을 따르지 않는 것이다.&lt;/p&gt;
&lt;p&gt;아래의 경우도 리스코프 치환 원칙의 안티 패턴이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;하위 클래스가 구현하려는 상위 클래스에서 선언한 기능을 위반하는 경우&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;상위 클래스에서 제공하는 &lt;code class=&quot;language-text&quot;&gt;sort()&lt;/code&gt;는 금액 기준으로 정렬을 한다고 가정하자.&lt;/li&gt;
&lt;li&gt;하위 클래스가 &lt;code class=&quot;language-text&quot;&gt;sort()&lt;/code&gt;를 재정의하여 날짜 기준으로 정렬을 한다면 이것은 리스코프 치환 원칙을 어기는 것이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;추상클래스가 제공하는 &lt;code class=&quot;language-text&quot;&gt;abstract&lt;/code&gt;를 제외한 어떤 기능도 재정의를 금지하면 해결할 수 있다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하위 클래스가 입력, 출력 및 예외에 대한 상위 클래스의 계약을 위반하는 경우&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;상위 클래스에서 작업 시 오류가 발생하면 &lt;code class=&quot;language-text&quot;&gt;null&lt;/code&gt;을 반환하며, 값을 얻을 수 없을 때는 &lt;code class=&quot;language-text&quot;&gt;빈 컬렉션&lt;/code&gt;을 반환하지만&lt;/li&gt;
&lt;li&gt;하위 클래스에서 함수를 재정의하면서 null 대신 &lt;code class=&quot;language-text&quot;&gt;예외&lt;/code&gt;를 발생 시키고, 값을 얻을 수 없을 때는 빈 컬렉션이 아니라 &lt;code class=&quot;language-text&quot;&gt;null&lt;/code&gt;을 반환한다면 리스코프 치환 원칙을 어기는 것이다.&lt;/li&gt;
&lt;li&gt;다른 예로는 상위 클래스에서 입력 시 모든 정수를 받아들일 수 있을 때, 하위 클래스에서 함수를 재정의하면서 음수일 경우에는 예외를 던진다면 하위 클래스의 유효성 검사가 상위 클래스 보다 훨씬 엄격하여 리스코프 치환 원칙을 어기는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하위 클래스가 상위 클래스의 주석에 나열된 특별 지침을 위반하는 경우&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;상위 클래스에서 사용자의 계좌에서 잔액을 출금하는 함수가 있는데 주석으로 &lt;code class=&quot;language-text&quot;&gt;출금 금액은 계좌 잔액을 초과할 수 없다.&lt;/code&gt; 작성이 되어있다고 생각해보자.&lt;/li&gt;
&lt;li&gt;이때 하위 클래스에서 해당 함수를 재정의하여 당좌 인출 기능을 구현하여 출금 금액이 계좌 잔액을 초과하는 경우를 허용한다면 리스코프 치환 원칙을 위반하는 것이다.&lt;/li&gt;
&lt;li&gt;상위 클래스의 주석을 수정하는 것이 원칙을 지킬 수 있는 간단한 방법일 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;계약에 따른 설계 (design by contract)&lt;/strong&gt; 라고 생각하면 쉽다.&lt;br&gt;
하위 클래스를 설계할 때는 &lt;strong&gt;상위 클래스의 동작 규칙을 따라야 하며&lt;/strong&gt;, 하위 클래스는 함수의 내부 구현 논리를 변경할 수 있지만 &lt;strong&gt;함수의 원래 동작 규칙은 변경할 수 없다는 것&lt;/strong&gt;을 명심해야한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;계약 규칙&lt;/strong&gt; 은 슈퍼타입과 서브타입 사이의 사전조건, 사후조건, 불변식에 대해 서술할 수 있는 제약에 관한 규칙이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서브타입에 더 강력한 사전조건을 정의할 수 없다.&lt;/li&gt;
&lt;li&gt;서브타입에 더 완화된 사후조건을 정의할 수 없다.&lt;/li&gt;
&lt;li&gt;슈퍼타입의 불변식은 서브타입에서도 반드시 유지돼야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;가변셩 규칙&lt;/strong&gt; 은 파라미터와 리턴 타입의 변형과 관련된 규칙이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서브타입의 메서드 파라미터는 반공변성을 가져야 한다.&lt;/li&gt;
&lt;li&gt;서브타입의 리턴 타입은 공변성을 가져야 한다.&lt;/li&gt;
&lt;li&gt;서브타입은 슈퍼타입이 발생시키는 예외와 다른 타입의 예외를 발생시켜서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;제네릭 파라미터 타입의 공변성,반공변성?&lt;/h3&gt;
&lt;p&gt;클라이언트 코드에서 부모 클래스에서 자식 클래스로 치환했을 때 문제가 전혀 없으려면 호출되는 함수와 파라미터 또한 공변성과 반공변성이 보장되어야 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;리스코프의 원칙은 새로운 객체 지향 프로그래밍 언어에 채용된 시그니처에 관한 몇 가지 표준적인 요구사항을 강제한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하위형에서 메서드 인수의 반공변성&lt;/li&gt;
&lt;li&gt;하위형에서 반환형의 공변성&lt;/li&gt;
&lt;li&gt;하위형에서 메서드는 상위형 메서드에서 던져진 예외의 하위형을 제외하고 새로운 예외를 던지면 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기에 더하여 하위형이 만족해야하는 행동 조건 몇 가지가 있다. 이것은 계약이 상속에 대해 어떻게 상호작용하는지에 대한 제약조건을 유도하는 계약에 의한 설계 방법론과 유사한 용어로 자세히 설명되어있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하위형에서 선행조건은 강화될 수 없다.&lt;/li&gt;
&lt;li&gt;하위형에서 후행조건은 약화될 수 없다.&lt;/li&gt;
&lt;li&gt;하위형에서 상위형의 불변조건은 반드시 유지되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 내용은 &lt;strong&gt;오현석님의 설명&lt;/strong&gt;으로 대체하겠다.&lt;/p&gt;
&lt;p&gt;리스코프 치환 원칙에서 제네릭 파라미터 타입의 공변성, 반공변성 부분은 그냥 사족일 뿐이다.&lt;br&gt;
&lt;strong&gt;선행조건 강화 금지&lt;/strong&gt;, &lt;strong&gt;후행조건 완화 금지&lt;/strong&gt;, &lt;strong&gt;불변조건 지키기&lt;/strong&gt; 정도로 충분하다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Foo&amp;lt;in A, out B&amp;gt; {
    fun foo(a: A) : B { ... }
} &lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;제네릭 타입 파라미터 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;가 함수 &lt;code class=&quot;language-text&quot;&gt;foo&lt;/code&gt;의 인자 타입으로 쓰이는 경우를 생각해보자.&lt;br&gt;
이 경우는 &lt;code class=&quot;language-text&quot;&gt;precondition (a is-a A)&lt;/code&gt;라고 볼 수 있다.&lt;br&gt;
여기서 &lt;strong&gt;a is-a A&lt;/strong&gt; 라는 선행조건을 약화시키는 방법은 &lt;strong&gt;A보다 더 느슨한 타입(상위타입)을 제공하는 것&lt;/strong&gt; 이고,&lt;br&gt;
강화시키는 방법은 &lt;strong&gt;A보다 더 빡빡한 타입(하위타입)을 제공하는 것&lt;/strong&gt; 이다. 다른 방법은 없다.&lt;/p&gt;
&lt;p&gt;하위 클래스가 메서드를 오버라이드하면서 선행조건을 더 강화하지 말라는 논리가 이해 된다면 이 부분에서 같은 논리로 &lt;code class=&quot;language-text&quot;&gt;인자의 반 공변성&lt;/code&gt;을 설명하는걸 이해할 수 있다.&lt;br&gt;
마찬가지로 반환값을 후행조건이라고 생각하면 더 느슨한 후행 조건은 허용하지 않는다는 것으로 생각할 수 있으므로, &lt;code class=&quot;language-text&quot;&gt;상위 타입의 값을 허용하지 못하는 걸로 반환 타입의 공변성&lt;/code&gt;을 이해할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;부모 타입에 조건을 추가해서 더 세분화한게 자식 타입이니까 자식 타입은 부모 타입의 제약을(부모 타입의 제약과 호환되는 방향에서) 더 강화한 것일 뿐이다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;&quot;부모 타입의 제약과 호환&quot;&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;&quot;호환&quot;&lt;/code&gt;이 무슨 뜻인지를 생각하다 보니 &lt;strong&gt;리스코프 치환 규칙&lt;/strong&gt;으로 정의할 수 있는 것이다.&lt;br&gt;
공변성과 반공변성은 따라나오는 정도에 지나지 않는다.&lt;br&gt;
또 어떤 면에서 보면 리스코프 치환 원칙은 그냥 &lt;code class=&quot;language-text&quot;&gt;is-a&lt;/code&gt;관계를 다른 말로 써놨을 뿐이라고 볼수도 있을 것 같다.&lt;br&gt;
결국 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Child is-a Parent&lt;/code&gt;라는 관계가 성립되기 위한 원칙인 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;인터페이스-분리-원칙&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;인터페이스 분리 원칙&lt;/strong&gt;&lt;a href=&quot;#%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EC%9B%90%EC%B9%99&quot; aria-label=&quot;인터페이스 분리 원칙 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;클라이언트는 필요하지 않은 인터페이스를 사용하도록 강요되어서는 안된다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;인터페이스 분리 원칙에서 이야기하는 인터페이스는 크게 다음 세 가지 중 하나를 의미한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;API나 기능의 집합&lt;/li&gt;
&lt;li&gt;단일 API 또는 기능&lt;/li&gt;
&lt;li&gt;객체지향 프로그래밍의 인터페이스&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;API나 기능의 집합&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface UserService {
    fun register(cellPhone: String, password: String) : Boolean
    fun login(cellPhone: String, password: String) : Boolean
    fun getUserInfoByCellphone(cellPhone: String) : UserInfo
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;만약 위와 같은 &lt;code class=&quot;language-text&quot;&gt;UserService&lt;/code&gt;에서 만약 사용자를 삭제하는 API가 추가해야된다면 어떻게 해야할까?&lt;br&gt;
대부분 반사적으로 &lt;code class=&quot;language-text&quot;&gt;UserService&lt;/code&gt;에 추가하게 될 것이다.&lt;br&gt;
하지만 &lt;strong&gt;사용자 삭제는 신중하게 수행해야 하는 작업이므로 백그라운드 관리 시스템을 통해서만 수행되어야 하며, 따라서 관련 인터페이스의 사용 범위는 백그라운드 관리 시스템으로 제한되어야 한다.&lt;/strong&gt;&lt;br&gt;
(예시가 사용자 도메인이기 때문에 여러 구현체가 있을지 만무하지만) 사용자 삭제 메서드를 현재 사용중인 인터페이스에 추가하게 된다면, 구현체들에서 모두 구현을 해줘야 해줄뿐더러 &lt;strong&gt;사용자 삭제와 관련없는 클라이언트에서도 삭제 메서드를 알게된다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface RestrictedUserService {
    fun deleteUserByCellphone(cellPhone: String) : Boolean
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;이렇게 새로운 인터페이스로 분리하는게 &lt;strong&gt;API나 기능의 집합&lt;/strong&gt; 관점에서 알맞을 것이다.&lt;br&gt;
핵심은 인터페이스 또는 기능의 일부가 호출자 중 일부에만 사용되거나 전혀 사용되지 않는다면 불필요한 항목을 &lt;strong&gt;강요&lt;/strong&gt;하는 대신, 인터페이스나 기능에서 해당 부분을 분리하여 해당 호출자에게 별도로 제공해야 하며, 사용하지 않는 인터페이스나 기능에는 접근하지 못하게 해야 한다.&lt;/p&gt;
&lt;h3&gt;단일 API나 기능으로서의 인터페이스&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;API나 기능은 가능한 한 단순해야 하며 하나의 기능에 여러 다른 기능 논리를 구현하지 않아야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Statistics (
    val max: Long,
    val min: Long,
    val avg: Long,
    val sum: Long
)

fun count(dataSet: Collection&amp;lt;Long&amp;gt;) : Statistics {
    // make Statistics
    return statistics
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;count(Collection&amp;lt;Long&gt;)&lt;/code&gt;에서 dataSet을 가공하여 &lt;code class=&quot;language-text&quot;&gt;Statistics&lt;/code&gt;를 반환하는 예제를 보면 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt; 함수는 단일하지 않다는 것을 알 수 있다.&lt;br&gt;
해결 방법은 여러가지다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Statistics&lt;/code&gt;를 풍부한 도메인 모델로 만든다.&lt;/li&gt;
&lt;li&gt;dataSet이 그렇게 크지 않다면 &lt;code class=&quot;language-text&quot;&gt;Statistics&lt;/code&gt; 생성자로 dataSet을 전달하여 불변 값을 세팅한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt; 함수에서 각 속성을 계산하는 함수를 다시 분리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;인터페이스 분리 원칙은 단일 책임 원칙과 유사하다.&lt;br&gt;
호출자가 인터페이스를 사용하는 방식에 따라 인터페이스에 단일 책임이 있는지 여부를 확인할 수 있다.&lt;br&gt;
&lt;strong&gt;호출자가 인터페이스의 일부 또는 그 기능의 일부만 사용하는 경우 해당 인터페이스 설계는 단일 책임 원칙을 충족하지 않는다고 말할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;객체지향 프로그래밍에서의 인터페이스&lt;/h3&gt;
&lt;p&gt;앞에서 언급한 두 가지 인터페이스 외에도 언어 수준에서의 &lt;code class=&quot;language-text&quot;&gt;interface&lt;/code&gt;를 다룰 수도 있다.&lt;br&gt;
아래와 같은 Concrete 클래스 3개가 있다고 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9c4b5a57d86ae68ee4eed88ec47a1999/17d12/isp1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.555555555555557%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABM0lEQVR42kWQ6ZKCMBCEef8ns1QUuSK6oFwBw+Wx4O4K6R0Sq/yRqlQ6X/dMG20LMD+HtQ1xCAo0NdA/JMaXREv3PO+x3YTYWREKuncNMI4Sj7tEUwF7ptlgr1mD+SU9nOB7HBuTwF2KWgyQExB+XckohuNksKwz6RGOQaMM2+YPnsuV7rm5Yu1dBmO5ZLCdGD7jyLKBzhO36wtSArYdY20GcN0UUdTStAN4/qPCxOUbiwVTfxgrwPmANB1grNYBTXXC+XxF1wENrTSvM0OOncA0D/CpEiFGpdeV1oTosVq92fjDGuY6IjBDkgyUChRc0oQTJoJ8ryTDEMdDg5KPuJSSdK1V4omZdZ0cSdy/2QmG75HZnNDq9ErowmcoPNaq7Eq8VOFKv2jDrv2l7mb2Rn1+2H8crrwEj0iG1gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;isp1&quot;
        title=&quot;&quot;
        src=&quot;/static/9c4b5a57d86ae68ee4eed88ec47a1999/1cfc2/isp1.png&quot;
        srcset=&quot;/static/9c4b5a57d86ae68ee4eed88ec47a1999/3684f/isp1.png 225w,
/static/9c4b5a57d86ae68ee4eed88ec47a1999/fc2a6/isp1.png 450w,
/static/9c4b5a57d86ae68ee4eed88ec47a1999/1cfc2/isp1.png 900w,
/static/9c4b5a57d86ae68ee4eed88ec47a1999/21482/isp1.png 1350w,
/static/9c4b5a57d86ae68ee4eed88ec47a1999/17d12/isp1.png 1666w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;여기서 **핫 업데이트 (Mysql 제외)**와 **모니터링 (Kafka 제외)**에 대한 요구사항이 들어왔다고 생각해보자.&lt;br&gt;
그렇다면 어떻게 설계하는 것이 좋을까?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;핫 업데이트를 지원하기 위해 &lt;code class=&quot;language-text&quot;&gt;periodInSeconds&lt;/code&gt; 설정 시간마다 반복하여 설정 정보를 업데이트하는 &lt;code class=&quot;language-text&quot;&gt;ScheduledUpdater&lt;/code&gt; 클래스 구현&lt;/li&gt;
&lt;li&gt;설정 정보를 업데이트 하기 위해 &lt;code class=&quot;language-text&quot;&gt;Updater&lt;/code&gt; 인터페이스 추가&lt;/li&gt;
&lt;li&gt;설정 정보를 출력하기 위해 &lt;code class=&quot;language-text&quot;&gt;Viewer&lt;/code&gt; 인터페이스 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://mermaid.live/edit#pako:eNrNVE1PwzAM_StVTkNs075YWTRxAQ4IJiE6OKBerMYdEWlS0gQ2xv47WVMggyFxJIcqfc-xn-3Ea5IphoSSVGYCquqMw0JDkcrIrduSgUEdTd-63egGGa9Olcz5Yg97Cfkj7GU7nSjJHpBZgawBvcUdx5dfnYfkbFU9iQ_S07XW8NTa49vVyWokUVZnSKPT4C8wAsY0VhWNEqO5XASM4QUqa2h0IU0AF7CcKwPiG35o65xaBx7ahPqCovxLfUFh_42-7SfU-P3uhEJxiZk1StMvq_MGSlA_8x3BXHLDQZyhgNWFTNBlyZz8K7UjvkTNFfud92pdxJ2rXCeirdxb5Z_Cp1MuHZJDhicnf2uVfw9_8OEqX1pzLYDLOS5N6-BnhxqbLTWD8jOY35A2KVAXwJkbCnW8lJgHLDAl1G0Z5mCFSd282DhTcLVOVjIj1GiLbeIzaIYIoTmIyqElSELXZElo57jf7fV6_dHwaBKPBoN-3CYrQsfH3dghk8koHo4Ho2G8aZNXpZyHfn36vt433tyjdw2e-bFVT6_NO8bwdZE&quot;&gt;&lt;img src=&quot;https://mermaid.ink/img/pako:eNrNVE1PwzAM_StVTkNs075YWTRxAQ4IJiE6OKBerMYdEWlS0gQ2xv47WVMggyFxJIcqfc-xn-3Ea5IphoSSVGYCquqMw0JDkcrIrduSgUEdTd-63egGGa9Olcz5Yg97Cfkj7GU7nSjJHpBZgawBvcUdx5dfnYfkbFU9iQ_S07XW8NTa49vVyWokUVZnSKPT4C8wAsY0VhWNEqO5XASM4QUqa2h0IU0AF7CcKwPiG35o65xaBx7ahPqCovxLfUFh_42-7SfU-P3uhEJxiZk1StMvq_MGSlA_8x3BXHLDQZyhgNWFTNBlyZz8K7UjvkTNFfud92pdxJ2rXCeirdxb5Z_Cp1MuHZJDhicnf2uVfw9_8OEqX1pzLYDLOS5N6-BnhxqbLTWD8jOY35A2KVAXwJkbCnW8lJgHLDAl1G0Z5mCFSd282DhTcLVOVjIj1GiLbeIzaIYIoTmIyqElSELXZElo57jf7fV6_dHwaBKPBoN-3CYrQsfH3dghk8koHo4Ho2G8aZNXpZyHfn36vt433tyjdw2e-bFVT6_NO8bwdZE?type=png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;각각의 인터페이스는 단일 기능을 가지도록 했고, &lt;code class=&quot;language-text&quot;&gt;ScheduledUpdater&lt;/code&gt; 클래스는 필요한 &lt;code class=&quot;language-text&quot;&gt;Updater&lt;/code&gt;에만 의존하고 불필요한 &lt;code class=&quot;language-text&quot;&gt;Updater&lt;/code&gt;에는 의존하지 않기 때문에 &lt;strong&gt;인터페이스 분리 원칙&lt;/strong&gt;을 만족한다.&lt;br&gt;
이와 같이 &lt;strong&gt;인터페이스 밀도가 작은 경우 인터페이스 변경에 따라 수정해야 하는 클래스도 그만큼 줄어든다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 인터페이스 분리 원칙을 지키지 못한 대표적인 예가 &lt;code class=&quot;language-text&quot;&gt;AbstractImmutableCollection&lt;/code&gt;이지 않을까 싶다.&lt;br&gt;
아래와 같이 &lt;code class=&quot;language-text&quot;&gt;UnsupportedOperationException&lt;/code&gt; 예외를 던지는 메소드들이 노출되어 있기 때문이다.&lt;br&gt;
아마 기존 자바의 가변 콜렉션 타입 계층에 포함시키기 위해 어쩔 수 없는 선택이지 않았을까 싶다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;static abstract class AbstractImmutableCollection&amp;lt;E&amp;gt; extends AbstractCollection&amp;lt;E&amp;gt; {
    // all mutating methods throw UnsupportedOperationException
    @Override public boolean add(E e) { throw uoe(); }
    @Override public boolean addAll(Collection&amp;lt;? extends E&amp;gt; c) { throw uoe(); }
    @Override public void    clear() { throw uoe(); }
    @Override public boolean remove(Object o) { throw uoe(); }
    @Override public boolean removeAll(Collection&amp;lt;?&amp;gt; c) { throw uoe(); }
    @Override public boolean removeIf(Predicate&amp;lt;? super E&amp;gt; filter) { throw uoe(); }
    @Override public boolean retainAll(Collection&amp;lt;?&amp;gt; c) { throw uoe(); }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;hr&gt;
&lt;h1 id=&quot;의존-역전-원칙&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;의존 역전 원칙&lt;/strong&gt;&lt;a href=&quot;#%EC%9D%98%EC%A1%B4-%EC%97%AD%EC%A0%84-%EC%9B%90%EC%B9%99&quot; aria-label=&quot;의존 역전 원칙 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;프레임워크는 객체를 조합하고 전체 실행 흐름을 관리하기 위한 확장 가능한 코드 &lt;strong&gt;골격&lt;/strong&gt; 을 제공한다.&lt;br&gt;
&lt;strong&gt;호출자 (상위 모듈)&lt;/strong&gt; 는 &lt;strong&gt;수신자 (하위 모듈)&lt;/strong&gt; 에 의존하지 않아야 하며, 추상화에 의존해야만 한다.&lt;br&gt;
또한 추상화가 세부 사항에 의존하는 것이 아니라, 세부 사항이 추상화에 의존해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;아래의 질문에 대해 생각해보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;의존 역전&lt;/strong&gt;이 뜻하는 것은 어떤 대상 사이의 역전인가? 그리고 어떤 의존이 역전되는 것인가? 그리고 여기서 말하는 &lt;strong&gt;역전&lt;/strong&gt;은 무엇을 의미하는가?
&lt;ul&gt;
&lt;li&gt;대상은 호출자와 수신자로 구분할 수 있고, 역전이 되는 대상은 &lt;strong&gt;프레임워크를 사용하기 전에 직접 작성했던 전체 프로그램 흐름의 실행을 제어하는 코드다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;프레임워크를 사용한 후 전체 프로그램의 실행 흐름은 프레임워크에 의해 &lt;strong&gt;제어&lt;/strong&gt; 되고, 흐름의 제어는 &lt;strong&gt;프로그래머에서 프레임워크로 역전되는 것&lt;/strong&gt; 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;종종 &lt;strong&gt;제어 반전&lt;/strong&gt;과 &lt;strong&gt;의존성 주입&lt;/strong&gt;이라는 두 가지 다른 개념을 접할 수 있는데, 이 개념은 &lt;strong&gt;의존 역전&lt;/strong&gt;과 같은 개념에 속하는가? 만약 그렇지 않다면 그 차이는 무엇인가?
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;제어 반전&lt;/strong&gt; 은 특정한 기술이 아니라 일반적으로 프레임워크를 사용할 때 만나게 되는 보편적인 설계 사상에 가깝다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의존성 주입&lt;/strong&gt; 은 특정한 프로그래밍 기술이다. 객체 생성 예약어를 사용하여 클래스 내부에 종속되는 클래스의 객체를 생성하는 대신, &lt;strong&gt;외부에서 종속 클래스의 객체를 생성한 후 생성자 또는 함수의 매개변수 등을 통해 클래스에 주입하는 것을 의미한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring 프레임워크의 IoC는 2번의 세 가지 개념과 어떤 관련이 있는가?
&lt;ul&gt;
&lt;li&gt;Spring과 같은 의존성 주입 프레임워크를 사용하면 생성해야 하는 모든 클래스 객체와 클래스 간의 의존성을 간단히 구성할 수 있으며, 프레임워크가 자동으로 객체를 생성하고, 객체의 라이프 사이클을 관리하고, 의존성 주입을 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;모듈간 의존 역전이 필요한 경우&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 수정 전
// moduleA
class Runner(val func: Func1) {
  fun exec() {...}
}

// moduleB
class Func1
fun main() {
  Runner(Func1()).exec()
}

// 수정 후
// moduleA
class Runner(val func: Func) {
  fun exec() {..}
}
interface Func

// moduleB
class Func1:Func

fun main() {
  Runner(Func1()).exec()
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;수정 전에는 &lt;code class=&quot;language-text&quot;&gt;moduleA&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;moduleB&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;Func1&lt;/code&gt;을 직접 의존하고 있어 &lt;code class=&quot;language-text&quot;&gt;Func1&lt;/code&gt;의 수정 사항에 &lt;code class=&quot;language-text&quot;&gt;Runner&lt;/code&gt;가 변경될 확률이 굉장히 높다.&lt;br&gt;
수정 후에는 &lt;code class=&quot;language-text&quot;&gt;moduleA&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;Func1&lt;/code&gt;이 구현하고 있는 &lt;code class=&quot;language-text&quot;&gt;Func Interface&lt;/code&gt;를 의존하게 되면서 외부로 드러난 API 목록에만 집중할 수 있게 되었다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;결론&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;결론&lt;/strong&gt;&lt;a href=&quot;#%EA%B2%B0%EB%A1%A0&quot; aria-label=&quot;결론 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;기술이나 설계방법론에는 정답이 없다.&lt;/strong&gt;&lt;br&gt;
내가 처해있는 상황을 철저하게 분석하고 어떤 방법이 더 옳은지 판단하여 최선의 방법을 선택해야 한다.&lt;br&gt;
코드의 확장성은 코드의 품질을 판단하는 중요한 기준이다. 하지만 확장성은 가독성을 떨어뜨릴 수 있기 때문에 &lt;code class=&quot;language-text&quot;&gt;공짜&lt;/code&gt;는 아니다.&lt;br&gt;
확장성이 더 중요한 일부 시나리오에서는 코드의 가독성을 희생할 가치가 있다.&lt;/p&gt;
&lt;h3&gt;위의 원칙들을 지켜내기만 하면 좋은 코드인가?&lt;/h3&gt;
&lt;p&gt;위에서 설명한 &lt;strong&gt;SOLID와 확장성, 코드의 품질을 판단하는 기준은 사람마다 다 제각각이다.&lt;/strong&gt;&lt;br&gt;
단일책임원칙을 평가하기 위한 명확하고 정량화할 수 있는 표준은 존재하지 않으며 과도하게 너무 세분화하여 설계할 필요도 없다.&lt;br&gt;
단일 책임을 갖는 인터페이스를 여러 개 갖는 구체 클래스는 단일책임이라고 말할 수 있을까? 합성으로 여러 인터페이스를 구현하는 것이 아니라 위임으로 해결해야 하지 않을까?&lt;br&gt;
처음에는 현재의 비즈니스 요구사항을 충족하기 위해 다른 사람이 볼때는 납득할 수 없는 형태의 클래스를 작성할 수도 있지만, &lt;strong&gt;사업이 발전하면서 기능이 점점 추가되고, 코드가 더 복잡해지면서 어느 기준을 넘길 때 점진적 추상화를 진행할 수 있을 것이다.&lt;/strong&gt;&lt;br&gt;
코드의 변경 가능한 부분과 변경할 수 없는 부분을 잘 식별해야 한다.&lt;br&gt;
&lt;strong&gt;변화율이 서로 다른 기능, 함수를 구분하여 분리하고 그 기능, 함수에 필요한 속성을 캡슐화하고 상위 시스템에서 사용되는 변경되지 않을 추상 인터페이스를 제공해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ce4f3f438ac81f9d228ccb0542043a0d/9b379/repeat.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/klEQVR42m2STUiTcRzHn2e2Nr1EUWCXiA5LRocdhA7lYYfqkr1AeAiqHTQTbdZGL0NaS+cs3XIzmiskdS8u19rStRxq1qEEkWGj1wVBGUgOkijKqPDTs8egoB2+/OAP38//y/f3EwRBoJBEUZRni+kgcd9Z+p1G/K5G/NL02evosh2lt90ovTfSJ+l29xmMht15j2T+D/YXeD/iYDEb5efcCCyMk3saZm7az4d0kM/PB/kxm2BpPgXvEgQ85rxHlICiDFiWgEIhysoDxwIXyEmAbOoKiy8jzNzzMJP08PXZTb69GOTX6yiZpJsv0kcDbhPC5hUCepXIVslcWbaBLdpNVJRrKV23WgZODzkhl+Juv41X4156nCYclho8zfWMhVpZehMjnezk45OQXI2wam0p6zdq0Go07NuuQ79Nx86KcnRla1ArBabiHcxLCRM3zjMZdxK9asFrPcbjmFNO+SkTIuA+TXbCx51rFgSFUoVCXUKRSk2xWoVqpRK1NEuKi+SEU/F2OZnTWstkrIOJgVYehu3wPkFmpItLTTX0uczwdohg58nlpRSS4s9SHkXa+C5157U34DpXy4Owg9FgM7e8FmzmI3gdx1lIB2B2mMDlE4WB4j9nc/HUIVK9Vnra6jFU7aLBsAdT9X6qKvUcPrCD7pY6ubvh602Yq/fyG67XXUiEDBTdAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;repeat&quot;
        title=&quot;&quot;
        src=&quot;/static/ce4f3f438ac81f9d228ccb0542043a0d/1cfc2/repeat.png&quot;
        srcset=&quot;/static/ce4f3f438ac81f9d228ccb0542043a0d/3684f/repeat.png 225w,
/static/ce4f3f438ac81f9d228ccb0542043a0d/fc2a6/repeat.png 450w,
/static/ce4f3f438ac81f9d228ccb0542043a0d/1cfc2/repeat.png 900w,
/static/ce4f3f438ac81f9d228ccb0542043a0d/9b379/repeat.png 951w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UwAoUshVpgM&amp;#x26;ab_channel=SpringCampbyKSUG&quot;&gt;이미지 출처&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;중복된 코드에 매몰되지 말고 API에 집중해서 API의 책임이 중복되지 않게 꾸준히 신경써야 하며, 고품질 코드에 대한 &lt;code class=&quot;language-text&quot;&gt;책임&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;끈기&lt;/code&gt;를 가져야 할 것이다.&lt;br&gt;
좋은 코드, 고품질 코드에 대해 스스로의 주관을 가져야 한다.&lt;br&gt;
내가 이렇게 작성한 이유를 상대방에게 설명하거나 설득시킬 수 있는 능력과 스스로의 안목을 키우기 위해 꾸준한 노력이 필요하다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Spring RW분리를 이해하기 위한 여정 (+ JDBC, 서비스 추상화)]]></title><description><![CDATA[IDC 환경의 MySQL 서버를 AWS로 마이그레이션하면서 스프링에서 읽기/쓰기 분리를 스프링에서 제공하는 LazyConnectionDataSourceProxy 클래스를 활용해서 해결하였다. 이번 글을 통해 JDBC와 스프링이 제공하는 PSA…]]></description><link>https://jdalma.github.io/2024y/rwseparate/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/rwseparate/</guid><pubDate>Sun, 14 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;IDC 환경의 MySQL 서버를 AWS로 마이그레이션하면서 &lt;a href=&quot;https://jdalma.github.io/2024y/mysqlMigration/#%EC%8A%A4%ED%94%84%EB%A7%81%EC%97%90%EC%84%9C-%EC%9D%BD%EA%B8%B0%EC%93%B0%EA%B8%B0-%EB%B6%84%EB%A6%AC&quot;&gt;스프링에서 읽기/쓰기 분리&lt;/a&gt;를 스프링에서 제공하는 &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.html&quot;&gt;LazyConnectionDataSourceProxy&lt;/a&gt; 클래스를 활용해서 해결하였다.&lt;/p&gt;
&lt;p&gt;이번 글을 통해 JDBC와 스프링이 제공하는 PSA에 대해 정리해보고 어떤 원리로 읽기/쓰기가 분리된 것인지 이해해보려한다.&lt;/p&gt;
&lt;h1 id=&quot;jdbc&quot; style=&quot;position:relative;&quot;&gt;JDBC&lt;a href=&quot;#jdbc&quot; aria-label=&quot;jdbc permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/57d01e37159315a501e82468a2eb06d9/54c3a/jdbcApi.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.111111111111114%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAABJUlEQVR42n2PTU7CUBSFuxKGLIABTlgFMzZnQDZQwoCBEyoJIGgCDUIlBSliQovYf7CU9rM8okNfcnPey333fPdI/X6fl+mUaVaqqrJcLDiezkRJyn8nSRKh3W6XcrlMpVJB13UkPTN4zkyVdpuHToe1YRDGZzZBjBmlPA2HyI0Gr/M5o9GIXq/HBRVGsdBqrUYulyOfz3NZTrpQrO2W2WyG53uCmqYpQZyycL8ZDB65b7XQJhPU8VgkCc9ghhGf2d/bao1CoUCxeCNgwlDTNOr1O97eN+xP8BGcMA9xtuk1VhAeiffZeBT9AX8jy7IsDEulEuMMKLmug7E2WK2WWDsLy3bxPZfAu6pt2+z2X9ieh+044u24rrh7vo9pmjSbTRRFEb0f7S5gaxbjceUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;jdbcApi&quot;
        title=&quot;&quot;
        src=&quot;/static/57d01e37159315a501e82468a2eb06d9/1cfc2/jdbcApi.png&quot;
        srcset=&quot;/static/57d01e37159315a501e82468a2eb06d9/3684f/jdbcApi.png 225w,
/static/57d01e37159315a501e82468a2eb06d9/fc2a6/jdbcApi.png 450w,
/static/57d01e37159315a501e82468a2eb06d9/1cfc2/jdbcApi.png 900w,
/static/57d01e37159315a501e82468a2eb06d9/54c3a/jdbcApi.png 1257w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;우리가 쉽게 접하고 사용하는 MyBatis와 하이버네이트에서도 기저에는 JDBC API를 사용하여 데이터베이스와 통신하기에 먼저 JDBC가 무엇인지 이해할 필요가 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 350px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/db6eac85a5d94264e0b8039c94e2bbe8/13ae7/jdbcArchitecture.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 111.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAIAAABPIytRAAAACXBIWXMAAAPoAAAD6AG1e1JrAAACkklEQVR42oXUaU+aURAF4Pf//wc/SGKMxjQKAu47SBFcse4L7rgraowL7eN7I7Y2rfPhzWXuzJyZc+YS/Xy3RqPhe3p6WiqVVlZWfvxpvb29t7e3zbBg0afkWq22v78vuVwuq1IoFHzPz89TqZSrr5OLxWLp3WZmZvxcWlpqa2vT1D+TmzY7Ozs6Ojo9PZ3NZoeHh3O53MTExPj4+N3d3RfJT09PZ2dn+jQhzNXV1ZubGx7fv2E+ty0O1OTkpJm/x5bP51VB2PX19RczA0wmkx0dHe3t7Z2xJRKJrq6ulpYWdf+X/Pr66nBxcYEnM6OaSLqYn5+X6bYR20fyJ9fV1dXh4eH9/X3QPBzq9frx8XGTsGASP5AfHh5OTk4uLy+btQge5mQvLy/ABTw+Pn4ga2xvb69arRKGntvb24KQDApVCwsLbtfX1+m/u7ur/4GBgYODg62trY2NjQiTlUpFkCoyR0ZGiCzahvC4cs5kMiZfW1ubm5uzfIJFIjISRIZkbJYB2tTUlEZU6enp6evrCxWRBzmEARdAiAiriulneXkZCNixsTEt7ezsVGI7OjoyEWQe4ywuLnIaqrW1NYJmBzUGXxo0EUKV9xikDQ0NudIzfGfI2pFC/AiNeFMVvsPz8zOqfZ3xh/BAdfOLI3KGn5FQMobX97tOVs1EHklzKwlLCN0hkq5vUkkwLSVcOLgIDxNsLTYM44ITc7qzf9SG7xu5CI/We0Bbf38/TIQJ5YQsaHBwEIZIfmFBKhREfuv5W2y8QSd3CiGsu7t7c3MTfzLx4pHhid9Zxcgmawb1OrQ3oj3p8E9EJE6DiDYwFRQSaefRYfi33VaVYNoQBDMwKTk40+m0fQzL75H7S4Fpunq9/gtKPIDr1PEtjAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;jdbcArchitecture&quot;
        title=&quot;&quot;
        src=&quot;/static/db6eac85a5d94264e0b8039c94e2bbe8/13ae7/jdbcArchitecture.png&quot;
        srcset=&quot;/static/db6eac85a5d94264e0b8039c94e2bbe8/3684f/jdbcArchitecture.png 225w,
/static/db6eac85a5d94264e0b8039c94e2bbe8/13ae7/jdbcArchitecture.png 350w&quot;
        sizes=&quot;(max-width: 350px) 100vw, 350px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JDBC(Java Database Connectivity)&lt;/strong&gt; 는 클라이언트가 모든 종류의 표 형식 데이터, 특히 관계형 데이터베이스에 액세스할 수 있는 방법을 정의하는 Java용 응용 프로그래밍 인터페이스이다.&lt;br&gt;
Java 애플리케이션과 데이터베이스 간의 &lt;strong&gt;중간 계층 인터페이스 역할&lt;/strong&gt; 을 하며, 데이터 소스에 연결하거나 쿼리를 실행하고 데이터베이스 질의 결과를 처리하기 위한 가이드이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JDBC 드라이버&lt;/strong&gt; 는 Java 애플리케이션의 요청을 DBMS가 이해할 수 있는 프로토콜로 변환하는 &lt;code class=&quot;language-text&quot;&gt;클라이언트 측 어댑터&lt;/code&gt;이다.&lt;br&gt;
즉, JDBC 드라이버는 Java 애플리케이션과 데이터베이스가 상호 작용할 수 있도록 JDBC API의 인터페이스를 구현하는 소프트웨어 구성 요소이다.&lt;br&gt;
이 드라이버의 유형에는 Sun Microsystem에서 정의한 4개의 유형이 있다.&lt;br&gt;
(자세한 설명은 &lt;a href=&quot;https://www.geeksforgeeks.org/jdbc-drivers/&quot;&gt;JDBC 드라이버&lt;/a&gt;를 참고하자.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Type-1 드라이버 또는 JDBC-ODBC 브리지 드라이버&lt;/li&gt;
&lt;li&gt;Type-2 드라이버 또는 Native-API 드라이버&lt;/li&gt;
&lt;li&gt;Type-3 드라이버 또는 네트워크 프로토콜 드라이버&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type-4 드라이버 또는 Thin 드라이버(완전 Java 드라이버)&lt;/strong&gt; : 데이터베이스에 직접 연결하여 JDBC 호출을 데이터베이스별 호출로 변환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;데이터베이스마다 고유한 프로토콜이 필요하다는 단점이 있긴 하지만, 데이터베이스 서버에 직접 연결하면 다른 Type에 비해 더 나은 성능을 제공하는 장점이 있는 Type-4가 가장 일반적으로 사용된다.&lt;/p&gt;
&lt;h2 id=&quot;jdbc-api-사용하기&quot; style=&quot;position:relative;&quot;&gt;JDBC API 사용하기&lt;a href=&quot;#jdbc-api-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; aria-label=&quot;jdbc api 사용하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 710px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/32a5df3c23949ec622af96963a1873d2/7131f/jdbc.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.22222222222223%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACxElEQVR42nVT224SURTlG/2JGh+8xKS+Wa3amBh9MMa+VkwAi22hpaRVQqClV6AU2oECM9zKwAyXggzMcO3ynI3TxISeZGXtGeZs1tpnHYsxGENp6Whqfcg3XYYeNGMIvm4xXfpwglxNh9zuY2K+vGdZUpUuzksdxBkS1xrVgqxRs2q1img0iouLC5zH40gwTiQSiLO63+/PbvhyI4un9is8/3GFeWcazxyMV9PQ+mOcRSP4urwMh8MBm83G2A6r1YqVlRV0Op2pi9v/JVvkVh/5hsEs9SCpXVYza63pv/9ptyFJEsrlMkGWKygWi4TRaDRb4YedAl65RbzzSHjNeMElEvcGE5RLRezt7yMcDuPk5ASnp6fER0dHMAxjtsJQ9ga/hQZ8SQahTnXgqokx+67dakEURVJUKBTuOJ/P36+QN3DHanCdKdiMKYxVeM5VjCZgm/Pw+/04Pj4mcIUHBwfYZ6pNhZPJhFSasHz2FbHE7C55cnjvzeHNlsTsi9CZ5aRwCbvdAZfLRXC73VhfX6da07TZlq+bBqS6DlHpIlvVILG88Xd88U38MCqVCoHHSJZlqnmjZrMJQRCQTqeRSqVoPJYXPzOY+ybg0fck5qxJPPgSw0P2zGOTl0Ts7OySzVAohMPDQwSDQfh8PrLMG9ntdqytrcHpdMLr9Zqx0VGo91BqMNwYqLAbwZ2Mx2MKMN/Moes6PZuhNhVydclkkiJm+bRbwFs2twVXlsL92JaaxmZ4i1KxQIfCo2LGhqvkau+NjZ9FZDtRhyeuws1OeOOshl+XDQxZbrp8hmxmtVoNqqoSK4pCs+TqZ8aGb/bE69iKqXCGK1gNK9iO1yg27XYL2WwWuVzuDtwWZ96wxXLKLWcyGbJNlj8yy4ubIsVl3pnBE3avF5ll/Z/lYHAPkUiELJs3hlseDAakNBAI0O/84Dj/BU1OMu3fQ/HOAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;jdbc&quot;
        title=&quot;&quot;
        src=&quot;/static/32a5df3c23949ec622af96963a1873d2/7131f/jdbc.png&quot;
        srcset=&quot;/static/32a5df3c23949ec622af96963a1873d2/3684f/jdbc.png 225w,
/static/32a5df3c23949ec622af96963a1873d2/fc2a6/jdbc.png 450w,
/static/32a5df3c23949ec622af96963a1873d2/7131f/jdbc.png 710w&quot;
        sizes=&quot;(max-width: 710px) 100vw, 710px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;    // 1. JDBC 드라이버 로드는 JDBC4.0부터 클래스 경로에 있는 모든 드라이버가 자동으로 로드되기 때문에 직접 Class.forName을 호출할 필요가 없다.
    public Member findById(String memberId) throws SQLException {
        String sql = &amp;quot;select * from member where member_id = ?&amp;quot;;

        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs =  null;

        try {
            // 2. 데이터베이스 연결 (커넥션 생성)
            con = getConnection();

            // 3. SQL 명령을 전송하기 위한 Statement 준비 (PreparedStatement 또는 CallableStatment)
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, memberId);

            // 4. SQL문 전송
            // 5. 질의문 결과(ResultSet) 생성
            rs = pstmt.executeQuery();
            if (rs.next()) {
                Member member = new Member();
                member.setMemberId(rs.getString(&amp;quot;member_id&amp;quot;));
                member.setMoney(rs.getInt(&amp;quot;money&amp;quot;));
                return member;
            } else {
                throw new NoSuchElementException(&amp;quot;member not found memberId = {}&amp;quot; + memberId);
            }
        } catch (SQLException e) {
            log.error(&amp;quot;db error&amp;quot;, e);
            throw e;
        } finally {
            // 6. 연결 해제 (사용된 리소스 해제)
            close(con, pstmt, rs);
        }
    }

    public Connection getConnection() {
        try {
            Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            log.info(&amp;quot;get connection = {}, class = {}&amp;quot;, connection, connection.getClass());
            return connection;
        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/898fb4670e3f3fb1f814df9e31154f25/c2d9c/sequence.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 30.22222222222222%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABmUlEQVR42n2P309ScRjGz3/RVl1oEmAl4K+FuS7sopuu/StKUfMXbl2RGvh/uBDlyEVOo+U6evCg44Qo57QWuCkH0VyCtWKw+enL7MKrnu2zPXufvXvfR0ql0szPh1mWl4lGoyxEZE5Ov9NQPL5OeCFCLCaL+RJy7B21Wo3/SRqbeMXNWy10tXfgcbm4Y2vjk6JSrF7y9Fk/LXcd9Hg92B338XQ+5vTfsXq9TqVywfn5FeVyhT/VKlIulyehJlhTNBRVQ9OSGMUzLmqXmIYhMo1VJcmGmmQ3naYqlhpaVxI473XT2+ul70kPzeKRwHQIqVEv/y1P0shhfhUIf/xDXPv1k6zxhUxmn487GfTdfbJZg8OTMw7Kv9nc1hkdnWR80o9/aooB3whvwxGk1zMhbty24XZ3Ym914XzQhba1hWmatLU/osnmwPuwVWROmu1u5JX3lEolCkeHFIsWVkFgWVfeKiAtLi4RCEwTDIaYmX1DMDTHpqqK6hrjE36evxhkeHiIgUEfvqGXfIjH2dvLkErp6HqDz9fQ+QuNFWqRUbGraAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sequence&quot;
        title=&quot;&quot;
        src=&quot;/static/898fb4670e3f3fb1f814df9e31154f25/1cfc2/sequence.png&quot;
        srcset=&quot;/static/898fb4670e3f3fb1f814df9e31154f25/3684f/sequence.png 225w,
/static/898fb4670e3f3fb1f814df9e31154f25/fc2a6/sequence.png 450w,
/static/898fb4670e3f3fb1f814df9e31154f25/1cfc2/sequence.png 900w,
/static/898fb4670e3f3fb1f814df9e31154f25/c2d9c/sequence.png 1326w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DriverManager와 Driver&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;DriverManager가 모든 Driver를 로드한 다음, 연결 요청이 있을 때마다 각 드라이버에 차례대로 대상 URL에 연결을 시도하도록 요청한다. (&lt;a href=&quot;https://www.baeldung.com/java-spi&quot;&gt;Service Provider Interface&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Driver는 모든 드라이버 클래스가 구현해야 하는 인터페이스이며, 실질적으로 데이터베이스에 연결을 시도하고 Connection을 반환하는 &lt;code class=&quot;language-text&quot;&gt;connect()&lt;/code&gt; 메서드가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;특정 데이터베이스와의 연결(세션)을 유지하며, SQL문이 실행되고 결과가 반환되는 컨텍스트이다.&lt;/li&gt;
&lt;li&gt;데이터베이스의 메타정보를 조회하거나 트랜잭션의 격리 수준과 커밋 모드 등을 수정하는 책임을 가지고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PreparedStatement&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;SQL문을 나타내는 객체이며, 파라미터를 바인딩할 수 있다.&lt;/li&gt;
&lt;li&gt;SQL문을 실행하고 결과를 반환하는 책임이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ResultSet&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;데이터베이스 결과 집합을 나타내며, 결과 집합의 데이터 행을 가리키는 커서를 유지한다.&lt;/li&gt;
&lt;li&gt;레코드의 데이터를 특정 데이터 타입으로 가져오는 방법을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아래와 같이 사용하는 데이터베이스 드라이버에 따라 다른 구현체가 선택되어 실행된다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JDBC API&lt;/th&gt;
&lt;th&gt;H2&lt;/th&gt;
&lt;th&gt;MySQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;java.sql.Driver&lt;/td&gt;
&lt;td&gt;org.h2.Driver&lt;/td&gt;
&lt;td&gt;com.mysql.cj.jdbc.NonRegisteringDriver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;java.sql.Connection&lt;/td&gt;
&lt;td&gt;org.h2.jdbc.JdbcConnection&lt;/td&gt;
&lt;td&gt;com.mysql.cj.jdbc.ConnectionImpl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;java.sql.PreparedStatement&lt;/td&gt;
&lt;td&gt;org.h2.jdbc.JdbcPreparedStatement&lt;/td&gt;
&lt;td&gt;com.mysql.cj.jdbc.ClientPreparedStatement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;java.sql.ResultSet&lt;/td&gt;
&lt;td&gt;org.h2.jdbc.ResultSet&lt;/td&gt;
&lt;td&gt;com.mysql.cj.jdbc.result.ResultSetInternalMethods&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&quot;커넥션-생성-추상화&quot; style=&quot;position:relative;&quot;&gt;커넥션 생성 추상화&lt;a href=&quot;#%EC%BB%A4%EB%84%A5%EC%85%98-%EC%83%9D%EC%84%B1-%EC%B6%94%EC%83%81%ED%99%94&quot; aria-label=&quot;커넥션 생성 추상화 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 816px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/661794a0a401f3f85d631b3888725674/b4098/individualConnection.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABH0lEQVR42l2Q6YrCUAyF+/6PJShuoNAFrT9cumgXtSrutmf4ApWZCYTk5ibnnMSZTCaaz+fmo9FIw+FQvu/rfr/rdrtZbPPX66WmafR+v/X5fISRP59Pe/Pn0IRvNht5nvf1KIq03W6tvl6vtVwuFYahsixTHMdKkkRFUajf76vT6WixWBiBMx6Pdblc1O12TSXNgADIUK/XUxAEGgwGpj7PcwMFDAK2OZ1ORo5K53q9qq5rU5CmqXa7nSmEEV+tVgYCEfWyLO293++/9fP5bCTgOMhkZRgw7oChcDqd/qkxxD0RQQQApfQeDgerGSAfrFdVlfnxeDTnFCghJ9Lz2yBqyYi2MmA8Ho+HDbbyXdfVbDazlbgRf/TQy8x/b+s/P5kOSM7JLvYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;individualConnection&quot;
        title=&quot;&quot;
        src=&quot;/static/661794a0a401f3f85d631b3888725674/b4098/individualConnection.png&quot;
        srcset=&quot;/static/661794a0a401f3f85d631b3888725674/3684f/individualConnection.png 225w,
/static/661794a0a401f3f85d631b3888725674/fc2a6/individualConnection.png 450w,
/static/661794a0a401f3f85d631b3888725674/b4098/individualConnection.png 816w&quot;
        sizes=&quot;(max-width: 816px) 100vw, 816px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이전에 알아보았던 &lt;code class=&quot;language-text&quot;&gt;DriverManager.getConnection(...)&lt;/code&gt;을 통한 커넥션 생성은 항상 새로운 커넥션을 생성한다.&lt;br&gt;
이런 비효율적인 작업을 &lt;strong&gt;Connection Pool을 통해 개선할 수 있다.&lt;/strong&gt;&lt;br&gt;
하지만 Connection Pool을 사용해서 Connection을 얻어오는 방법과 DriverManager를 사용해서 Connection을 얻어오는 방법이 서로 다를 수 있기에 자바는 &lt;strong&gt;Connection을 획득하는 방법을 추상화 시킬 수 있는 &lt;code class=&quot;language-text&quot;&gt;javax.sql.DataSource&lt;/code&gt; 인터페이스를 제공한다.&lt;/strong&gt;&lt;br&gt;
JDBC API처럼 드라이버 공급업체에서 해당 DataSource 인터페이스를 구현해놓았다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c8a39f4b73a98fcfdcd300564cc7f77f/a3bed/dataSource.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 40.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABXUlEQVR42o2RTUvDQBCGF/pz/AHevNSD9OzNi/gH9OpZ8CYeRagHwYMaLwUFEVtBpEmgYJuDaNOkpiEfhSa1janbfPS1WawkBbEDw+zOzjy8s0OwgE0mExZt20a1WoUoihAEAfV6HbquQ1EUOI7DnCTFaZ8HpfPNpgynP8CIBgjCGHJLQa1WA8dx7J1SCrKIwjAM8en78IYDuLYG19LQN9vwnC7irAYQ0zSZ7E6n8xs1TWNnVVXhui6GQ28qN4Le/cDGwQPW929R2LvHOW8mc2SgxPM8GIYBy7LQaDQgyzIqlQokSQLP8+xO6XjaF6IsvoGsniCXPwLJX2LzWE0GzQLTcoMgYPCZ93o9lvP9EeI4hv9FURLa4J4UnJVf8PpuZ5bGgP8tJbEoihHQESTZwMruDZZ3LrC0fY3DOyP54b8Vzm92Box/Oh6fVZDCKXKFIsjaFbaKrWl2jCgF/AZBXU6tROQwFQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;dataSource&quot;
        title=&quot;&quot;
        src=&quot;/static/c8a39f4b73a98fcfdcd300564cc7f77f/1cfc2/dataSource.png&quot;
        srcset=&quot;/static/c8a39f4b73a98fcfdcd300564cc7f77f/3684f/dataSource.png 225w,
/static/c8a39f4b73a98fcfdcd300564cc7f77f/fc2a6/dataSource.png 450w,
/static/c8a39f4b73a98fcfdcd300564cc7f77f/1cfc2/dataSource.png 900w,
/static/c8a39f4b73a98fcfdcd300564cc7f77f/21482/dataSource.png 1350w,
/static/c8a39f4b73a98fcfdcd300564cc7f77f/a3bed/dataSource.png 1721w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;    @Test
    void dataSourceDriverManager() throws SQLException {
        DataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
        
        useDataSource(dataSource);
    }

    @Test
    void dataSourceConnectionPool() throws SQLException {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
        dataSource.setMaximumPoolSize(10);
        dataSource.setPoolName(&amp;quot;My Pool!!!&amp;quot;);

        useDataSource(dataSource);
    }

    private void useDataSource(DataSource dataSource) throws SQLException {
        Connection con1 = dataSource.getConnection();
        Connection con2 = dataSource.getConnection();
        ...
    }&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// DrivetManager를 통한 요청할 때마다 Connection 새로 생성
[main] DEBUG o.s.j.d.DriverManagerDataSource -- Creating new JDBC DriverManager Connection to [..]
[main] DEBUG o.s.j.d.DriverManagerDataSource -- Creating new JDBC DriverManager Connection to [..]

// HikariCP 초기화를 위한 Connection 생성
[main] INFO  com.zaxxer.hikari.pool.HikariPool -- My Pool!!! - Added connection conn0: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn1: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn2: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn3: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn4: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn5: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn6: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn7: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn8: ...
[connection adder] DEBUG ...HikariPool -- My Pool!!! - Added connection conn9: ...&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;이 추상화 덕분에 클라이언트 입장에서는 인터페이스에만 의존하기 때문에 Connection을 반환하는 DataSource의 구현체가 변경되어도 상관없다.&lt;/p&gt;
&lt;h1 id=&quot;스프링-서비스-추상화&quot; style=&quot;position:relative;&quot;&gt;스프링 서비스 추상화&lt;a href=&quot;#%EC%8A%A4%ED%94%84%EB%A7%81-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%B6%94%EC%83%81%ED%99%94&quot; aria-label=&quot;스프링 서비스 추상화 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Connection에 대한 추상화를 통해 데이터베이스 의존성에 대한 결합도가 약해졌지만 아직 문제가 있다.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;데이터 접근 기술(JDBC, JPA...)마다 다른 트랜잭션 사용 방법은 어떻게 통일할 수 있을까? → &lt;strong&gt;트랜잭션 추상화&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;애플리케이션의 서비스 로직에 DB 트랜잭션 경계 설정을 사용하기 위해서는 어떻게 해야 할까? → &lt;strong&gt;리소스 동기화&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;반복되는 JDBC API 코드는 어떻게 해결할 수 있을까? → &lt;strong&gt;트랜잭션 AOP&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;스프링은 이미 모두 해결해놓았다.&lt;/p&gt;
&lt;h2 id=&quot;트랙잭션-추상화&quot; style=&quot;position:relative;&quot;&gt;트랙잭션 추상화&lt;a href=&quot;#%ED%8A%B8%EB%9E%99%EC%9E%AD%EC%85%98-%EC%B6%94%EC%83%81%ED%99%94&quot; aria-label=&quot;트랙잭션 추상화 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Spring 프레임워크는 트랜잭션 관리를 위한 일관된 추상화를 제공한다. 대표적인 인터페이스들을 알아보자.&lt;br&gt;
트랜잭션 상태를 질의할 수 있고 트랜잭션 Commit,Rollback에 사용되는 &lt;code class=&quot;language-text&quot;&gt;TransactionStatus&lt;/code&gt; 인터페이스를 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4b988c7e14f0a66e7ac4d3ba40607a45/fbf76/transactionStatus.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABk0lEQVR42nVS25ajIBD0N1au4iVoIAJmzs5sJiLiZTP//0Hb6mw2L3tOnabFKrpoOrGdtd3VbsvVuTdIykq21hjbwafW5iRr6xzsm51q3dV1b835QmiW3MPHvfdxnuM6hTEOY2yUuvl3H/3yWH++37S53MPt9tmPyzQuse8HoIEeE56kiKWIIswI54gyAOEiJYxkHGcsJZSwDG9gmDPMeJrSg0+ZSBgXjOeMbSBEyKaJa++HsP5+zMu6rg/IM1FSKg7ORuY5KDfxsRwgJINYSVmUstaqVqo61UUlweEr7Yl/YmiAKErdXqAZZ9U2Sp+VPnKlW57l/xHvzkEs6ybM9wGaNsY4f8XlKwxhmhdwLvISmPsFxdN5QghHiEEDEKaY5ikpAD9QxkrDTwazApEc0RJhvnM2pDt/E5+0+xWnYQhQEMr4EIcQ47Q4d4VzoYL7HDwQQvQ+gIXeB3gqeF2RV0leNQoGwLjWdi0MB0TjYBLqRsNF4Hip7cV8779GnhXfttMD+G+C2LPDGL/8PYDgqTfbfwAyBWv2hBAolwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;transactionStatus&quot;
        title=&quot;&quot;
        src=&quot;/static/4b988c7e14f0a66e7ac4d3ba40607a45/1cfc2/transactionStatus.png&quot;
        srcset=&quot;/static/4b988c7e14f0a66e7ac4d3ba40607a45/3684f/transactionStatus.png 225w,
/static/4b988c7e14f0a66e7ac4d3ba40607a45/fc2a6/transactionStatus.png 450w,
/static/4b988c7e14f0a66e7ac4d3ba40607a45/1cfc2/transactionStatus.png 900w,
/static/4b988c7e14f0a66e7ac4d3ba40607a45/fbf76/transactionStatus.png 1252w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그리고 DataSource와 TransactionStatus를 사용하는 &lt;code class=&quot;language-text&quot;&gt;PlatformTransactionManager&lt;/code&gt; 인터페이스를 제공한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;org.springframework.transaction.PlatformTransactionManager&lt;/code&gt; 인터페이스는 명령형 트랜잭션 관리를 위한   트랜잭션 추상화의 핵심이다.&lt;br&gt;
(반응형을 위한 ReactiveTransactionManager 인터페이스도 존재한다.)&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public interface PlatformTransactionManager extends TransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) 
        throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f0f039ea5f89657df54fda01d9f03f06/22284/platformTransactionManager.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 32%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAIAAABM9SnKAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA5UlEQVR42k2QCY7DMAhFc48m3rJ6iW0wTtK0c/9rDWnVqtKTBZgPXzRCmi+d0CGu5/m435/Oh3Faptm9sFL1v51vmk6wxghlpL7efhxp35Dq4ixWSrlAISTS/SAVC/SHl5hXIRKUwh1rzFyaFj9MVpmxYy/S3FoldZ8AMhLWI0PBsvXDfGtlwx82RCACpARY6uGBQsL9eG7Hn424QqXtBNpLPUOuCTcsOzuaF8e2Ne/hnTFTTBhj5tTa4H1kxsku1nu/csW56wqz9dYFHyIfohFCs8OEBS4/lafwuLZTb37jT6q/8T+c3DjL4t5WcQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;platformTransactionManager&quot;
        title=&quot;&quot;
        src=&quot;/static/f0f039ea5f89657df54fda01d9f03f06/1cfc2/platformTransactionManager.png&quot;
        srcset=&quot;/static/f0f039ea5f89657df54fda01d9f03f06/3684f/platformTransactionManager.png 225w,
/static/f0f039ea5f89657df54fda01d9f03f06/fc2a6/platformTransactionManager.png 450w,
/static/f0f039ea5f89657df54fda01d9f03f06/1cfc2/platformTransactionManager.png 900w,
/static/f0f039ea5f89657df54fda01d9f03f06/21482/platformTransactionManager.png 1350w,
/static/f0f039ea5f89657df54fda01d9f03f06/d61c2/platformTransactionManager.png 1800w,
/static/f0f039ea5f89657df54fda01d9f03f06/22284/platformTransactionManager.png 2428w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;리소스-동기화&quot; style=&quot;position:relative;&quot;&gt;리소스 동기화&lt;a href=&quot;#%EB%A6%AC%EC%86%8C%EC%8A%A4-%EB%8F%99%EA%B8%B0%ED%99%94&quot; aria-label=&quot;리소스 동기화 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;스프링은 Connection 동기화를 위해 &lt;code class=&quot;language-text&quot;&gt;ThreadLocal&lt;/code&gt;을 사용하는 &lt;strong&gt;TransactionSynchronizationManager&lt;/strong&gt; 를 제공한다.&lt;br&gt;
AbstractPlatformTransactionManager와 이 추상 클래스를 상속하고 있는 하위 클래스는 템플릿 메서드 패턴으로 협력하며, TransactionSynchronizationManager를 이용한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class MemberService {

    private final PlatformTransactionManager transactionManager;

    public void accountTransfer(String fromId, String toId, int money) {
        // TransactionStatus를 생성하면서 트랜잭션을 시작한다.
        TransactionStatus status = transactionManager.getTransaction(
            new DefaultTransactionDefinition()
        );

        try {
            bizLogic(fromId, toId, money);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw new IllegalStateException(e);
        }
    }
    ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ceb7c0749bcfbdac796575bc45f62089/1c181/transactionSynchronization.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABt0lEQVR42nWS227iUAxF+f/vQTwj8VAQggppWkBcCoWQEEJCroTc1tinoepoZo7kWMmJl+1td/jfaRo4nWD/+eUdB85nqjDEEb/dbNiIrddroihqQxo6Pxl5nuN5nsQ6BEFAld8pAp/d+zt4F7hcqOX99WVEt9tlOByyXC65Xq9/A+u6lgLOTKdTer0e8/kc7+RQJSlhnFDUUIrdC7C9CN/3sSwL27ZNhff73dg3sKoqacXlbb5gNBzxJlU57pVM7qy4YevDIYT4AYlAmzbuKFDtShOEIkeHsqQp5K+qJLmF2HuL+HjAl2qLNAW5q8qKSgi16tqSVJLD4cBkMmG1WhlolmUClMfjFhNrG15K5CdEbkAaZoR+Sh5EBqqcoiiMzqqV6rZYLOj3+0amZ5Wm5Vqzi4/yhs1qy6/pK57rfQndtlZKJ67rcrvdDEyrUc2N1i1ME3ae0zFtJA/so0WZpZxVcFkXDVK7yIQ1UI9ugbZ7au+f8Xo6z5UzwLRk/bEjkCq22w8zRQ3WyhQwGAyYzWaMx2MzWR2kmm6IQr/X5slPHjW745n90cbx/D8yq3YK1m/qFfSv8xuiKPpAxN4VpAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;transactionSynchronization&quot;
        title=&quot;&quot;
        src=&quot;/static/ceb7c0749bcfbdac796575bc45f62089/1cfc2/transactionSynchronization.png&quot;
        srcset=&quot;/static/ceb7c0749bcfbdac796575bc45f62089/3684f/transactionSynchronization.png 225w,
/static/ceb7c0749bcfbdac796575bc45f62089/fc2a6/transactionSynchronization.png 450w,
/static/ceb7c0749bcfbdac796575bc45f62089/1cfc2/transactionSynchronization.png 900w,
/static/ceb7c0749bcfbdac796575bc45f62089/21482/transactionSynchronization.png 1350w,
/static/ceb7c0749bcfbdac796575bc45f62089/d61c2/transactionSynchronization.png 1800w,
/static/ceb7c0749bcfbdac796575bc45f62089/1c181/transactionSynchronization.png 2696w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;DataSourceUtils.getConnection()&lt;/code&gt; : TransactionSynchronizationManager가 관리하는 Connection이 있으면 해당 Connection을 반환한다. 없으면 생성해서 반환한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;DataSourceUtils.releaseConnection()&lt;/code&gt; : 동기화된 Connection은 닫지 않고, 동기화되지 않은 Connection은 닫아버린다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;트랜잭션-aop&quot; style=&quot;position:relative;&quot;&gt;트랜잭션 AOP&lt;a href=&quot;#%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-aop&quot; aria-label=&quot;트랜잭션 aop permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;서비스 추상화를 통해 유연해졌고, 트랜잭션 동기화 매니저를 통해 Connection도 동기화하였다.&lt;br&gt;
하지만 아직 서비스 계층에서 트랜잭션 관련 코드가 남아있는데, 이 문제를 &lt;code class=&quot;language-text&quot;&gt;@Transacitonal&lt;/code&gt;을 통해 해결할 수 있다.&lt;br&gt;
일반적으로 데이터베이스, 트랜잭션 관련 API를 직접 사용하는 것보다 Spring 추상화를 통한 작업이 훨씬 더 유연하기 때문에 DataSourceUtils나 다른 헬퍼 클래스 사용을 자제하는 것이 좋다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// [4]
public interface Service {
    // [3]
    void method1();
    // [3]
    void method2();
}

// [2]
public class ServiceImpl implements Service {
    // [1]
    public void method1(){ ... }
    // [1]
    public void method2() { ... }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;을 적용할 때 &lt;strong&gt;4단계의 대체정책&lt;/strong&gt; 을 이용한다.&lt;br&gt;
타깃 오브젝트의 메소드 → 타깃 클래스 → 선언 메소드 → 선언 클래스(또는 인터페이스)의 순서에 따라서 적용됐는지 차례대로 확인하고,
가장 먼저 발견되는 속성정보를 사용하게된다.&lt;br&gt;
그리고 &lt;code class=&quot;language-text&quot;&gt;public&lt;/code&gt;으로 정의된 메서드만 트랜잭션이 걸리게 스프링이 제한해놓았다.&lt;/p&gt;
&lt;p&gt;이 AOP를 적용하기 위해 아래의 클래스가 제공된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;어드바이저&lt;/strong&gt; : &lt;code class=&quot;language-text&quot;&gt;BeanFactoryTransactionAttributeSourceAdvisor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;포인트컷&lt;/strong&gt; : &lt;code class=&quot;language-text&quot;&gt;TransactionAttributeSourcePointcut&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;어드바이스&lt;/strong&gt; : &lt;code class=&quot;language-text&quot;&gt;TransactionInterceptor&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/49c8549d53cd8118065147c65dac8727/50f29/transactionalAOP.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 23.111111111111114%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA9ElEQVR42mWPy3KEIBBF/ZDogCiCgLxEZ5GqSVZj/v+DbhomySaLW5fu0y+6fuCQSsM6j+E2/lPlbz1vXuNFrRiFfMXDizMuoLSBUgZdLXKbRy4FYpLUzFrR74BpXjDPqr1vTCDmTEMNeqqrTNDw2pfyTiroBCV9TAiU0Ktr2wUNqD7JBWkvKOcd86JRa3M56IBIC0dYvyGmHcY6yt9p2YlunmaY1cLaAE3f8Z4gXeHpamsMgg+IIcMZh3XViDE2xtkIMU6Np3iQRyip0F17wjN6XDk2fZHelURhAw7O8eHsH3vo5SfPcI4Mn8SeKZCIk19hwzeDEZUva0tLfQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;transactionalAOP&quot;
        title=&quot;&quot;
        src=&quot;/static/49c8549d53cd8118065147c65dac8727/1cfc2/transactionalAOP.png&quot;
        srcset=&quot;/static/49c8549d53cd8118065147c65dac8727/3684f/transactionalAOP.png 225w,
/static/49c8549d53cd8118065147c65dac8727/fc2a6/transactionalAOP.png 450w,
/static/49c8549d53cd8118065147c65dac8727/1cfc2/transactionalAOP.png 900w,
/static/49c8549d53cd8118065147c65dac8727/21482/transactionalAOP.png 1350w,
/static/49c8549d53cd8118065147c65dac8727/d61c2/transactionalAOP.png 1800w,
/static/49c8549d53cd8118065147c65dac8727/50f29/transactionalAOP.png 3038w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4b3bc8b5068ee8ed290789cdd4624700/fe6cb/transactional.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8ElEQVR42oVTa2/aQBD0//9FqZR8AjUKEUmkBgOC2Bgwxg9e5mH8nM5cYtRUlXrScvZ5b3ZmdrHwn1VVFc7nM47HI06nE+rLBXUQ4Oy6AHesVkAUKdHkW/ppmuYbSFMUaK5XgJe3cYzxeIyH+3vYwyHSJMGV4do26rJEmecomV/y+QZY1zWyLDNMtFcMbDYmouUS/ZcX/Li7Q+/5Gcl6bYDsfh+z2Qwh2SliFs4JbgATVhyNRhgMBvj1/o5AcnhJvBsWU2JBFiqmdSFz5Ym5TaYu8xeLhbHEAK7oxVOvhzcy+fn4CPfjAzcr/rKj9TU9HJCmKSaTCcIwpI2RKWqJiaJm9Wa///RPZzL5y+jW4z+9Fqh8W9ISyRWgClgFfSp3O+TcL/yYcS8IXPAs226NDHkrADG4slnyXDZNp1N0Oh0jW++ywsqZpBUTbPz6isj3b0w2BNywQEBLNDZ6Fos9C4qVgLrdLobsvnKMh6Jds3pAoBVj7s1MtYDzpXc1RHIcxzFdFbDPczVoRxUqUn81TkQsyah4sKax3nzOjs0MgEBluJh4nmcANQVz5kiqvutM4Ac2qF3WJzKwCGI43hJ+mNCvGu04yTOBik17sWVllFDqN0C96IJPiQN64nBklCSfNA6aNUmVRMkViEj86x8mwN/6XJBgmdiPlQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;transactional&quot;
        title=&quot;&quot;
        src=&quot;/static/4b3bc8b5068ee8ed290789cdd4624700/1cfc2/transactional.png&quot;
        srcset=&quot;/static/4b3bc8b5068ee8ed290789cdd4624700/3684f/transactional.png 225w,
/static/4b3bc8b5068ee8ed290789cdd4624700/fc2a6/transactional.png 450w,
/static/4b3bc8b5068ee8ed290789cdd4624700/1cfc2/transactional.png 900w,
/static/4b3bc8b5068ee8ed290789cdd4624700/21482/transactional.png 1350w,
/static/4b3bc8b5068ee8ed290789cdd4624700/d61c2/transactional.png 1800w,
/static/4b3bc8b5068ee8ed290789cdd4624700/fe6cb/transactional.png 2879w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Spring AOP 덕분에 비즈니스 로직까지 모두 해결하였다. 프록시 생성에 대한 자세한 내용은 &lt;a href=&quot;https://jdalma.github.io/2024y/postprocessor/#%EB%B9%88-%ED%9B%84%EC%B2%98%EB%A6%AC%EA%B8%B0%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9E%90%EB%8F%99-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%83%9D%EC%84%B1%EA%B8%B0&quot;&gt;빈 후처리기를 이용한 프록시 생성에 대해&lt;/a&gt;를 참고하자.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;spring-61-이전-읽기쓰기-분리&quot; style=&quot;position:relative;&quot;&gt;Spring 6.1 이전 읽기/쓰기 분리&lt;a href=&quot;#spring-61-%EC%9D%B4%EC%A0%84-%EC%9D%BD%EA%B8%B0%EC%93%B0%EA%B8%B0-%EB%B6%84%EB%A6%AC&quot; aria-label=&quot;spring 61 이전 읽기쓰기 분리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;JDBC부터 스프링이 Connection을 가져오는 방법을 추상화한 것과 ThreadLocal + TransactionSynchronizationManager를 통해 리소스 동기화를 하는 방법 그리고 스프링 AOP를 통해 트랜잭션 관련 로직과 비즈니스 로직을 완벽히 분리해내는 것까지 알아보았다.&lt;br&gt;
이제 본론으로 돌아가서 읽기/쓰기 분리한 방법에 대해 알아보자.&lt;br&gt;
테스트 환경은 Spring Boot 3.2.2, 도커 컨테이너로 직접 실행한 MySQL 컨테이너 2개, JdbcTemplate을 사용하였다.&lt;br&gt;
자세한 예제는 &lt;a href=&quot;https://github.com/jdalma/datasource-routing-test&quot;&gt;datasource-routing-test&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;yml&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;spring:
  datasource:
    primary:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/test
      username: root
      password: root
    secondary:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3307/test
      username: root
      password: root&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Configuration
class ReplicationDataSourceConfig {
    @Bean
    @Primary
    @DependsOn(&amp;quot;primaryDataSource&amp;quot;, &amp;quot;secondaryDataSource&amp;quot;, &amp;quot;routingDataSource&amp;quot;)
    fun dataSource(): DataSource = LazyConnectionDataSourceProxy(routingDataSource())

    @Bean
    fun routingDataSource(): DataSource = RoutingDataSource(primaryDataSource(), secondaryDataSource())

    @Bean
    @ConfigurationProperties(prefix = &amp;quot;spring.datasource.primary&amp;quot;)
    fun primaryDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }

    @Bean
    @ConfigurationProperties(prefix = &amp;quot;spring.datasource.secondary&amp;quot;)
    fun secondaryDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }
}

class RoutingDataSource(
    primary: DataSource,
    secondary: DataSource
): AbstractRoutingDataSource() {
    private val log = LoggerFactory.getLogger(RoutingDataSource::class.java)

    init {
        this.setTargetDataSources(mapOf(
            READ_WRITE to primary,
            READ_ONLY to secondary
        ))
        this.setDefaultTargetDataSource(primary)
    }

    override fun determineCurrentLookupKey(): Any {
        return DataSourceType.from(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).apply {
            log.info(&amp;quot;DataSource Type : {}&amp;quot;, this)
        }
    }

    private enum class DataSourceType {
        READ_WRITE,
        READ_ONLY;

        companion object {
            fun from(isReadOnly: Boolean) = if (isReadOnly) READ_ONLY else READ_WRITE
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;각 DB로 라우팅 역할을 하는 &lt;code class=&quot;language-text&quot;&gt;RoutingDataSource&lt;/code&gt;에 primary, secondary DataSource를 주입하고 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionDataSourceProxy&lt;/code&gt;로 감싼 DataSource를 &lt;code class=&quot;language-text&quot;&gt;@Primary&lt;/code&gt;로 등록한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5233d78fec13777390de802c6374de46/64ef0/lazyConnection.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABkklEQVR42pWST2+CQBTE/f4fyIs9a+KhXmqtghFEUFH+KCqCsNMZQlOb0kM3eexulp0377evh9sNyHM0c7s+7XbYWBbWjHC9RlUU0DDG4PF4oK6qJiqG9s+jl18ucFYrJFEE8GJNQddx8D6f4202w3yxQMZ/NLIsQ8T/7OUSHzx3XRdxHON+v38LGmWRkDLVtWwgSRJs6MznhcD3UbYOb0zmBwE8CnpM6noegu32h8seOobRh4kgZ8JAZ3LPmzCHA0BREAu0fhITkk7BnE7k0LVtpHRr5FxlMWoKV5oZBRMWWvNcPDX/EtTBgZlHoxH6/T4sMsyPxwZHQbGYCU7nM0o6i8hvv983Bm6sRIx/CSqLGC4I/XUygUNO9/ZRApb6MhhgOBzCJ1vtPZ7LgJJtybOzZImKh7gZlZymAF2ldHq9Xhsn0+kU4/EYNrHsyFOiFtusU/DrYdQB4mbEjwlSXpKYKgjDsMGjUiXk8NUl/qdg1zjTpZyXZdkI1y1XJbgQy5Lt9G/Bou1JNbPKl0OF9mr6T5feUCOP3qmLAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;lazyConnection&quot;
        title=&quot;&quot;
        src=&quot;/static/5233d78fec13777390de802c6374de46/1cfc2/lazyConnection.png&quot;
        srcset=&quot;/static/5233d78fec13777390de802c6374de46/3684f/lazyConnection.png 225w,
/static/5233d78fec13777390de802c6374de46/fc2a6/lazyConnection.png 450w,
/static/5233d78fec13777390de802c6374de46/1cfc2/lazyConnection.png 900w,
/static/5233d78fec13777390de802c6374de46/21482/lazyConnection.png 1350w,
/static/5233d78fec13777390de802c6374de46/d61c2/lazyConnection.png 1800w,
/static/5233d78fec13777390de802c6374de46/64ef0/lazyConnection.png 4071w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;대략적인 구조는 위와 같다.&lt;br&gt;
일반적인 경우에는 &lt;code class=&quot;language-text&quot;&gt;4. getConnection()&lt;/code&gt;에서 반환되는 것이 &lt;code class=&quot;language-text&quot;&gt;java.sql.Connection&lt;/code&gt;의 구현체가 반환되지만 현재 기본 DataSource로 등록된 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionDataSourceProxy.getConnection()&lt;/code&gt;을 사용하면 Proxy를 반환한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;LazyConnectionDataSourceProxy.getConnection()&lt;/strong&gt;&lt;br&gt;
Statement(또는 PreparedStatement나 CallableStatement)에 대한 요청이 있을 때 실제 JDBC Connection을 느리게 가져오는 Connection 핸들을 반환합니다.&lt;br&gt;
반환된 Connection 핸들은 ConnectionProxy 인터페이스를 구현하여 기본 대상 Connection을 검색할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
    
    @Override
    public Connection getConnection() throws SQLException {
        checkDefaultConnectionProperties();
        return (Connection) Proxy.newProxyInstance(
            ConnectionProxy.class.getClassLoader(),
            new Class&amp;lt;?&amp;gt;[] {ConnectionProxy.class},
            new LazyConnectionInvocationHandler()
        );
    }
    ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;어떤 흐름으로 사용할 Connection을 선택하는지 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3ad8ce5ae7adb9b6b42c9ec6b7057f1e/7798c/dataSourceProxy.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABpUlEQVR42oVS2W7bMBDU/39U+upXB25sJz7g1pKtIyJFk7p4TIeMkyIJihJYSKKWwzk2w/+Wc+ilhFEKfpqAYQCaBraqUF4uOO73OO52+HU8Yv/8jOzr+fAVjyBnHvh9OGDoOkBr4HbDxHeR51BFAUlgdb0iZ1/2DuJDQAjhG8F5nhOLzXoNE8FYoW0BVn8+Q5O5IWvnfaqsnzyE9ijEjFpZqMHDur/AloAtJTeUOUS59zWS+Xq7xY+HB6xWKxhj0n4mlcblWqF5bVHVr6mm2d31Ezj6RhZhHD/ZIXnJMM1ohGK/hRAyKcy0NqjKEsvlEtvthvbcMPQjEP0SIgUi6zqVvrNIgNFD1ePpUBJUp+8EKGSH0+mExWKRfKp4cDCUxidodAQ6MMHdywuainvefQDmxQVPGwZ2ztHy8iS5o+SSDOu6YdXJq3Gyb3JZ0egQZRPAMoiBEt3NwNHbaI33gcVA3NtFWQzAxvHQPdw4w/o4euFjgFqC/KTpq8dHFGQ087+1PvX470PBsYlM6BuuJVkIBCc/NcT0Cs5awVnrGM57Vv9afwBeiwL+2ajGhwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;dataSourceProxy&quot;
        title=&quot;&quot;
        src=&quot;/static/3ad8ce5ae7adb9b6b42c9ec6b7057f1e/1cfc2/dataSourceProxy.png&quot;
        srcset=&quot;/static/3ad8ce5ae7adb9b6b42c9ec6b7057f1e/3684f/dataSourceProxy.png 225w,
/static/3ad8ce5ae7adb9b6b42c9ec6b7057f1e/fc2a6/dataSourceProxy.png 450w,
/static/3ad8ce5ae7adb9b6b42c9ec6b7057f1e/1cfc2/dataSourceProxy.png 900w,
/static/3ad8ce5ae7adb9b6b42c9ec6b7057f1e/7798c/dataSourceProxy.png 1183w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1번부터 3번까지의 자세한 행위는 &lt;a href=&quot;https://jdalma.github.io/static/5233d78fec13777390de802c6374de46/64ef0/lazyConnection.png&quot;&gt;이 이미지&lt;/a&gt;와 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;커넥션을 요청한다.&lt;/li&gt;
&lt;li&gt;메소드에서 targetDataSource의 Connection을 통해 DB 세션의 자동 커밋 유무와 트랜잭션 격리수준을 저장한다. 이때 최초 Connection을 생성(커넥션 풀 준비)한다.&lt;/li&gt;
&lt;li&gt;ConnectionProxy를 구현한 타겟 클래스를 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionInvocationHandler&lt;/code&gt; 구현체로 동적 프록시를 생성하여 Connection 타입으로 반환하며 Connection을 바인딩한다.&lt;/li&gt;
&lt;li&gt;데이터 로직 처리에서 Query를 실행한다.&lt;/li&gt;
&lt;li&gt;바인딩되어 있는 Connection을 조회하여 반환한다.&lt;/li&gt;
&lt;li&gt;반환받은 Connection으로 Statement를 요청한다.&lt;/li&gt;
&lt;li&gt;RoutingDataSource를 생성할 때 &lt;code class=&quot;language-text&quot;&gt;setTargetDataSources()&lt;/code&gt;를 통해 정의해놓은 &lt;code class=&quot;language-text&quot;&gt;resolvedDataSources&lt;/code&gt;필드인 라우팅 맵을 이용하여 사용할 Connection을 조회한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;2번&lt;/code&gt;단계는 Connection이 최초 사용되는 경우라면 LazyConnectionDataSourceProxy의 auto-commit 유무와 격리수준 레벨을 세팅하기 위한 Connection을 최초로 생성하는 과정이 발생한다.&lt;br&gt;
이때 구현 드라이버에 따라 Connectipn Pool이 준비되고 바인딩된다.&lt;br&gt;
이 targetDataSource는 내가 커스텀한 RoutingDataSource가 주입되어 있으며 &lt;code class=&quot;language-text&quot;&gt;determineCurrentLookupKey()&lt;/code&gt;를 통해 readonly가 아닌 경우에는 항상 primary DB를 사용하도록 하여서 primary HikariCP가 먼저 준비된다.&lt;/p&gt;
&lt;p&gt;일반적인 경우에는 &lt;code class=&quot;language-text&quot;&gt;3번&lt;/code&gt;단계에서 바인딩되는 DataSource는 사용할 DataSource 구현체 자체가 저장되지만, LazyConnectionDataSourceProxy로 래핑된 DataSource를 저장하여 &lt;strong&gt;Statement를 생성할 때 등록된 DataSource 라우팅을 통해 결정하는 것이 핵심이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;7번&lt;/code&gt;에서 LazyConnectionDataSourceProxy$LazyConnectionInvocationHandler.getConnection()을 통한 determineTargetDataSource() 메서드에서 RoutingDataSource가 오버라이딩한 &lt;code class=&quot;language-text&quot;&gt;determineCurrentLookupKey()&lt;/code&gt; 메소드를 실행시켜 어떤 DataSource를 사용할지 query가 실행되어 실제로 Connection이 필요한 경우에 결정하는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;datasource-router-not-initialized-예외-발생&quot; style=&quot;position:relative;&quot;&gt;DataSource router not initialized 예외 발생&lt;a href=&quot;#datasource-router-not-initialized-%EC%98%88%EC%99%B8-%EB%B0%9C%EC%83%9D&quot; aria-label=&quot;datasource router not initialized 예외 발생 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;조금 더 깔끔하게 작성하고 싶어 DataSource를 등록할 때 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionDataSourceProxy&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;RoutingDataSource&lt;/code&gt;를 한 번에 등록하면 아래와 같이 예외가 발생한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Configuration
class ReplicationDataSourceConfig {
    @Bean
    @Primary
    @DependsOn(&amp;quot;primaryDataSource&amp;quot;, &amp;quot;secondaryDataSource&amp;quot;)
    fun routingDataSource(): DataSource = LazyConnectionDataSourceProxy(
        RoutingDataSource(primaryDataSource(), secondaryDataSource())
    )

    ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;java.lang.IllegalArgumentException: DataSource router not initialized
...lookup.AbstractRoutingDataSource.determineTargetDataSource(AbstractRoutingDataSource.java:255)
...lookup.AbstractRoutingDataSource.getConnection(AbstractRoutingDataSource.java:213)
...LazyConnectionDataSourceProxy.checkDefaultConnectionProperties(LazyConnectionDataSourceProxy.java:212)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;원인은 RoutingDataSource가 상속받은 &lt;code class=&quot;language-text&quot;&gt;AbstractRoutingDataSource&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;resolvedDataSource&lt;/code&gt; 필드가 초기화되지 않아 발생한 문제다.&lt;br&gt;
초기화되는 시점은 &lt;code class=&quot;language-text&quot;&gt;InitializingBean&lt;/code&gt; 인터페이스를 통한 &lt;code class=&quot;language-text&quot;&gt;afterPropertiesSet()&lt;/code&gt; 시점에 (RoutingDataSource 초기화 시점에 정의한) &lt;code class=&quot;language-text&quot;&gt;targetDataSource&lt;/code&gt;를 복사하여 &lt;code class=&quot;language-text&quot;&gt;resolvedDataSource&lt;/code&gt;를 초기화한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactory.html&quot;&gt;Interface BeanFactory&lt;/a&gt;를 보면 BeanFactory의 12번째 단계에서 &lt;code class=&quot;language-text&quot;&gt;afterPropertiesSet()&lt;/code&gt;를 호출하여 준다.&lt;br&gt;
즉, &lt;strong&gt;AbstractRoutingDataSource를 상속한 RoutingDataSource 자체가 Bean으로 정의되지 않아 BeanFactory를 통한 초기화 과정이 생략되었기에 위와 같은 예외가 발생하는 것이다.&lt;/strong&gt;&lt;br&gt;
그렇기에 꼭 Bean으로 등록해주여야 한다.&lt;/p&gt;
&lt;h1 id=&quot;spring-61-이후-추가된-읽기쓰기-분리-개선&quot; style=&quot;position:relative;&quot;&gt;Spring 6.1 이후 추가된 읽기/쓰기 분리 개선&lt;a href=&quot;#spring-61-%EC%9D%B4%ED%9B%84-%EC%B6%94%EA%B0%80%EB%90%9C-%EC%9D%BD%EA%B8%B0%EC%93%B0%EA%B8%B0-%EB%B6%84%EB%A6%AC-%EA%B0%9C%EC%84%A0&quot; aria-label=&quot;spring 61 이후 추가된 읽기쓰기 분리 개선 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이번에 Spring 6.1.2 부터 &lt;code class=&quot;language-text&quot;&gt;setReadOnlyDataSource()&lt;/code&gt; 메소드가 추가되었으며, 자세한 히스토리는 아래의 이슈 내용을 확인하길 바란다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/31785&quot;&gt;#31785 Support for a read-only DataSource in LazyConnectionDataSourceProxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/21415&quot;&gt;#21415 Document LazyConnectionDataSourceProxy setup for routing datasource to act on transaction definition read-only flag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이전에 사용했던 읽기/쓰기 분리 기준을 정의해 놓았던 &lt;code class=&quot;language-text&quot;&gt;RoutingDataSource&lt;/code&gt;는 필요하지 않고 아래와 같이 작성하면 끝이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Configuration
class ReplicationDataSourceConfig {
    @Bean
    @Primary
    @DependsOn(&amp;quot;primaryDataSource&amp;quot;, &amp;quot;secondaryDataSource&amp;quot;)
    fun dataSource(): DataSource = LazyConnectionDataSourceProxy(primaryDataSource()).apply {
        setReadOnlyDataSource(secondaryDataSource())
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;Connection을 생성할 DataSource를 결정할 때 추가된 &lt;code class=&quot;language-text&quot;&gt;getDataSourceToUse()&lt;/code&gt; 메소드로 가져오게 된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
    private DataSource readOnlyDataSource;
    ...

    private class LazyConnectionInvocationHandler implements InvocationHandler {
        private boolean readOnly = false;

        private DataSource getDataSourceToUse() {
            return (this.readOnly &amp;amp;&amp;amp; readOnlyDataSource != null ? 
                readOnlyDataSource : obtainTargetDataSource()
            );
        }
        ...
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b8d37cbee21c6d8847ad1011bf546150/b1c31/readonlyDataSource.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABoUlEQVR42o1S2ZLCMAzj//9vC7NQoNBy9E56kTaNVg6wwwN7eMZN48SypXihLheoLMNhvcaeLv9dUcBZCzvPGG43FGUJ3TSYJGYMwBiGAWBMHY9QaQrwTGxRclPlOcrrFcVuh3K/hyaAB2w0TF0hPxygzidYrTC1Ldw4AvSJwDnz0vMZYHEPKB/nHGJWipno5IB7sZ7A4km4QZ7EMCzQVxW7uSeP04SS+1wYPXIW8mPZTc/KPatZ0vCHjHWM6a7DibJkPGuZPApVKSrUuY5aY1TKdywx32Fd11itVvikV9TQ8RKEGim5m7kXmCza6wXpdossimB4Dyw29z1m3hUXXT2gIXJJahWBbww6XpLL3rk3BOu5ZqQWUucojjExp+N5GIbY8DEbAXxq+M4sm6rISpNJUTdYBh9YLpcIggAROxyFIjufyWImdc+C/g0ogaewz72dmcBQrVsvS8qJEJduBtFNxoeaexdWv3X4ak3b4XzNfGenJPHyDA8A/0Ayg69j85cJvapWyNldwcfwQ86ReWf/AhRtnOGLc2xkAmbR7wf7AtmAVZ0aJr+yAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;readonlyDataSource&quot;
        title=&quot;&quot;
        src=&quot;/static/b8d37cbee21c6d8847ad1011bf546150/1cfc2/readonlyDataSource.png&quot;
        srcset=&quot;/static/b8d37cbee21c6d8847ad1011bf546150/3684f/readonlyDataSource.png 225w,
/static/b8d37cbee21c6d8847ad1011bf546150/fc2a6/readonlyDataSource.png 450w,
/static/b8d37cbee21c6d8847ad1011bf546150/1cfc2/readonlyDataSource.png 900w,
/static/b8d37cbee21c6d8847ad1011bf546150/21482/readonlyDataSource.png 1350w,
/static/b8d37cbee21c6d8847ad1011bf546150/b1c31/readonlyDataSource.png 1413w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;1 ~ 3번&lt;/code&gt;은 스프링이 초기화하면서 &lt;code class=&quot;language-text&quot;&gt;SpringTransactionAnnotationParser&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;TransactionAttribute&lt;/code&gt;를 생성하며 생성에 성공한다면 해당 &lt;code class=&quot;language-text&quot;&gt;TransactionAttribute&lt;/code&gt;를 캐싱한다.&lt;br&gt;
캐싱되는 내용은 간략하게 아래와 같다. 자세한 내용은 &lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java#L59&quot;&gt;SpringTransactionAnnotationParser #L59&lt;/a&gt;를 확인하면 된다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;4 ~ 5번&lt;/code&gt;은 캐시되어 있는 &lt;code class=&quot;language-text&quot;&gt;TransactionAttribute&lt;/code&gt; 목록에서 현재 실행되고 있는 메소드 또는 클래스 기준으로 캐시 정보를 조회하며, 준비 작업을 한다.&lt;br&gt;
이때 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionDataSourceProxy&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;readonly&lt;/code&gt; 속성을 &lt;code class=&quot;language-text&quot;&gt;TransactionAttribute&lt;/code&gt; 기준으로 업데이트 해준다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TransactionAttribute에는 Transaction의 격리 수준, 전파 속성, &lt;strong&gt;읽기 전용 유무&lt;/strong&gt; , 타임아웃, 롤백할 클래스 이름 등이 저장되어 있다.&lt;br&gt;
한마디로 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;에서 기입할 수 있는 정보들을 묶어 놓은 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;{MethodClassKey@10042} &amp;quot;...MemberService.findAllPrimary() -&amp;gt; 
    {RuleBasedTransactionAttribute@10043} &amp;quot;PROPAGATION_REQUIRED,ISOLATION_DEFAULT&amp;quot;
{MethodClassKey@9559} &amp;quot;...MemberService.findAllSecondary() -&amp;gt; 
    {RuleBasedTransactionAttribute@9560} &amp;quot;PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly&amp;quot;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;7번&lt;/code&gt;의 동적 프록시 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionInvocationHandler&lt;/code&gt;는 매 요청마다 매번 새로 생성된다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;8번&lt;/code&gt; 과정에서 DataSource를 가져올 때 &lt;code class=&quot;language-text&quot;&gt;readOnly&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;readOnlyDataSource&lt;/code&gt; 유무를 기준으로 어떤 DataSource를 사용할지 결정된다.&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;readOnlyDataSource&lt;/code&gt; 관리를 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionDataSourceProxy&lt;/code&gt;가 해주면서 추가적인 작업이 필요없어졌다.&lt;/strong&gt;&lt;br&gt;
만약 readonly 힌트로 라우팅할 것이 아니라면 이전 방법처럼 &lt;code class=&quot;language-text&quot;&gt;determineCurrentLookupKey()&lt;/code&gt;를 오버라이딩해야 한다.&lt;/p&gt;
&lt;h1 id=&quot;참고&quot; style=&quot;position:relative;&quot;&gt;참고&lt;a href=&quot;#%EC%B0%B8%EA%B3%A0&quot; aria-label=&quot;참고 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://etutorials.org/SQL/Postgresql/Part+II+Programming+with+PostgreSQL/Chapter+13.+Using+PostgreSQL+from+a+Java+Client+Application/JDBC+Architecture+Overview/&quot;&gt;JDBC Architecture Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/java-jdbc&quot;&gt;Introduction to JDBC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/java-jdbc-loading-drivers&quot;&gt;Loading JDBC Drivers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ONYVhJGl48U&amp;#x26;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&quot;&gt;[10분 테코톡] 코코닥의 JDBC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jiwondev.tistory.com/291&quot;&gt;JDBC Connection 에 대한 이해, HikariCP 설정 팁&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1&quot;&gt;스프링 DB 1편 - 데이터 접근 핵심 원리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/tx-resource-synchronization.html&quot;&gt;Synchronizing Resources with Transactions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/data-access/transaction/strategies.html#page-title&quot;&gt;Understanding the Spring Framework Transaction Abstraction&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[invokedynamic 이란?]]></title><description><![CDATA[…]]></description><link>https://jdalma.github.io/2024y/invokedynamic/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/invokedynamic/</guid><pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;발단&quot; style=&quot;position:relative;&quot;&gt;발단&lt;a href=&quot;#%EB%B0%9C%EB%8B%A8&quot; aria-label=&quot;발단 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;자바와 코틀린에서 람다를 처리하는 방식이 다르다는 것을 알게됐다.&lt;br&gt;
코틀린을 먼저 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun main(args: Array&amp;lt;String&amp;gt;) {
    val numbers = listOf(1,2,4,5,6,7,8,9)
    val number = 7

    val result = numbers.filter { it == number }
}
private fun &amp;lt;T&amp;gt; Iterable&amp;lt;T&amp;gt;.filter(predicate: (T) -&amp;gt; Boolean): Iterable&amp;lt;T&amp;gt; {
    val destination = arrayListOf&amp;lt;T&amp;gt;()
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;(T) -&gt; Boolean&lt;/code&gt; 람다를 전달받아 해당 람다의 결과를 반환하는 확장함수에 람다를 전달하는 코틀린 코드는 아래와 같이 컴파일된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public final class MainKt {
   public static final void main(@NotNull String[] args) {
      ...
      Iterable result = filter((Iterable)numbers, (Function1)(new Function1() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1) {
            return this.invoke(((Number)var1).intValue());
         }

         public final boolean invoke(int it) {
            return it == number;
         }
      }));
   }

   private static final Iterable filter(Iterable $this$filter, Function1 predicate) {
      ...
      while(var4.hasNext()) {
         Object element = var4.next();
         if ((Boolean)predicate.invoke(element)) {
            destination.add(element);
         }
      }
      ...
   }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;filter()&lt;/code&gt;에 전달된 람다가 &lt;code class=&quot;language-text&quot;&gt;Function1&lt;/code&gt; 타입의 인스턴스로 생성되어 &lt;code class=&quot;language-text&quot;&gt;invoke()&lt;/code&gt; 함수를 통해 실행되는 것을 확인할 수 있다.&lt;br&gt;
이렇게 람다를 함수 유형의 인스턴스로 생성하여 사용하는 것은 단점이 존재한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;컴파일러는 익명 클래스에 대응하는 새로운 클래스 파일을 생성하며, 이 클래스를 사용하려면 각각의 클래스를 로드하고 검증하는 과정이 필요하므로 애플리케이션 스타트업의 성능에 악영향을 미친다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;새로운 익명 클래스는 클래스나 인터페이스의 새로운 서브형식을 만든다.&lt;/strong&gt; Comparator를 표현하는 수백 개의 람다가 있다면 결국 수백 가지의 Comparator 서브형식이 생긴다는 의미다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;코틀린은 자바6 와의 호환성을 위해 기본적으로 람다를 항상 함수 유형의 인스턴스를 생성하여(익명 클래스) 사용하기 때문에 &lt;code class=&quot;language-text&quot;&gt;inline&lt;/code&gt; 키워드를 사용하여 성능상 이점을 누릴 수 있다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;filter()&lt;/code&gt; 확장함수에 &lt;code class=&quot;language-text&quot;&gt;inline&lt;/code&gt; 키워드를 추가하면 컴파일러가 람다의 바이트 코드를 삽입하여 주기 때문에 아래와 같이 오버헤드를 줄일 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public final class MainKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkNotNullParameter(args, &amp;quot;args&amp;quot;);
      List numbers = CollectionsKt.listOf(new Integer[]{1, 2, 4, 5, 6, 7, 8, 9});
      int number = 7;
      Iterable $this$filter$iv = (Iterable)numbers;
      int $i$f$filter = false;
      ArrayList destination$iv = new ArrayList();
      Iterator var7 = $this$filter$iv.iterator();

      while(var7.hasNext()) {
         Object element$iv = var7.next();
         int it = ((Number)element$iv).intValue();
         int var10 = false;
         if (it == number) {
            destination$iv.add(element$iv);
         }
      }

      Iterable result = (Iterable)destination$iv;
   }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h1 id=&quot;자바-바이트코드-확인하기&quot; style=&quot;position:relative;&quot;&gt;자바 바이트코드 확인하기&lt;a href=&quot;#%EC%9E%90%EB%B0%94-%EB%B0%94%EC%9D%B4%ED%8A%B8%EC%BD%94%EB%93%9C-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0&quot; aria-label=&quot;자바 바이트코드 확인하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;코틀린에서는 람다를 사용할 때 생기는 문제를 해결하기 위해 &lt;code class=&quot;language-text&quot;&gt;inline&lt;/code&gt; 키워드를 제공하는 것을 알아보았다.&lt;br&gt;
그럼 자바는 여전히 함수 인스턴스를 매번 생성해서 사용할까?&lt;br&gt;
익명 클래스와 람다를 바이트코드로 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class InnerClass {
    Function&amp;lt;Object, String&amp;gt; toString1 = new Function&amp;lt;Object, String&amp;gt;() {
        @Override
        public String apply(Object o) {
            return o.toString();
        }
    };
    Function&amp;lt;Object, String&amp;gt; toString2 = Object::toString;

    public static void main(String[] args) {
        InnerClass innerClass = new InnerClass();
        innerClass.toString1.apply(&amp;quot;test&amp;quot;);
        innerClass.toString2.apply(&amp;quot;test&amp;quot;);
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class org.example.InnerClass {
  java.util.function.Function&amp;lt;java.lang.Object, java.lang.String&amp;gt; toString1;
  java.util.function.Function&amp;lt;java.lang.Object, java.lang.String&amp;gt; toString2;

  public org.example.InnerClass();
    Code:
       0: aload_0
       1: invokespecial #1      // Method java/lang/Object.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
       4: aload_0
       5: new           #2      // class org/example/InnerClass$1
       8: dup
       9: aload_0
      10: invokespecial #3      // Method org/example/InnerClass$1.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:(Lorg/example/InnerClass;)V
      13: putfield      #4      // Field toString1:Ljava/util/function/Function;
      16: aload_0
      17: invokedynamic #5,  0  // InvokeDynamic #0:apply:()Ljava/util/function/Function;
      22: putfield      #6      // Field toString2:Ljava/util/function/Function;
      25: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #7      // class org/example/InnerClass
       3: dup
       4: invokespecial #8      // Method &amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
       7: astore_1
       8: aload_1
       9: getfield      #4      // Field toString1:Ljava/util/function/Function;
      12: ldc           #9      // String test
      14: invokeinterface #10,2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
      19: pop
      20: aload_1
      21: getfield      #6      // Field toString2:Ljava/util/function/Function;
      24: ldc           #9      // String test
      26: invokeinterface #10,2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
      31: pop
      32: return
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;InnerClass&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;#1&lt;/code&gt;부터 &lt;code class=&quot;language-text&quot;&gt;#10&lt;/code&gt;까지는 익명 클래스로 지정한 &lt;code class=&quot;language-text&quot;&gt;toString1&lt;/code&gt;의 설명이며 &lt;code class=&quot;language-text&quot;&gt;InnerClass$1&lt;/code&gt;이라는 이름은 컴파일러가 익명 클래스에 붙인 이름이다.&lt;br&gt;
그리고 &lt;code class=&quot;language-text&quot;&gt;new&lt;/code&gt; 연산을 통해 메모리를 힙 안에 할당하고, 할당된 위치를 가리키는 참조를 오퍼랜드 스택에 쌓고 &lt;code class=&quot;language-text&quot;&gt;invokespecial&lt;/code&gt;을 통해 실행된다.&lt;br&gt;
하지만 &lt;strong&gt;람다로 지정한 &lt;code class=&quot;language-text&quot;&gt;toString2&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;invokedynamic&lt;/code&gt; 연산만 호출된다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;invokedynamic-이란&quot; style=&quot;position:relative;&quot;&gt;invokedynamic 이란?&lt;a href=&quot;#invokedynamic-%EC%9D%B4%EB%9E%80&quot; aria-label=&quot;invokedynamic 이란 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;deckgo-highlight-code language=&quot;ruby&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;def addtwo(a, b)
    a + b;
end&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 코드를 컴파일할 때는 a와 b의 형식을 알 수 없듯이 동적 타입 언어 컴파일의 난제는 프로그램이 컴파일된 후 메서드나 함수의 가장 적절한 구현을 선택할 수 있는 런타임 시스템을 구현하는 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;자바 7부터 JVM 자체에서 자바 언어뿐만 아니라 다른 언어, 특히 스크립트 언어들과 같이 타입이 고정되어 있지 않은 동적 타입 언어를 지원하기 위해 &lt;code class=&quot;language-text&quot;&gt;invokedynamic&lt;/code&gt;이 추가되었다.&lt;/strong&gt;&lt;br&gt;
(이 명령어는 Java 8에서 람다 표현식을 구현하기 위한 기반을 마련했을 뿐만 아니라 동적 언어를 Java 바이트 코드 형식으로 변환하는 데 있어서도 큰 전환점이 되었다.)&lt;/p&gt;
&lt;p&gt;실제 타입은 컴파일 시점에 존재하지 않고 런타임에 필요에 따라 생성되는데, 이 메커니즘을 이해하려면 &lt;code class=&quot;language-text&quot;&gt;Call sites&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Method handles&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Bootstrapping&lt;/code&gt;을 이해해야 한다.&lt;/p&gt;
&lt;h2 id=&quot;call-sites&quot; style=&quot;position:relative;&quot;&gt;Call sites&lt;a href=&quot;#call-sites&quot; aria-label=&quot;call sites permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;바이트 코드에서 메서드 호출 명령이 발생하는 위치를 &lt;code class=&quot;language-text&quot;&gt;call site&lt;/code&gt;라고 한다.&lt;br&gt;
이 메서드 호출에는 다양한 경우의 메서드 호출을 처리하기 위해 (기본적으로) 4가지의 opcode가 존재한다.&lt;/p&gt;
&lt;p&gt;아래의 예제를 통해 어떤 경우에 어떤 opcode가 사용되는지 알아보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 1. ParentClass를 상속하는 ChildClass (상속관계)
public class ParentClass {
    public void printMessage() { System.out.println(&amp;quot;ParentClass&amp;quot;); }
}
public class ChildClass extends ParentClass {
    @Override
    public void printMessage() { System.out.println(&amp;quot;ChildClass&amp;quot;); }
}

// 2. AbstractClass를 상속하는 AbstractChildClass (상속관계)
abstract class AbstractClass {
    public void printMessage() { System.out.println(&amp;quot;AbstractClass&amp;quot;); }
}
public class AbstractChildClass extends AbstractClass { }

// 3. Interface를 구현하는 ImplementClass (구현관계)
public interface Interface {
    void printMessage();
}
public class ImplementClass implements Interface {
    @Override
    public void printMessage() { System.out.println(&amp;quot;ImplementClass&amp;quot;); }
}

public class Main {

    public static void printMessage() { System.out.println(&amp;quot;Main&amp;quot;); }

    public static void main(String[] args) {
        // &amp;quot;invokevirtual&amp;quot; Method org/example/ParentClass.printMessage:()V
        ParentClass parentClass = new ParentClass();
        parentClass.printMessage(); 

        // &amp;quot;invokevirtual&amp;quot; Method org/example/ParentClass.printMessage:()V
        ParentClass parentClass1 = new ChildClass();
        parentClass1.printMessage();

        // &amp;quot;invokevirtual&amp;quot; Method org/example/ChildClass.printMessage:()V
        ChildClass childClass = new ChildClass();
        childClass.printMessage();

        // &amp;quot;invokevirtual&amp;quot; Method org/example/AbstractClass.printMessage:()V
        AbstractClass abstractClass = new AbstractChildClass();
        abstractClass.printMessage();

        // &amp;quot;invokevirtual&amp;quot; Method org/example/AbstractChildClass.printMessage:()V
        AbstractChildClass abstractClass1 = new AbstractChildClass();
        abstractClass1.printMessage();

        // &amp;quot;invokeinterface&amp;quot; InterfaceMethod org/example/Interface.printMessage:()V
        Interface implementClass = new ImplementClass();
        implementClass.printMessage();

        // &amp;quot;invokevirtual&amp;quot; Method org/example/ImplementClass.printMessage:()V
        ImplementClass implementClass1 = new ImplementClass();
        implementClass1.printMessage();

        // &amp;quot;invokestatic&amp;quot; Method printMessage:()V
        Main.printMessage();
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;details&gt;
&lt;summary&gt;모든 바이트코드 펼치기&lt;/summary&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// javap -v -p -s {class}
Constant pool:
  #1 = Methodref          #21.#49        // java/lang/Object.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
  #2 = Fieldref           #50.#51        // java/lang/System.out:Ljava/io/PrintStream;
  #3 = String             #52            // Main
  #4 = Methodref          #53.#54        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #5 = Class              #55            // org/example/ParentClass
  #6 = Methodref          #5.#49         // org/example/ParentClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
  #7 = Methodref          #5.#56         // org/example/ParentClass.printMessage:()V
  #8 = Class              #57            // org/example/ChildClass
  #9 = Methodref          #8.#49         // org/example/ChildClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
  #10 = Methodref          #8.#56         // org/example/ChildClass.printMessage:()V
  #11 = Class              #58            // org/example/AbstractChildClass
  #12 = Methodref          #11.#49        // org/example/AbstractChildClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
  #13 = Methodref          #59.#56        // org/example/AbstractClass.printMessage:()V
  #14 = Methodref          #11.#56        // org/example/AbstractChildClass.printMessage:()V
  #15 = Class              #60            // org/example/ImplementClass
  #16 = Methodref          #15.#49        // org/example/ImplementClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
  #17 = InterfaceMethodref #61.#56        // org/example/Interface.printMessage:()V
  #18 = Methodref          #15.#56        // org/example/ImplementClass.printMessage:()V
  #19 = Methodref          #20.#56        // org/example/Main.printMessage:()V
  #20 = Class              #62            // org/example/Main
  #21 = Class              #63            // java/lang/Object
  ...
{
  public org.example.Main();
    ...

  public static void printMessage();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Main
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=8, args_size=1
         0: new           #5                  // class org/example/ParentClass
         3: dup
         4: invokespecial #6                  // Method org/example/ParentClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
         7: astore_1
         8: aload_1
         9: invokevirtual #7                  // Method org/example/ParentClass.printMessage:()V
        12: new           #8                  // class org/example/ChildClass
        15: dup
        16: invokespecial #9                  // Method org/example/ChildClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
        19: astore_2
        20: aload_2
        21: invokevirtual #7                  // Method org/example/ParentClass.printMessage:()V
        24: new           #8                  // class org/example/ChildClass
        27: dup
        28: invokespecial #9                  // Method org/example/ChildClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
        31: astore_3
        32: aload_3
        33: invokevirtual #10                 // Method org/example/ChildClass.printMessage:()V
        36: new           #11                 // class org/example/AbstractChildClass
        39: dup
        40: invokespecial #12                 // Method org/example/AbstractChildClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
        43: astore        4
        45: aload         4
        47: invokevirtual #13                 // Method org/example/AbstractClass.printMessage:()V
        50: new           #11                 // class org/example/AbstractChildClass
        53: dup
        54: invokespecial #12                 // Method org/example/AbstractChildClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
        57: astore        5
        59: aload         5
        61: invokevirtual #14                 // Method org/example/AbstractChildClass.printMessage:()V
        64: new           #15                 // class org/example/ImplementClass
        67: dup
        68: invokespecial #16                 // Method org/example/ImplementClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
        71: astore        6
        73: aload         6
        75: invokeinterface #17,  1           // InterfaceMethod org/example/Interface.printMessage:()V
        80: new           #15                 // class org/example/ImplementClass
        83: dup
        84: invokespecial #16                 // Method org/example/ImplementClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
        87: astore        7
        89: aload         7
        91: invokevirtual #18                 // Method org/example/ImplementClass.printMessage:()V
        94: invokestatic  #19                 // Method printMessage:()V
        97: return
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/details&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;new&lt;/code&gt; : 인자로 지정된 클래스의 새 인스턴스에 필요한 메모리를 힙 안에 할당한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;invokespecial&lt;/code&gt; : 생성자 또는 슈퍼 클래스의 생성자를 호출할 때 사용된다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;invokevirtual&lt;/code&gt; : 상속 관계, 인스턴스의 메서드를 호출할 때 사용되며 동적 디스패치를 통한 런타임 해석을 담당한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;invokeinterface&lt;/code&gt; : 인터페이스 관계의 메서드를 호출할 때 사용된다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;invokestatic&lt;/code&gt; : 정적 메소드를 호출할 때 사용된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;자바 바이트코드에서 메서드를 호출하는 invokeinterface, invokespecial, invokestatic, invokevirtual의 4가지 opcode가 존재하며, 이렇게 메서드 호출 명령이 발생하는 위치를 &lt;strong&gt;call site&lt;/strong&gt; 라고 한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;invokedynamic&lt;/code&gt;은 &lt;strong&gt;이보다 훨씬 더 나아가 어떤 메서드가 실제로 호출될지 call site별로 결정할 수 있는 메커니즘을 제공한다.&lt;/strong&gt;&lt;br&gt;
해당 명령어가 실행되면 JVM은 해당 (실제로 호출하려는 메서드 핸들을 보유한) call site 객체를 찾는다. 만약 이 call site에 도달한 적이 없는 경우 객체를 생성한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;abstract public class CallSite {
    static { MethodHandleImpl.initStatics(); }

    // The actual payload of this call site:
    /*package-private*/
    MethodHandle target;    // Note: This field is known to the JVM.  Do not change.

    public abstract MethodHandle dynamicInvoker();
    ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/87f7a0772e657a29b2c55fe0e4c083c5/8078c/CallSite.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABHUlEQVR42p1R7WqDQBD0ORK98+JFEzUaT70mttV6fiRQaKHv/y7T9SgthQZCfwwLN7M7O7cO8zf4Cx4TOD9pmKnHMM4wZsA4TnhuO9zqWeDcHMg3iNMEaZZBlTV6M6LSJyRpBv6fgQtc5sPzBaJ9jGNRkomA6/H7NvxxFd9YtuyGBtfXC97ePyj6ABFIMC5+abkILOxAl3GsPYYVOa+5hOtvCRI8iGi7kEQSYneEzB7BBPEi/OIWXWj1K5cTmE3kRGmOvFAUqYKqT1CVtthGMZKixlGVyBXV8oH+UqMoK0jiDkpbTukGxVKrGrs4gZM1HVqKYnq64jRTrAnjMGGfHKDNhPaltW9mGG3te4MoyXCmy7dda3sWbr5cybDCJ2730Phsf0+UAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CallSite&quot;
        title=&quot;&quot;
        src=&quot;/static/87f7a0772e657a29b2c55fe0e4c083c5/1cfc2/CallSite.png&quot;
        srcset=&quot;/static/87f7a0772e657a29b2c55fe0e4c083c5/3684f/CallSite.png 225w,
/static/87f7a0772e657a29b2c55fe0e4c083c5/fc2a6/CallSite.png 450w,
/static/87f7a0772e657a29b2c55fe0e4c083c5/1cfc2/CallSite.png 900w,
/static/87f7a0772e657a29b2c55fe0e4c083c5/21482/CallSite.png 1350w,
/static/87f7a0772e657a29b2c55fe0e4c083c5/d61c2/CallSite.png 1800w,
/static/87f7a0772e657a29b2c55fe0e4c083c5/8078c/CallSite.png 2218w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;invokedynamic의 call site는 Java 힙에서 &lt;strong&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/CallSite.html&quot;&gt;CallSite&lt;/a&gt; 객체로&lt;/strong&gt; 표현된다.&lt;br&gt;
람다 표현식은 이 CallSite의 구현 클래스인 &lt;strong&gt;(처음 실행된 후에는 대상 메서드가 변경되지 않는) &lt;code class=&quot;language-text&quot;&gt;ConstantCallSite&lt;/code&gt;를 사용한다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;method-handles&quot; style=&quot;position:relative;&quot;&gt;Method Handles&lt;a href=&quot;#method-handles&quot; aria-label=&quot;method handles permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Java 7은 새로운 API인 &lt;code class=&quot;language-text&quot;&gt;java.lang.invoke&lt;/code&gt;를 도입했다.&lt;br&gt;
이에 포함되는 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;MethodHandle&lt;/code&gt;은 코드가 호출하고자 하는 메서드를 참조하는 클래스이다.&lt;/strong&gt;&lt;br&gt;
Reflection의 Method 객체와 유사하지만 더 효율적인 리플렉션 메커니즘이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 메서드를 찾고, 조정하고, 호출하기 위한 저수준 메커니즘이다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;MethodHandle&lt;/code&gt;을 사용하기 위해서는 4가지를 준비해야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Lookup 생성하기&lt;/strong&gt; : MethodHandle을 생성하기 위한 팩토리이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MethodType 생성하기&lt;/strong&gt; : 반환 타입과 매개변수 유형으로 구성되며, 불변이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MethodHandle 찾기&lt;/strong&gt; : 원본 클래스와 메서드 이름, MethodType을 Lookup 객체에 제공하여 조회할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MethodHandle 호출하기&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class Main {

    private final List&amp;lt;Integer&amp;gt; numbers;

    public Main(List&amp;lt;Integer&amp;gt; numbers) {
        this.numbers = numbers;
    }

    public Integer sum() {
        return numbers.stream().mapToInt(it -&amp;gt; it).sum();
    }

    public static void main(String[] args) throws Throwable {
        // 첫 번째 예제. Main의 sum 메서드 실행하기
        Method sumMethod = Main.class.getDeclaredMethod(&amp;quot;sum&amp;quot;);
        MethodHandle sum = lookup().unreflect(sumMethod);
        final Main main = new Main(Arrays.asList(1, 3, 5, 6, 7));
        Object invoke4 = sum.invoke(main);
        Object invoke5 = sum.invokeWithArguments(main);
        Integer invoke6 = (Integer) sum.invokeExact(main);
        
        // 두 번째 예제. String의 concat 메서드 실행하기
        Lookup publicLookup = lookup();
        MethodType methodType = MethodType.methodType(String.class, String.class);
        MethodHandle concat = publicLookup.findVirtual(String.class, &amp;quot;concat&amp;quot;, methodType);
        final String s1 = &amp;quot;who are &amp;quot;;
        final String s2 = &amp;quot;you?&amp;quot;;
        Object invoke1 = concat.invoke(s1, s2);
        Object invoke2 = concat.invokeWithArguments(s1, s2);
        String invoke3 = (String) concat.invokeExact(s1, s2);
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;bootstrapping&quot; style=&quot;position:relative;&quot;&gt;Bootstrapping&lt;a href=&quot;#bootstrapping&quot; aria-label=&quot;bootstrapping permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;바이트코드 명령에서 특정 invokedynamic call site가 처음 호출될 때, JVM에는 명령과 연관된 call site 객체가 없기 때문에 어떤 메서드를 대상으로 실행해야 하는지 알지 못한다.&lt;br&gt;
즉, &lt;strong&gt;이전에 보았던 invokestatic 및 invokespecial의 경우 컴파일 시점에 정확한 호출 대상을 알 수 있지만, invokedynamic은 호출 대상을 모르는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;invokedynamic은 &lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.23&quot;&gt;BootstrapMethods(BSM)&lt;/a&gt;라고하는 동적 특성 호출을 지원하는 추가 정보를 참조한다.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;특정 invokedynamic call site에 BSM을 연결할 수 있도록&lt;/strong&gt; Java 7부터 클래스 파일 형식에 InvokeDynamic라는 새로운 항목 유형이 추가되었다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class FirstClass {
    private final Function&amp;lt;String, String&amp;gt; toLowercase = String::toLowerCase;
    private final Predicate&amp;lt;Character&amp;gt; isUppercase = ch -&amp;gt; ch &amp;gt;= 65 &amp;amp;&amp;amp; ch &amp;lt;= 90;

    public static void main(String[] args) {
        FirstClass firstClass = new FirstClass();
        firstClass.toLowercase.apply(&amp;quot;TEST&amp;quot;);
        firstClass.isUppercase.test(&amp;#39;A&amp;#39;);
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;details&gt;
&lt;summary&gt;모든 바이트코드 펼치기&lt;/summary&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Constant pool:
   #1 = Methodref          #13.#40        // java/lang/Object.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
   #2 = InvokeDynamic      #0:#46         // #0:apply:()Ljava/util/function/Function;
   #3 = Fieldref           #6.#47         // org/example/FirstClass.toLowercase:Ljava/util/function/Function;
   #4 = InvokeDynamic      #1:#51         // #1:test:()Ljava/util/function/Predicate;
   #5 = Fieldref           #6.#52         // org/example/FirstClass.isUppercase:Ljava/util/function/Predicate;
   #6 = Class              #53            // org/example/FirstClass
   #7 = Methodref          #6.#40         // org/example/FirstClass.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
   #8 = String             #54            // TEST
   #9 = InterfaceMethodref #55.#56        // java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
  #10 = Methodref          #57.#58        // java/lang/Character.valueOf:(C)Ljava/lang/Character;
  #11 = InterfaceMethodref #59.#60        // java/util/function/Predicate.test:(Ljava/lang/Object;)Z
  #12 = Methodref          #57.#61        // java/lang/Character.charValue:()C
  #13 = Class              #62            // java/lang/Object
  #14 = Utf8               toLowercase
  #15 = Utf8               Ljava/util/function/Function;
  #16 = Utf8               Signature
  #17 = Utf8               Ljava/util/function/Function&amp;lt;Ljava/lang/String;Ljava/lang/String;&amp;gt;;
  #18 = Utf8               isUppercase
  #19 = Utf8               Ljava/util/function/Predicate;
  #20 = Utf8               Ljava/util/function/Predicate&amp;lt;Ljava/lang/Character;&amp;gt;;
  #21 = Utf8               &amp;lt;init&amp;gt;
  #22 = Utf8               ()V
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               LocalVariableTable
  #26 = Utf8               this
  #27 = Utf8               Lorg/example/FirstClass;
  #28 = Utf8               main
  #29 = Utf8               ([Ljava/lang/String;)V
  #30 = Utf8               args
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               firstClass
  #33 = Utf8               lambda$new$0
  #34 = Utf8               (Ljava/lang/Character;)Z
  #35 = Utf8               ch
  #36 = Utf8               Ljava/lang/Character;
  #37 = Utf8               StackMapTable
  #38 = Utf8               SourceFile
  #39 = Utf8               FirstClass.java
  #40 = NameAndType        #21:#22        // &amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
  #41 = Utf8               BootstrapMethods
  #42 = MethodHandle       #6:#63         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #43 = MethodType         #64            //  (Ljava/lang/Object;)Ljava/lang/Object;
  #44 = MethodHandle       #5:#65         // invokevirtual java/lang/String.toLowerCase:()Ljava/lang/String;
  #45 = MethodType         #66            //  (Ljava/lang/String;)Ljava/lang/String;
  #46 = NameAndType        #67:#68        // apply:()Ljava/util/function/Function;
  #47 = NameAndType        #14:#15        // toLowercase:Ljava/util/function/Function;
  #48 = MethodType         #69            //  (Ljava/lang/Object;)Z
  #49 = MethodHandle       #6:#70         // invokestatic org/example/FirstClass.lambda$new$0:(Ljava/lang/Character;)Z
  #50 = MethodType         #34            //  (Ljava/lang/Character;)Z
  #51 = NameAndType        #71:#72        // test:()Ljava/util/function/Predicate;
  #52 = NameAndType        #18:#19        // isUppercase:Ljava/util/function/Predicate;
  #53 = Utf8               org/example/FirstClass
  #54 = Utf8               TEST
  #55 = Class              #73            // java/util/function/Function
  #56 = NameAndType        #67:#64        // apply:(Ljava/lang/Object;)Ljava/lang/Object;
  #57 = Class              #74            // java/lang/Character
  #58 = NameAndType        #75:#76        // valueOf:(C)Ljava/lang/Character;
  #59 = Class              #77            // java/util/function/Predicate
  #60 = NameAndType        #71:#69        // test:(Ljava/lang/Object;)Z
  #61 = NameAndType        #78:#79        // charValue:()C
  #62 = Utf8               java/lang/Object
  #63 = Methodref          #80.#81        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #64 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
  #65 = Methodref          #82.#83        // java/lang/String.toLowerCase:()Ljava/lang/String;
  #66 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #67 = Utf8               apply
  #68 = Utf8               ()Ljava/util/function/Function;
  #69 = Utf8               (Ljava/lang/Object;)Z
  #70 = Methodref          #6.#84         // org/example/FirstClass.lambda$new$0:(Ljava/lang/Character;)Z
  #71 = Utf8               test
  #72 = Utf8               ()Ljava/util/function/Predicate;
  #73 = Utf8               java/util/function/Function
  #74 = Utf8               java/lang/Character
  #75 = Utf8               valueOf
  #76 = Utf8               (C)Ljava/lang/Character;
  #77 = Utf8               java/util/function/Predicate
  #78 = Utf8               charValue
  #79 = Utf8               ()C
  #80 = Class              #85            // java/lang/invoke/LambdaMetafactory
  #81 = NameAndType        #86:#90        // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #82 = Class              #91            // java/lang/String
  #83 = NameAndType        #92:#93        // toLowerCase:()Ljava/lang/String;
  #84 = NameAndType        #33:#34        // lambda$new$0:(Ljava/lang/Character;)Z
  #85 = Utf8               java/lang/invoke/LambdaMetafactory
  #86 = Utf8               metafactory
  #87 = Class              #95            // java/lang/invoke/MethodHandles$Lookup
  #88 = Utf8               Lookup
  #89 = Utf8               InnerClasses
  #90 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #91 = Utf8               java/lang/String
  #92 = Utf8               toLowerCase
  #93 = Utf8               ()Ljava/lang/String;
  #94 = Class              #96            // java/lang/invoke/MethodHandles
  #95 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #96 = Utf8               java/lang/invoke/MethodHandles
{
  private final java.util.function.Function&amp;lt;java.lang.String, java.lang.String&amp;gt; toLowercase;
    descriptor: Ljava/util/function/Function;
    flags: ACC_PRIVATE, ACC_FINAL
    Signature: #17                          // Ljava/util/function/Function&amp;lt;Ljava/lang/String;Ljava/lang/String;&amp;gt;;

  private final java.util.function.Predicate&amp;lt;java.lang.Character&amp;gt; isUppercase;
    descriptor: Ljava/util/function/Predicate;
    flags: ACC_PRIVATE, ACC_FINAL
    Signature: #20                          // Ljava/util/function/Predicate&amp;lt;Ljava/lang/Character;&amp;gt;;

  public org.example.FirstClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
         4: aload_0
         5: invokedynamic #2,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
        10: putfield      #3                  // Field toLowercase:Ljava/util/function/Function;
        13: aload_0
        14: invokedynamic #4,  0              // InvokeDynamic #1:test:()Ljava/util/function/Predicate;
        19: putfield      #5                  // Field isUppercase:Ljava/util/function/Predicate;
        22: return
      LineNumberTable:
        line 6: 0
        line 7: 4
        line 8: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lorg/example/FirstClass;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #6                  // class org/example/FirstClass
         3: dup
         4: invokespecial #7                  // Method &amp;quot;&amp;lt;init&amp;gt;&amp;quot;:()V
         7: astore_1
         8: aload_1
         9: getfield      #3                  // Field toLowercase:Ljava/util/function/Function;
        12: ldc           #8                  // String TEST
        14: invokeinterface #9,  2            // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
        19: pop
        20: aload_1
        21: getfield      #5                  // Field isUppercase:Ljava/util/function/Predicate;
        24: bipush        65
        26: invokestatic  #10                 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
        29: invokeinterface #11,  2           // InterfaceMethod java/util/function/Predicate.test:(Ljava/lang/Object;)Z
        34: pop
        35: return
      LineNumberTable:
        line 11: 0
        line 12: 8
        line 13: 20
        line 14: 35
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      36     0  args   [Ljava/lang/String;
            8      28     1 firstClass   Lorg/example/FirstClass;

  private static boolean lambda$new$0(java.lang.Character);
    descriptor: (Ljava/lang/Character;)Z
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #12                 // Method java/lang/Character.charValue:()C
         4: bipush        65
         6: if_icmplt     22
         9: aload_0
        10: invokevirtual #12                 // Method java/lang/Character.charValue:()C
        13: bipush        90
        15: if_icmpgt     22
        18: iconst_1
        19: goto          23
        22: iconst_0
        23: ireturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0    ch   Ljava/lang/Character;
      StackMapTable: number_of_entries = 2
        frame_type = 22 /* same */
        frame_type = 64 /* same_locals_1_stack_item */
          stack = [ int ]
}
SourceFile: &amp;quot;FirstClass.java&amp;quot;
InnerClasses:
     public static final #88= #87 of #94; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #42 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #43 (Ljava/lang/Object;)Ljava/lang/Object;
      #44 invokevirtual java/lang/String.toLowerCase:()Ljava/lang/String;
      #45 (Ljava/lang/String;)Ljava/lang/String;
  1: #42 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #48 (Ljava/lang/Object;)Z
      #49 invokestatic org/example/FirstClass.lambda$new$0:(Ljava/lang/Character;)Z
      #50 (Ljava/lang/Character;)Z&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/details&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7f529b09af10e982b601af1b16ecc927/e515d/bsm.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAADLklEQVR42n2T6XMaRxDF+f//iVS5knLFjpJYjg5LRpaQQVxCXMvCHrAXuyx7YHEIsUj80khOyh9S+fBqpnt6XvXreZPjOYPsm+D+v7Gds55PCRyN3eZ/6gS7zYzcdpWy8DQWY1Ng/ADzBUt/yHjQQm/cMLP7Ev9Qt9/7+xqDZWAxt2rklqsFncmETjilEyV041TWmG4guSBECSPKms5ZvU5T4q6cdaYRbcm3/eA7fFrBFNVShDANsdUGntbGLp8zuvyIU83jOD1sW8GyuhgjBdPs4jQKeO0SXqvIWKng+zpeYODvEVoMrSq5ldZlpho89C3S5h1O4Qr38gKv02HS6xH1RWYY8lyv4r39ma4gOj3CPHiP/SVP2qjzpKnsdJ2xeSsd2jpJtmS927IGZuEE5eqC0c01VumaqFlnrnZYfz5mWv5K8c8DWp+O0K7yTKol4tsKj2qbTEg9cz9DSyeW2SyChNTQiNp3RK0GqWsTDk28vkrguaxuqzzUbwgqJWp//Eb54B36xRluqcC3Zo2t1v+H0CByA+bjmLhwgXV8iC8d+gOV0JDuTZ1FErNuNgQ1dlLf+3TM5S9vyL/5Ce38lKd+l6zfwzNqr5JnD/dsnjIefY+5PaT/+YRRIc9YZKd30tle8ukhm0aVbKAw6zZltgqxEC0tk41nkY1dxlbjtUNzOsGKY1xVYWRbDETq0HVQDYN6q0lVutMKl4wkNmX4bkcckaY4cYKdJFhRhJXMMO0OuQdpVVd89F7IsCg2aRooX65p5wuohRKN03NKHz7SP8+jFStYiobd6sllH9NL6Ptb+sEWdQKGPRbbrJZo/iNaJImWQ9AbMjj9i9q7t3QOf6f1QV5VXtaWuZpnJ0SVIve9LmkaE0YzBpMntMmWQbgn9PaEi1fCUA6clXwzm6b4rPD+V+5OjhiWvmJXbsRGRayymFrke45NHE+ZvBBmQph9J3T3hA8SCNkUVIF7/0wyX+GGCd40IZwtmK02pIs16XL9ss7uV6wfM9nviXZyd8dgr1DcklstYnRTk+81FJiYI12+nIHjmq9wDIlfc/9CciPx71Cwv2OMDAzLZjio8DcasDko7rnOjgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;bsm&quot;
        title=&quot;&quot;
        src=&quot;/static/7f529b09af10e982b601af1b16ecc927/1cfc2/bsm.png&quot;
        srcset=&quot;/static/7f529b09af10e982b601af1b16ecc927/3684f/bsm.png 225w,
/static/7f529b09af10e982b601af1b16ecc927/fc2a6/bsm.png 450w,
/static/7f529b09af10e982b601af1b16ecc927/1cfc2/bsm.png 900w,
/static/7f529b09af10e982b601af1b16ecc927/21482/bsm.png 1350w,
/static/7f529b09af10e982b601af1b16ecc927/e515d/bsm.png 1430w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;즉, 상수 풀에 존재하는 &lt;code class=&quot;language-text&quot;&gt;#2&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;#4&lt;/code&gt;의 call site는 &lt;code class=&quot;language-text&quot;&gt;CONSTANT_InvokeDynamic&lt;/code&gt; 유형이며, 이 유형은 ClassFile 구조체의 &lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7&quot;&gt;BootstrapMethods 속성&lt;/a&gt;에 정의되어 있는 &lt;code class=&quot;language-text&quot;&gt;LambdaMetafactory.metafactory(...)&lt;/code&gt; 호출하며 이 BSM은 상수 풀의 &lt;code class=&quot;language-text&quot;&gt;#42&lt;/code&gt; 항목이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;
// 제공된 MethodHandle에 대한 위임을 통해 하나 이상의 인터페이스를 구현하는 간단한 &amp;quot;함수 개체&amp;quot; 생성을 용이하게 한다.
// 일반적으로 Java 프로그래밍 언어의 람다식 및 메서드 참조식 기능을 지원하기 위해 invokedynamic 호출 ​​사이트의 부트스트랩 메서드로 사용된다.
// 이 메서드에서 반환된 CallSite의 대상이 호출되면 결과 함수 객체는 invokedType의 반환 유형으로 명명된 인터페이스를 구현하고, 
// invokedName으로 지정된 이름과 samMethodType으로 지정된 서명으로 메서드를 선언하는 클래스의 인스턴스이다.
public class LambdaMetafactory {
    public static CallSite metafactory(
        MethodHandles.Lookup caller,
        String invokedName,
        MethodType invokedType,
        MethodType samMethodType,
        MethodHandle implMethod,
        MethodType instantiatedMethodType
    ) throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf = new InnerClassLambdaMetafactory(
            caller, 
            invokedType,
            invokedName, 
            samMethodType,
            implMethod, 
            instantiatedMethodType,
            false, 
            EMPTY_CLASS_ARRAY, 
            EMPTY_MT_ARRAY
        );
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }
    ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;BSM은 이 정적 메서드를 호출하여 CallSite 객체를 반환한다,&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;즉, &lt;code class=&quot;language-text&quot;&gt;invokedynamic&lt;/code&gt; 명령이 실행되어 반환된 CallSite는 람다의 대상 유형을 구현하는 클래스의 인스턴스를 포함하고 있는 MethodHandle을 포함하고 있는것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;람다와-클로저-특징&quot; style=&quot;position:relative;&quot;&gt;람다와 클로저 특징&lt;a href=&quot;#%EB%9E%8C%EB%8B%A4%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80-%ED%8A%B9%EC%A7%95&quot; aria-label=&quot;람다와 클로저 특징 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;람다가 둘러싸는 범위에서 매개변수를 캡쳐하지 않으면 람다 내부에서 사용하는 상태가 존재하지 않으므로 &lt;strong&gt;람다의 구현 클래스를 싱글톤으로 관리하여 최적화한다.&lt;/strong&gt;&lt;br&gt;
그리고 InvokeDynamic call site와 람다 구현 인스턴스가 한 번 연결된 후에는 &lt;code class=&quot;language-text&quot;&gt;LambdaMetaFactory.metafactory&lt;/code&gt; 메서드는 호출되지 않는다.&lt;br&gt;
하지만 동일한 형식의 람다 인스턴스라도 &lt;strong&gt;서로 다른 call site를 통해 생성된다면 BootstrapMethods가 InvokeDynamic call site별로 생성되기 때문에 독립적으로 구분된다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Function&amp;lt;Person, Integer&amp;gt; makeLambda() {
    return (person1) -&amp;gt; person1.age;
}

Function&amp;lt;Person, Integer&amp;gt; otherMakeLambda() {
    return (person1) -&amp;gt; person1.age;
}

@Test
void lambdaEqualTest() {
    Function&amp;lt;Person, Integer&amp;gt; lambda1 = makeLambda();
    Function&amp;lt;Person, Integer&amp;gt; lambda2 = makeLambda();

    assertThat(lambda1).isEqualTo(lambda2);

    Function&amp;lt;Person, Integer&amp;gt; lambda3 = makeLambda();
    Function&amp;lt;Person, Integer&amp;gt; lambda4 = otherMakeLambda();

    assertThat(lambda1).isEqualTo(lambda3);
    assertThat(lambda2).isEqualTo(lambda3);
    assertThat(lambda3).isNotEqualTo(lambda4);
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;하지만 &lt;strong&gt;람다가 외부 스코프에 존재하는 변수에 의존하게되면 람다는 싱글톤으로 관리되지 않는다.&lt;/strong&gt;&lt;br&gt;
(이 외부 스코프에 존재하는 변수에 의존하는 람다를 클로저라고 부른다.)&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Supplier&amp;lt;Integer&amp;gt; makeClosure(Person person) {
    return () -&amp;gt; person.age;
}

Supplier&amp;lt;Integer&amp;gt; makeAnonymous(Person person) {
    return new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            return person.age;
        }
    };
}

@Test
void closureEqualTest() {
    Person person = new Person();
    Supplier&amp;lt;Integer&amp;gt; lambda1 = makeClosure(person);
    Supplier&amp;lt;Integer&amp;gt; lambda2 = makeClosure(person);
    Supplier&amp;lt;Integer&amp;gt; anonymous1 = makeAnonymous(person);
    Supplier&amp;lt;Integer&amp;gt; anonymous2 = makeAnonymous(person);

//  assertThat(lambda1).isEqualTo(lambda2);    // fail !!!
    assertThat(lambda1).isNotEqualTo(lambda2);

//  assertThat(anonymous1).isEqualTo(anonymous2);    // fail !!!
    assertThat(anonymous1).isNotEqualTo(anonymous2);

    person.age = 1;
    assertThat(lambda1.get()).isEqualTo(1);
    assertThat(lambda2.get()).isEqualTo(1);
    assertThat(anonymous1.get()).isEqualTo(1);
    assertThat(anonymous2.get()).isEqualTo(1);
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 테스트 코드처럼 클로저가 싱글톤으로 관리되지 않는다고 해서 &lt;strong&gt;매번 새로운 CallSite가 생기는 것은 아니다.&lt;/strong&gt;&lt;br&gt;
익명 클래스는 외부 변수를 캡처해야한다면 생성자에 캡처링된 변수를 넘겨주며, 람다는 InvokeDynamic만 호출한다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// 익명 클래스
0: new           #3                  // class org/example/Main$1
3: dup
4: aload_0
5: invokespecial #4                  // Method org/example/Main$1.&amp;quot;&amp;lt;init&amp;gt;&amp;quot;:(Lorg/example/Person;)V
8: areturn

// 람다
0: aload_0
1: invokedynamic #2,  0              // InvokeDynamic #0:get:(Lorg/example/Person;)Ljava/util/function/Supplier;
6: areturn&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h1 id=&quot;callsite를-반환하는-bsm-만들어보기&quot; style=&quot;position:relative;&quot;&gt;CallSite를 반환하는 BSM 만들어보기&lt;a href=&quot;#callsite%EB%A5%BC-%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94-bsm-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;callsite를 반환하는 bsm 만들어보기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class Ops {
    public static Integer adder(Integer x, Integer y) {
        return x + y;
    }
    public static String adder(String x, String y) {
        return x + y;
    }
}

class MethodHandleTest {

    // 이 메서드는 호출된 invokedynamic call site를  adder 메서드에 연결하는 부트스트랩 메서드이다.
    public static CallSite mybsm(
        MethodHandles.Lookup callerClass, 
        String dynamicMethodName, 
        MethodType dynamicMethodType
    ) throws Throwable {
        // adder 메서드에 대한 정적 메서드 핸들을 생성한다.
        MethodHandle methodHandle = callerClass.findStatic(
            Ops.class,
            dynamicMethodName,
            dynamicMethodType
        );
        return new ConstantCallSite(methodHandle);
    }

    @Test
    void intAdder() throws Throwable {
        CallSite adder = mybsm(
            lookup(), 
            &amp;quot;adder&amp;quot;, 
            MethodType.methodType(Integer.class, Integer.class, Integer.class)
        );
        MethodHandle methodHandle = adder.dynamicInvoker();

        assertThat(methodHandle.invoke(1, 7)).isEqualTo(8);
    }

    @Test
    void stringAdder() throws Throwable {
        CallSite adder = mybsm(
            lookup(), 
            &amp;quot;adder&amp;quot;, 
            MethodType.methodType(String.class, String.class, String.class)
        );
        MethodHandle methodHandle = adder.dynamicInvoker();

        assertThat(methodHandle.invoke(&amp;quot;1&amp;quot;, &amp;quot;7&amp;quot;)).isEqualTo(&amp;quot;17&amp;quot;);
    }

    @Test
    void wrongAdder() {
        assertThatThrownBy(() -&amp;gt;
            mybsm(
                lookup(), 
                &amp;quot;adder&amp;quot;, 
                MethodType.methodType(Integer.class, String.class, String.class)
            )
        ).isExactlyInstanceOf(NoSuchMethodException.class);
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;java.lang.invoke.MethodHandles&lt;/code&gt; 및 &lt;code class=&quot;language-text&quot;&gt;java.lang.invoke.MethodHandle&lt;/code&gt; 클래스에는 기존 메서드 핸들을 기반으로 메서드 핸들을 생성하는 다양한 메서드가 포함되어 있다.&lt;br&gt;
런타임 시스템에서 사용할 수 있는 메서드가 여러 개 있고 각각 다른 인수 유형을 처리하는 경우 부트스트랩 메서드 mybsm은 &lt;strong&gt;dynamicMethodType 인수에 따라 메서드를 동적으로 선택할 수 있다.&lt;/strong&gt;&lt;br&gt;
invokedynamic 명령어는 컴파일러와 런타임 시스템의 동적 언어 구현을 단순화하며, 이는 Java 클래스 및 인터페이스에 특정한 연결 동작이 JVM에 의해 하드와이어링 되는 invokevirtual과 같은 다른 JVM 명령어와 대조된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;정리&quot; style=&quot;position:relative;&quot;&gt;정리&lt;a href=&quot;#%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;정리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;람다를 생성하기 위한 BSM은 &lt;code class=&quot;language-text&quot;&gt;LambdaMetafactory.metafactory()&lt;/code&gt; 표준화된 메서드이며, CallSite별로 넘기는 파라미터 인자들과 내부 정보가 달라질 뿐이다.&lt;br&gt;
상태를 포함하지 않는 람다는 컴파일러가 람다 표현식과 같은 시그니처를 갖는 메서드를 생성한다.&lt;br&gt;
만약 상태를 포함한다면 그 상태를 람다 표현식의 인수에 캡처한 각 변수를 추가하는 것이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;람다 덕분에 추가적인 필드나 정적 초기자 등의 오버헤드가 사라진다.&lt;/li&gt;
&lt;li&gt;상태 없는(캡처하지 않는) 람다에서 람다 객체 인스턴스를 만들고, 캐시하고, 같은 결과를 반환할 수 있다.
&lt;ul&gt;
&lt;li&gt;예를 들어, 정적 final 변수에 특정 Comparator 인스턴스를 선언할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;람다를 처음 실행할 때만 반환과 결과 연결 작업이 실행되므로 추가적인 성능 비용이 들지 않는다.
&lt;ul&gt;
&lt;li&gt;즉, 두 번째 호출부터는 이전 호출에서 연결된 구현을 바로 이용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;각 람다를 생성하는 CallSite는 ClassFile 정보에 BootStrapMethods 속성에 저장되어 있는 BSM을 실행하며, 이 BSM은 CallSite 객체를 반환하며 한 번 생성되면 다시 재생성되지 않는다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;참고&quot; style=&quot;position:relative;&quot;&gt;참고&lt;a href=&quot;#%EC%B0%B8%EA%B3%A0&quot; aria-label=&quot;참고 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/vm/multiple-language-support.html&quot;&gt;Java Virtual Machine Support for Non-Java Languages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://homoefficio.github.io/2019/01/31/Back-to-the-Essence-Java-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%EA%B9%8C%EC%A7%80-2/&quot;&gt;Back to the Essence - Java 컴파일에서 실행까지 - (2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.oracle.com/javamagazine/post/behind-the-scenes-how-do-lambda-expressions-really-work-in-java&quot;&gt;Behind the scenes: How do lambda expressions really work in Java?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.oracle.com/javamagazine/post/understanding-java-method-invocation-with-invokedynamic&quot;&gt;Understanding Java method invocation with invokedynamic&lt;/a&gt; : BootstrapMethods&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blogs.oracle.com/javamagazine/post/mastering-the-mechanics-of-java-method-invocation&quot;&gt;Mastering the mechanics of Java method invocation&lt;/a&gt; : vtable&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dzone.com/articles/dismantling-invokedynamic&quot;&gt;Dismantling invokedynamic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/77125987&quot;&gt;모던 자바 인 액션&lt;/a&gt; : 부록 D&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jcp.org/en/jsr/detail?id=292&quot;&gt;JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[보이스카웃 규칙 실천하기]]></title><description><![CDATA[The Boy Scout Rule : Always leave the campground cleaner than you found it…]]></description><link>https://jdalma.github.io/2024y/boyscoutRule/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/boyscoutRule/</guid><pubDate>Sat, 25 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;The Boy Scout Rule : Always leave the campground cleaner than you found it.&lt;br&gt;
&lt;strong&gt;보이 스카웃 규칙 : 언제나 처음 왔을 때보다 깨끗하게 해놓고 캠프장을 떠날 것.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;엉클 밥이 설명한 &lt;a href=&quot;https://www.oreilly.com/library/view/97-things-every/9780596809515/ch08.html&quot;&gt;보이스카웃 규칙&lt;/a&gt;을 실천한 경험을 정리해보려 한다.&lt;br&gt;
보이스카웃 규칙에 대한 내용은 종립님이 정리한 &lt;a href=&quot;https://johngrib.github.io/wiki/jargon/boy-scout-rule/&quot;&gt;글&lt;/a&gt;을 읽어보길 추천한다.&lt;/p&gt;
&lt;h1 id=&quot;실천하면서-느낀-점&quot; style=&quot;position:relative;&quot;&gt;실천하면서 느낀 점&lt;a href=&quot;#%EC%8B%A4%EC%B2%9C%ED%95%98%EB%A9%B4%EC%84%9C-%EB%8A%90%EB%82%80-%EC%A0%90&quot; aria-label=&quot;실천하면서 느낀 점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;NEXTSTEP의 코틀린 클린코드 교육을 듣고 클린코드 관련 서적 &quot;엘레강트 오브젝트&quot;, &quot;내 코드가 그렇게 이상한가요?&quot;, &quot;켄트 백의 구현 패턴&quot; 등을 읽게 되면서 깔끔한 코드를 작성하고 기존 코드를 청소하는 것에 관심을 가지게 되었다.&lt;br&gt;
업무로 할당된 것도 아니고 누가 시키지도 않았지만 코드를 정리하는 리팩토링을 진행하면서 배운 점도 많고 재미도 느꼈다.&lt;/p&gt;
&lt;h3&gt;작업 범위를 결정하고 작업 종류를 분류하기&lt;/h3&gt;  
&lt;ol&gt;
&lt;li&gt;작업 범위를 분석한다. 이때는 얕게 분석한다.&lt;/li&gt;
&lt;li&gt;그 범위에 해당하는 테스트를 작성한다.&lt;/li&gt;
&lt;li&gt;현재 테스트 범위에서 필요로 하는 의존성을 파악하여 필요한 의존성만 주입한다. 가능하다면 단위테스트로 해결한다.&lt;/li&gt;
&lt;li&gt;테스트를 진행하면서 코드를 제대로 분석한다.&lt;/li&gt;
&lt;li&gt;리팩토링 작업을 분류한다. 코드 정리인지? 동작을 변경해야 하는지? 둘 다 인지?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 작업을 하면서 불필요한 연산이나 논리적으로 잘못된 로직을 발견하는 경우가 많았다.&lt;br&gt;
만약 동작을 변경해야 한다면 변경하기 전에 먼저 팀내에 공유하고 허락을 받았으며, 설득하기 위해 프론트나 데이터팀에게 협조를 요청하여 근거를 마련해야 할 필요도 있었다.&lt;/p&gt;
&lt;h3&gt;리팩토링 실천과 결과 공유의 순영향&lt;/h3&gt;
&lt;p&gt;이 작업을 할 때 재직한 회사는 테스트 코드가 없었고 코드리뷰, 컨벤션도 존재하지 않았다.&lt;br&gt;
그렇기에 리팩토링 작업이 끝나면 항상 공유하는 자리를 만들어 &lt;code class=&quot;language-text&quot;&gt;&quot;작업을 하계된 계기&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;변경된 내용&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;변경 후 효과&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;검증 방법&quot;&lt;/code&gt; 들을 공유했다.&lt;/p&gt;
&lt;p&gt;리팩토링을 진행하면서 분석하고 설계한 자료를 다른 팀원들이 더 잘 이해하고 공감할 수 있도록 자료를 다듬고 질문에 대비하기 위한 조사들이 스스로에게 도움이 많이 되었다.&lt;br&gt;
그리고 리팩토링에 대한 피드백을 받고 서로의 생각을 공유하는 자리를 통해 클린 코드에 대한 지식들이 팀원간 &lt;code class=&quot;language-text&quot;&gt;싱크&lt;/code&gt;되고 있다는 것을 느꼈다.&lt;br&gt;
이 느낌 덕분에 리팩토링을 더 능동적으로 진행한 것 같다.&lt;/p&gt;
&lt;h3&gt;개인의 기준&lt;/h3&gt;
&lt;p&gt;리팩토링을 진행하면서 내가 생각하는 클린 코드에 대한 기준이 생기고 그 기준에 대한 근거를 스스로 확립해나갈 수 있었다.&lt;br&gt;
내가 접해있던 환경은 책이나 강의에서 이렇게 작성하지마라 라고 하는 내용들을 쉽게 접할 수 있는 환경이였다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;요청과 응답의 생명주기가 동일한 괴물 엔티티&lt;/li&gt;
&lt;li&gt;스프링 빈을 몇 십개 주입받는 갓 클래스&lt;/li&gt;
&lt;li&gt;가변적이며 공개되어 있는 필드 몇 십개를 포함하는 엔티티&lt;/li&gt;
&lt;li&gt;전달받은 파라미터의 속을 모두 꺼내어 헤집으면서 반환 값은 void인 메서드&lt;/li&gt;
&lt;li&gt;반환 값이 Map, Any, Object인 메서드&lt;/li&gt;
&lt;li&gt;getter와 setter에 비즈니스 로직이 녹아있어 직렬화/역직렬화에 발생하는 사이드 이펙트&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;안좋은 예제들이라고 배웠던 내용들을 직접 느껴보면서 왜 안좋은 예제인지 깊게 이해할 수 있었다.&lt;br&gt;
아래는 리팩토링을 작업하면서 생각을 메모한 내용들이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getter/setter에 비즈니스 로직이 들어가서 어떤 용도로 호출되는지 알 수가 없다. 직렬화/역직렬화할 때도 호출되어서 메서드를 리팩토링 하기 힘들다.&lt;/li&gt;
&lt;li&gt;한 가지의 용도로만 사용하거나 직렬화/역직렬화 규칙에 포함되지 않도록 메서드를 작성하는게 좋을 것 같다.&lt;/li&gt;
&lt;li&gt;어떤 setter가 명시적으로 한 곳에서 사용중이지만 해당 객체로 역직렬화하는 곳도 해당 setter가 호출되기 때문에 의도된 행위인지 파악하기 힘들다.&lt;/li&gt;
&lt;li&gt;getter에 필드를 갱신하는 로직이 들어가면 어떤 의미로 사용되는지 알기 힘들다. 최악은 해당 getter의 반환값을 사용하지 않으면서 getter를 호출하는 코드들이다.&lt;/li&gt;
&lt;li&gt;요청부터 응답까지 동일한 생명주기를 가진 객체가 있는데, 전반적인 연산 자체가 해당 객체의 속성들을 수정하는 기조이다. 그래서 어떤 메서드 호출 순서를 바꾸기 위해서는 서로 의존하고 있는 필드가 있는지 확인해야 하는 너무 큰 문제가 발생한다.&lt;/li&gt;
&lt;li&gt;레거시인 절차지향 코드들을 보면 가능한 DB 조회와 연산을 분리하려 한 것 같은 느낌이 있다. 대부분 이해가 안가는 코드들은 읽는 사람의 도메인 지식이 작성자와 싱크가 되지 않기 때문인 것 같다.&lt;/li&gt;
&lt;li&gt;반환 타입이 &lt;code class=&quot;language-text&quot;&gt;Map&lt;/code&gt;으로 작성된 경우가 많다. 유연하기 때문에 기존 작업자들이 많이 선택한 것 같은데, 이 정도의 유연함은 제어가 불가능하다. 이런 경우는 그 반환 타입만을 위한 클래스를 정의하였다.&lt;/li&gt;
&lt;li&gt;불변 객체를 참조하는 가변 필드와 가변 객체를 참조하는 불변 필드를 구분하여 객체의 변경 범위를 어디까지 정해야할지 고민해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;어떤 근거로 리팩토링을 진행했는지, 어떤 효과를 얻었는지 알아보자.&lt;/p&gt;
&lt;h1 id=&quot;라이브러리에-대한-의존성-제한하기&quot; style=&quot;position:relative;&quot;&gt;라이브러리에 대한 의존성 제한하기&lt;a href=&quot;#%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A0%9C%ED%95%9C%ED%95%98%EA%B8%B0&quot; aria-label=&quot;라이브러리에 대한 의존성 제한하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1ca94fb4d7ee01e7ade74f46d99e3ede/f02b9/before1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.999999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABLklEQVR42j1R7ZKCMBDj/Z/MP6DgjINCW4SRAsenCAgKue163s4sbFOaJsHCXxVFAdd14XkefN9HGIYYhoH31nVF3/e43++QUkIIwbPB3u83tm3jNmWZxzRNaJqGgWVZoHWGPM/5cNd1yLIMSZIwXpYV2rZlLI4/mKnn88m4ZZjHcYTjODidTqiqCkHg43h0EUWS11EUEVGBItf8llJgv7dxODg4n8/8jVKKZ8soSdMUu90Otm3TjTmiawd1rXG55GTNKKwhZIU46XCNG1IyQkUVQlHS5Qn1hSMyLv8VGmtseV6RFit+ekDFEzKtiahB3W2YXkBZv0jAgKIFdL0hEBXluODxGD6EhmSeZ1b57TC8kbqUrJp8Ngr/ASU1KbmRtZRzM3MQaNT1R8j3p/wCxuLBf5ySqGIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;before1&quot;
        title=&quot;&quot;
        src=&quot;/static/1ca94fb4d7ee01e7ade74f46d99e3ede/1cfc2/before1.png&quot;
        srcset=&quot;/static/1ca94fb4d7ee01e7ade74f46d99e3ede/3684f/before1.png 225w,
/static/1ca94fb4d7ee01e7ade74f46d99e3ede/fc2a6/before1.png 450w,
/static/1ca94fb4d7ee01e7ade74f46d99e3ede/1cfc2/before1.png 900w,
/static/1ca94fb4d7ee01e7ade74f46d99e3ede/21482/before1.png 1350w,
/static/1ca94fb4d7ee01e7ade74f46d99e3ede/f02b9/before1.png 1554w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;검색 서비스에 의존하는 서비스들의 클라이언트 코드들이 위의 그림과 같이 응답으로 받는 ElasticSearch의 &lt;code class=&quot;language-text&quot;&gt;SearchResponse&lt;/code&gt;에 직접 의존하는 것을 확인했다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;val searchResponse = searchService.search(...)
if (searchResponse == null || 
    searchResponse.failedShards &amp;gt; 0 || 
    searchResponse.hits.totalHits == 0) {
   // 검색 실패 로직
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 클라이언트 코드에서 &lt;code class=&quot;language-text&quot;&gt;failedShards&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;hits.totalHits&lt;/code&gt; 필드들에 직접 의존하고 있어 엘라스틱서치의 클라이언트 의존성 버전 변경에 굉장히 민감해질 수 밖에 없다.&lt;br&gt;
그리고 검색에 실패한 경우와 실패했을 때의 처리를 클라이언트 코드에서 직접 구분하고 있었기에 클라이언트는 &lt;code class=&quot;language-text&quot;&gt;SearchResponse&lt;/code&gt;에 너무 많은 정보를 알고 있었고 비슷한 코드가 여러 곳에서 반복되고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c10543874b2b82b1afd258676a907a3c/f349c/usingSearchResponse.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.444444444444443%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6klEQVR42pWO226DQAxE+REKyy7ZCxTKbUkEDQ0JEKjUVsr/f8vU0EjNQ1/6cOQZ2xrbMVJCaQOpo41QaigTIxA7uB7Dkx/8C0dSoJDqt8n4Dz7bcKnn3mfrgb+OPO44gj4K6UMVxzBJAqE1fAr0mYAXiLvm8MgzvgMLQtL8AYGAglZW7WilEZsIaV6grA8YpgUjcX3/wrR8YhwXzOSn+QOny4zm2KPat8QrMtviWFjcigK3qsRsazicCwrU0NEzjErQnc+4DCP604C2v+KtH3FoOmRFjZe8QppVVO1GSj7PSjRZjq6y2JcW3+fGlsbxStoXAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;usingSearchResponse&quot;
        title=&quot;&quot;
        src=&quot;/static/c10543874b2b82b1afd258676a907a3c/1cfc2/usingSearchResponse.png&quot;
        srcset=&quot;/static/c10543874b2b82b1afd258676a907a3c/3684f/usingSearchResponse.png 225w,
/static/c10543874b2b82b1afd258676a907a3c/fc2a6/usingSearchResponse.png 450w,
/static/c10543874b2b82b1afd258676a907a3c/1cfc2/usingSearchResponse.png 900w,
/static/c10543874b2b82b1afd258676a907a3c/21482/usingSearchResponse.png 1350w,
/static/c10543874b2b82b1afd258676a907a3c/d61c2/usingSearchResponse.png 1800w,
/static/c10543874b2b82b1afd258676a907a3c/f349c/usingSearchResponse.png 2002w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;클라이언트가 원하는 검색에 실패하였을 떄의 처리는 3가지 였다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;빈 검색 결과 객체를 반환하거나&lt;/li&gt;
&lt;li&gt;null을 반환하거나&lt;/li&gt;
&lt;li&gt;예외를 던지거나&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 검색 클라이언트 의존성을 격리시키면서 클라이언트가 직접 결과에 따라 원하는 행위를 직접 주입시킬 수 있는 방법은 없을까?&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;sealed class ResponseResult&amp;lt;out T&amp;gt; {

    data class Success&amp;lt;out T&amp;gt;(val body: T): ResponseResult&amp;lt;T&amp;gt;()
    data class Failure(val errorResponse: ErrorResponse): ResponseResult&amp;lt;Nothing&amp;gt;()

    val isSuccess: Boolean
        get() = this is Success
    val isFailure: Boolean
        get() = this is Failure

    inline fun onSuccess(action: (T) -&amp;gt; Unit): ResponseResult&amp;lt;T&amp;gt; {
        if (this is Success) {
            action(body)
        }
        return this
    }

    inline fun onFailure(action: (ErrorResponse) -&amp;gt; Unit): ResponseResult&amp;lt;T&amp;gt; {
        if (this is Failure) {
            action(errorResponse)
        }
        return this
    }

    fun getOrNull(): T? = if (this is Success) body else null

    inline fun getOrThrow(exception: () -&amp;gt; Nothing): T {
        return when (this) {
            is Success -&amp;gt; body
            is Failure -&amp;gt; exception()
        }
    }

    inline fun &amp;lt;R&amp;gt; getOrDefault(default: R): R {
        return when (this) {
            is Success -&amp;gt; body
            is Failure -&amp;gt; default
        }
    }
}

data class ErrorResponse(
    val message: String,
    val code: CustomError,
    val status: HttpStatus
)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/make-http-client-design-flexible/#%EA%B0%81%EA%B8%B0-%EB%8B%A4%EB%A5%B8-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD%EC%97%90-%EB%A7%9E%EA%B2%8C-%EC%9C%A0%EC%97%B0%ED%95%9C-%ED%95%B8%EB%93%A4%EB%A7%81-%EC%A7%80%EC%9B%90&quot;&gt;MSA 환경에서의 유연한 HTTP 클라이언트 설계 전략&lt;/a&gt;을 참고하여 이 문제를 해결할 수 있었다.&lt;br&gt;
간략하게 설명하자면 &lt;code class=&quot;language-text&quot;&gt;ResponseResult&lt;/code&gt; 추상 클래스를 상속하는 &lt;code class=&quot;language-text&quot;&gt;Success&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Failure&lt;/code&gt; 클래스를 통해 HTTP 통신 결과에 따라 클라이언트 코드가 원하는 행위를 지정할 수 있도록 도와주는 클래스이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/287d55030171a22736ff2ee7fb54c446/f02b9/refactor1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.999999999999996%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABM0lEQVR42j2R63KCMBCFef8n8w8odGpFSKAgCTDcEVBUTjdr251JsjmQk283Fn6jLEu4rgvP83A6nRCGIaZp4m+v1wvjOGIYBkgpIYTg3GjP5xPbtvEwYZlpWRa0bcvCuq7I8xxFUfDhvu95n6YptM5RVTW6rmMtSd6aidvtxrplnOd5huM4OB6PqOsa/vkMj2ijOOJ9HMdkVKIsNK9SCuz3Ng4HB77v8z9RFHFuGRKlFHa7HWzbRqHp0OcXtOMi8z4wDj103kDIGkna4ztpiWSmy2qEokIQpDTO3CJT5T+hKY37RehTVlF/gFZcUKoLGXVo+g3LA6iaBwFMKDtANxsCUVMfV1yv09vQmNzvd6ZURKeyDBmVrOhh8kjyJeN4RSQ1kWRUmuK+mTwINJrmDfL3KD91/cET6pxlEgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;refactor1&quot;
        title=&quot;&quot;
        src=&quot;/static/287d55030171a22736ff2ee7fb54c446/1cfc2/refactor1.png&quot;
        srcset=&quot;/static/287d55030171a22736ff2ee7fb54c446/3684f/refactor1.png 225w,
/static/287d55030171a22736ff2ee7fb54c446/fc2a6/refactor1.png 450w,
/static/287d55030171a22736ff2ee7fb54c446/1cfc2/refactor1.png 900w,
/static/287d55030171a22736ff2ee7fb54c446/21482/refactor1.png 1350w,
/static/287d55030171a22736ff2ee7fb54c446/f02b9/refactor1.png 1554w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;사실 검색 서비스에서 검색 결과를 &lt;code class=&quot;language-text&quot;&gt;ByteArrayInputStream&lt;/code&gt;으로만 응답해줄 것이 아니라 &lt;strong&gt;&quot;검색 서비스를 필요로 하는 서비스&quot;들에게 &lt;code class=&quot;language-text&quot;&gt;SearchResponse&lt;/code&gt; 타입을 전혀 몰라도 검색 결과를 알 수 있도록 &lt;code class=&quot;language-text&quot;&gt;규격화된 응답 객체&lt;/code&gt;를 전송해야 마땅하다.&lt;/strong&gt;&lt;br&gt;
하지만 검색 서비스 API 응답을 수정하는 작업은 해당 API를 사용하는 클라이언트에 대한 확인이 필요하여 작업 범위가 크다고 판단하였으며, 이번에 &lt;code class=&quot;language-text&quot;&gt;ResponseResult&lt;/code&gt;를 사용하여 의존성을 격리시킨다면 추후에 &lt;code class=&quot;language-text&quot;&gt;SearchResponse&lt;/code&gt; 의존성을 제거하는 것은 쉬울 것이므로 수정 범위를 이 정도로만 정했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;어떻게 적용하였는지 예제 코드를 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// Unirest 응답 타입 전용 확장함수
fun HttpResponse&amp;lt;SearchResponse&amp;gt;.responseResult(): ResponseResult&amp;lt;Document&amp;gt; {
    return when (
        this.isSuccess &amp;amp;&amp;amp; 
        this.body != null &amp;amp;&amp;amp; 
        this.body.failedShards == 0 &amp;amp;&amp;amp; 
        this.body.hits.hits.isNotEmpty()
    ) {
        true -&amp;gt; {
            val body = defaultMapper.readValue(hit.sourceAsString, Document::class.java)
            ResponseResult.Success(body)
        }
        false -&amp;gt; ResponseResult.Failure(
            ErrorResponse(
                &amp;quot;외부 통신 실패&amp;quot;, 
                CommonError.ERROR, 
                HttpStatus.INTERNAL_SERVER_ERROR
            )
        )
    }
}

// UnirestInstance 요청 결과인 응답을 (확장함수를 사용하여) ResponseResult로 래핑
fun searchResponseResult(...): ResponseResult&amp;lt;Document&amp;gt; {
   return unirestInstance.post(url)
      .cookie(cookie)
      .routeParam(&amp;quot;cluster&amp;quot;, cluster)
      .body(body)
      .asObject&amp;lt;SearchResponse&amp;gt; { raw: RawResponse -&amp;gt;
         // 검색 서비스에서 응답받은 Stream을 SearchResponse로 변환
      }
      .responseResult()
}

// ResponseResult를 반환받아 클라이언트 코드에서 행위를 지정할 수 있다.
// 1. 검색에 실패하였다면 null을 반환한다.
val document = searchService.searchResponseResult(...).getOrNull()

// 2. 검색에 실패하였다면 지정한 예외를 던진다.
val document = searchService.searchResponseResult(...).getOrThrow {
   throw DocumentNotFoundException(...)
}

// 3. 검색에 실패하였다면 지정한 기본값을 반환한다.
val document = searchService.searchResponseResult(...).getOrDefault(::EmtyDocument)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;이제부터는 격리시키고 싶은 의존성이 있다면 &lt;code class=&quot;language-text&quot;&gt;ResponseResult&lt;/code&gt;로 래핑하는 확장함수를 추가하면 된다.&lt;br&gt;
그리고 &lt;strong&gt;클라이언트가 통신 결과에 따라 유연하게 람다를 지정하여 호출할 수 있어 클라이언트 코드가 불필요한 의존성을 알아야 할 필요가 없어졌다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;공통-기능을-모아놓은-빈-제거하기&quot; style=&quot;position:relative;&quot;&gt;공통 기능을 모아놓은 빈 제거하기&lt;a href=&quot;#%EA%B3%B5%ED%86%B5-%EA%B8%B0%EB%8A%A5%EC%9D%84-%EB%AA%A8%EC%95%84%EB%86%93%EC%9D%80-%EB%B9%88-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0&quot; aria-label=&quot;공통 기능을 모아놓은 빈 제거하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;폴더 구조를 가지는 기능을 가진 서비스가 있다.&lt;br&gt;
어떤 정보들을 프로젝트안에 존재하는 폴더에 저장하는 기능이다. 즉, &lt;strong&gt;한 개의 프로젝트에 N개의 폴더를 생성할 수 있으며 각 폴더에 N개의 정보를 저장하는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;한 프로젝트의 모든 폴더를 조회하는 API&lt;/li&gt;
&lt;li&gt;한 프로젝트 안에 폴더를 생성하고 해당 폴더를 조회하는 API&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 두 개의 웹 API가 아래와 같은 의존도를 가지고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/799bda9f2dea5e27d16211b78f3071e5/b962a/commonBeanDeleteBefore.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABfklEQVR42m2S63LaMBCF8xIdQkBYtuQLtsCA7zaU5nem0/d/mtOzik0zKT/O7I5W+2n32C87HeG7VKCR5iXqtqU6tP2AhjHLHXRkEdkEh+qCqml53qPrR+TuiOl2x8tzYAibFnBlifJcETiiqluYOEWgjYemzsEdS5wvNU5UnO4J/PkcuEA3KoBiHhKgQwO10wRGXlvmUl/u/litMV4JlPU2iuIFf2mOCzg0MUySIbAxdgRvebZmXc11WV8mfn3bYhKgzQrsC0cd5uiQ7nMPEkmDYawYR1dgOhwwZhmyiBNzajMDVwvweGnRDSP6YfKS/EK/LKcSyXQxm9/54Eff4fcw4E9d4WQslFhBBQQ/Jly8+C7xRSQN8jFCQiPmltPq2QrRf0DxQQ4eEm8YF7OlQXIdcX0Bc0U113df6qv15h/wuTiRTeln4SXefn1Yc+UoSZDtnf9l1hv1+ZWfw0I2WZybnq/ecOX/VTedn2ZZM8kLNNPoa/f7L54ZDNMNfwGFQiGWksqujAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;commonBeanDeleteBefore&quot;
        title=&quot;&quot;
        src=&quot;/static/799bda9f2dea5e27d16211b78f3071e5/1cfc2/commonBeanDeleteBefore.png&quot;
        srcset=&quot;/static/799bda9f2dea5e27d16211b78f3071e5/3684f/commonBeanDeleteBefore.png 225w,
/static/799bda9f2dea5e27d16211b78f3071e5/fc2a6/commonBeanDeleteBefore.png 450w,
/static/799bda9f2dea5e27d16211b78f3071e5/1cfc2/commonBeanDeleteBefore.png 900w,
/static/799bda9f2dea5e27d16211b78f3071e5/21482/commonBeanDeleteBefore.png 1350w,
/static/799bda9f2dea5e27d16211b78f3071e5/d61c2/commonBeanDeleteBefore.png 1800w,
/static/799bda9f2dea5e27d16211b78f3071e5/b962a/commonBeanDeleteBefore.png 2465w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위의 다이어그램을 보면 의아한 점이 생긴다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ProjectFolderService와 FolderService, ProjectFolderMapper와 FolderMapper의 차이는 뭘까?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;ProjectFolder와 Folder를 분리할 필요가 없고 동일한 관심사를 가지고 있다고 판단되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CommonUtils는 어떤 공통 기능을 가졌을까?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;폴더의 재귀 구조를 생성해주는 기능이 구현되어 있었다.&lt;/li&gt;
&lt;li&gt;서로 같은 관심사를 가진 두 개의 Service가 존재하게 되면서 중복되는 코드를 CommonUtils라는 스프링 빈으로 몰아넣어져 있었다.&lt;/li&gt;
&lt;li&gt;이 공통 기능을 가진 빈은 다시 Mapper를 필요로 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;공통 기능을 가진 스프링 빈은 의존성을 분리하는 이점이 있긴 하지만 OOP에 전혀 도움이 되지 않으며, 깨진 창문처럼 더 더러워지기 쉬운 이름을 가져서 어정쩡한 책임을 쉽게 추가하기 좋은 클래스이다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;CommonUtils&lt;/code&gt; 라는 이름은 죄책감을 가지지 않고 기능을 추가하기 딱 좋은 이름이지 않나?&lt;/p&gt;
&lt;p&gt;결론은 &lt;strong&gt;한 개의 관심사이지만 분리된 책임을 한 곳으로 모으고, 공통 기능을 수행하는 객체를 생성하기로 했다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6496fa019901870a4a6811825992d459/36563/commonBeanDeleteAfter.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABDElEQVR42p2Py26DMBRE+YiqQg3mjWMIBltJSDDYhEZNH1I33fT/v2TquO0irZRFFiNL947PzPXCOMVVJTkKVoGyEstyhYKyf54oyeA/BBj0BO8ajEQJkoxhPx4wzzNOp2coNTjATcBf+STEIkqdfBKB3NzQKrUt+4JiEgKzlNhmGfIovoBeAIMwxsKm/tV5TqwxDwjeucDHqPCpNY5FAWYbB3ZHfv6evXf3PtSo4dGyBrfpvBVohERzfluJquYuOc2X2G879OsNpJ0J3tgZdTvGufO3cg1WVng8PsFbic6SDUb9rcGmaDOh2/XI8gK0arFRxqUP5mDPMiir2kGF2kHpEWaa0asRL69v+ALJCc16FjnAIgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;commonBeanDeleteAfter&quot;
        title=&quot;&quot;
        src=&quot;/static/6496fa019901870a4a6811825992d459/1cfc2/commonBeanDeleteAfter.png&quot;
        srcset=&quot;/static/6496fa019901870a4a6811825992d459/3684f/commonBeanDeleteAfter.png 225w,
/static/6496fa019901870a4a6811825992d459/fc2a6/commonBeanDeleteAfter.png 450w,
/static/6496fa019901870a4a6811825992d459/1cfc2/commonBeanDeleteAfter.png 900w,
/static/6496fa019901870a4a6811825992d459/21482/commonBeanDeleteAfter.png 1350w,
/static/6496fa019901870a4a6811825992d459/d61c2/commonBeanDeleteAfter.png 1800w,
/static/6496fa019901870a4a6811825992d459/36563/commonBeanDeleteAfter.png 2023w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;CommonUtils가 수행하던 기능을 (여러 FolderEntity를 대표하는) &lt;code class=&quot;language-text&quot;&gt;Folders&lt;/code&gt;라는 객체가 책임을 가지도록 수정하였다.&lt;br&gt;
즉, 영속성 계층에서 조회한 결과로 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;폴더들&lt;/code&gt;을 조작하는 책임&lt;/strong&gt;을 가진 객체를 추가하여 CommonUtils를 대신하였다.&lt;/p&gt;
&lt;h1 id=&quot;절차지향-메소드를-객체함수로-분리하여-책임-분리하기&quot; style=&quot;position:relative;&quot;&gt;절차지향 메소드를 객체,함수로 분리하여 책임 분리하기&lt;a href=&quot;#%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5-%EB%A9%94%EC%86%8C%EB%93%9C%EB%A5%BC-%EA%B0%9D%EC%B2%B4%ED%95%A8%EC%88%98%EB%A1%9C-%EB%B6%84%EB%A6%AC%ED%95%98%EC%97%AC-%EC%B1%85%EC%9E%84-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0&quot; aria-label=&quot;절차지향 메소드를 객체함수로 분리하여 책임 분리하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;사용자 회원가입의 검증 로직에 탈퇴한 ID와 동일하다면 회원가입이 되지 않도록 조건을 추가해야 하는 간단한 작업을 해야했다.&lt;br&gt;
기존에 작성되어 있던 회원가입 검증 로직을 보고 그냥 조건문 추가하면 해결되었겠지만.. 냄새나는 코드 작성에 일조하기가 싫어서 코드 정리를 결심했다.&lt;br&gt;
먼저 문제점을 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Service
class UserRegisterService(
    private val userRegisterMapper: UserRegisterMapper,
    ...
) {

   fun validationCheck(userDataList: List&amp;lt;UserDataVO&amp;gt;): Map&amp;lt;String, Any&amp;gt; {
      val userIdList = userDataList.map { it.userId }
      var errorReason = &amp;quot;&amp;quot;;
      val validationError = mutableMapOf&amp;lt;String, MutableSet&amp;lt;String&amp;gt;&amp;gt;()

      val userExistList = userRegisterMapper.selectExistId(userIdList).toMutableSet()
      if (userExistList.isNotEmpty()) {
         // 동일한 아이디가 이미 존재하면 errorReason에 에러 메세지를 누적하고, 동일한 아이디들을 validationError에 누적한다.
      }

      val companyIdExist = userRegisterMapper.selectExistCompanyId(userDataList[0].companyId)
      if (companyIdExist.isNullOrEmpty()) {
         // 회사 코드가 존재하지 않으면 errorReason에 에러 메세지를 누적하고, 회사 ID를 validationError에 누적한다.
      }

      val validationErrorValue = mutableSetOf&amp;lt;String&amp;gt;()
      userDataList.forEach {
         for (validation in RegisterValidation.values()) {
               // enum으로 정의된 회원가입시 기입해야할 필드들을 리플렉션을 통해 객체의 필드를 순회하여 해당하는 정보를 조회한다.
               val property =
                  it::class.members.first { value -&amp;gt; value.name == validation.key } as KProperty1&amp;lt;UserDataVO, Any&amp;gt;

               val value = property.get(it) as String?
               if (!expCheck(value, validation)) {
                  // 조회된 정보를 정규표현식을 통하여 검증한다.
                  // 정규표현식을 통과하지 못하면 validationErrorValue와 validationError에 정보를 누적한다.
               }
         }
      }
      errorReason += &amp;quot;생성 규칙에 안맞는 데이터 입니다 : $validationErrorValue&amp;quot;
      return mapOf(&amp;quot;error&amp;quot; to validationError, &amp;quot;reason&amp;quot; to errorReason)
   }
   ...
}

enum class RegisterValidation(val key: String, val required: Boolean, val regex: String) {
    USERID(&amp;quot;userId&amp;quot;, true, &amp;quot;...&amp;quot;),
    PASSWORD(&amp;quot;password&amp;quot;, true, &amp;quot;...&amp;quot;),
    EMAIL(&amp;quot;email&amp;quot;, true, &amp;quot;...&amp;quot;),
    MOBILE(&amp;quot;mobile&amp;quot;, true, &amp;quot;...&amp;quot;),
    LEVEL(&amp;quot;level&amp;quot;, true, &amp;quot;...&amp;quot;),
    TYPE(&amp;quot;type&amp;quot;, true, &amp;quot;...&amp;quot;),
    STATUS(&amp;quot;status&amp;quot;, true, &amp;quot;...&amp;quot;),
    COMPANYMOBILE(&amp;quot;companyMobile&amp;quot;, false, &amp;quot;...&amp;quot;)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ol&gt;
&lt;li&gt;각 검증마다 검증 결과를 누적하고 공유되고 있는 &lt;code class=&quot;language-text&quot;&gt;errorReason&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;validationError&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;validationErrorValue&lt;/code&gt; 필드&lt;/li&gt;
&lt;li&gt;사용자 회원가입 정보가 담겨있는 &lt;code class=&quot;language-text&quot;&gt;UserDataVO&lt;/code&gt; 객체의 정의된 필드를 검증 마다 매번 순회&lt;/li&gt;
&lt;li&gt;사용자 필드별 정규표현식을 가지는 &lt;code class=&quot;language-text&quot;&gt;RegisterValidation&lt;/code&gt; enum의 필드를 검증하는 관심사가 Service에 존재&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위 3개의 문제점을 발견하여 코드 정리를 진행했다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Service
class UserRegisterValidationService(
    private val userRegisterMapper: UserRegisterMapper
) {

    private val validationTasks: List&amp;lt;KFunction1&amp;lt;List&amp;lt;UserDataVO&amp;gt;, RegisterUserValidationResult?&amp;gt;&amp;gt; = listOf(
        ::userIdDuplicateValidation,
        ::dropUserIdValidation,
        ::companyIdValidation
    )

    fun validationCheck(registerUsers: List&amp;lt;UserDataVO&amp;gt;): RegisterUserValidationResultVO {
        val registerUserValidationResults = validationTasks.mapNotNull { it(registerUsers) }
        val registerUserFieldValidationResult = RegisterUserFieldValidation.validate(registerUsers)

        return RegisterUserValidationResultVO(
            errorDataCombine(...),
            errorReasonCombine(...)
        )
    }
   
    ...
}

enum class RegisterUserFieldValidation (
    private val property: KProperty1&amp;lt;UserDataVO, String?&amp;gt;,
    private val required: Boolean,
    private val regex: String
) {

    USERID(UserDataVO::userId, true, &amp;quot;...&amp;quot;),
    PASSWORD(UserDataVO::password, true, &amp;quot;...&amp;quot;),
    EMAIL(UserDataVO::email, true, &amp;quot;...&amp;quot;),
    MOBILE(UserDataVO::mobile, true, &amp;quot;...&amp;quot;),
    LEVEL(UserDataVO::level, true, &amp;quot;...&amp;quot;),
    TYPE(UserDataVO::type, true, &amp;quot;...&amp;quot;),
    STATUS(UserDataVO::status, true, &amp;quot;...&amp;quot;),
    COMPANYMOBILE(UserDataVO::companyMobile, false, &amp;quot;...&amp;quot;);

    companion object {
        fun validate(registerUsers: List&amp;lt;UserDataVO&amp;gt;): RegisterUserValidationResultVO {
            val errorData = // UserDataVO를 순회하며 registerUsersValidate를 통한 검증 결과 누적

            if (errorData.isEmpty()) {
                return RegisterUserValidationResultVO(emptyMap(), emptyList())
            }

            val errorReason = ValidationErrorReason(ValidationMessage.REG_EXP_VALIDATION_FAIL, errorData.values)
            return RegisterUserValidationResultVO(
                errorData,
                listOf(errorReason.convertMessage())
            )
        }

        private fun registerUsersValidate(registerUsers: List&amp;lt;UserDataVO&amp;gt;, fieldValidation: RegisterUserFieldValidation): ValidationErrorData? {
            val values = // 필드 검증

            return if (values.isEmpty()) null else ValidationErrorData(fieldValidation.property.name, values)
        }
    }
}

enum class ValidationMessage(private val description: String) {
    DUPLICATE_ID(&amp;quot;아이디가 이미 존재합니다. : %s&amp;quot;),
    DROP_ID(&amp;quot;탈퇴한 아이디가 존재합니다. : %s&amp;quot;),
    NOT_EXIST_COMPANY_ID(&amp;quot;존재하지 않는 회사입니다. : %s&amp;quot;),
    REG_EXP_VALIDATION_FAIL(&amp;quot;생성 규칙에 맞지 않은 데이터입니다. : %s&amp;quot;);

    fun convert(data: Collection&amp;lt;Any&amp;gt;) = this.description.format(data.toString())
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3c33170bc9b7bbc6598ee42c18c460a9/0907e/refactor2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.444444444444443%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABCUlEQVR42l2Ra07DMBCEfQV+FFTUxO84SWPH6bsgcQ8OwvmHXVNHFT9Gir27n2c2opEarFaZVfKhlu5DCJjzAaEfsWvVqv8zVcL5AOs6Gjao8Hdl8UZn6QJyzrhc70h5gQsDYpqR5gW+61fws8Tt/oHlcII2FvYBX6TEZ9NgYqfWgx/tyOEwRkwE3MdUHGuuEbhKGQdxvt5Kg/eeoi2Iccb3bouf7QZfBH2lV6W2MOR2mlKJH+dcAP2wpwR0ThkTzbEhwcWRGhtyU/ezUQ4v2mFLd945ipsLiPuM7wswL8cCMpTID2P55rqokOefoUi6nrWBoWg8qCmSJeDxfAGvinfJ7rV1f3Xr8QvGaK04I+ZVbQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;refactor2&quot;
        title=&quot;&quot;
        src=&quot;/static/3c33170bc9b7bbc6598ee42c18c460a9/1cfc2/refactor2.png&quot;
        srcset=&quot;/static/3c33170bc9b7bbc6598ee42c18c460a9/3684f/refactor2.png 225w,
/static/3c33170bc9b7bbc6598ee42c18c460a9/fc2a6/refactor2.png 450w,
/static/3c33170bc9b7bbc6598ee42c18c460a9/1cfc2/refactor2.png 900w,
/static/3c33170bc9b7bbc6598ee42c18c460a9/21482/refactor2.png 1350w,
/static/3c33170bc9b7bbc6598ee42c18c460a9/d61c2/refactor2.png 1800w,
/static/3c33170bc9b7bbc6598ee42c18c460a9/0907e/refactor2.png 3788w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;대략적인 코드 정리 결과물이다. 이 작업을 통해 어떤 점이 개선되었을까?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용자 유효성 검증을 진행하는 함수들을 List로 묶어서 각 검증 결과 DTO를 반환하도록 수정하여 추가가 수월해졌다.&lt;/li&gt;
&lt;li&gt;사용자 유효성 검증과 회원가입 필드 유효성 검증을 구분하여 관심사를 분리하였다. 예를 들어, 정규표현식을 보유하고만 있던 Enum에게 직접 유효성 검증을 하도록 책임을 부여하였다.&lt;/li&gt;
&lt;li&gt;UserDataVO 객체의 필드를 리플렉션으로 순회하는 부분을 제거하고 각 정규표현식이 관심가지는 UserDataVO의 필드를 직접 매핑하였다.&lt;/li&gt;
&lt;li&gt;각 단계별 검증 반환 DTO를 추가하여 검증 결과를 누적하기 위해 사용하던 변수들을 제거하였다.&lt;/li&gt;
&lt;li&gt;검증 실패 메시지를 Enum으로 분리하였다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;결론&quot; style=&quot;position:relative;&quot;&gt;결론&lt;a href=&quot;#%EA%B2%B0%EB%A1%A0&quot; aria-label=&quot;결론 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;스스로가 만든 작업 범위에서 &lt;code class=&quot;language-text&quot;&gt;분석 → 설계 → 개발 → 공유&lt;/code&gt;를 통해 많은 것을 배웠다.&lt;br&gt;
그리고 리팩토링이 쉬운 작업이 아니라는 것을 확실히 느꼈다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;기존 코드를 완벽하게 분석하여 이해하고&lt;/li&gt;
&lt;li&gt;이 작업이 어디까지 영향을 끼치는지 확인하여 작업 범위를 스스로 결정하고&lt;/li&gt;
&lt;li&gt;코드 정리인지, 동작 변경인지 분류하여&lt;/li&gt;
&lt;li&gt;설계,개발하여 검증하고&lt;/li&gt;
&lt;li&gt;작업 결과를 공유하기 위한 준비를 해야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그래도 힘든만큼 보람도 있었으며, 교육을 듣거나 책을 여러 권 읽는 것보다 직접 실천하며 부딪히며 배우는 것이 훨씬 더 머리에 쉽게 각인되었다.&lt;br&gt;
거기에다가 배운 것을 바로 써먹을 수 있는 기회까지 만들어 갈 수 있으니 쉬운 업무를 할당받거나 조금 더 도전적인 업무를 통해 성취감을 느끼고 싶다면 꼭 도전해보길 바란다.&lt;br&gt;
남의 코드를 읽고 비난을 자주하는 개발자라면 이 리팩토링 작업을 통해 다른 사람이 읽기 쉬운 클린 코드를 작성한다는 것과 객체지향 프로그래밍을 따르는 것은 말처럼 쉽지 않은 작업임을 느낄 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;클린 코드는 기계적인 원칙이 아닌 탐색의 결과이고 완성은 없다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;좋은 코드가 상업적 성공에 필수적인 부분은 아니다. 조잡한 코드로 돈을 많이 버는 사례가 있다고 할지라도, 나는 여전히 코드 품질이 매우 중요하다고 믿는다.&quot;&lt;br&gt;
&quot;기능을 개발하고 출시하며, 기회와 경쟁 상황에 따라 개발 방향을 바꿀 수 있으며 위기 속에서도 직원들의 사기를 높일 수 있는 회사는 조잡하고 버그가 있는 코드를 작성하는 회사에 비해 성공할 확률이 높다.&quot;&lt;br&gt;
&lt;strong&gt;&quot;설사 좋은 코딩이 장기적으로 경제적 이득을 가져오지 못한다고 하더라도 나는 여전히 내가 작성할 수 있는 최고의 코드를 작성할 것이다.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;켄트 백의 구현 패턴 중&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[AWS DMS를 이용한 MySQL 마이그레이션]]></title><description><![CDATA[IDC에서 운용중이던 단일 데이터베이스를 AWS Aurora로 이관하는 동시에 MySQL…]]></description><link>https://jdalma.github.io/2024y/mysqlMigration/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/mysqlMigration/</guid><pubDate>Sat, 11 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;IDC에서 운용중이던 단일 데이터베이스를 AWS Aurora로 이관하는 동시에 MySQL 버전업도 이루어져야 했기 때문에 신경써야할 것이 많았다.&lt;br&gt;
어떤 의사결정을 거쳐 어떤 부분들을 확인해야 했는지 정리해보려한다.&lt;/p&gt;
&lt;h1 id=&quot;직접-클러스터-구성하기&quot; style=&quot;position:relative;&quot;&gt;직접 클러스터 구성하기&lt;a href=&quot;#%EC%A7%81%EC%A0%91-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0&quot; aria-label=&quot;직접 클러스터 구성하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;읽기 전용 DB(Secondary)들을 Primary DB의 Replication DB로 구성하여 &lt;strong&gt;&lt;a href=&quot;https://proxysql.com/&quot;&gt;ProxySQL&lt;/a&gt;을 사용하여 RW를 분리&lt;/strong&gt;하고 &lt;strong&gt;&lt;a href=&quot;https://github.com/openark/orchestrator&quot;&gt;Orchestrator&lt;/a&gt;를 통하여 페일백/페일오버&lt;/strong&gt;를 직접 구성하여 클러스터를 직접 구축하는 것을 고민했다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7fd84940401d4df4b898a46074eb5676/d8c86/proxySQLstructure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.111111111111114%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACnklEQVR42n2Sy08TURTG548wGl+JxsRofEbjQqNb41JjDEo0oYgtj6KlBmIMMaxcGHChbly4MmHhQkSURaGGKAuChI2PKLZQSlvotJ3OTNvpzHSmPy+DLDTgufnu45x7vvOduSPxx+r1ureapoll1jBtsBZS2LJKMZUl+VNGz5QJ9w3Q2NqLU2NDk1YnwzBYWcmSk2WGJ78yM/sTihpoRchl0VNJCktx6iL+9PkLHjx6Ru1/hI7jCGUWtm2xkM6SFaqcRArkFbLpDMs/ZMyMKBiPo8k1bN2iZtc3Jlxtdb3ddRP8ooArFOXJJJKoQmGtbJJOLqEuJTDzukfojfrfkNjMRI26uwZEgfofePtN1HkKu7rChMNhQqEQfr+fYDBIW1s7nZ1BWlsD9PR0E4vF6H884J2Dwv/w5Sv6Bt8w++U7a29QxbIsD9KHyHvevR9lePgNQ0NDjIyMMD4+ztjYGJFIhGg0yvT0ZyYmJoQ/Ku68Znj0E/1PBjl/4SKB1hC+5g4aGnz4AyEkraR5VVzXxXEd8TD2P6271Ko6rl0RB1vcq3p9T01Osm37fo6fOCdwlh27DnDoyGmkglKkUtZZjMdIJ3KYZYWKlkcrrKDKKW+Vl5fIZpIYJRHTFSxDY+pjlKuNzXTe6REqu7h+3c+twB2kYnGVsMT83DcW52OohaxI0KmUiijpOcrKsvAJ8nwGQyuQW06w+CvJp4lZWvy3CYXv09HZzS2xb++4i6SqKoWCgq+phVMnz3DT5/cIyyK5rCloioycjgvMY5byVPUcTkUh8vY1W7bu89o9fOwMO3cfZM/eo0iGUUERKnvu9XGloYneBw+pOS5G1aQqfvbVuFFSPVT0IiW1IL6nwczMDJcu3yDQFqLJ1+blNlxr5jf8EDMP0xyo5gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;proxySQLstructure&quot;
        title=&quot;&quot;
        src=&quot;/static/7fd84940401d4df4b898a46074eb5676/1cfc2/proxySQLstructure.png&quot;
        srcset=&quot;/static/7fd84940401d4df4b898a46074eb5676/3684f/proxySQLstructure.png 225w,
/static/7fd84940401d4df4b898a46074eb5676/fc2a6/proxySQLstructure.png 450w,
/static/7fd84940401d4df4b898a46074eb5676/1cfc2/proxySQLstructure.png 900w,
/static/7fd84940401d4df4b898a46074eb5676/21482/proxySQLstructure.png 1350w,
/static/7fd84940401d4df4b898a46074eb5676/d8c86/proxySQLstructure.png 1663w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;하지만 큰 단점이 있었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Proxy SQL에 문제가 생긴다면 모든 DB를 사용할 수 없게 되는 &lt;strong&gt;단일 장애 지점&lt;/strong&gt; 이 생긴다.&lt;/li&gt;
&lt;li&gt;애플리케이션 외부에서 사용될 DB가 결정되기 때문에 애플리케이션에서 작업 단위(트랜잭션)를 지정하기 힘들어진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 두 문제가 있어 AWS Aurora 클러스터를 분석하여 사용해보기로 하였다.&lt;br&gt;
혹시 이 구성에 대해 더 자세히 알고 싶다면 &lt;a href=&quot;https://www.inflearn.com/course/mysql-docker/dashboard&quot;&gt;따라하며 배우는 MySQL on Docker&lt;/a&gt; 강의를 참고하길 바란다.&lt;/p&gt;
&lt;h1 id=&quot;aws-aurora-클러스터-분석&quot; style=&quot;position:relative;&quot;&gt;AWS Aurora 클러스터 분석&lt;a href=&quot;#aws-aurora-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EB%B6%84%EC%84%9D&quot; aria-label=&quot;aws aurora 클러스터 분석 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Aurora 클러스터를 사용하기 위해서는 확인해야 할 부분들이 있었다.&lt;/p&gt;
&lt;h2 id=&quot;aurora-클러스터의-페일오버와-페일백은-어떻게-진행되는가&quot; style=&quot;position:relative;&quot;&gt;Aurora 클러스터의 페일오버와 페일백은 어떻게 진행되는가?&lt;a href=&quot;#aurora-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EC%9D%98-%ED%8E%98%EC%9D%BC%EC%98%A4%EB%B2%84%EC%99%80-%ED%8E%98%EC%9D%BC%EB%B0%B1%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%A7%84%ED%96%89%EB%90%98%EB%8A%94%EA%B0%80&quot; aria-label=&quot;aurora 클러스터의 페일오버와 페일백은 어떻게 진행되는가 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;기본 인스턴스에 문제가 발생하는 경우 읽기 인스턴스 중 하나가 기본 인스턴스 역할을 인수한다.&lt;br&gt;
Aurora는 데이터베이스 문제를 감지하고 필요한 경우 장애 조치 메커니즘을 자동으로 활성화하여 장애 조치 중에 쓰기 작업을 처리하지 못하는 시간이 최소화된다.&lt;br&gt;
Aurora는 다음 두 가지 방법 중 하나를 사용하여 자동으로 새 기본 인스턴스로 장애 조치한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;우선순위가 높은 기존 Aurora 복제본을 새 기본 인스턴스로 승격시킴&lt;/li&gt;
&lt;li&gt;새로운 기본 인스턴스 만들기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DB 클러스터에 Aurora 복제본이 하나 이상인 경우에는 장애가 발생하더라도 Aurora 복제본이 기본 인스턴스로 승격되며 &lt;strong&gt;이 실패 이벤트로 인해 예외적으로 실패하는 읽기 및 쓰기 작업 동안 짧은 중단이 발생한다.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;일반적인 서비스 복구 시간은 60초 미만이지만 대부분 30초 미만에 복원된다.&lt;/strong&gt; (read only connection 예외 발생)&lt;br&gt;
최소 하나 이상의 Aurora 복제본을 둘 이상의 다른 가용 영역에 생성하는 것이 바람직하다.&lt;/p&gt;
&lt;p&gt;DB 클러스터에 Aurora 복제본이 포함되어 있지 않으면 기본 인스턴스가 동일한 AZ에 다시 생성되기 때문에 보통 10분 미만의 시간이 걸리며 그 동안 예외적으로 실패하는 읽기 및 쓰기 작업 동안 중단이 발생한다.&lt;br&gt;
Aurora 복제본을 기본 인스턴스로 승격시키는 것이 기본 인스턴스를 새로 생성하는 것보다 훨씬 빠르다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;출처&lt;/strong&gt; : &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/Concepts.AuroraHighAvailability.html&quot;&gt;Amazon Aurora의 고가용성&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;운영 중 DB 스케일 업에 대한 트러블 슈팅 글도 읽어보면 도움이 될 것이다. &lt;a href=&quot;https://medium.com/29cm/29cm-%EC%9D%98-%EC%9D%B4%EA%B5%BF%EC%9C%84%ED%81%AC-%EC%9E%A5%EC%95%A0%EB%8C%80%EC%9D%91-%EA%B8%B0%EB%A1%9D-177b6b2f07a0&quot;&gt;29CM 의 이굿위크 장애대응 기록&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;secondary의-동기화-지연율은-어느정도인가&quot; style=&quot;position:relative;&quot;&gt;Secondary의 동기화 지연율은 어느정도인가?&lt;a href=&quot;#secondary%EC%9D%98-%EB%8F%99%EA%B8%B0%ED%99%94-%EC%A7%80%EC%97%B0%EC%9C%A8%EC%9D%80-%EC%96%B4%EB%8A%90%EC%A0%95%EB%8F%84%EC%9D%B8%EA%B0%80&quot; aria-label=&quot;secondary의 동기화 지연율은 어느정도인가 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;먼저 쓰기 연산은 Primary에서 관리하며 Aurora 복제본(Secondary)은 클러스터 볼륨의 읽기 연산에 사용되므로 읽기 조정에 유용하다.&lt;br&gt;
데이터들은 단일 AWS 리전에서 다중 가용 영역에 걸쳐 DB 클러스터에 데이터 복사본을 저장하며, DB 클러스터의 인스턴스가 여러 가용성 영역에 걸쳐 있는지 여부에 관계없이 복사본을 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/04b4b629efa4729d97bdee3b00cbc141/0e94d/writeAndAsyncRead.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACWElEQVR42k1T22oTURSdf/ETRNFX/QBFQf9A/0QQ+mDVpypiC4JtEdHaiihVqom2kaY6ySSZTOaSuWU6meuZlFiR5d4nCfRhcfY+l33WOmsfZeh6GJgW+gRV68K0HXT1PkZRBCEE8qJAVpbIihJFOUPGcwSOc0Iq89maEoQjuL4P1/PBxZ2hi+PxGFmey2KMPxOBU0KWFyjpEMc8l87zvxRPOc9yKL/UFrSuDssZoj8wJWyKx0kib4zTDGvfenhR71PxHOMsw8pnDRsHBgRdFsQpHn9s4V3TQkHrih+GiOmwlDFnldKhnCQIUSIYp7iy8gNXVw5InsAwLnB+uYYba4eYnpygG2Q4d38Pd1+1JFul09PRJejGALpuQOv0YJNs+T7MYBTBtoewDQO+NUBgmXAo9mwTkWcj9h24NB/5NpLxCIpp2VLigEaDjPGDQDIVokJSJHj46CnWl+7g3+sLqNYvotq4hOnmZZxuX8dk+yaqnVs4eX8b1dtrSBrLJDkIwbLZVTYoJEZsyszBAmrPQvugh9DtIvT78HQdrt1Bo63CdAzEIwtJZMFs03rgQWFmLJtZen4AjxgmaSoLikpgdaeGew+28GWvhpam4+d3Dbu7dSw938Kb+hEah0d4srmLl88+SUMVZhMSuyiOpRmMkvpv9oYl+paFhvobbU2jlhpSizlQmxrU/Q7FHpp0yeqHfXxtHCEMAygskXuQpS8kLxqYTakmExQ55fSmfEFJYzA6pnZKKRdyvaJumPA+IiIbm01YsMvnP+BsUX7LRcwjn2HIX3PmAzD+A1ZpGe7mX/aOAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;writeAndAsyncRead&quot;
        title=&quot;&quot;
        src=&quot;/static/04b4b629efa4729d97bdee3b00cbc141/1cfc2/writeAndAsyncRead.png&quot;
        srcset=&quot;/static/04b4b629efa4729d97bdee3b00cbc141/3684f/writeAndAsyncRead.png 225w,
/static/04b4b629efa4729d97bdee3b00cbc141/fc2a6/writeAndAsyncRead.png 450w,
/static/04b4b629efa4729d97bdee3b00cbc141/1cfc2/writeAndAsyncRead.png 900w,
/static/04b4b629efa4729d97bdee3b00cbc141/21482/writeAndAsyncRead.png 1350w,
/static/04b4b629efa4729d97bdee3b00cbc141/d61c2/writeAndAsyncRead.png 1800w,
/static/04b4b629efa4729d97bdee3b00cbc141/0e94d/writeAndAsyncRead.png 4748w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7b498520d0ff7637179ef7a4ff3bfcfe/41c2c/replicas.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAACbElEQVR42jWSS2sTcRTF800UQRD8EK78AKIuBD+AC7tyIYILd4Kl6F7ddCGCpdQ2Nm21htpG0yYxmWTy6DSPeWUyM5lk8myjm593prg4c+HMuX/OPfcmipUK2UIepapyUihynCtycJQlVyxSUiuUayoHmV/8PMmRzefJKyVKFZX04S/8fsBsPmO+uMTsfEailOtw9L3B8eEZmmbien35McU2Amolh7rSYxBMaGoOmfTppa5h0dG7eH6A74ZUCzbVvIXe9EncebbKjQcr3Hz4mqyi0Wp3GPRdXrzd4dr9Za7efcXmjwpvPh5w5d5Lrj9YZnX7hDAc4Do9dtUmt99/5ta7DVbSeRIruWOW9r7yJJ2m6Xn0AxljNGK9Ueex8EvfvpGzLJLaKY9291ja3+fQMPh7ccH5dE5p6PG89ZunWp4Nt0MikzliL7XN190dycSXTOZcLBZUJNvU1hY7X77IeDq1WpX1T59Ibm6STCbZTqVICbY2t/i8ts7ah48UcuJQO2tKcxW1WpP8fAbDkGE4wjAtavUG1VpdsvKxLJtiUUEpq1REG/FRT1mtosiSikoZUyZJGF2PlunQNhzOmibtjsWppscPL/4smJ+fM5dN9iSKluMS69s2lu3i9Po4biDo0+0FTGayZa3tkC/rqHWDxmlHHuyiGz0RB3jeUDINGQxG6E4fVe/RaJmYYmA8mcRnEp1NhMl0KtyYRBiO44ZhVIdDwtE4Fkdj23aA3RUHjk/X9rBMTzhX3Hi44qrn+jGi8xmNL/sS0Wcy/Y9pTP7nTKuPokiWNVvc6xKJEdcoknq9I9WIUW+0ZSI/7vkHLBG7ke2t780AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;replicas&quot;
        title=&quot;&quot;
        src=&quot;/static/7b498520d0ff7637179ef7a4ff3bfcfe/1cfc2/replicas.png&quot;
        srcset=&quot;/static/7b498520d0ff7637179ef7a4ff3bfcfe/3684f/replicas.png 225w,
/static/7b498520d0ff7637179ef7a4ff3bfcfe/fc2a6/replicas.png 450w,
/static/7b498520d0ff7637179ef7a4ff3bfcfe/1cfc2/replicas.png 900w,
/static/7b498520d0ff7637179ef7a4ff3bfcfe/21482/replicas.png 1350w,
/static/7b498520d0ff7637179ef7a4ff3bfcfe/d61c2/replicas.png 1800w,
/static/7b498520d0ff7637179ef7a4ff3bfcfe/41c2c/replicas.png 4784w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;DB 클러스터 볼륨은 DB 클러스터의 여러 데이터 사본으로 구성되어 있지만, DB 클러스터의 Primary 및 Aurora 복제본(Secondary)에는 클러스터 볼륨 데이터가 단 하나의 볼륨으로 표시된다.&lt;br&gt;
신규 인스턴스가 추가되어도 기존 데이터를 새로 복사하지 않고 DB 클러스터 볼륨에 연결만하기 때문에 인스턴스를 추가하기 용이하다.&lt;br&gt;
전통적인(기존 MySQL) 방식으로 읽기 인스턴스를 스케일링 하기 위해서는 인스턴스들은 볼륨을 각각 소유하며 binlog를 이용해 SQL문을 복제본으로 전달하여 Primary에서는 멀티 스레드로 수행하던 쿼리를 Secondary에서는 싱글 스레드로 처리하기 때문에 동기화 지연율이 존재할 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b8545c225499d57c642813d00f089972/7a1fb/clusterRW.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 92.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAAEZUlEQVR42oWU228bRRTG/RfywCNFqBISCAleKLzQigJqUaCtSlEFAVSSpk1VkrQhTXohjXJ3UttJHF/j63q968uub3vxrtfX/Jg44gkkRhqt5puZ78ye853Pl8lkiCdjSEqOXCFNVkoTP4kQiUaIxWIk0zGqepmzcXp6yv8N3+h0SC3roAQslDc2arBN+ahN1+3T8/qUwmeYTTnkUgo5NHPe+KJTE3sHDuUD93weOnjGAN/ZZmiyzp/vF9m4VuLlJYWlD4q49T6eNeD5JyqvPlPYuK6yeFHGf0sfE2ZXLRbelVn/RuX1FYWn7xXHAceEB/dqLH9cZPuGyubVKq8/rdJpDulaQ1Y/L7N2ucTOzfKYPHi3PiaU1m2WP1TYmiizea3Mykcq1bB7Tthuelhql0JaZf8wRCKVo1CXaTkt6uUW1UKTqtREKzRo6gYDulimjSYLTOBn+5rcwHO754T/jO6og9aVqXVFNKdI3ohiDnSMgYY5FFN8jX6Vgpmg1T1fj/GhTmtQQXNlfEVFIZXOkM9L6FqNQX84Jh+JimqO8q8qDkcjtPZ/4MPh+LyvKZ5uWjYZpUSyWESScsgiSNNykA0Jpd4gW6kgyxJSUaYhziumjCrw3BgvIMkydcOiKgL5BsMRXcEef/qU1Nw8h7kKO34/5ZqCZOaJzs8TezBLIFNmZ3cfVROpMDPEnjwh9nCWULrE5pYfuZKn4sjnhLbnEftjhvjD+9zxW7zwh0kob4hrCRILsyQezXBnt8VfewnxF0EOygFSi39w8vgBP2zXWdmJciztIttpQTg6xXVdootXOJq6gLJ2W1Q5T9txyGiiaxa/FPg75F5+P66+ZVvk9CyJZ18Tnr5A5vmE6LQMLcMQOVTx9cULe70uQf82kaCf+OEeyUQc3aqKXOVIHh9xHNwjlzgkEYtSapSQzTQnkWOOA37yAo9FwhSFzFQ7h0/wUdV0MpO/cPgqyMNjl9DhgXhdhIpVI/lojtT0HLPxIa93jzhRjigYRVKPF0jNzPMo1ufFZoBIIYDZa53rMJMS5jA/web971jajxM6iuKJvJ5VOjx3k+PpyyxvbLF1kKTZqGG0PSILt4nNXGFpbZ31QIyarnNmHT5vIPSmZok8myC9/JUwhhWyBRW5XBlLJL76s8ivwHenSGclsrIiJGJzsjZJYukqpZ17ZHN5cUemN+jjczzRRqJl+r2ByOWIRrtHr+PgGg2Be3QFPhgI3B3itC2cZpW228HsDDE6I5ruANN20Gr6WPS+scP1PW7s6dwNicbvtZk8anB9ryl6sc1cvM4XW7qwM5v1vM2lHQ/ddKjX6qTyGq1Wi0bTIFOoo+kNUeXBEM9p8+NqlF83EnTMBlPbSW69iuCYTRYCab5dCdNstdl/84SffrtISdNQEmuczL1FpZhCzQvdzr2NerJ1XpRur4dRVTCFMzudDlatMl67HeFCddH8VVngfVwtTD89TcdtizNZjPjvOFYdu1WhlZjCrkv8Da5BTPDYAlIvAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;clusterRW&quot;
        title=&quot;&quot;
        src=&quot;/static/b8545c225499d57c642813d00f089972/1cfc2/clusterRW.png&quot;
        srcset=&quot;/static/b8545c225499d57c642813d00f089972/3684f/clusterRW.png 225w,
/static/b8545c225499d57c642813d00f089972/fc2a6/clusterRW.png 450w,
/static/b8545c225499d57c642813d00f089972/1cfc2/clusterRW.png 900w,
/static/b8545c225499d57c642813d00f089972/7a1fb/clusterRW.png 1030w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;동기식 복제에서는 하나의 쓰기가 내구성을 갖추려면, 모든 복사본을 확인해야 합니다.&lt;br&gt;
이 방법에서 속도는 디스크, 노드 또는 네트워크 경로에 중 가장 느린 요소에서 얼마나 느린가에 의해 결정됩니다.&lt;br&gt;
반면, 비동기식 복제는 지연 시간을 낮출 수 있지만 데이터를 복제하고 데이터 내구성을 확인하기 전에 장애가 발생하면 데이터가 손실될 수 있습니다.&lt;br&gt;
&lt;strong&gt;사실 두 방식 모두 완벽하지 않습니다.&lt;/strong&gt; 또한, 장애가 발생하면 복제 구성원 집합에 변경이 발생하게 되므로 쉽지 않습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Amazon Aurora는 &lt;strong&gt;쿼럼 모델&lt;/strong&gt;을 사용하여 3개의 가용 영역(AZ)에 걸쳐 4개의 쓰기 세트와 3개의 읽기 세트가 있는 6개 복사본 쿼럼을 사용하며, Pirmary에서 쓰기가 발생하면 6개의 스토리지 노드에 REDO 로그 레코드가 작성되며 &lt;strong&gt;이중 4개의 노드가 쓰기 완료 응답(ACK)을 전송하면 해당 쓰기가 완료된 것으로 확인한다.&lt;/strong&gt;&lt;br&gt;
3개의 AZ에 걸쳐 노드를 분산하기 때문에, 하나의 AZ에 문제가 발생하더라도 내결함성을 유지할 수 있으며, 느린 노드가 존재하더라도 다른 노드가 신속하게 응답하기 때문에 가용성이 떨어지지 않는다.&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;Aurora의 읽기 전용 복제본은 마스터 데이터베이스 노드와 동일한 스토리지 볼륨을 공유하며, 마스터의 실행 로그를 비동기로 전달받아 캐시내 데이터 페이지를 갱신한다.&lt;/strong&gt;&lt;br&gt;
그렇기 때문에 읽기 복제본의 데이터 유실이나 마스터 노드로 승격 시키는 비용도 줄어든다.&lt;/p&gt;
&lt;p&gt;마스터(쓰기) 노드에서 커밋된 모든 변경사항이 복제본으로 미처 전파되지 않았더라도 영구적으로 저장되며 복제 노드들은 독자적으로 읽기를 수행할 때 읽어야 할 데이터의 쓰기 및 쓰기 완료 응답 등의 현황에 대해 알 수 없다.&lt;br&gt;
그렇기에 마스터에서 복제본으로 실행 레코드들을 전달할 때 읽기 뷰와 개념적으로 동일한 기능을 함께 제공한다.&lt;br&gt;
이러한 뷰는 &lt;strong&gt;커밋 LSN&lt;/strong&gt; 과 &lt;strong&gt;어떤 세그먼트들이 어떤 LSN으로 영구 저장 되었는지&lt;/strong&gt; 의 정보를 보유하고 있으며, 일반적으로 커밋 LSN는 &lt;code class=&quot;language-text&quot;&gt;10 밀리초&lt;/code&gt;마다 반영될 수 있기에 &lt;strong&gt;복제본들은 최소한의 조정으로 마스터 노드와 거의 동일한 정보를 제공할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;읽기 뷰&lt;/strong&gt;&lt;br&gt;
읽기 뷰는 SQL문 수행 시 커밋된 모든 변경사항을 볼 수 있고, 아직 커밋되지 않은 변경사항은 보지 못하게 하기 위한 논리적인 시점을 설정합니다.&lt;br&gt;
MySQL은 가장 최근 커밋의 로그시퀀스번호(LSN)을 설정하여 이를 구현하며, 이러한 방식을 통해 이미 커밋된 모든 변경사항들은 조회 가능하게 하고, 조회되지 말아야 할 변경사항들은 활성 트랜잭션 리스트를 사용하여 확인합니다.&lt;br&gt;
특정 읽기 뷰를 가진 SQL문이 데이터 페이지를 조회할 때, 읽기 뷰 생성 당시 활성화된 트랜잭션들의 변경 사항은 조회되지 말아야 합니다.&lt;br&gt;
이는 변경 사항이 현재 커밋되었거나 읽기 시점 커밋 LSN 이후에 시작된 어떠한 트랜잭션들에 대해서도 동일하게 적용됩니다.&lt;br&gt;
트랜잭션이 읽기 뷰를 생성하면, 시스템 내 발생한 모든 다른 변경 사항들로부터 격리될 수 있습니다. —일관된 특정 시점을 참조할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;출처&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Replication.html&quot;&gt;Amazon Aurora MySQL을 사용한 복제&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/korea/amazon-aurora-under-the-hood-quorum-and-correlated-failure/&quot;&gt;Amazon Aurora 내부 들여다보기(1) – 쿼럼 및 상관 오류 해결 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/korea/amazon-aurora-under-the-hood-quorum-reads-and-mutating-state/&quot;&gt;Amazon Aurora 내부 들여다보기(2) – 쿼럼 읽기 및 상태 변경&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/garimoo/aws-aurora-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-6ff87b0d48c5&quot;&gt;AWS Aurora 아키텍처&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.asquareb.com/blog/2021/01/10/amazon-aurora-storage/&quot;&gt;Amazon Aurora Storage&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;aurora-클러스터-스토리지는-어느것을-사용하나&quot; style=&quot;position:relative;&quot;&gt;Aurora 클러스터 스토리지는 어느것을 사용하나?&lt;a href=&quot;#aurora-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80%EB%8A%94-%EC%96%B4%EB%8A%90%EA%B2%83%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%82%98&quot; aria-label=&quot;aurora 클러스터 스토리지는 어느것을 사용하나 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aurora 클러스터 스토리지에는 &lt;strong&gt;Aurora Standard&lt;/strong&gt; 와 &lt;strong&gt;Aurora I/O-Optimized&lt;/strong&gt; 가 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aurora Standard&lt;/strong&gt;는 I/O 사용량이 낮거나 보편적인 대부분의 환경에서 효율적인 요금으로 사용할 수 있는 구성이기에 많이 선택되는 것 같다.&lt;br&gt;
&lt;strong&gt;Aurora I/O-Optimized&lt;/strong&gt;는 I/O를 많이 사용하는 애플리케이션(결제 처리 시스템, 전자상거래 시스템, 금융 애플리케이션 등)에 우수한 가격 대비 성능을 제공하는 데이터베이스 클러스터 구성이다.&lt;br&gt;
또한 I/O 지출이 총 Aurora 데이터베이스 지출의 25%를 초과하는 경우 Aurora I/O-Optimized를 이용하면 I/O 집약적 워크로드의 비용을 최대 40% 절감할 수 있다.&lt;/p&gt;
&lt;p&gt;클러스터 구성에서 스토리지 구성 수정이 가능하기 때문에 현재는 Standard로 설정했으며, 운영 반영 후에 CloudWatch를 사용하여 비용을 추정해보려 한다.&lt;br&gt;
&lt;a href=&quot;https://aws.amazon.com/ko/blogs/tech/estimate-cost-savings-for-the-amazon-aurora-i-o-optimized-feature-using-amazon-cloudwatch/&quot;&gt;Amazon CloudWatch를 이용한 Amazon Aurora I/O Optimized 기능에 대한 비용 절감 예상하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;출처&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/rds/aurora/faqs/&quot;&gt;Aurora FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/Aurora.Overview.StorageReliability.html&quot;&gt;Aurora 클러스터 스토리지 구성&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/rds/aurora/pricing/&quot;&gt;Aurora 클러스터 스토리지 요금&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;정리&quot; style=&quot;position:relative;&quot;&gt;정리&lt;a href=&quot;#%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;정리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AWS Aurora 클러스터의 장점들을 확인할 수 있었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;동기화 지연율&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;안정적인 백업&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고가용성(페일오버)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 장점들만 보아도 클러스터를 직접 구성하여 관리 포인트가 늘어나는 것 보다 AWS Aurora를 사용하는 것이 타당한 선택이란 것을 확인할 수 있다.&lt;/p&gt;
&lt;h1 id=&quot;aurora-인스턴스-스펙-선정을-위한-부하-테스트&quot; style=&quot;position:relative;&quot;&gt;Aurora 인스턴스 스펙 선정을 위한 부하 테스트&lt;a href=&quot;#aurora-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%8A%A4%ED%8E%99-%EC%84%A0%EC%A0%95%EC%9D%84-%EC%9C%84%ED%95%9C-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8&quot; aria-label=&quot;aurora 인스턴스 스펙 선정을 위한 부하 테스트 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Aurora 클러스터를 사용하기로 결정했다면 이제 쓰기 인스턴스의 기본 스펙을 어느정도로 사용할지 추산해야 한다.&lt;br&gt;
부하 테스트 결과를 통해 운영 환경과 비슷한 스펙의 DB와 AWS 인스턴스를 비교하기에는 &lt;strong&gt;mysqlslap&lt;/strong&gt; 의 테스트 결과 내용은 &lt;strong&gt;sysbench&lt;/strong&gt;에 비해 테스트 결과 내용이 빈약하다고 느꼈다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;# mysqlslap 테스트 결과 정보

Benchmark
        Average number of seconds to run all queries: 217.151 seconds
        Minimum number of seconds to run all queries: 213.368 seconds
        Maximum number of seconds to run all queries: 220.934 seconds
        Number of clients running queries: 20
        Average number of queries per client: 50


User time 58.16, System time 18.31
Maximum resident set size 909008, Integral resident set size 0
Non-physical pagefaults 2353672, Physical pagefaults 0, Swaps 0
Blocks in 0 out 0, Messages in 0 out 0, Signals 0
Voluntary context switches 102785, Involuntary context switches 43&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;mysqlslap에 더 자세한 내용은 &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-measure-mysql-query-performance-with-mysqlslap&quot;&gt;mysqlslap으로 MySQL 쿼리 성능을 측정하는 방법&lt;/a&gt;을 참고하길 바란다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;# sysbench 테스트 결과 정보

Running the test with following options:				
Number of threads: 1				
Initializing random number generator from current time				
            
            
Initializing worker threads...				
            
Threads started!				
            
SQL statistics:				
    queries performed:				
        read:                            23282				
        write:                           0				
        other:                           3326				
        total:                           26608				
    transactions:                        1663   (166.17 per sec.)				
    queries:                             26608  (2658.80 per sec.)				
    ignored errors:                      0      (0.00 per sec.)				
    reconnects:                          0      (0.00 per sec.)				
            
General statistics:				
    total time:                          10.0022s				
    total number of events:              1663				
            
Latency (ms):				
         min:                                    4.93				
         avg:                                    6.01				
         max:                                   12.26				
         95th percentile:                        6.79				
         sum:                                 9992.34				
            
Threads fairness:				
    events (avg/stddev):           1663.0000/0.00				
    execution time (avg/stddev):   9.9923/0.00				&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;sysbench는 테스트에 실행된 총 쿼리수(초당 쿼리수), 총 소요시간, 지연율, &lt;a href=&quot;https://www.percona.com/blog/computing-95-percentile-in-mysql/&quot;&gt;95 백분위수&lt;/a&gt;까지 자동으로 계산해주기에 성능 비교에는 sysbench가 더 유용할 것이라고 판단했다.&lt;br&gt;
그리고 &lt;strong&gt;sysbench는 테스트에 사용할 task의 종류를 지정할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bulk_insert.lua&lt;/li&gt;
&lt;li&gt;oltp_common.lua&lt;/li&gt;
&lt;li&gt;oltp_delete.lua&lt;/li&gt;
&lt;li&gt;oltp_insert.lua&lt;/li&gt;
&lt;li&gt;oltp_point_select.lua&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;oltp_read_only.lua&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;oltp_read_only_custom.lua&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;oltp_read_write.lua&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;oltp_update_index.lua&lt;/li&gt;
&lt;li&gt;oltp_update_non_index.lua&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;oltp_write_only.lua&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;select_random_points.lua&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;select_random_ranges.lua&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code language=&quot;shell&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;#!/bin/bash
host=$1
port=$2
user=$3
password=$4
task=$5
filename=$6

sysbench --mysql-host=$host --mysql-port=$port --mysql-user=$user --mysql-password=$password --mysql-db=test --table-size=444444 /usr/share/sysbench/$task cleanup
sysbench --mysql-host=$host --mysql-port=$port --mysql-user=$user --mysql-password=$password --mysql-db=test --table-size=444444 /usr/share/sysbench/$task prepare

# 스레드 1개 ~ 2000개 까지
sysbench --mysql-host=$host --mysql-port=$port --mysql-user=$user --mysql-password=$password --mysql-db=test --threads=1 --table-size=444444 /usr/share/sysbench/$task run &amp;gt;&amp;gt; /stress_test/$filename.txt
sysbench --mysql-host=$host --mysql-port=$port --mysql-user=$user --mysql-password=$password --mysql-db=test --threads=2000 --table-size=444444 /usr/share/sysbench/$task run &amp;gt;&amp;gt; /stress_test/$filename.txt&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;테스트를 실행하기전 &lt;code class=&quot;language-text&quot;&gt;prepare&lt;/code&gt;를 꼭 거쳐야하며, 테스트가 끝나고 기존에 사용한 테이블과 데이터를 &lt;code class=&quot;language-text&quot;&gt;cleanup&lt;/code&gt;단계에서 제거해야 한다.&lt;br&gt;
테스트에 사용한 쉘 스크립트이며, &lt;code class=&quot;language-text&quot;&gt;cleanup&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;prepare&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;task&lt;/code&gt; 순서로 정의했다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- prepare 단계
create table sbtest1
(
   id int auto_increment
   primary key,
   k int default 0 not null,
   c char(120) default &amp;#39;&amp;#39; not null,
   pad char(60) default &amp;#39;&amp;#39; not null
);
create index k_1 on sbtest1 (k);

-- readonly 태스크
SELECT c FROM sbtest1 WHERE id=44
SELECT c FROM sbtest1 WHERE id=55
SELECT c FROM sbtest1 WHERE id BETWEEN 51 AND 150
SELECT SUM(k) FROM sbtest1 WHERE id BETWEEN 51 AND 150
SELECT c FROM sbtest1 WHERE id BETWEEN 51 AND 150 ORDER BY c
SELECT DISTINCT c FROM sbtest1 WHERE id BETWEEN 50 AND 149 ORDER BY c

-- writeonly 태스크
UPDATE sbtest1 SET k=k+1 WHERE id=2
UPDATE sbtest1 SET c=&amp;#39;03600646353&amp;#39; WHERE id=2
DELETE FROM sbtest1 WHERE id=2
INSERT INTO sbtest1 (id, k, c, pad) VALUES (2, 2, &amp;#39;38823248060&amp;#39;, &amp;#39;42610989539&amp;#39;)

-- select_random_points 태스크
SELECT id, k, c, pad FROM sbtest1 WHERE k IN (1, 2, 1, 2, 1, 1, 2, 1, 2, 2)

-- select_random_range 태스크
SELECT count(k)
FROM sbtest1
WHERE k BETWEEN 50 AND 55 OR k BETWEEN 50 AND 55 OR k BETWEEN 54 AND 59 OR k BETWEEN 51 AND 56 OR k BETWEEN 60 AND 65 OR k BETWEEN 50 AND 55 OR k BETWEEN 50 AND 55 OR k BETWEEN 51 AND 56 OR k BETWEEN 57 AND 62 OR k BETWEEN 50 AND 55&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;prepare 단계에서 &lt;code class=&quot;language-text&quot;&gt;sbtest1&lt;/code&gt;과 같은 테이블을 생성하고 각 &lt;code class=&quot;language-text&quot;&gt;task&lt;/code&gt;에 맞는 쿼리들을 부하 테스트에 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5de1571c97f22ae808f8947c1161f4da/b6e50/stressTest.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 82.22222222222221%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAABwElEQVR42m2UW6vCQAyE9///NJ9EEEFRH7zXu7bWe47f4MhSzkOYbDabZJK0qd1uR6vViuFwGM/nM97vt/D1ekl8btqtNzF1u93Y7XZxuVxiu93Gfr/XGR1Zr9ex2WxkW61W0hH8QPuhH4/HSDgRvaoqGQmAQ1EUcTgcYjKZxOl0iul0qvNyudRDhKBlWeod73mTCMDh8XjE/X6X3G43YV3XsoMkhYXt1+tVgo4/McA0GAxUARdkICtJEM6LxUJIhbABc5r4+4yoQldEFrJSCYJ+Pp9/iB9UXRmIDV+YgKnf7yuzHxGUitF5DDIQegVyTy/zPoJUx12azWbqD5kITDAemo6HAFWQM0kIiuCPHzZR9h52Oh0F/W/fsFmHmu+awn3q9XpqPBnIBjUQMTVTpmpXhN1VmjI+CSpkpYdQZqKeLoHYQwLQGlN3XxG3iGC/KTMx7x+S75j3zsh9PuGmTxqPxyobAxnQ/Rl5J/2J5ZX7EzX680zz+VyRaShlI1ACvSLsFw9B76131V8JFetbHo1G6p0vqBTEmf6AXmaCVt/g+eDcUy02TXbPoGSq+R9GQ/kwKRnEx7/8+JRfPwcz/gHJNMepwxEXRAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;stressTest&quot;
        title=&quot;&quot;
        src=&quot;/static/5de1571c97f22ae808f8947c1161f4da/1cfc2/stressTest.png&quot;
        srcset=&quot;/static/5de1571c97f22ae808f8947c1161f4da/3684f/stressTest.png 225w,
/static/5de1571c97f22ae808f8947c1161f4da/fc2a6/stressTest.png 450w,
/static/5de1571c97f22ae808f8947c1161f4da/1cfc2/stressTest.png 900w,
/static/5de1571c97f22ae808f8947c1161f4da/21482/stressTest.png 1350w,
/static/5de1571c97f22ae808f8947c1161f4da/d61c2/stressTest.png 1800w,
/static/5de1571c97f22ae808f8947c1161f4da/b6e50/stressTest.png 1862w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;총 5개의 task&lt;/strong&gt; 를 사용하여 스레드를 &lt;code class=&quot;language-text&quot;&gt;1 ~ 2000개&lt;/code&gt;까지 늘리면서 테스트를 진행하고 &lt;strong&gt;평균 실행 쿼리 개수&lt;/strong&gt;와 &lt;strong&gt;평균 지연율&lt;/strong&gt;, &lt;strong&gt;95 백분위수&lt;/strong&gt;를 기준으로 비교하였다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;즉, &lt;strong&gt;3개의 스펙 서버(AWS 인스턴스 2개, 운영 DB와 비슷한 스펙 서버) * 5개의 task * 10번의 스레드별 테스트&lt;/strong&gt; 를 진행하였다.&lt;br&gt;
각 서버들의 버퍼풀 크기가 상이하여 동일한 식별자를 가진 쿼리를 기준으로 테스트하지 않았으며 버퍼풀 워밍업은 고려하지 않았다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;부하 테스트를 통해 인스턴스 스펙간 차이는 어느정도 확인되었지만 현재 운영 중인 DB에 얼마정도의 요청이 들어오는지 확인이 필요했다.&lt;br&gt;
현재 모니터링 툴로 와탭을 사용중인데, 와탭은 짧은 시간에 다량으로 처리되는 쿼리는 확인하기 힘들다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;5초마다 조회된 시점의 액티브 세션 기준으로 조회되기 때문에 조회 시점에 액티브 세션이 존재하지 않는 건이라면 MySQL 통계에는 보이지 않을 수 있다.&lt;br&gt;
와탭을 이용해서 단시간에 빠르게 처리된 쿼리들을 모두 확인은 할 수 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위와 같은 이유로 운영 DB에 유입되는 질의문의 양을 측정할 수 있는 것은 MySQL의 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/server-status-variables.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Com_xxx&lt;/code&gt; 서버 상태 변수&lt;/a&gt;가 가장 믿을만하다고 판단하여 한 달 동안의 명령 횟수와 위에서 산출된 부하 테스트의 처리량을 비교하여 추산하였다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;전역 &lt;code class=&quot;language-text&quot;&gt;Com_xxx&lt;/code&gt; 변수는 서버가 실행되는 동안 특정 질의문이 실행된 횟수를 나타낸다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a35d7f65b54e85c0109fc6f7f9ce5703/5dded/comVariables.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.22222222222223%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABKUlEQVR42k2RyYrDQAxE/f+fFphLDjMxXuMVbBNv8ZJ40fiJtElD042kKpVK1jAMEkWRFEUhz+dTOOu6yjzP+n+/3+K6rpRlKcuy6N33XdI0lTiONb5tm95pmsS63+9yu92kbduzmCZZlikxlzgvJNTxp4njONJ1nWLGcdS8EgZBII/HQwtJopQkXYnxQhqGoTRNozEwYOu61kmMCCWk0KgA3Pe95Hl+KjRxmkDI3/d9SZLknAqMEuKfbdsq2Sisqko8zzsVmrGxBkWGkBp844DBAosEpHQGRDFGY/i3OoB/H6+NhygyisEwqQUr/pmNksQfunOWI7YejabDo5/LRdpP4++FgEEUE1iv1+tUYV6zNZazHbGhayULfQl/rzIcXm1HHty3JQbzD1/hY1D5cBtWAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;comVariables&quot;
        title=&quot;&quot;
        src=&quot;/static/a35d7f65b54e85c0109fc6f7f9ce5703/1cfc2/comVariables.png&quot;
        srcset=&quot;/static/a35d7f65b54e85c0109fc6f7f9ce5703/3684f/comVariables.png 225w,
/static/a35d7f65b54e85c0109fc6f7f9ce5703/fc2a6/comVariables.png 450w,
/static/a35d7f65b54e85c0109fc6f7f9ce5703/1cfc2/comVariables.png 900w,
/static/a35d7f65b54e85c0109fc6f7f9ce5703/21482/comVariables.png 1350w,
/static/a35d7f65b54e85c0109fc6f7f9ce5703/5dded/comVariables.png 1588w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h1 id=&quot;스프링에서-읽기쓰기-분리&quot; style=&quot;position:relative;&quot;&gt;스프링에서 읽기/쓰기 분리&lt;a href=&quot;#%EC%8A%A4%ED%94%84%EB%A7%81%EC%97%90%EC%84%9C-%EC%9D%BD%EA%B8%B0%EC%93%B0%EA%B8%B0-%EB%B6%84%EB%A6%AC&quot; aria-label=&quot;스프링에서 읽기쓰기 분리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;읽기/쓰기 커넥션을 분리하기 위해 &lt;a href=&quot;https://github.com/kwon37xi/replication-datasource&quot;&gt;권남님의 replication-datasource&lt;/a&gt;를 참고하였다.&lt;br&gt;
Primary와 Secondary의 DataSource 정보를 주입받아 읽기/쓰기 라우팅을 시켜줄 &lt;code class=&quot;language-text&quot;&gt;AbstractRoutingDataSource&lt;/code&gt;를 상속받았다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AbstractRoutingDataSource?&lt;/strong&gt;&lt;br&gt;
조회 키를 기반으로 getConnection() 호출을 다양한 대상 DataSource 중 하나로 라우팅하는 추상 DataSource 구현입니다.&lt;br&gt;
후자는 일반적으로 일부 스레드 바인딩 트랜잭션 컨텍스트를 통해 결정됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, 현재 컨텍스트에 따라 데이터 소스를 동적으로 결정할 수 있는 방법을 제공하는 추상 클래스이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class TransactionRoutingDataSource(
    primary: DataSource,
    secondary: DataSource
) : AbstractRoutingDataSource() {

    init {
        super.setTargetDataSources(mapOf(
            READ_WRITE to primary,
            READ_ONLY to secondary
        ))
        super.setDefaultTargetDataSource(primary)
    }

    override fun determineCurrentLookupKey(): Any {
        return DataSourceType.from(TransactionSynchronizationManager.isCurrentTransactionReadOnly())
    }

    private enum class DataSourceType {
        READ_WRITE,
        READ_ONLY;

        companion object {
            fun from(isReadOnly: Boolean) = if (isReadOnly) READ_ONLY else READ_WRITE
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;상속받아 구현한 라우팅의 책임을 가진 TransactionRoutingDataSource를 스프링에서 사용할 DataSource로 등록할 때 &lt;code class=&quot;language-text&quot;&gt;LazyConnectionDataSourceProxy&lt;/code&gt;로 랩핑하여 등록한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;LazyConnectionDataSourceProxy?&lt;/strong&gt;&lt;br&gt;
실제 JDBC 연결을 느리게 가져오는 대상 데이터 소스에 대한 프록시입니다.&lt;br&gt;
즉, 처음으로 명령문을 생성할 때까지는 가져오지 않습니다. 자동 커밋 모드, 트랜잭션 격리 및 읽기 전용 모드와 같은 연결 초기화 속성은 실제 연결을 가져오는 즉시(있는 경우) 실제 JDBC 연결에 유지되고 적용됩니다.&lt;br&gt;
...&lt;br&gt;
이 DataSource 프록시를 사용하면 실제로 필요한 경우가 아니면 풀에서 JDBC 연결을 가져오는 것을 방지할 수 있습니다. JDBC 트랜잭션 제어는 풀에서 연결을 가져오거나 데이터베이스와 통신하지 않고도 발생할 수 있습니다.&lt;br&gt;
이는 JDBC 문을 처음 생성할 때 느리게 수행됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, 실제로 커넥션이 필요한 경우가 아니라면 풀에서 커넥션을 점유하지 않고 실제로 필요한 시점에 커넥션을 점유하도록 할 수 있다.&lt;br&gt;
트랜잭션 진입 시점에 커넥션을 결정하지 않고 실제로 필요할 때 (&lt;code class=&quot;language-text&quot;&gt;readonly&lt;/code&gt; 속성에 따라) &lt;code class=&quot;language-text&quot;&gt;TransactionSynchronizationManager.isCurrentTransactionReadOnly()&lt;/code&gt; 결과를 기반으로 데이터 소스를 선택할 수 있게 된 것이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Bean
@ConfigurationProperties(prefix = &amp;quot;spring.datasource.primary&amp;quot;)
open fun primaryDataSource(): DataSource {
   return DataSourceBuilder.create().build()
}

@Bean
@ConfigurationProperties(prefix = &amp;quot;spring.datasource.secondary&amp;quot;)
open fun secondaryDataSource(): DataSource {
   return DataSourceBuilder.create().build()
}

@Primary
@Bean
@DependsOn(&amp;quot;primaryDataSource&amp;quot;, &amp;quot;secondaryDataSource&amp;quot;)
open fun routingDataSource(): DataSource {
   return TransactionRoutingDataSource(primaryDataSource(), secondaryDataSource())
}

@Bean
open fun dataSourceTransactionManager(routingDataSource: DataSource): PlatformTransactionManager {
   return DataSourceTransactionManager(LazyConnectionDataSourceProxy(routingDataSource))
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;더 자세한 이해와 원리는 다른 글로 정리하겠다.&lt;/p&gt;
&lt;h1 id=&quot;aws-dms를-이용한-데이터-이관&quot; style=&quot;position:relative;&quot;&gt;AWS DMS를 이용한 데이터 이관&lt;a href=&quot;#aws-dms%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9D%B4%EA%B4%80&quot; aria-label=&quot;aws dms를 이용한 데이터 이관 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5105fb2eca0a0be6a457e472a0a8a0d5/00d43/dms.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 30.22222222222222%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAACxKAAAsSgF3enRNAAABIElEQVR42lVRSU7EMBDM/z8BBw5IXHgAH+A+CC4zQghEEoesdry07aLsYbXUapdcru6ubsAjMZWE69sBFzc9lkVhmi0kAyFm+AAGs3zlilHx7oCcS+Sq0eDP2W2C9xTPUrF9uIR7vKLqCOgXxhura77knz+B1PQL0YgItNa1gpeExFdvcv3inu8Q3u9hXcY2G+oEJL8jSSSHsHBDQuSERSNQq5mmCafTCcYYOM6wrjtenxy2xdAKjyJ9PH7gcFCIOSJ4C72aytGrpWBpyFSNcRzRDMOAruuwbRv9Av1bMQwKMwtB2E0SqL6D6lpYu7MtCm0L+q54PYNDsZkdbdtCKfXfQxfKgs4OFV8kBIh3yFEYETH4issOyw4Kx/rz/dvDT+qw0TdnNDiVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;dms&quot;
        title=&quot;&quot;
        src=&quot;/static/5105fb2eca0a0be6a457e472a0a8a0d5/1cfc2/dms.png&quot;
        srcset=&quot;/static/5105fb2eca0a0be6a457e472a0a8a0d5/3684f/dms.png 225w,
/static/5105fb2eca0a0be6a457e472a0a8a0d5/fc2a6/dms.png 450w,
/static/5105fb2eca0a0be6a457e472a0a8a0d5/1cfc2/dms.png 900w,
/static/5105fb2eca0a0be6a457e472a0a8a0d5/00d43/dms.png 1000w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;DMS는 데이터 마이그레이션 서비스로, 다양한 소스 데이터베이스에서 AWS 클라우드로 데이터를 안전하고 쉽게 마이그레이션할 수 있도록 제공되는 서비스다.&lt;br&gt;
이관을 진행하기 위해서는 아래의 오브젝트를 준비해야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;소스 DB와 타겟 DB의 &lt;strong&gt;엔드포인트&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;이관을 진행할 때 사용될 &lt;strong&gt;복제 인스턴스&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;두 개의 엔드포인트와 복제 인스턴스를 사용하여 실제 이관할 이관 전략을 지정하는 &lt;strong&gt;데이터베이스 마이그레이션 태스크&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;제일 중요한 것은 &lt;strong&gt;데이터베이스 마이그레이션 태스크&lt;/strong&gt; 지정인데, 이와 관련해서 유의해야 할 점들을 살펴보자.&lt;/p&gt;
&lt;h2 id=&quot;적재-모드-선택과-데이터-검증&quot; style=&quot;position:relative;&quot;&gt;적재 모드 선택과 데이터 검증&lt;a href=&quot;#%EC%A0%81%EC%9E%AC-%EB%AA%A8%EB%93%9C-%EC%84%A0%ED%83%9D%EA%B3%BC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B2%80%EC%A6%9D&quot; aria-label=&quot;적재 모드 선택과 데이터 검증 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;초기 적재&lt;/strong&gt; 와 &lt;strong&gt;변경 사항 복제&lt;/strong&gt; 를 선택할 수 있으며 두 작업을 함께 진행하도록 할 수도 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;기존 데이터 마이그레이션(전체 로드만)&lt;/strong&gt; - 소스 엔드포인트에서 대상 엔드포인트로 한 번만 마이그레이션을 수행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기존 데이터 마이그레이션 및 진행 중인 변경 사항 복제(전체 로드 및 CDC)&lt;/strong&gt; - 소스에서 대상으로 한 번 마이그레이션을 수행한 다음, 소스에서 대상으로 데이터 변경 사항을 계속 복제&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 변경 사항만 복제(CDC만 해당)&lt;/strong&gt; - 일회성 마이그레이션을 수행하지 않고 소스에서 타깃으로 데이터 변경 사항을 계속 복제&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;변경 사항 복제는 초기 적재가 끝나면 소스 DB의 binlog를 기반으로 타겟 DB에 동기화 시키는 작업을 의미한다.&lt;br&gt;
해당 기능을 실행하기 위해서는 소스 DB의 binlog가 활성화되어 있어야 하며, &lt;a href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/mysql-stored-proc-configuring.html#mysql_rds_set_configuration-usage-notes.binlog-retention-hours&quot;&gt;binlog 보존 시간&lt;/a&gt;을 확인해야 한다.&lt;/p&gt;
&lt;p&gt;그리고 초기 적재가 끝나면 소스 DB와 타겟 DB의 &lt;strong&gt;데이터 검증&lt;/strong&gt; 작업도 지정할 수 있다.&lt;br&gt;
데이터 검증에 문제가 발생하면 어떤 문제가 발생했는지 &lt;code class=&quot;language-text&quot;&gt;awsdms_control.awsdms_validation_failures_v1&lt;/code&gt; 테이블에서 확인할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+----------+-----------------------+--------+-----------------------------------+--------------+
|TABLE_NAME|FAILURE_TIME           |KEY_TYPE|KEY                                |FAILURE_TYPE  |
+----------+-----------------------+--------+-----------------------------------+--------------+
|...       |2024-03-29 04:28:55.367|Row     |{ &amp;quot;key&amp;quot;:	[&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, ...] } |MISSING_TARGET|
|...       |2024-03-29 04:28:55.409|Row     |{ &amp;quot;key&amp;quot;:	[&amp;quot;key1&amp;quot;, &amp;quot;key2&amp;quot;, ...] } |MISSING_SOURCE|
+----------+-----------------------+--------+-----------------------------------+--------------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;어떤 DB에서 실패하였는지, 어떤 레코드인지 식별하기 위한 정보들이 모두 들어있다.&lt;br&gt;
실제로 이관이 끝나고 해당 로그를 기반으로 어떤 레코드에 문제가 있었는지 확인하기 편하다.&lt;/p&gt;
&lt;h2 id=&quot;파티셔닝-테이블-이관&quot; style=&quot;position:relative;&quot;&gt;파티셔닝 테이블 이관&lt;a href=&quot;#%ED%8C%8C%ED%8B%B0%EC%85%94%EB%8B%9D-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%9D%B4%EA%B4%80&quot; aria-label=&quot;파티셔닝 테이블 이관 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DMS는 분할된 테이블에 대한 DDL을 지원하지 않기 때문에 이관 대상 테이블에 파티셔닝된 테이블이 있다면, 대상 DB에 미리 생성해놓아야 한다.&lt;br&gt;
그리고 꼭 태스크 설정의 &lt;strong&gt;대상 테이블 준비 모드는 &lt;code class=&quot;language-text&quot;&gt;&quot;아무 작업 안함&quot;&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;&quot;자르기&quot;&lt;/code&gt;를 선택&lt;/strong&gt; 하여 이관 시작시 대상 DB의 메타데이터를 삭제하지 않도록 해야 한다.&lt;/p&gt;
&lt;h2 id=&quot;lob-컬럼&quot; style=&quot;position:relative;&quot;&gt;LOB 컬럼&lt;a href=&quot;#lob-%EC%BB%AC%EB%9F%BC&quot; aria-label=&quot;lob 컬럼 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이관 테이블 중 LOB 컬럼이 존재한다면 아래의 설정에 신경써야 한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/88f9cd68bc87c29ab03c7820276d6db9/eb3fa/lobColumn.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6ElEQVR42qVR0XKCMBDkXxwYCSIhBhKUJCgVtEiZ/v/HbC+p0/axgw87m73L7ezcRcP0gf42oT5ZMNODiRpsX4IVYhWi6/uM+/IJz2accGhabHO+3tCnu4x3jI8FmlLmQiLdi/WGt3nBMM0hYWPPL6ULhtXRwMPv0PMrZsHQvQ1QrYXtr5DNiYolMi5WI9J02VxoMnMo5BFxyhGz/yP5+85KRKxQ2Gw5NmlJRYE4O/ziqZOdpM8y6MTrZ/+Hqe/Z+0S1cdDuAk0HaYiV7aB8jXTdugDdUd99a2U6+JmKdh76Yf4cPHil8QX9sB0nFJE+mQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;lobColumn&quot;
        title=&quot;&quot;
        src=&quot;/static/88f9cd68bc87c29ab03c7820276d6db9/1cfc2/lobColumn.png&quot;
        srcset=&quot;/static/88f9cd68bc87c29ab03c7820276d6db9/3684f/lobColumn.png 225w,
/static/88f9cd68bc87c29ab03c7820276d6db9/fc2a6/lobColumn.png 450w,
/static/88f9cd68bc87c29ab03c7820276d6db9/1cfc2/lobColumn.png 900w,
/static/88f9cd68bc87c29ab03c7820276d6db9/eb3fa/lobColumn.png 1026w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;해당 컬럼의 최대 사이즈가 13kb인 것을 확인하여서 기본 값인 64kb를 넘지 않아 전체 LOB 모드로 실행하였다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;SELECT MAX(OCTET_LENGTH({lob_column})) / 1024 AS max_size_kb
FROM {table};&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;그리고 LOB 컬럼은 테이블에 데이터 INSERT 시점에 이관되지 않고 &lt;strong&gt;삽입된 레코드에 UPDATE로 이관&lt;/strong&gt;되기 때문에 만약 LOB 컬럼이 NOT NULL이라면 &lt;strong&gt;이관할 때에는 NULL을 허용하고 이관이 끝난 후에 제약조건을 추가&lt;/strong&gt; 해줘야 한다.&lt;/p&gt;
&lt;h2 id=&quot;이관-시-소스-엔드포인트-servertimezone-설정&quot; style=&quot;position:relative;&quot;&gt;이관 시 소스 엔드포인트 ServerTimeZone 설정&lt;a href=&quot;#%EC%9D%B4%EA%B4%80-%EC%8B%9C-%EC%86%8C%EC%8A%A4-%EC%97%94%EB%93%9C%ED%8F%AC%EC%9D%B8%ED%8A%B8-servertimezone-%EC%84%A4%EC%A0%95&quot; aria-label=&quot;이관 시 소스 엔드포인트 servertimezone 설정 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;소스 DB의 timezone이 UTC가 아니고 TIMESTAMPE 컬럼을 사용중이라면 소스 엔드포인트의 ServerTimeZone을 설정해줘야 한다.&lt;br&gt;
&lt;a href=&quot;https://repost.aws/knowledge-center/dms-migrate-mysql-non-utc&quot;&gt;How do I migrate a MySQL database in a non-UTC time zone using AWS DMS tasks?&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;mysql-버전업을-진행하면서-고려해야할-점&quot; style=&quot;position:relative;&quot;&gt;MySQL 버전업을 진행하면서 고려해야할 점&lt;a href=&quot;#mysql-%EB%B2%84%EC%A0%84%EC%97%85%EC%9D%84-%EC%A7%84%ED%96%89%ED%95%98%EB%A9%B4%EC%84%9C-%EA%B3%A0%EB%A0%A4%ED%95%B4%EC%95%BC%ED%95%A0-%EC%A0%90&quot; aria-label=&quot;mysql 버전업을 진행하면서 고려해야할 점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;예약어&quot; style=&quot;position:relative;&quot;&gt;예약어&lt;a href=&quot;#%EC%98%88%EC%95%BD%EC%96%B4&quot; aria-label=&quot;예약어 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;작성된 쿼리 중에 예약어가 포함되어 있는지 확인해야 한다. &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/keywords.html#keywords-8-0-detailed-C&quot;&gt;MySQL 예약어&lt;/a&gt;에서 사용 중인 예약어가 있는지 확인하는 것이 좋다.&lt;br&gt;
MySQL 8.0.2에 추가된 &lt;code class=&quot;language-text&quot;&gt;RANK&lt;/code&gt; 예약어가 포함되어 있어서 모두 백틱으로 처리해주었다.&lt;/p&gt;
&lt;p&gt;그리고 아래와 같이 Map의 Entry를 순회하며 생성하는 동적 쿼리에서 &lt;code class=&quot;language-text&quot;&gt;${key}&lt;/code&gt;에 예약어가 포함되는 경우가 있어서 해당 쿼리도 백틱으로 처리해주었다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;xml&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;&amp;lt;update id=&amp;quot;update...&amp;quot; parameterType=&amp;quot;java.util.Map&amp;quot;&amp;gt;
    UPDATE ...
    &amp;lt;foreach item=&amp;quot;value&amp;quot; index=&amp;quot;key&amp;quot; collection=&amp;quot;data.entrySet()&amp;quot; separator=&amp;quot;,&amp;quot;&amp;gt;
        `${key}` = #{value}
    &amp;lt;/foreach&amp;gt;
    WHERE ...
&amp;lt;/update&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;ssl-설정&quot; style=&quot;position:relative;&quot;&gt;SSL 설정&lt;a href=&quot;#ssl-%EC%84%A4%EC%A0%95&quot; aria-label=&quot;ssl 설정 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;각 애플리케이션에서 여러 DB를 사용하는데 DB 중에 MySQL 5.6이 포함되어 있어 해당 연결에서 &lt;code class=&quot;language-text&quot;&gt;useSSL=false&lt;/code&gt; 속성을 사용 중이였다.&lt;br&gt;
MySQL 5.6은 SSL 설정을 직접 해주어야 하는데 따로 설정하지 않고 사용 중이라서 해당 속성을 지정한 것 같다.&lt;/p&gt;
&lt;p&gt;이번에 추가한 AWS Aurora MySQL 8.0.28은 1.2와 1.3버전을 지원하고 Java 8부터 1.2를 지원하기 때문에 &lt;code class=&quot;language-text&quot;&gt;useSSL=false&lt;/code&gt; 속성을 모두 제거해도 무방하다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Java Version&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;SSL/TLS Default&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Other Supported Versions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Java 6&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;TLS 1.0&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;TLS 1.1 (update 111 and later), SSLv3.0*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Java 7&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;TLS 1.0&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;TLS 1.2, TLS 1.1, SSLv3.0*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Java 8&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;TLS 1.2&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;TLS 1.1, TLS 1.0, SSLv3.0*&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;show global variables like &amp;#39;%tls%&amp;#39;

+----------------------+---------------+
|Variable_name         |Value          |
+----------------------+---------------+
|admin_tls_ciphersuites|               |
|admin_tls_version     |TLSv1.2,TLSv1.3|
|aurora_zd_tls_v1_3    |ON             |
|tls_ciphersuites      |               |
|tls_version           |TLSv1.2,TLSv1.3|
+----------------------+---------------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;아래의 쿼리로 모든 연결이 SSL로 연결되었는지 확인할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;select b.processlist_user,
       b.processlist_id,
       b.connection_type,
       b.PROCESSLIST_HOST,
       a.ssl_version,
       a.ssl_cipher
from sys.session_ssl_status a join performance_schema.threads b
   on a.thread_id= b.thread_id
order by ssl_version desc;

+----------------+--------------+---------------+-----------+---------------------------+
|processlist_user|processlist_id|connection_type|ssl_version|ssl_cipher                 |
+----------------+--------------+---------------+-----------+---------------------------+
|user            |811513        |SSL/TLS        |TLSv1.3    |TLS_AES_256_GCM_SHA384     |
|user            |811738        |SSL/TLS        |TLSv1.3    |TLS_AES_256_GCM_SHA384     |
|user            |810957        |SSL/TLS        |TLSv1.2    |ECDHE-RSA-AES256-GCM-SHA384|
|user            |810965        |SSL/TLS        |TLSv1.2    |ECDHE-RSA-AES256-GCM-SHA384|
|user            |810970        |SSL/TLS        |TLSv1.2    |ECDHE-RSA-AES256-GCM-SHA384|
+----------------+--------------+---------------+-----------+---------------------------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;utf8mb3--utf8mb4&quot; style=&quot;position:relative;&quot;&gt;utf8mb3 → utf8mb4&lt;a href=&quot;#utf8mb3--utf8mb4&quot; aria-label=&quot;utf8mb3  utf8mb4 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;MySQL 8.0 부터는 기본적으로 utf8mb4를 지원하며 utf8mb3는 deprecate될 예정이므로 마이그레이션 하면서 테이블 collation을 변경하여야 한다.&lt;br&gt;
최대 문자 길이가 3바이트에서 4바이트로 더 많이 차지하기 때문에 확인할 것이 있다.&lt;/p&gt;
&lt;p&gt;첫 번째로 &lt;strong&gt;테이블의 컬럼 최대 사이즈를 확인&lt;/strong&gt; 하여야 한다.&lt;br&gt;
&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/char.html&quot;&gt;VARCHAR는 최대 65535 바이트를 저장&lt;/a&gt;할 수 있기에 최대 길이를 줄여야 한다.&lt;br&gt;
테이블에 &lt;code class=&quot;language-text&quot;&gt;VARCHAR(20000)&lt;/code&gt; 컬럼을 &lt;code class=&quot;language-text&quot;&gt;VARCHAR(15000)&lt;/code&gt;으로 수정하였다. 수정하기전에 당연히 해당 컬럼의 최대 길이를 확인해야 한다.&lt;/p&gt;
&lt;p&gt;두 번째로 최대 문자 수가 줄어들면서 인덱스의 사이즈도 줄어들기 때문에 &lt;strong&gt;인덱스 사이즈를 확인&lt;/strong&gt; 하여야 한다.&lt;br&gt;
ROW_FORMAT에 따라 최대 인덱스 사이즈가 다르기 때문에 ROW_FORMAT을 먼저 확인하여야 한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;SELECT TABLE_NAME, ROW_FORMAT
FROM INFORMATION_SCHEMA.TABLES

+----------+----------+
|TABLE_NAME|ROW_FORMAT|
+----------+----------+
|...       |Dynamic   |
|...       |Dynamic   |
|...       |Dynamic   |
+----------+----------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;Dynamic으로 구성되어 있는 경우 최대 3072바이트까지 가능하다.&lt;br&gt;
사용중인 모든 인덱스 구성의 컬럼 길이 합이 3072바이트를 초과하는지 확인하면 된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;SELECT
    S.TABLE_SCHEMA,
    S.TABLE_NAME,
    INDEX_NAME,
    SUM(COALESCE(SUB_PART, CHARACTER_MAXIMUM_LENGTH)) AS TOTAL_INDEX_LENGTH
FROM
    INFORMATION_SCHEMA.STATISTICS AS S JOIN INFORMATION_SCHEMA.COLUMNS AS C
        ON S.TABLE_SCHEMA = C.TABLE_SCHEMA
        AND S.TABLE_NAME = C.TABLE_NAME
        AND S.COLUMN_NAME = C.COLUMN_NAME
WHERE S.TABLE_SCHEMA = &amp;#39;business&amp;#39;
GROUP BY TABLE_SCHEMA,TABLE_NAME,INDEX_NAME;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-conversion.html&quot;&gt;Converting Between 3-Byte and 4-Byte Unicode Character Sets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-row-format.html#innodb-row-format-dynamic&quot;&gt;DYNAMIC Row Format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html&quot;&gt;InnoDB Limits&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;zerofill&quot; style=&quot;position:relative;&quot;&gt;ZEROFILL&lt;a href=&quot;#zerofill&quot; aria-label=&quot;zerofill permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;MySQL 8.0.17부터 숫자 데이터 유형에 대한 ZEROFILL 속성과 정수 데이터 유형에 대한 표시 너비 속성은 더 이상 사용되지 않기 때문에 테이블 DDL에 &lt;code class=&quot;language-text&quot;&gt;INT(4)&lt;/code&gt;와 같이 너비를 지정해주면 경고 메시지가 발생한다.&lt;br&gt;
추가로 FLOAT 및 DOUBLE 열에 대한 AUTO_INCREMENT 지원도 향후 MySQL 버전에서 제거될 예정이기 때무에 정수 유형으로 변환해야한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;참고&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/numeric-type-attributes.html&quot;&gt;Numeric Type Attributes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[DATETIME 필드와 TIMESTAMP 필드 차이]]></title><description><![CDATA[발단 얼마전에 IDC에서 운영 중이던 MySQL을 AWS Aurora로 데이터 이관 작업을 진행했었다. 기존에 사용하던 옵션들을 확인해서 패킷 바디 사이즈, 최대 커넥션 개수를 Aurora Cluster Parameter Group…]]></description><link>https://jdalma.github.io/2024y/troubleShooting/timezone/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/troubleShooting/timezone/</guid><pubDate>Wed, 01 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;발단&quot; style=&quot;position:relative;&quot;&gt;발단&lt;a href=&quot;#%EB%B0%9C%EB%8B%A8&quot; aria-label=&quot;발단 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;얼마전에 IDC에서 운영 중이던 MySQL을 AWS Aurora로 데이터 이관 작업을 진행했었다.&lt;br&gt;
기존에 사용하던 옵션들을 확인해서 패킷 바디 사이즈, 최대 커넥션 개수를 &lt;a href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_WorkingWithDBClusterParamGroups.html&quot;&gt;Aurora Cluster Parameter Group&lt;/a&gt;으로 재정의 했었다.&lt;/p&gt;
&lt;p&gt;이때 &lt;code class=&quot;language-text&quot;&gt;time_zone&lt;/code&gt;을 의식하지 못하여 설정해주지 않아.. 데이터가 UTC로 저장되어 사용자에게 노출되는 현재 시간 정보들은 9시간 이전의 정보로 노출되었다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;GLOBAL.time_zone&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;SESSION.time_zone&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;system_time_zone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;소스 DB&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;SYSTEM&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;SYSTEM&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;KST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;대상 DB&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;SYSTEM&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;SYSTEM&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;GLOBAL, SESSION time_zone 정보는 &lt;code class=&quot;language-text&quot;&gt;SYSTEM&lt;/code&gt;이라고 되어 있으면 &lt;code class=&quot;language-text&quot;&gt;system_time_zone&lt;/code&gt;과 동일하다는 의미이다.&lt;br&gt;
AWS DMS를 통한 이관은 &lt;strong&gt;SELECT (소스 엔드포인트)&lt;/strong&gt; → &lt;strong&gt;INSERT (타겟 엔드포인트)&lt;/strong&gt; 로 이루어지는데 KST 기준으로 저장된 정보를 UTC 기준으로 저장한 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;소스 DB의 timezone이 UTC가 아닐 때에는 AWS DMS의 소스 엔드포인트에 &lt;code class=&quot;language-text&quot;&gt;ServerTimezone&lt;/code&gt;을 설정하여야 한다. &lt;a href=&quot;https://repost.aws/knowledge-center/dms-migrate-mysql-non-utc&quot;&gt;참고&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;04.30 09:00:00&lt;/code&gt;까지 이관되었다고 가정하면 이관된 데이터는 KST 기준임에도 불구하고 UTC로 저장된 것이고, 이관 완료 후 오픈 시점 &lt;code class=&quot;language-text&quot;&gt;04.30 09:00:01&lt;/code&gt; 이후 정보들은 UTC로 저장된 것이다.&lt;/strong&gt;&lt;br&gt;
이관된 후에 대상 DB의 timezone을 &lt;code class=&quot;language-text&quot;&gt;Asia/Seoul&lt;/code&gt;로 변경하면 기존에 UTC로 저장된 데이터들을 KST로 계산하여 보여주기 때문에 이관된 데이터는 9시간 증가할 것이라고 이야기 들었다.&lt;br&gt;
예를 들어, 소스 DB에서 이관된 데이터는 &lt;code class=&quot;language-text&quot;&gt;04.30 01:00:00 KST&lt;/code&gt; 이지만 대상 DB에 저장될 때는 &lt;code class=&quot;language-text&quot;&gt;04.30 01:00:00 UTC&lt;/code&gt;로 저장된 것이기 때문에 이 시점부터 대상 DB의 timezone을 &lt;code class=&quot;language-text&quot;&gt;Asia/Seoul&lt;/code&gt;로 변경하면 기존 이관된 데이터들의 날짜:시간 정보들은 &lt;strong&gt;+9시간&lt;/strong&gt; 되어버린다는 것이다.&lt;/p&gt;
&lt;h1 id=&quot;두-필드의-차이점&quot; style=&quot;position:relative;&quot;&gt;두 필드의 차이점&lt;a href=&quot;#%EB%91%90-%ED%95%84%EB%93%9C%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot; aria-label=&quot;두 필드의 차이점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;UTC로 저장된 데이터가 timezone을 KST로 변경하면 9시간 누적되어서 출력될까?&quot;&lt;/code&gt; 궁금했다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;create table TIMEZONE_TEST
(
    id       int primary key,
    datetime     datetime     default CURRENT_TIMESTAMP not null,
    timestamp    timestamp     default CURRENT_TIMESTAMP not null
)

-- UTC 기준
+--------------------+---------------------+--------------------+
| @@GLOBAL.time_zone | @@SESSION.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM             | SYSTEM              | UTC                |
+--------------------+---------------------+--------------------+

mysql&amp;gt; select * from TIMEZONE_TEST;
+----+---------------------+---------------------+
| id | datetime            | timestamp           |
+----+---------------------+---------------------+
|  1 | 2024-05-01 10:07:30 | 2024-05-01 10:07:30 |
|  2 | 2024-05-01 10:07:33 | 2024-05-01 10:07:33 |
|  3 | 2024-05-01 10:07:37 | 2024-05-01 10:07:37 |
+----+---------------------+---------------------+

-- Asia/Seoul 기준 (UTC+9)
set time_zone = &amp;#39;Asia/Seoul&amp;#39;

+--------------------+---------------------+--------------------+
| @@GLOBAL.time_zone | @@SESSION.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM             | Asia/Seoul          | UTC                |
+--------------------+---------------------+--------------------+

mysql&amp;gt; select * from TIMEZONE_TEST;
+----+---------------------+---------------------+
| id | datetime            | timestamp           |
+----+---------------------+---------------------+
|  1 | 2024-05-01 10:07:30 | 2024-05-01 19:07:30 |
|  2 | 2024-05-01 10:07:33 | 2024-05-01 19:07:33 |
|  3 | 2024-05-01 10:07:37 | 2024-05-01 19:07:37 |
+----+---------------------+---------------------+

-- America/Anchorage 기준 (UTC-8)
set time_zone = &amp;#39;America/Anchorage&amp;#39;;

+--------------------+---------------------+--------------------+
| @@GLOBAL.time_zone | @@SESSION.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM             | America/Anchorage   | UTC                |
+--------------------+---------------------+--------------------+

mysql&amp;gt; select * from TIMEZONE_TEST;
+----+---------------------+---------------------+
| id | datetime            | timestamp           |
+----+---------------------+---------------------+
|  1 | 2024-05-01 10:07:30 | 2024-05-01 02:07:30 |
|  2 | 2024-05-01 10:07:33 | 2024-05-01 02:07:33 |
|  3 | 2024-05-01 10:07:37 | 2024-05-01 02:07:37 |
+----+---------------------+---------------------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;DATETIME&lt;/code&gt; 필드는 바뀌지 않지만 &lt;code class=&quot;language-text&quot;&gt;TIMESTAMP&lt;/code&gt; 필드만 DB 서버의 timezone 영향을 받는 것을 확인할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MySQL은 TIMESTAMP 값을 저장할 때 현재 표준 시간대에서 UTC로 변환하고, 검색할 때 UTC에서 현재 표준 시간대로 다시 변환합니다. (DATETIME과 같은 다른 유형에서는 이 작업이 수행되지 않습니다.)&lt;br&gt;
기본적으로 각 연결의 현재 표준 시간대는 서버의 시간입니다. 표준 시간대는 연결별로 설정할 수 있습니다. 표준 시간대 설정이 일정하게 유지되는 한 저장한 값과 동일한 값을 반환합니다.&lt;br&gt;
타임스탬프 값을 저장한 다음 표준 시간대를 변경하여 값을 검색하면 검색된 값이 저장한 값과 다릅니다.&lt;br&gt;
이는 &lt;strong&gt;양방향 변환&lt;/strong&gt; 에 동일한 표준 시간대가 사용되지 않았기 때문에 발생합니다. 현재 표준 시간대는 time_zone 시스템 변수의 값으로 사용할 수 있습니다. 자세한 내용은 섹션 7.1.15, &quot;MySQL 서버 표준 시간대 지원&quot;을 참조하세요.&lt;br&gt;
&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/datetime.html&quot;&gt;공식 문서 참고&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;결론&quot; style=&quot;position:relative;&quot;&gt;결론&lt;a href=&quot;#%EA%B2%B0%EB%A1%A0&quot; aria-label=&quot;결론 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이관 대상인 테이블을 모두 확인하여 &lt;code class=&quot;language-text&quot;&gt;TIMESTAMP&lt;/code&gt; 타입이 존재하는지 확인하였지만 &lt;code class=&quot;language-text&quot;&gt;DATETIME&lt;/code&gt; 필드만 사용되고 있었기에 데이터 재이관은 필요하지 않았고, &lt;strong&gt;데이터 수정만으로 이 문제를 해결할 수 있었다.&lt;/strong&gt;&lt;br&gt;
그래서 오픈 이후 적재된 데이터를 확인해보니 총 103만건(A 테이블 100만건, 그 외 합쳐서 3만건) 정도 확인되었다.&lt;br&gt;
새벽에 AWS 파라미터 그룹을 수정하고 인스턴스들을 재시작한 후에 일단 사용자에게 노출되는 정보인 3만건 먼저 업데이트하였으며 시간은 30초도 안걸렸다.&lt;br&gt;
그외 나머지 100만건은 파티셔닝된 테이블이기도 하고 데이터가 많아서 한 번에 기간으로 잡아서 업데이트하기에는 오래 걸릴 것으로 판단됐다.&lt;br&gt;
그래서 100만건의 프라이머리 키를 파일로 추출하여 10개의 스레드로 10만건씩 (한 번의 업데이트 쿼리에 10개의 레코드) 업데이트를 진행하여 10분만에 수정하였다.&lt;br&gt;
(동시에 유입된 쿼리는 최대 8개로 보인다.)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[테스트 불모지에 Testcontainers 심기]]></title><description><![CDATA[CI/CD를 고민하면서 서로 의존 관계에 있는 서비스들은 통합 테스트를 어떻게 진행할지 생각해본적이 있다.  위의 그림에서 A 서비스 를 수정하고 배포한다고 가정할 때 통합 테스트 환경에서 사용자의 요청을 처리하기 위해 이해관계에 놓여있는 B…]]></description><link>https://jdalma.github.io/2024y/testcontainers/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/testcontainers/</guid><pubDate>Sun, 07 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CI/CD를 고민하면서 서로 의존 관계에 있는 서비스들은 통합 테스트를 어떻게 진행할지 생각해본적이 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 773px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/da0ac8e27aa0636db1e07073b18aeda4/612f7/mocking.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABaUlEQVR42o2Th67CMAxF+/8fBxKIVQpllkKB7sW6z9cQVMETEMmqmjjH1yMWflx5nsNxHGw2G3ieh/l8jvV6rWe32+3pZ30DGeckSZ6gVquFNE31vwn7CfigIokiBRC83+8Rx/FT4Ufga0RdZYl0t4MzGCAIAjXCIwnyBrxcLgp5Ne6zbkVRoBRzRyNUoqoU+PF41Muu6yq8qir15ZnFQ26YwpuoTIe23W6xE3OnU2RSN/oY4OFwwEgCGdXj8RgWHa7XK7Isw2QywXA4VMhqtVJ4p9NRJaydCW6Cso70s21bJ2CxWMCiY13XCmWaxuhIAI0XAzYiDFE8wFyh/C+XSz0nR4FUdj6f35pCJb7v39MRxd12G7UoKwUSCoDrdDrdSyINo9H3+xw+vjG73O/Dly8DMQM24a3L/45JQ21zsAmicqaaS+ebL8XYzy+FjSCIY9Tr9XSPKr8O9qe3zG7OZjN9fuwqh/sV+Ad6CO3tvGsMGwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;mocking&quot;
        title=&quot;&quot;
        src=&quot;/static/da0ac8e27aa0636db1e07073b18aeda4/612f7/mocking.png&quot;
        srcset=&quot;/static/da0ac8e27aa0636db1e07073b18aeda4/3684f/mocking.png 225w,
/static/da0ac8e27aa0636db1e07073b18aeda4/fc2a6/mocking.png 450w,
/static/da0ac8e27aa0636db1e07073b18aeda4/612f7/mocking.png 773w&quot;
        sizes=&quot;(max-width: 773px) 100vw, 773px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위의 그림에서 &lt;strong&gt;A 서비스&lt;/strong&gt; 를 수정하고 배포한다고 가정할 때 통합 테스트 환경에서 사용자의 요청을 처리하기 위해 이해관계에 놓여있는 B 서비스는 모킹으로 처리하고 DB는 내장 DB를 사용하거나 모킹으로 처리할 것이다.&lt;br&gt;
개인적으로 모킹을 좋아하지 않는다. 필요하다면 모킹을 사용하긴 하지만 모킹 라이브러리를 추가하지 않기 위해 추상 계층, 위임을 활용하는 편이다.&lt;br&gt;
실제 실행환경이 아니기 때문에 신뢰가 가지 않는다고 생각한다.&lt;/p&gt;
&lt;p&gt;이번에 &lt;a href=&quot;https://jdalma.github.io/2024y/refactoring/refactoring1&quot;&gt;리팩토링&lt;/a&gt;을 진행하면서 통합 테스트를 작성하게 되었는데, 위와 같은 고민을 해소해주는 &lt;strong&gt;Testcontainers&lt;/strong&gt; 를 접하게 되었다.&lt;/p&gt;
&lt;h1 id=&quot;testcontainers란&quot; style=&quot;position:relative;&quot;&gt;Testcontainers란?&lt;a href=&quot;#testcontainers%EB%9E%80&quot; aria-label=&quot;testcontainers란 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;출처: &lt;a href=&quot;https://testcontainers.com/guides/introducing-testcontainers/&quot;&gt;What is Testcontainers, and why should you use it?&lt;/a&gt;, &lt;a href=&quot;https://testcontainers.com/getting-started/&quot;&gt;What is Testcontainers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;단위 테스트는 데이터베이스, 메시징 시스템 등과 같은 외부 서비스와 분리하여 비즈니스 로직 및 구현 세부 사항을 테스트하는 데 도움이 되지만, 애플리케이션 코드의 대부분은 여전히 이러한 외부 서비스와 통합되어 있을 수 있습니다.&lt;br&gt;
&lt;strong&gt;애플리케이션에 대한 완전한 확신을 가지려면 단위 테스트와 함께 통합 테스트를 작성하여 애플리케이션이 완벽하게 작동하는지 확인해야 합니다.&lt;/strong&gt;&lt;br&gt;
테스트 컨테이너는 데이터베이스, 메시지 브로커 등과 같은 &lt;strong&gt;애플리케이션 종속성을 Docker 컨테이너에서 실행하여 이러한 문제를 해결하고 실제 서비스와 대화하고 테스트 코드에 프로그래밍 방식의 API를 제공함으로써 안정적이고 반복 가능한 테스트를 실행할 수 있도록 도와줍니다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;테스트를 실행하는 환경에서 아래의 세 개 중 한 개만 준비되어 있으면 사용 가능하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Docker Desktop&lt;/li&gt;
&lt;li&gt;Docker Engine on Linux&lt;/li&gt;
&lt;li&gt;Testcontainers Cloud&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;흐름은 세 단계로 나뉜다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 820px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/03d8ef8e3709462dcc384bda0d75506e/9f82e/test-workflow.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABwklEQVR42lVSW2sTQRTeXy2+i88Bi2DV5KEgRVCsxhtWfTAIUrWpSStx3WZJE9sktjt7nfvM55mVFj3Dmct3zvedmcNEWSXnpwVHVXKf5wpFbsBygcUiQ1kKaO1QVxJNo8CUQVEprFYleYGTrKLcBnnBvW40eK3m0bIRbHiRQ3Ph68LifGmRpmsM3o+xOssQbL3McHFeIDUSZyS2//kYXz5N8DXLkVcNFZMenIpzyaJZssq2N1/h3kbf3+k8xvggaUWsDzkWXBqMhw0ORzWMtBDKUQxwlPPi4Qd0bz3FfeJ2bz/HaJhm0XGyZA+6b3DjWtff7ezg23AKyoeyBtq5Vnzwumnd0wiYdhbGOzx79BGbnSe4eb3nt3u7GO1PWSSkYYEUf595RT0K9mvqEI81kiON9IdGvyfIZbsPWIil8d9inEskk3m4A6QyLJLStI1yznln6TnWYneHYUbk09RivXA42hM43OPtPmCLxGL87jes1rDOw4UpCJJWZKxjl4JhLWuOrZcDqqZwaUnyE3E8uTpro/H2oA+hOf7lBq2obnRKF0PDtefCgFqAqlZ0NldeVoIw+R92Ql+sojVwauIGjaD1B09TUsDkPM/RAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;test workflow&quot;
        title=&quot;&quot;
        src=&quot;/static/03d8ef8e3709462dcc384bda0d75506e/9f82e/test-workflow.png&quot;
        srcset=&quot;/static/03d8ef8e3709462dcc384bda0d75506e/3684f/test-workflow.png 225w,
/static/03d8ef8e3709462dcc384bda0d75506e/fc2a6/test-workflow.png 450w,
/static/03d8ef8e3709462dcc384bda0d75506e/9f82e/test-workflow.png 820w&quot;
        sizes=&quot;(max-width: 820px) 100vw, 820px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;테스트 실행 전&lt;/strong&gt;: 테스트 컨테이너 API를 사용하여 필요한 서비스(데이터베이스, 메시징 시스템 등)를 Docker 컨테이너로 시작한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 실행 중&lt;/strong&gt;: 이러한 컨테이너화된 서비스를 사용하여 테스트를 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 실행 후&lt;/strong&gt;: 테스트가 성공적으로 실행되었는지 또는 실패가 있었는지 여부에 관계없이 Testcontainers가 컨테이너를 파기한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;즉, &lt;strong&gt;Dokcer 컨테이너를 코드 레벨에서 제어하여 테스트의 생명 주기에 맞게 컨테이너를 생성하고 종료하는 것이다.&lt;/strong&gt;&lt;br&gt;
현재 사내에서는 내부 Docker 레지스트리를 구축하여 모든 애플리케이션과 인프라를 이미지로 관리하고 있기 때문에 적용하기에 알맞다고 생각했다.&lt;/p&gt;
&lt;h1 id=&quot;testcontainers-사용해보기&quot; style=&quot;position:relative;&quot;&gt;Testcontainers 사용해보기&lt;a href=&quot;#testcontainers-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;testcontainers 사용해보기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;기존에는 필요한 인프라를 로컬에 Docker 컨테이너를 직접 실행시켜놓고 애플리케이션의 프로파일을 &lt;code class=&quot;language-text&quot;&gt;local&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt; 등으로 구분하여 사용 중이였다.&lt;br&gt;
먼저 local 테스트 환경에서 세션을 관리하는 Redis를 아래의 그림과 같이 Testcontainer를 적용해 보았다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f44f17c4985b6e3d9bba6f8435b118f2/8b640/testcontainers.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABpElEQVR42n1SPY8TMRD1P6GjouN30aAr7w9Q0VHxA2iQTkgoXUpEFKRQRQdNxO1mP+KNd2Nvdv21foydAHfiLk8azVozfvPmeRkIUwC0dpB1A2s9LiGEGOHJOqMyHH38vL7ClxfPwJeLU2WaHhKdMx81Pnz/gaY+gNcdnHUI1Dv5CZ7EsDgyEmafP+H27RtSuTsrCWdeavT+73kYDIQyGFsF2Q0QBwOjJ2gbsC8lmLUWQrRw/rSqlDLFo2sReRxe3HzE4uVz/Hr/LilXku7rFoL3YGVZIsZwPCIQKeccBZ2VUknder3GfD5PQyIsOVEtv2H5+hXqxVf0NCHPcniZoykVWJ7nqOsafX8i4HyXSIUQadXVaoXZbIau6xKhd9GvAOsdra9JNVKOVpjRgUl5IIUVeeBBvZDqiLIoEK148qUnj3a/h2gaGGPAG54ERZtYVCV7jX4MEL0nwy1Gesl/tj18lIhIstlssN1ukWVZylVV0b0RLDYOxsGQIq1qkn6En+7/KP/DOYeG1LVtm6z4k+NgFhuEHHF7V8EXS7h+BxcuE17Cb1mL/9Q119jbAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;testcontainers&quot;
        title=&quot;&quot;
        src=&quot;/static/f44f17c4985b6e3d9bba6f8435b118f2/1cfc2/testcontainers.png&quot;
        srcset=&quot;/static/f44f17c4985b6e3d9bba6f8435b118f2/3684f/testcontainers.png 225w,
/static/f44f17c4985b6e3d9bba6f8435b118f2/fc2a6/testcontainers.png 450w,
/static/f44f17c4985b6e3d9bba6f8435b118f2/1cfc2/testcontainers.png 900w,
/static/f44f17c4985b6e3d9bba6f8435b118f2/21482/testcontainers.png 1350w,
/static/f44f17c4985b6e3d9bba6f8435b118f2/8b640/testcontainers.png 1454w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;redis-컨테이너-생성하기&quot; style=&quot;position:relative;&quot;&gt;Redis 컨테이너 생성하기&lt;a href=&quot;#redis-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0&quot; aria-label=&quot;redis 컨테이너 생성하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// [3]
class RedisTestContainers: ApplicationContextInitializer&amp;lt;ConfigurableApplicationContext&amp;gt; {

    override fun initialize(applicationContext: ConfigurableApplicationContext) {
        redis.start()

        val redisContainerIP = &amp;quot;spring.redis.host=${redis.host}&amp;quot;
        val redisContainerPort = &amp;quot;spring.redis.port=${redis.getMappedPort(REDIS_PORT)}&amp;quot; // [2]

        TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
            applicationContext,
            redisContainerIP,
            redisContainerPort
        )
    }

    companion object {
        private const val REDIS_IMAGE = &amp;quot;redis:latest&amp;quot;
        private const val REDIS_PORT = 6379
        private val redis = RedisContainer(DockerImageName.parse(REDIS_IMAGE))
            .withExposedPorts(REDIS_PORT) // [1]
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[1]&lt;/code&gt; : Redis의 이미지 정보와 Redis의 컨테이너 내부 포트를 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[2]&lt;/code&gt; : Testcontainers를 통하여 컨테이너를 실행시키면 호스트의 &lt;strong&gt;랜덤 포트&lt;/strong&gt;로 컨테이너가 실행되기 때문에 Property를 동적으로 변경하기 위함이다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[3]&lt;/code&gt; : &lt;code class=&quot;language-text&quot;&gt;[2]&lt;/code&gt;번을 수행하기 위한 훅을 제공하는 콜백 인터페이스인 &lt;code class=&quot;language-text&quot;&gt;ApplicationContextInitializer&lt;/code&gt;를 구현하였다.
&lt;ul&gt;
&lt;li&gt;ApplicationContext가 준비되고 빈 정의가 로드되기 전에 ApplicationContextInitializer가 호출된다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-events-and-listeners&quot;&gt;Application Events and Listeners&lt;/a&gt;, &lt;a href=&quot;https://www.baeldung.com/spring-tests-override-properties#testPropertySourceUtils&quot;&gt;TestPropertySourceUtils&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;mysql-컨테이너-생성하기&quot; style=&quot;position:relative;&quot;&gt;MySQL 컨테이너 생성하기&lt;a href=&quot;#mysql-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0&quot; aria-label=&quot;mysql 컨테이너 생성하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class MysqlTestContainer: ApplicationContextInitializer&amp;lt;ConfigurableApplicationContext&amp;gt; {

    override fun initialize(applicationContext: ConfigurableApplicationContext) {
        mysql.start()

        // [1]
        val primaryUrl = &amp;quot;spring.datasource.primary.jdbc-url=${mysql.jdbcUrl}&amp;quot;
        val primaryUsername = &amp;quot;spring.datasource.primary.username=${mysql.username}&amp;quot;
        val primaryPassword = &amp;quot;spring.datasource.primary.password=${mysql.password}&amp;quot;

        val secondaryUrl = &amp;quot;spring.datasource.secondary.jdbc-url=${mysql.jdbcUrl}&amp;quot;
        val secondaryUsername = &amp;quot;spring.datasource.secondary.username=${mysql.username}&amp;quot;
        val secondaryPassword = &amp;quot;spring.datasource.secondary.password=${mysql.password}&amp;quot;

        TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
            applicationContext,
            primaryUrl,
            primaryPassword,
            primaryUsername,
            secondaryUrl,
            secondaryPassword,
            secondaryUsername
        )
    }

    companion object {
        private const val MYSQL_IMAGE = &amp;quot;mysql:8&amp;quot;
        private const val MYSQL_PORT = 3306
        private val mysql = MySQLContainer&amp;lt;Nothing&amp;gt;(MYSQL_IMAGE).apply {
            this.withExposedPorts(MYSQL_PORT)
            this.withInitScript(&amp;quot;init.sql&amp;quot;)     // [2] src/test/resources/init.sql
            this.withUsername(&amp;quot;test&amp;quot;)
            this.withPassword(&amp;quot;test&amp;quot;)
            this.withDatabaseName(&amp;quot;testdatabase&amp;quot;)
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;Redis 컨테이너 생성과 비슷하지만 다른 점은&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[1]&lt;/code&gt; : 테스트를 작성하는 애플리케이션은 읽기와 쓰기를 분리하는 환경이라서 primary와 secondary를 각각 지정하였다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[2]&lt;/code&gt; : 테스트에 필요한 스키마와 데이터를 준비하는 SQL문이 정의된 파일이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;통합-테스트-실행&quot; style=&quot;position:relative;&quot;&gt;통합 테스트 실행&lt;a href=&quot;#%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%A4%ED%96%89&quot; aria-label=&quot;통합 테스트 실행 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@SpringBootTest(&amp;quot;spring.profiles.active=local&amp;quot;)
@AutoConfigureMockMvc
@ContextConfiguration(initializers = [RedisTestContainer::class, MysqlTestContainer::class])
class IntegrationTest: FunSpec() {

    @Autowired
    private lateinit var redisTemplate: StringRedisTemplate
    @Autowired
    private lateinit var mockMvc: MockMvc

    private val key = &amp;quot;spring:session:sessions:expires:test&amp;quot;
    private val value = &amp;quot;{\&amp;quot;username\&amp;quot;:\&amp;quot;hjjeong\&amp;quot;}&amp;quot;
    private val cookie = Cookie(&amp;quot;SESSION&amp;quot;, &amp;quot;test&amp;quot;)

    init {
        extension(SpringExtension)

        beforeSpec {
            redisTemplate.opsForValue().set(key, value)
        }

        test(&amp;quot;GET ...&amp;quot;) {
            val mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(&amp;quot;{url}&amp;quot;).cookie(cookie))
                .andExpect { status().isOk }
                .andDo(MockMvcResultHandlers.print())
                .andReturn()
            // ...
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 &lt;code class=&quot;language-text&quot;&gt;test&lt;/code&gt;를 실행하면 Redis 컨테이너와 MySQL 컨테이너가 실행되고 테스트가 실행되기 전에 세션을 준비하고 통합 테스트가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fd0a954e95f0b58b3eb759a7a45fc915/cc6fe/containers.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 16.444444444444446%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAApUlEQVR42jWOzQ6CMBCEeQ9pCxqkQEv5iWBb1HhQE/X9X2fcFjxMZg8z304iZg9pn6j9B9lwBzcX8NZt8uBqBtcuioVbGsjBQlQd0qNGRp6rE5heswmfPEr7IOAbWX9DWihSs3kN1kwrrDQEaMHJZX+GkD12hcZejcj1FIEhm/B5gXQv6OsXgj6Fr6HMyhaMAEzZGEypHIGyQzU6WrgCD3+gWeLCH+t8WmnOKnGjAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;containers&quot;
        title=&quot;&quot;
        src=&quot;/static/fd0a954e95f0b58b3eb759a7a45fc915/1cfc2/containers.png&quot;
        srcset=&quot;/static/fd0a954e95f0b58b3eb759a7a45fc915/3684f/containers.png 225w,
/static/fd0a954e95f0b58b3eb759a7a45fc915/fc2a6/containers.png 450w,
/static/fd0a954e95f0b58b3eb759a7a45fc915/1cfc2/containers.png 900w,
/static/fd0a954e95f0b58b3eb759a7a45fc915/21482/containers.png 1350w,
/static/fd0a954e95f0b58b3eb759a7a45fc915/d61c2/containers.png 1800w,
/static/fd0a954e95f0b58b3eb759a7a45fc915/cc6fe/containers.png 1860w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;컨테이너/네트워크/볼륨/이미지를 제거할 수 있도록 도와주는 &lt;code class=&quot;language-text&quot;&gt;ryuk&lt;/code&gt; 컨테이너와 지정한 &lt;code class=&quot;language-text&quot;&gt;redis&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;mysql&lt;/code&gt; 컨테이너가 생성되었다.&lt;br&gt;
host 포트는 모두 랜덤 포트로 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;로그도 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- 스프링 테스트 로그 --
org.testcontainers.DockerClientFactory   : Docker host IP address is localhost
org.testcontainers.DockerClientFactory   : Connected to docker: 
  Server Version: 24.0.2
  API Version: 1.43
  Operating System: Docker Desktop
  Total Memory: 7851 MB
Container testcontainers/ryuk:0.6.0 is starting
Container testcontainers/ryuk:0.6.0 started in PT1.175S
Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
Checking the system...
✔︎ Docker server version should be at least 1.6.0
Creating container for image: redis:latest
Container redis:latest is starting
Container redis:latest started in PT0.206S
Creating container for image: mysql:8
Container mysql:8 is starting
Waiting for database connection to become available at jdbc:mysql://localhost:61593/testdatabase using query &amp;#39;SELECT 1&amp;#39;
Container mysql:8 started in PT7.378S
Container is started (JDBC URL: jdbc:mysql://localhost:61593/testdatabase)
Executing database script from init.sql
Executed database script from init.sql in 157 ms.

-- 도커 이벤트 로그 --
container create (image=testcontainers/ryuk:0.6.0 ...)
network connect (name=bridge, type=bridge)
container start (image=testcontainers/ryuk:0.6.0 ...)
volume create (driver=local)
container create (image=redis:latest ...)
network connect (name=bridge, type=bridge)
volume mount (destination=/data, driver=local, propagation=, read/write=true)
container start (image=redis:latest ...)

[테스트 진행]

container kill (image=testcontainers/ryuk:0.6.0 ...)
container kill (image=redis:latest ...)
network disconnect (name=bridge, type=bridge)
volume unmount (driver=local)
container die (image=redis:latest ...)
volume destroy (driver=local)
container destroy (image=redis:latest ...)
network disconnect (name=bridge, type=bridge)
container die (image=testcontainers/ryuk:0.6.0 ...)
container destroy (image=testcontainers/ryuk:0.6.0 ...)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;컨테이너 생성 후 초기 SQL 스크립트가 실행되고 테스트가 끝나면 컨테이너가 소멸되는 것들을 볼 수 있다.&lt;/p&gt;
&lt;h1 id=&quot;정리&quot; style=&quot;position:relative;&quot;&gt;정리&lt;a href=&quot;#%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;정리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;테스트 코드로 필요한 Docker 컨테이너를 생성하여 운영과 동일한 환경에서 테스트가 가능한 것을 확인할 수 있다.&lt;br&gt;
이 글에서는 Redis와 MySQL을 대체하였지만 &lt;strong&gt;실제 의존하고 있는 서비스들을 컨테이너로 실행시켜서 운영과 동일한 환경을 세팅하여 테스트를 진행할 수 있다는 것이 큰 장점으로 보인다.&lt;/strong&gt;&lt;br&gt;
테스트가 전혀 작성되어 있지 않은 환경에서 Testcontainers를 적용해보았다. 팀원들을 설득시키기 충분할 정도로 강력한 도구로 보이며 다음에는 Testcontainers를 적용한 실제 통합 테스트 작성기를 쓰도록 노력해봐야겠다.&lt;/p&gt;
&lt;p&gt;사용 사례는 &lt;a href=&quot;https://helloworld.kurly.com/blog/delivery-testContainer-apply/&quot;&gt;컬리의 TestContainers로 유저시나리오와 비슷한 통합테스트 만들어 보기&lt;/a&gt;를 참고하라.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[빈 후처리기를 이용한 프록시 생성에 대해]]></title><description><![CDATA[스프링의 프록시 방식으로 AOP를 생성하는 가장 기본적인 방법을 이해하기 위해서는 사전지식이 필요하다. Reflection Java Dynamic Proxy CGLIB ProxyFactory FactoryBean ProxyFactoryBean…]]></description><link>https://jdalma.github.io/2024y/postprocessor/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/postprocessor/</guid><pubDate>Sun, 17 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;스프링의 프록시 방식으로 AOP를 생성하는 가장 기본적인 방법을 이해하기 위해서는 사전지식이 필요하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reflection&lt;/li&gt;
&lt;li&gt;Java Dynamic Proxy&lt;/li&gt;
&lt;li&gt;CGLIB&lt;/li&gt;
&lt;li&gt;ProxyFactory&lt;/li&gt;
&lt;li&gt;FactoryBean&lt;/li&gt;
&lt;li&gt;ProxyFactoryBean&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;스프링을 더 깊게 이해하기 위해서는 필수적인 선수 지식이라고 생각한다.&lt;/strong&gt;&lt;br&gt;
이 글에서는 스프링 AOP를 사용하는 법, 포인트 컷, 어드바이스를 자세하게 설명하진 않으며, 스프링의 빈 후처리기를 통해 프록시가 어떻게 자동으로 생성되는지를 중점으로 다룬다.&lt;br&gt;
이 글을 통해 프록시가 무엇인지, 프록시를 관리하고 생성할 때 불편한 방법들을 스프링은 어떻게 해결했는지 알 수 있을 것이다.&lt;br&gt;
차근차근 단계별로 알아보자.&lt;/p&gt;
&lt;h1 id=&quot;1단계-java-reflection&quot; style=&quot;position:relative;&quot;&gt;1단계: Java Reflection&lt;a href=&quot;#1%EB%8B%A8%EA%B3%84-java-reflection&quot; aria-label=&quot;1단계 java reflection permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;자바의 리플렉션(Reflection)은 런타임 시에 클래스의 속성, 메소드, 생성자 등의 메타데이터를 조회하여 활용할 수 있는 강력한 기능이다.&lt;br&gt;
즉 &lt;strong&gt;런타임 시에 동적으로 객체를 생성하고, 가시성에 관계없이 메소드를 호출하거나, 필드에 접근할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.22222222222222%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAECBAX/xAAUAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHWcbIIYv8A/8QAGhAAAgIDAAAAAAAAAAAAAAAAAQMQETEyM//aAAgBAQABBQJmizaxg9o//8QAFhEAAwAAAAAAAAAAAAAAAAAAEBFB/9oACAEDAQE/Aax//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGhAAAQUBAAAAAAAAAAAAAAAAEAABAhEhQf/aAAgBAQAGPwKWquhj/8QAGxABAAICAwAAAAAAAAAAAAAAAQARECExQYH/2gAIAQEAAT8hd0p8lQGgdkCbNs1M4rP/2gAMAwEAAgADAAAAEI8v/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAEDAQE/EE2H/8QAFhEBAQEAAAAAAAAAAAAAAAAAAQAR/9oACAECAQE/EAC2/8QAHBABAQACAgMAAAAAAAAAAAAAAREAITGBEEFR/9oACAEBAAE/EEGwahE3995T0cV9PcuAAQ5Ql6wACKkOFbcNaPH/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;javalang&quot;
        title=&quot;&quot;
        src=&quot;/static/ba3e3f3cd9ce3e367559bba7dca13048/8e1fc/javalang.jpg&quot;
        srcset=&quot;/static/ba3e3f3cd9ce3e367559bba7dca13048/863e1/javalang.jpg 225w,
/static/ba3e3f3cd9ce3e367559bba7dca13048/20e5d/javalang.jpg 450w,
/static/ba3e3f3cd9ce3e367559bba7dca13048/8e1fc/javalang.jpg 900w,
/static/ba3e3f3cd9ce3e367559bba7dca13048/a2510/javalang.jpg 1000w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;
&lt;a href=&quot;https://www.geeksforgeeks.org/reflection-in-java/&quot;&gt;출처 geeksforgeeks&lt;/a&gt;&lt;/p&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;/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&gt;이 리플렉션을 사용하는 것은 &lt;strong&gt;메타 프로그래밍&lt;/strong&gt; 이라고 하며 위와 같은 행위를 할 수 있다.&lt;br&gt;
자바의 모든 클래스는 그 클래스 자체의 구성 정보를 담은 &lt;code class=&quot;language-text&quot;&gt;Class 타입&lt;/code&gt;의 오브젝트를 하나씩 갖고 있다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;Class&lt;/code&gt; 오브젝트를 이용하면 &lt;strong&gt;클래스 코드에 대한 메타 정보&lt;/strong&gt; 를 가져오거나 &lt;strong&gt;객체를 조작&lt;/strong&gt; 할 수 있다.&lt;br&gt;
테스트 코드로 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;data class Address(
    private val state: String,
    private val city: String
)

data class Person(
    private val name: String,
    var age: Int,
    private val address: Address
) {
    constructor(name: String, age: Int): this(name, age, Address(&amp;quot;empty&amp;quot;, &amp;quot;empty&amp;quot;))

    @Throws(IllegalArgumentException::class)
    constructor(age: Int): this(&amp;quot;Admin&amp;quot;, age, Address(&amp;quot;Admin&amp;quot;, &amp;quot;Admin&amp;quot;))

    fun greeting() = &amp;quot;안녕하세요.&amp;quot;
}

describe(&amp;quot;Person 클래스&amp;quot;) {
    val personClass: Class&amp;lt;Person&amp;gt; = Person::class.java

    it(&amp;quot;Constructor를 통해 Person 만들기&amp;quot;) {
        val constructors: Array&amp;lt;Constructor&amp;lt;Person&amp;gt;&amp;gt;  = personClass.constructors as Array&amp;lt;Constructor&amp;lt;Person&amp;gt;&amp;gt;
        constructors[0].toString() shouldBe 
            &amp;quot;public _22_Reflection.ReflectionTest\$1\$Person(java.lang.String,int)&amp;quot;
        constructors[1].toString() shouldBe 
            &amp;quot;public _22_Reflection.ReflectionTest\$1\$Person(int) throws java.lang.IllegalArgumentException&amp;quot;
        
        val nameAndAgeConstructor = constructors[0]
        val ageConstructor = constructors[1]

        nameAndAgeConstructor.newInstance(&amp;quot;Reflection&amp;quot;, 10) shouldBe Person(&amp;quot;Reflection&amp;quot;, 10)
        ageConstructor.newInstance(10) shouldBe Person(10)
    }

    it(&amp;quot;Field로 객체 내부 멤버변수 조회하기&amp;quot;) {
        val fields: Array&amp;lt;Field&amp;gt; = personClass.declaredFields
        fields[0].toString() shouldBe 
            &amp;quot;private final java.lang.String _22_Reflection.ReflectionTest$1\$Person.name&amp;quot;
        fields[1].toString() shouldBe 
            &amp;quot;private int _22_Reflection.ReflectionTest$1\$Person.age&amp;quot;
        fields[2].toString() shouldBe 
            &amp;quot;private final _22_Reflection.ReflectionTest$1\$Address _22_Reflection.ReflectionTest$1\$Person.address&amp;quot;

        val person = Person(100)

        fields.forEach { it.trySetAccessible() }

        val nameField = fields[0]
        val ageField = fields[1]
        val addressField = fields[2]

        nameField.get(person) shouldBe &amp;quot;Admin&amp;quot;
        ageField.get(person) shouldBe 100
        addressField.get(person) shouldBe Address(&amp;quot;Admin&amp;quot;, &amp;quot;Admin&amp;quot;)
    }

    it(&amp;quot;Method 정보로 객체 실행하기&amp;quot;) {
        val methods: Array&amp;lt;Method&amp;gt; = personClass.methods

        val getAgeMethod = methods.find { it.name == &amp;quot;getAge&amp;quot; }!!
        val setAgeMethod = methods.find { it.name == &amp;quot;setAge&amp;quot; }!!
        val greetingMethod = methods.find { it.name == &amp;quot;greeting&amp;quot; }!!

        val person = Person(100)

        person.age shouldBe 100
        getAgeMethod.invoke(person) shouldBe 100

        setAgeMethod.invoke(person, 50)

        person.age shouldBe 50
        getAgeMethod.invoke(person) shouldBe 50

        greetingMethod.invoke(person) shouldBe &amp;quot;안녕하세요.&amp;quot;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 &lt;code class=&quot;language-text&quot;&gt;Class&lt;/code&gt; 정보를 이용하여 실제 인스턴스를 조작하거나 생성할 수 있다. (더 자세한 예제는 &lt;a href=&quot;https://github.com/jdalma/kotlin-playground/blob/main/src/test/kotlin/_22_Reflection/ReflectionTest.kt&quot;&gt;여기&lt;/a&gt;를 확인하자.)&lt;/p&gt;
&lt;p&gt;리플렉션을 간단하게 확인해보았는데 강력한 기능이라는 것을 느낄 수 있다.&lt;br&gt;
이 강력한 기능을 통해 자바에서는 &lt;strong&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html&quot;&gt;Dynamic Proxy Class API&lt;/a&gt;&lt;/strong&gt; 를 제공한다.&lt;br&gt;
다이나믹 프록시에 대해 확인하기 전에 먼저 프록시에 대해 알아보자.&lt;/p&gt;
&lt;h1 id=&quot;2단계-proxy란&quot; style=&quot;position:relative;&quot;&gt;2단계: Proxy란?&lt;a href=&quot;#2%EB%8B%A8%EA%B3%84-proxy%EB%9E%80&quot; aria-label=&quot;2단계 proxy란 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;단순히 확장성을 고려해서 한 가지 기능을 분리한다면 아래와 같이 전형적인 전략 패턴(&lt;code class=&quot;language-text&quot;&gt;condition: (Member) -&gt; Boolean&lt;/code&gt;)을 사용할 수 있을 것이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun register(member: Member, condition: (Member) -&amp;gt; Boolean) {
    if(condition(member)) {
        // business logic
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;하지만 여러 곳에서 동일한 기능이 필요하다면 아주 번거로워질 것이다. 대표적으로 옛날 방식의 커넥션 관리가 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public void accountTransfer(String fromId, String toId, int money) throws SQLException {
    Connection con = dataSource.getConnection();
    try {
        con.setAutoCommit(false);
        bizLogic(con, fromId, toId, money); // 핵심 기능
        con.commit();
    } catch (Exception e) {
        con.rollback();
        throw new IllegalStateException(e);
    } finally {
        release(con);
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;핵심 기능인 &lt;code class=&quot;language-text&quot;&gt;bizLogic()&lt;/code&gt;을 실행시키기 위한 부가기능이 많이 존재한다.&lt;br&gt;
이 문제를 &lt;strong&gt;위임&lt;/strong&gt; 을 통해 해결하면 어떨까?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 574px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8218e1b1fc9598014689df3fa9c112af/86389/proxy.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 76.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACTElEQVR42o2Ue2/aMBTF+f4fYf9smjRNk6ZO6ya1GqylokAoCRCgaEB5bQyVlkdGbMd2Es6u3U6l60O90pHj6OaXe31PksEjsd1u7drtdpHP55HNZjGdTu39OI4fVZIkds3giTAJURRBa20TDczcM/uHUpSjbf6TQAMxySYWiwW6vQHW68DuU4LfNnEvpJTPV/gPuF4t0W2d4HLiIhUTbOVP0i9oNoLeDK22MYdS+mXAhI+wZRWAe9DLEsY/9jEhJRsHEC4QOojXHmQkkEnT1J7PrkwYmFLKXqugSUB6MPKhFg78w7fwvr4B7+WhBgXIUQHJqoxIhMgEQYAwDO9N9/8KxdLH/OIQV+Msrr0DxNUzKBIvFcGKJYSnBfDBEaRiyCyXSzDGrMz1arWiw19jMBjQfoHL+TV6/gHalfdwjl+j/OEV0paPuFlHYtZWA5F7Btb7fgMUQthKjOf6/T6GwyFGoxHa7Tbq9TouLoaolfYw9D9h3PmC8ckelOdhkj3E7CiHq8IJpFu9A85mM2uLzWZjfWRGb2ReZCrWcYpgVkX4Ow9JA9k0c1SRC+f9O/h7H9HZ/wxdcxH2cjdA06Jp97kp67BDk6ShiBp4h8AETNs+kmaDWm9AVCtg/dsKX2IbHZwDfyoWys+PoWoeonoVsu5CNsguu2e4a5WHwDvbWCDzLJAXHfCyA1GuWLEiTbtPLcvwZRXGYko+K1KFVcTzU7B2jsBHtzoG63wjY7eoQvX8t2yGdPMXSSD5AlE4g+KXZPg5lNzVFeVpcCHwF+EObsU94YOyAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;proxy&quot;
        title=&quot;&quot;
        src=&quot;/static/8218e1b1fc9598014689df3fa9c112af/86389/proxy.png&quot;
        srcset=&quot;/static/8218e1b1fc9598014689df3fa9c112af/3684f/proxy.png 225w,
/static/8218e1b1fc9598014689df3fa9c112af/fc2a6/proxy.png 450w,
/static/8218e1b1fc9598014689df3fa9c112af/86389/proxy.png 574w&quot;
        sizes=&quot;(max-width: 574px) 100vw, 574px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface UserService {
    fun accountTransfer(fromId: String, toId: String, money: Int)
}

class UserServiceProxy (
    private val userServiceTarget: UserService
): UserService {
    override fun accountTransfer(fromId: String, toId: String, money: Int) {
        // 부가기능
        userServiceTarget.accountTransfer(...)
        // 부가기능
    }
}

class UserServiceTarget: UserService {
    override fun accountTransfer(fromId: String, toId: String, money: Int) {
        // 핵심 기능
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위임을 통해 부가기능은 마치 &lt;strong&gt;자신이 핵심 기능 클래스인 것처럼 꾸며서, 클라이언트가 자신을 거쳐서 핵심 기능을 사용하도록 만드는 것이다.&lt;/strong&gt;&lt;br&gt;
클라이언트는 인터페이스를 통해서만 핵심 기능을 사용하게 하여, 부가기능은 자신도 같은 인터페이스를 구현한 뒤에 자신이 그 사이에 끼어들어 타깃을 직접 제어하는 것이다.&lt;/p&gt;
&lt;p&gt;이렇게 마치 &lt;code class=&quot;language-text&quot;&gt;자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 것&lt;/code&gt;을 대리자,대리인 과 같은 역할을 한다고 해서 &lt;strong&gt;프록시&lt;/strong&gt; 라고 부르며, 프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를 &lt;strong&gt;타깃&lt;/strong&gt; 이라고 부른다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;타깃과 동일한 인터페이스를 구현하고&lt;/li&gt;
&lt;li&gt;클라이언트와 타깃 사이에 존재하면서&lt;/li&gt;
&lt;li&gt;기능의 부가 또는 접근 제어를 담당하면 모두 &lt;strong&gt;프록시&lt;/strong&gt; 라고 볼 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 &lt;strong&gt;프록시의 사용 목적에 따라 디자인 패턴에서는 다른 패턴으로 구분된다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;데코레이터 패턴&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;타깃에 부가적인 기능을 런타임에 동적으로 부여하기 위해 프록시를 사용하는 패턴을 말한다.&lt;/strong&gt;&lt;br&gt;
대표적으로 &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4/%EA%B5%AC%EC%A1%B0_%EA%B4%80%EB%A0%A8.md#%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0-%ED%8C%A8%ED%84%B4&quot;&gt;자바 IO패키지의 InputStream 구현 클래스&lt;/a&gt;이다.&lt;/p&gt;
&lt;p&gt;프록시로서 동작하는 각 데코레이터는 위임하는 대상에도 인터페이스로 접근하기 때문에 자신이 최종 타깃으로 위임하는지, 아니면 다음 단계의 데코레이터 프록시로 위임하는지 알지 못한다.&lt;br&gt;
그래서 데코레이터의 다음 위임 대상은 인터페이스로 선언하고 생성자나 수정자 메소드를 통해 위임 대상을 외부에서 런타임 시에 주입받을 수 있도록 만들어야 한다.&lt;br&gt;
프록시가 꼭 한 개로 제한되지 않으며, 여러 개의 프록시를 &lt;strong&gt;순서를 정해서 단계적으로 위임하는 구조로 만들어야 한다.&lt;/strong&gt;&lt;br&gt;
데코레이터는 스스로 존재할 수 없고 항상 꾸며줄 대상이 있어야 하며 &lt;strong&gt;타겟에 대한 기능을 확장시키는 개념이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 &lt;code class=&quot;language-text&quot;&gt;UserServiceTarget&lt;/code&gt;에 부가기능을 제공하는 &lt;code class=&quot;language-text&quot;&gt;UserServiceProxy&lt;/code&gt;를 추가한 것도 데코레이터 패턴을 적용했다고 볼 수 있다.&lt;/p&gt;
&lt;h3&gt;프록시 패턴&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;클라이언트와 타깃 사이에서 대리 역할을 맡는 오브젝트를 두는 방법을 총칭하는 것이다.&lt;/strong&gt;&lt;br&gt;
디자인 패턴에서 말하는 프록시 패턴은 &lt;strong&gt;프록시를 사용하는 방법 중에 타깃에 대한 접근 방법을 제어하려는 목적을 가진 경우&lt;/strong&gt; 를 가리킨다.&lt;br&gt;
즉, 프록시 패턴의 프록시는 타깃의 기능을 확장하거나 추가하지 않고 클라이언트가 타깃에 대한 접근 권한을 제어하거나 접근하는 방식을 변경해주는 것이다.&lt;/p&gt;
&lt;p&gt;위에서 봤던 &lt;strong&gt;실제 타깃 오브젝트를 만드는 대신 프록시를 넘겨주는 것, 그리고 프록시의 메소드를 통해 타깃을 사용하려고 시도하면, 그떄 프록시가 타깃 오브젝트를 (생성하고) 요청을 위임해주는 식이다.&lt;/strong&gt;&lt;br&gt;
대표적으로 &lt;code class=&quot;language-text&quot;&gt;Collections.unmodifiableCollection()&lt;/code&gt;을 통해 만들어지는 객체가 전형적인 접근 권한 제어용 프록시라고 볼 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public static &amp;lt;T&amp;gt; Collection&amp;lt;T&amp;gt; unmodifiableCollection(Collection&amp;lt;? extends T&amp;gt; c) {
    if (c.getClass() == UnmodifiableCollection.class) {
        return (Collection&amp;lt;T&amp;gt;) c;
    }
    return new UnmodifiableCollection&amp;lt;&amp;gt;(c);
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;이렇게 프록시 패턴은 타깃의 기능 자체에는 관여하지 않으면서 접근하는 방법을 제어해주는 프록시를 &lt;code class=&quot;language-text&quot;&gt;이용&lt;/code&gt;하는 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;위임을 통한 프록시를 생성하여 부가기능과 핵심 기능을 분리했지만 프록시를 만드는 일은 번거롭고 타깃 인터페이스에 의존적이여서 타깃이 여러 개라면 인터페이스 API가 수정될 수 밖에 없을 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;3단계-proxy-생성-방법&quot; style=&quot;position:relative;&quot;&gt;3단계: Proxy 생성 방법&lt;a href=&quot;#3%EB%8B%A8%EA%B3%84-proxy-%EC%83%9D%EC%84%B1-%EB%B0%A9%EB%B2%95&quot; aria-label=&quot;3단계 proxy 생성 방법 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;java-dynamic-proxy&quot; style=&quot;position:relative;&quot;&gt;Java Dynamic Proxy&lt;a href=&quot;#java-dynamic-proxy&quot; aria-label=&quot;java dynamic proxy permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;일일이 프록시 클래스를 정의하는 것은 한계가 있다는 것을 알 수 있다.&lt;br&gt;
이 한계를 &lt;strong&gt;프록시처럼 동작하는 오브젝트를 동적으로 생성하는 JDK의 다이내믹 프록시&lt;/strong&gt; 와 &lt;strong&gt;리플렉션&lt;/strong&gt; 으로 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;이 프록시는 두 가지 기능으로 구성된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;타깃과 같은 메소드를 구현하고 있다가 메소드가 호출되면 타깃 오브젝트로 위임한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지정된 요청에 대해서는 부가기능을 수행한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다이나믹 프록시를 이용해 프록시를 만들어보자.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Hello&lt;/code&gt; 인터페이스를 구현한 프록시를 만들어보자.&lt;br&gt;
프록시에는 데코레이터 패턴을 적용해서 타깃인 &lt;code class=&quot;language-text&quot;&gt;HelloTarget&lt;/code&gt;에 부가기능을 추가해보자.&lt;br&gt;
리턴하는 문자를 모두 대문자로 바꾸는 부가기능을 만들어보자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Hello {
    fun sayHello(name: String): String
    fun sayHi(name: String): String
    fun sayThankYou(name: String): String
}
interface Goodbye {
    fun sayGoodbye(name: String): String
    fun sayThankYou(name: String): String
}
class HelloTarget: Hello {
    override fun sayHello(name: String): String = &amp;quot;Hello $name&amp;quot;
    override fun sayHi(name: String): String = &amp;quot;Hi $name&amp;quot;
    override fun sayThankYou(name: String): String = &amp;quot;Hello Thank You $name&amp;quot;
}

class GoodbyeTarget: Goodbye {
    override fun sayGoodbye(name: String): String = &amp;quot;Goodbye $name&amp;quot;
    override fun sayThankYou(name: String): String = &amp;quot;Goodbye Thank You $name&amp;quot;
}

class UppercaseHandler(
    helloTarget: Hello,
    goodbyeTarget: Goodbye
): InvocationHandler {

    private val lookupTarget = mapOf(
        Hello::class.java to helloTarget,
        Goodbye::class.java to goodbyeTarget,
    )

    override fun invoke(proxy: Any, method: Method, args: Array&amp;lt;out Any&amp;gt;): Any {
        val result: String = method.invoke(lookupTarget[method.declaringClass], *args) as String
        return result.uppercase()
    }
}

&amp;quot;Hello 동적 프록시 테스트하기&amp;quot; {
    val proxy = Proxy.newProxyInstance(
        javaClass.classLoader,
        arrayOf(Hello::class.java, Goodbye::class.java),
        UppercaseHandler(HelloTarget(), GoodbyeTarget())
    )

    (proxy is HelloTarget) shouldBe false
    (proxy is GoodbyeTarget) shouldBe false
    (proxy is Hello) shouldBe true
    (proxy is Goodbye) shouldBe true

    val hello = proxy as Hello
    hello.sayHello(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;HELLO ADMIN&amp;quot;
    hello.sayHi(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;HI ADMIN&amp;quot;
    hello.sayThankYou(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;HELLO THANK YOU ADMIN&amp;quot;

    val goodbye = proxy as Goodbye
    goodbye.sayGoodbye(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;GOODBYE ADMIN&amp;quot;
    goodbye.sayThankYou(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;HELLO THANK YOU ADMIN&amp;quot;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Proxy.newProxyInstance()&lt;/code&gt;로 중요한 두 가지의 값을 전달해야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;InvocationHandler&lt;/code&gt;를 구현한 부가기능을 가진 &lt;strong&gt;프록시&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Hello&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Goodbye&lt;/code&gt;와 같은 &lt;strong&gt;인터페이스&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;런타임 중에 주어진 인터페이스들을 구현하는 프록시 인스턴스를 생성하며, 프록시 인스턴스는 하나의 &lt;code class=&quot;language-text&quot;&gt;InvocationHandler&lt;/code&gt; 인스턴스와 연결된다.&lt;br&gt;
프록시 인스턴스에 대한 모든 메소드 호출은 &lt;code class=&quot;language-text&quot;&gt;InvocationHandler&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;invoke()&lt;/code&gt; 메소드로 호출되며, 여기서 타갯 메소드를 호출하기 전에 &lt;strong&gt;추가 기능을 가로채거나 추가할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;리플렉션 메소드인 invoke()를 이용해 타깃 오브젝트의 메소드를 호출할 때는 타깃 오브젝트에서 발생하는 예외가 &lt;code class=&quot;language-text&quot;&gt;InvocationTargetException&lt;/code&gt;으로 한 번 포장돼서 전달된다.&lt;br&gt;
따라서 InvocationTargetException으로 받은 후 &lt;code class=&quot;language-text&quot;&gt;getTargetException()&lt;/code&gt; 메소드로 &lt;strong&gt;중첩되어 있는 예외를 가져와야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Method&lt;/code&gt;를 이용한 타깃의 오브젝트의 메소드 호출을 구분하거나 &lt;code class=&quot;language-text&quot;&gt;args&lt;/code&gt; 인자들의 정보를 확인하거나 조작할 수 있다.&lt;br&gt;
리플렉션의 매우 유연하고 막강한 기능을 사용하여 동적 프록시를 조금 더 편하게 만들어보았다.&lt;br&gt;
하지만 인터페이스 기반이라는 단점이 있다. 이 단점을 해결하는 CGLIB에 대해 알아보자.&lt;/p&gt;
&lt;h2 id=&quot;cglibcode-generation-library&quot; style=&quot;position:relative;&quot;&gt;CGLIB(Code Generation Library)&lt;a href=&quot;#cglibcode-generation-library&quot; aria-label=&quot;cglibcode generation library permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;자바 클래스의 바이트코드를 런타임에 조작하여 새로운 클래스나 객체를 동적으로 생성하고 수정할 수 있는 강력한 라이브러리다.&lt;br&gt;
이 라이브러리는 스프링 AOP 프레임워크, ORM 툴, 그리고 다양한 테스팅 라이브러리 등에서 널리 사용되고 있다.&lt;br&gt;
&lt;strong&gt;클래스 기반으로 타겟 객체의 클래스를 상속 받아 새로운 서브 클래스로 프록시를 생성하는 방식이다.&lt;/strong&gt;&lt;br&gt;
인터페이스가 없는 클래스에도 적용할 수 있기에 자바의 동적 프록시의 단점을 해결한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Person: Hello, Goodbye {
    open fun greeting() = &amp;quot;안녕하세요!&amp;quot;
    override fun sayHello(name: String): String = &amp;quot;Hello $name&amp;quot;
    override fun sayHi(name: String): String = &amp;quot;Hi $name&amp;quot;
    override fun sayThankYou(name: String): String = &amp;quot;Thank you $name&amp;quot;
    override fun sayGoodbye(name: String): String = &amp;quot;Goodbye $name&amp;quot;
}

class MyMethodInterceptor: MethodInterceptor {
    override fun intercept(proxy: Any, method: Method, args: Array&amp;lt;out Any&amp;gt;, methodProxy: MethodProxy): Any =
        // 프록시의 슈퍼클래스의 타겟 메서드를 호출한다.
        &amp;quot;(Intercepted) &amp;quot; + methodProxy.invokeSuper(proxy, args)
}

&amp;quot;Person 클래스 Cglib 테스트&amp;quot; {
    val proxy = Enhancer.create(
        Person::class.java,
        MyMethodInterceptor()
    )

    (proxy is Hello) shouldBe true
    (proxy is Goodbye) shouldBe true
    (proxy is Person) shouldBe true

    val person = proxy as Person
    person.greeting() shouldBe &amp;quot;(Intercepted) 안녕하세요!&amp;quot;
    person.sayHello(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Hello admin&amp;quot;
    person.sayHi(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Hi admin&amp;quot;
    person.sayThankYou(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Thank you admin&amp;quot;
    person.sayGoodbye(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Goodbye admin&amp;quot;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;프록시 클래스는 구체적인 클래스와 인터페이스의 서브 클래스이므로, 모든 메서드를 가로챌 수 있다.&lt;br&gt;
인터페이스를 구현하지 않는 클래스를 프록시하고 싶을 때 유용하다.&lt;br&gt;
하지만 CGLIB을 통하여 프록시를 생성할 때 지켜야하는 규칙들이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;클래스를 상속하고 메서드를 오버라이드 하기 때문에 클래스와 메서드가 final이 선언되어 있지 않아야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기본 생성자가 꼭 있어야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CGLIB은 자바 동적 프록시에 비해 추가적인 기능도 제공한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class MyFixedValue: FixedValue {
    override fun loadObject(): Any = &amp;quot;Intercepted and always return \&amp;quot;Fixed\&amp;quot;&amp;quot;
}

class MyCallbackFilter : CallbackFilter {
    override fun accept(method: Method): Int =
        when(method.name) {
            &amp;quot;greeting&amp;quot; -&amp;gt; 1 // FixedValue 콜백 사용
            else -&amp;gt; 0       // MethodInterceptor 사용
        }

}

&amp;quot;Person 클래스 Cglib Callback, FixedValue 테스트&amp;quot; {
    val proxy = Enhancer.create(
        Person::class.java,
        arrayOf(Hello::class.java, Goodbye::class.java),
        MyCallbackFilter(),
        arrayOf&amp;lt;Callback&amp;gt;(MyMethodInterceptor(), MyFixedValue())
    )

    val person = proxy as Person
    person.greeting() shouldBe &amp;quot;Intercepted and always return \&amp;quot;Fixed\&amp;quot;&amp;quot;
    person.sayHello(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Hello admin&amp;quot;
    person.sayHi(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Hi admin&amp;quot;
    person.sayThankYou(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Thank you admin&amp;quot;
    person.sayGoodbye(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted) Goodbye admin&amp;quot;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Enhancer.create()&lt;/code&gt;에서 전달하는 Callback 순서대로 (&lt;code class=&quot;language-text&quot;&gt;arrayOf&amp;lt;Callback&gt;(MyMethodInterceptor(), MyFixedValue())&lt;/code&gt;) &lt;code class=&quot;language-text&quot;&gt;CallbackFilter&lt;/code&gt;에서 어떤 콜백을 실행할지 지정할 수 있다.&lt;/p&gt;
&lt;h1 id=&quot;4단계-프록시의-구현체-생성-방법을-선택하는-proxyfactory&quot; style=&quot;position:relative;&quot;&gt;4단계: 프록시의 구현체 생성 방법을 선택하는 ProxyFactory&lt;a href=&quot;#4%EB%8B%A8%EA%B3%84-%ED%94%84%EB%A1%9D%EC%8B%9C%EC%9D%98-%EA%B5%AC%ED%98%84%EC%B2%B4-%EC%83%9D%EC%84%B1-%EB%B0%A9%EB%B2%95%EC%9D%84-%EC%84%A0%ED%83%9D%ED%95%98%EB%8A%94-proxyfactory&quot; aria-label=&quot;4단계 프록시의 구현체 생성 방법을 선택하는 proxyfactory permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;스프링에서 제공하는 AOP 프록시용 팩토리는 인터페이스가 있을 때는 JDK 동적 프록시를 사용하고, 그렇지 않은 경우에는 CGLIB을 사용한다.&lt;br&gt;
이제는 부가기능을 적용할 때 &lt;code class=&quot;language-text&quot;&gt;Advice (부가기능)&lt;/code&gt;만 지정해주면된다. &lt;code class=&quot;language-text&quot;&gt;InvocationHandler&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;MethodInterceptor&lt;/code&gt;를 알 필요가 없다.&lt;br&gt;
프록시 팩토리가 내부에서 JDK 동적 프록시일 경우 InvocationHandler가 Advice를 호출하도록 하고, CGLIB인 경우 MethodInterceptor가 Advice를 호출하도록 기능을 개발해두었기 때문이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Hello {
    fun sayHello(name: String): String
    fun sayHi(name: String): String
    fun sayThankYou(name: String): String
}

open class Person: Hello {
    open fun greeting() = &amp;quot;안녕하세요!&amp;quot;
    override fun sayHello(name: String): String = &amp;quot;Hello $name&amp;quot;
    override fun sayHi(name: String): String = &amp;quot;Hi $name&amp;quot;
    override fun sayThankYou(name: String): String = &amp;quot;Thank you $name&amp;quot;
}

open class ConcretePerson {
    open fun greeting() = &amp;quot;Concrete 안녕하세요!&amp;quot;
    open fun sayHello(name: String): String = &amp;quot;Concrete Hello $name&amp;quot;
    open fun sayHi(name: String): String = &amp;quot;Concrete Hi $name&amp;quot;
    fun sayThankYou(name: String): String = &amp;quot;Concrete Thank you $name&amp;quot;
}

class MyMethodInterceptor1: MethodInterceptor {
    override fun invoke(invocation: MethodInvocation): String = 
        &amp;quot;(Intercepted1)&amp;quot; + invocation.proceed()
}

class MyMethodInterceptor2: MethodInterceptor {
    override fun invoke(invocation: MethodInvocation): String = 
        &amp;quot;(Intercepted2)&amp;quot; + invocation.proceed()
}

&amp;quot;인터페이스가 있으면 JDK 동적 프록시 사용&amp;quot; {
    val proxyFactory: ProxyFactory = ProxyFactory(Person())
    proxyFactory.addAdvice(MyMethodInterceptor1())
    proxyFactory.addAdvice(MyMethodInterceptor2())

    shouldThrowExactly&amp;lt;ClassCastException&amp;gt; { proxyFactory.proxy as Person }

    val proxy = proxyFactory.proxy as Hello

    proxy.javaClass.toString() shouldBe &amp;quot;class jdk.proxy2.\$Proxy7&amp;quot;

    proxy.sayHello(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Hello admin&amp;quot;
    proxy.sayHi(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Hi admin&amp;quot;
    proxy.sayThankYou(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Thank you admin&amp;quot;
}

&amp;quot;구체 클래스만 있으면 CGLIB 사용&amp;quot; {w
    val proxyFactory: ProxyFactory = ProxyFactory(ConcretePerson())
    proxyFactory.addAdvice(MyMethodInterceptor1())
    proxyFactory.addAdvice(MyMethodInterceptor2())

    val proxy = proxyFactory.proxy as ConcretePerson

    proxy.javaClass.toString() shouldBe &amp;quot;class ConcretePerson$\$SpringCGLIB$$0&amp;quot;

    proxy.greeting() shouldBe &amp;quot;(Intercepted1)(Intercepted2)Concrete 안녕하세요!&amp;quot;
    proxy.sayHello(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Concrete Hello admin&amp;quot;
    proxy.sayHi(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Concrete Hi admin&amp;quot;
    // open 되지 않은 메서드는 프록시가 실행되지 않는다.
    proxy.sayThankYou(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;Concrete Thank you admin&amp;quot;
}

&amp;quot;인터페이스가 있어도 클래스 기반 CGLIB 프록시 사용&amp;quot; {
    val proxyFactory: ProxyFactory = ProxyFactory(Person())
    proxyFactory.isProxyTargetClass = true
    proxyFactory.addAdvice(MyMethodInterceptor1())
    proxyFactory.addAdvice(MyMethodInterceptor2())

    val proxy = proxyFactory.proxy as Person

    proxy.javaClass.toString() shouldBe &amp;quot;class Person$\$SpringCGLIB$$0&amp;quot;

    proxy.greeting() shouldBe &amp;quot;(Intercepted1)(Intercepted2)안녕하세요!&amp;quot;
    proxy.sayHello(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Hello admin&amp;quot;
    proxy.sayHi(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Hi admin&amp;quot;
    proxy.sayThankYou(&amp;quot;admin&amp;quot;) shouldBe &amp;quot;(Intercepted1)(Intercepted2)Thank you admin&amp;quot;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- JDK 동적 프록시를 사용하는 경우
invoke:36, MyMethodInterceptor1
invoke:35, MyMethodInterceptor1
proceed:184, ReflectiveMethodInvocation (org.springframework.aop.framework)
invoke:220, JdkDynamicAopProxy (org.springframework.aop.framework)
sayHello:-1, $Proxy7 (jdk.proxy2)

-- CGLIB을 사용하는 경우
invoke:36, MyMethodInterceptor1
invoke:35, MyMethodInterceptor1
proceed:184, ReflectiveMethodInvocation (org.springframework.aop.framework)
proceed:765, CglibAopProxy$CglibMethodInvocation (org.springframework.aop.framework)
intercept:717, CglibAopProxy$DynamicAdvisedInterceptor (org.springframework.aop.framework)
greeting:-1, Person$$SpringCGLIB$$0&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
	@Nullable
	Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;invoke()&lt;/code&gt;의 파라미터인 &lt;code class=&quot;language-text&quot;&gt;MethodInvocation&lt;/code&gt; 내부에는 &lt;strong&gt;다음 메서드를 호출하는 방법&lt;/strong&gt;, &lt;strong&gt;현재 프록시 객체 인스턴스&lt;/strong&gt;, &lt;strong&gt;args&lt;/strong&gt;, &lt;strong&gt;메서드 정보&lt;/strong&gt;등이 포함되어 있다.&lt;br&gt;
Target이 MethodInvocation안에 포함되어 있기 때문에 이전 방법과 다르게 프록시 내부에서 Target을 신경쓰지 않아도 된다.&lt;/p&gt;
&lt;p&gt;스프링 부트는 AOP를 적용할 때 기본적으로 &lt;code class=&quot;language-text&quot;&gt;proxyTargetClass=true&lt;/code&gt;로 설정해서 사용하기 때문에 &lt;strong&gt;인터페이스가 있어도 CGLIB을 사용해서 구체 클래스를 기반으로 프록시를 생성한다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;5단계-factorybean&quot; style=&quot;position:relative;&quot;&gt;5단계: FactoryBean&lt;a href=&quot;#5%EB%8B%A8%EA%B3%84-factorybean&quot; aria-label=&quot;5단계 factorybean permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;스프링을 대신해서 오브젝트의 생성 로직을 담당하도록 만들어진 특별한 빈이다.&lt;br&gt;
생성할 프록시가 다른 빈을 주입 받을 필요가 있거나 스프링의 기능을 사용해야 한다면 스프링 빈으로 등록해야 한다.&lt;br&gt;
이때 &lt;strong&gt;스프링은 &lt;code class=&quot;language-text&quot;&gt;FactoryBean&lt;/code&gt; 인터페이스를 구현한 클래스가 Bean의 클래스로 지정되면 &lt;code class=&quot;language-text&quot;&gt;getObject()&lt;/code&gt;를 통해 오브젝트를 가져오고, 이를 빈 오브젝트로 사용한다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;복잡한 초기화가 필요한 빈을 생성 시&lt;/li&gt;
&lt;li&gt;AOP 프록시와 같이 런타임에 생성되는 프록시 객체를 스프링 빈으로 등록할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 경우 유용하게 사용할 수 있다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;과 같은 역할을 하는 특정 패턴의 메서드는 정상 처리가 되었다면 commit을 하고 예러가 발생되면 rollback하는 프록시를 만들어보자.&lt;br&gt;
아래의 예제를 확인해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;/**
 * 프록시로 등록할 인터페이스와 구현체
 */
interface Hello {
    fun sayGreeting(name: String): String
    fun sayHello(name: String): String
    fun sayHi(name: String): String
    fun sayThankYou(name: String): String
}
open class Person: Hello {
    open fun greeting() = &amp;quot;안녕하세요!&amp;quot;
    override fun sayGreeting(name: String) = &amp;quot;안녕하세요! $name&amp;quot;
    override fun sayHello(name: String): String = &amp;quot;Hello $name&amp;quot;
    override fun sayHi(name: String): String = &amp;quot;Hi $name&amp;quot;
    override fun sayThankYou(name: String): String = &amp;quot;Thank you $name&amp;quot;
}

/**
 * 등록할 프록시를 스프링에서 관리할 수 있도록 빈으로 등록하는 FactoryBean 구현체
 * 범용적으로 대상은 Any로 했고, Class 타입도 * 이다.
 */
class PersonFactoryBean(
    private val target: Any,
    private val `interface`: Class&amp;lt;*&amp;gt;
): FactoryBean&amp;lt;Any&amp;gt; {

    // 인터페이스 기반이 아닌 Person 구현체의 프록시를 생성하기 ProxyFactory로 프록시를 생성
    // ProxyFactory로 프록시를 생성하기 위해서는 (TransactionMethodInterceptor와 같은) 부가기능은 org.aopalliance.intercept.MethodInterceptor를 구현해야 한다.
    override fun getObject() = ProxyFactory(target).apply {
        this.addAdvice(TransactionMethodInterceptor(&amp;quot;say&amp;quot;))
        this.isProxyTargetClass = true
    }.proxy

    override fun getObjectType(): Class&amp;lt;*&amp;gt; = `interface`
}

/**
 * 부가기능을 정의한 MethodInterceptor 구현체이다.
 * 메소드 이름이 주입받은 pattern으로 시작하는 메소드만 부가기능을 실행하도록 한다.
 */
class TransactionMethodInterceptor(
    private val target: Any,
    private val pattern: String
): MethodInterceptor {

    override fun invoke(invocation: MethodInvocation) =
        if (invocation.method.name.startsWith(pattern)) invokeInTransaction(invocation)
        else invocation.proceed()

    private fun invokeInTransaction(invocation: MethodInvocation): String =
        try {
            &amp;quot;(Commit)&amp;quot; + invocation.proceed()
        } catch (e: InvocationTargetException) {
            throw e.targetException
        }
}

/**
 * Hello와 Person 빈을 등록할 때 프록시로 감싼 빈으로 등록한다.
 */
@Configuration
class AppConfig {
    @Bean
    fun hello() = PersonFactoryBean(Person(), Hello::class.java)
    @Bean
    fun person() = PersonFactoryBean(Person(), Person::class.java)
}

@SpringBootTest(&amp;quot;spring.profiles.active=local&amp;quot;, classes = [AppConfig::class])
class FactoryBeanTest(
    private val context: ApplicationContext,
    private val hello: Hello,
    private val person: Person
): AnnotationSpec() {

    private val NAME: String = &amp;quot;admin&amp;quot;

    init {
        extension(SpringExtension)
    }

    @Test
    @DisplayName(&amp;quot;인터페이스 기반 프록시&amp;quot;)
    fun interfaceProxy() {
        hello.sayGreeting(NAME) shouldBe &amp;quot;(Commit)안녕하세요! admin&amp;quot;
        hello.sayHello(NAME) shouldBe &amp;quot;(Commit)Hello admin&amp;quot;
        hello.sayHi(NAME) shouldBe &amp;quot;(Commit)Hi admin&amp;quot;
        hello.sayThankYou(NAME) shouldBe &amp;quot;(Commit)Thank you admin&amp;quot;
    }

    @Test
    @DisplayName(&amp;quot;클래스 기반 프록시&amp;quot;)
    fun concreteProxy() {
        person.greeting() shouldBe &amp;quot;안녕하세요!&amp;quot;
        person.sayGreeting(NAME) shouldBe &amp;quot;(Commit)안녕하세요! admin&amp;quot;
        person.sayHello(NAME) shouldBe &amp;quot;(Commit)Hello admin&amp;quot;
        person.sayHi(NAME) shouldBe &amp;quot;(Commit)Hi admin&amp;quot;
        person.sayThankYou(NAME) shouldBe &amp;quot;(Commit)Thank you admin&amp;quot;
    }

    @Test
    @DisplayName(&amp;quot;팩토리 빈과 빈을 직접 조회하기&amp;quot;)
    fun searchFactoryBean() {
        val factoryBean = context.getBean(&amp;quot;&amp;amp;person&amp;quot;, PersonFactoryBean::class.java)
        (factoryBean.`object` is Person) shouldBe true
        (factoryBean.`object` is Hello) shouldBe true

        val helloBean = context.getBean(&amp;quot;hello&amp;quot;, Hello::class.java)
        helloBean.sayGreeting(NAME) shouldBe &amp;quot;(Commit)안녕하세요! admin&amp;quot;
        helloBean.sayHello(NAME) shouldBe &amp;quot;(Commit)Hello admin&amp;quot;
        helloBean.sayHi(NAME) shouldBe &amp;quot;(Commit)Hi admin&amp;quot;
        helloBean.sayThankYou(NAME) shouldBe &amp;quot;(Commit)Thank you admin&amp;quot;

        val personBean = context.getBean(&amp;quot;person&amp;quot;, Person::class.java)
        personBean.greeting() shouldBe &amp;quot;안녕하세요!&amp;quot;
        personBean.sayGreeting(NAME) shouldBe &amp;quot;(Commit)안녕하세요! admin&amp;quot;
        personBean.sayHello(NAME) shouldBe &amp;quot;(Commit)Hello admin&amp;quot;
        personBean.sayHi(NAME) shouldBe &amp;quot;(Commit)Hi admin&amp;quot;
        personBean.sayThankYou(NAME) shouldBe &amp;quot;(Commit)Thank you admin&amp;quot;

        System.identityHashCode(hello) shouldBe System.identityHashCode(helloBean)
        System.identityHashCode(person) shouldBe System.identityHashCode(personBean)
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;say&lt;/code&gt;로 시작하는 메서드의 반환값에 &lt;code class=&quot;language-text&quot;&gt;(Commit)&lt;/code&gt; 문자열을 추가하여 반환하는 부가기능을 &lt;code class=&quot;language-text&quot;&gt;TransactionMethodInterceptor&lt;/code&gt;로 정의하여 빈을 등록할 때 &lt;code class=&quot;language-text&quot;&gt;PersonFactoryBean&lt;/code&gt;을 통해 프록시로 감싼 빈을 등록하였다.&lt;/p&gt;
&lt;p&gt;하지만 빈을 직접 등록해야 하는 불편함이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;한 번에 여러 개의 클래스에 공통적인 부가기능을 제공해야 한다면?&lt;/strong&gt; 비즈니스 로직을 담은 많은 클래스의 메소드에 부가기능을 적용하려 한다면 팩토리 빈의 설정 코드도 같이 늘어난다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;하나의 타깃에 여러 개의 부가기능을 적용해야 한다면?&lt;/strong&gt; 프록시를 생성하는 FactoryBean 구현체마다 advice를 직접 추가해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 번거로운 작업을 없애고 프록시를 모든 타깃에 적용 가능한 싱글톤 빈으로 만드는 방법을 알아보자.&lt;/p&gt;
&lt;h1 id=&quot;6단계-부가기능을-구현하는-차이점&quot; style=&quot;position:relative;&quot;&gt;6단계: 부가기능을 구현하는 차이점&lt;a href=&quot;#6%EB%8B%A8%EA%B3%84-%EB%B6%80%EA%B0%80%EA%B8%B0%EB%8A%A5%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot; aria-label=&quot;6단계 부가기능을 구현하는 차이점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이때까지 작성한 예제를 보면 어떤 부가기능은 &lt;code class=&quot;language-text&quot;&gt;java.lang.reflect.InvocationHandler&lt;/code&gt;를 구현하였고 어떤 부가기능은 &lt;code class=&quot;language-text&quot;&gt;org.aopalliance.intercept.MethodInterceptor&lt;/code&gt;를 구현하였다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class MyInvocationHandler(
    private val target: Any
): InvocationHandler {
    override fun invoke(proxy: Any, method: Method, args: Array&amp;lt;out Any&amp;gt;): Any =
        method.invoke(target, *args)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;자바에서 제공하는 &lt;code class=&quot;language-text&quot;&gt;InvocationHandler&lt;/code&gt;는 타겟이 달라지거나 메서드 선정 조건이 달라진다면 여러 프록시가 공유할 수 없었다.&lt;br&gt;
즉, &lt;strong&gt;부가기능 내부에서 타겟과 메소드 선정을 함께하고 있기 떄문에 오브젝트 차원에서 특정 타깃을 위한 프록시에 제한된다는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class MyMethodInterceptor: MethodInterceptor {
    override fun invoke(invocation: MethodInvocation) = invocation.proceed()
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;그에 반해, &lt;code class=&quot;language-text&quot;&gt;MethodInterceptor.invoke()&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;MethodInvocation&lt;/code&gt;을 통해 타깃 오브젝트에 대한 의존이 사라졌다.&lt;br&gt;
그 이유는 &lt;strong&gt;스프링이 aopaliance에서 제공하는 &lt;code class=&quot;language-text&quot;&gt;MethodInvocation&lt;/code&gt;을 확장하고 구현하여 타겟에 대한 정보, 메서드, 인자, 부가기능 등을 가진 Advice를 필드에 가지고 있기 때문이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7aadce2fa23bdf9b8bbf54caeb6bb450/f02b9/methodInvocation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACNElEQVR42p1T23baMBDkJ3pOW8DYsm6WbxhsY64hKcZA2v//nOmuCD1tnpI+DFgraTQ7Go2CUOAjkErBWI1yUWCxrNC0NbquRVnmvs6YRQKjjxIaazAOQiyIaLvd4jwMuFxvcFmJL18n0EZDxPJzhKxgGkSYEHEgQ4wnAbpVic26QpJYhCL+OKFNDKazCI5aXHUddvsDDk9H5EWBKI69wuizCplQWYs0L9BsWsyrJUIi+T6dQen/IIykgjIJTJJCOwepLazLEEQSii5NyDdCPvmB90SPOm8olyW1ucflcsVwHdD3Pc7nM1yaQZPCPx5GxBzRhjukx4wmuZ1HXXBsyEdlFWItYXMDaUiZoboziMnDKXGNeKHQym/+Gya3UM74w/hknSbo9ms8v/xAT5FhvJxOOJHC3eGJyCk2JGTEzMFbHO7tcRxmEJaIpMC38RRjMn0WRoiVpoAbaOv8bSvyUJOnsTQ+SiyMCJVv7+GXtBmKqsaybgkrNKvO36q0OYr5AnWzQrWsUbcNvZjGfy/rhuYqIpTvCUPyY46qWeNIGXs6HLGlvM2rBUxKz2218fnb7vZ47n/6/81252t100IQ14h/2KNQSI+AWjMRecbvlyBYNY2VuIPrbEEQJx6T4D7mllncKIwFsqrFYXekGJxx6ge8DldcCP1wwUAR4beb5DUO+2f8ut1wvb36tf158PPrzc6L8oTcqnYJbOYIqYdJHRGkHq7IaJzQjf+75o77Op5nMr7c3z4hutFSsVmrAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;methodInvocation&quot;
        title=&quot;&quot;
        src=&quot;/static/7aadce2fa23bdf9b8bbf54caeb6bb450/1cfc2/methodInvocation.png&quot;
        srcset=&quot;/static/7aadce2fa23bdf9b8bbf54caeb6bb450/3684f/methodInvocation.png 225w,
/static/7aadce2fa23bdf9b8bbf54caeb6bb450/fc2a6/methodInvocation.png 450w,
/static/7aadce2fa23bdf9b8bbf54caeb6bb450/1cfc2/methodInvocation.png 900w,
/static/7aadce2fa23bdf9b8bbf54caeb6bb450/21482/methodInvocation.png 1350w,
/static/7aadce2fa23bdf9b8bbf54caeb6bb450/f02b9/methodInvocation.png 1554w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;즉, 이제 부가기능을 정의하던 &lt;code class=&quot;language-text&quot;&gt;MethodInterceptor&lt;/code&gt;는 부가기능 자체에만 집중할 수 있게 되므로 많은 곳에서 재사용할 수 있게 되었다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ProxyFactoryBean&lt;/code&gt;은 MethodInterceptor 방식을 사용하고 있다.&lt;/p&gt;
&lt;h1 id=&quot;7단계-proxyfactorybean&quot; style=&quot;position:relative;&quot;&gt;7단계: ProxyFactoryBean&lt;a href=&quot;#7%EB%8B%A8%EA%B3%84-proxyfactorybean&quot; aria-label=&quot;7단계 proxyfactorybean permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5a003b0806e77a553858c5dfeea16347/0a867/proxyFactory.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 76.88888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB9UlEQVR42oVTi46qMBT0M1ToAyjlVYoCgsq6SiKCPBR3c///V+5B1s2N7uYmQ0Pazpkz03aCif4NhDVNZ74MgkW4StZxnALgJ0nWnvBh9d/NgMkTWTd4kqbbLKuquu7aqqlPZVVWdRwnKnolY01FVEFUvUNRyQBEpjOEDU0lZDpVYeZVdiBT3bQc6QnpesL1pC+XnDsGs2xHwKQvF54nKTVemQPZtPx0V+V53t3+nOv2eu03m8zz/CzLzk19vfXQNgTxszJ8BApjysSaMnfcNI6Gw3WT/aj5HJimMdMOGPc0zYDYGOcwkl8afiJr0abYH46X7nLtP/vbx+322bUXxxWQ4n/ICOuaziEnA6SjPXcC07RM0xmVwcWvnu+2dUqHTSoixHDgSIeTU8mYCNUYjHf+V5Wx6ARTQ1WxjHZvh1NVlW13aZqubQdA8kVxgkNbvSWHQw6GznXT9x9V3bwfC87tydgVMx3b8V3Xh2sYbksZroW/8IPI9QKdWdyGVSFEEG5iKRdwHWxXQDvfnqFJpKh4pmBkCIVwBZuIWpialHkKNmZzNJ2BDzJT0HyOwRRIPjwTfZHk2+y9LM+A4niEhl2QiHbZ7nA6VSOKomyadr/PH54f0TkigrsZRavVanhM8So1uW17oQzCMIrHGUCSbpbLaEz7L5HHkj0uA11lAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;proxyFactory&quot;
        title=&quot;&quot;
        src=&quot;/static/5a003b0806e77a553858c5dfeea16347/1cfc2/proxyFactory.png&quot;
        srcset=&quot;/static/5a003b0806e77a553858c5dfeea16347/3684f/proxyFactory.png 225w,
/static/5a003b0806e77a553858c5dfeea16347/fc2a6/proxyFactory.png 450w,
/static/5a003b0806e77a553858c5dfeea16347/1cfc2/proxyFactory.png 900w,
/static/5a003b0806e77a553858c5dfeea16347/0a867/proxyFactory.png 986w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Pointcut과 Advice를 재사용할 수 있는 &lt;code class=&quot;language-text&quot;&gt;MethodInterceptor&lt;/code&gt;에 대해 알아보았다. &lt;strong&gt;스프링은 Pointcut과 Advice를 사용하여 프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈을 제공한다.&lt;/strong&gt;&lt;br&gt;
(ProxyFactory와 비슷한 기능을 제공하지만 &lt;code class=&quot;language-text&quot;&gt;FactoryBean&lt;/code&gt;을 추가로 구현하는 것이다.)&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Configuration
class AppConfig {
    @Bean
    fun personProxyFactoryBean() = ProxyFactoryBean().apply {
        this.setTarget(Person())
        this.addAdvisor(advisor())
        this.isProxyTargetClass = true
    }

    // Pointcut과 Advice를 묶어서 Advisor 타입으로 전달하는 이유는
    // 여러 개의 어드바이스와 포인트컷이 추가될 수 있기 때문에 &amp;quot;이 포인트컷은 이 어드바이스에 적용한다&amp;quot; 라고 알리기 위해서다
    @Bean
    fun advisor() = DefaultPointcutAdvisor(pointcut(), advice())

    @Bean
    fun pointcut() = NameMatchMethodPointcut().apply {
        this.setMappedName(&amp;quot;sayH*&amp;quot;)
    }

    @Bean
    fun advice() = MethodInterceptor { it: MethodInvocation -&amp;gt;
        &amp;quot;intercept!!! &amp;quot; + (it.proceed() as String)
    }
}

@SpringBootTest(&amp;quot;spring.profiles.active=local&amp;quot;, classes = [AppConfig::class])
class FactoryBeanTest(
    private val context: ApplicationContext,
    private val person: Person
): AnnotationSpec() {

    private val NAME: String = &amp;quot;admin&amp;quot;

    init {
        extension(SpringExtension)
    }

    @Test
    @DisplayName(&amp;quot;ProxyFactoryBean 테스트&amp;quot;)
    fun proxyFactoryBean() {
        person.greeting() shouldBe &amp;quot;안녕하세요!&amp;quot;
        person.sayGreeting(NAME) shouldBe &amp;quot;안녕하세요! admin&amp;quot;
        person.sayHello(NAME) shouldBe &amp;quot;intercept!!! Hello admin&amp;quot;
        person.sayHi(NAME) shouldBe &amp;quot;intercept!!! Hi admin&amp;quot;
        person.sayThankYou(NAME) shouldBe &amp;quot;Thank you admin&amp;quot;
        shouldThrow&amp;lt;java.lang.RuntimeException&amp;gt; { person.throwException() }
            .message shouldBe &amp;quot;예외 테스트!&amp;quot;

        val proxyFactoryBean = context.getBean(&amp;quot;&amp;amp;personProxyFactoryBean&amp;quot;, ProxyFactoryBean::class.java)
        val person2 = proxyFactoryBean.`object` as Person

        person2.greeting() shouldBe &amp;quot;안녕하세요!&amp;quot;
        person2.sayGreeting(NAME) shouldBe &amp;quot;안녕하세요! admin&amp;quot;
        person2.sayHello(NAME) shouldBe &amp;quot;intercept!!! Hello admin&amp;quot;
        person2.sayHi(NAME) shouldBe &amp;quot;intercept!!! Hi admin&amp;quot;
        person2.sayThankYou(NAME) shouldBe &amp;quot;Thank you admin&amp;quot;
        shouldThrow&amp;lt;java.lang.RuntimeException&amp;gt; { person2.throwException() }
            .message shouldBe &amp;quot;예외 테스트!&amp;quot;

        System.identityHashCode(person) shouldBe System.identityHashCode(person2)
    }
}

interface Hello {
    fun sayGreeting(name: String): String
    fun sayHello(name: String): String
    fun sayHi(name: String): String
    fun sayThankYou(name: String): String
    fun throwException(): Nothing
}

open class Person: Hello {
    open fun greeting() = &amp;quot;안녕하세요!&amp;quot;
    override fun sayGreeting(name: String): String = &amp;quot;안녕하세요! $name&amp;quot;
    override fun sayHello(name: String): String = &amp;quot;Hello $name&amp;quot;
    override fun sayHi(name: String): String = &amp;quot;Hi $name&amp;quot;
    override fun sayThankYou(name: String): String = &amp;quot;Thank you $name&amp;quot;
    override fun throwException(): Nothing = throw RuntimeException(&amp;quot;예외 테스트!&amp;quot;)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;Pointcut과 Advice는 여러 프록시에서 공유가 가능하도록 만들 수 있기 때문에 스프링의 싱글톤 빈으로 등록이 가능하다.&lt;br&gt;
그렇기에 &lt;strong&gt;프록시를 생성할 때 사용되는 Pointcut과 Advice는 스프링의 DI로 주입하여 사용 할 수 있다.&lt;/strong&gt;&lt;br&gt;
InvocationHandler와 다르게 부가기능 구현체는 클라이언트로부터 받는 요청을 일일이 전달받을 필요가 없어졌다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 566px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dcf33ea9b90e862da3f1d6cc895f319e/6fe44/proxyFactoryBean.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 97.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAIAAAAf7rriAAAACXBIWXMAAAsTAAALEwEAmpwYAAACpUlEQVR42nVT246bMBDN/39Epb626kVqH9qHStvV3nJvkt0NkBBCMMQBE3zBEDA2dZJNGiL1aLAMzBnNHB+3qPHTaX+z7r+gyY86j1Rd1+qw1A1cvR7RUszLkc1DS9KVEtmBu88Uosx4qiNltNjtjt+VOpdSB3JdE8aDDZLqX3Uhle3Tu771MJjddo2JHeblW09S7kvoR29a0xUZzcKhtXl1yZZkGnmWQUQtKKZe2nkJRjYyofBRXuwyhLkdiskiNgGbgqz1NEU3g+X9CDw8b2DMKUkwTgCMLdudWWYQrG177rirALGUET8kr4G8HXo9C3XmvMVymRY129U0l6KSx7Z5Xi38xPGipR87IFqAOKb7sXUCTgUr9smYi9Zp+jcN1AlFKQuhdJTVfq0Os54UuxCswT6BEJxl/Cz+Ua19XCS3Lio1KmCMOefHfVVVyTbWh3d9zvV/cO4wToUD087I1oe3weVZlzey9oairtIm0cE8Javzbz3t3Kc+3FoL4AXRwieYVycTHsgVg8vHz87jV/P2I+h/r0Wq9g4TtZIJzZ4t13MXhjG1Z6bjQZKpBlkVtIBj6vbYqr+Dz7UsylKgOAbAc1xg+tz2SXc0M11kAE4y2SAfZ4Mw2G7RhbdFJcqiqvXAIcJgHYUxcdb0uu1CyJAqD+VBIkIipJR5nvtBsIEQ+PCpMxwO+p1OZzwavBjzazLNqoFNe7Ntd86ma5GwnGcZYwwnCQhCw/aiMDSt2Xrtr4IoSUWTnMun1819b97+dXfz48ZodzPfS1MeRSFm+Qom2vDa8Txl/holrGyQ07yyQrUEKWs//H7/7s+nD2TQi+MtwTja0vHU0Vd6PJ5on+j7EdMmWStUClmWVVWWSofQIY4ySlUHiK8gDiIGNsSFRAt0Nslf+8E0xD0MQgQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;proxyFactoryBean&quot;
        title=&quot;&quot;
        src=&quot;/static/dcf33ea9b90e862da3f1d6cc895f319e/6fe44/proxyFactoryBean.png&quot;
        srcset=&quot;/static/dcf33ea9b90e862da3f1d6cc895f319e/3684f/proxyFactoryBean.png 225w,
/static/dcf33ea9b90e862da3f1d6cc895f319e/fc2a6/proxyFactoryBean.png 450w,
/static/dcf33ea9b90e862da3f1d6cc895f319e/6fe44/proxyFactoryBean.png 566w&quot;
        sizes=&quot;(max-width: 566px) 100vw, 566px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[1]&lt;/code&gt; : &lt;code class=&quot;language-text&quot;&gt;CglibAopProxy.ProxyCallbackFilter&lt;/code&gt;에서 등록된 Pointcut을 통해 부가기능 대상인지 확인한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[2,3]&lt;/code&gt; : &lt;code class=&quot;language-text&quot;&gt;CglibAopProxy.DynamicAdvisedInterceptor&lt;/code&gt;에서 부가기능들을 관리하고 실행시키는 역할을 한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;[4]&lt;/code&gt; : &lt;code class=&quot;language-text&quot;&gt;CglibMethodInvocation&lt;/code&gt; → &lt;code class=&quot;language-text&quot;&gt;ReflectiveMethodInvocation&lt;/code&gt; 에서 부가기능과 타겟을 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;실제 위임 대상인 타깃 오브젝트의 레퍼런스를 갖고 있고, 이를 이용해 타깃 메소드를 직접 호출하는 것은 프록시가 메소드 호출에 따라 만드는 &lt;code class=&quot;language-text&quot;&gt;Invocation Callback&lt;/code&gt;의 역할이다.&lt;br&gt;
재사용 가능한 기능을 만들어두고 &quot;바뀌는 부분 (콜백 오브젝트와 메소드 호출정보)&quot;만 외부에서 주입해서 이를 &quot;작업 흐름 (부가기능 부여)&quot; 중에 사용하도록 하는 전형적인 &lt;strong&gt;템플릿/콜백&lt;/strong&gt; 구조다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7e1adceb41d12b5a1870848a720e632c/966a0/proxyFactoryBean2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABnUlEQVR42lVSiXLbIBD1/39cZzpNkzRumsq2LEACBIhD6HhdcOvGK+0sy7Bv3x4HfJJly3BJIy0e8xIQ5umuMfuqie5jprslYps1ltBVWzT7Kw7Yd4LaK2AJ8GnDiVkw6bFu/5OlmCC4AGccQz9AjgpGtwjeYZAjRkPJHCPAT1KYxbyhuSp8fT1BSIs0ZyiloLWCEAKMMfR9DzNZTLYjVRgGSqA0AvmHZAVRJccHAuDwk0OeZ1xlwPOvHg1z4JzXoK7rcLlcwDpWGWp5roBC9MSSWuU4DvbyhEm8U4BErwSMM8Rqhmgljk8faN7OaJoGMcbKrIArKes7O7bwTlMFmkp2N8DHkmkQU8T5vcXzlx9Q/YicaFDOwRhTSy9MNQFY6p2nnoVppHaMMNYhTn8B9zIY+iP1MIUZ3YlDXAds635PlnOGLFUQywI4EsMylOgt9EhnR7GTeGRYpjyvqZ738u3bzeJm/0nx4xqx0Irtq79vyRr7R8DCNOVY1XmD1+PLXV/evqPjLe1npL0sK7VSQMbAf+Pj5zd05yOCE/gDaGCzCYB3nTsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;proxyFactoryBean2&quot;
        title=&quot;&quot;
        src=&quot;/static/7e1adceb41d12b5a1870848a720e632c/1cfc2/proxyFactoryBean2.png&quot;
        srcset=&quot;/static/7e1adceb41d12b5a1870848a720e632c/3684f/proxyFactoryBean2.png 225w,
/static/7e1adceb41d12b5a1870848a720e632c/fc2a6/proxyFactoryBean2.png 450w,
/static/7e1adceb41d12b5a1870848a720e632c/1cfc2/proxyFactoryBean2.png 900w,
/static/7e1adceb41d12b5a1870848a720e632c/966a0/proxyFactoryBean2.png 944w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Pointcut과 Advice, 이 둘을 묶는 Advisor 각각 빈으로 등록하여 중복을 제거할 수 있게 되었다.&lt;br&gt;
하지만 &lt;strong&gt;부가기능의 적용이 필요한 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 빈 설정정보를 직접 작성해줘야하는 단점이 있다.&lt;/strong&gt;&lt;br&gt;
타겟을 제외하면 빈 클래스의 종류, Advice, Pointcut의 설정이 거의 동일하며 빈으로 관리가 가능한데 이러한 중복을 제거할 수 있는 방법은 없을까?&lt;/p&gt;
&lt;h1 id=&quot;8단계-빈-후처리기를-이용한-자동-프록시-생성기&quot; style=&quot;position:relative;&quot;&gt;8단계: 빈 후처리기를 이용한 자동 프록시 생성기&lt;a href=&quot;#8%EB%8B%A8%EA%B3%84-%EB%B9%88-%ED%9B%84%EC%B2%98%EB%A6%AC%EA%B8%B0%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9E%90%EB%8F%99-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%83%9D%EC%84%B1%EA%B8%B0&quot; aria-label=&quot;8단계 빈 후처리기를 이용한 자동 프록시 생성기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이제 스프링에서 프록시를 자동으로 생성하는 방법에 대해 알아볼 차례다.&lt;br&gt;
말 그대로 &lt;strong&gt;스프링 빈 오브젝트로 만들어지고 난 후에, 빈 오브젝트를 다시 가공할 수 있게 해주는 것이다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;BeanPostProcessor&lt;/code&gt;를 직접 구현한 아래의 예제를 보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;/**
 * 생성자로 pattern을 주입받아 빈의 이름이 해당 pattern으로 시작하는 빈만 프록시를 감싸서 반환하고 그렇지 않으면 빈 자체를 반환한다.
 */
class ClassLogTracePostProcessor(
    private val pattern: String,
    private val advisor: Advisor
): BeanPostProcessor {

    private val log = logger()
    override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any {
        log.info(&amp;quot;[before] pattern = $pattern, beanName = $beanName, bean = ${bean.javaClass}&amp;quot;)
        return bean
    }

    override fun postProcessAfterInitialization(bean: Any, beanName: String): Any {
        log.info(&amp;quot;[after] pattern = $pattern, beanName = $beanName, bean = ${bean.javaClass}&amp;quot;)
        if(!beanName.startsWith(pattern)) return bean

        val proxy = ProxyFactory(bean).apply {
            this.addAdvisor(advisor)
            this.isProxyTargetClass = true
        }.proxy

        log.info(&amp;quot;[after - proxy] target = ${bean.javaClass}, proxy = ${proxy.javaClass}&amp;quot;)
        return proxy
    }
}

@Configuration
class PostProcessorConfig {
    @Bean
    fun classLogTracePostProcessor() = ClassLogTracePostProcessor(&amp;quot;p&amp;quot;, advisor())
    @Bean
    fun advisor() = DefaultPointcutAdvisor(pointcut(), advice())
    @Bean
    fun pointcut() = NameMatchMethodPointcut().apply {
        this.setMappedName(&amp;quot;sayH*&amp;quot;)
    }
    @Bean
    fun advice() = MethodInterceptor { it: MethodInvocation -&amp;gt;
        &amp;quot;intercept!!! &amp;quot; + (it.proceed() as String)
    }
    @Bean
    fun hello(): Hello = Person()
    @Bean
    fun person(): Person = Person()
}

@SpringBootTest(&amp;quot;spring.profiles.active=local&amp;quot;, classes = [PostProcessorConfig::class])
class MemberServiceTest(
    private val hello: Hello,
    private val person: Person
): AnnotationSpec() {

    private val NAME: String = &amp;quot;admin&amp;quot;

    init {
        extension(SpringExtension)
    }

    @Test
    fun beanPostProcessor() {
        hello.sayGreeting(NAME) shouldBe &amp;quot;안녕하세요! admin&amp;quot;
        hello.sayHello(NAME) shouldBe &amp;quot;Hello admin&amp;quot;
        hello.sayHi(NAME) shouldBe &amp;quot;Hi admin&amp;quot;
        hello.sayThankYou(NAME) shouldBe &amp;quot;Thank you admin&amp;quot;

        person.greeting() shouldBe &amp;quot;안녕하세요!&amp;quot;
        person.sayGreeting(NAME) shouldBe &amp;quot;안녕하세요! admin&amp;quot;
        person.sayHello(NAME) shouldBe &amp;quot;intercept!!! Hello admin&amp;quot;
        person.sayHi(NAME) shouldBe &amp;quot;intercept!!! Hi admin&amp;quot;
        person.sayThankYou(NAME) shouldBe &amp;quot;Thank you admin&amp;quot;

        hello.javaClass.toString() shouldBe &amp;quot;class Person&amp;quot;
        person.javaClass.toString() shouldBe &amp;quot;class Person$\$SpringCGLIB$$0&amp;quot;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;BeanPostProcessor를 통해 빈의 이름이 &lt;code class=&quot;language-text&quot;&gt;p&lt;/code&gt;로 시작하는 빈만 프록시를 감싸서 반환하도록 하였고, Pointcut을 통해 &lt;code class=&quot;language-text&quot;&gt;sayH&lt;/code&gt;로 시작하는 메서드만 부가기능이 실행되도록 지정하였다.&lt;br&gt;
위의 테스트 코드를 보면 &lt;code class=&quot;language-text&quot;&gt;hello&lt;/code&gt;로 등록된 빈은 프록시가 적용되지 않았고, &lt;code class=&quot;language-text&quot;&gt;person&lt;/code&gt;으로 등록된 빈만 프록시가 적용되었고 &lt;code class=&quot;language-text&quot;&gt;sayH&lt;/code&gt;로 시작하는 메서드만 부가기능이 실행되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;[before] pattern = p, beanName = hello, bean = class Person
[after] pattern = p, beanName = hello, bean = class Person
[before] pattern = p, beanName = person, bean = class Person
[after] pattern = p, beanName = person, bean = class Person
[after - proxy] target = class Person, proxy = class Person$$SpringCGLIB$$0&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAADgklEQVR42kWUW08bVxSF+SV97w/pP6jUh6p96UMfqt4rRVVBIUJqBVHUpkmUNKoCBQoUCIGWQoOpgTiAsQ2+GzMY29gY3y9jsD22Z+br9pi2Iy3tM3POWbPXXvucAdo1LLSqoPWRVAJEAy7Ojr0oYY/1njjxE4t6SZwGOY0cUiucQ1fFbJVlr6BdtXgG/iOkAea1FU+iPrweFz6Pm6D3iKDPS+w4RPL4WBAh7HZTzWdlfUfWGxK7Foep9Qj1OkYxQ+P5KtqqDW3NTmfuOUbiGUZtEqP0DIrjVHIPeVn+CVt1AnttgqXSY14GhmBpCNM120+qWxdCSZtanoZjj4LNTsn+ipZrF/V8kUJilnxsimZumXJhie30HKvRCdZPJtgqLBKROfYF4x/Q3Z9GVcv/SlYl8RaaWaetX8FVBT1fQsvkaAn0fFHWtEAXiQ2Zb0ppDJFpitzek3TTfjFMIXN2Q2hIlolLOMrB2gZsPoKejIMZidNojik8NhtbuRqvsxUc2TKbmQrZ8A74X8DsZ3Qc45RLWQYMo4ppO4NPduFOAL56iukdoxm5Tyv8I1rwLsXwE578ts7gYY5hT8bCF6483hX58cxHGDtPMRpFug2RXEqd0fnuEEIZyV2TLB2YsQB5n5MzxwYp5zadeAhzb4Pr8gX1XIJGKWXB0DXLZV1gGr0uaTBQy6XoPvCLvJS4dIWeTqKHvegRPy2/h25vHPLRTR1L2WoYWgWjLTXuVmldROn4XXDuxVSOrEREstRwN4n55QHcdhL+OsXEIKzcM/njB5gfMRn/GH6e32KmcIeZ1CgzF3d5fvkt9x9/yJvv/Mrbn69QSCXAsd43xTBV9FwRM5hGDZU4dRcJbMUJbicIbMc5d6vE4wq+tA3fuR133EY0vYliX8QVK/LumJNbUz7qjj/7hKYuLpvSDvROSv0GUhOu/ofZlpJI63Tke7cp0zX0gNuq4bLjjPdGHeT+WugTjhwE+PSVl8H9ILf2QwztHlJw3YOoOK78AsokHXH6kU/h+6jKg5MKDyN5vrk9y1tDDt54f42F/Ti6c6NPOB1RGNk7YmTHyfD2AaO7bvLRBbrKArXwAqqgGZ3n71OF+UiapXCS5dMMi5PrjA3P4f59E2JizOsbQkuuOKhexlAvlP7NIQe+3azTlFPTEHS0a+t26daz0gxZ9EbhZp/ERg6zlJZY5h9pvCe/RbS6HQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;beanLifeCycle&quot;
        title=&quot;&quot;
        src=&quot;/static/55450b89cb04be0a1dc878607f8d65e2/1cfc2/beanLifeCycle.png&quot;
        srcset=&quot;/static/55450b89cb04be0a1dc878607f8d65e2/3684f/beanLifeCycle.png 225w,
/static/55450b89cb04be0a1dc878607f8d65e2/fc2a6/beanLifeCycle.png 450w,
/static/55450b89cb04be0a1dc878607f8d65e2/1cfc2/beanLifeCycle.png 900w,
/static/55450b89cb04be0a1dc878607f8d65e2/d0143/beanLifeCycle.png 1025w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;
&lt;a href=&quot;https://bootcamptoprod.com/spring-bean-life-cycle-explained/&quot;&gt;출처&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;빈 후처리기는 빈의 생명주기에 대한 두 가지 단계의 훅을 제공한다.&lt;/strong&gt;&lt;br&gt;
스프링은 이 빈 후처리기를 통해 프록시를 자동으로 생성하는 여러가지의 후처리기를 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d9e7026e412a532600e73302b41bac23/f726e/autoProxyCreator.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABUUlEQVR42j1R25aDIBDzL/bmBVEEW1QUQXt2a/f/fyobPO0+5DAmQ8iMWVlJ/EOks0ZeVBCyxewXtNogL8WpF2WNr0JAEg3r8XpFWDy8m6HqFi37spdZVTenmbEj1njD1Q6Y3AQfAuK2YZhS7altWIYJdqQ+ezi/wtE01abtkBU0y9ProjnPhH5iU9jZHM4LQioUr0eZ3KgOi/ecIGKJEX7lydpdLDLDVH2CeIKxbS3hlMZsLEZlcOE4Tvcn5zqNpVMICTSOWuPasYeaaRSyoRIQfL3h/mT1BOuWSRQbVKsxcA2RKdZ1x23/hhu5M6ZuCcsAFfdZnJDIurzEG5f8SeGjqAmO1nQY5gXjEjGuN9YBQvV4p2YmD+tWDM7jQmPN6UrezU9D/pQ7/9Bj2/EIEb9xx88asHPBBxMd5A5yB7m7f/KJo5a+E/+YHBomlQyR8Adx58995AQnBgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;autoProxyCreator&quot;
        title=&quot;&quot;
        src=&quot;/static/d9e7026e412a532600e73302b41bac23/1cfc2/autoProxyCreator.png&quot;
        srcset=&quot;/static/d9e7026e412a532600e73302b41bac23/3684f/autoProxyCreator.png 225w,
/static/d9e7026e412a532600e73302b41bac23/fc2a6/autoProxyCreator.png 450w,
/static/d9e7026e412a532600e73302b41bac23/1cfc2/autoProxyCreator.png 900w,
/static/d9e7026e412a532600e73302b41bac23/21482/autoProxyCreator.png 1350w,
/static/d9e7026e412a532600e73302b41bac23/d61c2/autoProxyCreator.png 1800w,
/static/d9e7026e412a532600e73302b41bac23/f726e/autoProxyCreator.png 2610w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;기본적으로 &lt;code class=&quot;language-text&quot;&gt;DefaultAdvisorAutoProxyCreator&lt;/code&gt;을 빈으로 등록하여 빈 후처리기 가공 시점에 프록시를 자동으로 생성하도록 할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Bean
fun defaultAdvisorAutoProxyCreator() = DefaultAdvisorAutoProxyCreator().apply {
    this.isProxyTargetClass = true
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()&lt;/code&gt;을 통해 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Advisor.class&lt;/code&gt; 타입을 구현하는 모든 빈을 찾고 빈으로 등록된 모든 구현 클래스(타겟)들을 순회하면서 대상선별을 통하여 프록시를 적용한다.&lt;/strong&gt;&lt;br&gt;
스프링 AOP (&lt;code class=&quot;language-text&quot;&gt;org.springframework.boot:spring-boot-starter-aop&lt;/code&gt;) 의존성을 추가한다면 이것조차 작성하지 않아도 된다.&lt;br&gt;
의존성을 추가하면 자동으로 &lt;code class=&quot;language-text&quot;&gt;AnnotationAwareAspectJAutoProxyCreator&lt;/code&gt; 빈 후처리기가 등록되어 빈으로 등록된 Advisor와 &lt;code class=&quot;language-text&quot;&gt;@AspectJ&lt;/code&gt;로 선언된 Advisor도 처리한다.&lt;/p&gt;
&lt;p&gt;이제는 Advisor만 빈으로 등록한다면 자동으로 모든 빈들을 순회하면서 Pointcut에 해당하는 대상이라면 Advice를 적용한 프록시를 등록한다.&lt;br&gt;
프록시 생성 단계와 사용 단계의 Pointcut을 구분할 수 있어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;생성 단계: 프록시 적용 여부 판단&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;AutoProxyCreator는 Pointcut을 사용해서 &lt;strong&gt;해당 빈의 프록시를 생성할 필요가 있는지 없는지 체크한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클래스와 메서드 조건을 모두 비교한다. Pointcut 하나하나에 매칭하여 해당되는 것이 하나라도 있으면 프록시를 생성한다.&lt;/li&gt;
&lt;li&gt;10개의 메서드 중에 하나만 Pointcut 조건에 만족해도 프록시 적용 대상이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용 단계: 어드바이스 적용 여부 판단&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;프록시가 호출되었을 때 부가 기능인 &lt;strong&gt;Advice를 적용할지 판단한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 786px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7f59b67ba794d27885536bf20de85e70/321ea/postProcessor.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.77777777777778%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABr0lEQVR42i2SW2+bQBSE+f9/p1LVh1Z9qSJHfmhc3+rYrmPAhthmIcACe4GvxziLRmcXRrMz5xA0pqS1Fc0nWlfTeY22Jc4bbFHQZjdsrjB5jqsqvOBebVmO1VUl5uNDUBCoCm5VT9N5mtYSXxvCtCbJNMv1C4e/G6r1inzyhF7MeU8S3qKIYxhyyzJet1uiOKbUGlNXBMuTY3V2IqS53hR/dhee50e2seIY7Tj/O6Cmz4TfvpI9/SINIwpxk6YpoYju93t2IprIWUuK4PvklZ/TPVmNrEGex7rXWiu8bsBZtMT2jabrOmpxMnKGgVzakIpr3/dYcRnMw47ZseFSelrjmK7OTBcx8504iPYoId+UYiMurpnCqgPbyRfSxQ+6PCI+JZwksnVOLmwIkgJOqqdqe7llIFXtiFvZURQXnK7RQnyXSJ2xDF0hgwox6shgq884j1x3buB7Ue6NwHLf98MDTt7p9jG53oqQxO6Nkb18d17Oftx7acEI4dynHhjfkuVXVpsls8Vv3uIDxnc0phZhP5Kt9MzI79G3nQzlyHr2IpgR7nbiSkvvauHUI/c/rcZei1tWxqsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;postProcessor&quot;
        title=&quot;&quot;
        src=&quot;/static/7f59b67ba794d27885536bf20de85e70/321ea/postProcessor.png&quot;
        srcset=&quot;/static/7f59b67ba794d27885536bf20de85e70/3684f/postProcessor.png 225w,
/static/7f59b67ba794d27885536bf20de85e70/fc2a6/postProcessor.png 450w,
/static/7f59b67ba794d27885536bf20de85e70/321ea/postProcessor.png 786w&quot;
        sizes=&quot;(max-width: 786px) 100vw, 786px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;등록되어 있는 빈 후처리기가 있다면 스프링 컨테이너는 빈 생성 후 빈 후처리기에게 빈을 전달한다.&lt;/li&gt;
&lt;li&gt;(일반적인) 빈 후처리기는 빈으로 등록된 모든 Advisor내의 Pointcut을 이용해 빈으로 등록된 모든 빈들을 &lt;strong&gt;프록시 적용 대상인지 확인한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;적용 대상이면 (내장된) 프록시 생성기에게 &lt;strong&gt;현재 빈에 대한 프록시를 만들고, 만들어진 프록시에 어드바이저를 연결한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;빈 후처리기는 스프링 컨테이너에게 전달받은 빈이 아니라 &lt;strong&gt;프록시를 컨테이너에게 돌려준다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;스프링-aop-맛보기&quot; style=&quot;position:relative;&quot;&gt;스프링 AOP 맛보기&lt;a href=&quot;#%EC%8A%A4%ED%94%84%EB%A7%81-aop-%EB%A7%9B%EB%B3%B4%EA%B8%B0&quot; aria-label=&quot;스프링 aop 맛보기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;프록시를 생성할 때 직접 빈으로 등록해주지 않고 Advisor만 등록하여도 빈 후처리기가 프록시를 생성해준다는 것을 배웠다.&lt;br&gt;
하지만 Pointcut, Advice, Advisor를 재활용하기 위해서는 각각 빈으로 직접 등록해줘야 하다는 단점이 있다.&lt;/p&gt;
&lt;p&gt;이것도 Spring의 &lt;code class=&quot;language-text&quot;&gt;@Aspect&lt;/code&gt;가 해결해준다. 간단한 예제를 보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Aspect
@Component
class MyAspect {

    /**
     * execution: 메소드 실행 조인 포인트에 대한 매칭을 지정합니다.
     * *: 메소드의 반환 타입을 나타내며, 여기서는 모든 반환 타입을 의미합니다.
     * *..*.*: 첫 번째 *는 모든 패키지를 의미하며, ..는 패키지의 모든 서브 패키지를 포함한다는 의미입니다. 두 번째 *는 모든 클래스를 의미합니다.
     * sayH*: 메소드 이름이 sayH로 시작하는 것을 의미합니다. *는 그 뒤에 오는 어떤 문자열과도 매칭될 수 있음을 나타냅니다.
     * (..): 메소드의 파라미터를 나타내며, 여기서는 어떤 타입과 어떤 개수의 파라미터도 허용한다는 의미입니다.
     */
    @Around(&amp;quot;execution(* *..*.*sayH*(..))&amp;quot;)
    fun execute(joinPoint: ProceedingJoinPoint): Any {
        println(&amp;quot;MyAspect!!!&amp;quot;)
        return joinPoint.proceed()
    }
}
&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JoinPoint&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;어드바이스가 적용될 수 있는 위치를 말한다&lt;/li&gt;
&lt;li&gt;스프링의 프록시 AOP에서 조인 포인트는 메소드의 실행 단계 뿐이다&lt;/li&gt;
&lt;li&gt;타깃 오브젝트가 구현한 인터페이스의 모든 메소드가 조인 포인트가 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aspect&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;한 개 또는 그 이상의 포인트컷과 어드바이스의 조합으로 만들어지며 보통 싱글톤 형태의 오브젝트로 존재한다.&lt;/li&gt;
&lt;li&gt;스프링의 Advisor는 아주 단순한 Aspect라고 볼 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;정리&quot; style=&quot;position:relative;&quot;&gt;정리&lt;a href=&quot;#%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;정리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;프록시를 구현하기 위해 사용되는 &lt;strong&gt;Reflection&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;프록시의 개념&lt;/li&gt;
&lt;li&gt;프록시를 구현하는 두 가지의 방법 (&lt;strong&gt;JDK Dynamic Proxy&lt;/strong&gt; 와 &lt;strong&gt;CGLIB&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;부가기능과 메소드 선별 기능을 스프링이 추상화한 &lt;strong&gt;ProxyFactory&lt;/strong&gt; 와 &lt;strong&gt;ProxyFactoryBean&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;빈 후처리기를 통해 프록시를 편리하게 등록하는 방법&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Aspect&lt;/code&gt; (AspectJ)를 사용하여 프록시를 등록하는 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같은 내용들을 순서대로 알아보았다. 많은 내용이고 더 깊게 조사할 수 있는 부분들도 존재하지만 자주 사용하는 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;과 같은 AOP 기능에 대해 더 가까워질 수 있었다.&lt;br&gt;
이런 프록시에 대한 이해가 없다면 스프링이 제공하는 많은 편리한 기능들을 이해할 수 없고 그저 마법같은 일이라고만 생각하고 넘어갈 것이다.&lt;/p&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;/li&gt;
&lt;li&gt;스프링이 프록시를 만들기 위해 제공하는 두 가지의 방법은 무엇인지?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 글에서는 스프링의 AOP와 AspectJ는 더 자세하게 설명하지 않았다. 준비가 된다면 다른 글로 정리할 예정이다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[제네릭 가변성에 대해]]></title><description><![CDATA[…]]></description><link>https://jdalma.github.io/2024y/generic/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/generic/</guid><pubDate>Fri, 23 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;자바나 코틀린을 사용하면서 언어 내부를 조사한다던지, 자료구조 성격의 클래스를 설계한다던지, 특정 계층에서만 사용할 수 있는 함수를 작성 할때 제네릭을 종종 접한다.&lt;br&gt;
하지만 매번 제네릭에 대한 이해가 부족해 타입 검사기가 왜 거부하는지 이해하지 못하여 원하는대로 작성을 못한 경험이 있다.&lt;br&gt;
그리고 제네릭하면 빠지지 않는 공변,불변,반공변에 대한 내용도 머리에서 정리되지 않아 누군가에게 설명하기도 힘들었다.&lt;/p&gt;
&lt;p&gt;이번에 &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000210397750&quot;&gt;타입으로 견고하게 다형성으로 유연하게&lt;/a&gt;를 읽으면서 제네릭에 대해 구멍나있던 지식들을 촘촘히 채울 수 있었다.&lt;br&gt;
(만약 이런 경험이 있는 개발자라면 이 책을 추천한다.)&lt;/p&gt;
&lt;p&gt;이 글을 통해 아래의 내용을 학습할 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;여러 종류의 다형성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제네릭 가변성이 왜 필요한지&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PECS를 왜 지켜야 하는지&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제네릭 가변성의 종류&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;일단 다형성에 대해 간단하게 알아보자.&lt;/p&gt;
&lt;h1 id=&quot;다형성&quot; style=&quot;position:relative;&quot;&gt;다형성&lt;a href=&quot;#%EB%8B%A4%ED%98%95%EC%84%B1&quot; aria-label=&quot;다형성 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;다형성은 프로그램의 &lt;strong&gt;한 개체가 여러 타입에 속하도록 만드는 것이다.&lt;/strong&gt;&lt;br&gt;
(개체는 값, 함수, 클래스, 메서드 등 여러 가지가 될 수 있다.)&lt;/p&gt;
&lt;p&gt;하나의 값이 여러 타입에 속할 수도 있고, 한 함수를 여러 타입의 함수로 사용할 수도 있는 것이다.&lt;br&gt;
&lt;strong&gt;다형성은 거의 모든 정적 타입 언어에서만 발견할 수 있는 매우 널리 사용되는 개념이다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;&quot;어떤 개체에 다형성을 부여하는지&quot;&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;&quot;어떻게 다형성을 부여하는지&quot;&lt;/code&gt; 를 이해하는 것이 중요하다.&lt;/p&gt;
&lt;h2 id=&quot;서브타입에-의한-다형성&quot; style=&quot;position:relative;&quot;&gt;서브타입에 의한 다형성&lt;a href=&quot;#%EC%84%9C%EB%B8%8C%ED%83%80%EC%9E%85%EC%97%90-%EC%9D%98%ED%95%9C-%EB%8B%A4%ED%98%95%EC%84%B1&quot; aria-label=&quot;서브타입에 의한 다형성 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 주제는 객체를 다룰 때 유용하며 &lt;strong&gt;서브타입&lt;/strong&gt; 이라는 개념을 통해 다형성을 실현한다.&lt;br&gt;
서브타입은 타입 사이의 관계이며, &lt;code class=&quot;language-text&quot;&gt;&quot;A는 B이다.&quot;&lt;/code&gt; 라는 설명이 올바르다면 &lt;code class=&quot;language-text&quot;&gt;A는 B의 서브타입&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;B는 A의 슈퍼타입&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;A는 B의 서브타입일 때 B 타입의 부품을 A 타입의 부품으로도 간주할 수 있게 하는 기능이 서브타입에 의한 다형성이다.&lt;br&gt;
즉, &lt;strong&gt;슈퍼타입이 요구되는 자리에 서브타입이 위치하더라도 타입 검사기가 문제삼지 않는다는 것이다.&lt;/strong&gt;&lt;br&gt;
반대로 서브타입이 요구되는 자리에는 슈퍼타입이 위치할 수는 없다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 451px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ab487c944bf8a83a56f771c3d2cfa5c7/38070/subtype.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACKElEQVR42lWTSY/TQBCF/f+Pc+AGB8SVXEAgoaBwgMlCRrNAEme1J453OxnHieO1H9Vle5ix9NRlu/vrV13VCugRAs0jUFZAdBGwI4GNW0A1M0x2KaYk1cqwcjIYQY7gWCDNK17zkqG0QZqXsA4Frv+Y+PVgYTBxMNkmWLrA3BEkQG00swSmVomFncPc58gYXEMVGRyTAnPafeEKfP5xjw+dLt53ehhMfWh+Ac0OsXNsmK7J46MTYmUnUO2SwGCwZLDD86WkVHLM6efCKaGFlOoeMMMTonCJ/DCmMxjRrlLDZhzx96dghkc3IDBlQAzJUjQv550kTGpGqdiOjurQbwBjiOj3C9Xv8jviIcUD+N6G12l+DmW6y7BkGBWAzmrr+DyJQcebemFyC1zuSXe1krvmP/3jOX1YnkXFK18DZzYQ+Et2JqKbZ3dfO1fofnqDLx+v8PPbW1x33yEPadP4hh0jHuEpVPHXIIe6X6e8ZIeA5Rq8o3RXOxjjYPQQ6F2E+nfstz1+b9OupMO4D8fdYO2Rw3P6uihcsWDCkxA3KZ1v6zSlZCzVHsdpgFP4gLl5xjmt2rapQapVELTi2PN0qqRcRKlFjY6thjwWVGnXXWFhJcQQ3ORKe0myouImlbAZgSfUvHKi4bgIAw3H/QJRMMXeVwmyxta2sbRiGGGFtGhuC5rGfr55fGMqhHFBE3Nqg5Idj9cpxpuUYkHnJKAHFbyjIND/dS3jH61mgkoOmwTNAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;subtype&quot;
        title=&quot;&quot;
        src=&quot;/static/ab487c944bf8a83a56f771c3d2cfa5c7/38070/subtype.png&quot;
        srcset=&quot;/static/ab487c944bf8a83a56f771c3d2cfa5c7/3684f/subtype.png 225w,
/static/ab487c944bf8a83a56f771c3d2cfa5c7/fc2a6/subtype.png 450w,
/static/ab487c944bf8a83a56f771c3d2cfa5c7/38070/subtype.png 451w&quot;
        sizes=&quot;(max-width: 451px) 100vw, 451px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Person(val name: String)
class Marathoner(name: String): Person(name)

fun run1(person: Person) { .. }
run1(Person(..))
run1(Marathoner(..))

fun run2(marathoner: Marathoner) { .. }
run2(Person(name))   // 컴파일 에러 !!!
run2(Marathoner(name))&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;오버로딩에-의한-다형성&quot; style=&quot;position:relative;&quot;&gt;오버로딩에 의한 다형성&lt;a href=&quot;#%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9%EC%97%90-%EC%9D%98%ED%95%9C-%EB%8B%A4%ED%98%95%EC%84%B1&quot; aria-label=&quot;오버로딩에 의한 다형성 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;같은 이름의 함수들을 매개변수 타입을 서로 다르게 하여 여러 개 정의하는 것이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun aging(person: Person) = Person(person.age + 1)
fun aging(marathoner: Marathoner) = Marathoner(marathoner.age + 1)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;aging&lt;/code&gt; 두 함수 중에 어느 것을 호출할지는 언어 수준에서 자동으로 결정된다.&lt;br&gt;
함수 선택의 가장 기본적인 규칙은 &lt;strong&gt;인자의 타입에 맞는 함수를 고른다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;예제는 정수를 관리하는 &lt;code class=&quot;language-text&quot;&gt;Numbers&lt;/code&gt; 클래스와 양수의 인덱스를 따로 가지는 &lt;code class=&quot;language-text&quot;&gt;PositiveNumbers&lt;/code&gt; 클래스가 있다고 가정하자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Numbers(val elements: List&amp;lt;Int&amp;gt;)
class PositiveNumbers(elements: List&amp;lt;Int&amp;gt;): Numbers(elements) {
    val positiveNumberIndexes: List&amp;lt;Int&amp;gt; = .. // 합계를 빠르게 계산하기 위해 양수의 index를 저장
}

fun positiveNumberSum(numbers: Numbers): Int = ..
fun positiveNumberSum(numbers: PositiveNumbers): Int = ..

// 각 타입에 맞는 함수가 실행된다. 
positiveNumberSum(Numbers(..))
positiveNumberSum(PositiveNumbers(..))

// Numbers가 정적 타입, PositiveNumber가 동적 타입인 경우 정적 선택을 우선한다. (static dispatch)
val numbers: Numbers = PositiveNumbers(..)
positiveNumberSum(numbers)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;오버로딩된 양수의 사이즈를 반환하는 &lt;code class=&quot;language-text&quot;&gt;positiveNumberSum()&lt;/code&gt; 함수를 확인할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;인자의 타입에 맞는 함수를 고른다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인자의 타입에 가장 특화된 함수를 고른다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;함수를 고를 때는 인자의 정적 타입을 우선으로 고려한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;2번&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;3번&lt;/code&gt;을 취합하면 &lt;strong&gt;함수는 인자의 동적 타입보다는 정적 타입을 우선 고려하며, 이 정적 타입에 가장 특화된 함수를 고른다.&lt;/strong&gt;&lt;br&gt;
이 이유로 함수 선택 시 정적 타입에 대한 이해가 없다면 버그를 발생시키기 쉽다.&lt;br&gt;
버그를 방지하기 위한 방법은 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;의 서브타입일 때 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;를 위한 함수가 이미 있다면 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;를 위한 같은 이름의 함수를 추가로 정의하지 않는 것이다.&lt;/strong&gt;&lt;br&gt;
즉, 함수 오버로딩은 서로 완전히 다른 타입들의 값을 인자로 받는 함수를 정의하는 용도로 사용하는 게 좋다.&lt;br&gt;
굳이 함수 오버로딩에 서브타입에 의한 다형성을 활용하여 복잡한 상황을 만들지 말자. (메서드 오버로딩도 동일하다.)&lt;/p&gt;
&lt;h2 id=&quot;오버라이딩에-의한-다형성&quot; style=&quot;position:relative;&quot;&gt;오버라이딩에 의한 다형성&lt;a href=&quot;#%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9%EC%97%90-%EC%9D%98%ED%95%9C-%EB%8B%A4%ED%98%95%EC%84%B1&quot; aria-label=&quot;오버라이딩에 의한 다형성 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;메서드 오버라이딩은 클래스를 상속해서 자식 클래스에 메서드를 새로 정의할 때 메서드의 이름과 매개변수 타입을 부모 클래스에 정의되어 있는 메서드와 똑같게 정의하여 &lt;strong&gt;자식 클래스에 특화된 방법을 정의하는 방법이다.&lt;/strong&gt;&lt;br&gt;
양수의 개수를 반환하는 &lt;code class=&quot;language-text&quot;&gt;Numbers&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;length()&lt;/code&gt;함수를 최적화한 &lt;code class=&quot;language-text&quot;&gt;PositiveNumbers&lt;/code&gt;가 있다고 가정하자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Numbers(val elements: List&amp;lt;Int&amp;gt;) {
    open fun length(): Int = ..
}
class PositiveNumbers(elements: List&amp;lt;Int&amp;gt;): Numbers(elements) {
    val positiveNumberIndexes: List&amp;lt;Int&amp;gt; = ..

    override fun length(): Int = ..
}

val elements = listOf(1,2,3,4)

// 각 타입이 소유하고 있는 메서드가 실행된다.
val numbers: Numbers = Numbers(elements)
numbers.length()
val positiveNumbers: PositiveNumbers = PositiveNumbers(elements)
positiveNumbers.length()

// 오버라이딩의 경우 동적 선택 (dynamic dispatch)을 우선한다.
val numbers2: Numbers = PositiveNumbers(elements)
numbers2.length()&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;인자의 타입에 맞는 메서드를 고른다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인자의 타입에 가장 특화된 메서드를 고른다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메서드를 고를 때는 인자의 정적 타입을 우선으로 고려한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메서드를 고를 때는 수신자의 동적 타입도 고려한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;오버로딩과 다르게 &lt;strong&gt;오버라이딩은 동적 타입에 대해 더 특화된 메서드가 선택되기 때문에, 정적 타입에 상관없이 언제나 그 특화된 동작이 사용되도록 만들 수 있다.&lt;/strong&gt;&lt;br&gt;
즉, 함수 선택은 인자의 정적 타입만 고려하지만 &lt;strong&gt;메서드 선택은 인자의 정적 타입도 고려하고 &lt;code class=&quot;language-text&quot;&gt;수신자(메서드를 호출하는 객체)의 동적 타입&lt;/code&gt;도 고려된다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;매개변수에-의한-다형성&quot; style=&quot;position:relative;&quot;&gt;매개변수에 의한 다형성&lt;a href=&quot;#%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EC%97%90-%EC%9D%98%ED%95%9C-%EB%8B%A4%ED%98%95%EC%84%B1&quot; aria-label=&quot;매개변수에 의한 다형성 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;매개변수에 의한 다형성은 &lt;strong&gt;타입 매개변수를 통해 다형성을 만드는 기능으로, 제네릭스라고도 부른다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun &amp;lt;T&amp;gt; choose(v1: T, v2: T): T {
   println(v1)
   println(v2)
   return if(readln() == &amp;quot;Y&amp;quot;) v1 else v2
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 매개변수 타입 표시와 결과 타입 표시에 사용했다. 이와 같이 한 개 이상의 타입 매개변수를 가지는 함수를 &lt;strong&gt;제네릭 함수&lt;/strong&gt;라고 부른다.&lt;br&gt;
타입 매개변수를 추가할 수 있는 곳은 함수뿐이 아니라 타입에 타입 매개변수를 추가하여 &lt;strong&gt;제네릭 타입&lt;/strong&gt;을 지정할 수 있고,&lt;br&gt;
타입 매개변수를 가진 클래스를 정의하여 &lt;strong&gt;제네릭 클래스&lt;/strong&gt;도 만들 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 제네릭 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;가 아무 타입이나 될 수 있기 때문에 특정 타입에서 제공하는 기능을 사용할 수 없다.&lt;br&gt;
&lt;strong&gt;타입 매개변수로 지정된 타입은 함수 또는 클래스 안에서 특정 능력이 필요한 자리에 사용된다면 제네릭으로 선언할 필요가 없다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;두-다형성의-만남&quot; style=&quot;position:relative;&quot;&gt;두 다형성의 만남&lt;a href=&quot;#%EB%91%90-%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%98-%EB%A7%8C%EB%82%A8&quot; aria-label=&quot;두 다형성의 만남 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;다형성의 내용은 이해하기 쉬웠을 것이다. 이제 이 글을 쓴 이유인 제네릭 가변성에 대해 알아보자.&lt;br&gt;
위에서 설명한 &lt;strong&gt;서브타입에 의한 다형성&lt;/strong&gt;과 &lt;strong&gt;매개변수에 의한 다형성&lt;/strong&gt;이 만나게 되면서 복잡한 여러 기능들이 탄생하게 되는데 순서대로 알아보자.&lt;/p&gt;
&lt;h2 id=&quot;제네릭-클래스와-상속&quot; style=&quot;position:relative;&quot;&gt;제네릭 클래스와 상속&lt;a href=&quot;#%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EC%83%81%EC%86%8D&quot; aria-label=&quot;제네릭 클래스와 상속 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;abstract class List&amp;lt;T&amp;gt; {
    abstract fun get(index: Int): T
}
class ArrayList&amp;lt;T&amp;gt;: List&amp;lt;T&amp;gt;() {
    override fun get(index: Int): T = ..
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같은 제네릭 클래스가 있을 때 타입들 사이의 서브타입 관계가 &lt;code class=&quot;language-text&quot;&gt;ArrayList&amp;lt;T&gt;: List&amp;lt;T&gt;&lt;/code&gt;라면 &lt;code class=&quot;language-text&quot;&gt;ArrayList&amp;lt;A&gt;&lt;/code&gt;는 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt;에 등장하는 모든 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;로 바꿔서 만든 타입의 서브타입&lt;/strong&gt; 이라고 이해할 수 있다.&lt;br&gt;
그리고 서브타입을 선언할 때 &lt;strong&gt;특정 타입에 대한 서브타입&lt;/strong&gt;으로 만들수도 있다.&lt;br&gt;
기존의 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;T&gt;&lt;/code&gt;를 구현하는 &lt;code class=&quot;language-text&quot;&gt;ArrayList&amp;lt;T&gt;&lt;/code&gt; 와 다르게 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Boolean&gt;()&lt;/code&gt; 에 대한 클래스와 함수를 필요로한다고 가정해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class AnotherList: List&amp;lt;Boolean&amp;gt;() {
    override fun get(index: Int): Boolean = ..
}

val stringList = ArrayList&amp;lt;String&amp;gt;()
val intList = ArrayList&amp;lt;Int&amp;gt;()
val anotherList = AnotherList()

fun &amp;lt;T&amp;gt; findFirst(list: List&amp;lt;T&amp;gt;): T = ..
fun isExist(list: List&amp;lt;Boolean&amp;gt;): Boolean = ..

findFirst(stringList)
findFirst(intList)
findFirst(anotherList)

isExist(stringList)     // 컴파일 에러 !!!
isExist(intList)        // 컴파일 에러 !!!
isExist(anotherList)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;AnotherList&lt;/code&gt;와 같이 특정 타입에 대한 서브타입을 지정하여 의도를 직관적으로 표현할 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;타입-매개변수-제한&quot; style=&quot;position:relative;&quot;&gt;타입 매개변수 제한&lt;a href=&quot;#%ED%83%80%EC%9E%85-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%EC%A0%9C%ED%95%9C&quot; aria-label=&quot;타입 매개변수 제한 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;제네릭 함수를 정의한다는 것은 여러 타입으로 사용될 수 있는 함수를 만드는 일이니, &lt;strong&gt;인자로 주어질 값이 특별한 능력을 가진다고 가정할 수 없다.&lt;/strong&gt;&lt;br&gt;
반대로 인자가 특별한 능력을 가져야만 한다면 그 함수는 제네릭 함수일 필요가 없다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Person(private val age: Int): Comparable&amp;lt;Person&amp;gt;{
    override fun compareTo(other: Person): Int = compareValuesBy(this, other, Person::age)
}
class Marathoner(age: Int): Person(age)

fun elder(person: Person, other: Person): Person =
    if(person &amp;gt; other) person else other

val person: Person = elder(person1, person2)
val marathoner: Marathoner = elder(marathoner1, marathoner1)    // [1] 컴파일 에러 !!!&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;[1]&lt;/code&gt; 컴파일 에러가 난 부분은 &lt;code class=&quot;language-text&quot;&gt;elder()&lt;/code&gt; 함수의 파라미터로 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;가 사용되어도 서브타입에 의한 다형성으로 컴파일을 통과하지만 기대하는 반환 타입이 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;이기 때문에 컴파일 에러가 발생한다.&lt;br&gt;
실제로 반환 타입인 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;의 서브타입이 아니기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;elder&lt;/code&gt; 함수를 사용하기 위해 제네릭을 적용하려 할 수 있다. 하지만 제네릭 타입은 모든 타입을 수용하기 때문에 &lt;code class=&quot;language-text&quot;&gt;&gt;&lt;/code&gt; 연산 같은 특별한 능력을 사용할 수 없다.&lt;br&gt;
이때 &lt;strong&gt;타입 매개변수 제한의 상한(upper bound)&lt;/strong&gt; 을 지정하여 &lt;strong&gt;&quot;T가 최대 Person 타입까지 커질 수 있다.&quot;&lt;/strong&gt; 라는 의미를 부여할 수 있다.&lt;br&gt;
즉, &lt;strong&gt;T가 Person의 서브타입이다.&lt;/strong&gt; 라고 선언하는 것이다. (제네릭 함수뿐 아니라 제네릭 클래스도 마찬가지다.)&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun &amp;lt;T: Person&amp;gt; elder(person: T, other: T): T =
    if(person &amp;gt; other) person else other

val person: Person = elder&amp;lt;Person&amp;gt;(person1, person2)
val marathoner: Marathoner = elder&amp;lt;Marathoner&amp;gt;(marathoner1, marathoner1)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 상한을 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;T: Person&gt;&lt;/code&gt;으로 지정하여 반환 타입으로 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;를 받을 수 있고, 특별한 &lt;code class=&quot;language-text&quot;&gt;&gt;&lt;/code&gt; 연산을 사용할 수 있게 되었다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class List&amp;lt;T : Person&amp;gt; {
    fun get(index: Int): T = TODO()
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;하지만 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;T&gt;&lt;/code&gt;의 상한을 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;으로 제한했다고 해서 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Marathoner&gt;&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Person&gt;&lt;/code&gt;의 서브타입이라고 보장하진 않는다.&lt;/p&gt;
&lt;h2 id=&quot;둘-이상의-상한-제한&quot; style=&quot;position:relative;&quot;&gt;둘 이상의 상한 제한&lt;a href=&quot;#%EB%91%98-%EC%9D%B4%EC%83%81%EC%9D%98-%EC%83%81%ED%95%9C-%EC%A0%9C%ED%95%9C&quot; aria-label=&quot;둘 이상의 상한 제한 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;기본적으로 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;&gt;&lt;/code&gt;에 하나의 상한만 지정이 가능하지만 &lt;strong&gt;코틀린에서는 둘 이상의 상한이 필요한 경우 &lt;code class=&quot;language-text&quot;&gt;where&lt;/code&gt;을 사용할 수 있다.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;전달된 유형은 절의 모든 조건을 동시에 만족해야한다.&lt;/strong&gt; 아래의 예제를 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 476px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a31fce48b48f12a00340d2c7fc9b87e6/f2205/intersection.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB+0lEQVR42n2Sa3OaUBCG/f9/pf3QtEkn07SNycR+iE2qUVEEhrugCN6IoFyeHjAxTSftzrzs4TDz7LvLNnpawL2k8yDbSJqH4a0way3/0uoo218j6XPmyy1VlEUpHgc1HpQp3ftbWjfXDKU+eZ7zvyifcpzs0QS8vitfvjdkK6oPhahS3RdJSmqqPGodoS6x2iExJBLHIewrhAOFSFLwOjJWT6Gc+WS+R+5NyH2fxvgILOqcTT1W6hW7lUEaaaQLk8dpG7s/wFNnhE5A5ITYQwdj5Ap7OUWaCpclmW1VwMUr4N63iO02VeNZntWudxsTs98jTctj0+l2h28GLzMUUblsjO3XwJ1nkfodhmOFiy/nXF232EQq8l2b88/faDYvOTs75fvXS1zNfwIWL8CRGb3psKqZpgm7TGTh0Br0SbYZ+yxlv0+J1zG+/obDnjY/gPY5WQXxXRZ6i+02JIkDdtslm3kPXcxwNtmwFquyWSU4AjYxDsBCbEaFzCqgG2xQnSX6ZInmx1gjE01WGcgPSKMug+EvZHWMIjpp3xm0fxrctnW6XZs8K/hzb2qHh/eynkNdZTaliON/7t9zJOLPaoaOK1bFcV1mqxVrTaVRPm/lc5UwJHMd8kUozsFRRTgnn8/JgoAyilgaBhfv39E8+cDN6SeaH0+wfrT4DfWnPeKOHHiFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;intersection&quot;
        title=&quot;&quot;
        src=&quot;/static/a31fce48b48f12a00340d2c7fc9b87e6/f2205/intersection.png&quot;
        srcset=&quot;/static/a31fce48b48f12a00340d2c7fc9b87e6/3684f/intersection.png 225w,
/static/a31fce48b48f12a00340d2c7fc9b87e6/fc2a6/intersection.png 450w,
/static/a31fce48b48f12a00340d2c7fc9b87e6/f2205/intersection.png 476w&quot;
        sizes=&quot;(max-width: 476px) 100vw, 476px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface Person
interface Marathoner

class Trainer: Person, Marathoner
class Developer: Person, Marathoner

interface Intersection&amp;lt;T&amp;gt; where T : Person, T : Marathoner

fun main() {
    val person = object : Intersection&amp;lt;Person&amp;gt; {}         // 컴파일 에러 !!!
    val marathoner = object : Intersection&amp;lt;Marathoner&amp;gt; {} // 컴파일 에러 !!!
    val trainer = object : Intersection&amp;lt;Trainer&amp;gt; {}
    val developer = object : Intersection&amp;lt;Developer&amp;gt; {}
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Intersection&amp;lt;T&gt;&lt;/code&gt; 인터페이스를 구현하는 타입은 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt; 둘 다 만족해야 한다.&lt;/p&gt;
&lt;p&gt;아래와 같이 함수의 인자를 동시에 제한하여 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;CharSequence&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Comparable&lt;/code&gt;을 구현하는 타입만 받을 수 있는 함수&lt;/strong&gt;를 만들수도 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun &amp;lt;T&amp;gt; copyWhenGenerator(list: List&amp;lt;T&amp;gt;, threshold: T): List&amp;lt;String&amp;gt;
    where T: CharSequence, T: Comparable&amp;lt;T&amp;gt; = 
        list.filter { it &amp;gt; threshold }.map { it.toString() }

describe(&amp;quot;copyWhenGenerator 함수는&amp;quot;) {
    val param1: Pair&amp;lt;List&amp;lt;String&amp;gt;, String&amp;gt; = listOf(&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;) to &amp;quot;b&amp;quot;
    val param2: Pair&amp;lt;List&amp;lt;StringBuilder&amp;gt;, StringBuilder&amp;gt; = listOf(
        StringBuilder(&amp;quot;A&amp;quot;),
        StringBuilder(&amp;quot;B&amp;quot;),
        StringBuilder(&amp;quot;C&amp;quot;)
    ) to StringBuilder(&amp;quot;B&amp;quot;)

    context(&amp;quot;threshold 보다 큰 값만 반환한다.&amp;quot;) {

        copyWhenGenerator(param1.first, param1.second) shouldBe listOf(&amp;quot;c&amp;quot; , &amp;quot;d&amp;quot;)
        copyWhenGenerator(param2.first, param2.second) shouldBe listOf(&amp;quot;C&amp;quot;)
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;가변성&quot; style=&quot;position:relative;&quot;&gt;가변성&lt;a href=&quot;#%EA%B0%80%EB%B3%80%EC%84%B1&quot; aria-label=&quot;가변성 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;지금까지 제네릭 함수를 정의할 때는 대개 매개변수 타입과 결과 타입의 관계를 유지해야 한다는 목표가 있었다.&lt;/strong&gt;&lt;br&gt;
아래의 &lt;code class=&quot;language-text&quot;&gt;choose()&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;elder()&lt;/code&gt; 같이 받은 타입을 그대로 &lt;strong&gt;반환&lt;/strong&gt;해야 하는 함수는 매개변수에 의한 다형성이 반드시 필요했다.&lt;br&gt;
반면 인자로 받아 &lt;strong&gt;소비&lt;/strong&gt;만하는 &lt;code class=&quot;language-text&quot;&gt;run()&lt;/code&gt;과 같은 함수는 서브타입에 의한 다형성이면 충분하다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Person
class Marathoner : Person()

fun &amp;lt;T&amp;gt; choose(v1: T, v2: T): T = if ( .. ) v1 else v2
fun &amp;lt;T: Person&amp;gt; elder(person: T, other: T): T = if( .. ) person else other

fun run(person: Person) {
    person.age ..
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;br&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;의 서브타입이 맞지만, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Marathoner&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Person&gt;&lt;/code&gt;의 서브타입이 아니기 때문에 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Marathoner&gt;&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Person&gt;&lt;/code&gt; 타입의 리스트를 모두 사용하기 위한 함수는 &lt;code class=&quot;language-text&quot;&gt;averageAge&lt;/code&gt;함수처럼 제네릭 함수로 정의하고 타입 매개변수 제한을 사용하였다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun &amp;lt;T: Person&amp;gt; averageAge(people: List&amp;lt;T&amp;gt;): Int = ..&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;하지만 이 방법대로라면 아래와 같은 귀찮은 점이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt; 타입의 인자를 받는 함수를 정의할 때 마다 매개변수 타입을 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;로 하는 대신, 상한이 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;인 타입 매개변수 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 정의하고 매개변수 타입을 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;T&gt;&lt;/code&gt;로 해야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제네릭 타입의 값을 인자로 받는 모든 함수를 동일하게 제네릭 함수로 만들어야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;그럼 그냥 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;의 서브타입인 것처럼 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Marathoner&gt;&lt;/code&gt;도 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Person&gt;&lt;/code&gt;의 서브타입이면 안 될까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, &lt;strong&gt;&quot;B가 A의 서브타입일 때 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;B&gt;&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;의 서브타입이라고 인정해주면 안될까?&quot;&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Marathoner&gt;&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Person&gt;&lt;/code&gt;의 서브타입이 된다고 가정하고 ReadOnlyList와 ReadWriteList의 예제를 보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Person(val age: Int)
class Marathoner(age: Int) : Person(age)

// [1] 가지고 있는 원소들을 알려줄 뿐, 원소를 추가하거나 제거할 수 없는 리스트다.
abstract class ReadOnlyList&amp;lt;T&amp;gt; {
    abstract fun get(index: Int): T
}

val marathoners: ReadOnlyList&amp;lt;Marathoner&amp;gt; = ..
val people: ReadOnlyList&amp;lt;Person&amp;gt; = marathoners
val person = people.get(0)
person.age ..&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;[1]&lt;/code&gt;의 상황을 보면 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;의 객체들로 구성된 &lt;code class=&quot;language-text&quot;&gt;marathoners&lt;/code&gt; 리스트는 &lt;code class=&quot;language-text&quot;&gt;people&lt;/code&gt;에 대입이 가능하고, &lt;code class=&quot;language-text&quot;&gt;people&lt;/code&gt;에서 꺼낸 원소는 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt; 타입으로 사용할 수 있다.&lt;br&gt;
런타임에는 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt; 객체이지만 타입 검사기가 알 수 있는 타입은 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;이다.&lt;br&gt;
하지만 이 상황은 문제가 되지 않는다. Marathoner는 이미 Person의 서브타입이므로 &lt;strong&gt;Marathoner 객체를 Person 객체처럼 사용해도 문제 없다.&lt;/strong&gt;&lt;br&gt;
즉, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;ReadOnlyList&amp;lt;Marathoner&gt;&lt;/code&gt; 를 &lt;code class=&quot;language-text&quot;&gt;ReadOnlyList&amp;lt;Person&gt;&lt;/code&gt;으로 취급함으로써 일어날 수 있는 일은 Person 객체를 기대한 곳에서 Marathoner 객체가 나오는 것 뿐이다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// [2] 가지고 있는 원소들을 알려주고 새 원소를 추가할 수 있다.
abstract class ReadWriteList&amp;lt;T&amp;gt; {
    abstract fun get(index: Int): T
    abstract fun add(element: T)
}

val marathoners: ReadWriteList&amp;lt;Marathoner&amp;gt; = ..
val people: ReadWriteList&amp;lt;Person&amp;gt; = marathoners
people.add(Person(..))

val marathoner: Marathoner = marathoners.get(0)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;[2]&lt;/code&gt;의 상황을 보면 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;의 객체들로 구성된 &lt;code class=&quot;language-text&quot;&gt;marathoners&lt;/code&gt; 리스트를 &lt;code class=&quot;language-text&quot;&gt;people&lt;/code&gt;에 대입하고, &lt;code class=&quot;language-text&quot;&gt;people&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;을 추가할 때 &lt;strong&gt;Marathoner와 Person이 같은 리스트를 나태나는 문제가 발생한다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;people&lt;/code&gt;에 Marathoner 객체와 Person 객체가 동시에 존재할 수 있게 되면서 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;marathoners&lt;/code&gt;에 Person 객체가 추가되게 되었다.&lt;/strong&gt;&lt;br&gt;
타입 검사기는 &lt;code class=&quot;language-text&quot;&gt;marathoners&lt;/code&gt;에서 꺼낸 원소의 타입은 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;라고 믿고있지만 실제로는 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt; 객체가 반환될 수 있기 때문에 &lt;strong&gt;타입 안전성을 깨트리는 큰 문제다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;즉, B가 A의 서브타입일 때&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ReadOnlyList&amp;lt;B&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;ReadOnlyList&amp;lt;A&gt;&lt;/code&gt;의 서브타입이 가능하지만,&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;B&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;A&gt;&lt;/code&gt;의 서브타입이 불가능하다는 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;원소 읽기만 허용하면 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;B&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;의 서브타입이 될 수 있지만, 원소 쓰기를 허용하면 서브타입이 될 수 없다.&lt;br&gt;
이 예제를 이해하면 &lt;strong&gt;PECS : producer-extends, consumer-super&lt;/strong&gt; 원칙이 등장한 이유를 이해할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 내용들로 알 수 있는 사실은 &lt;strong&gt;&quot;어떤 제네릭 타입은 타입 인자의 서브타입 관계를 보존하지만, 어떤 제네릭 타입은 그렇지 않다.&quot;&lt;/strong&gt; 라는 것이다.&lt;br&gt;
그러므로 제네릭 타입과 타입 인자 사이의 관계를 분류할 수 있다. 이 분류를 &lt;strong&gt;가변성&lt;/strong&gt; 이라고 부른다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;가변성은 제네릭 타입과 타입 인자 사이의 관계를 뜻하며, 제네릭 타입 사이의 서브타입 관계를 추가로 정의하는 기능이다.&lt;/strong&gt;&lt;br&gt;
(하나의 제네릭 타입에서 타입 인자만 다르게 하여 얻은 타입들 사이의 서브타입 관계를 만든다.)&lt;/p&gt;
&lt;h3&gt;공변&lt;/h3&gt;
&lt;p&gt;제네릭 타입이 타인 인자의 서브타입 관계를 보존하는 것이며, &lt;strong&gt;타입 인자가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;에서 서브타입인 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;로 변할 때 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt; 역시 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;B&gt;&lt;/code&gt;로 변한다고 말할 수 있다.&lt;/strong&gt;&lt;br&gt;
그래서 &quot;제네릭 타입이 타입 인자와 함께 변한다&quot;는 뜻을 담아, 이런 가변성을 &lt;strong&gt;공변 (convariance)&lt;/strong&gt; 라고 부른다.&lt;/p&gt;
&lt;h3&gt;불변&lt;/h3&gt;
&lt;p&gt;제네릭 타입이 타입 인자의 서브타입 관계를 무시하는 것이며, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;의 서브타입이더라도 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;B&gt;&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;는 아무런 관계가 없는 것이다.&lt;/strong&gt;&lt;br&gt;
서로 다른 타입인 것이다. 따라서 &quot;타입 인자가 서브타입으로 변해도 제네릭 타입은 서브타입으로 안 변한다&quot;는 뜻을 담아, 이런 가변성을 &lt;strong&gt;불변 (invariance)&lt;/strong&gt; 이라 부른다.&lt;/p&gt;
&lt;h3&gt;반변&lt;/h3&gt;
&lt;p&gt;반변을 이해하기 위해서는 먼저 &lt;strong&gt;함수와 서브타입&lt;/strong&gt; 관계에 대해서 이해해야 한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;open class Person
class Marathoner : Person()

fun selectBySuperType(selector: (Person) -&amp;gt; Person) {
    val person1: Person = selector(Person())
    val person2: Person = selector(Marathoner())
}

fun selectBySubType(selector: (Marathoner) -&amp;gt; Person) {
    val person1: Person = selector(Person())    // 컴파일 에러 !!!
    val person2: Person = selector(Marathoner())
}

val superTypeConsumer: (Person) -&amp;gt; Person = ..
val subTypeConsumer: (Marathoner) -&amp;gt; Person = ..

selectBySubType(superTypeConsumer)
selectBySubType(subTypeConsumer)

selectBySuperType(superTypeConsumer)
selectBySuperType(subTypeConsumer)      // 컴파일 에러 !!!&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;(결과 타입은 서브타입 관계를 유지하기 때문에 입력으로 받는 타입에 집중하자.)&lt;br&gt;
위의 예제를 보면 &lt;code class=&quot;language-text&quot;&gt;(Person) -&gt; Person&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;(Marathoner) -&gt; Person&lt;/code&gt;의 서브타입이 가능하다.&lt;br&gt;
&lt;strong&gt;&quot;사람을 인자로 받을 수 있는 함수는 마라토너를 인자로 받을 수 있는 함수다.&quot;&lt;/strong&gt; 가 성립되기 때문이다.&lt;br&gt;
즉, &lt;code class=&quot;language-text&quot;&gt;selectBySubType&lt;/code&gt; 함수에 &lt;code class=&quot;language-text&quot;&gt;superTypeConsumer&lt;/code&gt; 람다가 전달 가능하다는 것이다.&lt;/p&gt;
&lt;p&gt;하지만 그 반대인 &lt;code class=&quot;language-text&quot;&gt;(Marathoner) -&gt; Person&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;(Person) -&gt; Person&lt;/code&gt;의 서브타입이 아니다.&lt;br&gt;
&lt;strong&gt;&quot;마라토너를 인자로 받을 수 있는 함수는 사람을 인자로 받을 수 있는 함수다.&quot;&lt;/strong&gt; 가 성립되지 않는다.&lt;br&gt;
즉, &lt;code class=&quot;language-text&quot;&gt;selectBySuperType&lt;/code&gt; 함수에 &lt;code class=&quot;language-text&quot;&gt;subTypeConsumer&lt;/code&gt; 람다를 전달할 수 없다는 것이다.&lt;/p&gt;
&lt;p&gt;첫 번째 &quot;컴파일 에러&quot;는 &lt;strong&gt;함수가 하위 타입 인스턴스를 기대하는데 상위 타입 인스턴스를 전달하려고 했기 때문에 발생한다.&lt;/strong&gt;&lt;br&gt;
두 번째 &quot;컴파일 에러&quot;는 &lt;strong&gt;함수가 상위 타입 인스턴스를 기대하는데 &lt;code class=&quot;language-text&quot;&gt;더 구체적인 하위 타입을 처리하는 함수를 전달하려고 했기 때문에 발생&lt;/code&gt;한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 796px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b8fd32776c679261a601b162ffd247e4/d48f1/functionSubtype.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC6UlEQVR42p1U25KbOhD0jywgCQlxR1wMGHvt7Fad85L//51OS/iyyUMqyUPXGIFHPT09c+jrGpW1aFuHVFu8RQJRLP8Zh0xnSI2FzgporUPCkNTHf0lYMFlrHXJTIi8KWJszNihsBpOqv2Z8mJsW7+6KS3dF13ZYlhnODTgeF/RdhabMEP9NQp2SibQwykJJzbItJGPK8zQ1RLrLwI/fYvGUZJdFBuzv928OxuSITB0QmwYJNY1TgUSl1DZH7C8xBZRgFCZcaDKLLCsheJnIFKUxgZSSKQ5dVSErHbrxhKZf4KYZ8+mE47JhY/zvcsVtu6J1DpnRSJIUUmkiQyIVL1SsiGfCv1M49HUFm9cYJ2o2TBiPK97fP7BdvuF82vBtvOA6XDGOM4q2eDbp2azoLsWjZMGSYj4kQvEGGRgIf5tIA+KI5/H+O0rkFw0f+r0ShqZYWyCxHUTRQ+YO0moIrejLHIra2LzC3jg2ifDfZ7aE0gbCUj9GK3NoEgsJh6aGrRzG+Qw3rpjmleXSRjeWPbF8d8PRrXCDg1I7U19VYE/9BCsTrMrrFxK2RRludP2Epu0Zj1jWM9bThYkmLPUJYzWzYT1Z0ehve4kPywQ97wgJE0/1PmpR/OtUiJf4ifx5aqKvDfli7KqsocoRupmhqxGm5kxzOsq6Q1U1qKuWY1ij0DxPLceypA8LyExD5Sksz2zKZ5Yeujxw2xTUsKdeAzGvGy7XD5zfb/i83fD9839sywUdNZRSIqZWHp6xRxyrgCfD0rve+sVQo24chnFBR+2O/Ygzm7G2R2rbIc30yy6/m2UhDW9SO+KXp+K7TnH0YHWPjz8Hdi88E7Z1A0P9Mreh7GdUPZl2LdqmR1l2aMi6NN5n1Cr3662CNIZ+VfC7NFcF/Zm+utxxBxZlGyzjy93ON4Ljdjrjc/vAxLOqq+k7eS/3NXLRF7u8Fmzmt0cZJqKsuqBfy324dhN35IyhGZBzgXgD/8k+/AFhGEPuNY5zJQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;functionSubtype&quot;
        title=&quot;&quot;
        src=&quot;/static/b8fd32776c679261a601b162ffd247e4/d48f1/functionSubtype.png&quot;
        srcset=&quot;/static/b8fd32776c679261a601b162ffd247e4/3684f/functionSubtype.png 225w,
/static/b8fd32776c679261a601b162ffd247e4/fc2a6/functionSubtype.png 450w,
/static/b8fd32776c679261a601b162ffd247e4/d48f1/functionSubtype.png 796w&quot;
        sizes=&quot;(max-width: 796px) 100vw, 796px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;람다 \ 함수 인자&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;selectSuperToSuper&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;selectSubToSuper&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;selectSuperToSub&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;selectSubToSub&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;(Super) -&gt; Super&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;(Super) -&gt; Sub&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;(Sub) -&gt; Sub&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;(Sub) -&gt; Super&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;즉, A가 B의 서브타입일 떄 &lt;code class=&quot;language-text&quot;&gt;B → C&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;A → C&lt;/code&gt;의 서브타입이며 그 반대는 성립하지 않는다. 따라서 &lt;strong&gt;함수 타입은 매개변수 타입의 서브타입 관계를 뒤집는다.&lt;/strong&gt;&lt;br&gt;
결과 타입의 서브타입 관계가 유지된다는 사실(반환 타입의 공변성)은 나름 직관적인것에 비해, 매개변수 타입의 서브타입 관계가 뒤집히는게(매개변수의 반공변성) 이상할 수 있지만 논리적으로 타당하다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;selectSubToSuper&lt;/code&gt;의 함수는 4가지의 람다를 모두 허용하는 이유가 &lt;strong&gt;&quot;함수 타입은 매개변수 타입의 서브타입 관계를 뒤집고 결과 타입의 서브타입 관계는 유지하기 때문이다.&quot;&lt;/strong&gt; ⭐️&lt;/p&gt;
&lt;p&gt;즉, 함수의 결과 타입과 실제 결과 타입 사이의 관계는 &lt;strong&gt;공변&lt;/strong&gt;이다. 한편 함수 매개변수 타입과 실제 매개변수 타입 사이의 관계는 공변도 불변도 아니다.&lt;br&gt;
여기서 &lt;strong&gt;제네릭 타입이 타입 인자의 서브타입 관계를 뒤집는 가변성이 등장한다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;✋ 잠깐, 왜 함수의 매개변수에 대해서는 반공변성이 적용될까?&lt;/strong&gt;&lt;br&gt;
리스코프 치환 원칙에 따르면 &lt;strong&gt;&quot;서브타입은 슈퍼타입을 대체할 수 있어야 한다.&quot;&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;(Super) -&gt; Super&lt;/code&gt;가 필요한 자리에 &lt;code class=&quot;language-text&quot;&gt;(Sub) -&gt; Super&lt;/code&gt;를 사용할 수 있어야 한다면 &lt;code class=&quot;language-text&quot;&gt;(Sub) -&gt; Super&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;Super&lt;/code&gt;도 처리할 수 있어야 한다.&lt;br&gt;
하지만 실제로는 더 구체적인 타입인 &lt;code class=&quot;language-text&quot;&gt;Sub&lt;/code&gt;가 더 일반적인 타입인 &lt;code class=&quot;language-text&quot;&gt;Super&lt;/code&gt;를 처리할 수 없기 때문에 서브타입 함수가 슈퍼타입 함수처럼 사용되기 위해서는 매개변수의 타입이 더 일반적이어야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;결과 타입을 &lt;code class=&quot;language-text&quot;&gt;C&lt;/code&gt;로 고정할 때 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;의 서브타입이면 &lt;code class=&quot;language-text&quot;&gt;B → C&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;A → C&lt;/code&gt;의 &lt;strong&gt;슈퍼 타입이다.&lt;/strong&gt;&lt;br&gt;
타입 인자가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;에서 서브타입인 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;로 변할 때 &lt;code class=&quot;language-text&quot;&gt;A → C&lt;/code&gt;는 타입 인자와는 반대 반향으로 움직여 슈퍼 타입인 &lt;code class=&quot;language-text&quot;&gt;B → C&lt;/code&gt;로 변한다고도 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/be7b1988a84f607240aa2c956fdab9a4/951a4/variance.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 17.77777777777778%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAlUlEQVR42k2PCw6AIAxDuf9BNX5Q8Q8qNW8JxiXNxsrazS3LonEc1fe9rusy5Jy/fJ6n5nk23Pctgj71cRwKIXw8Mw5imiZVVWWiwzDoeR6r13VVSkn7vpswNYDnH3Olt22buq6TK44EjmUL3GKMJto0jWXvvdq2Na78R/z/djQ4u65rG2QzerixAcEb/ANxUHguwOwFoNA2xvhlrbwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;variance&quot;
        title=&quot;&quot;
        src=&quot;/static/be7b1988a84f607240aa2c956fdab9a4/1cfc2/variance.png&quot;
        srcset=&quot;/static/be7b1988a84f607240aa2c956fdab9a4/3684f/variance.png 225w,
/static/be7b1988a84f607240aa2c956fdab9a4/fc2a6/variance.png 450w,
/static/be7b1988a84f607240aa2c956fdab9a4/1cfc2/variance.png 900w,
/static/be7b1988a84f607240aa2c956fdab9a4/21482/variance.png 1350w,
/static/be7b1988a84f607240aa2c956fdab9a4/951a4/variance.png 1355w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;종합해보자면 &lt;code class=&quot;language-text&quot;&gt;ReadOnlyList&lt;/code&gt;는 원소 타입에 대해 &lt;strong&gt;공변&lt;/strong&gt; 이며, &lt;code class=&quot;language-text&quot;&gt;ReadWriteList&lt;/code&gt;는 &lt;strong&gt;불변&lt;/strong&gt; 이다.&lt;br&gt;
마지막으로 함수 타입은 매개변수 타입에 대해서는 &lt;strong&gt;반변&lt;/strong&gt; 이고, 결과 타입에 대해서는 &lt;strong&gt;공변&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;h3&gt;각 제네릭 타입의 가변성을 결정하는 일반적인 방법&lt;/h3&gt;
&lt;p&gt;논의를 간단하게 만들기 위해 타입 매개변수가 하나뿐인 제네릭 타입만 고려한다.&lt;br&gt;
제네릭 타임의 이름은 &lt;code class=&quot;language-text&quot;&gt;G&lt;/code&gt;, 타입 매개변수의 이름은 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;라고 하자.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;G&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 출력에만 사용하면 &lt;strong&gt;공변&lt;/strong&gt; , 입력에만 사용하면 &lt;strong&gt;반변&lt;/strong&gt; , 출력과 입력 모두에 사용하면 &lt;strong&gt;불변&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;G&lt;/code&gt;에 해당하는 타입&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 출력에 사용&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 입력에 사용&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;&lt;strong&gt;가변성&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;ReadOnlyList&amp;#x3C;T&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;공변&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;ReadWriteList&amp;#x3C;T&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;불변&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Int -&gt; T&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;공변&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;T -&gt; Int&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;❌&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;반변&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;즉, &lt;strong&gt;타입 매개변수를 출력에만 사용하는지, 입력에만 사용하는지, 둘 모두에 사용하는지 보면 가변성을 판단할 수 있다.&lt;/strong&gt;&lt;br&gt;
타입 매개변수를 사용한 곳에 따라 달라진다는 것이다.&lt;/p&gt;
&lt;h3 id=&quot;정의할-때-가변성-지정하기&quot; style=&quot;position:relative;&quot;&gt;정의할 때 가변성 지정하기&lt;a href=&quot;#%EC%A0%95%EC%9D%98%ED%95%A0-%EB%95%8C-%EA%B0%80%EB%B3%80%EC%84%B1-%EC%A7%80%EC%A0%95%ED%95%98%EA%B8%B0&quot; aria-label=&quot;정의할 때 가변성 지정하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;가변성은 각 제네릭 타입의 고유한 속성이다. 따라서 &lt;strong&gt;제네릭 타입을 정의할 때 가변성을 지정하는게 가장 직관적이다.&lt;/strong&gt;&lt;br&gt;
개발자는 제네릭 타입의 각 매개변수에 가변성을 표시함으로써 공변, 반변, 불변 중 하나를 고를 수 있다.&lt;/p&gt;
&lt;h3&gt;불변&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;abstract class List&amp;lt;T&amp;gt; {
    abstract fun length(): Int
    abstract fun get(index: Int): T
    abstract fun add(element: T)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;공변&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;abstract class ReadOnlyList&amp;lt;out T&amp;gt; {
    abstract fun get(index: Int): T
}

val marathoners: ReadOnlyList&amp;lt;Marathoner&amp;gt; = ..
val people: ReadOnlyList&amp;lt;Person&amp;gt; = marathoners
val person = people.get(0)

fun averageAge(people: ReadOnlyList&amp;lt;Person&amp;gt;): Int = ..

averageAge(marathoners)
averageAge(people)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ReadOnlyList&amp;lt;out T&gt;&lt;/code&gt;는 해당 타입 매개변수가 출력에만 사용됨을 뜻하며 원소를 추가할 수 없는 대신 공변인 리스트를 정의할 수 있다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 출력에만 사용한다고 했으니, &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 메서드 결과 타입으로 사용할 수 있는 있어도 매개변수 타입으로 사용할 수는 없다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ReadOnlyList&lt;/code&gt;는 &lt;strong&gt;공변이므로 타입 인자의 서브타입 관계를 보존한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이제 &lt;code class=&quot;language-text&quot;&gt;averageAge&lt;/code&gt; 함수를 제네릭 함수로 만들지 않고 타입 매개변수 제한없이 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 783px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/16c3998ca212b2bd0b7a9d4af61c113e/e51a6/genericOut.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAACIElEQVR42lWSCU8aURhF+f9/o6klrbUa05q2VtkGZgWhpGosGlkEHBHQYVhmOb2DTVMnuXnztvO+9+7NkaZkX5rGJKsJK/+c8WWBUfuI+9YhfnOfSWufzfUx0W2BqF8lHp8RP15t12f7/gK2TS5NE+KgTzS0SfsG65syF8VdLku7dMx9ru09OvZHknENJjb4JsmgRNIvEXdLRHe29g+ESgQVMPKbpL0y3DlSXYu1aWIST6pcuHnaVp6rn59oWTs0K284V39zb0iCScl9Bj7Rf4s02ajCngHDumAecb9C7JdJZhbJ3GEl8GJUZvrrmKl3wswqMKsWCB2DsF4lbKm9KLK+E7z/neihTY5hQzCXdFBTVao0EHzuwrNHMrUJmyVCyxTEYSGt6h6BrXHX2Y4tbUd9g2XnlI2g/4BxX5XNdd0n7wW4cAmvdLrj4RslrHdv8d7naXx4j5PfwZZc9Ttfv7BpNFh4NRY3h7pyt0IGTQcW0bioytwXaOCx0VxQq7D26qwarqpzWdadV+2qoTm3wdw8UEK8zJQWidxi4MqQDFraviFPWZUe69uKGsmqsXQFcD2W2QHSUlcOzAJza49QccqczqXyOrM9GgnYq5B2DeLeKfGwSOIbpDO97UOZVU9mXP4gaH8jODsiaH7muX1AeCOHgy7bzGSxeR3sR+LZb2JFIB55ck7x6CrMvaLyJg3LiodJ5LtEs3OS9fQlf/8F+w96RNZi2cqAkAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;genericOut&quot;
        title=&quot;&quot;
        src=&quot;/static/16c3998ca212b2bd0b7a9d4af61c113e/e51a6/genericOut.png&quot;
        srcset=&quot;/static/16c3998ca212b2bd0b7a9d4af61c113e/3684f/genericOut.png 225w,
/static/16c3998ca212b2bd0b7a9d4af61c113e/fc2a6/genericOut.png 450w,
/static/16c3998ca212b2bd0b7a9d4af61c113e/e51a6/genericOut.png 783w&quot;
        sizes=&quot;(max-width: 783px) 100vw, 783px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;abstract class ReadOnlyList&amp;lt;T&amp;gt; {
    abstract fun get(index: Int): T
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 선언된 List는 원소를 추가할 수도 없는 주제에 불변이기까지 한 불편한 리스트일 뿐이다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt;를 입력에 사용하는 메서드를 추가하려고 계획 중인 게 아니라면, 굳이 이런 리스트를 정의할 필요는 없다.&lt;/p&gt;
&lt;h3&gt;반변&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;abstract class Map&amp;lt;in K, V&amp;gt; {
    abstract fun size(): Int
    abstract fun get(key: K): V
    abstract fun add(key: K, value: V)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;타입 매개변수를 반변으로 만들고 싶을 때는 &lt;code class=&quot;language-text&quot;&gt;in&lt;/code&gt;을 붙여 &lt;strong&gt;그 타입 매개변수를 입력에만 사용한다는 뜻이다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;abstract fun getKey(value: V): K&lt;/code&gt; 이 메서드는 컴파일 에러를 발생시킨다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Map&amp;lt;in K, V&gt;&lt;/code&gt; 클래스는 두 개의 타입 매개변수를 가지며, &lt;code class=&quot;language-text&quot;&gt;in&lt;/code&gt;으로 가변성이 지정된 &lt;code class=&quot;language-text&quot;&gt;K&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;get()&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;add()&lt;/code&gt;에서 입력으로만 사용되었기 때문에 &lt;strong&gt;반변&lt;/strong&gt; 으로 정의해도 타입 검사기가 문제 삼지 않는다.&lt;br&gt;
반면 &lt;code class=&quot;language-text&quot;&gt;V&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;get()&lt;/code&gt;에서는 출력, &lt;code class=&quot;language-text&quot;&gt;add()&lt;/code&gt;에서는 입력으로 사용되었기 때문에 &lt;strong&gt;반드시 불변이어야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;열쇠 타입에 대해 반변이므로 &lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;의 서브타입일 때 &lt;code class=&quot;language-text&quot;&gt;Map&amp;lt;A, V&gt;&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;Map&amp;lt;B, V&gt;&lt;/code&gt;의 서브타입이다.&lt;br&gt;
예를 들면 &lt;code class=&quot;language-text&quot;&gt;Map&amp;lt;Person, Int&gt;&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;Map&amp;lt;Marathoner, Int&gt;&lt;/code&gt;의 서브타입이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;val personKey: Map&amp;lt;Person, Int&amp;gt; = ..
personKey.add(Person(10), 1)
personKey.add(Marathoner(10), 1)

val marathonerKey: Map&amp;lt;Marathoner, Int&amp;gt; = ..
marathonerKey.add(Person(10), 1)        &amp;quot;컴파일 에러&amp;quot;
marathonerKey.add(Marathoner(10), 1)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 783px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6b9822e8b29a39a5df81410ae89c62ce/e51a6/genericIn.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJUlEQVR42k2SiU7bUBBF8//fUVGVqiyNuggUwFmI4y0hhCYFKpTVZIM4OI4d2+907FQVlkbvWfacuTNzCyhF9iiVoKIlm+kdbveS2d1PZu0iL50iy9tTto9nJH2NZFQnmbZIXh5Iw2We9w+QHwWlUhJ/ROwaMLlm3SvRPj+gq32mWz7kyTymb5+wGWiwNEifr0lH5TySQZl4Ykj+WFCpQAUYz29Qw4rATAkH5ZqohQ5vBt7kglv9I/ftI3rOIT3rE16/hFrq7KYCm1VIZxn4Qu5tVLoThcMauI7AbGmnSjyX6qsGam2RrEx8t4wnql+tEp6h4RtVArNG4EjcSPQ0oonAR+fEiw4F3KbALNRYZrMUpb7APUsU2qSvDUkqE5oGkW2ztSy2cga2RWCZhNm7IUWNGts/l+wE+h+YjES+J3Nc23vgRn5+KBM0JLFpE7cc7r8X0Q8+cHv8hV9fT2QqOlHLJpQiG7vO5qkoLQ+qZFA1bhBPZfBv1h7q2+zkm1+v5gkZNHSkiGOKSjO/h02LqOmI0iaefko4t7OltEllW4wt0hy6nyHrTKVN1K/KIdGo5y2GVqbIyWNrSrv6FV7jiGB0nW+6oGTX2drjZwEOq6hBjWR4SeJqpPMaaiWzXVQIh1cEv0v4nTP81g/8m2+8dU4JnmTD/oDcM5lt3hs7DV9ks48kYoHkWeY2qknbV8RDTfwm4VbEHro4wSJedUmj173/3hn7Lx1j2Krz2R/8AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;genericIn&quot;
        title=&quot;&quot;
        src=&quot;/static/6b9822e8b29a39a5df81410ae89c62ce/e51a6/genericIn.png&quot;
        srcset=&quot;/static/6b9822e8b29a39a5df81410ae89c62ce/3684f/genericIn.png 225w,
/static/6b9822e8b29a39a5df81410ae89c62ce/fc2a6/genericIn.png 450w,
/static/6b9822e8b29a39a5df81410ae89c62ce/e51a6/genericIn.png 783w&quot;
        sizes=&quot;(max-width: 783px) 100vw, 783px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;정의할 때 가변성을 지정하는 방법은 이해하기 쉬운 대신 클래스를 정의할 때 큰 제약이 생긴다는 문제가 있다.&lt;/strong&gt;&lt;br&gt;
타입을 공변으로 만든다면 타입 매개변수를 입력에 사용하는 절반을 모두 포기해야 하고, 반변으로 만든나면 나머지 절반을 포기해야 한다.&lt;br&gt;
그러니 공변이나 반변을 선택하면 &lt;strong&gt;반쪽짜리 클래스를 만들 수 밖에 없는 것이다.&lt;/strong&gt;&lt;br&gt;
함수형 프로그래밍에서는 대부분의 경우 수정할 수 없는 자료구조만 사용해 프로그램을 작성하기 때문에 함수형 언어에서는 이 단점이 상대적으로 덜 드러난다.&lt;/p&gt;
&lt;h3 id=&quot;사용할-때-가변성-지정하기&quot; style=&quot;position:relative;&quot;&gt;사용할 때 가변성 지정하기&lt;a href=&quot;#%EC%82%AC%EC%9A%A9%ED%95%A0-%EB%95%8C-%EA%B0%80%EB%B3%80%EC%84%B1-%EC%A7%80%EC%A0%95%ED%95%98%EA%B8%B0&quot; aria-label=&quot;사용할 때 가변성 지정하기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;제네릭 타입을 사용할 때 가변성을 지정하는 경우, 제네릭 타입을 정의할 때는 가변성을 지정할 수 없다.&lt;br&gt;
&lt;strong&gt;모든 제네릭 타입은 불변으로 정의되며 타입 매개변수를 아무 데서나 사용할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;불변&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;abstract class ReadWriteList&amp;lt;T&amp;gt; {
    abstract fun length(): Int
    abstract fun get(index: Int): T
    abstract fun add(element: T)
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;공변&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;val onlyReadPeople: ReadWriteList&amp;lt;out Person&amp;gt; = ..
val size = onlyReadPeople.length()
val person: Person = onlyReadPeople.get(0)
onlyReadPeople.add(Person(10)) &amp;quot;컴파일 에러&amp;quot;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;onlyReadPeople&lt;/code&gt;은 출력 기능만 사용할 수 있고, 원소 타입이 매개변수 타입으로 사용되지 않는 메서드만 사용할 수 있다는 뜻이다.&lt;br&gt;
그리고 &lt;strong&gt;&quot;A를 출력과 입력에 모두 사용할 수 있는 불변 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;는 A를 출력에 사용할 수 있는 리스트다.&quot;&lt;/strong&gt; 가 사실이기 때문에 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;out A&gt;&lt;/code&gt;의 서브타입이다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;T&gt;&lt;/code&gt;는 불변이지만 &lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;out Person&gt;&lt;/code&gt;는 공변이다. 따라서 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;의 서브타입일 때 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;out B&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;out A&gt;&lt;/code&gt;의 서브타입이다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun averageAge(people: ReadWriteList&amp;lt;out Person&amp;gt;): Int {
    people.get(0)
    people.add(Person(10)) &amp;quot;컴파일 에러&amp;quot;
}

val onlyReadPeople: ReadWriteList&amp;lt;out Person&amp;gt; = ..
averageAge(onlyReadPeople)

val onlyReadMarathoners: ReadWriteList&amp;lt;out Marathoner&amp;gt; = ..
averageAge(onlyReadMarathoners)

val readWriteMarathoner: ReadWriteList&amp;lt;Marathoner&amp;gt; = ..
averageAge(readWriteMarathoner)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0372e7ff130078424ceddb061324fb5c/38124/chainingOut.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.55555555555555%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB5ElEQVR42k1S2XLaQBDk//8klVRBnASwzCVucTix8+ALgYQOTscIJ0KwR6cRhMpWTWm1s9vTM90ZnJfWAvttiG34E++uhWTagvKbUFMT2qtD+w3osA0160Et76DebKh4mb77f2W0EhDRBHreR/RUxrj9CY51hei5AcwHBGwhscvYHWNUgnRrLGBCOSXmaixgQW2nF+CMXNzyQhsIBgT4DqzugeUPKK/BB1XEowoL5OD0chh3stg+FrEfGwQ3ED/nET/mIOxvkCyu5Y4MvQ7ANnTYgQ4sRh/SqZKJyQI9Bs/S/BAIm9g8FAmexcT6iFHnA7zhZ+zta0j7CyTJZNRrF3/CGt5DE4d1C3LGr1uAWHewntxgxYjndai5CcG9Wlh4c0tY2kaai7wy9KILMT4yz5PhrwYTZdj3OfzmQ71uY+98RbKoYPZiIHgqYONzbqtmCiiCMsHy8B7yaW5pF3Hwr3F4yUKs7pA5HFULapzbsbVj24O0beHcUNkSz5r8Z4Sc8eyWcyVbr8oc2foG5OTqBMbxaJmQoZaQkQsZ9nmJ80wF6qePtdeFcusnENrnFNw7NQpRSNsUQfussjypfPEh7aN2K6jNCJI+kyHt4LfOapsUqQJJm8iAc14MeY8+TF4vQP98+Beos5JXTnVBiAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;chainingOut&quot;
        title=&quot;&quot;
        src=&quot;/static/0372e7ff130078424ceddb061324fb5c/1cfc2/chainingOut.png&quot;
        srcset=&quot;/static/0372e7ff130078424ceddb061324fb5c/3684f/chainingOut.png 225w,
/static/0372e7ff130078424ceddb061324fb5c/fc2a6/chainingOut.png 450w,
/static/0372e7ff130078424ceddb061324fb5c/1cfc2/chainingOut.png 900w,
/static/0372e7ff130078424ceddb061324fb5c/38124/chainingOut.png 953w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;averageAge()&lt;/code&gt; 함수와 같이 새로운 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;out Person&gt;&lt;/code&gt; 타입을 통해 함수 내부에서는 출력의 용도로만 사용하겠다고 선언하여 공변으로 지정하여 사용할 수 있다.&lt;/p&gt;
&lt;h3&gt;반변&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;in A&gt;&lt;/code&gt; 역시 &lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;A&gt;&lt;/code&gt;와 비슷하게 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt; 타입의 원소들로 구성된 리스트를 나타내지만 &lt;strong&gt;입력 기능만 사용할 수 있다는 차이가 있다.&lt;/strong&gt;&lt;br&gt;
정확히 말하면 &lt;strong&gt;메서드 중 원소 타입이 결과 타입으로 사용되지 않는 메서드만 사용할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;val readWritePeople: ReadWriteList&amp;lt;in Person&amp;gt; = ..
val people: Any? = readWritePeople.get(0)
readWritePeople.add(Person(10))
readWritePeople.add(Marathoner(10))

val readWriteMarathoners: ReadWriteList&amp;lt;in Marathoner&amp;gt; = ..
val marathoner: Any? = readWriteMarathoners.get(0)
readWriteMarathoners.add(Marathoner(10))&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;strong&gt;&quot;A를 출력과 입력에 모두 사용할 수 있는 불변 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;는 A를 입력에 사용할 수 있는 리스트다.&quot;&lt;/strong&gt; 가 사실이기 때문에 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;A&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;in A&gt;&lt;/code&gt;의 서브타입이다.&lt;br&gt;
따라서 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;B&lt;/code&gt;가 &lt;code class=&quot;language-text&quot;&gt;A&lt;/code&gt;의 서브타입일 때 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;in A&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;in B&gt;&lt;/code&gt;의 서브타입이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/48da0b2963cedd8e26b04c7ad7f470a3/38124/chainingIn.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.55555555555555%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB5ElEQVR42k1Si27aQBDk/z+lqgRJ1EDAPEwMmEdQkqqqmhrw2ca8AoEkCo7vMR2bJOpJq1tbtzs7s1PAx9E6xesuwEv8EwffxcvUhgqvoQOb0YZhbuZd6MUAen0HvZ9Av61hjMT/p2C0hH6eIZ27EP0ipt3v8LpFiOElVODCRF0kkzqOHoO3FDZMBiIsArWgY5f14qtxQa3GRO8C8Q1AZGzuGbf8dljQxPvMhhicwR+UMOuX2NSC9C0C1BgVpN4F1JTgyxGMOnLCyAWWQ5i4xyYD0hpB+U1O0uL/AaMPQ5pYjKHDNhA52P+uYeJ+QzguIvHbfE8Q7xyKwxSSxTWeAguHqIXXuIU0biIVFcjHHh5ntTzelixa2pDMNQHUrodd0MBONPJcr1zIaYVsLlF48suY3J8zShC/LnCMWSh+IFk1sPhbxfxPGXuCmY3DhpwkqkNtHSy9KuIHUt52kEZXSB+KkJs7argekQ6prPvAihFTi2hIGjXeFuk6lMHJpchpc+PSr0NHnDas4t07OzWjPEYl1NAoqIMPNR/yAXXMFsQ8KzYhNyjaeZOTfT4sRH3lpEyaV5CZlZ4DZH3yLX/6J7fPcUN/eVDctor7nKJDkJMXVaYXbaLmHajVzcmHyfar0acP/wGdHpKfkVW4jAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;chainingIn&quot;
        title=&quot;&quot;
        src=&quot;/static/48da0b2963cedd8e26b04c7ad7f470a3/1cfc2/chainingIn.png&quot;
        srcset=&quot;/static/48da0b2963cedd8e26b04c7ad7f470a3/3684f/chainingIn.png 225w,
/static/48da0b2963cedd8e26b04c7ad7f470a3/fc2a6/chainingIn.png 450w,
/static/48da0b2963cedd8e26b04c7ad7f470a3/1cfc2/chainingIn.png 900w,
/static/48da0b2963cedd8e26b04c7ad7f470a3/38124/chainingIn.png 953w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun addPerson(people: ReadWriteList&amp;lt;in Person&amp;gt;) {
    people.add(Person(..))
    people.add(Marathoner(..))
}

val readWritePeople1: ReadWriteList&amp;lt;in Person&amp;gt; = ..
val readWritePeople2: ReadWriteList&amp;lt;Person&amp;gt; = ..
addPerson(readWritePeople1)
addPerson(readWritePeople2)

val readWriteMarathoners1: ReadWriteList&amp;lt;in Marathoner&amp;gt; = ..
val readWriteMarathoners2: ReadWriteList&amp;lt;Marathoner&amp;gt; = ..
addPerson(readWriteMarathoners1)    &amp;quot;컴파일 에러&amp;quot;
addPerson(readWriteMarathoners2)    &amp;quot;컴파일 에러&amp;quot;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;addPerson&lt;/code&gt; 함수는 &lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;in Person&gt;&lt;/code&gt; 반변으로 지정되어 있기 때문에 &lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;Marathoner&gt;&lt;/code&gt;는 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;의 서브타입이긴 하지만 &lt;code class=&quot;language-text&quot;&gt;addPerson&lt;/code&gt;의 인자로 사용될 수 없다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;fun addMarathoner(people: ReadWriteList&amp;lt;in Marathoner&amp;gt;) {
    people.add(Person(..))      &amp;quot;컴파일 에러&amp;quot;
    people.add(Marathoner(..))
}

val readWritePeople1: ReadWriteList&amp;lt;in Person&amp;gt; = ..
val readWritePeople2: ReadWriteList&amp;lt;Person&amp;gt; = ..
addMarathoner(readWritePeople1)
addMarathoner(readWritePeople2)

val readWriteMarathoners1: ReadWriteList&amp;lt;in Marathoner&amp;gt; = ..
val readWriteMarathoners2: ReadWriteList&amp;lt;Marathoner&amp;gt; = ..
addMarathoner(readWriteMarathoners1)
addMarathoner(readWriteMarathoners2)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ReadWriteList&amp;lt;in Marathoner&gt;&lt;/code&gt; 반변으로 지정하면 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;Person&gt;&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;List&amp;lt;in Marathoner&gt;&lt;/code&gt;의 서브타입이기 때문에 &lt;code class=&quot;language-text&quot;&gt;addMarathoner&lt;/code&gt; 함수 호출이 다 가능해진다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;List&amp;lt;in Marathoner&gt;&lt;/code&gt;에는 &lt;code class=&quot;language-text&quot;&gt;Marathoner&lt;/code&gt;가 보장되어야 하기 때문에 더 작은 의미를 가지는 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;을 직접 추가하지는 못한다.&lt;br&gt;
이는 &lt;strong&gt;Person 리스트에 Marathoner 객체를 추가해도 괜찮고 Marathoner 리스트에 Person 객체를 추가하지 못한다는 직관과 일치한다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;맺으며&quot; style=&quot;position:relative;&quot;&gt;맺으며&lt;a href=&quot;#%EB%A7%BA%EC%9C%BC%EB%A9%B0&quot; aria-label=&quot;맺으며 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;여러 종류의 다형성을 이해하고 제네릭 가변성에 대해 알아보았다.&lt;br&gt;
사용하는 언어가 공변성과 반공변성을 어느 정도로 지원하는지를 정확하게 이해한 후 사용해야 한다.&lt;br&gt;
예를들어 자바는 리턴 타입 공변성을 지원하지만 C#은 리턴 타입 공변성을 지원하지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;개인적으로 공변,불변을 제외한 나머지를 반변이라고 설명하는 글들을 많이 접했었는데 이 책에서는 반변을 설명할 때 &lt;strong&gt;함수와 서브타입&lt;/strong&gt;에 대한 설명이 독특했다.&lt;br&gt;
&lt;strong&gt;&quot;함수 타입은 매개변수 타입의 서브타입 관계를 뒤집는다.&quot;&lt;/strong&gt; 는 사실이 반변을 이해할 때 도움이 되었다.&lt;/p&gt;
&lt;p&gt;어려운 제네릭 가변성에 대한 내용을 배워봤지만 제네릭이 능사는 아니다.&lt;br&gt;
서브타입 관계를 추가하는 대신 기능이 빠진 타입을 만들거나, 기능을 다 갖춘 타입을 만드는 대신 서브타입 관계를 포기하거나, 개발자는 반드시 이 둘 중 하나를 골라야 한다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[트랜잭션과 잠금]]></title><description><![CDATA[InnoDB Locking Dirty read, Non-repeatable read, and Phantom read 트랜잭션 트랜잭션은 작업의 완전성을 보장 (COMMIT, ROLLBACK…]]></description><link>https://jdalma.github.io/2024y/transaction/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/transaction/</guid><pubDate>Tue, 13 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html&quot;&gt;InnoDB Locking&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://jennyttt.medium.com/dirty-read-non-repeatable-read-and-phantom-read-bd75dd69d03a&quot;&gt;Dirty read, Non-repeatable read, and Phantom read&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;트랜잭션&quot; style=&quot;position:relative;&quot;&gt;트랜잭션&lt;a href=&quot;#%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98&quot; aria-label=&quot;트랜잭션 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;트랜잭션은 &lt;strong&gt;작업의 완전성을 보장 (COMMIT, ROLLBACK)&lt;/strong&gt; 해주는 것이다.&lt;br&gt;
즉 논리적인 작업 셋을 모두 완벽하게 처리하거나 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상(Partial update)이 발생하지 않게 만들어주는 기능이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;트랜잭션&lt;/strong&gt; : 데이터의 정합성을 보장하기 위한 기능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;잠금&lt;/strong&gt; : 동시성을 제어하기 위한 기능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;격리 수준&lt;/strong&gt; : 하나의 트랜잭션 내에서 또는 여러 트랜잭션 간의 작업 내용을 어떻게 공유하고 차단할 것인지 결정하는 레벨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 트랜잭션은 개발자에게 엄청난 혜택을 주지만 주의해야 할 점이 있다.&lt;br&gt;
&lt;strong&gt;트랜잭션 또한 DBMS의 커넥션과 동일하게 꼭 필요한 최소한의 코드에만 적용하도록 신경써야 한다.&lt;/strong&gt;&lt;br&gt;
(&lt;a href=&quot;https://www.youtube.com/watch?v=xc0tnJVGQEw&amp;#x26;ab_channel=%EC%B5%9C%EB%B2%94%EA%B7%A0&quot;&gt;최범균님이 DB 커넥션 풀 부족 장애&lt;/a&gt; 트러블 슈팅 내용을 업로드하셨는데 이 영상을 보는것도 좋다.)&lt;br&gt;
트랜잭션 내에 외부 네트워크 통신과 같은 작업을 최대한 제거해야 한다.&lt;/p&gt;
&lt;h1 id=&quot;mysql-엔진의-잠금&quot; style=&quot;position:relative;&quot;&gt;MySQL 엔진의 잠금&lt;a href=&quot;#mysql-%EC%97%94%EC%A7%84%EC%9D%98-%EC%9E%A0%EA%B8%88&quot; aria-label=&quot;mysql 엔진의 잠금 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;MySQL에서 사용되는 잠금은 크게 &lt;strong&gt;MySQL 엔진&lt;/strong&gt; 레벨과 &lt;strong&gt;스토리지 엔진&lt;/strong&gt; 레벨로 나눌 수 있다.&lt;br&gt;
MySQL 엔진 레벨의 잠금은 모든 스토리지 엔진에 영향을 미치지만, 스토리지 엔진 레벨의 잠금은 스토리지 엔진 간 상호 영향을 미치지 않는다.&lt;/p&gt;
&lt;p&gt;MySQL 엔진&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테이블 데이터 동기화를 위한 &lt;strong&gt;테이블 락&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;테이블의 구조를 잠그는 &lt;strong&gt;메타데이터 락&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;사용자의 필요에 맞게 사용할 수 있는 &lt;strong&gt;네임드 락&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;글로벌-락&quot; style=&quot;position:relative;&quot;&gt;글로벌 락&lt;a href=&quot;#%EA%B8%80%EB%A1%9C%EB%B2%8C-%EB%9D%BD&quot; aria-label=&quot;글로벌 락 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;글로벌 락은 &lt;code class=&quot;language-text&quot;&gt;FLUSH TABLES WITH READ LOCK&lt;/code&gt; 명령으로 획득할 수 있으며, 가장 범위가 큰 잠금이다.&lt;br&gt;
한 세션에서 글로벌 락을 획득하면 다른 세션에서 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt;를 제외한 대부분의 DDL, DML은 글로벌 락이 해제될 때까지 대기 상태로 기다린다.&lt;br&gt;
대표적으로 여러 데이터베이스, 테이블 대상으로 일관된 백업을 받기 위해 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;이 글로벌 락보다 조금 더 가벼운 백업 락이 도입됐다.&lt;br&gt;
특정 세션에서 백업 락을 획득하면 모든 세션에서 테이블의 스키마나 사용자의 인증 관련 정보를 변경할 수 없게된다.&lt;br&gt;
하지만 일반적인 테이블의 데이터 번경은 허용된다. 백업 락을 잡고 백업 진행 중에 DDL이 유입되면 백업은 실패한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;LOCK INSTANCE FOR BACKUP&lt;/code&gt; 명령으로 백업 락을 획득하고 &lt;code class=&quot;language-text&quot;&gt;UNLOCK INSTANCE&lt;/code&gt; 명령으로 락을 반납한다.&lt;/p&gt;
&lt;h2 id=&quot;테이블-락&quot; style=&quot;position:relative;&quot;&gt;테이블 락&lt;a href=&quot;#%ED%85%8C%EC%9D%B4%EB%B8%94-%EB%9D%BD&quot; aria-label=&quot;테이블 락 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;개별 테이블 단위로 설정되는 잠금이며, 명시적 또는 묵시적으로 특정 테이블의 락을 획득할 수 있다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;LOCK TABLES table_name [ READ | WRITE ]&lt;/code&gt; 명령으로 락을 획득하고 &lt;code class=&quot;language-text&quot;&gt;UNLOCK TABLES&lt;/code&gt; 명령으로 락을 반납한다.&lt;br&gt;
InnoDB 테이블의 경우 &lt;strong&gt;스토리지 엔진 차원에서 레코드 기반의 잠금&lt;/strong&gt; 을 제공하기 때문에 단순 데이터 변경 쿼리로 인해 묵시적인 테이블 락이 설정되지는 않으며 스키마를 변경하는 쿼리(DDL)의 경우에만 영향을 미친다.&lt;/p&gt;
&lt;h2 id=&quot;네임드-락&quot; style=&quot;position:relative;&quot;&gt;네임드 락&lt;a href=&quot;#%EB%84%A4%EC%9E%84%EB%93%9C-%EB%9D%BD&quot; aria-label=&quot;네임드 락 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;GET_LOCK()&lt;/code&gt; 함수를 이용해 임의의 문자열에 대해 잠금을 설정할 수 있다.&lt;br&gt;
이 잠금의 특징은 잠금 대상이 테이블이나 레코드 또는 AUTO_INCREMENT와 같은 데이터베이스 객체가 아니라는 것이다.&lt;br&gt;
&lt;strong&gt;단순히 사용자가 지정한 문자열에 대해 획득하고 반납하는 잠금&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;트랜잰셕 A&amp;gt;
select GET_LOCK(&amp;#39;test1&amp;#39;, -1);
+-----------------------+
| GET_LOCK(&amp;#39;test1&amp;#39;, -1) |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.01 sec)

-- &amp;lt;트랜잰셕 B&amp;gt;
select GET_LOCK(&amp;#39;test1&amp;#39;, -1);
-- 무한 대기 중

-- &amp;lt;트랜잰셕 A&amp;gt;
select RELEASE_LOCK(&amp;#39;test1&amp;#39;);
-- &amp;lt;/트랜잰셕 A&amp;gt;

-- &amp;lt;트랜잰셕 B&amp;gt;
+-----------------------+
| GET_LOCK(&amp;#39;test1&amp;#39;, -1) |
+-----------------------+
|                     1 |
+-----------------------+
&amp;quot;1 row in set (2 min 47.74 sec)&amp;quot;
-- &amp;lt;/트랜잰셕 B&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;메타데이터-락&quot; style=&quot;position:relative;&quot;&gt;메타데이터 락&lt;a href=&quot;#%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%9D%BD&quot; aria-label=&quot;메타데이터 락 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;데이터베이스 객체 (대표적으로 테이블이나 뷰 등)의 이름이나 구조를 변경하는 경우에 획득하는 잠금이다.&lt;br&gt;
&lt;strong&gt;메타데이터 락은 명시적으로 획득하거나 해제할 수 있는 락은 아니고 테이블을 수정할 때 자동으로 획득하는 잠금이다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;트랜잰셕 A&amp;gt;
set autocommit = FALSE;

select * from MEMBER;
+----+----------+
| id | name     |
+----+----------+
|  1 | primary1 |
|  2 | primary2 |
|  3 | primary3 |
|  4 | primary4 |
+----+----------+

rename table member TO updated_member;
&amp;quot;Query OK, 0 rows affected (0.05 sec)&amp;quot;

-- &amp;lt;트랜잰셕 B&amp;gt;
select * from member;
&amp;quot;ERROR 1146 (42S02): Table &amp;#39;test.member&amp;#39; doesn&amp;#39;t exist&amp;quot;

select * from updated_member;
+----+----------+
| id | name     |
+----+----------+
|  1 | primary1 |
|  2 | primary2 |
|  3 | primary3 |
|  4 | primary4 |
+----+----------+

-- &amp;lt;/트랜잰셕 A&amp;gt;
-- &amp;lt;/트랜잰셕 B&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 예시와 같이 트랜잭션 A에서 발생한 DDL은 트랜잭션과 무관하게 실행 즉시 커밋되며 롤백할 수 없다.&lt;br&gt;
&quot;일부&quot; DDL 명령(ALTER TABLE)에 대해서만 트랜잭션을 지원하지만 이것도 사용자가 명시적으로 트랜잭션을 제어할 수 있는 것은 아니고 내부적으로 실패하면 롤백한다.&lt;br&gt;
DDL은 단일 스레드로 작동한다.&lt;/p&gt;
&lt;h1 id=&quot;innodb-스토리지-엔진-잠금&quot; style=&quot;position:relative;&quot;&gt;InnoDB 스토리지 엔진 잠금&lt;a href=&quot;#innodb-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EC%97%94%EC%A7%84-%EC%9E%A0%EA%B8%88&quot; aria-label=&quot;innodb 스토리지 엔진 잠금 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;MySQL에서 제공하는 잠금과는 별개로 스토리지 엔진 내부에서 레코드 기반의 잠금 방식을 제공하며, 잠금 정보가 상당히 작은 공간으로 관리되기 때문에 락이 페이지 락으로, 또는 테이블 락으로 레벨업되는 경우(락 에스컬레이션)는 없다.&lt;br&gt;
InnoDB는 &lt;strong&gt;레코드 기반의 잠금 방식 덕분에 MyISAM보다는 훨씬 뛰어난 동시성 처리를 제공할 수 있다.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Inno DB내부에서는 여러 트랜잭션들이 경합하고 있는 상황에서 최대한의 성능을 위해서 &lt;code class=&quot;language-text&quot;&gt;여러 방식의 다양한 락(Lock)을 조합&lt;/code&gt;해서 사용하고 있다&lt;/strong&gt; &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html&quot;&gt;InnoDB Locking&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 858px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3134361084ee2563db7e0742903d1942/42d54/innodbLock.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAABYUlEQVR42p2U6Y6CQBCEef9Hg8g/MBKjqCjIoeCFB9Cbr7OT7LqHsztJ0TBH9VE9OPI+hmFQW9e1Ik1TRdM0am2H80xYFIVUVSWz2UziOFZC3/d1PssydWb2WhFyaLlcynw+VyRJooSHw0HKspTdbiePx8OecL/fS9/3cjweNRoGDpizTtmQYU1aWEDK0+lU1wxeEnZdpxFcr1eJokhWq5VMJhMF0bmu+2uanwghW6/XstlsFNQNYiIj0tvtJmEYqtLU8VXqDhs4RATn81mVxQmECMA6hIjDN86s2sbUkINEPB6PJQgCjXo0Gsn9fv9/HyIGKUK23W41QhtBviU0fYYFzC8Wi7+1zXOE9CKRAWqJ2hB+bJ2f8IWQ+tF31A/w7nme/V1G0bZtVWkU5HqRIr2IQPQhTljn9oDT6SSXy0UtnQGY57zDI89zTQ2LGKRqROFHwTeOTL+yZvYCRGSNcr0BVniISkararwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;innodbLock&quot;
        title=&quot;&quot;
        src=&quot;/static/3134361084ee2563db7e0742903d1942/42d54/innodbLock.png&quot;
        srcset=&quot;/static/3134361084ee2563db7e0742903d1942/3684f/innodbLock.png 225w,
/static/3134361084ee2563db7e0742903d1942/fc2a6/innodbLock.png 450w,
/static/3134361084ee2563db7e0742903d1942/42d54/innodbLock.png 858w&quot;
        sizes=&quot;(max-width: 858px) 100vw, 858px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;레코드 수준 잠금은 레코드 각각에 잠금이 걸리므로 테이블 수준의 잠금보다는 조금 더 복잡하다.&lt;/p&gt;
&lt;h2 id=&quot;record-locks&quot; style=&quot;position:relative;&quot;&gt;Record Locks&lt;a href=&quot;#record-locks&quot; aria-label=&quot;record locks permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;레코드 자체만을 잠그는 것을 의미하며, InnoDB 스토리지 엔진은 &lt;strong&gt;레코드 자체가 아니라 인덱스의 레코드를 잠근다는 점이다.&lt;/strong&gt;&lt;br&gt;
인덱스가 하나도 없는 테이블이더라도 내부적으로 자동 생성된 클러스터 인덱스를 이용해 잠금을 설정한다.&lt;/p&gt;
&lt;p&gt;InnoDB에서는 대부분 보조 인덱스를 이용한 변경 작업은 넥스트 키 락 또는 갭 락을 사용하지만 &lt;strong&gt;프라이머리 키 또는 유니크 인덱스에 의한 변경 작업에서는 갭에 대해서는 잠그지 않고 레코드 자체에 대해서만 락을 건다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;레코드를 잠그는 것과 인덱스를 잠그는 것은 중요한 차이를 만들어낸다.&lt;br&gt;
즉, &lt;strong&gt;변경해야 할 레코드를 찾기 위해 검색한 인덱스의 레코드를 모두 락을 걸어야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE employees (
    id int NOT NULL AUTO_INCREMENT,
    first_name varchar(255) DEFAULT NULL,
    last_name varchar(255) DEFAULT NULL,
    PRIMARY KEY (id),
    KEY idx_first_name (first_name)
) ENGINE=InnoDB

+----+------------+----------------+
| id | first_name | last_name      |
+----+------------+----------------+
|  1 | John       | Doe1           |
|  2 | John       | Doe2           |
|  3 | John       | Doe3           |
|  4 | John       | Doe4           |
|  5 | John       | Doe5           |
|  6 | John       | Doe6           |
|  7 | John       | Doe7           |
|  8 | John       | Doe8           |
|  9 | John       | Doe9           |
| 10 | John       | Doe10          |
| 11 | Jane       | Ann1           |
| 12 | Jane       | Ann2           |
+----+------------+----------------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;update employees SET last_name = &amp;#39;Updated Jane4&amp;#39; where first_name = &amp;#39;Jane&amp;#39; and last_name = &amp;#39;Ann1&amp;#39;;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+-------------+----------------+-----------+---------------+-------------+------------+
| OBJECT_NAME | INDEX_NAME     | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA  |
+-------------+----------------+-----------+---------------+-------------+------------+
| employees   | NULL           | TABLE     | IX            | GRANTED     | NULL       |
| employees   | idx_first_name | RECORD    | X             | GRANTED     | &amp;#39;Jane&amp;#39;, 11 |
| employees   | idx_first_name | RECORD    | X             | GRANTED     | &amp;#39;Jane&amp;#39;, 12 |
| employees   | PRIMARY        | RECORD    | X,REC_NOT_GAP | GRANTED     | 11         |
| employees   | PRIMARY        | RECORD    | X,REC_NOT_GAP | GRANTED     | 12         |
| employees   | idx_first_name | RECORD    | X,GAP         | GRANTED     | &amp;#39;John&amp;#39;, 1  |
+-------------+----------------+-----------+---------------+-------------+------------+

IX : Intentional Exclusive, 특정 레코드에 대해 쓰기 잠금을 가지고 있음
REC_NOT_GAP : 갭 락이 포함되지 않음 순수 레코드에 대한 잠금&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;Jane에 대해서 UPDATE를 실행하면 위와 같이 잠금이 활성화된다.&lt;br&gt;
&lt;strong&gt;UPDATE시 where 절에 index 컬럼으로 특정 지으면 해당 index에 해당하는 레코드들이 락이 걸리고 update에 해당하는 레코드와 동일한 보조 인덱스의 값들을 가지는 로우들도 모두 락이 걸린다.&lt;/strong&gt;&lt;br&gt;
테이블에 인덱스가 없으면 모든 행들이 락이 걸리며, where 절에 포함된 최소한의 범위로 락을 잡으려고 한다.&lt;br&gt;
클러스터 인덱스와 보조 인덱스를 같이 where 절에 추가한다면 클러스터 인덱스 기준으로 락을 건다.&lt;br&gt;
&lt;a href=&quot;https://stackoverflow.com/questions/60007863/why-does-innodb-block-more-records-in-case-of-a-secondary-index&quot;&gt;보조 인덱스의 경우 InnoDB가 더 많은 레코드를 차단하는 이유는 무엇입니까?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;하지만 조금 의아한 점은 아래의 SQL은 모든 레코드를 잠근다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;트랜잭션 A&amp;gt;
update employees SET last_name = &amp;#39;Updated Doe&amp;#39; where first_name = &amp;#39;John&amp;#39; and last_name = &amp;#39;Doe1&amp;#39;;

-- &amp;lt;트랜잭션 B&amp;gt;
update employees SET last_name = &amp;#39;Updated Jane&amp;#39; where first_name = &amp;#39;Jane&amp;#39;;
-- 트랜잭션 A의 UPDATE문을 기다린다.&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 UPDATE문을 작성하면 10건만 레코드 락이 걸려야하겠지만 아래와 같이 모든 레코드를 잠그게 된다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;SELECT OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-------------+------------+-----------+-----------+-------------+------------------------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA              |
+-------------+------------+-----------+-----------+-------------+------------------------+
| employees   | NULL       | TABLE     | IX        | GRANTED     | NULL                   |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | supremum pseudo-record |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 11                     |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 12                     |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 1                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 2                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 3                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 4                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 5                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 6                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 7                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 8                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 9                      |
| employees   | PRIMARY    | RECORD    | X         | GRANTED     | 10                     |
+-------------+------------+-----------+-----------+-------------+------------------------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;gap-locks&quot; style=&quot;position:relative;&quot;&gt;Gap Locks&lt;a href=&quot;#gap-locks&quot; aria-label=&quot;gap locks permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;갭 락은 레코드 자체가 아니라 &lt;strong&gt;레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 것을 의미한다.&lt;/strong&gt;&lt;br&gt;
갭 락의 역할은 &lt;strong&gt;레코드와 레코드 사이의 간격에 새로운 레코드가 생성되는 것을 제어하는 것&lt;/strong&gt; 이며, 넥스트 키 락의 일부로 자주 사용된다.&lt;/p&gt;
&lt;h2 id=&quot;next-key-locks&quot; style=&quot;position:relative;&quot;&gt;Next Key Locks&lt;a href=&quot;#next-key-locks&quot; aria-label=&quot;next key locks permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;레코드 락과 갭 락을 합쳐 놓은 형태의 잠금이며, &lt;strong&gt;갭 락이나 넥스트 키 락은 바이너리 로그에 기록되는 쿼리가 레플리카 서버에서 실행될 때 소스 서버에서 만들어 낸 결과와 동일한 결과를 만들어내도록 보장하는 것이 주 목적이다.&lt;/strong&gt;&lt;br&gt;
하지만 의외로 넥스트 키 락과 갭 락으로 인해 데드락이 발생하거나 다른 트랜잭션을 기다리게 만드는 일이 자주 발생하기 때문에 해당 락은 줄이는 것이 좋다.&lt;br&gt;
그래서 MySQL 8.0에서는 ROW 포맷의 바이너리 로그가 기본 설정으로 변경됐다. 바이너리 로그 포맷 관련 내용은 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/binary-log-setting.html&quot;&gt;Setting The Binary Log Format&lt;/a&gt; 참고하자.&lt;/p&gt;
&lt;h2 id=&quot;auto-increment-locks&quot; style=&quot;position:relative;&quot;&gt;Auto Increment Locks&lt;a href=&quot;#auto-increment-locks&quot; aria-label=&quot;auto increment locks permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;자동 증가하는 숫자 값을 추출(채번)하기 위해 AUTO_INCREMENT라는 컬럼 속성을 제공한다.&lt;br&gt;
&lt;strong&gt;해당 컬럼이 존재하는 테이블에 여러 레코드가 동시에 INSERT 되는 경우, 저장되는 각 레코드는 중복되지 않고 저장된 순서대로 증가하는 일련번호 값을 가져야하기 때문에 내부적으로 Auto increment lock 이라고 하는 테이블 수준의 잠금을 사용한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;새로운 레코드를 저장하는 쿼리에서만 필요하며, UPDATE나 DELETE의 쿼리에서는 잠금이 실행되지 않는다.&lt;br&gt;
&lt;strong&gt;이 락은 트랜잭션과 관계없이 INSERT나 REPLACE 문장에서 AUTO_INCREMENT 값을 가져오는 순간만 락이 걸렸다가 즉시 해제된다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 설명은 MySQL 5.0이하 버전에서 사용하던 방식이고 &lt;code class=&quot;language-text&quot;&gt;innodb_autoinc_lock_mode&lt;/code&gt;라는 시스템 변수를 이용해 자동 증가 락의 작동 방식을 변경할 수 있다.&lt;br&gt;
자세한 내용은 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html&quot;&gt;AUTO_INCREMENT Handling in InnoDB&lt;/a&gt;을 참고하자.&lt;/p&gt;
&lt;h2 id=&quot;shared-and-exclusive-locks&quot; style=&quot;position:relative;&quot;&gt;Shared and Exclusive Locks&lt;a href=&quot;#shared-and-exclusive-locks&quot; aria-label=&quot;shared and exclusive locks permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;InnoDB는 공유(&lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt;) 잠금과 독점(&lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt;) 잠금의 두 가지 잠금 유형이 있는 행 수준 잠금을 구현한다.&lt;br&gt;
사용자가 필요에 따라 명시적으로 &lt;code class=&quot;language-text&quot;&gt;locking read&lt;/code&gt;를 할 수 있도록 두가지 쿼리를 제공한다.&lt;/p&gt;
&lt;h3&gt;SELECT ... LOCK IN SHARE MODE&lt;/h3&gt;
&lt;p&gt;한 트랜잭션에서 읽어간 데이터를 &lt;strong&gt;다른 트랜잭션에서 배타적으로 수정하기 위해 락을 획득하려 할때 기다리게 한다. &lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
두 개의 세션에서 특정 레코드에 대한 &lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt; 락을 동시에 가질 수 있다. (Shared Lock이 걸려있는 동안 다른 트랜잭션이 해당 row에 대해 &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt; lock 획득은 불가능하지만 &lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt; lock 획득은 가능)&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;트랜잭션 A&amp;gt;
select * from employees where id = 10 for share;
-- 1 row in set (0.00 sec)

-- &amp;lt;트랜잭션 B&amp;gt;
select * from employees where id = 10 for share;
-- 1 row in set (0.00 sec)

SELECT OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-------------+------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+------------+-----------+---------------+-------------+-----------+
| employees   | NULL       | TABLE     | IS            | GRANTED     | NULL      |
| employees   | PRIMARY    | RECORD    | S,REC_NOT_GAP | GRANTED     | 10        |
| employees   | NULL       | TABLE     | IS            | GRANTED     | NULL      |
| employees   | PRIMARY    | RECORD    | S,REC_NOT_GAP | GRANTED     | 10        |
+-------------+------------+-----------+---------------+-------------+-----------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;MySQL 8.0 부터는 기존 &lt;em&gt;LOCK IN SHARE MODE&lt;/em&gt; 대신 &lt;code class=&quot;language-text&quot;&gt;FOR SHARE&lt;/code&gt;라고 간략하게 적어줘도 된다.  &lt;em&gt;(하위 호환성을 위해 기존 구문도 문제 없이 실행됨)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;SELECT ... FOR UPDATE&lt;/h3&gt;
&lt;p&gt;한 트랜잭션에서 읽어간 데이터를 &lt;strong&gt;다른 트랜잭션에서 배타적으로 읽거나, 수정하기 위해 락을 획득하려할 때 기다리게 한다. &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;exclusive lock&lt;/code&gt;이 걸려있으면 다른 트랜잭션이 해당 row에 대해 &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt; lock을 모두 획득하지 못하고 대기해야 한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;트랜잭션 A&amp;gt;
select * from employees where id = 10 for update;
-- 1 row in set (0.00 sec)

-- &amp;lt;트랜잭션 B&amp;gt;
SELECT OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-------------+------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+------------+-----------+---------------+-------------+-----------+
| employees   | NULL       | TABLE     | IX            | GRANTED     | NULL      |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 10        |
+-------------+------------+-----------+---------------+-------------+-----------+

select * from employees where id = 10 for update;
-- 대기&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;intention-locks&quot; style=&quot;position:relative;&quot;&gt;Intention Locks&lt;a href=&quot;#intention-locks&quot; aria-label=&quot;intention locks permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;행 잠금과 테이블 잠금이 공존할 수 있는 다중 세분성 잠금을 지원한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;intention shared lock (&lt;code class=&quot;language-text&quot;&gt;IS&lt;/code&gt;) : 트랜잭션이 테이블의 개별 행에 공유 잠금을 설정하려고 함을 나타낸다.&lt;/li&gt;
&lt;li&gt;intention exclusive lock (&lt;code class=&quot;language-text&quot;&gt;IX&lt;/code&gt;) : 트랜잭션이 테이블의 개별 행에 독점 잠금을 설정하려고 함을 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 &lt;code class=&quot;language-text&quot;&gt;SELECT ... FOR SHARE&lt;/code&gt;는 IS 잠금을 설정하고 &lt;code class=&quot;language-text&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;는 IX 잠금을 설정합니다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;+-------------+------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+------------+-----------+---------------+-------------+-----------+
| employees   | NULL       | TABLE     | IS            | GRANTED     | NULL      |
| employees   | PRIMARY    | RECORD    | S,REC_NOT_GAP | GRANTED     | 9         |
| employees   | NULL       | TABLE     | IX            | GRANTED     | NULL      |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 10        |
+-------------+------------+-----------+---------------+-------------+-----------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;X&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;IX&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;S&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;IS&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;X&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Intention Lock은 전체 테이블 요청(예: &lt;code class=&quot;language-text&quot;&gt;LOCK TABLES ... WRITE&lt;/code&gt;)을 제외하고는 아무것도 차단하지 않으며, 주요 목적은 &lt;strong&gt;누군가 행을 잠그고 있거나 테이블의 행을 잠그려고 한다는 것을 표시하는 것이다.&lt;/strong&gt;&lt;br&gt;
하지만 동일한 레코드에 Row-Level Lock의 실제 잠금(&lt;code class=&quot;language-text&quot;&gt;S&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;X&lt;/code&gt;)을 획득하는 과정에서 동시 접근을 막거나 허용하는 제어를 하게 된다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;LOCK TABLES&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;ALTER TABLE&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;DROP TABLE&lt;/code&gt;이 실행될 때는 &lt;code class=&quot;language-text&quot;&gt;IS&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;IX&lt;/code&gt;를 모두 &lt;strong&gt;block&lt;/strong&gt;하는 &lt;strong&gt;Table-Level Lock&lt;/strong&gt;이 걸린다 (즉 , &lt;code class=&quot;language-text&quot;&gt;IS&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;IX&lt;/code&gt; lock을 획득하려는 트랜잭션은 대기상태로 빠진다)&lt;/p&gt;
&lt;h1 id=&quot;db-트랜잭션-격리-수준&quot; style=&quot;position:relative;&quot;&gt;DB 트랜잭션 격리 수준&lt;a href=&quot;#db-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B2%A9%EB%A6%AC-%EC%88%98%EC%A4%80&quot; aria-label=&quot;db 트랜잭션 격리 수준 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;여러 트랜잭션이 동시에 처리될 때 &lt;strong&gt;특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것이다.&lt;/strong&gt;&lt;br&gt;
Thread에서 공유 자원에 동시 접근을 제한하기 위해 &lt;strong&gt;Lock&lt;/strong&gt;을 걸듯이 DB에서도 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Transaction&lt;/code&gt;간에 같은 동일한 데이터에 대한 동시 접근을 제한&lt;/strong&gt;하기 위하여 &lt;strong&gt;Lock&lt;/strong&gt;을 설정할 수 있다&lt;br&gt;
&lt;em&gt;&lt;code class=&quot;language-text&quot;&gt;Lock&lt;/code&gt;을 건다는 것은 동시처리량이 줄어든다는 의미이기 때문에 과도하게 사용하면 성능에 문제가 생길 수 있다&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;가장 낮은 &lt;strong&gt;레벨 0&lt;/strong&gt;의 경우 &lt;code class=&quot;language-text&quot;&gt;Lock&lt;/code&gt;이 걸리지 않기 때문에 속도는 매우 빠르나 동시 접근을 허용하기 때문에 데이터 정합성에 문제가 생길 수 있다&lt;br&gt;
가장 높은 &lt;strong&gt;레벨 3&lt;/strong&gt;의 경우 완전히 &lt;code class=&quot;language-text&quot;&gt;Lock&lt;/code&gt;을 걸어 동시 접근을 차단하고 순차적으로 처리하기 때문에 정합성은 완벽하지만 동시에 처리할 수 있는 양이 적어 속도가 매우 느리다&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;Isolation Level&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;Dirty Read&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;Nonrepeatable Read&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;Phantom Read&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;레벨0&lt;/code&gt; &lt;strong&gt;Read Uncommited&lt;/strong&gt; 커밋되지 않은 읽기&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;발생&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;발생&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;레벨1&lt;/code&gt; &lt;strong&gt;Read Committed&lt;/strong&gt;	커밋된 읽기&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;발생&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;레벨2&lt;/code&gt; &lt;strong&gt;Repeatable Read&lt;/strong&gt; 반복 가능한 읽기&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;발생  &lt;code class=&quot;language-text&quot;&gt;InnoDB는 발생 X&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;레벨3&lt;/code&gt; &lt;strong&gt;Serializable&lt;/strong&gt;	직렬화 가능&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;read-uncommited&quot; style=&quot;position:relative;&quot;&gt;READ-UNCOMMITED&lt;a href=&quot;#read-uncommited&quot; aria-label=&quot;read uncommited permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 631px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/eca1145dc503fac3b82564f1a99d645e/4597d/readUncommitted.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100.8888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHUlEQVR42pVUx6oqURD0s3WjGxeCfoArQV0pGEBFxY0LEQMIKhgwgznnrPVuHd4Z1OvFsWEYZk53dXV1n9Zcr1csFgssl0us12vx3u12+GS32034MlbGbbdbaBhcrVbRbDbF4WazQa/X+wh4Op1Qq9VQqVQUEt1uF5rj8YhcLodsNovBYIDJZIJ2u60E3u/3Xw/tfD6LuHQ6LeLG4zFarRY0pE70fr8vmJbLZVECjXLw/NEkIN8scTabiRhWRxyNdOAh2bEUZr9cLiB7gksp6MMkj0Y/MpRJFEAGMtsjC4JYrVYYjUZYLBaYzWZEIpEnHyaXmj8BMvtoNMJ8PhdMyY7M3G43HA4HvF4vXC4XEonEEyAZUq63gMxEUJZAdhTabrfDZrMJQKfTqR6QAK8lsyGlUkl0slAoIJ/Piwq+BpTCD4dDmEwmaLVaGAwG6PV6hMPhp3GiNB8B5ahQQ4/HA5/Ph2g0ikAggGQyKaThGecuk8mg0+moK3m1WglAPmTm9/sRj8cxnU6F1nwTTDXgO5N3VjawXq+j0WioAzwcDojFYoIZGYZCIVGm1Jl+vAhv5/BdUyg2h1qn0ylNCQaD380htw5ZEJxPKpVCsVgUm4TbhWW+Xr0/u8xBlteJLGUSjg+7yibwnyrAd3dZjRHwWcOfmbv+3yxcP2RGJ7KUm+dxZb0a/+33e+VbbGxmmP4sBOpEjfjNJctvlvmN/QNAjOeYLcH0YgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;readUncommitted&quot;
        title=&quot;&quot;
        src=&quot;/static/eca1145dc503fac3b82564f1a99d645e/4597d/readUncommitted.png&quot;
        srcset=&quot;/static/eca1145dc503fac3b82564f1a99d645e/3684f/readUncommitted.png 225w,
/static/eca1145dc503fac3b82564f1a99d645e/fc2a6/readUncommitted.png 450w,
/static/eca1145dc503fac3b82564f1a99d645e/4597d/readUncommitted.png 631w&quot;
        sizes=&quot;(max-width: 631px) 100vw, 631px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;각 트랜잭션에서의 변경 내용이 &lt;code class=&quot;language-text&quot;&gt;COMMIT&lt;/code&gt;이나 &lt;code class=&quot;language-text&quot;&gt;ROLLBACK&lt;/code&gt;여부에 상관없이 다른 트랜잭션에서 보여지게 된다&lt;/strong&gt;&lt;br&gt;
RDBMS 표준 에서는 트랜잭션의 격리 수준으로 인정하지 않을 정도로 문제가 많으며 &lt;code class=&quot;language-text&quot;&gt;Dirty Read&lt;/code&gt;가 허용되는 격리 수준이다&lt;/p&gt;
&lt;h3 id=&quot;dirty-read-문제&quot; style=&quot;position:relative;&quot;&gt;Dirty Read 문제&lt;a href=&quot;#dirty-read-%EB%AC%B8%EC%A0%9C&quot; aria-label=&quot;dirty read 문제 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;한 트랜잭션에서 처리한 작업이 완료되지 않았음에도 불구하고 다른 트랜잭션에서 볼 수 있게 되는 현상&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;set autocommit = FALSE;

set transaction_isolation = &amp;#39;READ-UNCOMMITTED&amp;#39;;
SHOW VARIABLES LIKE &amp;#39;%isolation&amp;#39;;

-- &amp;lt;트랜잰셕 A&amp;gt;
start transaction;

update bookmark set name = &amp;#39;탈출&amp;#39; where id = &amp;#39;1&amp;#39;;
-- &amp;lt;/트랜잰셕 A&amp;gt;

-- &amp;lt;트랜잭션 B&amp;gt; Dirty Read 발생
select * from bookmark;
-- &amp;lt;/트랜잭션 B&amp;gt;

-- &amp;lt;트랜잭션 A&amp;gt;
rollback;
-- &amp;lt;/트랜잭션 A&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;A 트랜잭션&lt;/code&gt;의 작업단위를 최종적으로 끝내지않아도 다른 트랜잭션들도 해당 변경사항에 대해 노출된다.&lt;/p&gt;
&lt;h2 id=&quot;read-committed&quot; style=&quot;position:relative;&quot;&gt;READ-COMMITTED&lt;a href=&quot;#read-committed&quot; aria-label=&quot;read committed permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 685px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2f449257e0b0d29202b5990ecb45571d/8ce22/readCommitted.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 101.33333333333334%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACbUlEQVR42pVUuapiQRD1P40EEwNjNRZMTQQDEzXQ5BkIgo+noIiIqKgouO/7vu/Lq5lT0Jf79A4z01Dcpvv2qXNOVbfqfD7Tbrejw+HAsd/v6fl8khiYy+P7+1vaOx6P/L/8q1osFlQsFmm5XNJ2u6XNZkPj8ZgPyA+/jvv9ToPBgEqlEq3XayY1Go1INZ1OKRKJULvdptlsxmACEKNer1MqleKkuVxO2gPbarVK8XichsMhn221WqQCTWQol8u8AaZCMvYsFgvp9XoyGo1kMBjI7XZLyaBotVoxy8lkwupUQhYWrtfrD6mQlUgkmAXY5PN5ZiGG+A+sH48Hz1WCDdjVajVmhc3b7cYJut0uB1RAFlgIIPGFd0jOgGIRgGCBgwBCxU+nE6/DRzDrdDpcTQGmyFAsomLNZpNQJATYYM1qtZLJZCKz2czx8fEhSYYKMFMEVPIQreDz+RgkHA6T3++naDTKTJEQnZFOp6nRaEhnfgAK34SP8AsgXq+XAoEAz9FisAV7CFQaHotaSICQCc9QyUwmw1nhYzabpa+vLwoGgxSLxbgXX4vyR8mXy4VNRwAcHmq1WlKr1aTT6Uij0bCnYAMlCKjq9XrvDOUeyhsXtwSB5k0mk9yT8A4gCMhFMRU9fC2K0sBjAom4ISga3gK0lCIgJCsBiJ7EvjzZXz18ZQh/PB4P+2az2chut1MoFHprbNwURUA8DGAkgPEFoMPhIJfLRU6nkz4/P/+NIQyuVCqcDU0rHtxCocCthHVczfl8/gaIbngDBDP5Sy0OCGDsw8fXThDPnFSUxe+MyArJeAgwR1v0+3224H/HL46t2QW8QSnKAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;readCommitted&quot;
        title=&quot;&quot;
        src=&quot;/static/2f449257e0b0d29202b5990ecb45571d/8ce22/readCommitted.png&quot;
        srcset=&quot;/static/2f449257e0b0d29202b5990ecb45571d/3684f/readCommitted.png 225w,
/static/2f449257e0b0d29202b5990ecb45571d/fc2a6/readCommitted.png 450w,
/static/2f449257e0b0d29202b5990ecb45571d/8ce22/readCommitted.png 685w&quot;
        sizes=&quot;(max-width: 685px) 100vw, 685px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;온라인 서비스에서 가장 많이 선택되는 격리수준&lt;/strong&gt; &lt;em&gt;오라클 DBMS에서 기본적으로 사용되고 있다&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Undo 영역&lt;/strong&gt;을 통해서 데이터를 가져오기 때문에 Dirty Read는 발생하지 않지만 , &lt;code class=&quot;language-text&quot;&gt;NON_REPEATABLE READ&lt;/code&gt; 문제가 발생한다&lt;/p&gt;
&lt;h3 id=&quot;non-repeatable-read-문제&quot; style=&quot;position:relative;&quot;&gt;NON-REPEATABLE READ 문제&lt;a href=&quot;#non-repeatable-read-%EB%AC%B8%EC%A0%9C&quot; aria-label=&quot;non repeatable read 문제 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 837px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d3e28c67b919bed963714cc9d885eefe/ddc81/nonRepetableRead.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 93.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAACEElEQVR42o1UyaoqUQzsz3YjgiC4cSHoF4iCbnQjLgS3giK6UBHneZ7nue6tQBp99PN6IJzudLpSqeQc4/l84vF4iN3vd9xuN9D312IM41//47PBl0ajgXa7jfV6jfl8jsViYf6kydQ0GZ87nQ6q1SpWq5XYZDKBwQ/5fB65XA7j8RiDwUB2BfzEsFQqIZvNYjQaCVi/34fBD/v9XrJ1u11hSfpcl8sFhUIB6XRafiwWizgej2aZjCWRWq2GzWYj8YayoGO73ZrlcJGt0+mEzWaD3W6Hy+VCq9USsOv1ivP5LKxYrrI2AZmNoK+lkjmZsTTVuVKpSIk0VlUul980t2SoPrJYLpfybbfbSdLpdCpl09dqtpDJZDAcDr8DVB1Zmo4I9WUiGn1kZ8mQDOr1ugAzkIv6uN1uOBwO0c/r9b7ppbJYakhA6sFGqJ5MEAgEEAqFEA6HEY1G39h8BGS5zWbTFJygfCdIIpFAMplEKpWSxF8D9no9YcAAlk3tdCatBvsjoFVTGByLxRAMBhGJRBCPx99i2DCeKlZjCahzqIM9m83g8/ng8XikIX6/X+aSfo4KTxZnlKP1FcPT6SSNUm3ZMIIcDgdpGIedc0j/nwz1puEQU0uWR9P5053s/juH/3bwm0W2r0fWuP92kRkoLHfeh2rsHncy+3SNvRKQC3bzC8aOUkPdmYCMmZ3lfbt+ACnyo1llYM13AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;nonRepetableRead&quot;
        title=&quot;&quot;
        src=&quot;/static/d3e28c67b919bed963714cc9d885eefe/ddc81/nonRepetableRead.png&quot;
        srcset=&quot;/static/d3e28c67b919bed963714cc9d885eefe/3684f/nonRepetableRead.png 225w,
/static/d3e28c67b919bed963714cc9d885eefe/fc2a6/nonRepetableRead.png 450w,
/static/d3e28c67b919bed963714cc9d885eefe/ddc81/nonRepetableRead.png 837w&quot;
        sizes=&quot;(max-width: 837px) 100vw, 837px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;동일한 SELECT 쿼리를 실행했을 때 항상 같은 결과를 보장해야 한다는 &lt;code class=&quot;language-text&quot;&gt;&quot;REPEATABLE READ&quot;&lt;/code&gt; 정합성에 어긋난다&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;set autocommit = FALSE;

set transaction_isolation = &amp;#39;READ-COMMITTED&amp;#39;;
SHOW VARIABLES LIKE &amp;#39;%isolation&amp;#39;;

-- &amp;lt;트랜잭션 A&amp;gt;
start transaction;

select * from bookmark;
-- &amp;lt;/트랜잭션 A&amp;gt;

-- &amp;lt;트랜잭션 B&amp;gt;
start transaction;

update bookmark set name = &amp;#39;탈출&amp;#39; where id = &amp;#39;1&amp;#39;;

COMMIT;
-- &amp;lt;/트랜잭션 B&amp;gt;

-- &amp;lt;트랜잭션 A&amp;gt; NON-REPEATABLE READ 문제 발생
select * from bookmark;
-- &amp;lt;/트랜잭션 A&amp;gt;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;트랜잭션 A&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;첫 번째 SELECT&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;두 번째 SELECT&lt;/code&gt;의 결과가 서로 달라 &lt;strong&gt;하나의 트랜잭션 내에서 항상 똑같은 결과를 가져와야 하는 &lt;code class=&quot;language-text&quot;&gt;REPEATABLE-READ&lt;/code&gt;가 보장되지 않는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;일반적인 웹 프로그램에서는 크게 문제되지 않을 수 있지만, &lt;strong&gt;하나의 트랜잭션에서 동일 데이터를 여러 번 읽고 변경하는 작업이 금전적인 처리와 연결되면 문제가 될 수도 있다.&lt;/strong&gt;&lt;br&gt;
예를 들어, 다른 트랜잭션에서 입금과 출금 처리가 계속 진행될 때 다른 트랜잭션에서 오늘 입금된 금액의 총합을 조회한다고 가정해보면&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;REPEATABLE-READ&lt;/code&gt;가 보장되지 않기 때문에 총합을 계산하는 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt;쿼리는 실행될 때 마다 다른 결과를 가져올 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;중요한 것은 사용 중인 트랜잭션의 격리 수준에 의해 실행하는 SQL문장이 어떤 결과를 가져오게 되는지를 정확히 예측할 수 있어야 한다는 것이다.&lt;/strong&gt;&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;READ-COMMITTED&lt;/code&gt;에서는 트랜잭션 외부에서 실행되는 &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt;문과 내부에서 실행되는 것과 차이가 없다.&lt;br&gt;
하지만 &lt;code class=&quot;language-text&quot;&gt;REPEATABLE-READ&lt;/code&gt; 트랜잭션 내부에서는 온종일 동일한 결과가 반환된기 때문에 큰 차이가 있다.&lt;/p&gt;
&lt;h2 id=&quot;repeatable-read&quot; style=&quot;position:relative;&quot;&gt;REPEATABLE-READ&lt;a href=&quot;#repeatable-read&quot; aria-label=&quot;repeatable read permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 781px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4f84513a095fd89c8cd9933700e4c0d0/7fee5/repeatableRead.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 111.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACjElEQVR42o1VWWsyQRD07wpC3kV/gEFBouKzEUENGA+UBEEJ8YAIQl48olE8Erzv+0h/qYaW9YuJDjQ7uztbU9XVPavabDY0Go1oNpvRdDql+XxOyvH19XWI/8dqtTr6drFYkAo32WyWPj8/+SWuu92OP9jv9xwCKPcyxuMxZTIZarVaDNhsNkmFCQDz+Tx9fHxQvV4/YgNwqBBg2QjPer0eJZNJKhQKDMaAsqjT6fAikYkRiUTIaDSSwWDgq8lkooeHB36P9f1+n2U2Gg2Wj3EA7Ha7tN1uj5hBzuPjI93d3VEwGKRAIEAvLy80mUxoMBiwzEqlwiHfHgEKQwwsQI4gC5La7TavQb7ADPLe39+pXC7T29vbaUAlw9/GcrlkZsPhkHMO6QAWMgdA7Aw2YAXnAZ7L5cjv95PP56NQKMTyS6XSUTlJ/n/kEO7CaQFFeL1eslgsbAbi+vqagZUl9SsgnKpWqwyIwAZPT08UDofJ7XYz0P39PRt1EUMxRXZGOYAlclWr1bjgMVd20p+AYAUQ5A2uIvlOp5N0Oh1ptVrS6/U8RwmJ5LMMwQBSwQbsotEouVwucjgc5PF4eINEInGZZJQApIkpKF5pO4QSQNnbfzKUOpSFKBOz2UxWq5Vubm7IbrdTLBY7gF4MKJ2STqfZ3dvbW65FyH5+fubUSN0Wi0WuzbOdguKGSXAV5YQU4B5HHFoP7xEwD224Xq/PHw5wHIzi8TjLT6VS9Pr6epB61mUlIIxAl2g0GlKr1XR1dcVzm83Gm4ElehhVgQPiRy+fOhzwkbiPUsIc6yAT8iUtAD1IBghu5J+AOQISTv1HTg3pMAbELp3vsw6Otb6vyIece5IXSFT+rE6FjH9aoWvXquU5PAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;repeatableRead&quot;
        title=&quot;&quot;
        src=&quot;/static/4f84513a095fd89c8cd9933700e4c0d0/7fee5/repeatableRead.png&quot;
        srcset=&quot;/static/4f84513a095fd89c8cd9933700e4c0d0/3684f/repeatableRead.png 225w,
/static/4f84513a095fd89c8cd9933700e4c0d0/fc2a6/repeatableRead.png 450w,
/static/4f84513a095fd89c8cd9933700e4c0d0/7fee5/repeatableRead.png 781w&quot;
        sizes=&quot;(max-width: 781px) 100vw, 781px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MySQL의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;&quot;NON-REPEATABLE READ&quot;&lt;/code&gt; 부정합이 발생하지 않는다&lt;br&gt;
&lt;strong&gt;InnoDB 스토리지 엔진&lt;/strong&gt;은 트랜잭션이 &lt;code class=&quot;language-text&quot;&gt;ROLLBACK&lt;/code&gt;될 가능성에 대비해 &lt;strong&gt;변경되기 전 레코드를 언두(Undo) 영역에 백업해두고 실제 레코드 값을 변경한다&lt;/strong&gt;&lt;br&gt;
이러한 변경 방식을 &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;MVCC(Multi Version Concurrency Control)&lt;/code&gt;&lt;/a&gt;이라고 한다&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;✋ &lt;code class=&quot;language-text&quot;&gt;MVCC(Multi Version Concurrency Control)&lt;/code&gt; - &lt;strong&gt;잠금을 사용하지 않는 일관된 읽기를 제공&lt;/strong&gt;&lt;br&gt;
MVCC는 다중 버전 병행수행 제어의 약자로 DBMS에서는 쓰기(Write) 세션이 읽기(Read) 세션을 블로킹하지 않고,&lt;br&gt;
읽기 세션이 쓰기 세션을 블로킹하지 않게 서로 다른 세션이 동일한 데이터에 접근했을 때 각 세션마다 스냅샷 이미지를 보장해주는 메커니즘이며 하나의 레코드에 대해 여러 개의 버전이 동시에 관리된다는 의미다.&lt;br&gt;
변경되기 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환하는 과정을 DBMS에서는 &lt;strong&gt;MVCC&lt;/strong&gt;라고 표현한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;COMMIT&lt;/code&gt;명령을 실행한다면 현재 시점의 상태를 영구적인 데이터로 만들어버리지만, (COMMIT이 된다고 항상 바로 삭제되지 않고, 해당 언두 영역을 필요로 하지 않는 트랜잭션이 더는 없을 때 삭제한다.)&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;ROLLBACK&lt;/code&gt;을 실행한다면 &lt;strong&gt;언두 영역에 있는 백업된 데이터를 InnoDB 버퍼 풀로 다시 복구하고, 언두 영역의 내용을 삭제해 버린다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;REPEATABLE READ&lt;/strong&gt;는 이 &lt;code class=&quot;language-text&quot;&gt;MVCC&lt;/code&gt;를 위해 &lt;strong&gt;언두 영역에 백업된 이전 데이터를 통해 동일한 트랜잭션 내에서는 동일한 결과를 보여줄 수 있도록 보장한다&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호(순차적으로 증가하는 값)를 가지며&lt;/strong&gt;, 언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함되어 있다&lt;br&gt;
&lt;strong&gt;REPEATABLE READ 격리 수준&lt;/strong&gt;에서는 &lt;code class=&quot;language-text&quot;&gt;MVCC&lt;/code&gt;를 보장하기 위해 실행중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 언두 영역의 데이터는 삭제할 수 없다&lt;/p&gt;
&lt;h3 id=&quot;phantom-read-문제&quot; style=&quot;position:relative;&quot;&gt;PHANTOM READ 문제&lt;a href=&quot;#phantom-read-%EB%AC%B8%EC%A0%9C&quot; aria-label=&quot;phantom read 문제 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 851px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/db48263d3b0bb69d05bbbcd0e708b275/0fcea/phantomRead.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100.8888888888889%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACP0lEQVR42o2UyW4qQQxF+XIiFkEsWMCCP4A1sGUDEpMAiSEBITET5nmeB793SqoWgUBiyerqrqpr+/q6TYfDQabTqaxWK8Ov16v8xTg7m81kvV7LcrmU3W4nJhaRSEQajYbabLfbcjwe1QWAnzn29fUlsVhMxuOxDIdD6XQ6YgK1Wq1KqVSSXq8no9Ho1wxPp5Nst1sV/PPzU2q1mnS7XVksFmLSlwHSmWkjGEGIPBgMFDWUh/OdisiMtTYDcDKZfANk7fF45O3tTbnZbBaLxaLogTuo6vf78vHxobLTFBmA8ECDtLGGikKhINFoVPL5vORyOSkWi0YzKLnZbKoKHjIE8DZDvsPVfD5XFwDZ7/fqHN8AImC5XJZWq/WYITzQNS5A7mazEa/XK3a7XZxOpzgcDnG5XCpL7HK5GNTA40OGREmn0yoyUev1uiQSCQkGg+L3+yUQCChnT2eDnc9n1dAHQAimNMimQZSVzWZVE3w+n9JbOBz+Vt5LQNKGaPQFV5SCxuLxuGpKMplU4LcdfQmo9YbW9DjRlN/sKeB9l1/Z7fgR9Cmg1qG+AA3wiCNgZIJ8/lTyfYas3W63WK1Wsdlsyt/f3yUUCimOAYYeuv7j6P0EyMFKpaLkw0WcvxIZoQT2MpmM0u+DsDl0O3oA8kMAIJVKqUus0Sk/DRqHVqGCbw+ApH/fVaYBjjjDk33K1SXrstGwUTLRFv9FTETK0E8tH568M4rPTI+hAqRMZhdBMyVE5R3nHdf7z/7it/YP0Nzpl5TRk9gAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;phantomRead&quot;
        title=&quot;&quot;
        src=&quot;/static/db48263d3b0bb69d05bbbcd0e708b275/0fcea/phantomRead.png&quot;
        srcset=&quot;/static/db48263d3b0bb69d05bbbcd0e708b275/3684f/phantomRead.png 225w,
/static/db48263d3b0bb69d05bbbcd0e708b275/fc2a6/phantomRead.png 450w,
/static/db48263d3b0bb69d05bbbcd0e708b275/0fcea/phantomRead.png 851w&quot;
        sizes=&quot;(max-width: 851px) 100vw, 851px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안보였다가 하는 현상을 &lt;code class=&quot;language-text&quot;&gt;PHANTOM READ(PHANTOM ROW)&lt;/code&gt;&lt;/strong&gt; 라고한다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;CREATE TABLE employees (
    id int NOT NULL,
    first_name varchar(255) DEFAULT NULL,
    last_name varchar(255) DEFAULT NULL,
    PRIMARY KEY (id),
    KEY idx_first_name (first_name)
) ENGINE=InnoDB;

+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
|  1 | John       | Doe1      |
|  2 | John       | Doe2      |
|  3 | John       | Doe3      |
|  4 | John       | Doe4      |
|  5 | Jane       | Ann1      |
|  6 | Jane       | Ann2      |
|  7 | Jane       | Ann3      |
|  8 | Jack       | Tim1      |
|  9 | Jack       | Tim2      |
| 10 | Jack       | Tim3      |
+----+------------+-----------+&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 테이블로 테스트를 해보자.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;트랜잭션 A&amp;gt;
set transaction_isolation = &amp;#39;READ-COMMITTED&amp;#39;;
set autocommit=false;

select * from employees where id &amp;gt;= 8 for update;
-- 3 rows in set (0.01 sec)

-- &amp;lt;트랜잭션 B&amp;gt;
set transaction_isolation = &amp;#39;READ-COMMITTED&amp;#39;;
set autocommit=false;

SELECT OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-------------+------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+------------+-----------+---------------+-------------+-----------+
| employees   | NULL       | TABLE     | IX            | GRANTED     | NULL      |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 8         |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 9         |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 10        |
+-------------+------------+-----------+---------------+-------------+-----------+

insert into employees(id, first_name, last_name) values (11, &amp;#39;Test&amp;#39;, &amp;#39;Test1&amp;#39; );
commit;
-- Query OK, 1 row affected (0.03 sec)
-- &amp;lt;/트랜잭션 B&amp;gt;

-- &amp;lt;트랜잭션 A&amp;gt;
select * from employees where id &amp;gt;= 8 for update;
-- 4 rows in set (0.00 sec)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;트랜잭션 A&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;첫 번째 SELECT FOR UPDATE&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;두 번째 SELECT FOR UPDATE&lt;/code&gt;의 결과가 서로 다르다.&lt;br&gt;
&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;SELECT ... LOCK IN SHARE MODE&lt;/code&gt;는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 레코드에는 잠금을 걸 수 없기 때문에 조회되는 레코드는 언두 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드의 값을 가져오게 되는 것이다.&lt;/strong&gt;&lt;br&gt;
격리 수준을 REPETABLE-READ로 올려보고 테스트하면 아래와 같이 해결된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;sql&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;-- &amp;lt;트랜잭션 B&amp;gt;
set transaction_isolation = &amp;#39;REPEATABLE-READ&amp;#39;;
set autocommit=false;
select * from employees where id &amp;gt;= 8 for update;
-- 3 rows in set (0.00 sec)

-- &amp;lt;트랜잭션 A&amp;gt;
set transaction_isolation = &amp;#39;REPEATABLE-READ&amp;#39;;
set autocommit=false;

SELECT OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-------------+------------+-----------+---------------+-------------+------------------------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA              |
+-------------+------------+-----------+---------------+-------------+------------------------+
| employees   | NULL       | TABLE     | IX            | GRANTED     | NULL                   |
| employees   | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 8                      |
| employees   | PRIMARY    | RECORD    | X             | GRANTED     | supremum pseudo-record |
| employees   | PRIMARY    | RECORD    | X             | GRANTED     | 9                      |
| employees   | PRIMARY    | RECORD    | X             | GRANTED     | 10                     |
+-------------+------------+-----------+---------------+-------------+------------------------+

insert into employees(id, first_name, last_name) values (11, &amp;#39;Test&amp;#39;, &amp;#39;Test1&amp;#39; );
-- 트랜잭션 B의 상한 락으로 인해 무한 대기

insert into employees(id, first_name, last_name) values (0, &amp;#39;Test&amp;#39;, &amp;#39;Test1&amp;#39; );
-- 8 이후의 레코드에만 락이 걸려있기 때문에 id가 0인 레코드는 삽입이 바로 가능하다.&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;InnoDB 스토리지 엔진에서는 레크도 락과 갭 락을 결합한 &lt;strong&gt;넥스트 키 락&lt;/strong&gt; 덕분에 &lt;code class=&quot;language-text&quot;&gt;PHANTOM READ&lt;/code&gt;문제가 발생하지 않는다.&lt;br&gt;
즉, 한 세션이 인덱스의 &lt;code class=&quot;language-text&quot;&gt;R&lt;/code&gt;레코드 에 공유 또는 독점 잠금을 설정한 경우, &lt;strong&gt;다른 세션은 인덱스 순서에서 &lt;code class=&quot;language-text&quot;&gt;R&lt;/code&gt;레코드 바로 앞에 새 인덱스 레코드를 삽입할 수 없다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;select .. where id &gt;= 8 for update;&lt;/code&gt; 쿼리로 인해 &lt;code class=&quot;language-text&quot;&gt;supremum pseudo-record&lt;/code&gt; 락이 발생하였으며 &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt;가 8 이후의 레코드에 대해서는 삽입을 할 수 없다.&lt;br&gt;
즉, &lt;strong&gt;테이블에 존재하지 않는 항목을 &apos;잠금&apos;할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;serializable&quot; style=&quot;position:relative;&quot;&gt;SERIALIZABLE&lt;a href=&quot;#serializable&quot; aria-label=&quot;serializable permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;가장 단순한 격리 수준이면서 동시에 가장 엄격한 격리 수준이다.&lt;br&gt;
그만큼 동시 처리 성능도 다른 격리 수준에 비해 떨어진다.&lt;br&gt;
위에서 말했던 **Non-Locking consistent read (잠금이 필요 없는 일관된 읽기)**를 지키지 않으며, &lt;strong&gt;읽기 작업도 공유 잠금(읽기 잠금)을 획득해야만 하며, 동시에 다른 트랜잭션은 접근하지 못한다.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[2024년 기록]]></title><description><![CDATA[강의 장애 없는 서비스를 만들기 위한 Resilience4j - CircuitBreaker 급하게 서킷브레이커를 학습하기 위해 수강해보았다. 강의 시간은 2시간 36분 밖에 되지 않지만 꽤 알찬 강의라고 느꼈다. 아래의 내용을 학습했다. Retry…]]></description><link>https://jdalma.github.io/2024y/bookReview/bookReview/</link><guid isPermaLink="false">https://jdalma.github.io/2024y/bookReview/bookReview/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;강의&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;강의&lt;/strong&gt;&lt;a href=&quot;#%EA%B0%95%EC%9D%98&quot; aria-label=&quot;강의 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;장애-없는-서비스를-만들기-위한-resilience4j---circuitbreaker&quot; style=&quot;position:relative;&quot;&gt;장애 없는 서비스를 만들기 위한 Resilience4j - CircuitBreaker&lt;a href=&quot;#%EC%9E%A5%EC%95%A0-%EC%97%86%EB%8A%94-%EC%84%9C%EB%B9%84%EC%8A%A4%EB%A5%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%9C%84%ED%95%9C-resilience4j---circuitbreaker&quot; aria-label=&quot;장애 없는 서비스를 만들기 위한 resilience4j   circuitbreaker permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;급하게 서킷브레이커를 학습하기 위해 수강해보았다. 강의 시간은 2시간 36분 밖에 되지 않지만 꽤 알찬 강의라고 느꼈다.&lt;br&gt;
아래의 내용을 학습했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Retry에 대한 이해&lt;/li&gt;
&lt;li&gt;서킷브레이커의 각 상태에 대한 이해 (HALF_OPEN 상태가 존재하는 이유 등)&lt;/li&gt;
&lt;li&gt;상태 변경을 위한 조건 설정&lt;/li&gt;
&lt;li&gt;fallback 기능에 대한 이해&lt;/li&gt;
&lt;li&gt;actuaotr를 활용한 서킷브레이커 설정 정보 조회 및 수정&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;자세한 설정 정보를 직접 확인하여 테스트를 진행할 수 있을 정도의 지식은 얻을 수 있기에, 급하게 서킷브레이커에 대한 내용을 개괄적으로 이해하고 싶다면 강의를 추천한다.&lt;/p&gt;
&lt;h2 id=&quot;견고한-결제-시스템-구축&quot; style=&quot;position:relative;&quot;&gt;견고한 결제 시스템 구축&lt;a href=&quot;#%EA%B2%AC%EA%B3%A0%ED%95%9C-%EA%B2%B0%EC%A0%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95&quot; aria-label=&quot;견고한 결제 시스템 구축 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;평소에 결제/정산 도메인에 관심이 있었어서 강의가 공개되자마자 구매해놓았었는데 이번에 완강해보았다.&lt;br&gt;
결제,정산,장부 서비스들을 Spring webflux, R2DBC, 헥사고날 아키텍처, kafka를 사용하여 구현해보는 강의이다.&lt;br&gt;
위의 기술 스택과 거리가 먼 나로서는 최소한 결제/정산 도메인 흐름과 헥사고날 아키텍처, kafka에 대한 이해를 목표로 하였다.&lt;/p&gt;
&lt;p&gt;이 강의를 통해 아래의 내용을 학습할 수 있었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;결제 시스템에 필요한 요소들&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주문부터 결제 요청,정산,장부, 결제완료까지의 흐름&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결제 승인 에러 핸들링과 재시도 메커니즘&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결제 복구 작업&lt;/strong&gt; (+ Bulk Head Pattern)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kafka를 통한 트랜잭션 아웃박스 패턴&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kafka와 kafka 트랜잭션에 대한 찍먹&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kafka의 DLQ를 통한 서비스 신뢰성 향상&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;더 견고해지기 위해 추가적으로 신경써야 할 부분&lt;/strong&gt; (보안, 모니터링 지표, 신뢰성을 향상 시킬 수 있는 다른 방법)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;강의의 흐름이 굉장히 빠르고 webflux와 헥사고날 아키텍처에 익숙하지 않다면 초반에 고생할 수도 있다..&lt;br&gt;
하지만 Spring webflux와 R2DBC가 도메인을 이해하는데 걸림돌이 될 정도는 아니며 제공되는 교안이 상당히 고품질이다.&lt;br&gt;
추후에 예제 코드와 교안만 다시 보아도 이해하는데 문제가 없을정도이다.&lt;br&gt;
그래도 헥사고날 아키텍처는 이 강의를 보기전에 대략적으로 이해하고 보는것을 추천한다. 그렇지 않으면 패키지 구조나 왜 이런 추상화와 구현체가 생기는지 이해할 수 없어 넋놓고 따라치기만 할 가능성이 높다.&lt;br&gt;
사전에 이 &lt;a href=&quot;https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8/dashboard&quot;&gt;강의&lt;/a&gt;를 보길 추천한다.&lt;/p&gt;
&lt;h2 id=&quot;javaspring-테스트를-추가하고-싶은-개발자들의-오답노트&quot; style=&quot;position:relative;&quot;&gt;Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트&lt;a href=&quot;#javaspring-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%B6%94%EA%B0%80%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%9D%80-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%93%A4%EC%9D%98-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8&quot; aria-label=&quot;javaspring 테스트를 추가하고 싶은 개발자들의 오답노트 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;극찬의 &lt;a href=&quot;https://inf.run/FcsUb&quot;&gt;수강평&lt;/a&gt;과 커리큘럼에 헥사고날 아키텍처가 있어서 구매해보았다.&lt;br&gt;
(&lt;a href=&quot;https://www.inflearn.com/course/%EA%B2%AC%EA%B3%A0%ED%95%9C-%EA%B2%B0%EC%A0%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95?srsltid=AfmBOooVYgoCpuSqsins2KXxboQ_iBZVndJoB9L7fa6OFB39qOdCTztw&quot;&gt;견고한 결제 시스템 구축&lt;/a&gt; 강의를 들으면서 헥사고날 아키텍처 때문에 고생하고 있었다.)&lt;/p&gt;
&lt;p&gt;강의 초반 ~ 중반까지는 지루한 이론 + 리팩토링 과정이 진행된다. &lt;em&gt;그래도 좋은 설계 + 객체지향 + 테스트 가능성에 대한 강조는 언제 들어도 항상 좋다.&lt;/em&gt;&lt;br&gt;
처음엔 mockito와 h2를 이용한 중형 테스트와 mockmvc를 이용한 대형 테스트부터 작성하여 이 코드를 점차 개선해나간다.&lt;br&gt;
외부 라이브러리에 의존하는 테스트를 제거하고 순수 도메인 코드로 테스트를 적용해간다. 이 부분이 꽤 지루하지만 이 작업을 견뎌내야 후반에 크게 공감할 수 있다.&lt;/p&gt;
&lt;p&gt;일반적인 테스트 코드 작성부터 시작하여 지루한 리팩토링 작업이 끝난 후에 레이어드 아키텍처의 단점부터 헥사고날 아키텍처에 대한 내용을 설명하는 빌드업이 너무 좋았다.&lt;br&gt;
게다가 아키텍처에 대한 강사님의 생각과 생각의 근거까지 완벽하게 설명하여 후반은 시간가는 줄 모르고 봤다.&lt;br&gt;
(&lt;code class=&quot;language-text&quot;&gt;패키지간 의존성 방향은 어떻게 되어야 하는지?&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;모델은 어디까지 분리해야 하는지?&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Repository는 어떻게 다뤄야 하는지?&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Use Case는 추상화 되어야 하는지?&lt;/code&gt; 등)&lt;/p&gt;
&lt;p&gt;대략적으로 아래의 내용들이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;layer 패키지 구조를 domain/layer 패키지 구조로 변경하여 도메인을 더 노출한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;domain 패키지를 추가하고 repository 패키지를 이제 외부 통신을 담당하는 infrastructure 패키지로 리네이밍한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UUID와 Clock 같은 테스트하기 힘든 지점을 의존성 역전을 통해 개선한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;port-adapter(의존성 역전) 패턴을 적용하여 헥사고날 아키텍처로 개선한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모든 테스트를 mockito와 h2에 대한 의존없이 개선한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;레이어드 아키텍처 밖에 모르고 헥사고날 아키텍처에 관심이 있는 분, 테스트 가능성과 좋은 코드에 대해 욕심이 있는 분이라면 시야를 넓히기 굉장히 좋은 강의다.&lt;br&gt;
강의가 총 6시간 20분 밖에 안되는게 아쉬울 정도로 너무 알찬 구성에 만족하는 강의였다.&lt;/p&gt;
&lt;h2 id=&quot;토비의-스프링-6---이해와-원리&quot; style=&quot;position:relative;&quot;&gt;토비의 스프링 6 - 이해와 원리&lt;a href=&quot;#%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-6---%EC%9D%B4%ED%95%B4%EC%99%80-%EC%9B%90%EB%A6%AC&quot; aria-label=&quot;토비의 스프링 6   이해와 원리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;얼마전에 올라온 인프런의 &lt;a href=&quot;https://www.inflearn.com/course/%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%816-%EC%9D%B4%ED%95%B4%EC%99%80-%EC%9B%90%EB%A6%AC#&quot;&gt;토비님 강의&lt;/a&gt;가 토비의 스프링 1권에 대한 내용을 설명한다고 하셔서 학습해보았다.&lt;br&gt;
1권을 읽은 입장으로는 요즘 시대에 맞는 내용들만 추려서 핵심만 설명해주신 것 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스프링 IoC/DI의 이해를 돕기위한 DIP와 OCP를 적용하면서 점진적인 구조 개선&lt;/li&gt;
&lt;li&gt;시간이나 외부 서비스가 포함된 테스트 케이스를 작성하는 방법&lt;/li&gt;
&lt;li&gt;스프링에서 사용하는 템플릿을 이해하기 위한 템플릿/콜백 패턴 예제&lt;/li&gt;
&lt;li&gt;스프링의 예외 추상화&lt;/li&gt;
&lt;li&gt;서비스 추상화를 이해하기 위한 JPA → JDBC로 변경&lt;/li&gt;
&lt;li&gt;트랜잭션 AOP를 이해하기 위한 트랜잭션 프록시 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 내용을 깊진 않지만 넓게 설명해주었다.&lt;br&gt;
1권을 읽은 분이라면 복습하기 좋고, 1권을 읽지 않았다면 강의에서 설명하는 내용만 더 자세한 내용을 책에서 읽어보면 좋을 것 같다.&lt;/p&gt;
&lt;h1 id=&quot;기술&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;기술&lt;/strong&gt;&lt;a href=&quot;#%EA%B8%B0%EC%88%A0&quot; aria-label=&quot;기술 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;만들면서-배우는-클린-아키텍처&quot; style=&quot;position:relative;&quot;&gt;만들면서 배우는 클린 아키텍처&lt;a href=&quot;#%EB%A7%8C%EB%93%A4%EB%A9%B4%EC%84%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98&quot; aria-label=&quot;만들면서 배우는 클린 아키텍처 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이때까지 도메인을 노출하는 형태의 레이어드 아키텍처만 사용해왔었다. 필요한 곳에 &lt;code class=&quot;language-text&quot;&gt;common&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;util&lt;/code&gt; 패키지를 추가하여 어중간한 책임을 가진 클래스를 몰아넣는 지름길을 많이 선택해왔다.&lt;br&gt;
이번 기회에 토이 프로젝트를 진행하면서 레이어드 아키텍처의 단점을 보완하면서 클린 아키텍처의 장점을 차용하는 멀티 모듈 아키텍처를 고민할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8/dashboard&quot;&gt;Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트&lt;/a&gt;, &lt;a href=&quot;https://www.inflearn.com/course/%EA%B2%AC%EA%B3%A0%ED%95%9C-%EA%B2%B0%EC%A0%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95?srsltid=AfmBOooVYgoCpuSqsins2KXxboQ_iBZVndJoB9L7fa6OFB39qOdCTztw&quot;&gt;견고한 결제 시스템 구축&lt;/a&gt; 이 두 강의를 먼저 학습한 후에 읽은 것이라서 클린 아키텍처에 대한 특징과 구조는 이해하고 있었다.&lt;br&gt;
하지만 레이어드 아키텍처의 명확한 단점을 설명하거나 클린 아키텍처의 각 계층에 대한 책임은 설명할 수 없는 수준이였기에 이 책을 읽게 되었다.&lt;br&gt;
(유명한 &apos;클린 아키텍처&apos;, &apos;엔터프라이즈 애플리케이션 아키텍처 패턴 책&apos;과 같이 고민하였지만.. 너무 두꺼워서 이 책을 선택하였다.)&lt;/p&gt;
&lt;p&gt;이 책은 레이어드 아키텍처의 단점을 설명하면서 이 단점을 상쇄하는 클린 아키텍처로 내용을 전개한다.&lt;br&gt;
책이 꽤 얇지만 인커밍, 아웃고잉 어댑터와 유스케이스가 가져야하는 책임과 계층간 모델 매핑 전략, 멀티 모듈에 대한 설명도 간략하게 하여 클린 아키텍처에 대한 윤곽은 잡기 충분하다고 느꼈다.&lt;br&gt;
그리고 저자는 어떤 아키텍처가 옳다, 틀리다라는 관점을 가지고 있지 않고 현재 구조의 단점을 파악하여 함정을 잘 피해나가라고 조언한다.&lt;/p&gt;
&lt;p&gt;즉, 레이어드 아키텍처의 단점을 상쇄하는 클린 아키텍처에도 함정은 있다는 것이다.&lt;br&gt;
대표적으로 복잡도가 올라가고 작성해야 하는 코드 양이 늘어나기에 적절한 트레이드 오프를 통해 결정해야 한다고 한다.&lt;/p&gt;
&lt;p&gt;이 책을 통해 토이 프로젝트 아키텍처의 가이드라인을 세우는 데 많은 도움이 되었다.&lt;br&gt;
클린 아키텍처에 대해 너무 심오하게는 말고 가볍게 윤곽을 잡고 싶다면 이 책을 추천한다.&lt;/p&gt;
&lt;h2 id=&quot;이벤트-소싱과-마이크로서비스-아키텍처&quot; style=&quot;position:relative;&quot;&gt;이벤트 소싱과 마이크로서비스 아키텍처&lt;a href=&quot;#%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%86%8C%EC%8B%B1%EA%B3%BC-%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&quot; aria-label=&quot;이벤트 소싱과 마이크로서비스 아키텍처 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;MSA에서 일관성 관리는 어떻게 하지? 작업 단위는 어떻게 관리하지? 이런 궁금증이 항상 있었는데 코드스피츠에서 이 책으로 스터디를 모집하기에 읽어보았다.&lt;br&gt;
도메인 주도 설계부터 애그리게이트 설계, 객체지향 설계 원칙까지 설명하고 나서야 이벤트 소싱과 MSA를 설명해주기 때문에 베이스 지식이 없어도 부담이 조금 덜 했던 것 같다.&lt;/p&gt;
&lt;p&gt;이벤트 소싱 자체는 나에게 엄청 신기하게 다가왔다. 상태 변화를 도메인 이벤트를 기준으로 기록하여 필요할 때 재수화하여 (성능상 스냅샷이나 CQRS를 이용할 수 있다.) 최종 상태를 만들어 응답한다는 것은 생각도 해본적이 없었기 때문이다.&lt;br&gt;
(이 이벤트 소싱을 적극적으로 활용하는 곳이 있다면 다녀보고 싶다는 생각이 들었다.)&lt;br&gt;
배우게 된 내용을 정리해보자면&lt;/p&gt;
&lt;ol&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;li&gt;EventStore (메시지 릴레이에서 이벤트 브로커로 도메인 이벤트를 발행하기 위함)&lt;/li&gt;
&lt;li&gt;분산 트랜잭션&lt;/li&gt;
&lt;li&gt;사가 (오케스트레이션, 코레오그래피) : 시나리오를 전부 설명하기에 294p부터 참고&lt;/li&gt;
&lt;li&gt;CQRS&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;평소에 궁금했었던 내용들을 이 책을 통해 학습할 수 있게 되어서 재밌었다.&lt;br&gt;
이벤트 소싱과 MSA에 대해 윤곽도 잡으면서 입문하기에는 부담없는 책인 것 같다.&lt;/p&gt;
&lt;h2 id=&quot;자바의-신-1권-2권&quot; style=&quot;position:relative;&quot;&gt;자바의 신 1권, 2권&lt;a href=&quot;#%EC%9E%90%EB%B0%94%EC%9D%98-%EC%8B%A0-1%EA%B6%8C-2%EA%B6%8C&quot; aria-label=&quot;자바의 신 1권 2권 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;자바 기본서를 진득하게 다 읽어본적도 없고 flab 필독서로 나와있길래 읽어보았다.&lt;br&gt;
언어를 처음 배울 때 학습했던 것들을 상기시키기 좋았으며, 처음 배운 내용도 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;String 클래스가 자바 9부터 개선되었다.
&lt;ol&gt;
&lt;li&gt;내부 문자열 저장을 &lt;code class=&quot;language-text&quot;&gt;char[]&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;byte[]&lt;/code&gt;로 변경하였다.&lt;/li&gt;
&lt;li&gt;내부 &lt;code class=&quot;language-text&quot;&gt;coder&lt;/code&gt; 필드를 통해 인코딩 방식에 따라 UTF16을 사용할지, LATIN1을 사용할지 지정하여 최적화한다. (Compact String)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;자식이 메서드 오버라이딩 시 접근 권한을 확장 시킬 수 있다.&lt;/li&gt;
&lt;li&gt;자바의 sealed class와 permits, non-sealed&lt;/li&gt;
&lt;li&gt;HotSpot 클라이언트 컴파일러와 HotSpot 서버 컴파일러&lt;/li&gt;
&lt;li&gt;Thread 생성 시 &lt;code class=&quot;language-text&quot;&gt;stackSize&lt;/code&gt;를 통해 스택의 크기를 제한할 수 있다.&lt;/li&gt;
&lt;li&gt;Thread는 임계영역 안에서 &lt;code class=&quot;language-text&quot;&gt;wait()&lt;/code&gt;을 실행하면 해당 lock을 반환한 상태로 기다린다.&lt;/li&gt;
&lt;li&gt;Serializable과 &lt;code class=&quot;language-text&quot;&gt;serialVersionUID&lt;/code&gt;에 대한 내용&lt;/li&gt;
&lt;li&gt;non reifiable varargs 타입에 대한 &lt;code class=&quot;language-text&quot;&gt;@SafeVarargs&lt;/code&gt; 어노테이션&lt;/li&gt;
&lt;li&gt;NIO의 WatchService&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openjdk.org/projects/jdk/21/&quot;&gt;OpenJDK 21&lt;/a&gt; 문서를 통해 자바의 버전별 개선 사항을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;Publish-Subscribe 프레임워크의 publisher와 subscriber 사이에 존재하는 Processor&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;더 빨리 배웠으면 좋았을만한 내용들이 많아서 첫 회사에 입사하자마자 읽었었다면 도움이 많이 되었을텐데 이제 읽어서 아쉽다.&lt;br&gt;
자바의 Thread, NIO, 버전별 변화에 대해 학습하고 싶다면 2권만 읽기를 추천한다.&lt;/p&gt;
&lt;h2 id=&quot;oauth2-인-액션-네티-인-액션-리뷰-내용-작성-시점을-놓침&quot; style=&quot;position:relative;&quot;&gt;OAuth2 인 액션, 네티 인 액션 &lt;em&gt;리뷰 내용 작성 시점을 놓침..&lt;/em&gt;&lt;a href=&quot;#oauth2-%EC%9D%B8-%EC%95%A1%EC%85%98-%EB%84%A4%ED%8B%B0-%EC%9D%B8-%EC%95%A1%EC%85%98-%EB%A6%AC%EB%B7%B0-%EB%82%B4%EC%9A%A9-%EC%9E%91%EC%84%B1-%EC%8B%9C%EC%A0%90%EC%9D%84-%EB%86%93%EC%B9%A8&quot; aria-label=&quot;oauth2 인 액션 네티 인 액션 리뷰 내용 작성 시점을 놓침 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h2 id=&quot;오브젝트&quot; style=&quot;position:relative;&quot;&gt;오브젝트&lt;a href=&quot;#%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8&quot; aria-label=&quot;오브젝트 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 책은 2년전 코드숨 소소한 스터디에서 한 번 진행한적이 있었는데 초반에 조금 읽다가 포기한 경험이 있다.&lt;br&gt;
그간 읽어야지 읽어야지 하면서 책이 두꺼워 못 펼쳐보았었는데 이번에 다 읽게 되었다.&lt;br&gt;
토비의 스프링과 모던 자바 인 액션 같이 &apos;조금 더 빨리 읽었으면 좋았겠다.&apos; 라는 생각이 들 정도로 내용이 좋았다.&lt;br&gt;
얄팍한 지식으로 대충 지레직잠하고 있던 내용들을 확실하게 학습할 수 있었고, 깨닫게 되는 내용도 많았어서 좋았다.&lt;/p&gt;
&lt;p&gt;책이 꽤 두꺼운만큼 여러 개의 설계 예제들이 등장하며, 이 예제들을 이리저리 수정하고 확장해보면서 트레이드 오프가 어디서 어떻게 발생하는지 친절하게 설명해준다.&lt;br&gt;
&apos;이것이 왜 좋은 설계인지? 이것이 왜 나쁜 설계인지?&apos;를 배울 수 있다.&lt;/p&gt;
&lt;p&gt;책을 읽은 후에 아래와 같은 내용들도 깨닫게 되었다.&lt;/p&gt;
&lt;ol&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;li&gt;...&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;객체지향 프로그래밍이라는 크고 두루뭉술한 단어에 대한 스스로의 주관적인 생각과 윤곽을 잡기에 좋은 책이라는 생각이 들었다.&lt;/strong&gt;&lt;br&gt;
만약 아래에 대한 질문에 관심이 생긴다면 이 책을 강력 추천한다.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 협력, 책임, 역할에 대해 이해하고 있는가? 설계에서 얼마나 고려하는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 명령 쿼리 분리 원칙을 이해하고 있는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 유연하고 확장 가능한 협력관계를 구축하기 위해서는 객체와 객체 간에 어떤 의존성이 존재해야 하는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 서브클래스라고 모두 서브타이핑이라고 볼 수 있는가?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 추상 클래스와 인터페이스, 상속과 합성에 대한 차이점을 이해하고 트레이드 오프를 고민해볼 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;코틀린-디자인-패턴-2e&quot; style=&quot;position:relative;&quot;&gt;코틀린 디자인 패턴 2/e&lt;a href=&quot;#%EC%BD%94%ED%8B%80%EB%A6%B0-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-2e&quot; aria-label=&quot;코틀린 디자인 패턴 2e permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 책은 코틀린으로 생성 패턴, 구조 패턴, 동작 패턴 그리고 동시성 패턴까지 활용하는 방법을 알려준다.&lt;br&gt;
가장 인상 깊은 내용은 코루틴을 활용하는 동시성 패턴이였다.&lt;br&gt;
코틀린의 sequence, channel, flow가 자바의 Executor API보다 훨씬 사용하기 편하고 빠르다는 것을 알 수 있어서 좋았다.&lt;/p&gt;
&lt;p&gt;언어 레벨에서 연산이 오래 걸리는 작업을 분배하여 처리하고 결과를 다시 묶어서 보내는 등 코루틴에 대한 흥미가 생겼다.&lt;br&gt;
그리고 코틀린 다운 코드는 어떻게 작성하는지, &lt;code class=&quot;language-text&quot;&gt;reified&lt;/code&gt;가 무엇인지 알 수 있었다.&lt;/p&gt;
&lt;p&gt;코틀린 기본 정도만 알고 있으면 충분히 학습할 수 있는 내용들이라서 코틀린과 코루틴에 대한 인사이트를 얻고 싶다면 추천하고 싶다.&lt;br&gt;
스터디 내용은 &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%8A%A4%ED%84%B0%EB%94%94/%EC%BD%94%ED%8B%80%EB%A6%B0%20%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4.md&quot;&gt;여기서&lt;/a&gt;확인할 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;http2-인-액션&quot; style=&quot;position:relative;&quot;&gt;HTTP/2 인 액션&lt;a href=&quot;#http2-%EC%9D%B8-%EC%95%A1%EC%85%98&quot; aria-label=&quot;http2 인 액션 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;사내에서 Armeria + gRPC를 통한 POC를 진행한 적이 있는데 HTTP/2에 대한 이해가 없었어서 힘들었던 경험이 있다.&lt;br&gt;
위의 경험 때문에 &lt;a href=&quot;https://www.codesoom.com/courses/soomtudy&quot;&gt;코드숨 스터디&lt;/a&gt; 모집을 보고 바로 참가했다.&lt;/p&gt;
&lt;p&gt;HTTP/2가 개선되긴 했지만 HTTP/1.1과 비교해서 &lt;strong&gt;명확하게 어떤 부분이 개선되었는지&lt;/strong&gt; , 스트림에 대해서는 들어봤는데 &lt;strong&gt;명확하게 어떤 방식으로 통신이 이루어지는지&lt;/strong&gt; 알지 못했다.&lt;br&gt;
이 책은 HTTP/2 뿐만이 아니라 HTTP/1.0, HTTP/1.1, HTTPS, TCP, QUIC에 대해서도 설명해주어서 가려웠던 부분을 시원하게 다 긁어준다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTTP/1.1까지의 문제점과 한계&lt;/li&gt;
&lt;li&gt;HTTP/2 개념과 프레임, 단일 연결 다중 요청&lt;/li&gt;
&lt;li&gt;HTTP/2 푸시, 최적화&lt;/li&gt;
&lt;li&gt;HTTP/2 스트림, 흐름 제어, 스트림 우선순위&lt;/li&gt;
&lt;li&gt;QUIC과 HTTP/3를 설명하기 위한 TCP의 비효율성과 UDP를 선택한 이유&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이번 책으로 인해서 HTTP/2가 &lt;strong&gt;어떤 문제를 어떻게 해결하는지, 한계는 무엇인지를 이해할 수 있었다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스터디에서 &lt;a href=&quot;https://github.com/CodeSoom/http2-in-action&quot;&gt;매 챕터마다 문제를 제공&lt;/a&gt;해줘서 &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%8A%A4%ED%84%B0%EB%94%94/HTTP2%EC%9D%B8%EC%95%A1%EC%85%98.md&quot;&gt;문제를 풀면서 학습&lt;/a&gt;할 수 있었어서 좋았다.&lt;br&gt;
네트워크 내용이 너무 방대해서 항상 네트워크 책을 읽을 때 마다 힘들었는데, 이번 책은 HTTP 프로토콜에 대해 집중적으로 설명해줘서 다른 책에 비해 덜 힘들었던 것 같다.&lt;/p&gt;
&lt;p&gt;요즘 많은 회사에서 HTTP/2를 적용한 통신 방법을 채택하고 있고 전반적인 네트워크 변화의 흐름에 대해 이해하기 위해서는 정말 좋은 책이라고 생각한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.codenary.co.kr/techstack/detail/grpc&quot;&gt;gRPC를 사용하는 기업들&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.linecorp.com/ko/blog/reactive-streams-with-armeria-1&quot;&gt;Armeria로 Reactive Streams와 놀자!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.banksalad.com/tech/production-ready-grpc-in-golang/&quot;&gt;뱅크샐러드 프로덕션 환경에서 사용하는 golang과 gRPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://story.cookapps.com/articles/187&quot;&gt;쿡앱스는 gRPC를 어떻게 사용하나요?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;real-my-sql-1권&quot; style=&quot;position:relative;&quot;&gt;Real My SQL 1권&lt;a href=&quot;#real-my-sql-1%EA%B6%8C&quot; aria-label=&quot;real my sql 1권 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;MySQL의 아키텍처부터 메모리 영역, 인덱스, 옵티마이저와 힌트, 실행 계획까지 넓고 깊은 내용을 학습할 수 있었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;아키텍처&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 엔진, 스토리지 엔진, 핸들러 API&lt;/li&gt;
&lt;li&gt;포그라운드, 백그라운드 스레드&lt;/li&gt;
&lt;li&gt;글로벌 메모리 영역 (InnoDB 버퍼풀, InnoDB 어댑티브 해시 인덱스, InnoDB 리두 로그 버퍼 등)&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;strong&gt;InnoDB 스토리지 엔진 아키텍처&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;언두 로그를 활용한 MVCC와 잠금 없는 일관된 읽기&lt;/li&gt;
&lt;li&gt;버퍼풀 (데이터 페이지를 관리하는 LRU 리스트 구조, 클린 페이지, 더티 페이지)&lt;/li&gt;
&lt;li&gt;리두 로그, 언두 로그, 체인지 버퍼, 어댑티브 해시 인덱스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인덱스&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;인덱스를 통한 데이터 읽는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;옵티마이저와 힌트&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;데이터 스캔 방식&lt;/li&gt;
&lt;li&gt;정렬과 조인 처리 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실행 계획과 통계정보&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;실행 계획 분석 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;많은 양의 이론적인 내용들을 설명해서 지루한 부분도 있었지만 겉핥기 식으로 알고 있었던 인덱스와 실행 계획에 대해 학습할 수 있어서 좋앗다.&lt;br&gt;
한 번 읽고 모든 내용을 기억할 수는 없으니 일회독으로 MySQL에 대한 윤곽을 어느정도 잡고 나중에 필요할 때 찾아봐야할 것 같다.
MySQL을 사용하는 개발자라면 필수로 읽어야 할 책이라고 생각한다.&lt;/p&gt;
&lt;h2 id=&quot;타입으로-견고하게-다형성으로-유연하게&quot; style=&quot;position:relative;&quot;&gt;타입으로 견고하게 다형성으로 유연하게&lt;a href=&quot;#%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EA%B2%AC%EA%B3%A0%ED%95%98%EA%B2%8C-%EB%8B%A4%ED%98%95%EC%84%B1%EC%9C%BC%EB%A1%9C-%EC%9C%A0%EC%97%B0%ED%95%98%EA%B2%8C&quot; aria-label=&quot;타입으로 견고하게 다형성으로 유연하게 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;정적 타입 언어는 타입 검사기를 통하여 생산성을 높이기도 좋고, 불필요한 실행 중 검사를 없앨 수 있어서 프로그램이 좋은 성능을 내는데 유리하고 큰 프로그램을 만들 때 타입 검사기의 위력은 강력하다.&lt;br&gt;
타입 검사기가 거부하는 코드를 줄이고 정적 언어를 잘 사용하기 위해 여러 경우에 대한 다형성,가변성에 대한 이해와 타입 검사기와 친해지도록 노력해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 책을 통해 아래의 내용을 학습할 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;여러 종류의 다형성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;(JVM 언어에서는 지원하지 않는) &lt;strong&gt;다른 언어의 기능&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;최대, 최소 타입&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제네릭 가변성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PECS를 지켜야하는 이유&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;타입 클래스, 카인드&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;제일 와닿았던 부분은 제네릭 가변성에 대한 이해가 없을 때 이게 왜 안되는지 몰랐던 부분들을 깨닫게 도와줬다.&lt;br&gt;
&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/effective-java/item31_%ED%95%9C%EC%A0%95%EC%A0%81%20%EC%99%80%EC%9D%BC%EB%93%9C%EC%B9%B4%EB%93%9C%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%B4%20API%20%EC%9C%A0%EC%97%B0%EC%84%B1%EC%9D%84%20%EB%86%92%EC%9D%B4%EB%9D%BC.md#pecs--producer-extends-consumer-super&quot;&gt;PECS : producer-extends, consumer-super&lt;/a&gt;를 외워서 사용했는데 왜 저렇게 제한해야 하는지 더 깊은 이해를 할 수 있도록 도와주었다.&lt;/p&gt;
&lt;p&gt;이 책을 읽으면서 안다고 착각했던 부분들이 많았다는 것을 깨달았다. 정적 타입 언어에 대한 이해를 한층 더 높여준 책이다.&lt;br&gt;
개발하면서 타입 검사기가 거부하는 상황이 발생하면 IDE의 도움을 받아 어영부영 넘어간 경험 또는 제네릭이나 제네릭을 통한 타입 매개변수 제한을 사용할 때 타입 검사기가 왜 거부하는지 이해하지 못 했던 경험이 있다면 이 책은 큰 도움이 될 것이다.&lt;/p&gt;
&lt;h2 id=&quot;내-코드가-그렇게-이상한가요&quot; style=&quot;position:relative;&quot;&gt;내 코드가 그렇게 이상한가요?&lt;a href=&quot;#%EB%82%B4-%EC%BD%94%EB%93%9C%EA%B0%80-%EA%B7%B8%EB%A0%87%EA%B2%8C-%EC%9D%B4%EC%83%81%ED%95%9C%EA%B0%80%EC%9A%94&quot; aria-label=&quot;내 코드가 그렇게 이상한가요 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Ffupfin.geek%2Fposts%2Fpfbid09WddbAYkCwAaB3xKi4tazk2WzvuFhzqGvUhqYMCDdxxGJ7stEcm8nLMJbMdGpudZl&amp;amp;show_text=true&amp;amp;width=500&quot; style=&quot;border:none;overflow:hidden; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot; allow=&quot;autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;p&gt;(박성철님의 이 글을 보고 구매해서 읽어보았다.)&lt;br&gt;
대부분의 개발자들은 코드를 작성하는 시간보다 코드를 읽고 이해하려 노력하는 시간이 더 많을 것이다.&lt;br&gt;
그 이해를 바탕으로 코드를 수정하거나 기능을 추가하고 테스트를 할 것이다. 하지만 이 행위는 말처럼 쉽지 않다.&lt;/p&gt;
&lt;p&gt;이해하기 힘든 코드, 의도를 전혀 알 수 없는 코드들을 맞닥뜨린 경험 또는 수정이나 확장했을 때 어디까지 영향을 끼치는지 확인하다가 하루가 다 가버린 경험, 냄새나는 코드인 줄은 알지만 어떻게 수정해야 할지 몰라서 결국 냄새나는 코드를 또 작성하며 죄책감을 느낀 경험이 있다면 이 책이 재밌게 읽힐 것이다.&lt;br&gt;
이 책에서는 이와같이 개발 능력을 떨어뜨리고 소프트웨어의 성장을 방해하는 설계, 또는 구현상의 문제를 &lt;code class=&quot;language-text&quot;&gt;악마&lt;/code&gt;에 비유하며 악마를 인식할 수 있는 시각과 악마가 코드에 스며들지 못하도록 예방하고 무찌를 수 있도록 도와준다.&lt;/p&gt;
&lt;p&gt;많은 책에서 &lt;strong&gt;응집도와 결합도&lt;/strong&gt; 이야기가 자주 나오는데 항상 뉘앙스로 이해 했었다.&lt;br&gt;
이 책에서는 명확하게 &lt;strong&gt;데이터와 로직 등이 분산되어 있는 것을 응집도가 낮은 구조&lt;/strong&gt; 라고 정의한다.&lt;br&gt;
응집도가 낮고 결합도가 높을 때 발생하는 &lt;code class=&quot;language-text&quot;&gt;코드 중복&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;수정 누락&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;가독성 저하&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;초기화되지 않은 객체&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;잘못된 값 할당&lt;/code&gt; 들을 설명해준다.&lt;br&gt;
그렇다고 코드 중복을 해결하기 위해 섣부른 일반화를 해서도 안된다. &lt;strong&gt;코드 중복을 제거하는것이 핵심이 아니라 비즈니스적인 책무를 일반화 해도 괜찮은지를 먼저 고민해야한다.&lt;/strong&gt;&lt;br&gt;
즉, &lt;strong&gt;같은 로직, 비슷한 로직이라도 개념이 다르면 중복을 허용해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;성숙한 클래스를 설계하기 위해 클래스의 내부 필드와 그 필드를 가공하는 로직이 분산되지 않도록 노력해야하며 &lt;strong&gt;인스턴스 변수가 잘못된 상태에 빠지지 않게 하기 위한 구조를 만드는 것임을 명심해야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;값 객체&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;완전 생성자&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;불변&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;부수 효과 방지&lt;/code&gt; 는 기본적으로 유의해야 한다. 그렇다고 응집도를 높이기 위해 로직을 한곳에 모아 강한 결합 구조를 만들지 않도록 신경써야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;..Manager&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Product&lt;/code&gt;와 같은 너무 범용적인 이름을 사용하여 많은 책임과 관심사를 가지도록 하면 안된다. &lt;strong&gt;목적 중심 이름 설계&lt;/strong&gt;를 명심해라.&lt;/li&gt;
&lt;li&gt;관심사가 다르고 관계없는 책무를 가진 메서드는 동사 + 목적어 형태가 되는 경향이 있으므로 &lt;strong&gt;메서드 이름은 &lt;code class=&quot;language-text&quot;&gt;동사&lt;/code&gt; 하나로 구성되게 해라.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모델&lt;/strong&gt;은 &lt;code class=&quot;language-text&quot;&gt;특정 목정 달성을 위해서, 최소한으로 필요한 요소를 갖춘 것&lt;/code&gt;이다. 그렇기에 &lt;strong&gt;목적 달성의 수단&lt;/strong&gt; 이며 목적별로 모델링해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모델 != 클래스&lt;/strong&gt; 이다. 일반적으로 모델 하나는 여러 개의 클래스로 구현된다. 클래스 설계와 구현에서 무언가를 깨닫는다면, 이를 모델에 꾸준하게 피드백해야 한다. 그렇지않으면 서로 괴리가 생긴다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;일급 컬렉션&lt;/code&gt;에 대한 내용을 &lt;a href=&quot;https://jdalma.github.io/2023y/kotlinCleanCode/&quot;&gt;넥스트스텝 코틀린 교육&lt;/a&gt;에서 접했을땐 코틀린에서 지원하는 컬렉션 공통 함수들을 사용하기 불편하다고 생각해서 와닿지 않았었는데 이 책을 통해 잘못이해하고 있었다는 것을 느꼈다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;인상 깊은 내용들은 이 정도로 정리할 수 있겠다. 중후반부 까지는 좋은 코드를 작성하는 방법, 설계에 대한 강조, 모델의 개념들에 대해 설명했다.&lt;br&gt;
(&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%97%98%EB%A0%88%EA%B0%95%ED%8A%B8-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8&quot;&gt;엘레강트 오브젝트&lt;/a&gt;가 자주 떠올랐다.)&lt;/p&gt;
&lt;p&gt;대체로 이렇게 코드,설계 레벨에서 마무리짓는데 이 책은 후반부에 &lt;strong&gt;레거시 코드와 엔지니어의 성장 가능성의 관계&lt;/strong&gt;, &lt;strong&gt;설계를 방해하는 환경&lt;/strong&gt;, &lt;strong&gt;리뷰의 의의&lt;/strong&gt; 들도 설명하면서 자신의 업무 환경을 개선하기위한 용기와 방법을 알려준다.&lt;br&gt;
팀에 새로운 인원이 투입된다면 온보딩 기간에 읽어야 할 책으로 선정해도 무방할 정도로 좋은 책이였다.&lt;/p&gt;
&lt;p&gt;이 책 덕분에 &lt;a href=&quot;https://m.yes24.com/Goods/Detail/64586851&quot;&gt;레거시 코드 활용 전략&lt;/a&gt;에 흥미가 생겨 읽어볼 예정이다.&lt;/p&gt;
&lt;h2 id=&quot;켄트-벡의-tidy-first&quot; style=&quot;position:relative;&quot;&gt;켄트 벡의 Tidy First?&lt;a href=&quot;#%EC%BC%84%ED%8A%B8-%EB%B2%A1%EC%9D%98-tidy-first&quot; aria-label=&quot;켄트 벡의 tidy first permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 책은 설계와 코드 정리를 잘 하기 위한 방법을 배우기 위해 읽기도 좋지만 &lt;strong&gt;코드 정리와 설계를 진행하면서 발생하는 비용에 대한 생각&lt;/strong&gt; 을 깨닫기 위해 읽기 좋은 책이라고 생각한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;이미 잦은 코드 정리가 진행되고 있어 변경사항 적응하는 것에 대한 팀원들의 불만은 없는지?&lt;/li&gt;
&lt;li&gt;코드 정리가 결과적으로 도움이 되는지?&lt;/li&gt;
&lt;li&gt;해당 변경사항이 코드 정리인지, 동작 변경인지? 코드 정리를 먼저할지? 동작 변경을 먼저할지?&lt;/li&gt;
&lt;li&gt;코드 정리, 설계 대상이 얼마나 자주 변경되는지? 얼마나 자주 읽히는지? 얼마나 읽기 어려운지?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같이 코드 정리, 설계를 진행할 때 고려해야할 것들의 근본적인 원인은 코드 정리, 설계를 진행하는 것은 &lt;strong&gt;비용이 들기 때문이다.&lt;/strong&gt;&lt;br&gt;
일반적으로 개발자들이 근무하는 회사는 영리기업이고, 영리기업은 이윤을 목적으로 하기 때문에 지금 당장 이익을 얻기는 힘들면서 미래에 투자하는 코드 정리와 설계같은 업무는 비용 지출에 대한 이익과 손실을 고려하여 진행해야 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;오늘의 1달러가 내일의 1달러보다 더 가치가 있기 때문에 버는 것은 빨리하고, 쓰는 것은 가능한 뒤로 미룹니다.&lt;br&gt;
혼란스러운 상황에서는 어떤 물건에 대한 옵션이 물건 자체보다 낫기 때문에 불확실성에 맞서는 옵션을 만듭니다.&lt;br&gt;
&lt;strong&gt;즉, 지금 돈을 벌려면 미래의 옵션이 줄어들 수 있지만, 지금 돈을 벌지 못하면 미래의 옵션을 행사할 여력이 없을 수도 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;금융 업계의 옵션을 소프트웨어 설계 결정에 투영한다. 옵션은 &apos;선택&apos;의 의미를 내포하는데,&lt;br&gt;
소프트웨어 설계 문제에서 옵션을 보유하는 일은 당장 모든 비용을 &apos;동작 변경&apos;에 모두 투자하는 대신에 &apos;선택 가능성&apos; 혹은 &apos;선택의 기회&apos;를 유지하는 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이익이 미래에 언제 발생하는지? 그리고 그 이익은 얼마나 확실한지?를 스스로 질문해야 한다.&lt;br&gt;
결국 &lt;strong&gt;지금 돈 벌기&lt;/strong&gt;와 &lt;strong&gt;지금 돈 쓰기&lt;/strong&gt;를 고민하는 것이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;지금 돈 벌기&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;지금 돈 쓰기&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정의&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;현금 흐름 할인, 발생하는 비용의 시간 가치&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;옵션, 선택 가능성을 늘리기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;흐름 할인지금 돈을 벌 수 있게 기능 변경 및 추가&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;미래에 기능 번경을 더 쉽게 할 수 있게 코드 정리, 리팩토링, 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;지금 돈을 못 벌면 미래는 없다.&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;변경 비용이 높아지면 미래가 없다.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;옵션을 만드는 것과 동작을 변경하는 것의 균형을 맞추는데 집중해야 한다.&lt;br&gt;
&lt;strong&gt;가치에 대한 예측이 불확실할수록 바로 구현하는 것보다 옵션이 지닌 가치가 더 커진다. 바로 구현하는 것보다 변화를 포용하면 창출한 가치를 극대화할 수 있지만, 그 상황은 통상적으로 소프트웨어 개발이 극적으로 실패하는 지점이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;비용(코드 정리) + 비용(코드 정리 후 동작 변경) &amp;lt; 비용(바로 동작 변경)&lt;/code&gt; 이라면 코드 정리를 진행해도 좋다. 그 반대라면 고민해야한다..&lt;br&gt;
비용에 대한 판단력을 기르기 위한 연습을 꾸준히 한다면 어떻게 설계해야 하고 언제 설계하지 말아야하는지 직감적으로 느낄 수 있을것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;비용&lt;/strong&gt; : 코드를 정리하면 비용이 줄까요? 아니면 나중에 하는 편이 나을까요? 줄일 가능성이 있을까요?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;수익&lt;/strong&gt; : 코드를 정리하면 수익이 더 커질까요? 혹은 더 빨리 발생하거나 커질 가능성이 있나요?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결합도&lt;/strong&gt; : 코드를 정리하면 변경에 필요한 요소의 수가 줄어드나요?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;응집도&lt;/strong&gt; : 코드를 정리하면 변경을 더 작고 좁은 범위로 집중시켜 더 적은 수의 요소만 다룰 수 있을까요?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/@bettercoder/&quot;&gt;옮긴이의 글들&lt;/a&gt;도 참고하자.&lt;/p&gt;
&lt;h2 id=&quot;켄트-백의-구현-패턴&quot; style=&quot;position:relative;&quot;&gt;켄트 백의 구현 패턴&lt;a href=&quot;#%EC%BC%84%ED%8A%B8-%EB%B0%B1%EC%9D%98-%EA%B5%AC%ED%98%84-%ED%8C%A8%ED%84%B4&quot; aria-label=&quot;켄트 백의 구현 패턴 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;클린코드와 리팩토링에 관심을 가지면서 접하게 된 책이다. 아마 이때까지 읽은 책들 중에 가장 많은 인덱스 스티커를 사용한 것 같다.&lt;br&gt;
켄트 백은 서문에서부터 &lt;code class=&quot;language-text&quot;&gt;&quot;프로그램을 짤 때는 자신과 컴퓨터뿐만 아니라, 다른 사람들을 생각해야 한다!&quot;&lt;/code&gt;고 강조한다.&lt;br&gt;
기능을 개발하는 비용이 1이라면 그 소프트웨어가 폐기될 때까지 유지보수하고 기능을 추가하기 위한 비용은 1보다 크다는 것을 다 알고 있을 것이다.&lt;br&gt;
그렇기에 정확하게 작동하도록 코드를 작성하는 것은 당연하지만 다른 사람들이 읽기 편하도록 작성하는 것도 정말 중요하다.&lt;/p&gt;
&lt;p&gt;이 책은 개발하면서 만나는 많은 선택들을 &lt;code class=&quot;language-text&quot;&gt;&quot;내 생각을 다른 사람에게 어떻게 전달할까?&quot;&lt;/code&gt; 고민에 차선책을 선택할 수 있도록 도와준다.&lt;br&gt;
이런 고민과 결정은 &lt;strong&gt;책임감&lt;/strong&gt;과 &lt;strong&gt;장인 정신&lt;/strong&gt;을 지키기 위한 자신의 양심과 자랑스럽지 않은 일을 하지 않으려는 마음에 의존한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;좋은 코드가 상업적 성공에 필수적인 부분은 아니다. 조잡한 코드로 돈을 많이 버는 사례가 있다고 할지라도, 나는 여전히 코드 품질이 매우 중요하다고 믿는다.&quot;&lt;br&gt;
&quot;기능을 개발하고 출시하며, 기회와 경쟁 상황에 따라 개발 방향을 바꿀 수 있으며 위기 속에서도 직원들의 사기를 높일 수 있는 회사는 조잡하고 버그가 있는 코드를 작성하는 회사에 비해 성공할 확률이 높다.&quot;&lt;br&gt;
&quot;설사 좋은 코딩이 장기적으로 경제적 이득을 가져오지 못한다고 하더라도 &lt;strong&gt;나는 여전히 내가 작성할 수 있는 최고의 코드를 작성할 것이다.&lt;/strong&gt;&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;많은 패턴을 학습하여도 개발하면서 모든 상황을 커버할 수 없고 정형화된 패턴도 적용할 수 없는 경우가 있다.&lt;br&gt;
이때는 학습한 이론과 패턴이 아닌 결정 사항에 영향을 끼치는 &lt;strong&gt;가치&lt;/strong&gt;와 &lt;strong&gt;원칙&lt;/strong&gt; 이 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가치는 코드의 복잡성을 낮추고 가독성을 중시하며, 유연성있는 코드를 작성하는 클린 코드의 &lt;strong&gt;가치&lt;/strong&gt;이다.&lt;/li&gt;
&lt;li&gt;원칙은 가치처럼 언제나 적용되는 것은 아니지만 때로는 직접 적용하기 어려운 가치와 적용법은 명확하지만 조금은 지엽적인 패턴 사이의 가교 역할을 하는 것이 &lt;strong&gt;원칙&lt;/strong&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;패턴은 &lt;code class=&quot;language-text&quot;&gt;&quot;지금 당장 무엇을 해야할지&quot;&lt;/code&gt; 알려주며, 가치는 &lt;code class=&quot;language-text&quot;&gt;&quot;패턴을 사용해야 하는 동기&quot;&lt;/code&gt;를 알려주고, 원칙은 &lt;code class=&quot;language-text&quot;&gt;&quot;동기를 행동으로 어떻게 바꿔줄지&quot;&lt;/code&gt; 알려준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;절대적인 최고의 개발 스타일이란 존재하지 않는다. 가치, 원칙, 패턴은 실무 경험과 다른 프로그래머와의 대화를 통해 스스로 만들어 가야한다.&lt;br&gt;
일하는 환경에 따라 다르겠지만 업무에 치이다보면 좋은 코드와 나쁜 코드 작성에 대한 저울질을 할 때가 많다.&lt;br&gt;
자신있게 항상 좋은 코드를 작성하기 위해 노력했다라고 말하기 힘든 경험들이 분명있을 것이다. 그리고 함께 일하는 동료들이 클린 코드에 대한 공감이 없어 괴리감을 느낄 수도 있다.&lt;/p&gt;
&lt;p&gt;이 책은 그 괴리감을 메꿔주며 스스로의 소프트웨어 장인 정신을 확립해나가는데 큰 도움을 준다.&lt;/p&gt;
&lt;h2 id=&quot;uml-실전에서는-이것만-쓴다&quot; style=&quot;position:relative;&quot;&gt;UML 실전에서는 이것만 쓴다&lt;a href=&quot;#uml-%EC%8B%A4%EC%A0%84%EC%97%90%EC%84%9C%EB%8A%94-%EC%9D%B4%EA%B2%83%EB%A7%8C-%EC%93%B4%EB%8B%A4&quot; aria-label=&quot;uml 실전에서는 이것만 쓴다 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;책의 제목을 보면 UML을 사용해야 한다고 강조할 것 같지만 실제로는 &lt;code class=&quot;language-text&quot;&gt;UML은 그저 표기법이며 목표를 이루기 위한 도구&lt;/code&gt; 임을 강조한다.&lt;br&gt;
UML 다이어그램을 그리는 일도 비용이다. &lt;strong&gt;시험해 볼 구체적인 것이 있고, 그것을 코드로 시험해 보는 것보다 UML로 시험해 보는 쪽이 비용이 덜 들때 사용해야 한다.&lt;/strong&gt;&lt;br&gt;
항상 산출물로 작성해야 한다는 강제성이 포함되거나 형식적인 문서라고 생각해서는 안된다. UML 다이어그램의 수명 주기는 짧게 가져가도록 버리는 습관을 길러야 한다.&lt;br&gt;
잘 그려야한다는 생각을 버려라. 다이어그램을 잘 그리는 것, 시퀀스 번호에 점을 제대로 찍는 것은 중요하지 않다.&lt;br&gt;
&lt;strong&gt;앞에 있는 사람들이 지금 논의하는 내용을 다 이해하고, 회의를 빨리 끝내 사람들이 코드를 빨리 작성하기 시작하도록 돕는 것이 핵심이다.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;클래스 다이어그램&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;객체 다이어그램&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시퀀스 다이어그램&lt;/strong&gt; : 객체 사이의 연결을 드러내기 위해 사용하지, 알고리즘의 세부사항을 자세하게 보여주기 위해 사용해서는 안된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 다이어그램&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유스케이스&lt;/strong&gt; : 사용자의 눈에 보이지 않는 동작을 전혀 기술하지 않고 시스템 안에 숨겨진 메커니즘도 다루지 않는다. 오직 사용자가 직접 볼 수 있는 것을 적어 놓을 뿐이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;UML 다이어그램을 이용해 시스템 설계에 대한 놓친 부분이나 보완할 점을 빨리 찾기 위함도 있다.&lt;br&gt;
많은 책임을 가진 클래스를 발견하거나, 수정에 의한 여파가 여러 클래스에 전달되는 것을 발견하는 것이 대표적이다.&lt;/p&gt;
&lt;p&gt;객체지향 개발의 &lt;code class=&quot;language-text&quot;&gt;SOLID&lt;/code&gt; 설계 원칙을 명심하고 나쁜 설계의 냄새가 스며들지 않도록 신경써야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;하나를 바꿀 때 반드시 다른 것도 바꿔야 하는 &lt;strong&gt;경직성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;한 부분을 변경하면 다른 부분에 문제가 생기는 &lt;strong&gt;부서지기 쉬움&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;작은 단위로 분해해서 다른 시스템에 재사용하기 힘든 &lt;strong&gt;부동성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;YAGNI 원칙을 위배한 &lt;strong&gt;쓸데없이 복잡함&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필요없는 반복&lt;/strong&gt; 과 코드를 작성한 의도가 &lt;strong&gt;불투명함&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;자주 변경되는 콘크리트 클래스에 의존하지 마라.&lt;br&gt;
만약 어떤 클래스에서 상속받아야 한다면, 기반 클래스를 추상 클래스로 만들어라.&lt;br&gt;
어떤 클래스의 참조를 가져야 한다면, 참조 대상이 되는 클래스를 추상 클래스로 만들어라.&lt;br&gt;
만약 어떤 함수를 호출해야 한다면 호출되는 함수를 추상 함수로 만들어라.&lt;br&gt;
&lt;strong&gt;구체적인 것보다는 이런 추상적인 것에 의존하는 편이 좋으며 추상적인 것은 구체적인 것에 의존하지 말아야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 원칙들을 적용하는 가장 좋은 방법은 능동적으로 적극 적용하는 것이 아니라, &lt;strong&gt;문제가 생겼을 때 그에 대한 &lt;code class=&quot;language-text&quot;&gt;반응으로써 작용하는 것&lt;/code&gt; 이다.&lt;/strong&gt;&lt;br&gt;
코드의 구조적인 문제를 처음 발견했거나, 어떤 모듈이 다른 모듈에서 생긴 변화에 영향을 받음을 처음 깨달았을 때 그때 &lt;code class=&quot;language-text&quot;&gt;비로소&lt;/code&gt; 원칙 가운데 하나 또는 여러 개를 써서 이 문제를 해결할 수 있는지 알아보아야 한다.&lt;/p&gt;
&lt;p&gt;이 책에서 UML 다이어그램의 내용은 절반도 되지 않는 것 같다.&lt;br&gt;
UML 다이어그램은 단지 도구에 불과하다는 것을 명심하고 본질에 집중하라는 느낌이다.&lt;br&gt;
좋은 품질의 소프트웨어를 작성하기 위한 객체지향 설계, 짝 프로그래밍, 테스트 코드(분로 단위 테스팅 패턴 등), 리팩토링, 개발자의 꾸준한 학습, 휴리스틱, 개발 일정 등에 대해 설명한다.&lt;br&gt;
가장 중요한 핵심은 우리가 좋은 코드를 작성하기 위한 이유가 &lt;strong&gt;사람을 위한 것이라는 점을 명심하자.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;휴리스틱&lt;/strong&gt;&lt;br&gt;
우리말로 번역하면 경험적 방법이며, 어떤 문제가 있는데 그 문제를 푸는 방법이 아직 없거나 현실적으로 불가능할 때, 문제를 풀기 위한 정보가 완전하지 않을 때,&lt;br&gt;
또는 확립된 절차에 따라 답을 구할 수 있을 정도로 문제가 명확하게 정의되지 않았을 때 경험이나 직관을 사용하거나, 노력을 기울여 시행착오를 거치며 &lt;strong&gt;충분히 효율적인 해답이나 지식을 알게 되는 과정이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;엘라스틱서치-바이블&quot; style=&quot;position:relative;&quot;&gt;엘라스틱서치 바이블&lt;a href=&quot;#%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1%EC%84%9C%EC%B9%98-%EB%B0%94%EC%9D%B4%EB%B8%94&quot; aria-label=&quot;엘라스틱서치 바이블 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;실무에서 엘라스틱서치와 맞닿아 있는 업무를 할 때 모르고 넘어가는 것이 많아 불편하고 찝찝한 느낌이 있었다.&lt;br&gt;
그리고 데이터 팀의 공유 사항을 들으면 이해 못하는 것이 많았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;운영중인 여러 개의 검색 클러스터, ES 내부 아키텍처에 대한 이해가 없어 검색 이슈가 발생했을 때의 상황과 해결 방법을 이해할 수 없음&lt;/li&gt;
&lt;li&gt;ES QueryDSL 작성할 때 마다 구글링을 하는 불편함&lt;/li&gt;
&lt;li&gt;현재 운영중인 ES 환경의 문제점과 개선점에 대해 고민하거나 공감할 수 없음&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같은 이유로 엘라스틱서치를 공부해야겠다는 생각이 들었고 사내 스터디를 만들어서 읽어보았다.&lt;br&gt;
ES에 대한 지식 하나도 없었지만 읽기 편했고 대부분의 개발책은 외국에서 쓰여서 번역본으로 읽게 되는데 이 책은 내국인이 써서 읽기 편했다.&lt;/p&gt;
&lt;p&gt;읽으면서 내용들을 &lt;a href=&quot;https://github.com/jdalma/footprints/tree/main/%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1%EC%84%9C%EC%B9%98&quot;&gt;정리&lt;/a&gt;해 보았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;엘라스틱서치 클러스터 수준에서 루씬 세그먼트와 루씬 commit, flush 그리고 translog 수준까지의 &lt;strong&gt;개괄적인 구조&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;복잡한 수준의 쿼리를 작성하면서 설명하진 않지만 기본적인 여러 종류의 &lt;strong&gt;ES Query DSL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인덱스 설정과 필드 타입&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;문서를 검색하거나 조작하는 &lt;strong&gt;여러 문서 API&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;저수준, 고수준 REST 클라이언트와 최신 자바 클라이언트 사용 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 내용들을 재밌게 읽었다.&lt;br&gt;
하지만 아래와 같이 실제 운영하는 인원에게 도움이 되는 내용들은 흥미가 없었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;엘라스틱서치, 키바나, 클라이언트 등을 TLS로 구성하는 방법&lt;/li&gt;
&lt;li&gt;인덱스, 샤드 운영 전략과 스냅샷 복구 방법&lt;/li&gt;
&lt;li&gt;코드 레벨의 동작과 커스텀 플러그인&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;전반적인 내용으로 봤을 때, 엘라스틱서치 클라이언트 입장이거나 엘라스틱서치의 관계자인 개발자보다는 클러스터를 직접 운영하고 튜닝하거나 깊은 내용의 이해를 필요로 하는 개발자한테 도움이 많이 될 것으로 보인다.&lt;br&gt;
그래도 학습하면서 현재 실무 운영 환경과 책의 내용을 비교하면서 읽을 수 있었고 같은 회사의 팀원들과 읽어서 더욱 재밌게 읽었던 것 같다.&lt;br&gt;
이 책으로 진행한 스터디 덕분에 실무 환경에서 모르고 넘어갔던 부분들을 배울 수 있었고 백엔드만의 영역이 아니라 데이터 팀의 영역에 대한 시야도 넓어진 느낌이다.&lt;/p&gt;
&lt;h2 id=&quot;실용주의-프로그래머&quot; style=&quot;position:relative;&quot;&gt;실용주의 프로그래머&lt;a href=&quot;#%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8&quot; aria-label=&quot;실용주의 프로그래머 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;12월 8일부터 01월 18일까지&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;처음엔 3,4주면 다 읽을 줄 알았는데 6주가 걸렸다. 너무 기대해서 그런지 지루한 느낌이 있었다.&lt;br&gt;
기술 책이라기 보다는 &lt;strong&gt;개발자라는 직업의 장인 정신에 대한 이야기&lt;/strong&gt; 를 한다고 느꼈다.&lt;br&gt;
누구나 문제에 접근하는 태도와 방식, 철학에 차이가 있는 &lt;strong&gt;실용주의 프로그래머&lt;/strong&gt; 가 될 수 있다고 설득한다.&lt;/p&gt;
&lt;ol&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;li&gt;누군가에 문제를 설명해봐라. 고무 오리라도 괜찮다.&lt;/li&gt;
&lt;li&gt;여러분은 완벽한 소프트웨어를 만들 수 없다.&lt;/li&gt;
&lt;li&gt;이 책의 저자는 문제를 찾고 원인을 밝히기 위해서는 사고가 난 지점에서 &quot;일찍 멈추기&quot;를 장려한다.&lt;/li&gt;
&lt;li&gt;has-a가 is-a 보다 낫다.&lt;/li&gt;
&lt;li&gt;의도적으로 프로그래밍하라. 우연에 기대지 말고 가정에 의존하지 마라. 신뢰할 수 있는것에만 기대라.&lt;/li&gt;
&lt;li&gt;리팩토링가 기능 추가를 동시에 하지 말라.&lt;/li&gt;
&lt;li&gt;요구 사항 문서는 의뢰인을 위한 것이 아니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;책을 읽으면서 표시해놓은 인덱스들을 작성해봤다. 위의 내용만 보아도 책에서 설명하는 범위는 굉장히 넓은 것을 알 수 있다.&lt;br&gt;
개인적으로는 &lt;code class=&quot;language-text&quot;&gt;철학, 편집도구, 클린코드, 테스트, 태도, 문서화, 협업 등&lt;/code&gt; 내용이 너무 포괄적이라서 흥미를 느끼지 못한 것 같다.&lt;br&gt;
순서대로 읽기 보다는 목차에서 관심있는 주제들을 골라서 읽는 것을 추천한다.&lt;/p&gt;
&lt;h2 id=&quot;개발자의-글쓰기&quot; style=&quot;position:relative;&quot;&gt;개발자의 글쓰기&lt;a href=&quot;#%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EA%B8%80%EC%93%B0%EA%B8%B0&quot; aria-label=&quot;개발자의 글쓰기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;책이 얇고 글씨도 커서 금방 읽은 것 같다. 글을 쓸때 꼭 포함시켜야지 하는 중요한 내용들을 위주로 쓰다보면 문맥 연결이 잘 되지않을 때가 있어 글쓰기 책을 한번 읽어보고 싶어 이 책을 읽어보았다.&lt;/p&gt;
&lt;h4&gt;서술식, 개조식, 도식의 차이&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;서술식&lt;/strong&gt; : &lt;code class=&quot;language-text&quot;&gt;~다.&lt;/code&gt;로 끝나는 완전한 문장으로 구성된 글을 말하며, 무엇을 설명하거나 논증할 때 주로 사용하는 방식이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개조식&lt;/strong&gt; : 어떤 사항을 나열할 때 사용하며 종결 어미 대신 명사나 용언의 명사형(&lt;code class=&quot;language-text&quot;&gt;~했음&lt;/code&gt;)으로 끝내는 것이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도식&lt;/strong&gt; : 사물의 구조나 관계, 상태를 그림이나 서식으로 보여주는 것이다.&lt;/li&gt;
&lt;li&gt;줄거리가 있는 설명이나 이야기라면 &lt;strong&gt;서술식&lt;/strong&gt; , 여러 가지 종류의 항목과 내용이 반복되거나 서술식에서 강조가 필요한 내용이라면 &lt;strong&gt;개조식&lt;/strong&gt; , 각 항목이나 사항의 관계를 명확히 규정하고 싶다면 &lt;strong&gt;도식&lt;/strong&gt; 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;내가 작성하려하는 글의 특징을 잘 이해하여 선택하여야 한다.&lt;/p&gt;
&lt;h4&gt;네이밍 컨벤션&lt;/h4&gt;
&lt;p&gt;이 네이밍 컨벤션은 항상 같은 조직 구성원들의 노력이 동반되어야 한다고 생각한다. 사내에는 네이밍 컨벤션이 따로 없지만 이 책의 내용이 좋은 가이드가 되어준 것 같다.&lt;br&gt;
&lt;strong&gt;반대되는 단어를 선택할 때 일관성과 개연성이 있어야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이상, 초과, 이하, 미만&lt;/strong&gt; : and over, over, or under, under&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;중단에 대한 단어 선택&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;잠시 중단하고 재시작 가능할 때는 &lt;code class=&quot;language-text&quot;&gt;stop&lt;/code&gt;, 완전히 중단되어 재시작 가능성이 없을 때 &lt;code class=&quot;language-text&quot;&gt;end&lt;/code&gt;, 끝장을 봐서 재시작을 고려할 필요도 없을 때 &lt;code class=&quot;language-text&quot;&gt;finish&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;다음 단계의 시작을 중단했을 때 &lt;code class=&quot;language-text&quot;&gt;suspend&lt;/code&gt;, 중단한 어떤 의도가 있을 때 &lt;code class=&quot;language-text&quot;&gt;hold&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;어떤 값을 가져올 때&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;어떤 값을 돌려받아서 반환하는 함수일 때 &lt;code class=&quot;language-text&quot;&gt;get&lt;/code&gt;, 검색해서 가져온다는 의미인 &lt;code class=&quot;language-text&quot;&gt;retrieve&lt;/code&gt;, 다른 것을 가져가지 못하게 독점하고자 할 때는 &lt;code class=&quot;language-text&quot;&gt;acquire&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;현재 값을 가리키는 포인터가 다음 값으로 이동한 것을 가져올 때는 &lt;code class=&quot;language-text&quot;&gt;fetch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;어떤 값을 만들거나 수정할 때&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;어떤 값을 정해진 틀에 넣을 때 &lt;code class=&quot;language-text&quot;&gt;set&lt;/code&gt;, 정해진 틀이 없으므로 먼저 틀을 만들때는 &lt;code class=&quot;language-text&quot;&gt;create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;내용을 단순히 바꾸는 &lt;code class=&quot;language-text&quot;&gt;change&lt;/code&gt;, 잘못된 것을 바로잡을 때 &lt;code class=&quot;language-text&quot;&gt;modify&lt;/code&gt;, 기존 내용과 달라졌음을 분명히할 때 &lt;code class=&quot;language-text&quot;&gt;revise&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;코드를 보는 개발자 사이에 일관적이고 개연적인 합의만 되어 있다면 어떻게 쓰든 상관없다.
&lt;a href=&quot;https://brunch.co.kr/@goodvc78/12#comments&quot;&gt;오픈소스의 네이밍 특징들&lt;/a&gt;을 읽어보는 것도 추천한다.&lt;/p&gt;
&lt;h4&gt;기술 블로그의 4종류&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;저&lt;/strong&gt; : 직접 경험하고 실험한 과정이나 결과&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;술&lt;/strong&gt; : 어떤 것을 분석하여 의미를 풀이하고 해석한 것&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;편&lt;/strong&gt; : 산만하고 복잡한 자료를 편집해 질서를 부여한 것&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;집&lt;/strong&gt; : 여러 사람의 견해나 흩어진 자료를 한데 정리한 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 책에서 더 좋은 많은 내용들을 서술한다.&lt;br&gt;
느끼기로는 글쓰기 가장 중요한점은 내가 쓸 글의 특징과 이 글을 읽을 독자의 관심이 무엇인지, 비즈니스 관점의 글쓰기가 필요하진 않은지 잘 판단하여야 한다는 것이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;이 글을 왜 쓰는지?&lt;/li&gt;
&lt;li&gt;이 글을 읽는 독자는 누구인지?&lt;/li&gt;
&lt;li&gt;이 글을 읽는 독자에게 무엇을 말하려고 하는지?&lt;/li&gt;
&lt;li&gt;이 글이 주장하는 바가 무엇인지?&lt;/li&gt;
&lt;li&gt;이 글이 주장하는 바의 근거가 분명한지?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;문제를 해결하면서 만난 모든 문제를 순차적으로 서술하기 보다는 문제 해결의 핵심 내용들을 정리하여 간결하게 써봐야겠다.&lt;/p&gt;
&lt;h1 id=&quot;그-외&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;그 외&lt;/strong&gt;&lt;a href=&quot;#%EA%B7%B8-%EC%99%B8&quot; aria-label=&quot;그 외 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id=&quot;채식주의자&quot; style=&quot;position:relative;&quot;&gt;채식주의자&lt;a href=&quot;#%EC%B1%84%EC%8B%9D%EC%A3%BC%EC%9D%98%EC%9E%90&quot; aria-label=&quot;채식주의자 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&apos;채식주의자&apos;에서 꽤 불편한 내용이 등장하는데.. 그 불편한 내용조차 전달력이 너무 좋아 생생하게 읽혔다.&lt;/p&gt;
&lt;p&gt;이 책을 읽으면서 &lt;code class=&quot;language-text&quot;&gt;흥미 → 불쾌 → 답답함 → 공감 → 수긍&lt;/code&gt; 단계를 거친 것 같다.&lt;br&gt;
영혜가 무너지기 시작하면서 가까운 주변 인물들이 어떻게 무너지는지 자세하게 묘사하는데, 왜 주인공 입장에서 공감해주려 노력하는 사람이 없을까? 왜 자기가 가진 렌즈로만 가까운 사람을 보려고 할까? 라는 답답함을 많이 느꼈다.&lt;br&gt;
가장 가까운 남편, 가족들이 강요만 하고 이해하려 시도하지 않는 것부터 너무 답답했다...&lt;/p&gt;
&lt;p&gt;공감하기 힘들었던 부분은 영혜가 채식을 시작하게 된 이유이다. 왜 나무가 되려 하는지, 왜 이기적이게 변했는지도 궁금하다. 영혜에 대한 이야기가 더 있었다면 답답함이 사라질 수 있지 않았을까&lt;br&gt;
가장 안타까운 인물은 영혜의 언니라고 생각했다. 평생을 참아오고 지켜내면서 이겨내온 인물이였지만 스스로 무너질 뻔 했던 내용들이 마음이 아팠다.&lt;/p&gt;
&lt;p&gt;이 책은 과하게 이야기하면 역겨운 내용이 포함되어 있기에 마음먹고 읽어보길 바란다. (불편했지만 재밌긴 했다)&lt;/p&gt;
&lt;h2 id=&quot;소년이-온다&quot; style=&quot;position:relative;&quot;&gt;소년이 온다&lt;a href=&quot;#%EC%86%8C%EB%85%84%EC%9D%B4-%EC%98%A8%EB%8B%A4&quot; aria-label=&quot;소년이 온다 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;5.18 민주화운동을 소재로한 영화를 보면서도 사실 엄청 와닿진 않았던 것 같다. &apos;아 이런 일이 있었구나..&apos; 정도였던 것 같다.&lt;br&gt;
하지만 이번엔 이 책을 읽으면서 그 때의 상황들을 확실하게 느낄 수 있었다. 출퇴근길 지하철에서 읽으면서 울컥도 하고 진짜 이런 일이 가능한 것인지 생각에 잠기기도 했다.&lt;br&gt;
읽다보면 너무 빠져서 영화 한 편 보는 느낌이였다. 책을 다 읽었다면 &lt;a href=&quot;https://www.youtube.com/watch?v=Rf7X6WMuHH4&amp;#x26;ab_channel=%EC%97%A0%EB%B9%85%EB%89%B4%EC%8A%A4&quot;&gt;노벨문학상 수상 특집 다큐 &apos;한강이 온다&apos;&lt;/a&gt; 영상도 추천한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;썩어가는 내 옆구리를 생각해.&lt;br&gt;
거길 관통한 총알을 생각해.&lt;br&gt;
내 모든 따뜻한 피를 흘러나가게 한 구멍을 생각해.&lt;br&gt;
그걸 쏘아보낸 총구를 생각해.&lt;br&gt;
차디찬 방아쇠를 생각해.&lt;br&gt;
그걸 당긴 따뜻한 손가락을 생각해.&lt;br&gt;
나를 조준한 눈을 생각해.&lt;br&gt;
쏘라고 명령한 사람의 눈을 생각해.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;한국인이라면 꼭 읽어야 할 책이 아닐까&lt;/p&gt;
&lt;h2 id=&quot;싯다르타&quot; style=&quot;position:relative;&quot;&gt;싯다르타&lt;a href=&quot;#%EC%8B%AF%EB%8B%A4%EB%A5%B4%ED%83%80&quot; aria-label=&quot;싯다르타 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;싯다르타를 보고 석가모니(고타마 싯다르타) 이야기로 오해할 수 있지만 동명이인인 싯다르타의 내면 성찰에 대한 이야기이다.&lt;br&gt;
(고타마 싯다르타가 등장하긴 하지만 연관있진 않다.)&lt;br&gt;
싯다르타는 최고 계급인 브라만 계급(바라문)의 가문에 속하였으며 다른 사람들에게 사랑과 존경을 받는 환경에서 자랐다.&lt;br&gt;
게다가 바라문에는 최고의 지혜와 풍부한 지식이 있었음에도 불구하고 스스로 만족하지 않는 삶이였다.&lt;br&gt;
깨달음, 자기를 비우는 일, 평정함을 얻는 것, 자기를 초탈하는 경지에 이르는 것이 목표였다.&lt;/p&gt;
&lt;p&gt;그러기위해 여러가지 도전을 하게된다. 사문이 되어 자아로부터 벗어나는 법을 배우고 고타마 싯다르타의 가르침을 듣지만 그대로 수긍하지 않고 스스로 깨달음을 얻으러 가는 등 수많은 자기 발전의 단계를 거치게 된다.&lt;br&gt;
싯다르타의 이런 경험들과 경험들에서 얻는 깨달음을 같이 경험할 수 있었다.&lt;/p&gt;
&lt;p&gt;지식은 타인에게서 얻을 수 있지만 지혜와 깨달음은 그렇지 않다는 것이 기억에 남는다.&lt;br&gt;
이 책은 한 고행자의 희로애락을 경험할 수 있는 책이다. 불안하고 잡생각에 자주 빠진다면 읽어보길 추천한다.&lt;/p&gt;
&lt;h2 id=&quot;나의-하루는-4시-30분에-시작된다&quot; style=&quot;position:relative;&quot;&gt;나의 하루는 4시 30분에 시작된다&lt;a href=&quot;#%EB%82%98%EC%9D%98-%ED%95%98%EB%A3%A8%EB%8A%94-4%EC%8B%9C-30%EB%B6%84%EC%97%90-%EC%8B%9C%EC%9E%91%EB%90%9C%EB%8B%A4&quot; aria-label=&quot;나의 하루는 4시 30분에 시작된다 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;어떤 블로그에서 이 책을 알게되어 시간 관리에 대한 동기를 얻을 수 있지 않을까 싶어서 읽어보았다.&lt;br&gt;
책 읽기 전에 &lt;a href=&quot;https://www.youtube.com/watch?v=hEu_16312Xw&quot;&gt;유퀴즈에 출연한 영상&lt;/a&gt;도 시청하는 것도 좋다.&lt;/p&gt;
&lt;p&gt;저자는 미국 2개 주에서 변호사 자격증을 따고 국내 대기업에서 사내 변호사로 활동 중이다.&lt;br&gt;
이 결과만 보면 평탄하고 문제 없는 삶을 살아왔으리라 짐작한다.&lt;br&gt;
하지만 이 책을 통해 어린 시절 이민으로 겪은 따돌림, 주변 사람들과 스스로를 비교하며 쫓기는듯한 삶, 변호사 시험에 떨어질 떄 느낀 좌절들에 대한 이야기를 읽을 수 있었다.&lt;br&gt;
이런 고난을 이겨내기 위해 수영을 연습하여 청소년 대표가 되고, 자아성찰과 건강한 피드백을 통해 스스로를 돌아보는 습관과 부족한 시간을 모아 시험에 재도전하는 노력을 이루었다.&lt;/p&gt;
&lt;p&gt;이 기저에는 새벽 시간을 통해 &lt;code class=&quot;language-text&quot;&gt;내가 주도하는 시간&lt;/code&gt;을 꾸준히 만들어낸 것이다.&lt;br&gt;
저자는 살면서 무언가를 쉽게 얻은 적이 없다고 얘기한다. 사람들이 피하는 험난한 길을 걸으며 인내를 기르고, 힘듦에서 즐거움을 찾는 습관을 갖게 되었다고 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;새벽 기상은 시간이 없다는 핑계를 댈 수 없게 만든다. 새벽 시간은 &lt;code class=&quot;language-text&quot;&gt;내가 주도 하는 시간&lt;/code&gt;, 그 밖의 시간은 &lt;code class=&quot;language-text&quot;&gt;운명에 맡기는 시간&lt;/code&gt;이다.&lt;/li&gt;
&lt;li&gt;몸이 무엇을 하고 있는지가 아니라 머리와 마음이 무엇을 느끼는지가 휴식의 질을 좌우한다.&lt;/li&gt;
&lt;li&gt;자기계발을 할 때는 &quot;멀리 가려면 같이 가라.&quot;가 적용되지 않는다. 외부 소음과 주변인들의 조언을 차단할 수 있는 용기가 필요하다.&lt;/li&gt;
&lt;li&gt;순간의 즐거움을 나의 발전과 교환해서는 안 된다. 타인의 설득에 쉽게 휘말리는 삶은 결코 안정적일 수 없다.&lt;/li&gt;
&lt;li&gt;진정한 발견은 자신이 잘하는 걸 찾는 것이 아니라 부족함을 인정하고 어제보다 더 나은 자신이 되기 위해 노력하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;엄청난 위인이 얘기해주는 것이 아니라 일반인이 자신의 삶을 통해 교훈을 전달하니 더욱 와닿았다. (일반이이라고 하기에는 엄청난 정신력의 소유자이지만)&lt;br&gt;
덕분에 평소 배우고 싶었지만 용기내지 못했던 일을 현재 새벽에 일어나서 실천하게 되었다.&lt;br&gt;
이 책은 시간 관리를 어떻게 해야하는지도 설명하지만 (가장 중요한 것은) 시간 관리를 왜 해야하는지, 자기 인생을 주도하는 방법을 깨닫을 수 있어 좋았다.&lt;/p&gt;
&lt;h2 id=&quot;이방인&quot; style=&quot;position:relative;&quot;&gt;이방인&lt;a href=&quot;#%EC%9D%B4%EB%B0%A9%EC%9D%B8&quot; aria-label=&quot;이방인 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Hdf35IzBpIU&amp;#x26;ab_channel=%EB%84%88%EC%A7%84%EB%98%91NJTBOOK&quot;&gt;너진똑 - 카뮈 [이방인]&lt;/a&gt;의 영상을 보고 내용이 궁금하여 읽어보았다.&lt;br&gt;
&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%97%88%EB%AC%B4%EC%A3%BC%EC%9D%98&quot;&gt;허무주의&lt;/a&gt;와 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%97%BC%EC%84%B8%EC%A3%BC%EC%9D%98&quot;&gt;염세주의&lt;/a&gt;에 가까워 보이는 주인공이 삶을 대면하는 자세를 볼 수 있었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;뫼르소에게는 모든 것들이 무관하다. 결혼하는 것과 결혼하지 않는 것, 범속한 장례식과 종교적 장례식, 직장에서 승진을 하는 것과 승진을 하지 않는 것이 다르다고 느끼지 못한다.&quot;&lt;br&gt;
&quot;자기 자신에게도 무관심하다. 자기 스스로의 감정에 대해서도 이방인인 것이다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;주인공은 어머니의 죽음, 여자친구의 청혼, 바다로 떠나는 즐거운 여행 등 삶을 살면서 경험하는 희노애락에 초연하다는 것을 느꼈다.&lt;br&gt;
재판을 받으면서 부조리한 상황에도 거짓말을 하지 않겠다는 결연한 자세도 볼 수 있었다.&lt;br&gt;
이런 주인공이 사형을 선고받고 죽음을 앞둔 심경 변화가 인상깊다.&lt;/p&gt;
&lt;p&gt;책의 저자인 카뮈는 &quot;죽음&quot;을 통해 삶의 의미에 대한 물음을 던진다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;죽음은 우선 &apos;몸&apos;의 문제다. 햇빛과 바다가 주는 행복감을 전신에 맛보며 수영하는 젊은 육체, 축구장에서, 연극 문대에서, 신문 기자로 삶의 현장에서 매 순간 속에 열정적으로 몰입하는 육체에는 오직 현재만이 있을 뿐이다.&lt;br&gt;
&quot;내일 없는&quot; 현재의 가득함, 이것을 카뮈는 &lt;strong&gt;&quot;희망 없는&quot; 삶&lt;/strong&gt; 이라고 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;주인공이 죽음에 대하는 자세를 통해 인생을 대하는 자세를 배울 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;인간은 모두 다 &quot;사형수&quot;다. 삶의 끝에서 기다리고 있는 죽음의 확신이 인간을 사형수로 만들어 놓는다.&lt;br&gt;
사형수는 죽음과 정대면함으로써 비로소 삶의 가치를 깨닫는다. 죽음은 삶의 가치를 더욱 돋보이게 하는 어두운 배경이며 거울이다.&lt;br&gt;
필연적인 죽음의 운명 때문에 삶은 의미가 없으므로 자살해야 하는 것이 아니라 이 한정된 삶을 더욱 치열하게 살아야 한다.&lt;br&gt;
이 소설의 참다운 주제는 삶의 찬가, 행복한 찬가다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 책은 서문, 작품 해설까지 포함되어 있어 이런 고전 문학에 익숙하지 않은 나에게 책을 이해하는데 굉장히 도움이 되었다.&lt;br&gt;
그렇다고 모두 이해했는지는 모르곘지만.. 내년에 한 번 더 읽어보고 싶다.&lt;/p&gt;
&lt;h2 id=&quot;프레임&quot; style=&quot;position:relative;&quot;&gt;프레임&lt;a href=&quot;#%ED%94%84%EB%A0%88%EC%9E%84&quot; aria-label=&quot;프레임 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;책을 추천받아 읽어보았다. 아마 현재를 만족하지 못하고 불평만 하며 가지지 않은 것에 대한 욕심만 높아 스트레스를 받는 나를 보고 추천해준 것 아닐까 싶다.&lt;br&gt;
책 이름을 보고 따분한 자기계발서로 예상할 수 있지만 연구 결과에 기반한 심리학 내용을 전개하여 흥미롭게 읽었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;프레임&lt;/strong&gt;은 &quot;세상을 바라보는 마음의 창&quot;이다. 어떤 문제를 바라보는 관점, 세상을 향한 마인드셋, 세상에 대한 온유, 사람들에 대한 고정관념등이 모두 프레임의 범주에 포함되는 말이다.&lt;br&gt;
마음을 비춰보는 창으로서의 프레임은 특정한 방향으로 세상을 보도록 이끄는 조력자의 역할을 하지만, 동시에 우리가 보는 세상을 제한하는 검열관의 역할도 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;프레임&lt;/strong&gt;은 우리가 지각하고 생각하는 과정을 선택적으로 제약하고, 궁극적으로는 지각과 생각의 결과를 결정한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;프레임&lt;/strong&gt;은 다양한 형태를 지닌다. 우리의 가정, 전제, 기준, 고정관념, 은유, 단어, 질문, 경험의 순서, 맥락 등이 프레임의 대표적인 형태다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 &lt;strong&gt;프레임&lt;/strong&gt; 에 대한 특성과 연구 결과들을 설명한다.&lt;br&gt;
그중에 인상깊은 연구들이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;동메달이 은메달보다 행복한 이유&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이미지 투사&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;타인을 평가하는 기준은 자기 자신을 평가할 때도 그 기준을 중요하게 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;행동의 원인은 사람인가? 상황인가?&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;나치의 반인륜적인 악행을 행한 사람들을 &quot;소수의 악인, 소수의 사이코패스가 저지른 악행&quot; 이라는 &lt;strong&gt;사람 프레임&lt;/strong&gt; 으로 보지 않는 것&lt;/li&gt;
&lt;li&gt;악이 사람이 아니라 상황에 의해 유발된다는 &lt;strong&gt;상황 프레임&lt;/strong&gt; 으로 보는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위의 연구에 힘을 싣는 &lt;strong&gt;권위에 대한 위험한 복종 연구&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;지시자, 교사, 학생 3명의 인원 중 교사만 실험 대상이다.&lt;/li&gt;
&lt;li&gt;지시자는 교사에게 학생이 오답을 말하면 전기가 통하는 스위치를 누르도록 지시&lt;/li&gt;
&lt;li&gt;학생이 고의로 오답을 선택하게 하여 전기 충격을 15V에서 450V 까지 단계적으로 올렸다.&lt;/li&gt;
&lt;li&gt;마지막 수준까지 전기 충격을 가할 사람의 수는 극소수라고 생각했지만 67%에 해당하는 사람이 끝까지 복종&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;방관자 효과&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;살인 사건에 최소한 49명의 목격자가 있었지만 경찰에 즉각적으로 알라지 않고 서로 떠넘김으로써 골든타임을 놓쳐 사망한 사건&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;군중의 힘&lt;/strong&gt;
&lt;ul&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;strong&gt;가장 긴 선을 고르세요.&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;8명 중 1명을 속이는 실험, 8명 중 7명은 가장 긴 선을 선택하라는 질문에 동일하게 잘못된 답을 선택&lt;/li&gt;
&lt;li&gt;그 중 실험 대상인 1명은 처음엔 맞는 답을 선택하였지만 시간이 지날수록 7명이 선택한 잘못된 답을 선택한다.&lt;/li&gt;
&lt;li&gt;하지만 7명의 동조자 중 1명이라도 정상적인 답을 선택하면 정답률이 100%에 가깝게 회복된 것&lt;/li&gt;
&lt;li&gt;조금 다르게 7명의 동조자 중 1명이 다수가 선택한 오답이 아닌 다른 답을 선택하여도 정답률이 상승한 것&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;집단의 다양성을 보장하여 우리 모두의 소신을 이끌어내야한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;매몰 비용&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;할인할 때 3000원 주고 스파게티를 사서 집에왔지만 친구가 온다하여 다시 사러 갔을 때는 6000원 주고 스파게티를 사왔다.&lt;/li&gt;
&lt;li&gt;친구가 못 온다고 하였을 때, 두 개의 스파게티 중 많은 사람들이 비싼 스파게티를 선택하는 것&lt;/li&gt;
&lt;li&gt;아깝다는 이유만으로 제값에 산 걸 먹어야만 된다고 생각하는 &lt;strong&gt;손실에 대한 원초적 거부&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;책이 나를 설명한다고 느낄 정도로 읽으면서 뜨끔한 적이 많았다.&lt;br&gt;
그리고 읽으면서 &lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%9C%A0%EC%97%B0%ED%95%A8%EC%9D%98-%ED%9E%98&quot;&gt;유연함의 힘&lt;/a&gt;이 떠올랐다. 유연함의 힘은 커리어, 회사생활에 대한 내용이였다면 이 책은 삶에 대한 내용을 설명한다고 느꼈다.&lt;/p&gt;
&lt;p&gt;심리학에 흥미가 있는 사람이라면 추천해주고 싶다.&lt;/p&gt;
&lt;h2 id=&quot;타이탄의-도구들&quot; style=&quot;position:relative;&quot;&gt;타이탄의 도구들&lt;a href=&quot;#%ED%83%80%EC%9D%B4%ED%83%84%EC%9D%98-%EB%8F%84%EA%B5%AC%EB%93%A4&quot; aria-label=&quot;타이탄의 도구들 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;개인적으로 자기계발서 책을 선택할 때는 어떤 주제에 특정적으로 관심이 생기거나 &quot;몰입&quot;과 &quot;프레임&quot;, &quot;이너게임&quot;처럼 심리학을 근거로 연구 결과에 기반한 자기계발서들을 선택하려 한다.&lt;br&gt;
타이탄의 도구들과 같이 성공한 사람들의 이야기를 전달하는 자기계발서를 좋아하지 않다보니 거부감이 드는 내용도 있었다.&lt;br&gt;
원래는 조금 읽다가 덮었겠지만 추천받은 책이라서 끝까지 읽어보았다.&lt;/p&gt;
&lt;p&gt;이 책은 세상에서 가장 성공한 사람들, 특정 분야에서 거대하고 영향력이 큰 사람들 &lt;code class=&quot;language-text&quot;&gt;타이탄&lt;/code&gt;과 나눈 이야기들을 전개한다.&lt;br&gt;
그중에 인상깊은 내용만 간략하게 적어보면&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;나는 가진 것의 가치를 낮게 보고 가지지 않은(못한) 것에 집착한다는 이야기를 들은적이 있다.
&lt;ul&gt;
&lt;li&gt;타이탄들은 &lt;code class=&quot;language-text&quot;&gt;&quot;추구하는 것에만 집착하면 현재 갖고 있는 걸 잃는다. 반대로 현재 갖고 있는 것에 감사하면 마침내 추구하는 것을 얻게 된다.&quot;&lt;/code&gt; 라고 말한다.&lt;/li&gt;
&lt;li&gt;&apos;내가 감사하게 여기는 것들&apos;을 떠올려라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;나는 회의를 하거나 토론할 때 감정적으로 동요할 때가 있다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;비판하는 사람도, 그것을 달게 받는 사람도 모두 자기 자신과 상대, 회사를 위해 기여하고 있다는 걸 깨닫아야 한다.&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;점점 똑똑해진다는 것은 점점 강해진다는 뜻이니 강력한 의견과 침착한 태도를 유지해라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;전속력으로 달릴 때의 속도와 즐기면서 달릴 때의 속도 차이는 크지 않다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;우리는 천천히 해도 충분하다. 우리가 저지른 실수들은 대부분 나태함 때문이 아니라 야심과 욕심 때문이다.&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&quot;그러니 명상을 하든 컴퓨터 모니터 앞에서 벗어나 나 자신을 위해 시간을 쓰든, 아니면 함께 있는 사람에 집중하든지 속도를 늦춰야 한다.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부정적인 감정에 휩쓸리지 마라. 화를 내고 속상해하는 것은 백해무익이다. 그 시간에 &lt;code class=&quot;language-text&quot;&gt;대안&lt;/code&gt;을 찾아라.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;당신은 인생의 몇 퍼센트를 화를 내고 걱정하고 좌절하는데 사용하고 있는가?&quot;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;책 에서는 &lt;a href=&quot;https://m.blog.naver.com/stranger234/221394229740&quot;&gt;우리에게 소중한 시간은 얼마나 남았을까?&lt;/a&gt;를 추천하지만 개인적으로는 &lt;a href=&quot;https://www.youtube.com/watch?v=oBIo2AyjNMo&amp;#x26;list=LL&amp;#x26;index=106&amp;#x26;ab_channel=%ED%95%9C%EB%88%88%EC%97%90%EB%B3%B4%EB%8A%94%EC%84%B8%EC%83%81%E2%80%93Kurzgesagt&quot;&gt;낙관적 허무주의&lt;/a&gt;도 추천한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;자기 전에, 꼭 생각할 거리를 정해두고 자라.&quot;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&quot;몰입&quot;에서도 수면을 취하면서 무의식과 잠재의식으로 독창적인 해결책들을 찾는다고 하였는데 이 책에서도 관련 내용이 나왔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사회생활을 한다면 명심하라. &lt;strong&gt;위기에 처했을 때 모욕감없이 자존심을 굽힐 수 있게 해주고, 편견없이 모든 유용한 조언들을 스폰지처럼 흡수할 수 있게 해준다.&lt;/strong&gt;
&lt;ul&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;/ol&gt;
&lt;p&gt;그리고 책 마지막에 &lt;a href=&quot;https://brunch.co.kr/@yunseop17/15&quot;&gt;크리스토퍼 소머의 &apos;단 하나의 결단&apos;&lt;/a&gt;의 내용도 인상깊었다.&lt;br&gt;
이루고 싶고 원하는 것을 위해 노력하고 있지만 발전이 보이지 않는다고 생각하는 사람이라면 꼭 읽어봤으면 좋겠다.&lt;/p&gt;
&lt;p&gt;이 글 시작에 이런 종류의 자기계발서를 좋아하지 않는다고 적었지만 위에 작성한 내용 말고도 인상 깊은 내용들이 많았다.&lt;br&gt;
모든 내용들이 다 와닿지는 않았지만 주옥같은 조언들이 많으니 나와 같이 이런 종류의 자기계발서를 기피하고 있다면 한 번은 읽어보길 추천한다.&lt;/p&gt;
&lt;h2 id=&quot;가난한-아이들은-어떻게-어른이-되는가&quot; style=&quot;position:relative;&quot;&gt;가난한 아이들은 어떻게 어른이 되는가?&lt;a href=&quot;#%EA%B0%80%EB%82%9C%ED%95%9C-%EC%95%84%EC%9D%B4%EB%93%A4%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%96%B4%EB%A5%B8%EC%9D%B4-%EB%90%98%EB%8A%94%EA%B0%80&quot; aria-label=&quot;가난한 아이들은 어떻게 어른이 되는가 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;회사 책장을 둘러보다가 제목을 보고 재밌어 보여서 읽어보았다.&lt;/p&gt;
&lt;p&gt;&quot;나도 어린시절이 풍족하진 않았지&quot; 라고 생각하면서 책을 읽었는데 내가 생각한 &quot;풍족하진 않다.&quot;와 책에서 말하는 &quot;빈곤&quot;이라는 단어는 확실히 달랐다.&lt;br&gt;
이 책은 8명의 청(소)년들 이야기를 어린 시절부터 성인이 될 때까지 3,4년 주기로 세 번이상 인터뷰를 진행한 내용들을 담았으며, 개개인들이 빈곤, 어려운 환경들을 어떻게 극복해나가는지 설명하는데 기질에 따라, 환경에 따라 다 다른 과정과 다른 결말이였다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;불우한 가정환경 때문에 자신을 챙겨주고, 염려해주는 사람은 없지만 철저한 자기 객관화와 주변 환경(복지센터 등)을 잘 활용한 사례&lt;/li&gt;
&lt;li&gt;가족이 어려움을 겪고 헤어지는 위기도 있었지만, 결국엔 다시 결합하는 과정을 통해 가족에 대한 애틋함을 배워 행복한 가정을 꾸리는 것이 꿈인 사례&lt;/li&gt;
&lt;li&gt;가난은 사회적으로 만들어진 현상이라는 것을 깨닫아 타인의 편견과 시선에 굴하지 않고 자신이 원하는 바를 추구해나간 사례&lt;/li&gt;
&lt;li&gt;자신을 돌보고 자기 욕망과 사회적 위치를 사고하고 판단하는 내면적 성숙도, 성찰하는 힘이 강한 사례&lt;/li&gt;
&lt;li&gt;한 개인이 어디에 위치해 있는가에 대한 정확한 파악과 자아실현을 위한 일이 무엇인가에 대한 인식 혹은 사고, 즉 자아정체감을 찾으려 노력한 사례&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이런 긍정적인 작용들이 있었지만 &quot;빈곤&quot;이 주는 벗어나기 힘든 반작용들도 있었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사람을 쉽게 믿지 못하고, 깊은 관계를 가질 수 없다.&lt;/li&gt;
&lt;li&gt;가족에 대한 책임감으로 부모님의 잘못된 집착과 관심에 벗어나기 힘든 상황&lt;/li&gt;
&lt;li&gt;물질적인 것에 집착하는 상태&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;불평등한 사회에서 빈곤은 단순히 경제적 수치에 해당하는 저소득의 문제가 아니고, 그 영향력이 삶의 전반에 미친다.&lt;br&gt;
불평등한 사회에서 사람들은 자신의 욕구 실현이 매번 좌절되는 경험을 하게 된다.&lt;br&gt;
공부하고 싶어도 가정형편 때문에 못 하고, 안락한 주거환경에서 편안한 생활을 하고 싶지만 항상 불안에 휩싸여 낮은 삶의 질을 경험해야 하고,&lt;br&gt;
모범이 될 만한 사람을 만나 배우고 각성해서 어떤 꿈을 실현하고 싶지만 주위에서 그런 사람을 접해보지 못하는 삶,&lt;br&gt;
또한 이 삶의 패턴이 조부모, 부모로부터 지속적으로 반복되어왔다는 것을 경험하는 삶,&lt;br&gt;
이러한 과정이 누적되면 자신이 누구인가 하는 사회적 존엄성에 침해를 입고, 이렇게 침해된 존엄성은 주체를 불안정한 상태로 만들며, 건강한 사회적 관계를 맺는데 질곡이 된다.&lt;br&gt;
결국, &lt;strong&gt;오랜 시간 축적된 빈곤은 자신의 욕구를 실현하고, 거기서 만들어진 능력을 발휘해 사회에 기여하고 이를 통해 개인적이며 사회적인 행복감을 추구하려는 가능성을 모두 훼손한다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그럼에도 불구하고 이 책에서 소개하는 청(소)년들은 성인이되어 회사를 다니고, 결혼도 하며, 삶을 정상적인 궤도에 올리게 된다.&lt;br&gt;
이 책은 개인적으로 굉장히 잘 읽혔다. 자기 삶이 지루하고 허무하다고 느껴진다면 이 책을 읽어보면 좋은 자극을 받을 수 있을 것이다.&lt;br&gt;
이 책을 통해 가난과 빈곤은 대물림되어 사회적으로 만들어질 수 있다는 것과 극심한 빈곤은 생존 자체에 에너지가 너무 많이 들어가서 합리적 판단을 하고 미래지향적 사고를 할 에너지가 바닥난다는 것을 배웠다.&lt;/p&gt;
&lt;h2 id=&quot;인간-불평등-기원론&quot; style=&quot;position:relative;&quot;&gt;인간 불평등 기원론&lt;a href=&quot;#%EC%9D%B8%EA%B0%84-%EB%B6%88%ED%8F%89%EB%93%B1-%EA%B8%B0%EC%9B%90%EB%A1%A0&quot; aria-label=&quot;인간 불평등 기원론 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&apos;불안&apos;을 읽다가 소개되어 불안보다 먼저 읽어보았다.&lt;br&gt;
대부분의 사람들이 남에 비해 자신이 더 행복하지 않다고 생각하며 살아간다. 무엇인가 남보다 못한 점이 있다는 생각을 떨치지 못하는 것이다.&lt;br&gt;
상대적으로 무엇인가를 더 가진 자와 비교하면서 발생하는 &lt;code class=&quot;language-text&quot;&gt;불평등&lt;/code&gt;에 대한 장자크 루소의 개인적인 생각을 전개한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;인류에게 두 가지 불평등이 있다고 생각한다.&lt;br&gt;
하나는 자연적 또는 신체적 불평등이라고 부르는 것이다. 이것은 자연에 의해 정해지는 것으로 나이, 건강, 체력의 차이와 정신이나 영혼의 자질 차이로 성립된다.&lt;br&gt;
다른 하나는 일종의 약속에 좌우되고, 사람들의 동의로 정해지거나 적어도 용납되는 것으로 도덕적 또는 정치적 불평등이라고 할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;다른 사람들보다 더 부유하다거나, 더 존경을 받는다거나, 권력을 더 가지고 있다거나 또는 타인을 복종하게 만든다거나 하는 &lt;code class=&quot;language-text&quot;&gt;특권&lt;/code&gt;들에 의해 성립되는 두 번째 불평등에 대해 소개한다.&lt;br&gt;
이 불평등의 기원은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사회가 형성되고&lt;/li&gt;
&lt;li&gt;사유재산이 인정되고&lt;/li&gt;
&lt;li&gt;공동 생활의 경험과&lt;/li&gt;
&lt;li&gt;무제한적인 인간의 가능성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;에 따른 문명 사회의 부작용이라고 설명하며, 문명보다 원시를 더 높이 평가하는 듯한 느낌이다.&lt;br&gt;
지구가 멸망하고 인류가 다시 등장하지 않는 한 이 책에서 설명하는 원초적인 자연 상태로 돌아갈 수 없을 것이다.&lt;br&gt;
하지만 선과 악이라는 개념이 존재하지 않고 사회가 형성되기 이전, 완전 원초적 자연 상태에 살았던 미개인들은 더 행복하고 자유롭지 않았을까? 라는 생각이 들게한 책이다.&lt;/p&gt;
&lt;p&gt;나의 문해력이 낮다는 것을 알게된 책이다. 읽는데 많은 집중이 필요했다.&lt;br&gt;
불평등에 대한 생각을 해본적이 있다면 읽어보길 추천한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;인간이 자연의 상태에 가깝게 있으면 있을수록 능력과 욕망의 차이는 점점 더 적어지고, 따라서 행복으로부터 그만큼 가까워지게 된다.&lt;br&gt;
모든 것을 잃어버린 것처럼 보인다고 해서 그때가 가장 불행한 것은 아니다.&lt;br&gt;
&lt;strong&gt;왜냐하면 불행이란 사물의 결핍 상태에서 오는 것이 아니라, 결핍감을 느끼게 하는 욕구에서 오는 것이기 때문이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;왜-일하는가&quot; style=&quot;position:relative;&quot;&gt;왜 일하는가?&lt;a href=&quot;#%EC%99%9C-%EC%9D%BC%ED%95%98%EB%8A%94%EA%B0%80&quot; aria-label=&quot;왜 일하는가 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;나는 개발자를 하기전에 기계설비 관련 일을 먼저 했었다.&lt;br&gt;
처음에는 자동차 엔진 생산 라인 설비 유지보수를 하다가 유지보수말고 설비를 만드는 회사에 가고 싶어서 전기 자동차 모터 테스트 설비를 만드는 회사로 이직했었다.&lt;br&gt;
그리고 설비를 만드는 회사에서 소프트웨어 팀과 협업했던 경험이 지금 개발자로 지내게된 계기가 되었다.&lt;/p&gt;
&lt;p&gt;개발자를 하기전에는 일을 잘 해내고 싶다는 욕심이 없었다. 개인 시간에 공부를 한다거나 자기개발에 대한 노력조차 없었다.&lt;br&gt;
삶과 일을 분리하여 생각하면서 퇴근 시간만 기다리고, 일에 대한 재미를 느끼지 못한 시기였다.&lt;br&gt;
삶에 대해 무료함을 느끼면서 생산적이지 않은 행동들로 시간을 허투루 보내왔다.&lt;/p&gt;
&lt;p&gt;하지만 개발자를 시작하면서 삶과 일을 분리하려 해도 분리할 수 없는 경험을 하고 있다.&lt;br&gt;
읽고 싶은 책과 듣고 싶은 강의 목록이 생기고, 다음에는 무엇을 공부할지, 일을 더 잘 해내기 위해서는 무엇이 필요한지를 고민하며, 하고 싶고 경험하고 싶은 일, 환경, 목표가 생겼다.&lt;br&gt;
자연스럽게 삶에 생기가 돌기 시작했고 생산적인 행동들로 시간을 보내기 위해 노력하고 있다.&lt;/p&gt;
&lt;p&gt;이런 경험 덕분에 이 책을 읽으면서 공감할 수 있는 내용들이 많았다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;세상에 태어나 단 한 번뿐인 귀한 삶을 사는데, 지금 당신은 정말로 가치 있는 삶을 살고 있는가?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;내가 하고 있는 일을 사회악으로 생각하면서 단순히 돈을 벌기 위한 수단이라고만 생각하면 삶의 아름다움과 일의 재미를 느끼기 힘들다.&lt;/li&gt;
&lt;li&gt;일하는 수고로움을 아는 사람만이 안락함의 소중함도 아는 법이다. 추위를 알아야 따뜻함을 알듯이 말이다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;인생과 일 = 능력 * 열의 * 사고방식&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;자신의 일을 진심으로 사랑하는 사람을 볼 때 감동을 느끼며, 나도 그런 사람이 되기 위해 노력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 책을 통해 일을 사랑하는 방법과 일을 통해 삶의 아름다움을 느끼는 방법을 배울 수 있을 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;일하기 싫다. 먹고 살기 위해 어쩔 수 없이 회사에 나간다. 그러니 가능하다면 힘든 일은 피하고, 몸도 마음도 편하게 일하고 싶다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위와 같은 생각을 한적이 있고 인생이 무료하고 일이 재미없다면 이 책을 읽어보길 추천한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;오늘도 습관처럼 출근하는 당신에게 묻는다.&lt;br&gt;
&lt;strong&gt;당신은 어떤 일을 하는가?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;그 일을 통해 당신은 무엇이 되길 꿈꾸는가?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;당신이 꿈꾸는 일과 삶의 미래는 어떠한 모습을 하고 있는가?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;물고기는-존재하지-않는다&quot; style=&quot;position:relative;&quot;&gt;물고기는 존재하지 않는다.&lt;a href=&quot;#%EB%AC%BC%EA%B3%A0%EA%B8%B0%EB%8A%94-%EC%A1%B4%EC%9E%AC%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4&quot; aria-label=&quot;물고기는 존재하지 않는다 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;어린 시절 저자는 아버지에게 &quot;인생의 의미가 뭐에요?&quot; 라고 물었을 때, 아래의 이야기를 듣게 된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;인생의 의미? 그런 것은 없어. 신도 없어. 어떤 식으로든 너를 지켜보거나 보살펴주는 신적인 존재는 없어.&quot;&lt;br&gt;
&quot;그런 것들은 모두 사람들이 이 모든 게 아무 의미도 없고 자신도 의미가 없다는 무시무시한 감정에 맞서 자신을 달래기 위해 상상해 낸 것일 뿐이니까.&quot;&lt;br&gt;
&quot;너 스스로는 특별하게 느껴지더라도 너는 한 마리 개미와 전혀 다를 게 없다는 걸.&quot;&lt;br&gt;
&quot;&lt;code class=&quot;language-text&quot;&gt;혼돈&lt;/code&gt;만이 우리의 유일한 지배자 이며, &lt;code class=&quot;language-text&quot;&gt;혼돈&lt;/code&gt;은 우리의 그 무엇에도 관심이 없단다.&quot;&lt;br&gt;
&quot;우리는 전체 시간의 선에서 아주 아주 작은 영억에만 존재한단다. 그리고는 결국 사라질거야.&quot;&lt;br&gt;
&quot;&lt;strong&gt;그러니 너 좋은대로 살아&lt;/strong&gt;&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저자는 힘들었던 학창시절, 자신의 실수로 인해 사랑하는 사람과 이별하는 등 삶에 대한 무력감을 느끼던 상황에서 아버지가 말해주던 혼돈과 대비되는 데이비드 스타 조던의 인생에 관심을 가지게 된다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/David_Starr_Jordan&quot;&gt;데이비드 스타 조던&lt;/a&gt;은 19세기에 스탠포드 대학교의 창립 총장으로 재임했었던, 어류학자로서 지대한 영향을 끼친 인물이다.&lt;/p&gt;
&lt;p&gt;데이비드는 자신이 평생을 투자하여 만든 표본 컬렉션들과 문서들이 벼락으로 인해 모두 불탔을 때 &lt;strong&gt;자신이 잃은 것들을 되찾기 위해 재를 털고 곧바로 다시 물이 있는 곳을 찾으러 가는 의지&lt;/strong&gt; , 사랑하는 아내, 자식이 숨을 거두었을 때에도 &lt;strong&gt;툴툴 털고 황야로 다시 나가는 의지&lt;/strong&gt; , 샌프란시스코 지진으로 인해 표본 단지들이 박살날때에도 &lt;strong&gt;좌절하지 않고 표본들의 이름표를 표본에 직접 꼬메어 다시 단지에 넣는 의지&lt;/strong&gt; 에 저자는 관심을 가지게 된다.&lt;/p&gt;
&lt;p&gt;데이비드는 얼마나 많은 시간을 허비했는지 좌절할 시간조차 가지지 않고 혼돈이 지배하는 세계에서 질서를 만들려는 일이 거의 불가능해 보인다는 점도 고려하지 않았기에 저자는 데이비드 스타 조던에게서 &lt;strong&gt;희망을 품는 비결&lt;/strong&gt; , &lt;strong&gt;가장 암울한 날에도 계속 앞으로 나아가는 비결&lt;/strong&gt; , &lt;strong&gt;신앙없이도 믿음을 갖는 비결&lt;/strong&gt; 배울 수 있을 것이라고 생각했다.&lt;/p&gt;
&lt;p&gt;데이비드는 &lt;strong&gt;자기가 옳은 일을 하고 있다고 자신을 설득하고, 그런 다음 무한해 보이는 에너지로 목표를 추구하는 능력&lt;/strong&gt; 이 있었다.&lt;br&gt;
하지만 이 능력, 즉 &lt;strong&gt;자기기만(긍정적 착각)&lt;/strong&gt; 을 능수능란하게 다루는 이 능력이 옳지 않은 곳에 발휘되어 데이비드의 어두운 면도 확인하게 된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;족벌주의&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;미궁으로 빠진 제인의 죽음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자연속에 사다리가 내재되어 있다는 믿음&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20세기 초반, 과학의 탈을 쓰고 퍼져나갔던 거짓된 신념 &lt;code class=&quot;language-text&quot;&gt;우생학&lt;/code&gt;의 기여를 한 전도사&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;우생학&lt;/code&gt;적 목표를 이루기 위해 전쟁을 반대하는 평화주의자&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;우생학&lt;/code&gt;으로 인한 강제적인 불임 시술 지지&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;저자는 모델로 삼으려했던 인물의 잔인성과 무자비함에 충격을 받지만 데이비드는 자기 죄에 대한 벌을 받지 않고 생을 마감한다.&lt;/p&gt;
&lt;p&gt;1980년대에 분류학자들이 타당한 생물 범주로서 &quot;어류란 존재하지 않는다&quot;는 사실을 발견한다.&lt;br&gt;
데이비드에게 너무 소중했던 그 생물의 범주, 그가 역경의 시간이 닥쳐올 때마다 의지했던 범주, 그의 평생을 바쳤던 그 범주는 결코, 단 한 번도 존재한 적이 없었다.&lt;/p&gt;
&lt;p&gt;하지만 그 범주가 존재하지 않는다는 사실이 중요한 일일까?&lt;br&gt;
우리가 다른 집단, 인종, 성별에 대한 생각이 우생학과 비슷한 차별일 수 있지 않을까?&lt;br&gt;
나는 내가 존경하는 사람의 오류를 보았을 때 어떻게 대처할 수 있을까?&lt;/p&gt;
&lt;p&gt;처음엔 위인에 대한 대단한 면을 읽으면서 존경심이 들었지만, 어두운 면을 읽으면서 어떻게 저런 행동을 할 수 있었을까? 라는 혐오감도 들었다.&lt;br&gt;
이 책은 자연과학, 과학자, 에세이 분류에 속하여 넓은 범위에 대한 내용을 설명하여 읽는 사람마다 느끼고 깨닫는 점이 다를 것이라고 보인다.&lt;br&gt;
(문학인 줄 알고 읽었지만) 과학에 더 가까운 내용이였음에도 불구하고 재밌게 읽었다.&lt;/p&gt;
&lt;h2 id=&quot;불안&quot; style=&quot;position:relative;&quot;&gt;불안&lt;a href=&quot;#%EB%B6%88%EC%95%88&quot; aria-label=&quot;불안 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 책은 군대 훈련소에서 처음으로 읽어보고 감명받았던 책이였는데, 개정판이 나왔다는 소식을 듣고 구매하여 다시 읽어보았다.&lt;br&gt;
퇴사를 진지하게 고민하고 있던 시점에 읽어 더 재밌게 읽힌건지 모르겠다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;소속이 없는 무직&lt;/li&gt;
&lt;li&gt;안정적인 수입이 사라지는 것&lt;/li&gt;
&lt;li&gt;어느정도 나이에 맞는 사회적 지위가 사라지는 것&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이런 걱정들 때문에 불안하여 퇴사를 미뤄왔었지만, 이 책을 읽고 내가 경험해보고 싶은 환경, 잘하고 싶은 분야로 도전하기 위한 퇴사에 대한 용기를 받았던 것 같다.&lt;/p&gt;
&lt;p&gt;각설하고, 이 책은 &lt;code class=&quot;language-text&quot;&gt;&quot;사람은 왜 불안하나?&quot;&lt;/code&gt;에 대한 근본적인 내용을 설명한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;불안&lt;/code&gt; 이라는 문제 해결을 위해 &lt;code class=&quot;language-text&quot;&gt;왜 불안한지?&lt;/code&gt;에 대한 원인을 이해하는 노력은 필수이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사람은 왜 높은 지위를 갖기 위해 노력하나?&lt;/li&gt;
&lt;li&gt;사람은 왜 충분한 돈이 있음에도 불구하고 돈을 계속 모으려고 하나?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;사랑을 얻을 수 있는 수단이기 때문이다. 다른 사람의 사랑이 중요한 이유는 &lt;strong&gt;사람은 날 때부터 자신의 가치에 확신을 갖지 못하고 괴로워할 운명을 타고났기 때문이다.&lt;/strong&gt;&lt;br&gt;
그 결과 다른 사람이 우리를 바라보는 방식이 우리가 스스로를 바라보는 방식을 결정하게 된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;자신의 자리에 확신을 가지는 사람은 남들을 경시하는 것을 소일거리로 삼지 않는다.&quot;&lt;br&gt;
&quot;오만 뒤에는 공포가 숨어 있기에, 괴로운 열등감에 시달리는 사람만이 남에게 당신은 나를 상대할 만한 인물이 못 된다는 느낌을 심어주려고 기를 쓴다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;하지만 다른 사람의 인정과 사랑을 받기 위한 지위와 명예, 부는 끝이 없다. &lt;strong&gt;부는 절대적인 것이 아니고 욕망에 따라 달라지는 상대적인 것이기 때문이다.&lt;/strong&gt;&lt;br&gt;
장자크 루소의 &amp;#x3C;인간 불평등 기원론&gt;에서 주장하는 내용처럼 &lt;code class=&quot;language-text&quot;&gt;근현대의 노동자가 야만인보다 행복한 것이 사실일까?&lt;/code&gt; 라는 질문도 생각해봄직 하다.&lt;br&gt;
다른 사람이 나를 바라보는 시선, 나를 판단하는 그 잣대가 나에게 무슨 의미가 있을까 라는 생각이든다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;칭찬을 받으면 &lt;strong&gt;더 나아지는가?&lt;/strong&gt; 에메랄드가 칭찬을 받지 못한다고 더 나빠진다더냐?&quot;&lt;br&gt;
&quot;만일 청중이 한두 사람만 빼고는 모두 귀머거리라면 그들의 우렁찬 박수갈채를 받는다 해서 연주가가 기분이 좋을까?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위에서 말하는 지위와 명에, 부를 스스로 쫓고 있다고 느껴진다면 이 책을 통해 생각할 기회를 가져보는 것은 어떨까?&lt;br&gt;
난 뭔가를 얻기 위해 노력하고 성취하는 경험은 아주 좋은 행동이라고 생각한다. 하지만 스스로가 원하는 것이 아니라 단순히 누군가의 인정과 사랑을 받기 위한 노력은 아닌지 확인해보면 좋을 것이다.&lt;/p&gt;
&lt;h2 id=&quot;몰입&quot; style=&quot;position:relative;&quot;&gt;몰입&lt;a href=&quot;#%EB%AA%B0%EC%9E%85&quot; aria-label=&quot;몰입 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;이 &lt;a href=&quot;https://www.youtube.com/watch?v=TTk9Q5FyD3s&amp;#x26;ab_channel=%EC%A7%80%EC%8B%9D%EC%9D%B8%EC%82%AC%EC%9D%B4%EB%93%9C&quot;&gt;영상&lt;/a&gt;을 보고 책에 대해 알게되었다.&lt;br&gt;
책을 읽거나 업무를 할때 집중력을 금방 잃는다는 것을 느꼈다. 그리고 잠들기전에 유튜브 쇼츠를 보는 버릇이 생겼다.&lt;br&gt;
유튜브 쇼츠를 보는 것만으로도, 그저 평범한 영상임에도 불구하고 기분이 좋아지고 편안함을 느끼는 자신을 발견하고 놀랐다.&lt;br&gt;
쇼츠를 볼 때는 넋 놓고 있는 것처럼, 아무 생각없이 뇌를 뺀것처럼 손가락만 슥슥 움직이는 스스로를 보고 심각하구나라고 생각했다.&lt;br&gt;
이런 습관을 없애고 집중력을 높일 수 있는 방법이 있을까 궁금하여 읽게되었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;천재와 보통 사람 사이의 지적 능력 차이는 질보다는 양의 문제이다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저자는 천재와 보통 사람의 차이는 재능 문제가 아니라 생각을 하는 절대적인 양의 차이라고 강조한다.&lt;br&gt;
그리고 (노력하면) &lt;strong&gt;누구나&lt;/strong&gt; 몰입에 빠질 수 있고, 즐길 수 있으며, 몰입을 통하여 평소에는 겪어보지 못한 행복감을 느낌과 동시에 삶의 질이 상승한다고 말한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 499px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cf8fec45ebc0ebce486359766ed6eb98/5cb26/immersion.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 92.44444444444446%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB7lLRF0qzzDYH/8QAGBAAAgMAAAAAAAAAAAAAAAAAATEAAhD/2gAIAQEAAQUCMUCO2yq//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAFxAAAwEAAAAAAAAAAAAAAAAAACEwMf/aAAgBAQAGPwIWw//EABwQAQACAwADAAAAAAAAAAAAAAEAIRExQRCB4f/aAAgBAQABPyHobjkVPaJQphgv7N68UTEFu+zTP//aAAwDAQACAAMAAAAQN8g8/8QAFREBAQAAAAAAAAAAAAAAAAAAASD/2gAIAQMBAT8QI//EABURAQEAAAAAAAAAAAAAAAAAAAEg/9oACAECAQE/EGP/xAAdEAEAAgICAwAAAAAAAAAAAAABABEhMVGxQYGR/9oACAEBAAE/EGUFClBiFiLwFN8y4kGTiA3HSLFirYb31DIRtpVitSlk9p919z//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;immersion&quot;
        title=&quot;&quot;
        src=&quot;/static/cf8fec45ebc0ebce486359766ed6eb98/5cb26/immersion.jpg&quot;
        srcset=&quot;/static/cf8fec45ebc0ebce486359766ed6eb98/863e1/immersion.jpg 225w,
/static/cf8fec45ebc0ebce486359766ed6eb98/20e5d/immersion.jpg 450w,
/static/cf8fec45ebc0ebce486359766ed6eb98/5cb26/immersion.jpg 499w&quot;
        sizes=&quot;(max-width: 499px) 100vw, 499px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;위의 이미지처럼 실력과 과제의 난이도가 적당한 균형을 갖춰야 몰입에 이를 수 있다고 한다.&lt;br&gt;
몰입에 빠지면 수면 상태에서 고도로 활성화된 장기 기억을 활용하여 잠을 자면서도 문제를 풀려는 생각을 계속한다고 한다.&lt;br&gt;
절대적인 생각의 양을 늘려서 우연한 영감을 떠올리는 기회를 많이 만드는 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;세런디피티&lt;/strong&gt;는 완전한 우연으로부터 중대한 발견이나 발명이 이루어지는 것을 말하며 특히 과학연구의 분야에서 실험 도중에 실패해서 얻은 결과에서 중대한 발견 또는 발명을 하는 것을 말한다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;예전에 팀 프로젝트로 서비스 개발할 때 한 번 느껴본 것 같다. 잠자려고 누울때도 내일 뭐 개발할지 고민하고 걸을때도 문제에 대해 생각했었다. 그 이후로는 한 번도 경험해본 적 없다..&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;문제 해결을 목적으로 몰입을 시도할 경우에는 &lt;code class=&quot;language-text&quot;&gt;&quot;어떻게 하면 되는가?&quot;&lt;/code&gt;라는 물음보다는 &lt;code class=&quot;language-text&quot;&gt;&quot;왜 그렇게 되는가?&quot;&lt;/code&gt; 하는 물음이 훨씬 더 절실한 감정을 불러일으킨다.&lt;br&gt;
대체로 &lt;code class=&quot;language-text&quot;&gt;왜&lt;/code&gt;에 대한 답은 한 가지 원인으로 생각을 집중시켜서 수렴적 사고를 유도하지만, &lt;code class=&quot;language-text&quot;&gt;어떻게&lt;/code&gt;에 대한 답은 여러가지 가능성을 열어두어, 집중을 분산시키는 발산적 사고를 유도하는 경향이 있기 때문이다.&lt;br&gt;
초기에는 &lt;code class=&quot;language-text&quot;&gt;왜&lt;/code&gt;라는 형식의 물음으로 시작하여 &lt;code class=&quot;language-text&quot;&gt;어떻게&lt;/code&gt;라는 분산적 사고로 들어가야한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;위와 같이 사고에 대한 방법과 하루에 한 시간 운동 그리고 천천히 생각하기를 통하여 몰입에서 잘 나오고 지치지 않게 페이스를 조절해야 한다고 한다.&lt;br&gt;
이런 몰입을 하는 능력은 곧 경쟁력이다. &lt;strong&gt;깊이 생각하지 않고 주어진 일을 밤새워 열심히 하면 자신이 발전하기 보다는 소모된다는 느낌을 갖는다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;일상속에서 20분 생각하기 훈련을 해보자.&lt;/strong&gt;&lt;br&gt;
우리는 머리를 잠시도 비워두지 않는다. 항상 무엇인가를 생각하고 있다.&lt;br&gt;
사람은 한 시간에 2천 가지를 생각하고 하루 24시간 대략 5만 가지를 생각한다고 한다.&lt;br&gt;
그래서 &lt;code class=&quot;language-text&quot;&gt;오만가지 생각&lt;/code&gt;이라는 말이 생겼다. 그러나 이것은 &lt;strong&gt;상념에 해당하는 &apos;생각나기&apos; 이다.&lt;/strong&gt;&lt;br&gt;
이것은 내가 내 뇌의 주인이 되는 것이 아니고 의도되지 않은 상념이 자리를 차지하고 있는 것이다.&lt;br&gt;
자신이 뇌의 주인이 되어 문제에 대한 해결을 향한 체계적인 사고를 하는 &lt;strong&gt;&apos;생각하기&apos;&lt;/strong&gt; 를 해야 두뇌를 활용하고 즐거움을 얻을 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;생각하는 시간을 점진적으로 늘려가는 훈련과 천천히 생각하기 훈련을 통하여 몰입에 빠지거나 재몰입하기 쉽도록 해야한다.&lt;br&gt;
저자는 &lt;code class=&quot;language-text&quot;&gt;&quot;해야 할 일을 즐기며 행복하게 사는 방법. 나는 그 해답을 &apos;몰입&apos;에서 찾았다.&quot;&lt;/code&gt; 라고 한다.&lt;br&gt;
올해 연말회고에 몰입한 사례를 기록하기 위해 노력해봐야겠다.&lt;/p&gt;
&lt;h2 id=&quot;도둑맞은-집중력&quot; style=&quot;position:relative;&quot;&gt;도둑맞은 집중력&lt;a href=&quot;#%EB%8F%84%EB%91%91%EB%A7%9E%EC%9D%80-%EC%A7%91%EC%A4%91%EB%A0%A5&quot; aria-label=&quot;도둑맞은 집중력 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;몰입을 읽은 후에 집중력에 대한 관심이 커져서 읽어보았다.&lt;br&gt;
무엇인가에 깊게 집중하지 못하면 항상 &lt;code class=&quot;language-text&quot;&gt;&quot;왜 이렇게 집중이 안되지?&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;이 정도밖에 집중을 못하나?&quot;&lt;/code&gt; 라며 스스로를 탓했다.&lt;br&gt;
하지만 이 책에서는 스스로의 문제가 아니라 &lt;strong&gt;주변 환경&lt;/strong&gt;, &lt;strong&gt;시스템&lt;/strong&gt; 에 대해 지적한다.&lt;/p&gt;
&lt;p&gt;저자는 여러 심리학자, 연구원, 엔지니어 등과 나누었던 이야기들을 전달한다.&lt;/p&gt;
&lt;p&gt;그 중 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8A%A4%ED%82%A4%EB%84%88_%EC%83%81%EC%9E%90&quot;&gt;스키너 상자&lt;/a&gt;이야기가 인상 깊다.&lt;br&gt;
비둘기를 새장에 넣어 특정 행동을 하면 먹이를 주어 비둘기의 움직임을 제어하는 것이다. 이 강화 훈련을 통해 보상만 제대로 하면 많은 동물들이 우리가 선택한 행동들을 수행한다는 것이다.&lt;br&gt;
스키너는 이 원칙으로 인간의 행동들을 설명한다. 자신이 스스로 선택을 내린다고 확신하며 자유로운 존재라고 믿지만 다 환상이라고 주장한다. 우리의 집중력은 그동안 살면서 경험한 강화 훈련의 총합이라고 말이다.&lt;br&gt;
인스타그램의 설계자들은 이 강화 훈련을 인스타그램의 &apos;하트&apos;와 &apos;좋아요&apos;를 통해 수십억 사용자에게 적용했다.&lt;/p&gt;
&lt;p&gt;하지만 저자와 많은 미국 심리학자들은 이렇게 생각하지 않았다. 사람들의 집중력은 강화 훈련으로 생기는 것이 아니라 내면에 있는 &lt;strong&gt;몰입&lt;/strong&gt; 이라고 주장한다. (이전에 읽은 &quot;몰입&quot;의 내용과 비슷한 설명들을 한다.)&lt;br&gt;
이 몰입에 쉽게 빠져들기 위한 집중력을 강화하기 위해 수면, 음식, 긴 텍스트를 읽는 능력 등의 중요성을 설명한다.&lt;/p&gt;
&lt;p&gt;또한 저자가 강조하는 우리가 집중력이 떨어지는 이유 중 하나는 거대 테크 기업들의 비즈니스 모델 때문이라고 설명한다.&lt;br&gt;
조금 더 서비스를 오래 사용하도록 자극적이고 비관적인 내용을 노출시켜 사용자들의 &lt;a href=&quot;https://brunch.co.kr/@brilife78/204&quot;&gt;부정성 편향&lt;/a&gt;을 자극한다며 기업의 사회적 책임에 대해 이야기 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;나는 깊이 집중하는 능력이 식물과 같다고 생각한다. 우리의 집중력이 잘 자라서 잠재력을 온전히 피워내려면 특정 조건이 갖춰져야 한다.&quot;&lt;br&gt;
&quot;몰입을 느끼고, 책을 읽고, 자신이 집중하고 싶은 유의미한 활동을 찾고, 자기 삶을 이해할 수 있도록 생각이 배회할 공간을 마련하고, 신체 활동을 하고, 잘 자고, 뇌가 건강하게 발달할 수 있도록 영양가 있는 음식을 먹고, 안정감을 느껴야 한다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;항상 집중력이 낮은 스스로를 탓했었는데 항상 환경이나 시스템 탓을 할 순 없지만 주변 환경과 시스템에 대해 생각해본 계기가 됐다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[2023년 회고]]></title><description><![CDATA[들어가며 벌써 2023년이 끝난다. 2022년 회고에 이어 2023년 회고, 두 번째 회고를 작성해보려 한다. 많은 일들이 있었는데 찬찬히 정리해보자. 회사 적응기 2년 반정도 SI…]]></description><link>https://jdalma.github.io/2023y/retrospect/</link><guid isPermaLink="false">https://jdalma.github.io/2023y/retrospect/</guid><pubDate>Sun, 31 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;들어가며&quot; style=&quot;position:relative;&quot;&gt;들어가며&lt;a href=&quot;#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0&quot; aria-label=&quot;들어가며 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;벌써 2023년이 끝난다. &lt;a href=&quot;https://jdalma.github.io/2022y/yearend&quot;&gt;2022년 회고&lt;/a&gt;에 이어 2023년 회고, 두 번째 회고를 작성해보려 한다.&lt;br&gt;
많은 일들이 있었는데 찬찬히 정리해보자.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;회사-적응기&quot; style=&quot;position:relative;&quot;&gt;회사 적응기&lt;a href=&quot;#%ED%9A%8C%EC%82%AC-%EC%A0%81%EC%9D%91%EA%B8%B0&quot; aria-label=&quot;회사 적응기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;2년 반정도 SI 회사에서 근무한 습관은 금방 털어내고 회사에 금방 적응할 줄 알았지만, 쉽지 않았다. 가장 힘들었던 점은 &lt;code class=&quot;language-text&quot;&gt;일을 진행하면서 고민해야하는 범위&lt;/code&gt;이다.&lt;br&gt;
SI회사에서 업무를 진행할 때는 RFP가 존재하고, 요구사항을 충족시키기 위한 기술도 정해져있기 때문에 문제를 해결해야 할 영역이 매우 좁았다.&lt;br&gt;
되돌아보면 대부분 코드레벨의 구현 수준을 고민했었다. Java 8을 쓰니 Stream이나 Optional을 써볼까? 추상화는 어떻게 할까? SP를 애플리케이션 레벨로 어떻게 옮길까? 와 같은 고민들이다.&lt;/p&gt;
&lt;p&gt;하지만 이번에 이직한 회사는 분석 → 설계 → 개발의 사이클을 거치면서 &lt;strong&gt;목표와 문제만 주어지고 목표에 도달할 수 있는 많은 방법들을 프로젝트의 실무자들이 결정하여 주도한다.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;문제를 해결하기 위한 방법들을 다각도로 고민하여, 프로젝트의 이해관계자들을 설득하여 일을 진행하는 것&lt;/strong&gt; 이 핵심이다.&lt;br&gt;
(개발자는 코딩하는 시간이 대부분이라고 생각했었는데 실제로 개발하는 시간은 10~20% 밖에 되지 않았다.)&lt;/p&gt;
&lt;p&gt;올해 진행한 프로젝트는 5,6개 정도 되는데, 어떤 프로젝트는 수동적으로 답을 얻으러 다녔고 어떤 프로젝트는 능동적으로 진행하는 스스로를 볼 수 있었다.&lt;br&gt;
내가 업무를 처리하는 자세가 다른 이유는 &lt;code class=&quot;language-text&quot;&gt;업무에 흥미가 있냐 없냐&lt;/code&gt;로 구분되었다.&lt;br&gt;
어떤 문제를 기술적으로 해결할 떄는 흥미를 느꼈고, 비즈니스적으로 해결할 때는 흥미를 느끼지 못했다.&lt;/p&gt;
&lt;p&gt;진행하고 있는 프로젝트가 어떤 문제를 해결하려 하는지, 사용자에게 어떤 편의를 제공하는지, 기존에는 어떤 불편함이 있었는지 등을 공감하려 하지 않았었다.&lt;br&gt;
사용하는 기술이나 개발 방법에만 관심을 두고 비즈니스에 공감하려 하지 않아 설계 단계에서 분석으로 또는 개발 단계에서 설계 단계로 다시 돌아가는 경우도 있었다.&lt;/p&gt;
&lt;p&gt;SI에서 묻은 때를 벗기려 스스로의 습관이나 생각을 고치려고 노력을 많이 했으며 아직도 하고 있다.&lt;br&gt;
다른 분들의 피드백을 적극적으로 수용하려 했고 책에서도 많은 영감을 얻으려 노력했다. (이 계기로 책과 많이 친해진 것 같다.)&lt;br&gt;
많은 노력으로 연말 프로젝트 회고를 통해 처음 절반 프로젝트는 대실패였지만 뒤의 절반은 좋은 평가를 받았기에 어느 정도 개선된 것을 몸소 느꼈다.&lt;br&gt;
하지만 좋은 평가를 받은 프로젝트들은 R&amp;#x26;D 성향이 강했다. 간헐적으로 응답이 엄청 느리게 반환되는 문제를 분석하거나 특정 API의 개선 지점을 찾는 것과 같이 말이다.&lt;/p&gt;
&lt;p&gt;이제 프론트 팀, 데이터 팀, QA 팀, 기획 팀과 같이 협업을 진행해야는 프로젝트를 더 많이 진행할텐데, 올해는 능동적인 개발자가 되는것이 목표였다면 내년에는 &lt;strong&gt;소프트 스킬&lt;/strong&gt; 을 향상시키는 것이 목표이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;건강과-운동&quot; style=&quot;position:relative;&quot;&gt;건강과 운동&lt;a href=&quot;#%EA%B1%B4%EA%B0%95%EA%B3%BC-%EC%9A%B4%EB%8F%99&quot; aria-label=&quot;건강과 운동 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;5월달에 건강상의 문제로 3주 휴직 기간을 가졌다. (지금은 건강하다.)&lt;br&gt;
부작용으로 재입원도 하면서 대부분의 사람들은 경험하지 않을 경험을 하니 당시에는 슬럼프도 왔었다.&lt;br&gt;
다 접고 쉬고 싶다는 생각이 들었지만 이 슬럼프를 잘 넘기면서 조금 더 성장하지 않았나? 생각한다.&lt;/p&gt;
&lt;p&gt;나에게 이런 일이 생긴것은 굉장히 슬프지만 이 일이 나에게 긍정적인 효과를 끼치도록 만들려면 &lt;strong&gt;내가 어떤 행동으로 어떤 결과를 만드냐&lt;/strong&gt; 에 달려있다.&lt;br&gt;
스스로 불행하다고 생각하면서 퇴사하고 건강을 챙기지 않으면 이 일은 내 생에 최악의 일이였겠지만 퇴사는 하지않았고 런닝과 금연을 시작했다.&lt;/p&gt;
&lt;p&gt;올해 5월부터 런닝을 다시 시작하면서 10km 완주가 목표였었는데 총 4번의 10km 마라톤에 참가하여 완주했다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/19cd3/running.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 32.44444444444445%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABj0lEQVR42m2PzU4TcRTF/wtLaQJrTEPiwi2gJJhgfBjWGpX4AMWQEE3YARtfQBMJRhZAI9FIpCzYIJnaqQKdr84wU2lpGTrT0vn42U5i3HiSk3vPybk39wrNKVM6k7ho1rCaMq3OBQOYpoGiqkmfz+fJZDJMj99hduo+MxOTpG+lWX2zjN5QkE9UGtcGauMIUbDeIVsFar8VftS+UWtrKKbL7oHEp/1jZKPJ1tYOo6MjZB+MkX2Y5fajMVIjaRZWnlD2v1C1JKzGKUf2LmJbWUOyv2KeqxROP6DXyxQrLfaOdfa+a5SqbTY2PiKEYGhqiNS9FMN304l+8WqOw6v3mHaFknZAobKOKNuH2HWDqqOjOkVOqgatdoeO5+J718nLxaLE/PN5FnOL5F7nWHq5xNPHz9j8/BbHP+OyWceo/8K8/IkIg4jeTYDn+cmwVLGRNQfr3ELVddp9PwxD/ocojBJ6vkcQBIRBiIjjvhmF9Hq9JDS4zvU6dLtdrlyXm74fRVGyNKn97KD/qyEmjv/xD2tfjluo9YebAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;running&quot;
        title=&quot;&quot;
        src=&quot;/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/1cfc2/running.png&quot;
        srcset=&quot;/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/3684f/running.png 225w,
/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/fc2a6/running.png 450w,
/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/1cfc2/running.png 900w,
/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/21482/running.png 1350w,
/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/d61c2/running.png 1800w,
/static/1d2fd8d00104a1a2ad7a4cb7fd80e59a/19cd3/running.png 3129w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그리고 5월 ~ 12월까지 대략 350km 정도 달린 것 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5월: 35.56km&lt;/li&gt;
&lt;li&gt;6월: 30.18km&lt;/li&gt;
&lt;li&gt;7월: 45.63km&lt;/li&gt;
&lt;li&gt;8월: 33.65km&lt;/li&gt;
&lt;li&gt;9월: 61.11km&lt;/li&gt;
&lt;li&gt;10월: 62.78km&lt;/li&gt;
&lt;li&gt;11월: 35.72km&lt;/li&gt;
&lt;li&gt;12월: 55.59km&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스트레스 받을 때나 주말 아침에 달리고 오면 기분이 상쾌함을 느낀다. 몸은 힘들지만 에너지가 더 채워지는 느낌을 받는 것을 보면 나랑 잘 맞는 것 같다.&lt;br&gt;
내년 목표가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;10km에 대략 1시간 ~ 1시간 10분 정도 나오는데 50분대로 줄이기&lt;/li&gt;
&lt;li&gt;평소에는 5km만 달리는데 10km로 늘리기&lt;/li&gt;
&lt;li&gt;하프 마라톤&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 3가지를 지키기 위해 꾸준히 달려야겠다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;책-읽기와-스터디&quot; style=&quot;position:relative;&quot;&gt;책 읽기와 스터디&lt;a href=&quot;#%EC%B1%85-%EC%9D%BD%EA%B8%B0%EC%99%80-%EC%8A%A4%ED%84%B0%EB%94%94&quot; aria-label=&quot;책 읽기와 스터디 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;올해는 살면서 가장 많은 책을 읽은 해이다. 내적, 외적 동기부여가 골고루 갖춰진 환경이였어서 읽다보니 이렇게 된 것 같다.&lt;br&gt;
(책을 많이 읽었다고 해서 꼭 성장했다고 볼 순 없지만) &lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/&quot;&gt;총 18권의 책과 2편의 강의&lt;/a&gt;를 보았다.&lt;br&gt;
개발 관련은 10권, 개발 외적으로는 8권을 읽었다.&lt;/p&gt;
&lt;p&gt;개발 관련 책은 대부분 &lt;a href=&quot;https://www.codesoom.com/&quot;&gt;코드숨&lt;/a&gt; 스터디에서 읽었다.&lt;br&gt;
스터디는 총 8개 진행한 것 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98&quot;&gt;모던 자바 인 액션&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%BD%94%ED%8B%80%EB%A6%B0-%ED%95%B5%EC%8B%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot;&gt;코틀린 핵심 프로그래밍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%95%84%EB%A6%84%EB%8B%A4%EC%9B%80&quot;&gt;디자인패턴의 아름다움&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9C%BC%EB%A1%9C&quot;&gt;자바에서 코틀린으로&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%9C%EC%A0%95-4%ED%8C%90&quot;&gt;알고리즘 개정 4판&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%97%85%EB%AC%B4-%EC%8B%9C%EA%B0%81%ED%99%94&quot;&gt;업무 시각화&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%ED%95%9C-%EA%B6%8C%EC%9C%BC%EB%A1%9C-%EC%9D%BD%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0%EC%99%80-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot;&gt;한 권으로 읽는 컴퓨터 구조와 프로그래밍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#http-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C&quot;&gt;HTTP 완벽 가이드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개발과 무관한 책은 출퇴근 시간에 읽었다.&lt;br&gt;
출근에는 잠을 덜 깨서, 퇴근에는 진이 다 빠져서 출퇴근 시간에 책을 읽는 자체가 처음엔 힘들었다.&lt;br&gt;
진짜 읽고 싶은 책, 어떤 책이든 상관없이 습관을 들이는 목적으로만 골라서 그 환경에 책을 읽는 습관을 들이니 이제 어떤 책을 읽을지 고민할 정도로 수월해졌다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=y00tvum7g1I&amp;#x26;list=LL&amp;#x26;index=61&amp;#x26;ab_channel=%ED%95%9C%EB%88%88%EC%97%90%EB%B3%B4%EB%8A%94%EC%84%B8%EC%83%81%E2%80%93Kurzgesagt&quot;&gt;인생을 바꾸는 법 - 한 걸음씩 나아가기&lt;/a&gt; 이 영상이 루틴과 습관, 트리거에 대한 이해에 도움이 되었다.&lt;/p&gt;
&lt;p&gt;내년에는 20권 정도 읽었으면 좋겠다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;사내-스터디&quot; style=&quot;position:relative;&quot;&gt;사내 스터디&lt;a href=&quot;#%EC%82%AC%EB%82%B4-%EC%8A%A4%ED%84%B0%EB%94%94&quot; aria-label=&quot;사내 스터디 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;지금 회사의 메인 비즈니스는 특허 검색 서비스이며 해당 서비스에 엘라스틱서치를 사용하고 있다.&lt;br&gt;
(백엔드의 입장으로서는 Query DSL을 만드는 것이 다 이지만) 개발팀 회의를 진행할 때 마다 클러스터, 노드, 샤드, 인덱스, 형태소 분석기 등 모르는 용어들이 많아 데이터팀의 공유 사항을 이해하기가 힘들었다.&lt;br&gt;
그리고 키바나를 사용하거나 Query DSL을 작성할 때 매번 구글링하는 스스로가 너무 답답했다.&lt;/p&gt;
&lt;p&gt;이 이유로 &lt;a href=&quot;https://www.yes24.com/Product/Goods/119719070&quot;&gt;엘라스틱서치 바이블&lt;/a&gt; 스터디를 만들었다.&lt;br&gt;
이제 절반정도 도달했고 1월 말에 스터디가 끝날 예정이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e1b748dae2f9d896bbf7c6755f14a3dc/ae2f8/elasticStudy.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.111111111111114%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAArElEQVR42o2QCwuDMAyE/f+/ckNtq9a39qXtLRFlg21g4SPtkRzpZc4FtN0E3fZQVYPVODi/fUN982qxEFznxWJaDNU3PJvte4TWA4qyJMOaRAM+KaWfxHjW83692ceHgIyFvp8glMIwjAgkcsM/U3yCUwMbR4Rt4w0TfXmEVBWkrNBoDWPtYRyvTW6wR96QDD3lo+oOj7zEMxcohIKkLIWqj0wsZXeH1fojwxdxzoXfccYtoQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;elasticStudy&quot;
        title=&quot;&quot;
        src=&quot;/static/e1b748dae2f9d896bbf7c6755f14a3dc/1cfc2/elasticStudy.png&quot;
        srcset=&quot;/static/e1b748dae2f9d896bbf7c6755f14a3dc/3684f/elasticStudy.png 225w,
/static/e1b748dae2f9d896bbf7c6755f14a3dc/fc2a6/elasticStudy.png 450w,
/static/e1b748dae2f9d896bbf7c6755f14a3dc/1cfc2/elasticStudy.png 900w,
/static/e1b748dae2f9d896bbf7c6755f14a3dc/21482/elasticStudy.png 1350w,
/static/e1b748dae2f9d896bbf7c6755f14a3dc/d61c2/elasticStudy.png 1800w,
/static/e1b748dae2f9d896bbf7c6755f14a3dc/ae2f8/elasticStudy.png 2496w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;절반정도 읽었는데 굉장히 만족중이다. 데이터팀의 회의 내용을 이제 이해할 수 있게 되었고 학습하면서 실무에 어떻게 적용되어 있는지 비교할 수 있으니 이해가 더 잘되는 것 같다.&lt;br&gt;
다른 팀원들도 적극적으로 잘 참여해주셔서 재밌게 진행 중이다. 사내 스터디 덕분에 활기를 찾은 느낌이다.&lt;/p&gt;
&lt;p&gt;이번 스터디를 잘 마무리하고 다음 스터디도 꾸준히 진행하고 싶다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;칸반&quot; style=&quot;position:relative;&quot;&gt;칸반&lt;a href=&quot;#%EC%B9%B8%EB%B0%98&quot; aria-label=&quot;칸반 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/86627323?pid=123487&amp;#x26;cosemkid=go15803705973738109&amp;#x26;gad_source=1&amp;#x26;gclid=Cj0KCQiAv8SsBhC7ARIsALIkVT0tW1ZLWdfN7eGtAMkonCv92sKwKsSeSWkBgyT5x7g7HH8lVc5dyXYaAr_dEALw_wcB&quot;&gt;업무 시각화&lt;/a&gt;를 읽고 TODO 목록을 칸반으로 관리해보고 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2b3c89d38108da3b9870fb872f313c1b/afa26/todo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABdElEQVR42m2S227DIBBE/f/f17f2oW2c+IIxGAO+AJ4OJHVSqSshIxjO7s660pPGxBVjxHEc5wohIIYIbRXcYpFiglIjnLOYZ1O+ObJuNqbsU0qohJT4+vokdCqHBw/Jw2b5gJvLIDDwQT7vuhaDEATOT+C2YB5qBBaUUkSllELbNgUYwqNKCp3owRJRiwmtNDkTBOHGPBI/dHFfobsPajpCAyrDdpvmBmvts0J+F9XhYFY3NdgWg5gOyGGAYeIMSw9dDCtM/w7vfDmvRq3Q9T32fT+BOazq6VuEz0A/FaDW+pk4+8LYtx1mVMXLg5pK0ujccjbce5+lRWhUS0EoValxPE1/HVzxkIVM3TeCM8X7ShNUXy4FuixLAWapFdfioeAQRg7uP+C95QVT84bd2zvQcYL3oWj64M6WA/c5BIczyuFPm68R9g1K1Od95fkL3G5X3K51GY4cJbZ1pWd30/u+gyTw/Dfp6+/KFa/UCjGUfb7/ARZruzaooj6jAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;todo&quot;
        title=&quot;&quot;
        src=&quot;/static/2b3c89d38108da3b9870fb872f313c1b/1cfc2/todo.png&quot;
        srcset=&quot;/static/2b3c89d38108da3b9870fb872f313c1b/3684f/todo.png 225w,
/static/2b3c89d38108da3b9870fb872f313c1b/fc2a6/todo.png 450w,
/static/2b3c89d38108da3b9870fb872f313c1b/1cfc2/todo.png 900w,
/static/2b3c89d38108da3b9870fb872f313c1b/afa26/todo.png 1258w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이전에는 온전히 기억력으로만 관리하던 것을 시각화하니 기억에 대한 부담이 덜하여 까먹는 일이 줄었고, 새로운 것을 추가해야 할 때 우선순위를 비교해볼 수 있고 어떤 것이 더 중요한지 결정하는 것이 편하다.&lt;br&gt;
그리고 완료 상태로 넘길 때의 뿌듯함도 있다.&lt;/p&gt;
&lt;p&gt;내년 회고를 작성할 때까지 꾸준히 사용하는 것이 목표다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;총평&quot; style=&quot;position:relative;&quot;&gt;총평&lt;a href=&quot;#%EC%B4%9D%ED%8F%89&quot; aria-label=&quot;총평 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;2022년 회고에 작성했던 올해 목표들이다.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 블로그 꾸준히 운영하기, 학습 블로그와 기술 블로그 분리하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 인프런 사놓은 강의 보기
&lt;ul&gt;
&lt;li&gt;2편 밖에 못 봤으니.. 이루지 못 했다고 보자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 책 10권 읽기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 다른 개발자들과 교류하기
&lt;ul&gt;
&lt;li&gt;넥스트스텝 코틀린 교육의 오프라인 모임에 참석 그리고 코드숨 연말 회고에 참석&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 관심가는 스터디에 참여하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 사람들 앞에서 기술 발표 해보기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 개발자, 한 달에 책 한 권 읽기 참여하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;총 7개 중에 4개면 절반 넘게 성공했으니 나름 만족한다.&lt;/p&gt;
&lt;h3&gt;2024년 목표&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;하프 마라톤 완주하기&lt;/li&gt;
&lt;li&gt;책 20권 읽기&lt;/li&gt;
&lt;li&gt;인프런 강의 5편 이상 보기&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&gt;SI 회사를 퇴사하고 서비스 회사를 1년 다니면서 느낀 점은 SI 회사에 비해 업무 자체가 개발자답다.&lt;br&gt;
문제를 해결하기 위해 여러 방법과 기술들을 고민하고 클러스터를 직접 구성하여 운영하면서 발생하는 문제들을 해결하기 위해 여러 방안들을 고민하는 것들이 새로웠다.&lt;br&gt;
그만큼 많은 기술들이 사용되고 있고 배워야 할 기술들은 산더미이다.&lt;br&gt;
단순히 코더처럼 일하는 것이 아니라 문제를 해결하기 위한 여러 방안들을 모색하고 타협점을 찾아 결정하는 것 그리고 누군가를 설득하고 일의 진행 순서와 우선순위를 스스로 결정하며 진행하는 것이 재밌다고 느꼈다.&lt;br&gt;
이제 공부 주제의 우선순위도 실무에 맞닿아 있는 것들을 위주로 학습하려고 한다.&lt;/p&gt;
&lt;p&gt;오프라인 마라톤 대회 출발 초반에 다른 사람들이 나를 추월하는 것을 보기 힘들어 스스로의 페이스를 유지하기가 힘들었다.&lt;br&gt;
주변 사람들이 빠르게 뛰니 나도 덩달아 &lt;code class=&quot;language-text&quot;&gt;빨리 뛰어야 하나?&lt;/code&gt; 라는 생각에 페이스를 잃은 경우가 있다.&lt;br&gt;
하지만 &lt;code class=&quot;language-text&quot;&gt;와 저 속도로 뛴다고?&lt;/code&gt;하는 사람들 절반 이상은 결국 따라 잡는다. 앞에서 걷거나 속도가 현저히 줄어들었기 때문이다.&lt;br&gt;
나머지 소수는 진짜 끝까지 일정한 속도로 뛰는 그 사람들의 페이스였다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;마치 날씨가 너무 좋은 날 경치가 아름다운 길을 돌아보지 않고 바삐 지나치는 것이 그 시간에 대한 모욕인 것 처럼,&lt;br&gt;
기껏 수능 시험을 얼마나 잘 보았나, 혹은 어떤 명문 대학에 입학했는가를 자랑할 정도라면 그것은 그보다 흥미로운 지적 체험이 없었다는 자기 고백일 뿐이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;공부란 무엇인가?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;주변 사람들의 속도에 연연하지 말고 나만의 페이스로 주변 경치도 보면서 꾸준히 뛰는 것이 정답이다. &lt;a href=&quot;https://orbi.kr/00015929827&quot;&gt;연세대 교수가 말하는 3수&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;경치나 아름다운 길을 돌아보지 않고 앞만 보고 달리는 사람, 목적에 심취하여 과정에서 달콤함을 찾지 못하는 사람이 되지 않도록 의식하자.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[NEXTSTEP 클린코드 with Kotlin 회고]]></title><description><![CDATA[TDD, 클린 코드 with Kotlin 이 교육은 코틀린과 클린코드(OOP, 컨벤션), TDD를 가르쳐준다. 하지만 나는 코틀린과 클린코드만 배우기에도 벅차다고 생각하여 TDD…]]></description><link>https://jdalma.github.io/2023y/kotlinCleanCode/</link><guid isPermaLink="false">https://jdalma.github.io/2023y/kotlinCleanCode/</guid><pubDate>Sun, 10 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;tdd-클린-코드-with-kotlin&quot; style=&quot;position:relative;&quot;&gt;TDD, 클린 코드 with Kotlin&lt;a href=&quot;#tdd-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C-with-kotlin&quot; aria-label=&quot;tdd 클린 코드 with kotlin permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이 교육은 코틀린과 클린코드(OOP, 컨벤션), TDD를 가르쳐준다.&lt;br&gt;
하지만 나는 코틀린과 클린코드만 배우기에도 벅차다고 생각하여 TDD는 신경쓰지 않았다 ㅎㅎ&lt;br&gt;
구현해야하는 미션 게임들을 보면 단순하다고 생각하여 쉽게 생각할 수도 있겠다.&lt;br&gt;
하지만 &lt;strong&gt;단순히 돌아가는 구현과 OOP를 고민하며 구현하는 것은 하늘과 땅 차이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;스스로 &lt;code class=&quot;language-text&quot;&gt;&quot;OOP가 무엇인지 대충 알고있다&quot;&lt;/code&gt; 라는 생각을 가지고 있었는데 &lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%97%98%EB%A0%88%EA%B0%95%ED%8A%B8-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8&quot;&gt;엘레강트 오브젝트&lt;/a&gt;를 같이 읽으면서 교육을 진행하다보니 OOP에 대해 굉장히 무지했다는 것을 깨달았다. (&lt;a href=&quot;https://namu.wiki/w/%EB%8D%94%EB%8B%9D%20%ED%81%AC%EB%A3%A8%EA%B1%B0%20%ED%9A%A8%EA%B3%BC#s-2&quot;&gt;더닝 크루거&lt;/a&gt;가 떠올랐다.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이따금 클래스를 “객체 템플릿”으로 부르는 것(예를 들면 위키피디아에서 그렇게 하고 있다)을 듣곤 한다.&lt;br&gt;
이 같은 정의는 정확하지 않은데, 이 정의에 따르면 클래스는 수동적인 위치에 있기 때문이다.&lt;br&gt;
이 정의는 누군가가 템플릿을 가지고 그것을 사용해 객체를 만들어낸다고 가정한다. 그럴 수도 있지만 엄밀히 말하자면 개념적으로 틀린 말이다.&lt;br&gt;
생성된 객체는 스스로 동작한다. 자신을 누가 만들었고 클래스에 형제 자매가 얼마나 더 있는지 알아서는 안 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;클래스와 객체를 이야기할 때 대부분 &lt;code class=&quot;language-text&quot;&gt;&quot;붕어빵&quot;&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;&quot;붕어빵을 찍어낼 수 있는 틀&quot;&lt;/code&gt; 정도로 설명한다.&lt;br&gt;
수동적이라는 인식이 큰데, 객체지향 프로그래밍에서 객체는 완전히 살아있는 유기체라고 생각해야 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;알고리즘&lt;/strong&gt; 과 &lt;strong&gt;실행&lt;/strong&gt; 대신 &lt;strong&gt;객체&lt;/strong&gt; 와 &lt;strong&gt;행동&lt;/strong&gt; 의 관점에서 사고해야한다.&lt;br&gt;
절차적인 프로그래밍과 OOP의 중요한 차이점은 &lt;strong&gt;책임을 지는 주체가 무엇인가&lt;/strong&gt; 이다.&lt;br&gt;
우리는 그저 누가 누구인지만 정의하고 객체들이 필요할 때 &lt;strong&gt;스스로&lt;/strong&gt; 상호작용하도록 제어를 위임하는 것이다.&lt;br&gt;
객체는 &lt;strong&gt;살아있는 유기체&lt;/strong&gt; 이며, 다른 유기체들과 &lt;strong&gt;의사소통&lt;/strong&gt; 하면서 그들의 작업을 지원하고, 다른 유기체들 역시 이 객체에게 도움을 받는다.&lt;br&gt;
이렇게 각 객체들은 서로를 필요로 하기 때문에 &lt;strong&gt;결합된다는 것&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;객체는 어떻게 분리하고 협력하는지, 문맥은 어떻게 구성할지, 상태는 어디까지 공유하고 생명주기는 어디까지 해야할지 등 많은 고민들을 자동차 경주, 계산기, 로또, 블랙잭, 지뢰찾기 구현 미션들을 통해 고민해볼 수 있었다.&lt;br&gt;
그리고 각 미션마다 강조하는 디자인 패턴이나 핵심 지점들이 단계적으로 제공되었어서 보람도 단계적으로 느낄 수 있었던 것 같다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;추상화를 통해 의미있는 의존성이 분리되었는지, 테스트가 가능해졌는지, 코드나 가독성이 좋아졌는지 등의 고민을 하면서 현준님만의 기준을 찾아보셔도 좋을거 같습니다!&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이번 교육과 엘레강트 오브젝트 책을 통해 OOP에 대한 주관적인 기준이 생겼고 OOP에 대한 무지함을 깨달았으며, 본격적인 OOP를 공부할 준비가 된 것 같다는 느낌이 든다.&lt;br&gt;
완벽한 설계는 없으니 완벽하게 구현하려 욕심부리지말고 스스로 정도를 정해가면서 꾸준한 리팩토링을 통해 개선해나가는 것이 효과적이다라는 것을 느꼈다.&lt;/p&gt;
&lt;p&gt;개발하다보면 다른 사람이 작성한 코드를 보고 평가하면서 오만한 자세를 가지기 쉬운데 겸손해지는 빠른 방법은 이런 교육을 통해 리뷰를 받아보는것이지 않을까 싶다.&lt;br&gt;
이 교육은 금액도 꽤 나가는데 미션이 힘든만큼 완주하지 못하는 교육생들도 많다. 이번 기수 교육생이 총 66명인데, 지뢰찾기 게임까지 완료한 사람이 20명도 안돼 보인다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;한 모듈의 간단한 수정이 이와 관계없는 모듈을 통해 시스템 전역으로 퍼져 나가거나 다른 곳의 무언가를 깨뜨리는 경우&lt;/li&gt;
&lt;li&gt;수정하는 부분이 시스템에 어떤 영향을 미칠지 몰라 코드의 수정이 두려운 경우&lt;/li&gt;
&lt;li&gt;변경 사항에 어디가 영향을 받는지 파악하고 있는 사람이 없어서 결국 모든 사람이 참석해야 하는 회의&lt;/li&gt;
&lt;li&gt;서비스 레이어에 모든 비즈니스 로직을 절차지향적으로 작성하거나 연쇄 메서드 호출이 습관인 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 경우에 해당한다면 이 교육을 추천하고 싶다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;자동차-경주&quot; style=&quot;position:relative;&quot;&gt;자동차 경주&lt;a href=&quot;#%EC%9E%90%EB%8F%99%EC%B0%A8-%EA%B2%BD%EC%A3%BC&quot; aria-label=&quot;자동차 경주 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).
pobi,crong,honux
시도할 횟수는 몇 회인가요?
5

실행 결과
pobi : -
crong : -
honux : -

pobi : --
crong : -
honux : --

pobi : ---
crong : --
honux : ---

pobi : ----
crong : ---
honux : ----

pobi : -----
crong : ----
honux : -----

pobi, honux가 최종 우승했습니다.&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;strong&gt;1단계&lt;/strong&gt;: 환경 세팅&lt;br&gt;
&lt;strong&gt;2단계&lt;/strong&gt;: 문자열 계산기 &lt;a href=&quot;https://github.com/next-step/kotlin-racingcar/pull/1315&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3단계&lt;/strong&gt;: 자동차 경주 구현 &lt;a href=&quot;https://github.com/next-step/kotlin-racingcar/pull/1381&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4단계&lt;/strong&gt;: 자동차 경주 우승자 기능 추가 &lt;a href=&quot;https://github.com/next-step/kotlin-racingcar/pull/1440&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;5단계&lt;/strong&gt;: 자동차 경주 리팩토링 &lt;a href=&quot;https://github.com/next-step/kotlin-racingcar/pull/1473&quot;&gt;리뷰&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;자동차 경주는 우테캠 프리코스에서 한 번 해본 경험도 있었고 기능 자체는 간단하여 쉬울 줄 알았지만.. 여전히 책임을 나누는 것이 쉽지 않았다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;자동차 경주 첫 구현&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d6e13609130319cdba415623b94b5f35/63908/racingCarInit.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlUlEQVR42oWS626cMBCFeY7d5WJzW1hYwgLhYm62AQNOyG6SX63U93+MTDZVVbVSa32y5sfMOccjK5eipNPSUU4YD5LUdf2qZQ0TlI9lVSdZUbW87lmSPmYFKfup5xNlQ9120UOiVHQcXn+0y9as8qEkvh+WVBC+tnQkTZ+TftnexHJtOk6FnLdv43pbnja53fKyVgxkm7ZvWS7Ctqah3V7b7VVNRwhZ/ikAzmF0OoWeF0APxhbGtqohA1mAoukYmUeYhLumIxsFH0WaFY7r8+VlWK/TvAB3kTQvSoiTZjm6SyiagXVka7pl2l7FFsJly0SS5Y7jz9fvVL6LRc7yOQjjmgn29D6sL8M0u15wUA1FVY0vDgd9t1P3Bx2ZjnM8uUff8wPb8W3Hw6ajGyYAUb8K3cAQWQmjOHks4JCmKSoSX5IwSoZ1mxd59ALQgngAbAG4F/gXSlW38voGKyVtf44u4TnWQf5zHzY4/N76Nwo4Cvm83V4ZG6L4AsE+5X9a4f8MJ3ndjJJ0Q9Mx+Crwwj+y/WP4A/QMblLkjM7cAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;racingCarInit&quot;
        title=&quot;&quot;
        src=&quot;/static/d6e13609130319cdba415623b94b5f35/1cfc2/racingCarInit.png&quot;
        srcset=&quot;/static/d6e13609130319cdba415623b94b5f35/3684f/racingCarInit.png 225w,
/static/d6e13609130319cdba415623b94b5f35/fc2a6/racingCarInit.png 450w,
/static/d6e13609130319cdba415623b94b5f35/1cfc2/racingCarInit.png 900w,
/static/d6e13609130319cdba415623b94b5f35/21482/racingCarInit.png 1350w,
/static/d6e13609130319cdba415623b94b5f35/d61c2/racingCarInit.png 1800w,
/static/d6e13609130319cdba415623b94b5f35/63908/racingCarInit.png 2410w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;자동차 경주 마지막 구현&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/91b1079b44ceaac4371eace9d0cfa0b4/cb88c/racingCar.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAIAAACZeshMAAAACXBIWXMAAAsTAAALEwEAmpwYAAACEUlEQVR42pVT2ZKbMBDkNzY292VsYw4JCWTA4IP7MOvd8lv+/zMyxqlNKg+uTdWUHkT3qKen4TwcZsfL8VzaO3fJS6KkCOJ3i9s6XhRnJIpX1nYmq/9BxmQf7VPG4iCgurHiBfk1Abo/60Huh3tR9sN0G6cPeHyxFPmXZAA8C2RyK83QNUMzLE03ZUVTVQNOUVR4XlryDwRoWc5QuBFE1XZ818MooIa55pr+vemvWX7ML23Rf9bDrerGKIpRCFac2D5xEGKHfH/IaJJatlO1Y9lOzTDhgHJhlHqY+pgQdkBhQvcpYcnORTs/cANmu8i0trZPLNt1UKBbG0oZCSIUhNZmx8FuJFmD0zTX683O97HjeIZhwXigVhRlWVZ0fWWYFjgCGLBqwYuAh3E4SXkwN7ZXjvf6eq+athuvh/y0FGRFNfKyPfX3crjVbVfWzaWskkMGj2n6Cr5y0ODHQtSMdXgoWFZEcZpkR4Tp29sSQDhK0f6EaIJJ6GESUAYTfS2Mg92apgWqnm7Pt8rWwXGawQ0vSIIgzc7LXxv6E5JzMxXtte3HuhvAwOdKYHbKYlUzH5l55EH9Jx6/ydXwXveT6yEQCQW9Vd3My+5Udds57a/imZd9UdX5qQhIiANirbeKZhT1UFSd7XjQ60Xauer2M+8+yvGzv75348TidCZ878egWUWSM9hDKAtZDOl7LfVv8i/J7aL+UZ8+2QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;racingCar&quot;
        title=&quot;&quot;
        src=&quot;/static/91b1079b44ceaac4371eace9d0cfa0b4/1cfc2/racingCar.png&quot;
        srcset=&quot;/static/91b1079b44ceaac4371eace9d0cfa0b4/3684f/racingCar.png 225w,
/static/91b1079b44ceaac4371eace9d0cfa0b4/fc2a6/racingCar.png 450w,
/static/91b1079b44ceaac4371eace9d0cfa0b4/1cfc2/racingCar.png 900w,
/static/91b1079b44ceaac4371eace9d0cfa0b4/21482/racingCar.png 1350w,
/static/91b1079b44ceaac4371eace9d0cfa0b4/d61c2/racingCar.png 1800w,
/static/91b1079b44ceaac4371eace9d0cfa0b4/cb88c/racingCar.png 2728w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;처음 작성한 것에 비해 미션이 끝날 때의 (기능이 추가되긴 했지만) 다이어그램을 보면 책임이 더 분리된 것을 확인할 수 있다.&lt;br&gt;
이번 미션의 핵심은 &lt;strong&gt;랜덤 기능&lt;/strong&gt; 을 최대한 밖으로 끌어내어 주입할 지점을 찾고 &lt;strong&gt;I/O 기능과 비즈니스를 분리하며 개발하는 것&lt;/strong&gt;
이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;코틀린 클래스는 프로퍼티, 초기화 블록, 부 생성자, 함수, 동반 객체 순으로 작성하는 것이 컨벤션이다.&lt;/li&gt;
&lt;li&gt;계층간 &quot;결과 객체&quot;인 DTO를 추가하여 결합도를 낮출 수 있었다.&lt;/li&gt;
&lt;li&gt;비즈니스 로직과 IO 로직이 혼재해 있었는데, 중간 DTO를 추가하니 책임과 역할이 더 잘 보였다.&lt;/li&gt;
&lt;li&gt;DIP를 잘 지켜내면서 개발하면 테스트가 쉽다.&lt;/li&gt;
&lt;li&gt;랜덤 기능과 같이 테스트가 불가능한 기능은 최대한 외부로 밀어내라. 또한 추상화를 통해 가짜 객체를 주입하여 테스트 할 수 있도록 작성해라.&lt;/li&gt;
&lt;li&gt;예측하지 못하거나, 비정상적인 오류인 경우에는 예외로 처리하지만 그 외는 null 또는 예외 타입으로 표현할 수 있도록 해라.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;You Aren&apos;t Gonna Need It&quot;&lt;/code&gt; 지금 당장 필요하지 않은 설계나 기능은 작성하지 마라.&lt;/li&gt;
&lt;li&gt;data class는 equals와 hashCode, copy가 필요하다면 사용하는 것이다. 기능이 있다고 이상하게 생각하지마라.&lt;/li&gt;
&lt;li&gt;스마트 캐스트를 의식하라.&lt;/li&gt;
&lt;li&gt;클래스 이름이 Order라면 orderShip()보다는 짧게 ship()이라고 하면 클라이언트에서는 order.ship()라고 호출하며, 간결한 호출의 표현이 된다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remotty.github.io/blog/2014/03/01/hyogwajeogin-ireumjisgi/&quot;&gt;효과적인 이름짓기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;코틀린은 기본적으로 프로퍼티 기반인 것을 명심해라.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;어떤 경우에는 과하게 분리하여 불필요한 주입을 받도록 작성하기도 하고, 분리하여 주입이 필요한 부분을 알아차리지 못하고 많은 책임을 가지도록 작성하기도 했다.&lt;br&gt;
특히 검증하는 부분을 외부에서 주입받도록 계속 작성한 것 같다.. 책임이 적절히 잘 나눠진 클래스는 테스트하기가 굉장히 쉬웠다.&lt;br&gt;
대표적으로 &lt;code class=&quot;language-text&quot;&gt;RandomRacingRule&lt;/code&gt;이 잘 나눠졌다고 생각한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;추상화된 게임 규칙 구현 클래스&lt;/li&gt;
&lt;li&gt;추상화된 랜덤 번호 생성기 주입받아 위임&lt;/li&gt;
&lt;li&gt;게임 규칙의 조건에 필요한 정보를 주입받아 활용&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이와 같이 적절하게 분리되어 있어 &lt;strong&gt;랜덤 기능&lt;/strong&gt; 과 &lt;strong&gt;게임 규칙 조건&lt;/strong&gt; 에 대한 테스트 작성이 수월했다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;응집도가 높고 견고한 클래스에는 적은 수의 메서드와 상대적으로 더 많은 수의 생성자가 존재한다.&quot;&lt;br&gt;
&quot;생성자의 주된 임무는 제공된 인자를 사용해서 캡슐화된 프로퍼티를 초기화하는 것이고, 메서드의 수가 많을수록 SRP을 위반할 확률이 높지만 생성자는 많을수록 클라이언트가 유연하게 사용할 수 있다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h1 id=&quot;문자열-덧셈-계산기와-로또&quot; style=&quot;position:relative;&quot;&gt;문자열 덧셈 계산기와 로또&lt;a href=&quot;#%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%8D%A7%EC%85%88-%EA%B3%84%EC%82%B0%EA%B8%B0%EC%99%80-%EB%A1%9C%EB%98%90&quot; aria-label=&quot;문자열 덧셈 계산기와 로또 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이번 미션은 4단계로 이루어져 있다.&lt;/p&gt;
&lt;h3&gt;1단계. 문자열 덧셈 계산기&lt;/h3&gt;
&lt;ol class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 쉼표 또는 콜론을 구분자로 가지는 문자열을 입력하는 경우 구분자를 기준으로 분리하여 숫자들의 합을 반환
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 공백이라면 0을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 문자열 앞부분의 &quot;//&quot;와 &quot;\n&quot; 사이에 위치하는 문자를 커스텀 구분자로 사용한다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 문자열 계산기에 숫자 이외의 값 또는 음수가 입력된 경우 &quot;런타임 예외&quot;를 던진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/de60ac1a374b7b20de785a420274a56c/65d79/stringAddCalculator.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 86.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAC9ElEQVR42o1U2ZLaMBDkP5bDBhsDPrgWjDl8H/gAcyzsQ1KV//+KzoyypJZNUpWHLlnyaEbq7lGj3eniK5otCcZ4gm3gIk0zJEmGbJ8jz0usnDXFyPjbPkbj60KH8EIJ7fkCWZ4jqd9RHGpESYr6coUfxqLg/yeUeujIPSiKBn1sYWSN0e+P0NdGMK0JhroFRR2g1ZZFYkarLYn5R0L56WdbojmtNSmo2e7QaTsiUO6qUPpDzF9tLG0Htr2GNtBFIZXWe3QAuaugoU+W2Lo+4jhBREiIszhOBZIsw3qzhWlYCMIAXlQQl3tUhyMOx/pjPCKlOD8IYI2naKyXNvZlibS+oaKgMIoRUmLG8XxBQkLMzDHSOIZfvSE/XpDmBXZ+gPXOE3C2LgI6wJR4bzizOWJS0K3uCMor3PwEd3+GW1Cy4ogVBS+ocr3PEJ6/I77+QHX7huJ8p4MckBUVUkJ1umC2sNHo9lSo2hCKpqPHXJAAAsQNk8/caf0BZvNXmDMb1tyBPnNg0LggHhnL1Qa2s4E2NNBoEeEsCqMjdSGRwhKPrDaRLMkKifNLwcHQxGhkwDJNGIYJjQ7C+15e2gL83WCbNJsSrOkUyT4RRDOXGfNUldiGIdoUqGkDEi1DlPP1rjhd78KbrPDDbsI2/MFGnhNPrLBfneEdKkpYwi9KrMNIVB4SDXnkw68p0eGGIKXOqWr0yTqfjf5sbFGlh95AFeZ+VOYNQ7rePgoRUufsT++kdAmPukb9uPZTQl5QVA2GrkPXTeqIMQyyik7+GwxGIrDb62MymVH3zKGOxuTNMXGqU2HlufXElZsdrBZLlGWO8nTG2/2Oy9sNb7d3xMQTC8MJufVYCO4Ii0aNDtH62ssd8brImJoWVmTSlZdg5wXYuAGNoRiZCr5B7O0QpSmJdsKpPtMrlFLL9f+8cvuTdVrc7NzXj2+2heBQR0HJYhIkJfALlFdH0cv/FuUf4NdEUYdYb30EbgjH2eLVXtHc+/3yPBL+BBR9Ey/hy0OQAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;stringAddCalculator&quot;
        title=&quot;&quot;
        src=&quot;/static/de60ac1a374b7b20de785a420274a56c/1cfc2/stringAddCalculator.png&quot;
        srcset=&quot;/static/de60ac1a374b7b20de785a420274a56c/3684f/stringAddCalculator.png 225w,
/static/de60ac1a374b7b20de785a420274a56c/fc2a6/stringAddCalculator.png 450w,
/static/de60ac1a374b7b20de785a420274a56c/1cfc2/stringAddCalculator.png 900w,
/static/de60ac1a374b7b20de785a420274a56c/65d79/stringAddCalculator.png 905w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;TYPE&gt; -&gt; List&amp;lt;TYPE&gt;&lt;/code&gt;을 반환하는 Spliterator 인터페이스&lt;/li&gt;
&lt;li&gt;기본 구분자와 커스텀 구분자 기준으로 분리하는 (Spliterator를 구현하는) &lt;code class=&quot;language-text&quot;&gt;유틸리티 StringSpliterator&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;피연산자&lt;/code&gt; 를 값 클래스로 래핑&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;피연산자&lt;/code&gt; 검증 인터페이스&lt;/li&gt;
&lt;li&gt;검증 인터페이스를 구현한 &lt;code class=&quot;language-text&quot;&gt;음수 검증기&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;검증기를 주입받는 &lt;code class=&quot;language-text&quot;&gt;문자열 계산기&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;StringSpliterator를 유틸리티로 사용하였는데 유틸리티 클래스는 유익하지 않으니 문자열 계산기에 검증기와 같이 주입하는 것도 괜찮을 것 같다.&lt;/p&gt;
&lt;h3&gt;로또&lt;/h3&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;구입금액을 입력해 주세요.
14000

수동으로 구매할 로또 수를 입력해 주세요.
3

수동으로 구매할 번호를 입력해 주세요.
8, 21, 23, 41, 42, 43
3, 5, 11, 16, 32, 38
7, 11, 16, 35, 36, 44

수동으로 3장, 자동으로 11개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[23, 25, 33, 36, 39, 41]
[1, 3, 5, 14, 22, 45]
[5, 9, 38, 41, 43, 44]
[2, 8, 9, 18, 19, 21]
[13, 14, 18, 21, 23, 35]
[17, 21, 29, 37, 42, 45]
[3, 8, 27, 30, 35, 44]

지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6
보너스 볼을 입력해 주세요.
7

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
5개 일치, 보너스 볼 일치(30000000원) - 0개
6개 일치 (2000000000원)- 0개
총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;로또 구현 목록&lt;/h3&gt;
&lt;ol class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 구입금액을 입력 받는다.
&lt;ul&gt;
&lt;li&gt;정수만 입력 가능하다.&lt;/li&gt;
&lt;li&gt;잘못된 입력 또는 천원 단위가 아니라면 IllegalArgumentException을 던진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 구입금액의 1000원 단위로 로또를 구매한다.
&lt;ul&gt;
&lt;li&gt;로또 번호는 1부터 45까지 존재한다.&lt;/li&gt;
&lt;li&gt;6개의 번호를 선택하여 1개의 로또를 구매한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 구매한 로또를 출력한다.
&lt;ul&gt;
&lt;li&gt;&quot;[%d, %d, ..]&quot; 형식으로 출력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 지난 주 담청 번호를 입력 받는다.
&lt;ul&gt;
&lt;li&gt;&quot;%d, %d, %d&quot; 형식으로 입력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 입력 받은 당첨 번호 기준으로 당첨 금액과 당첨된 로또의 개수를 출력한다.
&lt;ul&gt;
&lt;li&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
6개 일치 (2000000000원)- 0개&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 수익률을 계산하여 출력한다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 보너스 볼을 입력받는다.
&lt;ul&gt;
&lt;li&gt;정수 한 자리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 당첨 결과에서 보너스 볼에 대한 결과도 출력한다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 수동으로 구매할 로또 수를 입력 받는다.
&lt;ul&gt;
&lt;li&gt;수동으로 구매할 로또 수는 입력 받은 구입 금액을 넘을 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 수동으로 구매할 수만큼 로또 번호를 입력 받는다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 로또를 자동과 수동을 구분해서 구매한다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 로또 결과를 자동과 수동을 구분하여 출력하고 수익률을 함께 계산한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;2단계&lt;/strong&gt;: 로또(자동) &lt;a href=&quot;https://github.com/next-step/kotlin-lotto/pull/845&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3단계&lt;/strong&gt;: 로또(2등) &lt;a href=&quot;https://github.com/next-step/kotlin-lotto/pull/900&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4단계&lt;/strong&gt;: 로또(수동) &lt;a href=&quot;https://github.com/next-step/kotlin-racingcar/pull/1440&quot;&gt;리뷰&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/55e7ef8968b0ed0cd07b9342c4f97eab/bb2ee/lotto.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.33333333333334%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACI0lEQVR42nVSWa6bQBDkGjEMgxf2xezGGDDgRVaiKH+5/00qNQhHfpHyUZph6K5eqjTH2sPdO7B5SrlDqhs4bXR4uoAlLGwNCambf2Gt30kQY7gM8A4uzI2AyVid0OpjgcfzB2IGOAw+Jym6vMQ5zRGyUBEfcalb9PUZXXVCfcyQuD6ulxHX5oLY8XHOGB+niI45tDLJ0HRXRFECnxWG4Y5hfmK8TohDkjU9btMd3aVf3tqmReSH6PsZfccO3QBpnKFW+STVUsdDGUTIbBeWuYVDUpud7hR4D/k2piXmukG6OyBj1x076ThJxXHjnY2IULEb5mihkKi4t9LgHrhDnbBJUpsSrRCI5BZl3aFse+xZtO5G3H/9xvz6jqEfkOUFCq5jb3sQzNMUiUFBDGu3ECpYxIFECxgkWHlDIQyirFoM0xMdx6/yCjr/GYx552rvyxtyPcV6hhwlInKOV3I9Jy9ARSESNqH+eSTbfcR/IRTryLrcY7O+KaEsdp9QvJxqFkWJlA5wWECaagrGqEa2B0gW+UKoquXcXUUUUtKTAgXtNL1+YrxR/XGGTwFL2mh6vDDdn7jNd5wokNqjKqp9dqd2FpnWgoDwKVhGW9TdjIoJBX1oc9y2GTBND/TTDTMRsDthHTjJPx0a68jvU3Dckh5rxxtcP8I3CqArAZhoLkLuFxKHxcX/RPnseEvEyou0RE8fVtFx8ekCEit7eby7ygkr4R8c0GLDZOu08QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;lotto&quot;
        title=&quot;&quot;
        src=&quot;/static/55e7ef8968b0ed0cd07b9342c4f97eab/1cfc2/lotto.png&quot;
        srcset=&quot;/static/55e7ef8968b0ed0cd07b9342c4f97eab/3684f/lotto.png 225w,
/static/55e7ef8968b0ed0cd07b9342c4f97eab/fc2a6/lotto.png 450w,
/static/55e7ef8968b0ed0cd07b9342c4f97eab/1cfc2/lotto.png 900w,
/static/55e7ef8968b0ed0cd07b9342c4f97eab/21482/lotto.png 1350w,
/static/55e7ef8968b0ed0cd07b9342c4f97eab/d61c2/lotto.png 1800w,
/static/55e7ef8968b0ed0cd07b9342c4f97eab/bb2ee/lotto.png 4522w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;일련의 과정은 위와 같다.&lt;br&gt;
중요한 점은 각 과정 사이에서 결합도를 낮추고 있는 &lt;strong&gt;1번&lt;/strong&gt; 과 &lt;strong&gt;3번&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1번&lt;/strong&gt; 은 구매자와 로또 가게에 대한 결합도를 낮추고 있다. &lt;code class=&quot;language-text&quot;&gt;LottoShop&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;LottoPurchase&lt;/code&gt;에 대해서만 의존한다.&lt;br&gt;
&lt;strong&gt;3번&lt;/strong&gt; 은 로또 구매와 로또 당첨금 확인 및 통계에 대한 결합도를 낮추고 있다. &lt;code class=&quot;language-text&quot;&gt;LottoMachine&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;LottoWinningNumber&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;LottoWinningResult&lt;/code&gt;에 대해서만 의존한다.&lt;br&gt;
이렇게 중간 DTO를 잘 활용하면 책임과 역할을 잘 구분할 수 있다.&lt;/p&gt;
&lt;p&gt;로또를 구현하면서 놓쳤던 부분들이 있다.&lt;/p&gt;
&lt;h4&gt;로또 당첨 등수를 결정하는 함수에 대한 책임&lt;/h4&gt;
&lt;p&gt;2등은 보너스 볼이 맞았는지 확인해야 하는 것처럼 다른 등수를 확인하는 방법과 다르다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;LottoRank&lt;/code&gt; 내부에서 직접 확인하는 것으로 구현했지만 아래와 같은 피드백이 왔다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;어떤 랭크인지에 대한 판단을 지금 상위객체(LottoRank)에서 직접 평가하고 있어요.&quot;&lt;br&gt;
&quot;이렇게되면 상위 객체에서 하위 인스턴스를 모두 파악해야하고 하나의 함수에서 여러 인스턴스 타입에 대해 알고 종속되버리기에 유 연하지 못한 코드가 됩니다.&quot;&lt;br&gt;
&quot;실제로 지금 이 로직에서 또 여러 조건들의 로또들이 추가되거나 랭크가 기획이 추가되면 로직 변경이 까다로워지겠죠?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;LottoRank&lt;/code&gt;는 enum이니 유틸리티 클래스처럼 생각하여 직접 확인하여도 무방하다고 생각했다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;열거타입은 결국 하나의 최상위 불변 인터페이스이고, 하위 속성들이 이 인터페이스를 구현한 구현체 인스턴스라고 할 수 있습니다&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;LottoRank&lt;/code&gt;의 하위 속성들이 직접 당첨이 되었는지 각자 확인하도록 책임을 전가해야 했다. 이 생각을 하지 못한 이유가 열거타입과 하위 속성을 분리해서 생각해본적이 없었기 때문이다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;typealias MatchedPredicate = (LottoWinningResult) -&amp;gt; Boolean

enum class LottoRank(val prize: Int, val matchedPredicate: MatchedPredicate ) {
   FIRST(2_000_000_000) { it.matchCount == 6 },
   SECOND(30_000_000) { it.matchCount == 5 &amp;amp;&amp;amp; it.isBonus == true }, 
   ...

    companion object {
        fun valueOf(lottoWinningResult: LottoWinningResult): LottoRank =
            entries.find { it.matchPredicate(lottoWinningResult) } ?: MISS
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;Lotto를 이루는 일급 컬렉션의 네이밍과 역할에 대한 결정&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AS-IS&lt;/th&gt;
&lt;th&gt;TO-BE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;로또 번호&lt;/td&gt;
&lt;td&gt;LottoNumber&lt;/td&gt;
&lt;td&gt;LottoNumber&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로또 (한 줄)&lt;/td&gt;
&lt;td&gt;LottoLine&lt;/td&gt;
&lt;td&gt;Lotto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;여러 개의 로또&lt;/td&gt;
&lt;td&gt;Lotto&lt;/td&gt;
&lt;td&gt;Lottos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;// AS-IS
Lotto(
   lines: List&amp;lt;LottoLine&amp;gt;
)
LottoLine(
   numbers: List&amp;lt;LottoNumber&amp;gt;
)

// TO-BE
Lottos(
   auto: List&amp;lt;Lotto&amp;gt;,
   manual: List&amp;lt;Lotto&amp;gt;
)
Lotto(
   numbers: List&amp;lt;LottoNumber&amp;gt;
)&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;리뷰어님과 서로 생각하는 단위가 달라서 아마 리뷰어님이 이해하기 힘드셨지 않을까 싶다..&lt;br&gt;
나는 &lt;code class=&quot;language-text&quot;&gt;&quot;한 개의 로또 라인이 여러 개로 이루어져 한 개의 로또를 구성한다&quot;&lt;/code&gt;라는 것이고, 리뷰어님은 &lt;code class=&quot;language-text&quot;&gt;&quot;한 개의 로또가 여러 로또를 구성한다.&quot;&lt;/code&gt; 라는 것이다.&lt;br&gt;
나름 이유를 대자면 실제로 한 개의 로또에 5개까지 구매할 수 있으니 여기서는 5개라는 제한이 없고 그냥 &lt;code class=&quot;language-text&quot;&gt;&quot;복수의 LottoLine은 5개를 초과하여도 Lotto를 구성한다&quot;&lt;/code&gt;라는 생각이였다.&lt;/p&gt;
&lt;p&gt;하지만 이렇게보니 리뷰어님의 제안이 더 이해하기 쉬운 것 같다.&lt;/p&gt;
&lt;h3&gt;플라이웨이트 패턴&lt;/h3&gt;
&lt;p&gt;로또를 구매할 때 마다 항상 새로운 &lt;code class=&quot;language-text&quot;&gt;LottoNumber&lt;/code&gt;를 생성하였다.&lt;br&gt;
하지만 피드백을 통해 모든 로또는 똑같은 &lt;code class=&quot;language-text&quot;&gt;LottoNumber&lt;/code&gt;를 보유하도록 미리 초기화를 해놓았다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@JvmInline
value class LottoNumber private constructor(
    private val number: Int
) {
   companion object {
      private val LOTTO_NUMBER_RANGE = IntRange(1, 45)
      private val LOTTO_NUMBERS: Map&amp;lt;Int, LottoNumber&amp;gt; = LOTTO_NUMBER_RANGE.associateWith(::LottoNumber)

      fun from(value: Int): LottoNumber = LOTTO_NUMBERS[value] 
         ?: throw IllegalArgumentException(&amp;quot;[입력:$value] 1에서 45사이의 정수만 허용됩니다.&amp;quot;)

      fun random() : LottoNumber = from(LOTTO_NUMBER_RANGE.random())
   }
   ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;일급 객체인 로또 번호를 &lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-Flyweight-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90&quot;&gt;플라이웨이트 패턴&lt;/a&gt;을 적용하여 재활용을 통한 메모리 사용을 최적화할 수 있다.&lt;/p&gt;
&lt;h3&gt;그 외&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/CS-%F0%9F%91%A8%E2%80%8D%F0%9F%92%BB-%EC%9D%BC%EA%B8%89-%EA%B0%9D%EC%B2%B4first-class-object&quot;&gt;일급 객체&lt;/a&gt;와 추상화 사이에서의 고민&lt;/li&gt;
&lt;li&gt;JvmInline과 value class를 활용한 최적화&lt;/li&gt;
&lt;li&gt;리스코프 치환 원칙을 위반한 예제&lt;/li&gt;
&lt;li&gt;상속이 문제가 아니라 &lt;strong&gt;상속을 잘 활용하는 것이 문제&lt;/strong&gt; 다.&lt;/li&gt;
&lt;li&gt;코틀린에서 Int와 Integer를 처리하는 방법과 Int의 캐싱 범위&lt;/li&gt;
&lt;li&gt;백킹 프로퍼티를 활용하여 객체 내부에서는 Mutable하게 외부로는 Immutable하게 사용하고, 방어적 복사를 적용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;List(size: Int, init: (index: Int) -&gt; T)&lt;/code&gt; 같은 가짜 생성자나 생성자를 여러 개 선언하는 것은 클래스를 유연하게 사용할 수 있도록 도와준다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;by&lt;/code&gt; 키워드를 통해 다른 클래스의 기능을 편리하게 위임할 수 있는 장점이 있지만 모든 기능이 열리는 단점도 있다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=dJ5C4qRqAgA&amp;#x26;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&quot;&gt;우아한객체지향 by 조영호&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/9879422&quot;&gt;Spring Batch를 더 우아하게 사용하기 - Spring Batch Plus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/type-safe-builders.html&quot;&gt;type safe builders&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h1 id=&quot;블랙잭&quot; style=&quot;position:relative;&quot;&gt;블랙잭&lt;a href=&quot;#%EB%B8%94%EB%9E%99%EC%9E%AD&quot; aria-label=&quot;블랙잭 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)
pobi,jason

pobi의 배팅 금액은?
10000

jason의 배팅 금액은?
20000

딜러와 pobi, jason에게 2장의 나누었습니다.
딜러: 3다이아몬드
pobi카드: 2하트, 8스페이드
jason카드: 7클로버, K스페이드

pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)
y
pobi카드: 2하트, 8스페이드, A클로버
pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)
n
jason은 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)
n
jason카드: 7클로버, K스페이드

딜러는 16이하라 한장의 카드를 더 받았습니다.

딜러 카드: 3다이아몬드, 9클로버, 8다이아몬드 - 결과: 20
pobi카드: 2하트, 8스페이드, A클로버 - 결과: 21
jason카드: 7클로버, K스페이드 - 결과: 17

## 최종 수익
딜러: 10000
pobi: 10000 
jason: -20000&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;블랙잭 구현 목록&lt;/h3&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 게임에 참여할 사람의 이름을 입력받는다.
&lt;ul&gt;
&lt;li&gt;쉼표 기준으로 분리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 플레이어에게 각각 2장의 카드를 나눠준다.
&lt;ul&gt;
&lt;li&gt;플레이어들에게 나눠준 카드를 &lt;code class=&quot;language-text&quot;&gt;{이름} 카드: {카드}, {카드}, ...&lt;/code&gt; 형식으로 출력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 각 플레이어들은 가진 카드의 합이 21을 초과하지 않는다면 카드를 계속 뽑을 수 있다.
&lt;ul&gt;
&lt;li&gt;카드를 뽑는다면 결과를 &lt;code class=&quot;language-text&quot;&gt;{이름} 카드: {카드}, {카드}, ...&lt;/code&gt; 형식으로 출력한다.&lt;/li&gt;
&lt;li&gt;카드를 뽑는 순서는 앞의 사용자가 끝나야 다음 사용자가 뽑을 수 있다.&lt;/li&gt;
&lt;li&gt;21이 초과한 상태에서 뽑는다면 다음 사용자로 넘어가도록 할 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 사용자들이 모든 결정을 하였다면 결과를 출력한다.
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;{이름} 카드: {카드}, {카드}, ... - 결과: {합계}&lt;/code&gt; 형식으로 출력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 딜러도 게임에 참여하며 딜러의 점수가 16이하이면 한 장의 카드를 추가로 받는다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 딜러가 21을 초과하면 플레이어가 가지고 있는 패에 상관없이 승리한다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 게임을 완료한 후 각 플레이어별로 승패를 출력한다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 게임 참가자들의 베팅 금액을 입력받는다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 블랙잭이라면 배팅 금액의 1.5배를 받고 나머지는 승패에 따라 수익을 계산한다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 최종 수익에서 베팅 결과를 출력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1단계&lt;/strong&gt;: 코틀린 DSL &lt;a href=&quot;https://github.com/next-step/kotlin-blackjack/pull/592&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2단계&lt;/strong&gt;: 블랙잭 &lt;a href=&quot;https://github.com/next-step/kotlin-blackjack/pull/620&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3단계&lt;/strong&gt;: 블랙잭(딜러 역할 추가) &lt;a href=&quot;https://github.com/next-step/kotlin-blackjack/pull/635&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4단계&lt;/strong&gt;: 블랙잭(베팅 기능 추가) &lt;a href=&quot;https://github.com/next-step/kotlin-blackjack/pull/660&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;5단계&lt;/strong&gt;: 상태 추상화 &lt;a href=&quot;https://github.com/jdalma/kotlin-blackjack/tree/refactor/src/main/kotlin/blackjack/state&quot;&gt;코드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이번 미션의 핵심은 블랙잭 게임을 진행하는 동안 &lt;strong&gt;게임 상태를 어떻게 객체지향적으로 설계하는 것&lt;/strong&gt; 이다.&lt;br&gt;
일단 내가 설계한 흐름을 보면&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1e3edb0e371e6de04e4e28ba045bd793/9bbea/blackjack.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACnUlEQVR42m1T2ZaiUAz0M+yWfVOQRQQBF1A2UbR1znTPy/z/f9QUHGd5mIccuLk3lUolmUiyBpE2k3V+dQzn0SQVsmbieqrxOF9Q7g5IwwjlIUe13SNdhbD5Zi0pWMkqtHcBQRhjIjBYo8MWFWiChBkfSYpBIygBs3CDKM7gBmsczg9UH59oLj12RQmFb31ZgUMM/TegzwyxoqLWdbSeh63nY0mfS8DBIkGEyEQig1UmMCwHhrngvzFWNpVISNFhEtAfARkkqgaMxRLbQ4E9yzEYqAzMCZJKMlSVASOQDZ2mqCY0fT4CywQziPEH0ONBoHlBhOP5hryqcaJm2twZGRS2Q1+Hormgpn+T7RGsExQdyz/3aK83NFUDayb+C6jDsmyEUYpsk+EWhsh0A7Eso1cpvL+G5/gIHBeBtcA2SlA1d5THBnV+RLs/YC68AFeGOQLGboC2uaK/3PE8n9E3LZ7MnKosR5ThihLmZJEKAhySmFPDwa+8CVBZ7nQ6w2oArNL9iByzm9XtB5rbd1xvD3S0e39Hblo4Ll3cgwAP38PZD5CycQvDIhETQRAiSbdY2EuEY1Mo+MDQZCM86uYtnDG7QZ+t6ah5bouhtAodZ/BRlvg6nhDzjUqmeXVFe/uGhNp6q+ilIcX3VjGKtkfVdrAIMsyjwrthzgJjjnBuY88GtZaF0jRRU9uScmVkH7s+HBI7bdK/gA6d6T5Htj1wJEzMBBkyhz3jfcWS2l2OngzveYEqSRExwSnJ0O0L9PkJ3XaHn3WNicMteSOgQVYhH8S7HcJNAtsL4C49LHknD0M9MObADzb8a685HbZFHVaXzYm4mpOIJZlkk3grXDhbz+sHPvsez+6Kr+6CgFq9D+v42vn/mUS9p+/iuHq/AAJ+js8tBR/rAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;blackjack&quot;
        title=&quot;&quot;
        src=&quot;/static/1e3edb0e371e6de04e4e28ba045bd793/1cfc2/blackjack.png&quot;
        srcset=&quot;/static/1e3edb0e371e6de04e4e28ba045bd793/3684f/blackjack.png 225w,
/static/1e3edb0e371e6de04e4e28ba045bd793/fc2a6/blackjack.png 450w,
/static/1e3edb0e371e6de04e4e28ba045bd793/1cfc2/blackjack.png 900w,
/static/1e3edb0e371e6de04e4e28ba045bd793/21482/blackjack.png 1350w,
/static/1e3edb0e371e6de04e4e28ba045bd793/d61c2/blackjack.png 1800w,
/static/1e3edb0e371e6de04e4e28ba045bd793/9bbea/blackjack.png 3329w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 802px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/26a3d35b385604fc6f14ab53bbe3abf7/5a6dd/blackjack2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABXElEQVR42o1S2XKDMBDL//9e+9CGG1IM2JirzBQIqKvNkLyGGc1i49VKMhdrHeq6xk9Zoqxq1I2VdQNjKkXf97jf73j3uaRZhq/vK8IoQhCEiihOdB0nCYIwRJ4XaKyFc63Ul4C6aXT4OI4vwq7rQZXed+B73w9yYNLqvddvVoisc9pcCVkjLoiTkL3HcTwIz49sbIWgbVtR4jBOE/Z9F7s7jn2Vo/t7lsvSqHxTMTOjlZMfqr0oshg7g22dsawblmXBtm34k7osK9Z11b2nwjTNkChSzYq5JUmKLMt1PwwjFLdSsoz1/ePzE7FkfDpxUtvWqxslzIubNNyUgAdJyCF5nuMaBDqoEtVKIGCGJJqmX/iuUxeTxPO0HFONKIviVBVVEnJpaNtJFEYts5EEwzAo+Cvx0lg7AW///LUuhSikVapinpzI2+bNEVTDTNlEhfM8ayMzI2j1zI+E/xkmqbm3lm5sAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;blackjack2&quot;
        title=&quot;&quot;
        src=&quot;/static/26a3d35b385604fc6f14ab53bbe3abf7/5a6dd/blackjack2.png&quot;
        srcset=&quot;/static/26a3d35b385604fc6f14ab53bbe3abf7/3684f/blackjack2.png 225w,
/static/26a3d35b385604fc6f14ab53bbe3abf7/fc2a6/blackjack2.png 450w,
/static/26a3d35b385604fc6f14ab53bbe3abf7/5a6dd/blackjack2.png 802w&quot;
        sizes=&quot;(max-width: 802px) 100vw, 802px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1번 블랙잭 게임 준비&lt;/strong&gt; 는 카드 셔플 전략과 셔플 전략을 사용하여 카드를 분배하는 딜러, 딜러를 가지는 블랙잭 게임과 게임 플레이어들을 생성하는 책임들을 가진다. 생성한 블랙잭 게임을 통해 게임을 진행한다.&lt;br&gt;
&lt;strong&gt;2번 블랙잭 게임 진행&lt;/strong&gt; 은 딜러와 입출력 전략을 받아 뷰와 도메인을 연결해주는 역할과 게임을 진행하는 역할을 동시에 가진다.&lt;br&gt;
&lt;strong&gt;3번 게임 진행 중&lt;/strong&gt; 은 플레이어가 카드를 계속 받는지에 대한 입,출력과 비즈니스 로직을 빈번하게 넘나들어야만 했기에 꼬리 재귀를 통해 공유되는 문맥(스택)의 정보를 최소화하여 반복적으로 받도록 하였고 비즈니스 로직에서 입,출력에 대한 구현체를 주입받아 문제를 해결했다.&lt;br&gt;
&lt;strong&gt;4번과 5번에서 게임이 끝난 참가자들을 승패, 수익률을 계산&lt;/strong&gt; 한다.&lt;/p&gt;
&lt;p&gt;플레이어가 &lt;strong&gt;게임 진행 중인 상태&lt;/strong&gt; 는 GameParticipant 추상 클래스를 구현하는 Player와 Dealer로 표현하였고,&lt;br&gt;
&lt;strong&gt;게임이 끝난 상태&lt;/strong&gt; 를 GameParticipants 클래스로 표현하였고,&lt;br&gt;
&lt;strong&gt;게임 결과를 계산한 상태&lt;/strong&gt; 를 GameParticipantPlayerResult 클래스로 표현하였다.&lt;br&gt;
그리고 게임 결과를 계산할 때 점수 비교는 아래와 같이 분기문으로 해결하였다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class GameParticipantDealer(
   name: String = NAME,
   cards: List&amp;lt;Card&amp;gt; = emptyList(),
   betAmount: Int = 0
) : GameParticipant(name, cards, betAmount) {

   fun compareScore(player: GameParticipantPlayer): MatchResult {
      val playerScore = player.getScore()
      val dealerScore = this.getScore()
      return if (player.isBust || this.isBlackjack()) MatchResult.LOSS
      else if (player.isBlackjack()) MatchResult.BLACKJACK
      else if (this.isBust) MatchResult.WIN
      else if (dealerScore &amp;lt; playerScore) MatchResult.WIN
      else if (playerScore &amp;lt; dealerScore) MatchResult.LOSS
      else MatchResult.DRAW
   }

   ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위에서 나열한 상태와 점수 비교를 &lt;strong&gt;객체지향의 다형성을 통하여 해결할 수 있는 방법이 이번 과제의 핵심이다.&lt;/strong&gt;&lt;br&gt;
하지만 분기문이 항상 나쁘다고 봐야할지는 생각해봐야 할 문제다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt; 게임 결과를 판단할 때 조건문의 순서에 많이 의존되는데.. 다른 작업자가 볼때 이 의도를 파악하기 힘들 것 같다고 생각됐습니다.&lt;br&gt;
테스트 코드로 설명을 대체할 수 있을 것 같기도 한데 설계의 방법으로 순서에 대한 강조를 어떻게 표현할 수 있을까요??&lt;br&gt;
&lt;strong&gt;A&lt;/strong&gt; 순서에 관해서는 저도 고민을 많이 해봤는데 블랙잭의 승패를 정하는 룰 자체가 이미 너무 길기 때문에 이를 다른 방식으로 표현하거나 쪼개서 표현하는 것 자체가 더 알아보기 어려운 것 같더라구요.&lt;br&gt;
순서가 존재 + 두 객체를 비교하는 경우에는 그냥 한 메서드에 로직을 쭉 나열하는 것보다 알기 쉬운 방법이 없더라구요&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;리뷰어님의 의견에 동의하기도 한다. 항상 디자인 패턴과 추상화가 옳다고 말할 수 있는지는 모르겠다.&lt;br&gt;
하지만 학습에 중점을 두어야하니 &lt;strong&gt;상태를 다형성으로 해결하는 방법을 알아보자&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;객체지향의 다형성을 이용해 조건문 줄이기&lt;/h3&gt;
&lt;p&gt;기존에는 상태가 서로 다른 클래스로 표현되며 점수 비교는 여러 분기문으로 작성되어 있었다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d7769b287cd7cce58011cef606fd3c26/bb072/statePattern.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 138.66666666666669%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAIAAADuuAg3AAAACXBIWXMAAAsTAAALEwEAmpwYAAADvUlEQVR42oVVh5baSBDc7zDKCWWEECItCOWIhIiLfX7+/7+4EvIm1ud7rx+I0dT0dFVX80TRHIKkWF6QdM3QRqORNR6NxpqmD2WVpLh+wx/jqf8iSEY3xmGS+c2uObRte0yybLl6xvr/gRmeICjDmlXXH0n7T1bsgjhtDqcgSgcETTP838B4TZKMZthBXq2TXZCk7mIZJfny2SNIFm//C/+EagmCGZA0QTEkzdKoXR0yvIAHiuGwgvVvA5ogaex8BEuSbDj2ZOpM3ZlljTleJEkWPIni0LQs05m4s/nUnVuWLcvaI9iezNLjodgfTufrrtkrqgGSECNrmlR1VO+yoiiqXZoX8+Xqgb8nZ76prr+2u1tS1HFWKJqJ6w0GpDXdlNefUXMrqm59f7x4fgj+el1/gxmWU+2NbLi4v6aP+lWaZhfrqDi9BPW1qI/rLais58s1MjOswPLSG2EMzas8GkQ3RUlRNEMcqhTFrr34cLkmxSHKd14Q5lWz9nwgZUXXDQsPd6kolhU1mhviN8D9kXjmBcGwXHO8kGQVJzruYmxPKZq9B8fdk7+DIaYgyhTNM9yQE1VOUElGZKQRLZoUK3UrosrwMtmdzrKc+ADmREl23NUmzKIoWj2vFUWdPwd+3P2EkmBkHRZhFKZpFgShquoPYCXI9vH+XO3P0Ma0nHR/jZtz3Z7Wngdd8uOLX1/z5hzGiTGyH8BqWrVle0nLQ5xlY3tWHS95fcmqGmzNltv2egurW7I7Xm7fHXf+CYya7am73fiuu3Sm86GswVib9dadLy3bUTXTdWeOM7cmAC7RTp8zizJ4Hio6J0h9J4AYTuiEgMLokG+vJvs2oLDyiW2Wk3oPYbcEzVVNVg1VN83RWFF1TItOpNc9H9mWGUZAHlgCL3DqeOIGYbSNq6SsfB/shhNniuTY8+ZQ+JlleIXmZcjIS90VWEEhGcFdReXLz7h5yYoqzkvMhsVqjduiPT6COUjfHcHitjxKRfEDgpouvOJ0y5tT3R6iNEfY09kfM8t9SyJ6nnrr4SzYG/p1Vnl9i8+/gYGUZVUDUWPX2/rQBc0EIYj7ubwg36lh32um7gxDFdgNSZ69IMrKqDwkaRpGSZJmGCa9mQ1zjEZ8B9OchOQIUAXaYAM/a9Ljj2R3SssK1ZZ1u1htOtl5ibtb4sPofZ9sv9nyojJtb1lzadojwH4Y247b90zv5E9D/y1AxmBABXmTX36Vh+/H86U9X8umBfjrDH/6Oo1Rj6zin8eCB7swLHNki0Pl6+j9F9ZqJUV94GWEAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;statePattern&quot;
        title=&quot;&quot;
        src=&quot;/static/d7769b287cd7cce58011cef606fd3c26/1cfc2/statePattern.png&quot;
        srcset=&quot;/static/d7769b287cd7cce58011cef606fd3c26/3684f/statePattern.png 225w,
/static/d7769b287cd7cce58011cef606fd3c26/fc2a6/statePattern.png 450w,
/static/d7769b287cd7cce58011cef606fd3c26/1cfc2/statePattern.png 900w,
/static/d7769b287cd7cce58011cef606fd3c26/bb072/statePattern.png 1318w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;게임 내 규칙을 객체로 추상화&lt;/strong&gt; 하였다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface State {
   fun draw(card: PlayingCard): State
   fun stay(): State
   fun profit(money: Int): Double
}

abstract class Finished : State {

   protected abstract val rate: Double

   override fun draw(card: PlayingCard): State {
      throw IllegalStateException()
   }

   override fun stay(): State {
      throw IllegalStateException()
   }

   override fun profit(money: Int): Double = money * rate
}

abstract class Started : State {

   override fun profit(money: Int): Double {
      throw IllegalStateException()
   }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;클라이언트는 블랙잭 게임 상태의 자세한 구현을 전혀 알 필요가 없다.&lt;br&gt;
클라이언트는 사용자가 &lt;code class=&quot;language-text&quot;&gt;draw&lt;/code&gt;나 &lt;code class=&quot;language-text&quot;&gt;stay&lt;/code&gt;를 선택하기만 하면 내부에서 상태는 자동으로 계산된다.&lt;br&gt;
상태 전이표(행에서 열로 전이)는 아래와 같다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Start&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Hit&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Stay&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Blackjack&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Bust&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Start&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Hit&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Stay&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Blackjack&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Bust&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol&gt;
&lt;li&gt;시작(Start): 카드 2장을 받고 있는 상태 (2장을 다 받으면 다음 상태로 바로 전이한다.)&lt;/li&gt;
&lt;li&gt;힛(Hit): 처음 2장의 상태에서 카드를 더 뽑는 것&lt;/li&gt;
&lt;li&gt;스테이(Stay): 카드를 더 뽑지 않고 차례를 마치는 것&lt;/li&gt;
&lt;li&gt;블랙잭(Blackjack): 처음 두 장의 카드 합이 21인 경우, 베팅 금액의 1.5배&lt;/li&gt;
&lt;li&gt;버스트(Bust): 카드 총합이 21을 넘는 경우. 배당금을 잃는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Started&lt;/code&gt; 추상 클래스를 구현하는 Start와 Hit는 수익률을 계산할 수 없는 &lt;strong&gt;게임 진행 중인 상태&lt;/strong&gt; 를 표현한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;Finished&lt;/code&gt; 추상 클래스를 구현하는 Stay와 Bust, Blackjack은 카드를 더 받거나 받지않는다는 선택을 할 수 없는 상태이다. &lt;strong&gt;게임이 끝난 상태&lt;/strong&gt; 를 표현한다.&lt;br&gt;
이 두 개의 추상 클래스를 아우르는 &lt;code class=&quot;language-text&quot;&gt;State&lt;/code&gt; 인터페이스가 해당 타입들을 추상화하였다.&lt;/p&gt;
&lt;p&gt;이 구현에서 두 개의 디자인 패턴이 활용되었는데 내부에서 상태를 변경하는 &lt;strong&gt;상태 패턴&lt;/strong&gt; 과 &lt;code class=&quot;language-text&quot;&gt;draw&lt;/code&gt;,&lt;code class=&quot;language-text&quot;&gt;stay&lt;/code&gt;,&lt;code class=&quot;language-text&quot;&gt;profit&lt;/code&gt;에 대한 중복되는 처리를 &lt;strong&gt;템플릿 메서드 패턴&lt;/strong&gt; 으로 해결하였다.&lt;/p&gt;
&lt;h3&gt;그 외&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;수신 객체 지정 람다를 이용한 Kotlin DSL&lt;/li&gt;
&lt;li&gt;Builder들의 책임과 비즈니스 로직에서 사용할 값 객체의 책임
&lt;ul&gt;
&lt;li&gt;1단계 예제에서 PersonBuilder 내부 필드를 한 번에 초기화 하는 것이였는데 Skill과 Language 빌더들이 너무 더럽다고 느꼈지만 제이슨님은 PersonBuilder 자체가 더러움을 책임지는 객체라고 생각하셨다.&lt;/li&gt;
&lt;li&gt;비즈니스 로직에서 관심가지는 것은 값 객체에 대한 정보(프로퍼티)이기 때문에 각 data class들이 불변 필드들을 가질 수 있도록 각 Builder이 가변 필드들을 소유하는것은 문제가 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모든 플레이어들이 카드를 요청하여 받는 부분에서 &lt;code class=&quot;language-text&quot;&gt;출력 → 입력 → 카드 배분 → 출력&lt;/code&gt; 흐름을 따르는 부분이 입출력 로직과 비즈니스 로직을 문맥에 맞게 넘나들도록 해야하기 때문에 힘들었다.
&lt;ul&gt;
&lt;li&gt;딜러가 추가되면서 카드 배분 받는 로직은 달라지지만 출력되는 부분은 같은 게임 플레이어로 인식해야하는 점&lt;/li&gt;
&lt;li&gt;핵심은 딜러가 추가되면서 기존 플레이어와 중복되는 코드를 어떻게 제거할 것인가? abstract class? interface? sealed class? sealed interface? 이 방법들의 차이는 무엇이고 어떤 기준으로 사용하는가?&lt;/li&gt;
&lt;li&gt;sealed class와 interface는 내부 라이브러리를 개발하는 상황과 같이 확장을 제한할 떄는 유용하지만 일반적인 상황에서는 유의미한가 싶다. &lt;a href=&quot;https://kotlinlang.org/docs/sealed-classes.html&quot;&gt;참고&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h1 id=&quot;지뢰찾기&quot; style=&quot;position:relative;&quot;&gt;지뢰찾기&lt;a href=&quot;#%EC%A7%80%EB%A2%B0%EC%B0%BE%EA%B8%B0&quot; aria-label=&quot;지뢰찾기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;높이를 입력하세요.
10

너비를 입력하세요.
10

지뢰는 몇 개인가요?
10

지뢰찾기 게임 시작
open: 1, 1
0 1 C C C C C C C C
0 1 C C C C C C C C
0 1 C C C C C C C C
1 1 C C C C C C C C
C C C C C C C C C C
C C C C C C C C C C
C C C C C C C C C C
C C C C C C C C C C
C C C C C C C C C C
C C C C C C C C C C

open: 4, 1
Lose Game.&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;지뢰찾기 구현 목록&lt;/h3&gt;
&lt;ol class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 게임판의 높이와 너비, 지뢰 개수를 입력받는다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 게임판을 만든다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 입력받은 지뢰 개수만큼 지뢰를 생성하며 위치는 랜덤이다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 지뢰찾기 게임판을 만든다.
&lt;ul&gt;
&lt;li&gt;지뢰 주변의 셀 숫자를 증가시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 셀 위치를 반복적으로 입력 받는다.
&lt;ul&gt;
&lt;li&gt;입력받은 위치는 &lt;code class=&quot;language-text&quot;&gt;,&lt;/code&gt;로 구분되어야 하고 2개의 정수이어야 하며 높이와 너비를 초과해서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 입력받은 위치의 셀을 연다.
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 주변에 지뢰가 존재하지 않는 셀(0인 셀)이라면 반복해서 주변의 셀을 연다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 주변에 지뢰가 존재하는 셀이라면 선택한 셀만 열린다.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 선텍한 셀이 지뢰라면 게임은 끝난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;1단계&lt;/strong&gt;: 지뢰 찾기 (그리기) &lt;a href=&quot;https://github.com/next-step/kotlin-minesweeper/pull/367&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2단계&lt;/strong&gt;: 지뢰 찾기 (지뢰 개수) &lt;a href=&quot;https://github.com/next-step/kotlin-minesweeper/pull/378&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3단계&lt;/strong&gt;: 지뢰 찾기 (게임 실행) &lt;a href=&quot;https://github.com/next-step/kotlin-minesweeper/pull/396&quot;&gt;리뷰&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4단계&lt;/strong&gt;: 지뢰 찾기 (리팩터링) &lt;a href=&quot;https://github.com/next-step/kotlin-minesweeper/pull/419&quot;&gt;리뷰&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e6ebcc10d922d18f839810890e93b86d/06076/minesweeper.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACRklEQVR42l1S2XKbQBDkI/zgkrhBSFziMAJxCZC1yJIjJVGlyuWXVOX/P6LTED/loWt3maFnpnukStXgaAa8GTo83cBa0ZD5IW7jO96HI3zGVqqOTtPw0XQYX0dcDgeE5gq+YaJKElTpC4TvQ0pI+KyZkDULy/k0sVQN2G4AP0phrT1ohs0cCzsW/NEdUY9XDKcRmr2B7ScQPz8xXO/4zuJSyiSFJBNxTui8a7qJ/u2O0/kd3jbG6XKHR/KdouJTXNDzLcY3GKYzQ9x+oT9fcWfXUqqqkHULJmFwNJWEE+KXYobL0cumxzqMZsLeCxAXNaqqgRtEjA28t1i5zNu4kO78UJ8u6PojmsMwd5fkNZpe8N0hSl4QRgmCZDdPkMkKoqJBXtaIdnt290DddlgoBrYsKHVhgk2UwQ+28MN47s4NEhxY5FW8IdgmUKnpkjpOhAXNWVBPm8TZJBNNWTM2y0b5pGSp4FnWoCg6ZELhzxbFLuoO9aGH729JGiOrW4gwRMQNeNJtxHmDlhM1NUdn/Ek1EU6EkylL6qfNZlgz6a7sMX570EmBLC//6WqvUbGbhyLjQN0/yga/jwJ/jkc84giNLM9rNa/N4ssI5Qs6nbMcD2leId9X2FLDBYvtmNuTMOZKCRp15QSXusZrsUduTXFqOOkwkRgT0Rc06mTQHMH1uFxvEOIMgx1OGqYU3tmEKKoBWTsgKVuERQXDchAyLhVknbpM/4euo1ytsV85PJ1Z8Jq5I1FxvJbnbTjhXNVwadDEMZnyF1AmUjvRszfQAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;minesweeper&quot;
        title=&quot;&quot;
        src=&quot;/static/e6ebcc10d922d18f839810890e93b86d/1cfc2/minesweeper.png&quot;
        srcset=&quot;/static/e6ebcc10d922d18f839810890e93b86d/3684f/minesweeper.png 225w,
/static/e6ebcc10d922d18f839810890e93b86d/fc2a6/minesweeper.png 450w,
/static/e6ebcc10d922d18f839810890e93b86d/1cfc2/minesweeper.png 900w,
/static/e6ebcc10d922d18f839810890e93b86d/21482/minesweeper.png 1350w,
/static/e6ebcc10d922d18f839810890e93b86d/d61c2/minesweeper.png 1800w,
/static/e6ebcc10d922d18f839810890e93b86d/06076/minesweeper.png 4402w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 게임 준비&lt;/strong&gt; 단계에서는 게임판 높이와 너비, 지뢰 생성 전략, 게임에 사용되는 게임판 생성 전략, IO 전략을 준비한다.&lt;br&gt;
&lt;strong&gt;2. 게임판 생성&lt;/strong&gt; 게임을 진행하는 동안 사용자 게임판과 관리자 게임판을 관리하는 책임을 가진 &lt;code class=&quot;language-text&quot;&gt;PlayingGameBoard&lt;/code&gt;을 생성한다.&lt;br&gt;
&lt;strong&gt;3. 게임 진행&lt;/strong&gt; 게임에 사용될 게임판인 &lt;code class=&quot;language-text&quot;&gt;PlayingGameBoard&lt;/code&gt;와 플레이어 IO를 책임지는 &lt;code class=&quot;language-text&quot;&gt;ViewStrategy&lt;/code&gt;를 보유하는 &lt;code class=&quot;language-text&quot;&gt;MinesweeperGame&lt;/code&gt;을 통해 게임을 진행한다.&lt;br&gt;
처음에는 컨트롤러 역할을하는 Game 클래스가 게임판을 준비하고 플레이어 IO와 게임 진행을 하는 책임을 모두 가지고 있었다.&lt;br&gt;
해당 의존성을 아래처럼 분리했다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;                                          ➚ DefaultGameBoard extends GameBoard
Game → MinesweeperGame → PlayingGameBoard
                                          ➘ MinesweeperGameBoard extends GameBoard&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;다이어그램을 통해 알 수 있듯이 책임 사이사이에 구현체에 직접 의존하지 않고 인터페이스에 의존하도록 구현했다.&lt;br&gt;
게임을 진행하는동안 플레이어의 입출력을 담당할 로직도 &lt;code class=&quot;language-text&quot;&gt;MinesweeperGame&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;ViewStrategy&lt;/code&gt;를 주입하여 결합도를 낮추도록 하였다.
지뢰 위치를 기억하는 관리자 게임판과 플레이어가 사용하는 게임판을 같은 타입으로 구분하기 위해 추상 클래스 &lt;code class=&quot;language-text&quot;&gt;GameBoard&lt;/code&gt; 추가하였다.&lt;/p&gt;
&lt;p&gt;미션을 진행하면서 엘레강스 오브젝트의 &lt;a href=&quot;https://jdalma.github.io/2023y/bookReview/bookReview/#%EC%97%98%EB%A0%88%EA%B0%95%ED%8A%B8-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8&quot;&gt;퍼블릭 상수를 사용하지 마라&lt;/a&gt;가 떠올랐다.&lt;br&gt;
책임이 너무 많다고 인지할 수 있는 힌트 3가지가 있다는 것을 느꼈다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상수의 개수&lt;/li&gt;
&lt;li&gt;파라미터의 개수&lt;/li&gt;
&lt;li&gt;프로퍼티의 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;상수를 private으로 선언하지 못하거나 한 클래스에 private 상수가 많은것도 힌트라고 보인다.&lt;/p&gt;
&lt;h3&gt;지뢰 주변 셀들 값 증가시키기&lt;/h3&gt;
&lt;p&gt;플레이어가 특정 셀을 열었을 때 해당 셀의 주변 8방향 셀 주변에 지뢰가 없다면 연쇄적으로 셀을 열어야 할 필요가 있었다.&lt;br&gt;
그래서 지뢰찾기 게임판을 생성할 때 지뢰 주변의 셀들을 1씩 증가시켜서 게임판을 생성하는 관리자 게임판 Render가 있다.&lt;br&gt;
이때 기준 셀의 주변 8방향 셀의 위치를 반환하는 기능을 &lt;strong&gt;게임판의 높이와 너비를 가지는 &lt;code class=&quot;language-text&quot;&gt;BoardElement&lt;/code&gt;&lt;/strong&gt; 에 구현할지, &lt;strong&gt;셀의 위치를 가지는 &lt;code class=&quot;language-text&quot;&gt;Position&lt;/code&gt;&lt;/strong&gt; 에 구현할지 고민했다.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;BoardElement&lt;/code&gt;는 게임판의 높이와 너비를 이미 가지고 있기 때문에 IndexOutOfBounds 예외를 피할 수 있다.&lt;br&gt;
하지만 문맥상 &lt;code class=&quot;language-text&quot;&gt;Position&lt;/code&gt; 클래스가 맞는 것 같아 &lt;code class=&quot;language-text&quot;&gt;Position&lt;/code&gt;에 구현했다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class MinesweeperBoardRender(
    private val mines: Set&amp;lt;Position&amp;gt;
): BoardRenderStrategy {

    override fun invoke(boardElement: BoardElement): GameBoard {
        val board = List(boardElement.width) { col -&amp;gt;
            List(boardElement.height) { row -&amp;gt;
                makeCell(col, row, INIT_CELL_NUMBER)
            }
        }

        mineMarking(board, boardElement)
        return MinesweeperGameBoard(board)
    }

    private fun makeCell(col: Int, row: Int, value: Char): Cell {
        val position = Position(col, row)
        val type = if (mines.contains(position)) CellType.MINE else CellType.NORMAL
        return Cell(position, type, value)
    }

    private fun mineMarking(board: List&amp;lt;List&amp;lt;Cell&amp;gt;&amp;gt;, boardElement: BoardElement) {
        mines.forEach { mine -&amp;gt;
            mine.nearPositions(boardElement)
                .forEach { if (!board[it.col][it.row].isMine()) board[it.col][it.row].increaseValue() }
        }
    }
    ...
}

data class Position(
    val col: Int,
    val row: Int
) {
    constructor(col: String, row: String): this(col.toInt() - 1, row.toInt() - 1)
    operator fun plus(other: Position) = Position(this.col + other.col, this.row + other.row)

    fun nearPositions(boardElement: BoardElement): List&amp;lt;Position&amp;gt; =
        NEAR_POSITIONS.map { this + it }
            .filter { !boardElement.isOutOfRange(it) }

    companion object {
        private val NEAR_POSITIONS = arrayOf(
            Position(0, -1),
            Position(1, -1),
            Position(1, 0),
            Position(1, 1),
            Position(0, 1),
            Position(-1, 1),
            Position(-1, 0),
            Position(-1, -1)
        )
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;</content:encoded></item><item><title><![CDATA[2023년 기록]]></title><description><![CDATA[…]]></description><link>https://jdalma.github.io/2023y/bookReview/bookReview/</link><guid isPermaLink="false">https://jdalma.github.io/2023y/bookReview/bookReview/</guid><pubDate>Sun, 01 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;엘레강트-오브젝트&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;엘레강트 오브젝트&lt;/strong&gt;&lt;a href=&quot;#%EC%97%98%EB%A0%88%EA%B0%95%ED%8A%B8-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8&quot; aria-label=&quot;엘레강트 오브젝트 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;넥스트 스텝의 클린코드를 진행하면서 해당 책의 내용이 많이 인용되기에 윤석님에게 빌려 읽어봤다. (절판이라서 구매할 수 없음)&lt;br&gt;
절차지향적으로 컴퓨터를 위해 코드를 작성하는 것을 멀리하고 &lt;strong&gt;유지보수성&lt;/strong&gt; 을 위해 지켜야 할 규칙들을 설명한다.&lt;/p&gt;
&lt;p&gt;공감하는것은 체크로 표시해보았다.&lt;/p&gt;
&lt;ol class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;-er로 끝나는 이름을 사용하지 마라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;클래스의 이름은 무엇을 하는지가 아니라 &lt;code class=&quot;language-text&quot;&gt;무엇인지&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;할 수 있는 일&lt;/code&gt;에 기반해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;-er&lt;/code&gt;로 끝난다면 어떤 데이터를 다루는 절차들의 집합일 뿐이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;생성자 하나를 주 생성자로 만들어라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;응집도가 높고 견고한 클래스에는 적은 수의 메서드와 상대적으로 더 많은 수의 생성자가 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;생성자에 코드를 넣지 마라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;인자를 그대로 캡슐화하면 연산 시점을 자유롭게 수정할 수 있어 사용자가 쉽게 제어할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;가능하면 적게 캡슐화하라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;내부 상태 (또는 식별자)를 최대 4개 이하로 캡슐화하라.&lt;/li&gt;
&lt;li&gt;상태의 개수가 클래스를 분할할지 결정하는 신호로 봐야하며, 상태가 없는 객체는 존재해서는 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;최소한 뭔가는 캡슐화하라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;상태가 없는 클래스는 OOP에서 멀리해야 할 정적메서드와 비슷하며 오직 행동만 존재하기 때문에 OOP에서는 존재하지 말아야 할 개념이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;strong&gt;항상 인터페이스를 사용하라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;객체간에 인터페이스를 통하여 결합도를 낮추기 위한 내용에는 완전히 공감하지만, 개발 초기부터 API 목록을 잘 뽑아낼 자신이 없다면 일단 콘크리트 클래스로 개발해도 된다고 생각한다.&lt;/li&gt;
&lt;li&gt;모든 메서드를 인터페이스를 통해 구현하는 것은 추상화에 대한 능력이 어느정도 있어야한다고 생각한다.&lt;/li&gt;
&lt;li&gt;처음에는 어느정도 절차지향적으로 작성하여 리팩토링을 통해 추상화 지점들을 더 잘 찾아낼 수 있다고 생각한다. 이른 추상화를 한다면 큰 그림을 못 볼 가능성이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;메서드 이름을 신중하게 선택하라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;뭔가를 조작한 후 반환하거나, 뭔가를 만드는 동시에 조작하는 메서드가 있어서는 안된다는 것이다.&lt;/li&gt;
&lt;li&gt;예를 들어, &lt;code class=&quot;language-text&quot;&gt;fun write(content: InputStream): Int&lt;/code&gt; 처럼 content를 전달받아 어딘가에 작성하고 쓰여진 바이트 수를 반환하는 것처럼 메서드의 목적이 명확하지 않기 때문에 깔끔하게 명사나 동사 둘 중 하나로 이름을 지을 수가 없는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;strong&gt;퍼블릭 상수를 사용하지 마라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;객체들은 어떤 것도 공유해서는 안되며 독립적이어야 하고 &lt;strong&gt;닫혀 있어야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;상수라도 객체간에 공유하게 되면 결합도가 상승하고 응집도가 저하되기에 &lt;strong&gt;기능을 공유할 수 있도록 새로운 클래스를 만들어야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;이 말은 퍼블릭 상수마다 캡슐화하는 새로운 클래스를 만들어야 한다는 것이다. 수백 개의 단순한 상수 문자열 리터럴 대신 수백 개의 마이크로 클래스를 만들어야 한다는 것이다.&lt;/li&gt;
&lt;li&gt;굉장히 맞는 말이지만 OOP에 대한 실력이 낮기에 개발하면서 적용하기가 쉽지않다.. 노력해봐야겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;불변 객체로 만들어라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;식별자 가변성&lt;/strong&gt; 문제가 없고 &lt;strong&gt;실패 원자성&lt;/strong&gt; 이 지켜지며, &lt;strong&gt;시간적 결합&lt;/strong&gt; 을 제거할 수 있다.&lt;/li&gt;
&lt;li&gt;그리고 &lt;strong&gt;사이드 이펙트&lt;/strong&gt; 가 존재할 수 없게 되며, &lt;strong&gt;스레드에 안전&lt;/strong&gt; 하다.&lt;/li&gt;
&lt;li&gt;상수 객체와 불변 객체를 혼용해서 표현하곤 하는데, 상수 객체는 불변 객체의 특별한 경우일 뿐이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;strong&gt;모의 객체 대신 페이크 객체를 사용해라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;모키토 같은 라이브러리에 의존하여 모킹을 하는 방법을 지양하고 가짜 객체를 사용하는 점은 굉장히 공감한다.&lt;/li&gt;
&lt;li&gt;하지만 가짜 객체를 선언하는 위치가 인터페이스 내부인 것은 공감하지 못 하겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;5개 이하의 public 메서드만 (protected 포함) 노출하라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;의도는 클래스를 작게 만들어서 &lt;strong&gt;유지보수성&lt;/strong&gt; , &lt;strong&gt;응집도&lt;/strong&gt; , &lt;strong&gt;테스트 용이성&lt;/strong&gt; 을 챙기는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;정적 메서드(유틸리티 클래스)를 사용하지 마라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;저자는 OOP를 제대로 이해하지 못한 프로그래머를 구별할 수 있는 최적의 지표이며 정적 메서드를 순수한 악이라고 생각한다.&lt;/li&gt;
&lt;li&gt;정적 메서드를 작성하지 말고 그 기능을 수행하는 객체를 만들어라고 말한다.&lt;/li&gt;
&lt;li&gt;싱글톤 패턴은 (캡슐화를 통한) &lt;strong&gt;의존성을 분리하는 이점&lt;/strong&gt; 이 있긴 하지만 안티패턴인 이유와 비슷하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;절대 getter와 setter를 사용하지 마라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;getter와 setter가 존재하는 클래스는 객체라고 볼 수 없다. 그저 자료구조에 불과하며 어떤 개성도 지니지 않은 &lt;strong&gt;단순한 데이터 가방일 뿐이다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;어떤 식으로든 멤버에게 접근하는 것을 허용하지 않아야 한다.&lt;/li&gt;
&lt;li&gt;자료구조는 투명하지만 &lt;strong&gt;객체는 불투명 해야한다.&lt;/strong&gt; 가시성의 범위를 축소해서 &lt;strong&gt;사물을 단순화 시켜야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;내부 멤버들이 모두 공개된 클래스는 절차적인 프로그래밍 스타일을 사용하도록 부추기는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;부 생성자 밖에서는 new를 절대 사용하지 마라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;A가 B에 의존할 때 A의 부 생성자 밖(내부 멤버 함수)에서는 절대 &lt;code class=&quot;language-text&quot;&gt;new B()&lt;/code&gt;를 사용하지 마라는 것이다.&lt;/li&gt;
&lt;li&gt;A가 필요한 의존성을 직접 생성하는 대신, &lt;strong&gt;생성자를 통해 의존성을 주입받아야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;strong&gt;절대 NULL을 반환하지 마라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;어떤 연산의 결과에 NULL이 포함된다면 클라이언트 코드는 NEP에 대해 불안할 수 밖에 없다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빠르게 실패하기&lt;/strong&gt; 와 &lt;strong&gt;안전하게 실패하기&lt;/strong&gt; 중에서 저자는 빠르게 실패하기를 지지한다.&lt;/li&gt;
&lt;li&gt;빠르게 실패하기 위해서는 예외로 프로그래밍을 해야하고 코틀린에서도 &lt;code class=&quot;language-text&quot;&gt;xxxorNull()&lt;/code&gt; 함수가 등장하고 있기 때문에 이 토픽에 대해서는 절반만 공감한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; &lt;strong&gt;체크 예외만 던져라&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;저자는 언체크 예외를 사용하는 것은 실수이며, 모든 예외는 체크 예외여야 한다고 생각한다.&lt;/li&gt;
&lt;li&gt;개인적인 생각으로 체크 예외는 객체 간의 결합도를 증가시키는 행위라고 생각한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;항상 예외를 체이닝 해라&lt;/code&gt;라는 말도 공감하긴 하지만 &lt;strong&gt;체크 예외를 런타임 예외로 체이닝하는 방법을 선호하기 때문에 이 토픽에는 공감하지 못했다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; &lt;strong&gt;final이거나 abstract이거나&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;사람들은 대부분 &lt;strong&gt;캡슐화가 상속보다 더 나은 대안이다&lt;/strong&gt; 라고 이야기하지만, 상속 자체가 문제가 아니라 상속을 올바르게 사용하는 방법을 찾아야한다.&lt;/li&gt;
&lt;li&gt;둘 중 어느쪽도 아닌 메서드는 작성하지 말고 &lt;code class=&quot;language-text&quot;&gt;final (블랙 박스)&lt;/code&gt; 또는 &lt;code class=&quot;language-text&quot;&gt;abstract (글래스 박스)&lt;/code&gt;로 선언하면 상속의 단점을 보완할 수 있다.&lt;/li&gt;
&lt;li&gt;둘 중 어느쪽도 아닌 메서드는 블랙 박스와 글래스 박스 둘 중 어느쪽도 될 수 있기 때문에 혼란스러울 수 밖에 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;알고리즘&lt;/strong&gt; 과 &lt;strong&gt;실행&lt;/strong&gt; 대신 &lt;strong&gt;객체&lt;/strong&gt; 와 &lt;strong&gt;행동&lt;/strong&gt; 의 관점에서 사고해야한다.&lt;br&gt;
절차적인 프로그래밍과 OOP의 중요한 차이점은 &lt;strong&gt;책임을 지는 주체가 무엇인가&lt;/strong&gt; 이다.&lt;br&gt;
우리는 그저 누가 누구인지만 정의하고 객체들이 필요할 때 &lt;strong&gt;스스로&lt;/strong&gt; 상호작용하도록 제어를 위임하는 것이다.&lt;br&gt;
객체는 &lt;strong&gt;살아있는 유기체&lt;/strong&gt; 이며, 다른 유기체들과 &lt;strong&gt;의사소통&lt;/strong&gt; 하면서 그들의 작업을 지원하고, 다른 유기체들 역시 이 객체에게 도움을 받는다.&lt;br&gt;
이렇게 각 객체들은 서로를 필요로 하기 때문에 &lt;strong&gt;결합된다는 것&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;객체란 디스크에 있는 파일, 웹 페이지, 바이트 배열, 해시맵, 달력의 월과 같은 실제 엔티티의 &lt;strong&gt;대표자&lt;/strong&gt; 이다.&lt;br&gt;
어떤 일을 해야 한다면 객체에게 그 일을 하도록 요청하고, 수신한 요청을 처리하기 위해 &lt;strong&gt;객체 스스로&lt;/strong&gt; 무엇을 할지 결정해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;진정한 객체지향에서 인스턴스화란 더 작은 객체들을 &lt;strong&gt;조합해서&lt;/strong&gt; 더 큰 객체를 만드는 것을 의미한다.&lt;br&gt;
객체들을 조합해야 하는 단 하나의 이유는 새로운 계약을 준수하는 새로운 엔티티가 필요하기 때문이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;객체 분리&lt;/strong&gt; 란, 상호작용하는 다른 객체를 수정하지 않고도 해당 객체를 수정할 수 있도록 만든다는 것을 의미한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저자의 단정적인 문체와 공감하지 못하는 내용도 포함되어 있어 거부감이 들순 있지만 객체지향 프로그래밍에 대해 어느정도의 주관이 생길 수 있는 책이라고 느꼈다.&lt;br&gt;
얇지만 OOP에 대해 고민들을 할 수 있는 책이다. 오브젝트 읽기 전에 읽으면 좋을 책이라고 생각한다.&lt;/p&gt;
&lt;h1 id=&quot;인프런-스프링-db-1편&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;인프런 스프링 DB 1편&lt;/strong&gt;&lt;a href=&quot;#%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%8A%A4%ED%94%84%EB%A7%81-db-1%ED%8E%B8&quot; aria-label=&quot;인프런 스프링 db 1편 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;실무에서 RW 분리를 진행하는 업무를 진행하면서 리드온리 힌트로 쿼리 라우팅을 통해 DataSource를 분리하는 방법을 찾아 해결했다.&lt;br&gt;
하지만 DataSource와 TransactionManager에 대한 이해가 없었어서 답답하여 사놓은지 1년만에 강의를 다 듣게 되었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;드라이버 구현체에 따른 커넥션 생성&lt;/li&gt;
&lt;li&gt;커넥션 풀&lt;/li&gt;
&lt;li&gt;DataSource를 통한 추상화&lt;/li&gt;
&lt;li&gt;커넥션과 세션, 트랜잭션에 대한 이해&lt;/li&gt;
&lt;li&gt;트랜잭션 매니저와 트랜잭션 동기화 매니저&lt;/li&gt;
&lt;li&gt;트랜잭션 프록시&lt;/li&gt;
&lt;li&gt;DB 관련 예외 계층과 예외 변환기&lt;/li&gt;
&lt;li&gt;TransactionTemplate과 JdbcTemplate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;현재 많은 개발자가 애용하는 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;에 대한 변쳔사를 설명한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt;를 자주 사용하지만 &lt;strong&gt;선언전 트랜잭션 관리&lt;/strong&gt; 를 통해 어떤 문제를 편하게 해결하고 왜 발전하게 되었는지를 이해할 수 있는 좋은 강의였다.&lt;/p&gt;
&lt;h1 id=&quot;공부란-무엇인가&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;공부란 무엇인가?&lt;/strong&gt;&lt;a href=&quot;#%EA%B3%B5%EB%B6%80%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot; aria-label=&quot;공부란 무엇인가 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;회사 책장의 책들을 보다가 제목을 보고 무슨 내용일까 궁금해서 읽어봤다.&lt;br&gt;
이 책은 공부에 대해 본질적인 질문을 하는 것 같다.&lt;br&gt;
(목적에 도달하기 위한) &lt;strong&gt;&quot;과정에 들어가는 노력과 시간 자체가 삶이라는 점을 망각하며 삶을 현재와 동떨어져 전개되는 무엇으로 보도록 길들여진다&quot;&lt;/strong&gt; 고 아래의 비유와 함께 설명한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;마치 날씨가 너무 좋은 날 경치가 아름다운 길을 돌아보지 않고 바삐 지나치는 것이 그 시간에 대한 모욕인 것 처럼,&lt;br&gt;
기껏 수능 시험을 얼마나 잘 보았나, 혹은 얼마나 명문 대학에 입학했는가 정도라면 그것은 그보다 흥미로운 지적 체험이 없었다는 자기 고백일 뿐이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;낙화암에서 떨어진다고 모두 꽃은 아니다 (에필로그 중에서)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;내용을 서술하는 방법이 직설적이고 저자의 생각을 편하게(거침없이) 설명해준다.&lt;br&gt;
기술 책이나 이 책에 비해 선비같은 책들만 보다가 이런 책을 접하니 재밌고 신선했다.&lt;/p&gt;
&lt;p&gt;인상깊은 내용들이 있었다.&lt;br&gt;
공부가 즉각적인 쓸모와 거리가 멀면 멀수록, 묘한 &apos;간지&apos;가 난다고 한다. 누구나 쉽게 배울 수 있는 것지 않을 것들을 공부하는 사람은 &lt;code class=&quot;language-text&quot;&gt;정신의 척추 기립근&lt;/code&gt;이 잘 세워진 것 처럼 느껴진다.&lt;br&gt;
기자가 등반가에게 &lt;code class=&quot;language-text&quot;&gt;&quot;설산을 오르는 것이 대체 무슨 의미가 있나요?&quot;&lt;/code&gt;라고 물었을 때 &lt;code class=&quot;language-text&quot;&gt;&quot;그렇게 묻는 당신의 인생은 무슨 의미가 있는가?&quot;&lt;/code&gt; 라고 대답하는 아주 멋진 사람 처럼 말이다.&lt;/p&gt;
&lt;p&gt;그리고 토론의 정의를 설명하고 참여자의 필요한 자세를 설명하는 4부(생각의 심화)가 도움이 많이 되었다.&lt;br&gt;
나는 토론을 하다보면 나도 모르게 불필요한 방어기제가 나온다. 내 의견에 반대되는 의견이 나온다면 감정적으로 동요가 크다는 것을 요즘 느끼고 있다.&lt;br&gt;
&lt;strong&gt;토론은 다양성을 무한정 확보하는 것이라기보다는, 다양한 의견을 취합하여 좀 더 나은 지점으로 나아가는 것이라는 것을 기억해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;예리한 비판을 제기해야 할 순간에 불필요한 공격성을 드러내면, 그것은 미성숙의 표지일 뿐이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;비판에 대한 생각도 필요하다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;상대를 무시하는 가장 흔한 방법은 져주거나 침묵하는 것이다. 상대를 존중하는 사람만이 비판한다.&lt;br&gt;
결함으로 인해 삶이 아름다워지는 것은 그 결함을 인정할 때 뿐이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;비판을 하는 사람도 덕성이 필요하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;상대 주장의 약점보다는 강점과 마주하여 비판적인 논의를 해야 한다.&lt;/li&gt;
&lt;li&gt;비판을 불필요하게 길게 할 필요는 없다.&lt;/li&gt;
&lt;li&gt;비판이나 비난, 불평만 하는 것은 어떤 바보라도 할 수 있다. 건설적인 제안이나 대안을 제시하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;불필요하게 공격적인 언사를 남발해서는 안된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위에서 말한 것 처럼 경치나 아름다운 길을 돌아보지 않고 앞만 보고 달리는 사람들, 목적에 심취하여 과정에서 달콤함을 찾지 못하는 사람들에게 추천하고 싶다.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;인덱스&lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;어떤 공부도 오늘 날 우리가 처한 지옥을 순식간에 천국으로 바꾸어주지는 않겠지만, 탁월함이라는 별빛을 바라볼 수 있게는 해줄 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;공부하는 중에 한없이 편하다는 느낌이 들면, 뭔가 잘못하고 있을 공산이 크다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;호기심에서 출발한 지식 탐구를 통해 어제의 나보다 나아진 나를 체험할 것을 기대한다.&lt;br&gt;
공부를 통해 무지했던 과거의 나로부터 도망치는 재미를 기대한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;심오한 공부일수록 쾌감을 느낄 수 있을 때까지 고된 훈련 기간이 필요하다.&lt;br&gt;
훈련을 마치기 전에 공부를 포기하면, 공부가 주는 쾌락을 충분히 누릴 수 없다.&lt;br&gt;
쉽지 않은 공부는 늘 결기를 요구한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;h1 id=&quot;모던-자바-인-액션&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;모던 자바 인 액션&lt;/strong&gt;&lt;a href=&quot;#%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98&quot; aria-label=&quot;모던 자바 인 액션 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;08월 21일 1주차: 책 알아보기&lt;/li&gt;
&lt;li&gt;08월 28일 2주차: 1장 ~ 2장 - &lt;strong&gt;동작 파라미터화&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;09월 04일 3주차: 3장 - &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/LambdaExpression.md&quot;&gt;&lt;strong&gt;람다 표현식, 메서드 참조, 형식 검사와 추론&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;09월 18일 4주차: 4장 ~ 5장 - &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/Stream.md&quot;&gt;&lt;strong&gt;스트림 활용&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;09월 25일 5주차: 6장 ~ 7장 - &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/Stream%EC%9C%BC%EB%A1%9C%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%88%98%EC%A7%91.md&quot;&gt;&lt;strong&gt;스트림으로 데이터 수집&lt;/strong&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/Collector.md&quot;&gt;&lt;strong&gt;Collector&lt;/strong&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/ForkJoin%20Framework.md#spliterator-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4&quot;&gt;&lt;strong&gt;Spliterator&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;10월 02일 6주차: 8장 ~ 9장 - &lt;a href=&quot;https://github.com/jdalma/kotlin-playground/blob/main/src/test/java/Collection.java&quot;&gt;&lt;strong&gt;컬렉션 API 개선&lt;/strong&gt;&lt;/a&gt;, &lt;strong&gt;람다를 활용한 디자인 패턴&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;10월 09일 7주차: 10장 ~ 12장 - &lt;strong&gt;자바 DSL&lt;/strong&gt;, &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/Optional.md&quot;&gt;&lt;strong&gt;Optional&lt;/strong&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/%EC%83%88%EB%A1%9C%EC%9A%B4%20%EB%82%A0%EC%A7%9C%EC%99%80%20%EC%8B%9C%EA%B0%84%20API.md&quot;&gt;&lt;strong&gt;새로운 날짜와 시간 API&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;10월 16일 8주차: 13장 ~ 14장 - &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/DefaultMethod.md&quot;&gt;&lt;strong&gt;디폴트 메서드&lt;/strong&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/ModuleSystem.md&quot;&gt;&lt;strong&gt;자바 모듈 시스템&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;10월 23일 9주차: 15장 ~ 16장 - &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/CompletableFuture.md&quot;&gt;&lt;strong&gt;Future와 CompletableFuture&lt;/strong&gt;&lt;/a&gt;, &lt;a href=&quot;https://github.com/jdalma/kotlin-playground/blob/main/src/test/java/reactive/shop/AsyncShop.java&quot;&gt;&lt;strong&gt;CompletableFuture 예제&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;10월 30일 10주차: 17장 - &lt;a href=&quot;https://github.com/jdalma/kotlin-playground/tree/main/src/test/java/reactive/temperature&quot;&gt;&lt;strong&gt;Flow 예제&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;11월 06일 11주차: 18장 ~ 19장 - &lt;strong&gt;함수형 프로그래밍, 영속 자료구조, 패턴 매칭 등&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;11월 13일 12주차: 20장 ~ 21장 - &lt;strong&gt;자바와 스칼라 비교&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;책이 꽤 두꺼워서 12주 동안 진행했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stream, Collector를 활용하는 방법과 Collector를 커스터마이징 하는 방법&lt;/li&gt;
&lt;li&gt;병렬 스트림에서 태스크를 분할하는 방법&lt;/li&gt;
&lt;li&gt;디폴트 메서드의 다중 상속에 대한 해결 규칙&lt;/li&gt;
&lt;li&gt;Optional에서도 스트림 기능을 사용할 수 있다는 것&lt;/li&gt;
&lt;li&gt;개선된 날짜 API와 커스터마이징하는 방법&lt;/li&gt;
&lt;li&gt;Future와 CompletableFuture 활용 방법&lt;/li&gt;
&lt;li&gt;Flow API를 직접 구현해보는 경험&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;배운 내용(현재 기억나는 것들)은 위와 같다.&lt;br&gt;
Stream과 Optional에 대해 익숙하겠지만 &lt;strong&gt;Collector&lt;/strong&gt; 와 &lt;strong&gt;Spliterator&lt;/strong&gt; , &lt;strong&gt;CompletableFuture&lt;/strong&gt; 그리고 &lt;strong&gt;Flow API&lt;/strong&gt; 와 같이 관심 가지지 않으면 알 수 없는 것들을 이 책은 예제와 함께 직접 커스터마이징 할 수 있도록 도와준다.&lt;/p&gt;
&lt;p&gt;그리고 부록의 내용도 좋다.&lt;br&gt;
&lt;strong&gt;한 스트림에서 여러 결과를 얻는 방법&lt;/strong&gt; 과 &lt;strong&gt;익명 함수 대신 람다를 써야하는 이유&lt;/strong&gt; 를 설명한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;부록 C : 스트림에 여러 연산 병렬로 실행하기 &lt;a href=&quot;https://github.com/jdalma/kotlin-playground/commit/bd5308611a9a02a1de7c2e4e663cd8d1ac7a46b1&quot;&gt;예제&lt;/a&gt;, &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%9E%90%EB%B0%94/StreamForker.md&quot;&gt;정리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;부록 D : 람다와 익명함수를 JVM 바이트코드는 어떻게 최적화 되는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2년전에 이 책으로 스터디를 진행한적이 있는데 도중에 탈주하여 이때까지 덮어놓았다가 이번에 다시 읽게 되었다.&lt;br&gt;
앞부분 조금만 읽고 책의 내용을 다 아는것 마냥 &lt;code class=&quot;language-text&quot;&gt;&quot;스트림에 대해 설명하는 책이겠지&quot;&lt;/code&gt; 라고 대충 생각하며 펼쳐보지 않았었는데, 왜 이제야 읽기 시작했는지 후회할 정도로 좋은 책이였다.&lt;br&gt;
개인적으로는 토비의 스프링 다음으로 좋은 책이라고 생각한다.&lt;/p&gt;
&lt;p&gt;자바 개발자라면 꼭 읽으라고 추천하고 싶다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;유연함의-힘&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;유연함의 힘&lt;/strong&gt;&lt;a href=&quot;#%EC%9C%A0%EC%97%B0%ED%95%A8%EC%9D%98-%ED%9E%98&quot; aria-label=&quot;유연함의 힘 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;NEXTSTEP 뉴스레터에서 이 책을 추천하여 뭔가 성장에 대한 이야기이지 않을까 싶어서 읽어보았다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;성장과 편안함은 절대 공존할 수 없다. 끊임없이 위험을 감수할 의지가 있는 사람과 조직만이 현재는 물론 미래에도 성공할 수 있다.&lt;br&gt;
안전지대로 돌아갈지 성장할지는 각자 선택할 몫이다.&lt;br&gt;
우리는 끊임없이 성장을 선택해야 하고 반복해서 두려움을 이겨내야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 책은 &lt;strong&gt;유연함의 기술&lt;/strong&gt; 에 대해 설명하는데 유연함의 기술은 아주 넓은 의미를 가지고 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사람마다 경험에서 얻어내는 교훈은 다 다르다. 현재 무엇을 경험 중이든 그것을 활용해 스스로 성장하려고 노력하여야 한다.&lt;/li&gt;
&lt;li&gt;통제할 수 없는 외부 원인을 탓하고 집착하지 마라.&lt;/li&gt;
&lt;li&gt;성과 증명 마인드셋을 버리려 노력하고, &lt;strong&gt;학습 마인드셋&lt;/strong&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;/li&gt;
&lt;li&gt;스트레스를 받거나 불안하고 두려울 때 그 감정을 흥분이나 열정으로 재해석하라. &quot;무척 기대가 커&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;즉, &lt;strong&gt;기존의 행동을 포기하고 새로운 행동을 받아들이는 능력이 유연함의 힘의 대부분이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;사람마다 경험에서 얻어내는 교훈이 다른 것과 스트레스를 받거나 불안할 때 그 감정을 재해석하라&lt;/code&gt; 는 내용이 나에게 정말 필요했던 내용이었다.&lt;br&gt;
재미없는 업무를 하거나 나에게 도움이 안될 것 같은 업무를 받게되면 그 업무하는 모든 시간이 스트레스였다.&lt;br&gt;
하지만 위 내용을 읽고 생각을 조금 바꿔보았을 때 편한 느낌을 느꼇다.&lt;br&gt;
실제로 재미없거나 도움이 안될 줄 알았지만 하다보면 그 안에서 재미있는 일, 도움이 되는 일을 발견해낼 수 있었다.&lt;/p&gt;
&lt;p&gt;이런 유연함의 힘은 &lt;code class=&quot;language-text&quot;&gt;&quot;심적 여유&quot;&lt;/code&gt;에서 나오는 것이 아닐까 싶다.&lt;br&gt;
스스로의 삶을 살아가는 방식과 생각을 한번 되돌아보고 싶다면 강력 추천한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;코틀린-핵심-프로그래밍&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;코틀린 핵심 프로그래밍&lt;/strong&gt;&lt;a href=&quot;#%EC%BD%94%ED%8B%80%EB%A6%B0-%ED%95%B5%EC%8B%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot; aria-label=&quot;코틀린 핵심 프로그래밍 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 22%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fhika00%2Fposts%2Fpfbid036LeauGbWWn1uW14Qtpengk6tjQDCiYijFMJj2iSZ7tSG5Ls1PzofQ281gcU7BvDLl&amp;amp;show_text=true&amp;amp;width=500&quot; style=&quot;border:none;overflow:hidden; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot; allow=&quot;autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;p&gt;오현석님의 &lt;strong&gt;코틀린 핵심 프로그래밍&lt;/strong&gt;이 출간되어 맹대표님 스터디에 참여하면서 읽게 되었다.&lt;br&gt;
모임 내용을 정리하고 책에 나오는 내용들을 테스트 코드로 &lt;a href=&quot;https://github.com/jdalma/kotlin-playground&quot;&gt;직접 작성&lt;/a&gt;해보면서 읽었다.&lt;br&gt;
그리고 &lt;a href=&quot;https://github.com/jdalma/footprints/tree/main/%EC%BD%94%ED%8B%80%EB%A6%B0&quot;&gt;이론적인 내용도 정리&lt;/a&gt;해보았다.&lt;/p&gt;
&lt;p&gt;이 책을 읽을 때 처음에는 &lt;code class=&quot;language-text&quot;&gt;&quot;입문서인가?&quot;&lt;/code&gt; 했는데 뒤로 갈수록 매운 맛이 난다. (저자 피셜 입문서는 아니라고 한다.)&lt;br&gt;
코틀린 인 액션에서는 짧은 설명으로 넘어가는 주제들 중 이 책에서는 자세하게 설명하는 내용도 있어서 코틀린 인 액션과 같이 읽는 것도 좋지 않을까 싶다.&lt;br&gt;
그리고 코틀린의 Sequence와 Stream, Coroutine에 대해 설명하진 않지만 &lt;strong&gt;제네릭스&lt;/strong&gt;와 &lt;strong&gt;컬렉션 함수&lt;/strong&gt; 에 대한 내용이 자세하여 읽을 이유는 충분하다.&lt;br&gt;
해설은 없지만 &lt;a href=&quot;https://github.com/jdalma/kotlin-playground/blob/main/%EC%BD%94%ED%95%B5%ED%94%84_%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C.md&quot;&gt;연습문제&lt;/a&gt;도 제공한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;디자인-패턴의-아름다움&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;디자인 패턴의 아름다움&lt;/strong&gt;&lt;a href=&quot;#%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%95%84%EB%A6%84%EB%8B%A4%EC%9B%80&quot; aria-label=&quot;디자인 패턴의 아름다움 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 22%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem&quot; &gt; &lt;iframe src=&quot;https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fhika00%2Fposts%2Fpfbid01GvYG7STwJ5eTt3PPuQfFtfXxJkZbXF9HKs3QrCMo9EnYh2iLbymbQ1wESD3FviLl&amp;amp;show_text=true&amp;amp;width=500&quot; style=&quot;border:none;overflow:hidden; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot; allow=&quot;autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;p&gt;고품질 코드를 작성하는 것에 초점을 맞춰 다섯 가지 관련 주제인 &lt;strong&gt;객체지향 프로그래밍 패러다임&lt;/strong&gt;, &lt;strong&gt;설계 원칙&lt;/strong&gt;, &lt;strong&gt;코딩 규칙&lt;/strong&gt;, &lt;strong&gt;리팩터링 기술&lt;/strong&gt;, &lt;strong&gt;디자인 패턴&lt;/strong&gt;을 모두 포괄적으로 설명한다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;&quot;이런 디자인 패턴이 생겨난 이유는 무엇인가?&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;어떤 종류의 프로그래밍 문제를 해결하는데 사용되는가?&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;적용시 장단점은 무엇인가?&quot;&lt;/code&gt;와 같은 질문을 이해하고 적용할 수 있도록 안내한다.&lt;/p&gt;
&lt;p&gt;나는 디자인 패턴 책으로 유명한 &lt;a href=&quot;https://www.yes24.com/Product/Goods/17525598&quot;&gt;GoF의 디자인 패턴&lt;/a&gt; 과 &lt;a href=&quot;https://www.yes24.com/Product/Goods/108192370&quot;&gt;헤드퍼스트의 디자인 패턴&lt;/a&gt; 책을 읽지 않아 차이점을 알순 없지만 이 책을 읽으면서 좋았던 점은 SOLID 원칙에 대한 자세한 내용과 패턴에서 소개하는 예제들이 좋았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;리스코프 치환 원칙&lt;/strong&gt;을 지키기 위한 자세한 설명&lt;/li&gt;
&lt;li&gt;팩토리 패턴에서 &lt;strong&gt;BeanFactory&lt;/strong&gt; 예제&lt;/li&gt;
&lt;li&gt;프록시 패턴에서 &lt;strong&gt;자바의 동적 프록시&lt;/strong&gt; 예제&lt;/li&gt;
&lt;li&gt;데코레이터 패턴에서 &lt;strong&gt;JavaIO 라이브러리&lt;/strong&gt; 예제&lt;/li&gt;
&lt;li&gt;옵저버 패턴의 &lt;strong&gt;비동기식 비차단 옵저버 패턴&lt;/strong&gt;과 &lt;strong&gt;EventBus&lt;/strong&gt; 예제&lt;/li&gt;
&lt;li&gt;템플릿 메서드 패턴에서 &lt;strong&gt;InputStream 클래스&lt;/strong&gt; 예제&lt;/li&gt;
&lt;li&gt;템플릿 메서드 콜백 패턴에서 &lt;strong&gt;JdbcTemplate&lt;/strong&gt; 예제&lt;/li&gt;
&lt;li&gt;책임 연쇄 패턴에서 &lt;strong&gt;스프링의 필터와 인터셉터&lt;/strong&gt; 그리고 &lt;strong&gt;MyBatis의 인터셉터&lt;/strong&gt; 예제&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;패턴을 배우는 동시에 우리가 자주 사용하지만 따로 시간을 내지 않으면 내부 원리는 알기 힘든 기능들에 대해 예제를 작성해줘서 더 재밌었던 것 같다.&lt;br&gt;
그리고 비슷한 패턴간의 차이점이나 해당 패턴을 사용했을 때 생기는 문제점과 극복할 수 있는 방법들도 중간중간 소개를 해줘서 좋았다.&lt;/p&gt;
&lt;p&gt;스터디를 통해 경험이 많은 사람들의 이야기를 들으면서 읽은 탓에 더 좋은 느낌을 받은것도 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%8A%A4%ED%84%B0%EB%94%94/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4%EC%9D%98%20%EC%95%84%EB%A6%84%EB%8B%A4%EC%9B%80.md&quot;&gt;모임 내용&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(디자인 패턴 책은 이번이 처음이지만) 디자인 패턴 자체가 가독성을 지키면서 확장에 유연한 코드를 작성하기 위한 &lt;strong&gt;컨셉&lt;/strong&gt; 이라고 생각한다.&lt;br&gt;
각 패턴들에 매몰되어 패턴 자체를 외워가면서 학습하는 것이 아니라 &lt;strong&gt;어떤 문제를 &lt;code class=&quot;language-text&quot;&gt;어떻게&lt;/code&gt; 해결하는지 에 대해 전체적인 그림을 이해하는 것&lt;/strong&gt; 이 중요하다고 느꼈다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;&quot;이 패턴을 써볼까?&quot;&lt;/code&gt;라고 먼저 사용할 패턴을 정하고 설계하는 것 보다 &lt;code class=&quot;language-text&quot;&gt;&quot;이 문제는 이 패턴의 장점을 활용하면 이렇게 해결할 수 있겠는데?&quot;&lt;/code&gt;라고 생각하는게 더 유연한 설계를 하는 방법이라고 생각한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이 책은 자바와 스프링에 대해 친숙하고 다양한 예제들을 접하고 싶다면 해당 책을 추천하고 싶다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;이 책을 읽으면서 정리한 내용들이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://jdalma.github.io/2023y/oop/&quot;&gt;좋은 코드, 고품질 코드란?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4/%EA%B0%9D%EC%B2%B4_%EC%83%9D%EC%84%B1_%EA%B4%80%EB%A0%A8.md&quot;&gt;객체 생성 관련 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4/%EA%B5%AC%EC%A1%B0_%EA%B4%80%EB%A0%A8.md&quot;&gt;구조 관련 패턴&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4/%ED%96%89%EB%8F%99_%EA%B4%80%EB%A0%A8.md&quot;&gt;행동 관련 패턴&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 &lt;a href=&quot;https://refactoring.guru/ko/design-patterns/book&quot;&gt;디자인 패턴에 뛰어들기&lt;/a&gt;를 읽으면서 예제를 더 업데이트할 생각이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;자바에서-코틀린으로&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;자바에서 코틀린으로&lt;/strong&gt;&lt;a href=&quot;#%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-%EC%BD%94%ED%8B%80%EB%A6%B0%EC%9C%BC%EB%A1%9C&quot; aria-label=&quot;자바에서 코틀린으로 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;업무를 진행하면서 자바와 코틀린 중에 선택할 수 있었다.&lt;br&gt;
새로 개발하는 작업들은 모두 코틀린을 지향하고 있기도 하고, 이 기회에 코틀린을 써보고 싶어 코틀린을 선택했다.&lt;/p&gt;
&lt;p&gt;시기적절하게 코드숨 커뮤니티에서 스터디 모집 공지가 올라왔다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/373060fa3b201a3b8fe80fda2f915b61/ad68d/study.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 84.44444444444446%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAARABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBP/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHhRZkDIUH/xAAZEAACAwEAAAAAAAAAAAAAAAAAARARMUH/2gAIAQEAAQUC0oYi3HI//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAFRABAQAAAAAAAAAAAAAAAAAAMEH/2gAIAQEABj8Cg//EAB4QAAIBAwUAAAAAAAAAAAAAAAABERAhMUFhcYGR/9oACAEBAAE/IZelOh8PCxk4sbA3LwLOv//aAAwDAQACAAMAAAAQFx9B/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHhAAAgICAgMAAAAAAAAAAAAAAREAITFhEEFRcaH/2gAIAQEAAT8QzWgAEFxCp29QBQgK8KW6bcV0mYAHU+jn/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;study&quot;
        title=&quot;&quot;
        src=&quot;/static/373060fa3b201a3b8fe80fda2f915b61/8e1fc/study.jpg&quot;
        srcset=&quot;/static/373060fa3b201a3b8fe80fda2f915b61/863e1/study.jpg 225w,
/static/373060fa3b201a3b8fe80fda2f915b61/20e5d/study.jpg 450w,
/static/373060fa3b201a3b8fe80fda2f915b61/8e1fc/study.jpg 900w,
/static/373060fa3b201a3b8fe80fda2f915b61/ad68d/study.jpg 1170w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;소개를 보면 코틀린을 처음 접하는 상황에서 진행하기는 버거워 보였지만 일단 신청했다.&lt;br&gt;
실제로 쫓아가기도 바빳고, 좋은 이야기들을 많이 해주셨는데 소화하기도 힘들었던 것 같다.&lt;/p&gt;
&lt;p&gt;총 13주간 진행한 &lt;a href=&quot;http://www.yes24.com/Product/Goods/115221699&quot;&gt;자바에서 코틀린으로&lt;/a&gt;스터디를 회고해보려 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/java-to-kotlin/wiki&quot;&gt;모임 내용 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;코틀린을 몰라도 따라갈 수 있나?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://kotlinlang.org/docs/data-science-overview.html&quot;&gt;공식문서&lt;/a&gt;에서 &lt;code class=&quot;language-text&quot;&gt;&quot;Kotlin은 간결하고 읽기 쉽고 배우기 쉽습니다.&quot;&lt;/code&gt; 라고하듯이 문법을 배워서 작성하는 것은 자바를 사용했다면 코틀린 자체를 빨리 이해할 수 있는 것은 맞다.&lt;br&gt;
하지만 &lt;strong&gt;코틀린을 코틀린답게, 객재지향 프로그래밍과 함수형 프로그래밍을 적절히 선택하여 코틀린답게 작성하는 것은 매우 어려운일이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;함수형 프로그래밍의 &lt;code class=&quot;language-text&quot;&gt;ㅎ&lt;/code&gt;도 모르는 입장에서&lt;/em&gt; 코틀린의 문법과 코틀린이 해결하려는 문제를 동시에 이해하기는 힘들었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://overreacted.io/ko/algebraic-effects-for-the-rest-of-us/&quot;&gt;대수적 효과&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://overcurried.com/3%EB%B6%84%20%EB%AA%A8%EB%82%98%EB%93%9C/&quot;&gt;모나드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;딱딱한 자바만 사용하다가 코틀린을 만나면서 자바에 비해 문제를 해결하는 문법적인 방법이 많다는 것을 느꼈다.&lt;br&gt;
이 책은 코틀린의 기능을 친절하게 설명해주는 책은 아니고 자유도가 높은 만큼 &lt;strong&gt;저자 입장에서&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;자바를 코틀린으로 전환&lt;/code&gt;하거나, &lt;code class=&quot;language-text&quot;&gt;자바와 코틀린의 기능적 차이&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;코틀린의 특정 기능을 사용하는 목적과 이유&lt;/code&gt;를 설명하려 하는 것 같다.&lt;/p&gt;
&lt;p&gt;코틀린에 대한 이해가 깊고 함수형 프로그래밍에 대해 관심을 가지는 인원들과 같이 하는것이 아니라면 해당 책을 읽기에는 놓치는 것이 많을 것 같다.&lt;br&gt;
혼자 책으로 공부하려 했다면 이 책보다는 &lt;a href=&quot;http://www.yes24.com/Product/Goods/55148593&quot;&gt;코틀린 인 액션&lt;/a&gt; 이나 &lt;a href=&quot;http://www.yes24.com/Product/Goods/107698728&quot;&gt;코틀린 완벽 가이드&lt;/a&gt;로 진행했을 것 같다.&lt;/p&gt;
&lt;h3&gt;책을 읽고 얻은 것은?&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;코틀린에서 널과 예외를 대처하는 자세&lt;/li&gt;
&lt;li&gt;코틀린의 함수 타입과 개념 (고차함수, 일급함수) , 람다와 친해지기&lt;/li&gt;
&lt;li&gt;예외를 &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt;와 같이 값으로 반환하는 예외를 다루는 방법 (+ &lt;a href=&quot;https://github.com/npryce/result4k&quot;&gt;Result4K&lt;/a&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;/li&gt;
&lt;li&gt;값 이론과 객체 전파, 방어적 복사&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;아는만큼 보인다라는 말이 있듯이, 자바 함수형 프로그래밍과 스트림을 정확하게 이해하지 못하는 수준인 나의 입장에서 얻은 것들은 이 정도가 대표적인 것 같다.&lt;/p&gt;
&lt;p&gt;코틀린의 기능을 &lt;code class=&quot;language-text&quot;&gt;언제&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;어떤 기준&lt;/code&gt;으로 사용하는지 주관적인 기준이 생겼다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;마법같은&lt;/em&gt; 코틀린 컴파일러를 통해 컴파일된 class를 자바 코드로 디컴파일해서 확인하면서 학습하는 것이 큰 도움이 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;모임을 참여하고 얻은 것은?&lt;/h3&gt;
&lt;p&gt;책을 이렇게 음미하면서 잘근잘근 씹어먹는 스터디는 처음이였다.&lt;br&gt;
아마 코틀린과 함수형 프로그래밍에 능통하신 맹대표님과 책을 번역하신 오현석님, 그리고 백지훈님이 계셔서 가능하지 않았을까 싶다.&lt;br&gt;
&lt;em&gt;세 분이 토론하면 살벌하다&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;이번 모임을 통해 &lt;strong&gt;책을 비판적으로 읽는 시각&lt;/strong&gt;과 &lt;strong&gt;읽기 모임의 정석&lt;/strong&gt;을 경험한 것 같다.&lt;br&gt;
지식을 습득하려는 목적으로 책을 읽으면서 책의 내용에 항상 순응하고 &lt;code class=&quot;language-text&quot;&gt;&quot;오 이렇게 쓰나보다&quot;&lt;/code&gt; 하고 넘어가는 부분들에 대해, 사람들이 모여 진지하게 토론하는 자체가 좋은 경험이였다.&lt;br&gt;
목표는 두 시간에 두 개의 장을 나가는 것이 목표였지만, 두 시간을 넘기거나 두 장을 못 나가는 경우도 허다했다.&lt;br&gt;
그만큼 토론에 진심인 분들을 보며 &lt;code class=&quot;language-text&quot;&gt;&quot;책을 이렇게 까지 읽을 수 있구나, 이 주제로 이 정도의 얘기를 할 수 있구나&quot;&lt;/code&gt;를 느꼈다.&lt;br&gt;
&lt;em&gt;해당 토론에서 튀어나오는 키워드들만 주워 먹기도 벅찼다.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;핵심&lt;/h3&gt;
&lt;p&gt;책을 다 읽고나니 스터디 초중반에 말씀해주셨던 아래의 내용이 책을 관통하는 내용이였다는 것을 알 수 있었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;코틀린은 &lt;code class=&quot;language-text&quot;&gt;가변성&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;OOP&lt;/code&gt;를 바탕에 깔고 있는 언어이기 때문에 아에 가변성을 쓰지 않을 이유는 없다&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;전체적인 프로젝트의 흐름은 객체지향을 쓴다&lt;/li&gt;
&lt;li&gt;객체지향을 쓸 때도 꼭 필요한 경우가 아니라면 가변 필드 + 가변 객체를 쓰는 일은 지양한다&lt;/li&gt;
&lt;li&gt;객체 사이의 통신에 사용하는 메서드는 가능하면 명시적 수단 (파라미터와 반환 값을 사용)을 활용하고 부수효과(side effect)를 만들지 않는다&lt;/li&gt;
&lt;li&gt;한 함수 / 메서드 내부에는 가능한 불변성을 활용한다&lt;/li&gt;
&lt;li&gt;여러 원소를 저장할 컬렉션에 대한 집계, 필터링, 변환 등의 로직을 직접 구현하기 보다는 표준 컬렉션 라이브러리를 활용한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;상태를 경계에 잘 가둬서 처리하느냐 → &lt;strong&gt;객체지향&lt;/strong&gt;&lt;br&gt;
상태 변화를 없애고 값 객체, 불변으로 처리하느냐 → &lt;strong&gt;함수형&lt;/strong&gt;&lt;br&gt;
함수 경계 안에서는 불변객체와 컬렉션을 쓰고 각종 순수 함수를 도우미로 써서 함수형으로 처리하면 좀 더 코드를 쉽게 작성할 수 있고,&lt;br&gt;
전체적으로는 객체지향적으로 사고하면서 모듈간의 결합도를 최소화하고 응집도를 최대화한다는 기본 원칙을 지켜나가는 스타일이 이미 자바 개발자인 분들에게는 최선이 될 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바에서 코틀린으로 옮긴이 &lt;a href=&quot;http://www.yes24.com/24/AuthorFile/Author/192002&quot;&gt;오현석&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;값 컨텍스트로 프로그래밍하면 그 여파가 미치는 곳은 코드의 디자인적인 측면이나 설계까지 전부 영향이가고,&lt;br&gt;
더 나아가 코드를 사용하는 방법 마저도 달라지고 필요한 자료구조도 달라진다.&lt;br&gt;
단지 저장에 해당되는 부분만 값으로 대치한다고 끝이 아니다. 값 컨텍스트를 도입하려면 값을 다루는 모든 프로그래밍 스킬도 배워야 한다.&lt;br&gt;
값과 함수형을 고려할 때는 조직 내 사람이라는 문제를 간과하지 않아야 한다.&lt;br&gt;
값 컨텍스트를 펼치고 함수형 프로그래밍을 팀에 도입한뒤 &quot;넌 이거 왜 못해?&quot;라고 하는 것은 폭력이 될 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rocketpunch.com/@hikamaeng&quot;&gt;맹기완&lt;/a&gt; 대표&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;소감&lt;/h3&gt;
&lt;p&gt;언어에 대해 새로운 인사이트를 얻게해주시고, 경험과 조언들을 아낌없이 쏟아내주신 스터디원 분들과 맹대표님, 오이사님 그리고 스터디에 참석할 수 있게 공유해주신 윤석님에게 감사하다.&lt;br&gt;
스터디에서 주제를 공급하기보단 소비만하는 스터디원이였는데 쫓아내시지 않은것도 감사하다.&lt;/p&gt;
&lt;p&gt;스터디를 하면서 팀원들과 코틀린에 대한 이야기를 몇번 나누었었는데, 코틀린의 기능을 제한적으로 사용할 때도 있고, 자율적으로 사용할 때도 있다.&lt;br&gt;
코딩한다는 행위 자체가 정답이 있는 행위가 아니다. &lt;strong&gt;혼자 일하는 것이 아닌 이상 코드리뷰를 통해 회사 내의 컨벤션을 맞춰가거나 팀원들과 조율해나가야 할 것이다.&lt;/strong&gt;&lt;br&gt;
실무에 코틀린을 사용하면서 진짜 간단한 작성도 고민을 하게 됐으며, &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;이걸 이렇게 사용하면 장단점이 뭐지? 의도가 잘 드러날까?&lt;/code&gt;&lt;/strong&gt; 고민을 하는 자세가 이 모임 덕분이 아닐까 싶다.&lt;br&gt;
&lt;em&gt;코틀린으로 개발하고 있는 나를 뒤에서 스터디원분들이 지켜보고 있는 느낌?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;추가로 &lt;a href=&quot;http://www.yes24.com/24/AuthorFile/Author/192002&quot;&gt;오현석&lt;/a&gt;님이 코틀린 핵심 프로그래밍 책을 내신다고 하셨다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;알고리즘-개정-4판&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;알고리즘 개정 4판&lt;/strong&gt;&lt;a href=&quot;#%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%9C%EC%A0%95-4%ED%8C%90&quot; aria-label=&quot;알고리즘 개정 4판 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 433px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8591e49351da0ec2c43426846cc50881/55fc0/algoStudy.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 81.77777777777779%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAACXklEQVR42n1UV1caQRjdn5MTI1UIJQhSXJYO23uhqMlJ/v/bl7lX9KAmPuxhZ5i5c9usNpwYku0fJExyabZvpVRtSvWmLZV6i7/l2nf5Vr55fa4rjU8fbXQ/lyDOxXIjyQ+PsjV9WaxNvsfpQe7GBkEBXmt0eNBnwNpkupAo3RMwTAqCDycz2Vq+eGEmjh+LGySSFieOF2tLvlxVqOS/DAFoOiHBkuwok+lS1juX8wCEJaYbylWpTqD3DC/HGjZHSpqtGPpRRpBnQFtsL5blxib7RqtHwIqSD9mlMwAOgCUvjMkQUk0nkDg7EGSiLzgHiWDnBimBV1uHczvbVwc6tGJnB4pILj/6Yx549rDgIkjGBjBcKq9CxZb+5ScF/Cinn3/EU+ORPifr09NvegrJ5dqZIQIAEDwMkz3fp/MNGYEZxg5Y4xA1VxyfpNMbcvNlrV491I0VfQNDUPfCVCbGktIPp1+S5Ed6PFbWbEyPgFABhjjgA+BYn5OZ7UUExi9YY85TDAGYFg+cc/yE3tabHXYSDD/UBsVFGJaqBW9MXIihJCMAlBuHABihQTbaAG8RCir1vujaYDSlvK3lsTaQPlYpz1cmD4Es2AAArH3xFmuNxVa+XtfeMry9u2eSWBgle9YATO6VtwDCRsjWZ2v6iLXWubMIDBfgkqWG/mAjagLzwQq+ImlXeQbJgQqrP9QJCiAAAgyBsuQX11DrDcbiq9Rmyx0Nx4fhmV3GEFrdPs0vVRu8JfVGl4HUm10mjDK/kTwY6awLPEM48BKgYAFAvOM/dK/Z7tMzgOD511fnL5AlUa04X/g9AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;algoStudy&quot;
        title=&quot;&quot;
        src=&quot;/static/8591e49351da0ec2c43426846cc50881/55fc0/algoStudy.png&quot;
        srcset=&quot;/static/8591e49351da0ec2c43426846cc50881/3684f/algoStudy.png 225w,
/static/8591e49351da0ec2c43426846cc50881/55fc0/algoStudy.png 433w&quot;
        sizes=&quot;(max-width: 433px) 100vw, 433px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;처음 알고리즘에 관심을 가지게 된 계기가 기업용 코딩 테스트를 통과하기 위해서였다.&lt;br&gt;
코딩 테스트를 통과하기 위한 알고리즘 공부는 지문에서 키워드를 모으고 최대 입력, 허용 가능한 시간과 공간을 확인해서 적합한 알고리즘을 판단하는 연습의 반복이다.&lt;br&gt;
코딩 테스트를 위한 알고리즘 공부는 이 책에 비해 얕고 넓다. 이와 같은 학습은 사용하는 알고리즘과 자료구조는 늘어나지만 자료구조 구현 레벨까지 학습을 하지않아 단편적인 공부에 해당한다.&lt;/p&gt;
&lt;p&gt;하지만 이 책은 &lt;strong&gt;추상화 데이터 타입, API의 개념 그리고 시간복잡도부터 천천히 빌드업하여 자료구조와 알고리즘을 설명해나간다.&lt;/strong&gt;&lt;br&gt;
알고리즘의 문제해결에 집중하다보면 절차지향적으로 작성하게 되는데, 저자는 자료구조를 구현할 때 API 목록을 먼저 뽑아내고 그에 맞게 구현해나가는 것도 인상깊었다.&lt;br&gt;
자료구조와 알고리즘만 설명해주려하기 보다는 &lt;strong&gt;외부 클라이언트 코드와 내부 구현을 분리&lt;/strong&gt;하려는 자세도 학습시켜주려 하는 것 같았다.&lt;br&gt;
그리고 알고리즘과 자료구조의 변화 과정과 다양한 구현 방법을 설명해줘서 많은 생각을 할 수 있었던 것 같다.&lt;br&gt;
아래의 이미지는 책을 통해 배운 내용들이다. (책에서 제공하는 목차와 다를 수 있다.)&lt;br&gt;
&lt;span style=&quot;color:red&quot;&gt;붉은색 박스&lt;/span&gt;는 &lt;span style=&quot;color:red&quot;&gt;알고리즘&lt;/span&gt;을 의미하고, &lt;span style=&quot;color:blue&quot;&gt;푸른색 박스&lt;/span&gt;는 &lt;span style=&quot;color:blue&quot;&gt;자료구조&lt;/span&gt;를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4f282f0b08256ea94bc89a51d2a3009f/07d7d/algorithm.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.77777777777777%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABxElEQVR42o1Ty27TUBDNt7Nkxyew6JJFhcSKBRJs2qoSEijUDU4DiePYvo0f9/3y6dgNyAmt6FyNrq41OnPmnPEMp9H36GMccxrBB9wmKzSNOJT1eCpmp2BDSFaBLX/C9wE2eli6uZLYFXs0XKNqLTrt/w9oeQdR5OCUzWKBs/VnnG2/4M3qAz5mX6F4QKvMyLTIqymHY0BtIwR1NMYjXW6xL0q0eQYT3ZiaspMcm18ZGkYst2uIsnieoTIOLZewLqAjBp706kM40dCjWvwAzzfjO/oXjPyPP5MzvHR9D9PWBHyD3c338duTgINjU9dYLSGUPSqM5LroOighcTdPsEmS5wH/hG4biKZGxhRYI8llDx8DHDmtnR115pS3aYYyLxFI1+jdI/Bh1WaDs80uh5MCjEZhqyWkiai5wOv0HG+zT3iVvsP77BKx5RA0tqx2uLu+wPL6EnW2htcK4p5BEZmZEx26Na3B/BsUFap6T64Pjls42r9hF4fbWAMrOIIxY9P06gLtdjPR+zHHkf2hWLIShjRqBK2KOXZxGKerSpKlxpaaF8mcmGloKWkD3MtcPo2hqaM/pv69gt4zeotx1GD0X8AHZAahDZIeRnkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;algorithm&quot;
        title=&quot;&quot;
        src=&quot;/static/4f282f0b08256ea94bc89a51d2a3009f/1cfc2/algorithm.png&quot;
        srcset=&quot;/static/4f282f0b08256ea94bc89a51d2a3009f/3684f/algorithm.png 225w,
/static/4f282f0b08256ea94bc89a51d2a3009f/fc2a6/algorithm.png 450w,
/static/4f282f0b08256ea94bc89a51d2a3009f/1cfc2/algorithm.png 900w,
/static/4f282f0b08256ea94bc89a51d2a3009f/21482/algorithm.png 1350w,
/static/4f282f0b08256ea94bc89a51d2a3009f/07d7d/algorithm.png 1478w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그리고 각 챕터마다 &lt;strong&gt;연습문제&lt;/strong&gt;, &lt;strong&gt;창의적인 문제&lt;/strong&gt;, &lt;strong&gt;실험&lt;/strong&gt; 으로 몇십문제씩 제공된다. 책에서 해설을 제공하고 있진 않지만 어떤 분이 &lt;a href=&quot;https://github.com/reneargento/algorithms-sedgewick-wayne&quot;&gt;모든 문제에 대한 해설을 작성한 레포&lt;/a&gt;를 만드셔서 참고하면서 봐도 좋다.&lt;br&gt;
이번 책을 통해서 평소에 자주 사용하지만 내부 구현은 확실히 몰랐던 &lt;strong&gt;정렬&lt;/strong&gt;과 &lt;strong&gt;우선순위 큐&lt;/strong&gt;, &lt;strong&gt;균형 트리&lt;/strong&gt;, &lt;strong&gt;문자열 패턴 매칭&lt;/strong&gt;을 학습하게 됐다.&lt;br&gt;
겨우 1회독이라서 책에서 소개하는 많은 알고리즘과 자료구조, 연습문제들을 모두 이해하지는 못 했지만 알고리즘과 자료구조에 대해 흥미가 다시 생긴 것 같다.&lt;br&gt;
&lt;strong&gt;이제 인덱스 우선순위 큐, 2-3트리, 레드블랙트리, B-트리, 문자열 패턴 매칭, 정규표현식 매칭 알고리즘을 순서대로 다시 학습하여 완벽히 이해하고 넘어가는 것이 목표이다.&lt;/strong&gt;&lt;br&gt;
아래는 읽으면서 정리한 내용이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/%EC%A0%95%EB%A0%AC.md&quot;&gt;정렬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/%ED%95%B4%EC%8B%9C%ED%85%8C%EC%9D%B4%EB%B8%94.md&quot;&gt;해시 테이블&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/%EA%B7%B8%EB%9E%98%ED%94%84.md&quot;&gt;그래프&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/%ED%8A%B8%EB%9D%BC%EC%9D%B4.md&quot;&gt;트라이&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84%20%ED%81%90.md&quot;&gt;우선순위 큐&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;공부할 주제는 차고 넘치는데 &lt;code class=&quot;language-text&quot;&gt;알고리즘을 왜 공부하는지&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;실무에 직접적인 도움이 되는지&lt;/code&gt; 궁금한 분들도 있을 것이다.&lt;br&gt;
도메인이나 직무에 따라 다르겠지만 이때까지 실무에 BFS와 DFS를 사용한 적이 딱 한 번 밖에 없어서 다른 개발자에게 선뜻 추천하기는 쉽지않은 것 같다.&lt;br&gt;
이 책은 총 932페이지인 만큼 두껍고, 이해하기 어려운 내용들도 많고, 시간도 많이 걸리는데&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;이 책을 공부할 시간에 실무에 맞닿아있는 기술 스택을 공부하는게 더 효과적이지 않나?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;실제로 알고리즘과 자료구조는 많은 곳에서 사용되고 있긴하지만, 그렇다고 해서 &lt;code class=&quot;language-text&quot;&gt;사용만 잘 하면되지 우리가 내부 구현을 다 알아야 돼?&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;라고 물어볼 수 있다. 책을 다 읽고 난 입장에서 봐도 &lt;strong&gt;맞는 말이다.&lt;/strong&gt; 실무에 도움이 안될지도 모르는 이 책을 공부하기에는 시간 낭비일 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 개인적인 생각으로는 알고리즘과 자료구조도 네트워크, 운영체제, 데이터베이스와 같이 &lt;strong&gt;유통기한이 긴 지식&lt;/strong&gt;에 해당한다고 생각한다.&lt;br&gt;
이 유통기한이 긴 지식은 &lt;strong&gt;건축 기초공사&lt;/strong&gt;에 비유하곤 하는데, &lt;strong&gt;&quot;건축물을 지탱할 수 있도록 단단한 지면을 만드는 것 처럼, 개발 지식도 확장하기 위해서는 지반을 단단히 다져야한다&quot;&lt;/strong&gt; 라고 막연하게 믿고있다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;막연하다&lt;/code&gt;라고 표현한 이유는 이런 학습들은 당장 확인할 수 없는 &lt;strong&gt;복리 효과&lt;/strong&gt;이기 때문이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;복리의 힘은 오랜 시간 (10년 이상) 동안 꾸준히 비가 오나 눈이 오나 해야 효과가 보이기 시작한다.&quot;&lt;br&gt;
&quot;자꾸 잘하는지 도움이 되는지 확인하지 않는 것이 중요하다. 그냥 하는 거다.&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/posts/activity-7091133720857882624-Ek9Q/?utm_source=share&amp;#x26;utm_medium=member_desktop&quot;&gt;출처 : 기용님의 &quot;커리어를 긴 호흡으로 바라보기&quot;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&quot;누구나 프로그래머가 될 수 있지만 누구나 좋은 프로그래머가 될 수 있는 것은 아니다.&quot;&lt;br&gt;
&quot;자동차를 잘 운전하기 위해 꼭 차를 조립할 수 있어야 하는 것은 아니지만, 최상위 클래스 F1 드라이버가 되려면 드라이빙에 필요한 신체나 정신적인 능력은 물론 레이스에 적합하게 차를 설정하기 위한 다양한 공학적 지식도 필요한 것 처럼, 좋은 프로그래머가 되기 위해서는 도메인 지식은 물론 컴퓨터의 동작 원리나 컴퓨터공학 전반에 대해 잘 이해할 필요가 있다.&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 권으로 읽는 컴퓨터 구조와 프로그래밍 옮긴이 &lt;a href=&quot;https://www.kyobobook.co.kr/service/profile/information?chrcCode=1000477804&quot;&gt;오현석&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://outstanding.kr/bootcamp20230914&quot;&gt;아웃 스탠딩의 &quot;왜 부트캠프는 개발자 인력난을 해소하지 못하는 걸까요&quot;&lt;/a&gt; 글에서도 비전공자를 긁지않은 복권에 비유하면서 &lt;strong&gt;CS 이론&lt;/strong&gt;에 대한 이야기가 나온다. 유료 글이라 내용을 인용하기가 힘드니 회원가입하고 무료로 한번 읽어보기를 추천한다.&lt;/p&gt;
&lt;p&gt;이 책 한 권을 읽었다고 해서 코드 퀄리티가 드라마틱하게 상승하고, 시간과 공간을 효율적으로 작성하고 해결하려는 문제에 맞는 자료구조를 적절히 선택한다는 보장은 없다.&lt;br&gt;
알고리즘이라는 것이 실무에서는 몰라도 큰 상관이 없을 때도 많다. 실무에서는 유지보수성과 협업이 중요한데, 성능은 좋지만 필수적이지 않은 곳에 복잡한 알고리즘을 작성하여 협업 능력을 떨어뜨릴 수 있다.&lt;br&gt;
하지만 &lt;strong&gt;현재 업무 환경에서 필요없다고 해서 배우지 않아도 되는것은 아니지않을까?&lt;/strong&gt; 알고리즘 자체를 등한시한다면 실제로 필요한 곳에 적용할 시도조차 못하게 되니 &lt;strong&gt;복리 효과&lt;/strong&gt;를 기대하고 싶다면 한 번은 읽어보는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 16.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAl0lEQVR42lWO6w6CMAyF9/5vpzEqg8EusI1bEAwikeTYDaPxx5f2NO3pYdYIGJFBnA6w4gidX0if0ZRZxGkOpzi8SSNBP6f6y3pviL0uowfb5g51KaEEh1UJvE6jQVVcYWUSZ20laM4joW+rHL0rCIma9oMOz+fBgmEd0NkcmlJFE7UfBtPArdF40dNfmn+W8ZOW6vbo8Qa/at4/am3cGQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;recommend&quot;
        title=&quot;&quot;
        src=&quot;/static/3694a786b3140ea3082a17bd7e300210/1cfc2/recommend.png&quot;
        srcset=&quot;/static/3694a786b3140ea3082a17bd7e300210/3684f/recommend.png 225w,
/static/3694a786b3140ea3082a17bd7e300210/fc2a6/recommend.png 450w,
/static/3694a786b3140ea3082a17bd7e300210/1cfc2/recommend.png 900w,
/static/3694a786b3140ea3082a17bd7e300210/0eb6d/recommend.png 913w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;
이 책을 추천하는 이유 중 &lt;a href=&quot;https://en.wikipedia.org/wiki/Robert_Sedgewick_(computer_scientist)&quot;&gt;로버트 세지윅&lt;/a&gt;이 저자라는 점도 한 몫한다.&lt;br&gt;
1회독으로 모두 흡수하기에는 힘들 것 같다. 길게 1~2년 정도 꾸준히 볼만한 가치는 충분한 책인 것 같다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;도메인-주도-설계란-무엇인가&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;도메인 주도 설계란 무엇인가?&lt;/strong&gt;&lt;a href=&quot;#%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EC%84%A4%EA%B3%84%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot; aria-label=&quot;도메인 주도 설계란 무엇인가 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;왜 DDD가 오늘날 더욱 중요하다고 생각하십니까?&lt;/strong&gt;&lt;br&gt;
옛날에는 웹 개발이 보편적이지 않기도 했고, 일반적인 기능의 웹 개발을 할 때도 신경써야할 부분이 많았습니다.&lt;br&gt;
지금은 웹 사용의 기초적인 수준에 대해서 많은 사람들이 이해하고 개발자가 비즈니스에 집중할 수 있도록 도와주는 기술들이 많이 만들어졌습니다.&lt;br&gt;
이러한 환경으로 인해, 많은 사람들이 다시 비즈니스 로직에 도전하기 시작하고 있습니다.&lt;br&gt;
기본적으로 DDD는, 사용자들이 밀접하게 관계되어 있는 도메인 이슈에 집중해야 한다는 원칙을 기반에 둡니다.&lt;br&gt;
도메인이란 우리가 이해하기 위해 혼신의 힘을 다해야만 하는 가장 중요한 부분입니다.&lt;br&gt;
DDD는 팀 전체가 함께하는 거대한 작업이라는 것을 잊지 말아야 하고 모든 것에 적용하려고 하지 마세요.&lt;br&gt;
실험을 많이 하고 실수를 많이 할 것이라고 예상하세요. 모델링은 창조적인 작업입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에릭 에반스&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 책은 도메인 주도 설계라는 주제의 기초를 요약한 소개서이다.&lt;br&gt;
도메인 전문가에게 도메인 지식을 끌어내어 아키텍트가 설계하고, 그 설계한 내용에 따라 개발자가 구현을 해나가는 흐름에 있어, &lt;strong&gt;정확한 도메인 모델링을 어떻게 하고, 그 모델을 코드로 어떻게 변환할 것인가?&lt;/strong&gt; 가 주제이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;엔티티&lt;/strong&gt;, &lt;strong&gt;값 객체&lt;/strong&gt;, &lt;strong&gt;도메인&lt;/strong&gt;, &lt;strong&gt;모델&lt;/strong&gt;, &lt;strong&gt;서비스&lt;/strong&gt;, &lt;strong&gt;모듈&lt;/strong&gt;, &lt;strong&gt;집합&lt;/strong&gt;, &lt;strong&gt;리포지토리&lt;/strong&gt; 등 이 용어에 대해 설명하며 DDD를 전체적으로 (추상적이지만) 훑어준다고 생각하면 좋다.&lt;br&gt;
처음 DDD를 읽을 때 낯선 용어 때문에 힘들었는데, 대략적으로 각 책임들을 설명해줘서 좋았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대략적으로 &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%A0%95%EB%A6%AC/DDD.md&quot;&gt;정리&lt;/a&gt;했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;처음부터 완벽한 모델링은 존재할 수 없으니 때때로 도메인에 대한 새로운 통찰이 생기고 모델간의 관계가 발견될 때, 리팩터링을 통해 설계에 반영되어야 한다고 강조한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;개발자들에게 도메인의 비즈니스 로직은 지루하고 보상이 없는 것일 수 있지만 도메인의 비즈니스 로직은 도메인의 심장이다.&quot;&lt;br&gt;
&quot;만약 핵심 비즈니스 로직이 제 역할을 하지 못한다면, 멋진 기술적 부가 기능은 모두 &lt;strong&gt;무용지물&lt;/strong&gt;이 된다.&quot;&lt;br&gt;
&quot;정교한 도메인 모델과 핵심 도메인은 단번에 만들어지지 않는다. 핵심이 한층 명확하게 통합되기 위해서 정제와 &lt;strong&gt;지속적인 리팩터링&lt;/strong&gt;이 필요하다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;실제로 기술에만 집중했었던 것 같아서 위의 내용을 읽고 뜨끔했었다..&lt;br&gt;
DDD에 관심을 가지고 있다면 웜업 느낌으로 가볍게 읽을 수 있을 것 같아 추천하고 싶다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;프로그래머의-뇌&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;프로그래머의 뇌&lt;/strong&gt;&lt;a href=&quot;#%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EB%87%8C&quot; aria-label=&quot;프로그래머의 뇌 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;프로그래밍은 가장 까다로운 인지 활동 중 하나로 간주된다.&lt;br&gt;
즉, 문제를 추상적으로 해결하는 동시에 프로그램을 작성하는데, 이런 일은 대부분의 사람이 자연스럽게 가질 수 없는 수준의 주의력을 요구한다.&lt;br&gt;
프로그래밍을 하는 동안 일어나는 실수는 수없이 잦다. 우리가 범하는 많은 오류는 인지적 문제에 뿌리를 두고 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 책은 인지과학과 연구 결과에서 얻은 두뇌의 작용을 프로그래밍과 프로그래밍 언어에 대한 맥락에서 설명한다.&lt;br&gt;
새로운 프로젝트를 맡게 되었을 때를 생각해보면 (클린 코드의 유무를 떠나서) 로직과 비즈니스를 이해하기 위해 부단히 노력한다.&lt;br&gt;
이 책을 통해 그 노력안에 엄청 복잡하고 많은 과정이 일어난다는 사실과 그 과정들에 대해 알아갈 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;장기 기억 공간(LTM), 단기 기억 공간(STM), 작업 기억 공간의 &lt;strong&gt;인지 과정&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;코드를 빨리 이해하기 위한 &lt;strong&gt;코드 청킹 연습&lt;/strong&gt; , &lt;strong&gt;시각화를 통한 영상 기억 공간의 활용&lt;/strong&gt; , &lt;strong&gt;절대적인 지식의 양&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;장기 기억 공간은 네트워크 망 구조로 되어 있다.&lt;/li&gt;
&lt;li&gt;긴 간격을 두고 반복 읽기 (반복 학습 하기) → &lt;strong&gt;더 많은 시간을 학습해야 한다는 것이 아니라 더 오랜 간격을 두고 학습해야 한다&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;LTM으로부터 기억을 가져오는 두 가지 서로 다른 기제, &lt;strong&gt;저장 강도 (학습해내는 것)&lt;/strong&gt; 와 &lt;strong&gt;인출 강도 (기억해내는 것)&lt;/strong&gt; → &lt;strong&gt;저장하는 것보다 그것을 얼마나 잘 발견하느냐, 인출 강도에 집중해야 한다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;청킹되지 않는 문제를 풀려고 할 때 발생하는 과부하 상태 &lt;strong&gt;인지 부하의 종류&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;초급자와 숙련자는 코드를 읽는 방식부터 다르다 → 이해 전략으로 &lt;strong&gt;시각화(상태표 등)&lt;/strong&gt; , &lt;strong&gt;중요한 부분을 스스로 결정하고 스스로 질문하기&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;네이밍에 대한 강조&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/89649360&quot;&gt;리팩터링&lt;/a&gt; 코드 스멜이 인지 부하를 초래하는 이유&lt;/li&gt;
&lt;li&gt;초급 프로그래머와 &lt;strong&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%94%BC%EC%95%84%EC%A0%9C%EC%9D%98_%EC%9D%B8%EC%A7%80_%EB%B0%9C%EB%8B%AC%EB%A1%A0&quot;&gt;피아제의 인지 발달론&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;인지 과정에서 일어나는 많은 과정에 대해 이해하는 것은 많은 도움이 될 것이라 생각한다.&lt;br&gt;
만약 복잡한 문제 또는 이해하기 힘든 코드 구조를 맞닥뜨렸을때 스트레스를 받거나 화내기 보다는 &lt;code class=&quot;language-text&quot;&gt;&quot;청킹이 잘 안됐나?&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;어떤 부분에서 인지 부하가 온거지?&quot;&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;&quot;내가 어떤 지식이 부족하지?&quot;&lt;/code&gt; 라는 생각을 할 수 있는 돌파구를 찾아준 느낌이다.&lt;br&gt;
그리고 내가 쓴 글이나 이미 읽은 책은 잘 안읽는 경향이 있는데, 이 책을 읽고 조금 &lt;strong&gt;간격을 두고 반복적으로 읽어야함을 느꼈다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;위의 내용 중에 흥미가 생기는 부분이 있거나 이해하기 힘든 문제를 만났을 때 어떻게 해야할지 모른적이 있다면 이 책을 읽어보기를 추천한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;업무-시각화&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;업무 시각화&lt;/strong&gt;&lt;a href=&quot;#%EC%97%85%EB%AC%B4-%EC%8B%9C%EA%B0%81%ED%99%94&quot; aria-label=&quot;업무 시각화 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;시각을 활용하면 문제를 명확히 할 수 있고, 의사결정이 쉬워진다.&lt;br&gt;
인간의 두뇌는 시각으로 인지한 것에서 &lt;strong&gt;패턴과 구조를 찾도록 설계됏기 때문에&lt;/strong&gt; 시각화는 업무를 개선할 수 있는 가장 기본적인 방법 중 하나다.
업무에 시각화를 적용하기 위해 시각화를 설계하는 것은 업무 흐름을 개선할 수 있는 방법을 찾기 위한 &lt;strong&gt;실험&lt;/strong&gt;이다.&lt;br&gt;
칸반보드를 설계하는 데 &apos;모범 사례&apos;가 실제로 존재하지 않는 이유다.&lt;br&gt;
칸반보드와 기타 발표 자료를 만들 때 흥미를 끌 수 있게 시각화해 사람들의 참여를 유도해야 하고, 실험을 통해 자신의 상황과 조직에 더 적합한 사례를 찾는 것이 더 나은 방법이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;실무에서 시각화(칸반 등) 정보를 토대로 업무를 진행하고 있지 않고, 칸반에 익숙하지 않아 책 내용이 많이 와닿지 않은 것 같다.&lt;br&gt;
그래도 다른 스터디원들의 경험이나 회사에서 어떤 식으로 사용하는지들을 들을 수 있어서 좋았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;너무 많은 진행 중 업무&lt;/li&gt;
&lt;li&gt;계획에 없던 업무&lt;/li&gt;
&lt;li&gt;방치된 업무&lt;/li&gt;
&lt;li&gt;상충하는 우선순위&lt;/li&gt;
&lt;li&gt;알려지지 않은 의존성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위 다섯 가지의 이유 때문에 업무가 지연된다고 이야기한다.&lt;br&gt;
시각화를 통해 업무가 지연되는 이유들을 구성원들에게 더 잘 전달할 수 있고 시각화한 정보가 정확한 통계치가 되기 때문에 설득하기가 더 쉽다고 한다.&lt;br&gt;
그리고 시각화를 통해 어딘가에 기록을 하면 &quot;내가 뭘 빠뜨리지 않았나?&quot;, &quot;이런것도 해야 되는데&quot; 라는 생각이 들지 않아 현재 진행하는 한 가지의 일에만 집중할 수 있다.&lt;/p&gt;
&lt;p&gt;지금 당장 시각화를 통해 협업을 할 수 없으니 개인적으로 업무와 개인 계획 내용을 칸반으로 만들어 사용해보고 있다.&lt;br&gt;
어딘가에 기록해놓으니 확실히 마음이 편안해지는 느낌을 느꼈다.&lt;br&gt;
책을 읽고 시각화의 효과와 (개인적으로) 칸반을 시작하게 되었으니 나에겐 굉장히 효과적인 책이였다.&lt;/p&gt;
&lt;p&gt;이 책은 혼자 읽기 보다는 서로 다른 회사를 다니는 스터디원들과 같이 읽기를 추천한다.&lt;br&gt;
칸반에 대해 궁금하거나 업무 프로세스를 개선하고 싶은 생각이라면 이 책을 추천한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;데브옵스-핸드북&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;데브옵스 핸드북&lt;/strong&gt;&lt;a href=&quot;#%EB%8D%B0%EB%B8%8C%EC%98%B5%EC%8A%A4-%ED%95%B8%EB%93%9C%EB%B6%81&quot; aria-label=&quot;데브옵스 핸드북 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;(경험이 아직 부족해서 공감가는 내용이 많지 않았지만) 인상깊은 내용들도 많고, 실제 기업의 개선 사례도 설명해줘서 재밌었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기술 부채를 해결하지 않는 조직은 일상 업무에서의 문제가 해결되지 않은 채로 작업을 하다가 부담이 가중돼 더 이상 새로운 작업을 완료할 수 없게 된다. &lt;strong&gt;즉, 기술 부채의 현재 이자만 상환하게 된다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;스페셜리스트보단 제네럴리스트가 되어라.&lt;/li&gt;
&lt;li&gt;팀 규모를 작게 유지하라 (피자 두 판의 법칙)&lt;/li&gt;
&lt;li&gt;&quot;자동화 테스팅 없이 코드를 작성하는 경우, 코드를 테스트하기 위해서는 더 많은 시간과 비용이 소비된다.&quot;&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;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%A0%95%EB%A6%AC/%ED%85%8C%EC%8A%A4%ED%8A%B8.md&quot;&gt;테스트 코드에 대해&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;핵심은 문제를 빠르게 발견하고 수정하도록 빠른 피드백을 받을 수 있는 (기술적,인적) 환경을 만드는 것이다.&lt;/strong&gt;&lt;br&gt;
데브옵스가 정확히 뭐지? CI/CD가 뭐지? 글로벌 회사는 데브옵스를 어떻게 적용할까? 라는 고민을 한적 있다면 추천하고 싶다.&lt;br&gt;
주니어가 읽어도 좋지만 리드급이 읽으면 더 좋을 것 같다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;한-권으로-읽는-컴퓨터-구조와-프로그래밍&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;한 권으로 읽는 컴퓨터 구조와 프로그래밍&lt;/strong&gt;&lt;a href=&quot;#%ED%95%9C-%EA%B6%8C%EC%9C%BC%EB%A1%9C-%EC%9D%BD%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0%EC%99%80-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot; aria-label=&quot;한 권으로 읽는 컴퓨터 구조와 프로그래밍 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;간략하게 &lt;a href=&quot;https://github.com/jdalma/footprints/tree/main/%ED%95%9C%20%EA%B6%8C%EC%9C%BC%EB%A1%9C%20%EC%9D%BD%EB%8A%94%20%EC%BB%B4%ED%93%A8%ED%84%B0%20%EA%B5%AC%EC%A1%B0%EC%99%80%20%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C&quot;&gt;정리&lt;/a&gt;하면서 읽었다.&lt;/p&gt;
&lt;p&gt;추천의 글과 옮긴이의 말을 보면서 자극 만땅으로 받고 책을 폈지만 중후반부터 정신이 혼미해진다.&lt;br&gt;
저자는 이 책에서 많은 기초적인 내용을 다룬다고 설명한다.&lt;/p&gt;
&lt;p&gt;초반에서 중반까지는 전자 회로부터 빌드업하여 내부구조, 컴퓨터 아키텍처와 운영체제까지 얕게 설명해준다.&lt;br&gt;
고민조차 할 수 없었던 이야기들이 나온다. 예를 들어 &lt;code class=&quot;language-text&quot;&gt;실제로 비트는 어떻게 저장되나?&lt;/code&gt; 와 같은 고민이다.&lt;/p&gt;
&lt;p&gt;중반에서 후반까지는 좋은 내용도 많지만 갑자기 이런 내용이 왜 나오지? 하는 내용들이 있다.&lt;br&gt;
저자 말대로 넓고 얕은 내용을 설명하여 관심있는 주제는 얕아서 문제고 특정 내용은 기반지식이 없으면 이해조차 할 수 없는 챕터도 있었다.&lt;/p&gt;
&lt;p&gt;그래고 컴퓨터 구조 책을 처음으로 다 읽어봐서 뿌듯하다&lt;br&gt;
&lt;strong&gt;각 잡고 읽는 책이기 보다는 출퇴근할 떄 가볍게 읽어도 될, 컴퓨터 구조라는 학문을 시작하는 책이라고 생각한다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;토비의-스프링-부트---이해와-원리&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;토비의 스프링 부트 - 이해와 원리&lt;/strong&gt;&lt;a href=&quot;#%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8---%EC%9D%B4%ED%95%B4%EC%99%80-%EC%9B%90%EB%A6%AC&quot; aria-label=&quot;토비의 스프링 부트   이해와 원리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;큰 맥락으로 보면 &lt;code class=&quot;language-text&quot;&gt;@SpringBootApplication&lt;/code&gt; 어노테이션이 제공해주는 마법같은 일들을 알려주는 강의이다.&lt;/p&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실무에서 대충 감으로 이해하던 내용들이 많았다.&lt;br&gt;
스프링 부트가 뭐냐고 물어보면 &quot;스프링을 편하게 써줄 수 있게 해주는 프레임워크&quot;라고만 생각했었는데 조금 더 자세하게 설명해줄 수 있을 것 같다.&lt;/p&gt;
&lt;p&gt;처음 접한다면 모르는 기술적인 단어들이 많이 나와서 스프링과 자바에 익숙하지 않다면 힘들수도 있을 것 같긴 하지만&lt;br&gt;
&lt;strong&gt;스프링 부트의 컨셉과 구성 정보를 관리하는 구조에 대해 알 수 있어 굉장히 좋은 강의였다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;http-완벽-가이드&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;HTTP 완벽 가이드&lt;/strong&gt;&lt;a href=&quot;#http-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C&quot; aria-label=&quot;http 완벽 가이드 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;해당 책의 스터디는 &lt;a href=&quot;https://github.com/SeolYoungKim/http-definitive-guide-study/issues?q=is%3Aissue+is%3Aclosed&quot;&gt;이슈 기반&lt;/a&gt;으로 진행되었다.&lt;br&gt;
대략적인 &lt;a href=&quot;https://github.com/jdalma/footprints/tree/main/HTTP%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C&quot;&gt;정리&lt;/a&gt;를 하면서 읽었다.&lt;/p&gt;
&lt;p&gt;책이 &lt;code class=&quot;language-text&quot;&gt;2002년&lt;/code&gt;에 출간되었고, 번역본은 &lt;code class=&quot;language-text&quot;&gt;2013년&lt;/code&gt;에 출간된 만큼 요즘이랑 비교하면서 읽기에는 무리가 있다.&lt;br&gt;
하지만 많은 사람들이 추천하는 이유가 있다.&lt;/p&gt;
&lt;p&gt;전반적인 네트워크 지식을 익힐 수 있고, &lt;code class=&quot;language-text&quot;&gt;HTTP&lt;/code&gt;가 진화하면서 기존 근간 기술들은 변하지 않기 때문에 읽을 가치는 충분하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;TCP 커넥션에 대한 개념&lt;/li&gt;
&lt;li&gt;프록시, 게이트웨이, 터널에 대한 이해&lt;/li&gt;
&lt;li&gt;HTTP 헤더에 대한 이해&lt;/li&gt;
&lt;li&gt;I/O 아키텍처에 대해 얕게..&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 내용이 대표적으로 생각나는 것 같다.&lt;br&gt;
혼자서 읽었으면 진작에 포기했을 것 같은데 다른 분들과 같이 질문을 준비하고 토론을 하면서 읽으니 잘 버틴 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;코틀린 스터디로 인해 13장까지 읽고 도중 하차하여 14,15,18,20장만 추후에 읽어야겠다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;대체-뭐가-문제야&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;대체 뭐가 문제야?&lt;/strong&gt;&lt;a href=&quot;#%EB%8C%80%EC%B2%B4-%EB%AD%90%EA%B0%80-%EB%AC%B8%EC%A0%9C%EC%95%BC&quot; aria-label=&quot;대체 뭐가 문제야 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;문제란 바라는 것과 인식하는 것 간의 차이다.&quot;&lt;br&gt;
&quot;우리는 문제를 해결하려고만 하지 문제의 본질과 문제를 해결했을 때 생길 수 있는 다른 문제들에 대해서 생각하지 않는다&quot;&lt;br&gt;
&quot;문제를 정확한 정의를 내렸다고 결고 확신하지 말라. 그러나 그것을 얻기 위한 노력은 계속해야 한다.&quot;&lt;br&gt;
&lt;strong&gt;&quot;문제를 이해할 때, 잘못될 수 있는 경우를 적어도 세 가지 이상 생각해 내지 못한다면 당신은 문제를 이해하지 못한 것이다.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;나는 문제를 정의하는데 시간을 보내기보다는 거의 성급하게 해결안을 찾는 것에 매달린다.&lt;br&gt;
해결안을 찾는 도중에 발생하는 문제들에 대해 몰두하게 되어 본질적인 문제를 잊기도 한다.&lt;/p&gt;
&lt;p&gt;이 책은 &lt;code class=&quot;language-text&quot;&gt;무엇이 문제인가&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;그것은 어떤 문제인가&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;누구의 문제인가&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;문제는 어디에서 비롯되는가&lt;/code&gt;를 설명한다.&lt;br&gt;
문제를 인식하고 그 문제를 해결하기 위한 방법이 낳는 다른 문제들에 대한 인식, 내가 세운 이 해결책이 진짜 문제를 해결하는 것인지, 내가 인식하지 못한 문제들이 더 존재하는지, 내가 세운 해결책이 도덕성과 관련있는지에 대해 사례를 통해 설명한다.&lt;/p&gt;
&lt;p&gt;책의 내용 중 엘리베이터 얘기가 인상 깊은데, 문제를 건의한 사람이 제안한 문제 해결책 중에서 말도 안되는 &lt;strong&gt;&quot;옆 건물의 엘리베이터 시간을 훔치자&quot;&lt;/strong&gt; 라는 제안을 한다.&lt;br&gt;
이 말도 안되는 제안은 (마지막 즈음에 밝혀지지만) 옆 건물 주인도 바라던 것이였다. 회사에서 이런 비슷한 경험이 있는데 나는 실없는 해결책인 줄 알았지만 그런 것도 한 방법이라고 얘기를 해주셨다.&lt;br&gt;
&lt;strong&gt;&quot;유머감각이 없는 사람의 문제를 해결하려고 노력하지 말라.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=3H4umWD5bwI&amp;#x26;t=9s&amp;#x26;ab_channel=EO%EC%9D%B4%EC%98%A4&quot;&gt;우아한 형제들 김범준 대표의 EO 영상&lt;/a&gt;에서 나오는 엘리베이터 이야기가 (이 책을 인용했는지는 모르지만)이 책에서 등장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;문제를 &lt;code class=&quot;language-text&quot;&gt;단수에서 복수로 보는 사고의 전환&lt;/code&gt;이 필요하고, 문제를 해결하며 &lt;code class=&quot;language-text&quot;&gt;되돌아보는 자세&lt;/code&gt;가 필요하다.&lt;br&gt;
이 책에서 세상에는 두 종류의 사람, &lt;strong&gt;일을 하는 사람&lt;/strong&gt;과 &lt;strong&gt;할 일을 만드는 사람&lt;/strong&gt;이 있다고 한다.&lt;br&gt;
할 일을 만드는 사람이 되지 않아야지&lt;/p&gt;
&lt;p&gt;책이 얇고 읽기 쉬워서 &lt;strong&gt;문제 해결에 대한 고민&lt;/strong&gt;을 한 경험이 있다면 읽을 이유는 충분하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;이너게임&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;이너게임&lt;/strong&gt;&lt;a href=&quot;#%EC%9D%B4%EB%84%88%EA%B2%8C%EC%9E%84&quot; aria-label=&quot;이너게임 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;이너게임에 대한 나의 연구를 한마디로 요약한다면 ‘사람을 좀 더 효과적으로 변화시킬 수 있는 방법을 발견했다’는 것이다.&lt;br&gt;
이너게임의 원리는 테니스에서 포핸드, 백핸드, 또는 서브를 어떻게 바꿀 것인가를 연구하면서 발견되었지만, 이 원리는 테니스 외의 다른 여러 분야에 응용될 수 있다.&lt;br&gt;
이 책은 우리가 일하는 방법을 어떻게 바꿀 것인가에 대한 것이다. 달리 표현한다면, 일이 우리를 위해 존재하게 만드는 방법에 관한 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Timothy_Gallwey&quot;&gt;Timothy Gallwey&lt;/a&gt;는 테니스를 코칭하면서 터득한 코칭의 기술, 집중에 대한 방법과 일터에서 즐거움을 느끼고, 그 즐거움 속에서 배우고 성장하며 성과를 내는 방법, 비평가적 인지에 대해 설명한다.&lt;br&gt;
일반적으로 코칭을 한다는 것은 훈련생은 코치에게 고민을 전달하고 코치는 고민을 해결하기 위해 훈련생을 모델링하며 훈련생은 코치에게 평가를 받는다.&lt;br&gt;
이런 코칭의 단점이 클라이언트는 자신의 문제를 해결하지 못 하는 이유 또는 발생한 이유 등에 집중하여 방어적으로 문제에 접근한다.&lt;br&gt;
이로 인해 클라이언트는 자신의 문제에 대한 인지 수준을 높이지 못한다는 것이다.&lt;br&gt;
이때 코치는 클라이언트가 비평가적 인지를 하도록 유도하며 클라이언트가 스스로 상황을 주도하도록 만들고 인지 수준을 더욱 높이도록 가이드 하는 것이 코칭 효과가 좋다는 것을 깨닫는다.&lt;br&gt;
그렇다고 이 규칙이 테니스에만 적용되는 것이 아니다. 개개인의 일상과 업무 속에서도 적용할 수 있다.&lt;br&gt;
&lt;a href=&quot;https://www.facebook.com/hika00/posts/7116315028383895?ref=embed_post&quot;&gt;이 글&lt;/a&gt;에서 교수가 한 학생을 코치하는 내용을 보면 이너게임에서 말하는 코칭과 유사하다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;간략하게 정리하면 &lt;strong&gt;내부에서 일어나는 셀프1과 셀프2의 싸움에서 셀프1을 무시하고 셀프2에만 집중하여 비평가적 인지를 유지해야 한다는 것&lt;/strong&gt;이다.&lt;br&gt;
셀프1과 셀프2에 대한 정의는 책 전반적으로 많이 설명되는데&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;셀프1 : 지시하고 평가하는 쪽, 타인이 나에게 요구하는 우선순위, 타인에게 인정받으려 하는 욕구&lt;/li&gt;
&lt;li&gt;셀프2 : 자신의 실체, 자신의 내면에 존재하는 본질적인 우선순위,&lt;/li&gt;
&lt;li&gt;비평가적 인지 : 어떤 현상을 평가하지 말고 자신과 자신의 행동을 있는 그대로 느끼고 받아들이는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;책에서 읽은 내용을 일상생활, 업무속에 적용하려 할 때 굉장히 어렵다라고 느꼈다.&lt;br&gt;
나는 매순간을 스스로를 평가하고 책망하였었다는걸 알 수 있었다.&lt;br&gt;
하지만 스스로 평가를 내리는 습관을 &lt;strong&gt;인지&lt;/strong&gt;하는 것이 비평가적 인지를 갖추는 첫 걸음이라고 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;스스로가 항상 부족하고 멍청하다고 책망하고 평가하여 해야하는 일에 대해 집중을 하지 못하고 계획조차 세우지 못한 경험&lt;/li&gt;
&lt;li&gt;일에 집중하기도 힘들고 재미를 느끼기 힘들었던 경험&lt;/li&gt;
&lt;li&gt;일에서 얻을 수 있는 것은 오로지 성과(급여)라고 생각하는 사람&lt;/li&gt;
&lt;li&gt;몰입을 경험하고 싶은 사람&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위의 사항에 해당한다면 이 책을 추천해주고 싶다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;데일-카네기의-자기관리론&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;데일 카네기의 자기관리론&lt;/strong&gt;&lt;a href=&quot;#%EB%8D%B0%EC%9D%BC-%EC%B9%B4%EB%84%A4%EA%B8%B0%EC%9D%98-%EC%9E%90%EA%B8%B0%EA%B4%80%EB%A6%AC%EB%A1%A0&quot; aria-label=&quot;데일 카네기의 자기관리론 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;중요한 일을 앞두고 스트레스를 많이 받았다.&lt;br&gt;
준비해야 할 일을 하지 못하고 걱정과 잡생각만 하면서 계획조차 세우지 못했다.&lt;/p&gt;
&lt;p&gt;데일 카네기는 &quot;인간관계론&quot;으로 처음 알게 됐다. &quot;인간관계론&quot;이나 &quot;자기관리론&quot;이나 뭔가 엄청 대단한 정보를 전달해주기 보다는 읽으면 당연한 것이라고 생각하지만 평소에 놓치고 있는 것들, 깊게 생각하지 않은 것들을 상기시켜주는 내용이라고 느꼈다.&lt;br&gt;
1장을 읽고 공감을 하나도 하지 못한다면 &quot;당연하고 진부한 이야기들을 왜 말하지?&quot;라고 생각할 수 있다.&lt;br&gt;
책에서도 말하지만 위와 같은 생각이 든다면 책을 다 읽게 되더라도 얻는 것은 없으니 책을 쓰레기통에 버리라고 한다.&lt;br&gt;
난 엄청 재밌게 읽은 것 같다. 출퇴근 시간에 책 읽을 생각에 조금 신난 적도 있다.&lt;br&gt;
&lt;strong&gt;평소에 걱정이나 불안을 달고 살거나, 과거나 미래에 집착하고 있다면 이 책을 읽으라고 추천하고 싶다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;아래는 가장 인상 깊은 내용이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;그 어떤 일이라 해도 그 사람이 무한한 열정을 가진 일이기만 하면 그는 성공할 수 있다.&quot;&lt;br&gt;
&quot;자신의 일에서 보수 이외에 다른 아무런 보람도 얻지 못하는 사람이 가장 불쌍한 사람이라고 생각합니다.&quot;&lt;br&gt;
&lt;strong&gt;&quot;자신의 천직을 찾아낸 사람은 축복 받은 사람이다. 더 이상의 축복은 바라지 마라&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;details&gt;
&lt;summary&gt;내용&lt;/summary&gt;
&lt;h3&gt;걱정에 대해 알아야 할 기본 사실&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;첫 번째. 오늘에 충실하라&lt;/strong&gt;&lt;br&gt;
모든 지적 능력과 정열을 다 동원해서 오늘 해야 할 일을 오늘 가장 잘하는 데 집중하는 것이 내일을 가장 잘 준비하는 길이다.&lt;br&gt;
&apos;오늘만의 구획&apos;을 만들어라.&lt;br&gt;
살아가는 단계마다 버튼을 눌러서 강철문이 과거, 즉 이제는 죽어버린 지난날을 격리하는 소리를 들으십시오.&lt;br&gt;
또 다른 버튼을 눌러서 금속 막벽으로 미래, 즉 아직 태어나지 않은 내일을 격리하십시오.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;삶을 모래시계라고 생각하게나, 위에는 수천 개의 모래알이 있지만 그 모래알들은 천천히, 그리고 일정하게 잘록한 부분을 통과하지&quot;&lt;br&gt;
&quot;아침에 일과를 시작할 때 우리가 그날 해야하는 것으로 보이는 일이 수백 개나 되지.&quot;&lt;br&gt;
&quot;하지만 모래시계에서 모래알이 좁은 구멍을 지나가듯이. 한 번에 하나씩, 천천히, 그리고 일정하게 일을 처리하지 않으면 우리는 스스로의 육체적 혹은 정신적 상태를 무너뜨리게 되어 있어&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;우리가 살 수 있는 유일한 시간을 사는 것으로, 지금부터 잠자리에 드는 시간까지 사는 것으로 만족하기로 하자.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;자신의 짐이 아무리 무겁더라도 밤이 올 떄 까지라면 누구나 견딜 수 있다.&quot;&lt;br&gt;
&quot;자기가 해야 할 일이 아무리 힘들더라도 하루동안이라면 누구나 할 수 있다.&quot;&lt;br&gt;
&quot;해가 떨어질 때까지라면 누구나 달콤하게, 참을성 있게, 사랑스럽게, 순수하게 살 수 있다.&quot;&lt;br&gt;
&lt;strong&gt;&quot;이것이 삶이 실제로 의미하는 전부이다.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;아래의 질문에 대답해보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;미래에 대한 걱정으로, 혹은 &apos;눈앞에 있는 장미보다 지평선 너머 어딘가에 있는 마법의 장미 정원&apos;을 고대하며 오늘을 사는 걸 미루는 경향이 있지는 않은가?&lt;/li&gt;
&lt;li&gt;가끔 과거에 일어난 일, 이제는 지나버리고 어쩔 수 없는 일에 대해 후회하느라 괴로워한 적이 있는가?&lt;/li&gt;
&lt;li&gt;아침에 일어나면서 오늘 하루 24시간을 최대한 활용할 결심을 하는가?&lt;/li&gt;
&lt;li&gt;&apos;오늘 충실하게 생활&apos;함으로써 인생을 더 밀도 있게 살 수 있는가?&lt;/li&gt;
&lt;li&gt;언제부터 이렇게 할 수 있는가? 다음 주? 내일? 오늘?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;두 번째. 걱정스런 상황을 해결하는 마법의 공식&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;사실을 직시해라. 걱정을 집어치워라. 해결하기 위해 행동하라&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;1단계. 스스로에게 &lt;code class=&quot;language-text&quot;&gt;&quot;일어날 수 있는 최악은 어떤 것인가?&quot;&lt;/code&gt; 물어보라.&lt;br&gt;
2단계. 필요한 경우 최악을 받아들일 준비를 하라.&lt;br&gt;
3단계. 침착하게 최악의 상황을 개선하기 위해 노력하라.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;세 번째. 걱정이 미치는 효과&lt;/strong&gt;&lt;br&gt;
걱정을 할 경우 얼마나 큰 건강상의 대가를 치러야하는지 기억하라&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;걱정에 대처할 줄 모르는 사업가는 오래 살지 못한다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;걱정 분석의 기본 테크닉&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;사실을 확인하라. 세성 걱정 가운데 절반은 무엇을 근거로 결정을 내려야 할지 충분히 알지도 못한 채 결정을 내리려고 하는 사람들이 만든 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1단계. 내가 걱정하고 있는 것을 정확하게 적는다.&lt;br&gt;
2단계. 그것에 대해 내가 할 수 있는 것을 적는다.&lt;br&gt;
3단계. 어떻게 할지 결정한다.&lt;br&gt;
4단계. 결정을 즉각적으로 실행에 옮긴다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;일단 결정이 내려지고 실행할 일만 남아 있다면, 결과에 대한 책임감과 관심은 조금도 남김없이 엊어버려라.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;걱정하는 습관을 없애는 방법&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;바쁘게 움직여라. 걱정하는 사람이 절망의 늪에 빠지지 않으려면 행동에 몰두해야만 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;비참해지게 되는 비결은 자신이 행복한지 아닌지 고민할 여유를 가지는 것이다.&quot;&lt;br&gt;
우리들은 &apos;인간&apos;에게 닥쳐오는 보기 드물 정도로 심한 폭풍우나 혹은 번개와 같은 자연재해는 잘 견뎌내고도 조그만 딱정벌레, 손가락으로도 간단히 죽일 수 있을 정도로 아주 조그만 &apos;걱정&apos;이라는 딱정벌레에 우리 마음을 잠식당하고 있지 않은가?&lt;br&gt;
우리가 무시하고 잊어버려야 할 만한 사소한 일이 우리 속을 뒤집어 놓도록 놔두지 말라.&lt;br&gt;
&quot;인생은 사소한 일에 신경 쓰기에는 너무나 짧다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;피할 수 없다면, 받아들여라&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;내가 변화시킬 수 있을 것 같지 않은 일로 걱정하려고 할 때면 나는 어깨를 으쓱하며 이렇게 말합니다. &quot;잊어버리자고&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;여러분의 걱정에 &apos;손절매&apos;주문을 내라&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;에디슨 숙모는 오래 간직해온 감정의 응어리 때문에 비싼 대가를 치러야했다.&lt;br&gt;
마음의 평화를 대가로 지불했던 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;대개로 화를 내는 입장은 대가를 지불한다고 생각하지 못 했는데, 이 책에서는 마음속에 응어리를 가지고 있는 자체를 &lt;code class=&quot;language-text&quot;&gt;마음의 평화를 대가로 지불&lt;/code&gt;했다고 표현하는 것이 인상깊다.&lt;br&gt;
&lt;strong&gt;걱정을 하는 자체가 이미 스스로 대가를 지불하고 있다는 뜻도 있다.&lt;/strong&gt;&lt;br&gt;
어느정도에서 이 걱정을 &apos;손절매&apos;할 것 인가?&lt;br&gt;
걱정에 대해 비용을 얼마만큼 낼 것인가? 이미 너무 많은 비용을 낸것은 아닌가?&lt;/p&gt;
&lt;h3&gt;평화와 행복을 부르는 정신자세를 갖추는 방법&lt;/h3&gt;
&lt;p&gt;관심과 걱정의 차이는 무엇일까?&lt;br&gt;
관심이란 문제가 어떤 것인지 이해하고 조용히 그 문제에 대처하기 위한 조치를 취하는 것이다.&lt;br&gt;
걱정이란 미친 듯 쓸데없이 제자리를 빙글빙글 맴도는 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;인간은 일어나는 일에 의해서가 아니라 일어나는 일에 대한 자신의 의견에 의해서 더 큰 상처를 입는다.&quot;&lt;br&gt;
그리고 일어나는 일에 대한 우리의 의견은 전적으로 우리에게 달려있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;유쾌하게 생각하고 행동하라. 그러면 유쾌해진다.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;행동이 감정을 따라오는 것 같지만, 실제로는 행동과 감정은 동시에 일어난다.&quot;&lt;br&gt;
우리의 행동을 바꿈으로써 자동적으로 우리의 느낌과 기분을 변화시킬 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;앙갚음은 비용이 많이 든다.&lt;/strong&gt;&lt;br&gt;
우리가 적을 증오할 때 우리는 적에게 우리를 지배하는 힘을 부여한다.&lt;br&gt;
우리의 잠, 식욕, 혈압, 건강, 행복을 지배하는 힘을 부여한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;이기적인 사람들이 당신을 이용해 득을 보려고 하더라도, 무시해버리고 똑같이 갚아주려고 노력하지 말라.&quot;&lt;br&gt;
&quot;똑같이 갚아주려고 하는 순간 당신은 다른 사람이 아니라 자기 자신을 더 해치게 되기 때문이다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;10억을 준다면 지금 갖고 있는 것을 포기하겠는가?&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;우리는 우리가 가진 것에 대해서는 거의 생각하지 않지만, 갖지 못한 것에 대해서는 언제나 생각한다.&quot;&lt;br&gt;
이 마음이야 말로 인류 최대의 비극이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;불평, 불만을 얘기 할 때 마다 듣던 말이다.&lt;br&gt;
내가 가진 것을 하찮게 여기고 다른 사람이 그것을 가지고 있으면 부러워하고 신세를 한탄한다.&lt;br&gt;
책에서 이런 이야기를 보니 스스로 찔린다..&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;두 사람이 감옥 창살 밖을 보았네&quot;&lt;br&gt;
&quot;한 사람은 진흙탕을 보고, 한 사람은 별을 보았다네&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;관점의 차이를 이야기하는 것 같다.&lt;br&gt;
&quot;걱정과 불안은 환경 때문에 생기는 것이 아니다. 그 환경을 받아들이는 스스로의 마음에서 생기는 것이다.&quot; 라는 말이 기억난다.&lt;br&gt;
이 말과 의미가 비슷하지 않을까?&lt;/p&gt;
&lt;h3&gt;걱정을 극복하는 완벽한 방법&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;기도와 종교의 신비를 이해하지 못한다는 사실 때문에 종교가 가져다주는 더 풍요롭고 행복한 삶을 누리지 말아야 하는 것도 아니다.&quot;&lt;br&gt;
&quot;믿음이란 인간이 의지하고 살아가는 힘이며, 믿음이 전혀 없다는 것은 붕괴를 의미한다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;나는 무신론자 이지만, 가끔씩 종교를 생각하면 마음의 짐을 덜어내는 정도의 믿음이 가장 좋을 것이라고 생각한적이 있다.&lt;br&gt;
이 책에서 기도의 힘에 대해 설명을 한다.&lt;br&gt;
기도는 스스로의 짐을 혼자지는게 아니라 나누어진다는 느낌을 갖게 해준다고 설명한다.&lt;br&gt;
가상의 존재에게 기도하여, 이 행위를 통해 마음을 안정을 찾는다는 의미인 것 같다.&lt;/p&gt;
&lt;h3&gt;비판을 받고도 걱정하지 않을 방법&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;여러분이 비판을 받는다면, 그 사람은 그럼으로써 자신이 중요해진다고 느끼기 때문에 그렇게 한다는 사실을 잊지 말라.&quot;&lt;br&gt;
&quot;그것은 종종 여러분이 좋은 실적을 내고 있으며 주목할 만한 사람임을 뜻한다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;내가 내 자신을 지켜보고 있지 않으면, 누군가 나를 비판하기 시작하는 순간 나는 상대방이 무슨 얘기를 하는지 제대로 이해하기도 전에 자동적으로 방어에 들어간다.&lt;br&gt;
비판을 거부하고 칭찬은 그대로 받아들이는 경향이 있다.&lt;br&gt;
비판이 정당한지 칭찬이 정당한지는 신경도 쓰지 않는다.&lt;br&gt;
인간은 논리적인 존재가 아니다. 인간은 감정적인 존재이다. 인간의 논리란 깊고 캄캄하며 폭풍이 몰차이는 감정이라는 바다 한가운데서 이리저리 흔들리는 작은 카누에 불과하다.&lt;br&gt;
누군가 나를 비판한다면 방어하려고 애쓰지 말자. 겸손하면서 재치있는 방법을 쓰자.&lt;/p&gt;
&lt;h3&gt;걱정과 피로를 막고 활력과 의욕을 고취시키는 방법&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&quot;휴식을 자주 취하라. 여러분의 심장이 그렇듯이 지치기 전에 휴식을 취하라.&quot;&lt;/li&gt;
&lt;li&gt;&quot;긴장을 풀고 일하는 법을 배워라.&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&quot;정신병리학자들의 말에 따르면 대부분의 피로는 우리의 정신적, 감정적 태도에서 생긴다. 우리가 피로해지는 것은 우리의 감정들이 육체에 신경성 긴장 상태를 유발하기 때문이다. 걱정과 긴장, 감정적 동요가 피로를 유발하는 3대 요인이다.&quot;&lt;br&gt;
3. &quot;우리의 삶은 우리가 생각하는대로 만들어진다.&quot;&lt;br&gt;
4. &quot;걱정과 피로를 막기위해 일에 열정을 쏟아라.&quot;&lt;br&gt;
5. &quot;수면 부족보다 불면증에 대한 걱정이 더 나쁜 영향을 미친다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;hr&gt;
&lt;h1 id=&quot;내-생에-단-한-번&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;내 생에 단 한 번&lt;/strong&gt;&lt;a href=&quot;#%EB%82%B4-%EC%83%9D%EC%97%90-%EB%8B%A8-%ED%95%9C-%EB%B2%88&quot; aria-label=&quot;내 생에 단 한 번 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;동욱님의 &lt;a href=&quot;https://jojoldu.tistory.com/715?category=689637&quot;&gt;나를 위해 남을 도와주기&lt;/a&gt; 게시글에서 &lt;code class=&quot;language-text&quot;&gt;내 생에 단 한 번&lt;/code&gt; 책의 내용중 한 챕터로 예를 들어주셨는데 책의 내용이 너무 마음에 들어 바로 구매하고 읽어보았다.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;내용&lt;/summary&gt;
&lt;!-- summary 아래 한칸 공백 두어야함 --&gt;
&lt;blockquote&gt;
&lt;p&gt;남들은 멀쩡히 잘도 걸어다니는데 왜 하필이면 나만 목발에 의지해야 하고,&lt;br&gt;
어떤 사람은 펜만 잡으면 멋진 글이 술술 잘도 나오는데 왜 하필이면 나만 이 짤막한 글 하나 쓰면서도 머리를 벽에 박아야 하는가.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;하필이면&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;태어남은 하나의 약속이다.&lt;br&gt;
나무로 태어남은 한여름에 힘껏 물오른 가지로 푸르름을 뽐내리라는 약속이고,&lt;br&gt;
꽃으로 태어남은 흐드러지게 활짝 피어 그 화려함으로 이 세상에 아름다움을 더하리라는 약속이고,&lt;br&gt;
짐승으로 태어남은 그 우직한 본능으로 생명의 규율을 지키리라는 약속이다.&lt;br&gt;
...&lt;br&gt;
다른 생명과 달리 우리의 태어남은 생각하고 이해하고 사랑할 수 있는 기회의 약속이다.&lt;br&gt;
미움 끝에 용서할 줄 알고, 비판 끝에 이해할 줄 알며, 절시 끝에 사랑할 줄 아는 기적을 만드는 일이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;약속&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;사랑받는다는 것은 &quot;진짜&quot;가 될 수 있는 귀중한 기회이다.&lt;br&gt;
모난 마음은 동그랗게,&lt;br&gt;
잘 깨지는 마음은 부드럽게,&lt;br&gt;
너무 비싸서 오만한 마음은 겸손하게 누그러뜨릴 때에야 비로소 &quot;진짜&quot;가 되는 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;&apos;진짜&apos;가 되는 길&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;영겁의 시간 속에 비하면 우리 한평생 칠, 팔십 정도는 눈 깜짝할 순간이다.&lt;br&gt;
좋은 마음으로 좋은 말만하고 살아도 아까운 세월인데,&lt;br&gt;
우리는 타고난 재주로 이리저리 시간 쪼개어&lt;br&gt;
미워할 시간, 시기할 시간, 불신할 시간, 아픔 줄 시간을 따로 마련하면서 산다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;은하수와 개미마음&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;작품에 작중 인물이 그저 &apos;남&apos;이고 &apos;남의 일&apos;이라고 단정해 버리면,&lt;br&gt;
&apos;나&apos;와 &apos;남&apos; 사이에 공존하는 인간의 보편적 성향을 공부하는 문학은 애당초 의미를 잃는다.&lt;br&gt;
...&lt;br&gt;
가끔 누군가 내게 행한 일이 너무나 말도 안 되고 화가나서 견딜 수 없을 때가 있다.&lt;br&gt;
며칠 동안 가슴앓이하다가도 문득 &quot;만약 내가 그 사람 입장이었다면 나라도 그럴 수 있었을지 모르겠다.&quot;는 생각이 들 때가 있다.&lt;br&gt;
&quot;오죽하면 그랬을까&quot;라는 동정심이 생기는 것이다.&lt;br&gt;
&apos;남&apos;의 마음을 &apos;나&apos;의 마음으로 헤아릴 때 생기는 기적이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;나와 남&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;우리에게 주어지는 선택은 단 두 가지 뿐이다.&lt;br&gt;
완전히 좌절하고 삶을 포기하거나, 아니면 그 상황을 또 다른 시작의 계기로 삼는 일이다.&lt;br&gt;
그리고 최후의 승리는 두 번째 길을 택하는 자에게 돌아간다고 나는 확신한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;막다른 골목&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;이 눈먼 소년처럼 도움을 필요로 하는 사람이 있으면 모두 자기 시간을 쪼개 그를 도와야 할 겁니다.&lt;br&gt;
그러면 남을 돕고, 남을 위해 나의 작은 것을 희생할 수 있는 배려하는 마음을 배울 수 있다고 생각합니다.&lt;br&gt;
...&lt;br&gt;
남을 돕고 함께 나눌 줄 모르는 나라라면, 그런 나라에서 사느니 차라리 죽는게 나을지도 모릅니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;눈먼 소년이 어떻게 돕는가?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;살아가면서 누군가를 미워할 때 그를 &apos;용서해야 할 이유&apos;보다는 &apos;용서하지 못할 이유&apos;를 먼저 찾고,&lt;br&gt;
누군가를 비난하면서 그를 &apos;좋아해야 할 이유&apos;보다는 &apos;좋아하지 못할 이유&apos;를 먼저 찾고,&lt;br&gt;
마음의 문을 꽁꽁 닫아건 채 누군가를 &apos;사랑해야 할 이유&apos;보다는 &apos;사랑하지 못할 이유&apos;를 먼저 찾지는 않았는지&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;못 줄 이유&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;어쩌면 우리 삶 자체가 시험인지 모른다.&lt;br&gt;
우리 모두 삶이라는 시험지를 앞에 두고 정답을 찾으려 애쓴다.&lt;br&gt;
그것은 용기의 시험이고, 인내와 사랑의 시험이다.&lt;br&gt;
그리고 어떻게 시험을 보고 얼마만큼의 성적을 내는가는 우리들의 몫이다.&lt;br&gt;
...&lt;br&gt;
우리에게 인생의 시험을 주는이가 그 누구든, 어떤 문제를 내더라도 절대로 우리가 실패하기를 원치 않는다고 생각한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;실패없는 시험&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;일상생활 속에서 여러 사람을 만나면서 내가 정말로 그 사람 자체의 됨됨이만으로 그 사람을 평가했던가?&lt;br&gt;
그의 배경이나 그가 속한 집단에 대한 일반적 평판만으로 그를 섣불리 판단하거나 질시하는 일은 없었던가?&lt;br&gt;
그가 사는 방법을 나의 틀에 맞춰 그를 비판하거나 도외시하는 일은 없었던가?&lt;br&gt;
우리들은 종종 다른 사람들을 편의상 한 집단으로 분류해 놓고 단정적으로 판단하는 경향이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;연주야!&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/details&gt;</content:encoded></item><item><title><![CDATA[2022년 회고]]></title><description><![CDATA[들어가며 개발자로 지내면서 연말회고를 처음 작성하여 제목은 "202…]]></description><link>https://jdalma.github.io/2022y/yearend/</link><guid isPermaLink="false">https://jdalma.github.io/2022y/yearend/</guid><pubDate>Sat, 31 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;들어가며&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;들어가며&lt;/strong&gt;&lt;a href=&quot;#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0&quot; aria-label=&quot;들어가며 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;개발자로 지내면서 연말회고를 처음 작성하여 제목은 &quot;2022년 회고&quot;이지만 지금까지의 생각을 정리하는 긴 글이 될 것 같다.&lt;br&gt;
&lt;strong&gt;잊고 싶지 않은 올해의 이벤트들과 내 심정을 기록하기 위해 작성한다&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;블로그&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;블로그&lt;/strong&gt;&lt;a href=&quot;#%EB%B8%94%EB%A1%9C%EA%B7%B8&quot; aria-label=&quot;블로그 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;블로그를 시작하게 된 계기는&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;스스로에 대한 동기부여&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;글쓰기 훈련 (코딩도 언어만 다르지 결국 글쓰기라고 생각하기 때문이다)&lt;/li&gt;
&lt;li&gt;유명한 개발자들의 블로그
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://johngrib.github.io/&quot;&gt;종립 님의 블로그&lt;/a&gt;와 &lt;a href=&quot;https://jojoldu.tistory.com/&quot;&gt;향로 님의 블로그&lt;/a&gt; 영향이 컷다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유명한 회사들의 기술 블로그&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;2021년 2월 20일&lt;/code&gt;부터 시작하여 대략 2년 정도 블로그에 글을 작성하고 있다.&lt;br&gt;
블로그를 관리하면서 얻은&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;첫 번째 장점은 스스로에 대한 동기부여이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 681px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d7bf5eade433fedbaee8fd9780d6e92e/8ce52/2022commit.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABuklEQVR42iWRS3PSABSF8//3bnRc1HbjiLZWa5m2A7Y0PBMI5TWWNwRJQl4kgRAS+Exxde6duWfmnO8KcRzjbwLMvcNQmyD1myjLLmNnzsCesvA1VG+Zqs7YmjGxVXTfxPFdnMBFDyzm7hLDX+HFAYLn+Vi+w9zTkGdtLnt3fJnccv03xy/tN6It8bASKVp1CkYNcSVTs1rIdouePSCrFfih5cjqBZR1F2G7DVkFNqbnMLOXJ2NeLZPXK1TXTep2h7KlILttKnaTsp3OTue015wXKo5CyW3wU8szDlSE4/HIJtrypm/VN/sQZ+exDn2iOCKINieNkohNEmKGLuF+R5IkxIeEXfL/ZrPfkhwThDhKWAQaXX+AvGrzvJAoa03azh8qukLPHSHpLxQ1mYJaQzZalFYK92oRcSZTNdIWVvvk1UMTYW2tqacVbow8N2nsd50LPvYzZGZZLtUHLkbXnL1+4/3oM+evV5xPv/NhmuHT+Iqz7ldujceU9SOiKTPdLhAO8YG23U+TDJFS0AVDSjk2UPwuDbfDo1Hl3iiSX5bTR3QQU8Y5vcSTKXGnP9NbD5l4Cxp2Fydy+Qccx/cZIaSPWQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;2022commit&quot;
        title=&quot;&quot;
        src=&quot;/static/d7bf5eade433fedbaee8fd9780d6e92e/8ce52/2022commit.png&quot;
        srcset=&quot;/static/d7bf5eade433fedbaee8fd9780d6e92e/3684f/2022commit.png 225w,
/static/d7bf5eade433fedbaee8fd9780d6e92e/fc2a6/2022commit.png 450w,
/static/d7bf5eade433fedbaee8fd9780d6e92e/8ce52/2022commit.png 681w&quot;
        sizes=&quot;(max-width: 681px) 100vw, 681px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;2021,2022년 커밋 내역이다.&lt;br&gt;
위의 모든 커밋들이 유의미한 커밋이라고 말할 수도 없고 대부분 앵무새 글(강의,책 내용 정리)들 이지만,&lt;br&gt;
&lt;strong&gt;학습 능력을 기르고 학습 습관&lt;/strong&gt;을 잡는 것에는 큰 도움이 되었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;두 번째 장점으로는 메타인지를 높일 수 있는 기회이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;머리에 담긴 &lt;code class=&quot;language-text&quot;&gt;파편적인 지식들을 연결시켜가며&lt;/code&gt; 글로 작성하면서 모르는 내용들이나 키워드들을 발견하게 된다.&lt;br&gt;
그리고 작성한 게시글을 시간이 흐른뒤에 보면 내가 무엇을 알고 무엇을 모르는지 알게될 기회가 많다.&lt;br&gt;
알고리즘 공부하면서 많이 느꼇다.&lt;/p&gt;
&lt;p&gt;위의 장점들을 보면 블로그를 하는게 무조건 이득일 것 같지만, 단점도 존재한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;첫 번째 단점은 죽은 문서들이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;게시글을 작성하는 재미에 빠져 학습한 내용들을 많이 작성하게 되지만, 그때만 유효하고 그 글들을 계속 관리하지 않기 때문이다.&lt;br&gt;
학습 내용을 정리하여 글로 작성하면 그 내용들이 내것이 된 것 마냥 신경쓰지 않고 내버려두게 된다.&lt;br&gt;
그렇다고 지우기에는 아까워 &lt;code class=&quot;language-text&quot;&gt;저장강박증&lt;/code&gt;이 생겼다..&lt;/p&gt;
&lt;p&gt;이 &lt;strong&gt;죽은 문서들을 살리기 위한 노력과 시간이 추가로 필요하다.&lt;/strong&gt;&lt;br&gt;
어떤 분들은 글들을 리팩토링하는 즐거움이 있다고 한다&lt;/p&gt;
&lt;p&gt;종립님은 이 문제를 해결하기위해 블로그에 &lt;strong&gt;랜덤 버튼&lt;/strong&gt;을 추가하여 죽은 문서들이 없게 노력하고 계신다고 하였다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;두 번째 단점은 블로깅에만 집중하는 것이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이력서를 작성할 때 블로그 링크를 당당하게 작성하게 되는데, 블로그가 독이 될 때도 있다.&lt;br&gt;
첫 번째 단점과 연관이 있는데, 학습 내용들을 확실히 이해하지 않고 넘어가거나 중요한 포인트를 간과하고 블로그에 마구마구 작성하면서 &lt;code class=&quot;language-text&quot;&gt;죽은 문서들&lt;/code&gt;은 불어난다.&lt;br&gt;
면접관분들은 블로그를 보고 질문을 추가로 생각하게 될텐데 그 질문에 잘 대답하면 본전이고 대답을 못하게 된다면 블로그의 게시글들은 신뢰가 떨어진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;위의 문제를 해결하기 위해 &lt;code class=&quot;language-text&quot;&gt;개인 학습 공간&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;개인 기술 블로그&lt;/code&gt;를 분리할 생각이다.&lt;/strong&gt;&lt;br&gt;
분리하면서 게시글들도 정리해야 할 것이다. (2023년 목표이다)&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고 &lt;code class=&quot;language-text&quot;&gt;죽은 문서들&lt;/code&gt;이 걱정돼서 블로그를 망설이고 있다면 &lt;strong&gt;무조건 시작하라고 할 것 이다.&lt;/strong&gt;&lt;br&gt;
개인적으로 개발자가 가져야 할 가장 중요한 소양은 &lt;strong&gt;학습 습관&lt;/strong&gt;이라고 생각하기 때문이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;알고리즘&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;알고리즘&lt;/strong&gt;&lt;a href=&quot;#%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; aria-label=&quot;알고리즘 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;이직을 고민하면서 알고리즘을 하게 되었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떤 분이 &lt;code class=&quot;language-text&quot;&gt;&quot;알고리즘 한 문제에 5만원이라고 생각해라&quot;&lt;/code&gt;라고 한게 갑자기 떠올랐다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SI 업무에 찌들어 있을 때, 알고리즘 푸는게 재밌다라고 느꼈었다.&lt;br&gt;
문제를 고민하고 해결하는게 진짜 개발자 같다라고 생각이 들었다.&lt;/p&gt;
&lt;p&gt;처음에는 &lt;a href=&quot;https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4-%EC%BD%94%ED%85%8C%EB%8C%80%EB%B9%84/dashboard&quot;&gt;인프런 강의&lt;/a&gt;로 기본을 떼고 운이 좋게 &lt;a href=&quot;https://github.com/jdalma/Algorithm-Study/tree/main&quot;&gt;스터디&lt;/a&gt;에 참가하게 되었다.&lt;br&gt;
이 스터디에 &lt;a href=&quot;https://solved.ac/profile/opera_tive&quot;&gt;알고리즘 랭커분&lt;/a&gt;이 계셨는데 엄청 큰 도움이 되었다.&lt;br&gt;
단순히 알고리즘 분야를 떠나서 &lt;code class=&quot;language-text&quot;&gt;&quot;이렇게 열심히 하시는 분도 있구나&quot;&lt;/code&gt; 라는 생각에 동기부여도 많이 되었다.&lt;/p&gt;
&lt;p&gt;알고리즘이 실무의 문제를 해결할 떄 무조건 도움은 된다고 확신한다.&lt;br&gt;
단순히 문제를 해결하기 위해 무지성으로 짜는 것이 아니라 &lt;strong&gt;시간,공간 복잡도를 고려하는 자세&lt;/strong&gt;가 생긴다.&lt;br&gt;
최적의 답을 무조건 찾아내지 못 하더라도 (나는 시간복잡도를 확실히 모른다.) 생각하는 개발자라면 기계처럼 코드를 작성하는 것이 아니라 몇 분이라도 고민하고 작성하는것이 중요하다고 생각한다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;내가 사용하는 자바의 정렬 메소드는 어떻게 정렬이 되는지?  
자료구조 마다 그리고 자료구조 구현체 마다 장,단점은 무엇인지?  &lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;사소하지만 원리를 알게 될 학습을 할 확률이 높다고 생각한다.&lt;/p&gt;
&lt;p&gt;이번년도 목표는 플레티넘이였지만 &lt;code class=&quot;language-text&quot;&gt;골드 1 (1541점)&lt;/code&gt; 59점을 남겨놓고 멈췄다.&lt;br&gt;
기업용 코테 통과가 목표였는데&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;특정 깊이 이상으로 파고들기에는 가성비가 떨어진다. (시간 소모가 너무 크다)&lt;/li&gt;
&lt;li&gt;문제해결능력보단 solved.ac 티어와 점수에 집착을 하게 된다.&lt;/li&gt;
&lt;li&gt;백준에는 문제가 너무 많아 스스로에게 도움이 되는 문제를 구별해내지 못 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;계획은 영어권 플랫폼이긴 하지만.. 문제가 명료하고 프리미엄 결제를 하면 여러 방법의 해설을 설명해주는 &lt;strong&gt;리트코드&lt;/strong&gt;를 활용할 생각이다.&lt;br&gt;
&lt;em&gt;추가로 데브매칭이나 쇼미더코드 같은 이벤트에 무지성으로 참여하기&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;스터디와-읽기모임&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;스터디와 읽기모임&lt;/strong&gt;&lt;a href=&quot;#%EC%8A%A4%ED%84%B0%EB%94%94%EC%99%80-%EC%9D%BD%EA%B8%B0%EB%AA%A8%EC%9E%84&quot; aria-label=&quot;스터디와 읽기모임 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;1년 전만해도 &lt;code class=&quot;language-text&quot;&gt;&quot;모르는 사람과 스터디나 모임을 어떻게 하지..&quot;&lt;/code&gt;라는 걱정을 했지만 지금은 새로운 개발자들과 모이는 것이 재밌다.&lt;br&gt;
&lt;strong&gt;다양한 생각과 의견을 나누고 조언을 듣거나 학습 습관들을 보고 배울 수 있기 때문이다.&lt;/strong&gt;&lt;br&gt;
그래서 올해는 온라인 스터디나 모임을 찾으려고 부단히 노력한 것 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;(이전 직장) &lt;a href=&quot;https://github.com/jdalma/dev-study&quot;&gt;사내 스터디&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;토론 보다는 단방향 교육에 가까워 아쉬운 스터디 중 하나다.&lt;/li&gt;
&lt;li&gt;스터디 주제가 너무 포괄적이였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/Algorithm-Study/tree/main&quot;&gt;파워 알고리즘 스터디&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;운이 좋게 아주 뛰어나신 분들과 스터디를 하게 되었다.&lt;/li&gt;
&lt;li&gt;알고리즘 공부에 있어서 큰 영향을 준 스터디&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/algorithm-for-coding-test&quot;&gt;코드숨 알고리즘 스터디&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Najeong-Kim&quot;&gt;나정님&lt;/a&gt;이 리딩하신 스터디&lt;/li&gt;
&lt;li&gt;19일차, 20일차는 완료 못했다 ㅎㅎ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/object/wiki&quot;&gt;코드숨 오브젝트 스터디&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kyuwon53&quot;&gt;규원님&lt;/a&gt;이 리딩하신 스터디&lt;/li&gt;
&lt;li&gt;토비의 스프링 읽기모임에 집중하느라 2장까지만 진행하고 포기했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드숨 코딩인터뷰완전분석 읽기모임
&lt;ul&gt;
&lt;li&gt;여러 분들과 모여서 많은 이야기를 할 수 있어서 재밌었다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;토비님이 주관하신 &lt;a href=&quot;https://github.com/jdalma/tobyspringin5/wiki&quot;&gt;토비의 스프링 읽기모임&lt;/a&gt;&lt;/strong&gt;
&lt;ul&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;a href=&quot;https://github.com/jdalma/tdd&quot;&gt;테스트주도개발&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;1부만 읽고 2부는 읽지 못 했다&lt;/li&gt;
&lt;li&gt;2부를 읽을 때 &lt;a href=&quot;https://www.youtube.com/watch?v=tdKFZcZSJmg&amp;#x26;ab_channel=TobyLee&quot;&gt;토비님 영상&lt;/a&gt;도 참고하자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;맹기완 대표님이 주관하신 &lt;a href=&quot;https://github.com/jdalma/java-to-kotlin&quot;&gt;자바에서 코틀린으로 읽기모임&lt;/a&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;코드숨 대표 윤석님 덕분에 참여하게 됐고 맹기완 대표님, 책을 번역하신 오현석님과 함께 할 수 있는 모임이다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;책을 이렇게 까지 읽을 수 있구나. 같은 책을 봐도 아는 만큼 보인다&quot;&lt;/code&gt;를 느낀 모임이다.&lt;/li&gt;
&lt;li&gt;스스로 지식이 얕아 중요하게 보지 않고 넘기는 부분들을 깊게 얘기해주시고 토론을 하여 굉장히 재밌게 진행하고 있는 모임이다.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;재밌다고해서 마냥 쉽진 않았다&lt;/em&gt; 스터디나 읽기모임에 자주 참여하지만 이렇게 깊게 얘기하고 부담되는 모임은 처음이다.&lt;/li&gt;
&lt;li&gt;단순히 책에 나오는 내용들만 보고 넘어가는게 아니라 &lt;strong&gt;객체지향&lt;/strong&gt;, &lt;strong&gt;함수형 프로그래밍&lt;/strong&gt;, &lt;strong&gt;값 이론&lt;/strong&gt; 등에 대한 내용들을 얘기해주셔서 재밌게 완독했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h1 id=&quot;코드숨&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;코드숨&lt;/strong&gt;&lt;a href=&quot;#%EC%BD%94%EB%93%9C%EC%88%A8&quot; aria-label=&quot;코드숨 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;코드 리뷰&lt;/strong&gt;를 경험하고, &lt;strong&gt;테스트 코드&lt;/strong&gt;를 배우고 싶어서 관심갖게된 교육이다.&lt;br&gt;
금액 때문에 고민을 잠깐 했지만 &lt;code class=&quot;language-text&quot;&gt;종립님&lt;/code&gt;이 직접 리뷰를 해주시는 것을 보고 바로 참여했다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;제목&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;리뷰&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;회고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;1주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week1-assignment-1&quot;&gt;&lt;strong&gt;Java로 ToDo REST API만들기&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week1-assignment-1/pull/115&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#%EC%BD%94%EB%93%9C%EC%88%A8-1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-java%EB%A1%9C-todo-rest-api%EB%A7%8C%EB%93%A4%EA%B8%B0&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;2주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week2-assignment-1&quot;&gt;&lt;strong&gt;Spring Web 으로 ToDo REST API 만들기&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week2-assignment-1/pull/94&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#8%EC%9B%94-%EB%91%98%EC%A7%B8-%EC%A3%BC-%ED%9A%8C%EA%B3%A0-%EC%BD%94%EB%93%9C%EC%88%A8-2%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-spring-web-%EC%9C%BC%EB%A1%9C-todo-rest-api-%EB%A7%8C%EB%93%A4%EA%B8%B0&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;3주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week3-assignment-1&quot;&gt;&lt;strong&gt;Spring Mock MVC , 유닛 테스트 작성하기&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week3-assignment-1/pull/83&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#%EC%BD%94%EB%93%9C%EC%88%A8-3%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-spring-mock-mvc--%EC%9C%A0%EB%8B%9B-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;4주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week4-assignment-1&quot;&gt;&lt;strong&gt;TDD로 &lt;code class=&quot;language-text&quot;&gt;상품 도메인&lt;/code&gt; 구현하기&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week4-assignment-1/pull/76&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#%EC%BD%94%EB%93%9C%EC%88%A8-4%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-tdd%EB%A1%9C-%EC%83%81%ED%92%88-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;5주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week5-assignment-1&quot;&gt;&lt;strong&gt;TDD로 &lt;code class=&quot;language-text&quot;&gt;사용자 도메인&lt;/code&gt; 구현하기&lt;/strong&gt; (+ 유효성 검사)&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week5-assignment-1/pull/75&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#%EC%BD%94%EB%93%9C%EC%88%A8-5%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-tdd%EB%A1%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0--%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;6주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week6-assignment-1&quot;&gt;&lt;strong&gt;JJWT &lt;code class=&quot;language-text&quot;&gt;인증&lt;/code&gt;(Authentication) 구현하기&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week6-assignment-1/pull/70&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#%EC%BD%94%EB%93%9C%EC%88%A8-6%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-jwt%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;7주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week7-assignment-1&quot;&gt;&lt;strong&gt;사용자 패스워드 &lt;code class=&quot;language-text&quot;&gt;암호화&lt;/code&gt; 및 &lt;code class=&quot;language-text&quot;&gt;인가&lt;/code&gt; 처리를 Spring Security로 처리&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week7-assignment-1/pull/74&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#%EC%BD%94%EB%93%9C%EC%88%A8-7%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-spring-security-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;8주차&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/spring-week8-assignment-1&quot;&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Spring REST Docs&lt;/code&gt; 적용, &lt;code class=&quot;language-text&quot;&gt;Docker&lt;/code&gt;로 빌드 및 배포&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/CodeSoom/spring-week8-assignment-1/pull/67&quot;&gt;코드 리뷰&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%BD%94%EB%93%9C%EC%88%A8.md#%EC%BD%94%EB%93%9C%EC%88%A8-8%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-spring-rest-docs-%EC%A0%81%EC%9A%A9-docker%EB%A1%9C-%EB%B9%8C%EB%93%9C-%EB%B0%8F-%EB%B0%B0%ED%8F%AC&quot;&gt;회고&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;코드숨 교육을 들으면서 기술적인 내용만이 아니라 &lt;strong&gt;개발자 커리어를 관통하는 말씀들을 많이 해주셨다.&lt;/strong&gt;&lt;br&gt;
종립님이 리뷰해주신 내용 중 아래의 내용이 가장 기억에 남는다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;우리가 작성하는 코드는 그냥 기계를 굴러가게 하기만 하는 것이 아닙니다.&lt;br&gt;
레퍼런스 문서를 토대로 실천하는 활동이기도 하다는 점을 곱씹어보세요.&lt;br&gt;
공식 문서의 힘을 끌어다 내 코드에 연결할 수 있다고 생각해 보세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;살면서 종립님을 직접 뵐 기회가 있을까 싶었는데, 코드숨 사무실에 직접 오셔서 강연까지 해주셨다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;자주 사용하는 것을 자세하게 들여다 봐라.
&lt;ul&gt;
&lt;li&gt;자바 &lt;code class=&quot;language-text&quot;&gt;util&lt;/code&gt; 패키지를 뜯어보거나 무의식적으로 사용하는 기능들에 대해 공부해라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;블로그 게시글들을 오래 묵혀두지 않기, 텀을 두고 반복읽기&lt;/li&gt;
&lt;li&gt;1차 창작자의 글 또는 책을 읽어라
&lt;ul&gt;
&lt;li&gt;과제를 진행하면서 &lt;strong&gt;RFC&lt;/strong&gt; , &lt;strong&gt;JLS&lt;/strong&gt;를 많이 접했다&lt;/li&gt;
&lt;li&gt;개인적으로 공감되는게 공식 문서들이 읽기는 힘들지만 습득할 때 찝찝하지가 않다&lt;/li&gt;
&lt;li&gt;&lt;em&gt;영어를 읽기가 힘들긴 하다...&lt;/em&gt; 적응하면 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;책 추천
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&amp;#x26;ejkGb=KOR&amp;#x26;barcode=9788966260997&quot;&gt;&lt;strong&gt;생각하는 프로그래밍&lt;/strong&gt;&lt;/a&gt; &lt;code class=&quot;language-text&quot;&gt;퀵 소트 알고리즘 공동 저술자&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=2354267&quot;&gt;&lt;strong&gt;자바 병렬 프로그래밍&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;면접은 가기 싫은 순으로 많이 봐라
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;긍정적인 에너지를 보이려고 노력해라&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;면접에 붙다보면 자신감도 붙는다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그리고 코드숨 디스코드 커뮤니티도 도움이 많이 되었다.&lt;br&gt;
스터디 모집도 활발하고 정보 공유도 많이 해주신다.&lt;br&gt;
퇴근하고 새벽에도 공부하시는 분들 보면 동기부여도 많이 된다.&lt;/p&gt;
&lt;p&gt;코드숨 교육은 &lt;strong&gt;올해의 두 번째로 잘한 일이다.&lt;/strong&gt; &lt;em&gt;첫 번째는 퇴사&lt;/em&gt;&lt;br&gt;
금전적인 여유가 된다면 코드숨 교육을 &lt;strong&gt;강력 추천&lt;/strong&gt;한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;이직&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;이직&lt;/strong&gt;&lt;a href=&quot;#%EC%9D%B4%EC%A7%81&quot; aria-label=&quot;이직 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;SI 회사에서 개발자로 경험할 수 있는 범위는 한정적이라고 생각되었다.&lt;br&gt;
프로젝트를 빠르게 개발하고 다음 프로젝트에 투입되는 식의 반복으로 개발한 기능에 대한 효과를 알 수 없었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;내가 수행한 프로젝트의 영향은 얼마정도일까?&lt;/li&gt;
&lt;li&gt;내가 개발한 기능에서 장애가 발생했을까? 발생했다면 원인이 뭐였을까?&lt;/li&gt;
&lt;li&gt;기능을 사용하다가 불편한 점은 없었을까?&lt;/li&gt;
&lt;li&gt;내가 기능을 요구한대로 완벽하게 개발한걸까?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;등 내가 맡은 프로젝트에 개발,운영,피드백을 경험할 수 있는 곳으로 도전하고 싶었다.&lt;/p&gt;
&lt;p&gt;SI 회사를 &lt;a href=&quot;https://jdalma.github.io/2022y/leave/&quot;&gt;퇴사&lt;/a&gt;하고 3달동안 하고싶은 공부를 했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드숨 교육&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%ED%9A%8C%EA%B3%A0/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84%ED%94%84%EB%A1%9C_%EC%82%AC%EC%A0%84%EA%B3%BC%EC%A0%9C.md&quot;&gt;우테캠프로 사전과제&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;입사 후에 트레이닝 프로젝트와 도메인 교육을 진행했다.&lt;br&gt;
내가 맡은 트레이닝 프로젝트는 &lt;code class=&quot;language-text&quot;&gt;n차&lt;/code&gt;로 나뉘어 진행되고 있는데&lt;br&gt;
&lt;strong&gt;1차&lt;/strong&gt;는 &lt;a href=&quot;https://jdalma.github.io/2022y/trainingProject/&quot;&gt;Armeria와 gRPC로 통신하기 위한 템플릿 서버 개발&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2차&lt;/strong&gt;는 1차에서 만든 템플릿 서버로 ES서버에 부하를 주는 검색식 검증 규칙 관리 개발이다.&lt;/p&gt;
&lt;p&gt;1차 트레이닝 프로젝트는 &lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%A0%95%EB%A6%AC/Armeria_gRPC.md&quot;&gt;기술적인 키워드들&lt;/a&gt;이 너무 많이 포함되어 있었어서 힘들었다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;&quot;시간만 있다면 스스로 어떤 일이든 해낼 수 있다&quot;&lt;/code&gt; 라고 생각하고 있었지만 CS지식의 중요성을 깨달았다.&lt;br&gt;
그래도 스스로 테스트 케이스를 생각하며 테스트하고, 어디를 더 분석해야할지 결정하여 오랜만에 재밌게 개발한 것 같다.&lt;br&gt;
원리를 이해하기 위한 학습을 계속 해야하고, 이번에 Armeria와 gRPC를 사용하는 만큼 비동기 프로그래밍과 HTTP/2는 확실하게 짚고가고 싶다.&lt;/p&gt;
&lt;p&gt;SI 회사와 굉장히 다른 점은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;새로운 기술을 도입할 때 그저 사용하는 개발이 아니라, &lt;strong&gt;원리를 파악하고 사용하기 위해 분석을 하고 개발 하는 것&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;분석이나 개발하면서 조언을 받고 싶은것이 있다면 리뷰를 요청하기만 하면 되는 &lt;strong&gt;리뷰 문화&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;레거시 프로젝트에서 특정 기능을 라이브러리로 분리하면서 사소한 리팩토링을 진행했는데 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;어떻게 하면 객체지향적일까?&lt;/code&gt;라고 팀원들에게 여쭤보면 진지하게 고민하시는 것&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;SI 회사에서는 이런 내용을 고민하는 분이 없었다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;분석 → 설계 → 개발&lt;/code&gt;의 한 사이클을 직접 경험할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h1 id=&quot;소감&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;소감&lt;/strong&gt;&lt;a href=&quot;#%EC%86%8C%EA%B0%90&quot; aria-label=&quot;소감 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;연말회고를 처음 작성해보는데 이렇게 나열해보니 열심히 살아온 것 같기도 하다.&lt;br&gt;
올해 목표들이 있었는데&lt;/p&gt;
&lt;ol class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 수익이 나는 서비스 회사&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; mac을 지원해주는 회사&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; IDE를 지원해주는 회사&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; B2C 서비스 회사 → &lt;code class=&quot;language-text&quot;&gt;B2B 회사&lt;/code&gt;&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 많은 트래픽을 경험할 수 있는 회사&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; AWS, MSA, 이벤트 주도 아키텍처 시스템을 경험할 수 있는 회사&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 블로그 꾸준히 운영하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 학습하는 자세 유지하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 다른 개발자들과 교류하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 관심가는 스터디에 망설이지 말고 참여하기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 코드리뷰 받아보기&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; 테스트 코드 배워보기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;시간이 지나면서 점점 목표한 것들을 해내고 있다는 생각이 든다.&lt;br&gt;
&lt;strong&gt;옛날엔 어려워 했던 것을 &lt;code class=&quot;language-text&quot;&gt;지금은 아무렇지 않게 해내게 된 것&lt;/code&gt;이 가장 많이 성장한 것 같다.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;2023년-목표&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;2023년 목표&lt;/strong&gt;&lt;a href=&quot;#2023%EB%85%84-%EB%AA%A9%ED%91%9C&quot; aria-label=&quot;2023년 목표 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;네티 인 액션&lt;/li&gt;
&lt;li&gt;코틀린 인 액션&lt;/li&gt;
&lt;li&gt;자바에서 코틀린으로&lt;/li&gt;
&lt;li&gt;Real MySQL 1,2권&lt;/li&gt;
&lt;li&gt;HTTP 완벽가이드&lt;/li&gt;
&lt;li&gt;이펙티브 자바&lt;/li&gt;
&lt;li&gt;이펙티브 코틀린&lt;/li&gt;
&lt;li&gt;리팩터링&lt;/li&gt;
&lt;li&gt;자바 ORM 표준 JPA 프로그래밍&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;/li&gt;
&lt;li&gt;멀티코어를 100% 활용하는 자바 병렬 프로그래밍&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;/li&gt;
&lt;li&gt;코드 CODE&lt;/li&gt;
&lt;li&gt;만들면서 배우는 클린 아키텍처&lt;/li&gt;
&lt;li&gt;도메인 주도 개발 시작하기&lt;/li&gt;
&lt;li&gt;도메인 주도 설계&lt;/li&gt;
&lt;li&gt;1일 1로그 100일 완성 IT 지식&lt;/li&gt;
&lt;li&gt;죽을 때까지 코딩하며 사는 법&lt;/li&gt;
&lt;li&gt;개발자의 글쓰기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2023년 연말 회고 때 얼마나 읽었는지 확인해야지&lt;/p&gt;
&lt;h3&gt;**2023년에 지키고 싶은 목표들**&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;블로그 꾸준히 운영하기
&lt;ul&gt;
&lt;li&gt;학습 블로그와 기술 블로그 분리하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;학습하는 자세 유지하기
&lt;ul&gt;
&lt;li&gt;인프런 사놓은 강의 보기&lt;/li&gt;
&lt;li&gt;책 10권 읽기&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;li&gt;&lt;a href=&quot;https://www.facebook.com/dev.reader&quot;&gt;개발자, 한 달에 책 한 권 읽기&lt;/a&gt; 참여하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;지금 회사에서 학습 습관을 좋게 봐주셨는데, 2차 면접에서 &lt;code class=&quot;language-text&quot;&gt;&quot;이렇게 몰두한 경험이 또 있나?&quot;&lt;/code&gt;라는 질문이 기억에 남는다.&lt;br&gt;
개발자를 시작하기 전에 이렇게 내 모든 에너지와 시간을 쏟아부어서 열정적이게 했던 일은 없었던 것 같다.&lt;br&gt;
개발자라는 직업이 매력적인 만큼 스스로 변화했다고 많이 느껴진다.&lt;br&gt;
다른 전공자들에 비해 늦게 시작한 만큼 불안하기도 한데, 조급함에 무너지지 않게 스스로의 페이스를 잘 유지해야겠다.&lt;/p&gt;
&lt;p&gt;2023년 연말 회고를 작성할 때 이 글이 귀엽게 보일 정도로 성장했으면 좋겠다.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Armeria + gRPC 사용해보기]]></title><description><![CDATA[기존에 존재하던 REST API를 그대로 유지하면서 gRPC 통신을 같이 사용할 수 있을지 확인하고, Stream 통신을 테스트해보는 것이다. REST API와 RPC 통신을 한 포트로 처리할 수 있는 Armeria를 사용한다. protocol…]]></description><link>https://jdalma.github.io/2022y/trainingProject/</link><guid isPermaLink="false">https://jdalma.github.io/2022y/trainingProject/</guid><pubDate>Thu, 15 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;기존에 존재하던 REST API를 그대로 유지하면서 gRPC 통신을 같이 사용할 수 있을지 확인하고, Stream 통신을 테스트해보는 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;REST API와 RPC 통신을 한 포트로 처리할 수 있는 Armeria를 사용한다.&lt;/li&gt;
&lt;li&gt;protocol buffer를 통해 기존 JSON보다 데이터 사이즈를 줄일 수 있을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;armeria와-tomcat을-한-개의-포트로&quot; style=&quot;position:relative;&quot;&gt;Armeria와 Tomcat을 한 개의 포트로?&lt;a href=&quot;#armeria%EC%99%80-tomcat%EC%9D%84-%ED%95%9C-%EA%B0%9C%EC%9D%98-%ED%8F%AC%ED%8A%B8%EB%A1%9C&quot; aria-label=&quot;armeria와 tomcat을 한 개의 포트로 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Armeria에서는 &lt;a href=&quot;https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/server/HttpService.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;javadoc&lt;/code&gt; HttpService&lt;/a&gt;로 Tomcat, gRPC 들을 추상화 해놓았다.&lt;br&gt;
Armeria 서버가 실행될 때 원하는 Service들을 ServerBuilder에 추가시키기만 하면 된다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Configuration
class ArmeriaConfig{

    @Bean
    fun armeriaServerConfigurator(
      context: ServletWebServerApplicationContext, 
      services: List&amp;lt;BindableService&amp;gt;
    ): ArmeriaServerConfigurator {
        val grpcService: GrpcService =
            GrpcService.builder().apply {
                this.addServices(services)
            }.build()

        val container = context.webServer as TomcatWebServer
        container.start()

        return ArmeriaServerConfigurator { builder -&amp;gt;

            // tomcatService 바인딩
            builder.serviceUnder(&amp;quot;/&amp;quot;,  TomcatService.of(container.tomcat))

            // stub 구현체 등록
            builder.service(grpcService)

            // Armeria Service 문서 활성화
            builder.serviceUnder(&amp;quot;/docs&amp;quot;, DocService())
        }
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;✋&lt;br&gt;
Tomcat 포트를 직접 지정해주고 해당 포트로 REST 요청을 보내게되면 Armeria를 거치지 않고 바로 TomcatWebServer가 처리한다.&lt;br&gt;
Tomcat 포트를 &lt;code class=&quot;language-text&quot;&gt;-1&lt;/code&gt;로 지정하고 Armeria 포트만 지정해주면 Armeria가 REST 요청과 gRPC 요청 둘 다 처리하게 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ebff249f14a266fa7fbdc0f601d37a08/90eea/flow.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.22222222222223%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABZ0lEQVR42j1Si27CMBDr//8ZQ0yIISGKBm2hlL6Avt/17ACrdMql8fnsSyzwm+dZC+q6hm3b2O/38H0fXddhGAbM02TOZ+7npsHctuAhRuL7okDHGKsKzfMJy3VdPB4PTCwax/E/RNay8Gu5hHc+vwhZiDd5S+KGhCMbDsT2fY/sfodVEHRnouKKXaIoQpIkBvgMQ9jbLRKq7bMMExuHtxtK4oSRm4B7CVLExFtSttlsjMUgCHA8HvHLiLj/Wa8RcGUn5HlurLqOY5oK7zAXseqMEIal+TlvUBzHONOewOH1CvdwMCpn2inK0hA6bCaVPs+l8MbcOZ1w5T5lvaV5aGYNZ6JPitXE/OOMoJz2RWgugnnP+AhIqfB7tcJut3tZVnHG+Sg0B8/zcLlcjMU0Tc1sRW4IqfRz4yPXhmeKio1rCipZY2a4WCwMiTqK8EQLytecoezry3XDekJ6OiTGJ/jPrHoZRYE/1OVhZDum0IYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;flow&quot;
        title=&quot;&quot;
        src=&quot;/static/ebff249f14a266fa7fbdc0f601d37a08/1cfc2/flow.png&quot;
        srcset=&quot;/static/ebff249f14a266fa7fbdc0f601d37a08/3684f/flow.png 225w,
/static/ebff249f14a266fa7fbdc0f601d37a08/fc2a6/flow.png 450w,
/static/ebff249f14a266fa7fbdc0f601d37a08/1cfc2/flow.png 900w,
/static/ebff249f14a266fa7fbdc0f601d37a08/21482/flow.png 1350w,
/static/ebff249f14a266fa7fbdc0f601d37a08/d61c2/flow.png 1800w,
/static/ebff249f14a266fa7fbdc0f601d37a08/90eea/flow.png 2254w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Netty의 &lt;code class=&quot;language-text&quot;&gt;EventLoop&lt;/code&gt;가 client로부터 오는 모든 요청을 다 받는다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;gRPC는 NonBlocking으로 처리&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;REST는 Tomcat이 Blocking으로 처리&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;서블릿 스레드는 Armeria의 &lt;code class=&quot;language-text&quot;&gt;BlockingTaskExecutor&lt;/code&gt;이다&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;EventLoop&lt;/code&gt;가 해당 request를 처리하라는 작업을 &lt;code class=&quot;language-text&quot;&gt;BlockingTaskExecutor&lt;/code&gt;에게 위임&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;BlockingTaskExecutor&lt;/code&gt;에서 어댑터를 이용해 서블릿 컨테이너를 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 448px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8943dd5097e3110b3ec831856ba0d15c/33b38/blockNonBlockThread.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.333333333333332%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABG0lEQVR42j1R7XKCMBDkcTq2ts70d6sYQgKB8C0iglp9/1fY3p2lPzK3bG43O0uQag2blbBpju+dQmwdTJJBmxRKW5oO+8ggzQrh+SidIFQGRdUiL2rR8v7reoPAGQvna7nY7TUSVxBukPmKsJfJgrLuCNfwZSs7sc1QNb3sLgHeN58Ihq7HMN1wnn9IdMDxdCF8x+l8xTBehO+HCfP1QdwN0+VO/ExmR8IPjNPzvutHChQjSGMjCTlJqGJ5nZPw5GQurxDFiUxOzJXwd1F1xJWULkd7GIRbvX0gsFEkHSYux9d2L4KlEz6Mt2EkkzsNlZZOfdkQ5wXXbS+GL6s1G2qYtJDXF0PGSy+Mn8m9iNjgadj+JeQuD/8/5RcCG+GpqeQkfQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;blockNonBlockThread&quot;
        title=&quot;&quot;
        src=&quot;/static/8943dd5097e3110b3ec831856ba0d15c/33b38/blockNonBlockThread.png&quot;
        srcset=&quot;/static/8943dd5097e3110b3ec831856ba0d15c/3684f/blockNonBlockThread.png 225w,
/static/8943dd5097e3110b3ec831856ba0d15c/33b38/blockNonBlockThread.png 448w&quot;
        sizes=&quot;(max-width: 448px) 100vw, 448px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;EventLoop는 변경되지 않는 하나의 Thread로 움직이며,&lt;br&gt;
작업 (Runnable 또는 Callable)을 EventLoop 구현으로 직접 제출해 즉시 또는 예약 실행할 수 있다.&lt;br&gt;
구성과 사용 가능한 코어에 따라서는 리소스 활용을 최적화하기 위해 여러 EventLoop가 생성되고,&lt;br&gt;
여러 Channel에 서비스를 제공하기 위해 단일 EventLoop가 할당되는 경우도 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;네티인액션&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;grpc-통신-종류&quot; style=&quot;position:relative;&quot;&gt;gRPC 통신 종류&lt;a href=&quot;#grpc-%ED%86%B5%EC%8B%A0-%EC%A2%85%EB%A5%98&quot; aria-label=&quot;grpc 통신 종류 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jdalma/armeria-grpc-kotlin/blob/master/src/test/java/com/example/armeriaserver/grpc/SampleServiceTest.java#L58&quot;&gt;테스트 코드&lt;/a&gt;에서 확인할 수 있다.&lt;br&gt;
또는 &lt;a href=&quot;https://grpc.io/docs/languages/java/basics/&quot;&gt;공식 문서 Basics Tutorial&lt;/a&gt;에서도 확인 가능하다.&lt;/p&gt;
&lt;p&gt;protocol buffer는 이진 부호화 라이브러리이며 부호화할 데이터를 위한 스키마를 &lt;code class=&quot;language-text&quot;&gt;proto&lt;/code&gt;언어를 통해 아래와 같이 메세지를 정의하고 protocol buffer compiler를 통해 원하는 언어로 &lt;a href=&quot;https://protobuf.dev/programming-guides/proto3/#generated&quot;&gt;generate&lt;/a&gt; 할 수 있다.&lt;br&gt;
생성된 코드를 호출해 부호화하고 복호화할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;syntax = &amp;quot;proto3&amp;quot;;

package grpc.hello;

option java_multiple_files = true;
option java_package = &amp;quot;stub.hello&amp;quot;;

message HelloRequest {
  string message = 1;
}

message HelloResponse {
  string message = 1;
}

service HelloService {
  rpc SimpleRPC (HelloRequest) returns (HelloResponse) {}
  rpc ClientSideStreaming (stream HelloRequest) returns (HelloResponse) {}
  rpc ServerSideStreaming (HelloRequest) returns (stream HelloResponse) {}
  rpc BidirectionalStreaming (stream HelloRequest) returns (stream HelloResponse) {}
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;HelloService&lt;/code&gt;의 내용과 같이 이 메시지를 통하여 어떤 통신을 사용할 것인지 정의할 수 있다.&lt;br&gt;
각 통신 방법마다 지원하는 요청-응답 방식이 다르다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Async는 비동기-논블로킹 통신, Future는 동기-논블로킹 통신&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simple&lt;/strong&gt; : 단일 요청, 단일 응답&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Future Stub&lt;/li&gt;
&lt;li&gt;Blocking Stub&lt;/li&gt;
&lt;li&gt;기본(Async) Stub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ServerSideStreaming&lt;/strong&gt; : 서버 → 클라이언트 스트림 통신&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;not support&lt;/code&gt; Future Stub&lt;/li&gt;
&lt;li&gt;Blocking Stub&lt;/li&gt;
&lt;li&gt;기본(Async) Stub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ClientSideStreaming&lt;/strong&gt; : 클라이언트 → 서버 스트림 통신&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;not support&lt;/code&gt; Future Stub&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;not support&lt;/code&gt; Blocking Stub&lt;/li&gt;
&lt;li&gt;기본(Async) Stub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;BidirectionalStreaming&lt;/strong&gt; : 양방향 스트림 통신&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;not support&lt;/code&gt; Future Stub&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;not support&lt;/code&gt; Blocking Stub&lt;/li&gt;
&lt;li&gt;기본(Async) Stub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;armeria의-server-thread는-어떻게-처리될까&quot; style=&quot;position:relative;&quot;&gt;Armeria의 Server Thread는 어떻게 처리될까?&lt;a href=&quot;#armeria%EC%9D%98-server-thread%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B2%98%EB%A6%AC%EB%90%A0%EA%B9%8C&quot; aria-label=&quot;armeria의 server thread는 어떻게 처리될까 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/03f8e242df029769652d8a7aa43df5f7/705cc/expect.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.44444444444444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlUlEQVR42mVSWXbjMAzzfRpb1Oo1XpOmTe5/HhSkJ7O8+cCzRNEgSKLqhw5OGngvhCOa/yCMv+HDv2f/9xtRNY4X8SQVBuU3SWByCCRjMc3x3qNuaite143lO3F8O9+VQ8+VssacEGJg4AL3C03zcX4Jrb5s24l1wf3xwOP5wrytmNcV63FDaYsVr7zUiClCW9+3Bet6JWbcbjuOY8O+L4ZxGk11jIKcA7q+Q0qeELRdS0HaXY1KSJhyZLCg7ws6fjcSf34euN8P+z6fX/h+vfiWkVNAKQmpZHbwQVUXBAp6z/8kZIJW0cqlREPij/M8kfCGZZnw+P6yAuPYYRh7U6wKMzGo+uj/KCxU1bb5TCCptqWY5wHX62DKZ85OVWhOaU8BMTobwzCOPJNQjPBi7Wob52YFA+e5LFcj0Ng09diO3QjexXQJduZ76Vv7z1puXE0/BXhuWYPvoWsBPeuw1Q4S1AW1bdzs471ZyKltRMw6zrYctUW1TSLxOQcdg8JMLWrswCLFfKoIgUuxu7d7jJFOyUb+A0WpQjpfCD9CAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;expect&quot;
        title=&quot;&quot;
        src=&quot;/static/03f8e242df029769652d8a7aa43df5f7/1cfc2/expect.png&quot;
        srcset=&quot;/static/03f8e242df029769652d8a7aa43df5f7/3684f/expect.png 225w,
/static/03f8e242df029769652d8a7aa43df5f7/fc2a6/expect.png 450w,
/static/03f8e242df029769652d8a7aa43df5f7/1cfc2/expect.png 900w,
/static/03f8e242df029769652d8a7aa43df5f7/705cc/expect.png 1071w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1a839906e933a6d47cb6711d8d54e93c/77672/real.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABbElEQVR42nVSW3KDMAzkQAVLfoAxr5BASyYfbe9/GFWSA6Wd9mPH2rG8WskqUopkoDqA+BcMGVMq0BoC5pVwyT9zPgtJAkQCQL60mvBbsDJGcwyAxmXFxQ0oL/m9cGsd3wEVImAdkq9rrQRQHm4AXg4eU0uX242GaaRpnun+eNCyrjSMmU/zhc2IoLooKTSBxiHRPHPC1NM4dkcs6LqWQu3JO6AQLMU2Mncae2+pjo12o4LiomUHXRcVIvb6eqNtW+l+f1N8fL7TdbmqgKDtEr/LbwUueJ11sc9JKuzJNVcW0ZQadSdOl3Wh7b5pLPfSetN4jeVMffp2KIj8294jOW5JzoHbb9uGek4ULnN2wakbyW+4o/MHyox/CKa+e84DdSaWk89OL/PEY4lacG9ZcnZE4VmQdwwM+RC4YnZ4hnyY808B79Wp/CY6dougXE50XnUKXRXeP+Cd+n8P897lXcwGkGNEm+9Ains9vwBJh0JZg038fgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;real&quot;
        title=&quot;&quot;
        src=&quot;/static/1a839906e933a6d47cb6711d8d54e93c/1cfc2/real.png&quot;
        srcset=&quot;/static/1a839906e933a6d47cb6711d8d54e93c/3684f/real.png 225w,
/static/1a839906e933a6d47cb6711d8d54e93c/fc2a6/real.png 450w,
/static/1a839906e933a6d47cb6711d8d54e93c/1cfc2/real.png 900w,
/static/1a839906e933a6d47cb6711d8d54e93c/77672/real.png 1060w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트의 메인 스레드에서 다른 서비스로 요청해도 모든 요청을 처리하는 스레드는 서버 스레드 한 개이다
&lt;ul&gt;
&lt;li&gt;Sample, Hello 서로 다른 서비스로 요청을 보내도 요청을 처리하는 스레드는 한 개이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위와 같은 테스트를 두 개의 클라이언트에서 각각 한 번씩 보내도 똑같은 결과다
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;결국 서버에서 받는 모든 요청들은 각 클라이언트의 소켓에 맞는 서버의 스레드들이 처리한다&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;한 개의 클라이언트의 메인 스레드에서 자식 스레드 여러 개를 서버에 요청 전송하여도 한 개의 서버 스레드가 처리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;위와 같은 결과가 나오는 이유를 이해하려면 이벤트 루프, 멀티플렉싱에 대한 이해가 필요하다.&lt;br&gt;
&lt;a href=&quot;https://github.com/jdalma/footprints/blob/main/%EC%A0%95%EB%A6%AC/%EB%A9%80%ED%8B%B0%ED%94%8C%EB%A0%89%EC%8B%B1.md&quot;&gt;EventLoop와 멀티플렉싱에 대해 정리한 글&lt;/a&gt;을 참고하자&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;EventLoop는 단일 스레드로 요청과 응답을 처리하므로 해당 스레드를 블록하면 EventLoop 자체를 블록하는 것과 같다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/084f82f52f6b152b7a175bdb3bc733ab/561da/blockingTaskExecutor.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 34.22222222222222%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA7UlEQVR42pWRSW7DMAxFdY7WQ2IN1izbQpw6qwK9/5l+KSnu2l08EBxA8pNsWTO+jx98pQNcOwhlIeaCq0xSQ3CBWTZk4Z1TJoCrViu1xzgpsJQyXvuBHBKUDdAu/THbWK1xFLe+YlyE8S1XbGOpvjIebDYOz8cLwUT0k8RwFxjvsjIQ3cjxWRgmfAy8+t04Vf+ke+cLzNEG2/ZEDBmSJsjZt2k6kIxAxQL97Tqsv0mkTA23HUtpvO6Iy6NaHzPJWUlKpFvaS7Ce1jVugQ0bXNxqg4KmmKBtuSqPuQ4rdzqPWh5QGrQ78X9JPSX/AkqfyygqbdphAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;blockingTaskExecutor&quot;
        title=&quot;&quot;
        src=&quot;/static/084f82f52f6b152b7a175bdb3bc733ab/1cfc2/blockingTaskExecutor.png&quot;
        srcset=&quot;/static/084f82f52f6b152b7a175bdb3bc733ab/3684f/blockingTaskExecutor.png 225w,
/static/084f82f52f6b152b7a175bdb3bc733ab/fc2a6/blockingTaskExecutor.png 450w,
/static/084f82f52f6b152b7a175bdb3bc733ab/1cfc2/blockingTaskExecutor.png 900w,
/static/084f82f52f6b152b7a175bdb3bc733ab/561da/blockingTaskExecutor.png 969w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ee84d55503ee3e11c8d3623ef8b53226/129e9/armeriaThread.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63.11111111111111%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACeElEQVR42nVSy2oUQRTtj3MnSkSJuHAn+AE+l4ILMREj6MalEgyCEOLCBwTduBAEH5CoSZxEyTg909PPqn7Uo7uPp2okKsSGy7l1u+rUOfdW8OnLFna297D9dQ97YYjz0X3Mh0s4O72HU9ktzJWLOCGJYgFzkrlYJC7gpLiN0+kS5rMlnMnu4Fh6E0/Ktwjer67h89pzfHn6AtvPXmJ94zVWd95hef0Vrn14jAubD3FlcxmXth7h8scHuEi8urGCc4O7OFrcwPGEF2WLOJJcx4p8g6CuahhGb1q0WYao2EdkY4RVxMgwFSkmjMjleUzMEYkMo3KCQRtioEPsmhDfzAhZKxFAKcBawBhiCwgBE6YoEw0jFIqpu9CgiCoo6dYl6kKhThlx48/o3KCZ1IA2CKJhjjxTsLrDOKxQ5xW6pkGtLGTVoKw0qkb7ddsBynTQtvOo6Mp91tWVoUsS6togSWkjmkJQQVdTMQn7vvebteZldNBSSdu26BhFkWM0GqEspa/5/1TnXAbJVCIcsz9xhvFEQgtKV47Q83GPmRErKq1ot24gpcR4PGZes07rFGB/ty4oSailpk2DMm1gMvkPodbaq1DKofVqXJRlhSROPPmBQkuF/mTXo+/YCLjcNUQfWHYKXe6i4z9L664mhMRwOERDlS2ba3nGK8Rh31+EM4Ude8gh0JahEkfsaoIvIo5j9lSgO1B4KKH6o1DPyHu6QD9z0rpnxqUk4e7uAGmawrKPfiiH8fVUqDT71CgflmQNn4jDms9DcjDKvT+q7niJq7uh/ZewmRbIfiSoJjkSoooLj3XE+n7KeoHiZ8ZIAU6+igTy7zH6usEviGfRJsFy3hEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;armeriaThread&quot;
        title=&quot;&quot;
        src=&quot;/static/ee84d55503ee3e11c8d3623ef8b53226/1cfc2/armeriaThread.png&quot;
        srcset=&quot;/static/ee84d55503ee3e11c8d3623ef8b53226/3684f/armeriaThread.png 225w,
/static/ee84d55503ee3e11c8d3623ef8b53226/fc2a6/armeriaThread.png 450w,
/static/ee84d55503ee3e11c8d3623ef8b53226/1cfc2/armeriaThread.png 900w,
/static/ee84d55503ee3e11c8d3623ef8b53226/129e9/armeriaThread.png 1161w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event Loop가 처리하는 기준&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 호스트의 동일한 Port에서 10초 간격으로 요청을 보내면 한 개의 Armeria Server Thread로 처리하지만&lt;/li&gt;
&lt;li&gt;11초 간격으로 보내면 클라이언트의 Port가 바뀌면서 각기 다른 Armeria Server Thread가 처리한다&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7540#section-6.8&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;GOAWAY&lt;/code&gt; 프레임&lt;/a&gt;을 통해 서로 데이터를 다 보냈다는 확인을 한다.&lt;/li&gt;
&lt;li&gt;마지막 &lt;strong&gt;RST flag&lt;/strong&gt;를 보내면서 서버에서 Socket이 닫히고 TCP 커넥션을 끊는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0000c7780b5d9a5ecc10f5a4a85f67c7/4ef3c/packet.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAACKElEQVR42jWRW1PaUBSF8/+nfXDaUVvbh9qXUsRaL0BIuCQQEohAguGWcBNUKNQBLzii1a8Hpj0z66x19sxas/c+kpXNY6R1lLjCwf4hJ0fRNaLHMY4PT4SOcfSPD34cEY8mSCtp9JSGkclRzBcYd9r0/Rbz+RzJq59Rb9bQTYOfsghRZBQthaolkdPKWseSCRIZVbBMOqdh2hZ2+RT3zKEmvFeBT6VQYH5zg2TpHlbWQz4xCH2LEw7J7EdSRCJJwmGVyF6SkKjtCb3i48Oc6NIkKdtkU47w1rmoNenWPRaLBVLw+5729YKCPyRqOSilGik3IOn66PUOxe6QvN+n0Blg+D2qF1Nak9s1gukdvvDfzuYsRdiflxekwX3AxUOHUr9ItBgXIRlUR0VvZMg2M1jtHGagi2BDBGp44wqjxy7DRVsg4Oqxx3QyoNVoMpvNkC7FNRKzO+0OqVKJ3JmH7lbRHRelaKNVXFGrYoqRzJpHSSy/N5nQGY/oTsYEgu9v5jwtH1kul0j9S5/zK5+yZ5PJJ8WvJbGKGpYrOippOA2bcr3AqWeusdLdYZP2oE5n0BDc4GE6gudnXgHJ7ftUzwPSjk1ETZBSZVRDI1HKEzU1lNM8sm0Qt3OCc6hlk0q3hdvzcXqCz9tc/xrxcHfH6kjl3e9UvobJfvpCbHMbdfMDia2PJLZ3iG2J985nYhubnLx5R/Tte5SNbcq7IVa+/5i3Al6fluvAv/O6Y4OsC0+WAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;packet&quot;
        title=&quot;&quot;
        src=&quot;/static/0000c7780b5d9a5ecc10f5a4a85f67c7/1cfc2/packet.png&quot;
        srcset=&quot;/static/0000c7780b5d9a5ecc10f5a4a85f67c7/3684f/packet.png 225w,
/static/0000c7780b5d9a5ecc10f5a4a85f67c7/fc2a6/packet.png 450w,
/static/0000c7780b5d9a5ecc10f5a4a85f67c7/1cfc2/packet.png 900w,
/static/0000c7780b5d9a5ecc10f5a4a85f67c7/21482/packet.png 1350w,
/static/0000c7780b5d9a5ecc10f5a4a85f67c7/d61c2/packet.png 1800w,
/static/0000c7780b5d9a5ecc10f5a4a85f67c7/4ef3c/packet.png 2863w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SETTINGS 프레임&lt;/strong&gt; 을 통해 초기 흐름 제어 창 크기와 최대 동시 스트림 수를 전송한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WINDOW_UPDATE 프레임&lt;/strong&gt; 을 통해 수신자를 압도하지 않도록 전송하는 데이터의 양을 조절하기 위해 창 크기를 업데이트한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HEADERS 프레임&lt;/strong&gt; 으로 HTTP/2 요청을 전송하고 &lt;strong&gt;DATA 프레임&lt;/strong&gt; 으로 응답을 받는다.&lt;/li&gt;
&lt;li&gt;응답이 처리되고 클라이언트가 더 이상의 데이터를 기다리지 않으면 클라이언트는 &lt;strong&gt;GOAWAY 프레임&lt;/strong&gt; 을 보내 연결을 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;소감과-무지-목록&quot; style=&quot;position:relative;&quot;&gt;소감과 무지 목록&lt;a href=&quot;#%EC%86%8C%EA%B0%90%EA%B3%BC-%EB%AC%B4%EC%A7%80-%EB%AA%A9%EB%A1%9D&quot; aria-label=&quot;소감과 무지 목록 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;proto를 작성해서 generate된 stub들을 이용하여 client ↔︎ server 스트림 통신을 테스트해보는 간단한 테스트 코드를 작성해보았다.&lt;br&gt;
그리고 새로운 서비스를 개발할 때 다른 팀원들이 템플릿처럼 사용할 수 있도록 회사 레포에 등록해두었다.&lt;br&gt;
이벤트 루프를 블로킹하지 않는 주변 인프라가 더 많이 필요하며, 클라이언트와 서버 둘 다 수정이 필요하여 기존 서비스들에 적용하기에는 큰 도전일 것으로 예상된다.&lt;br&gt;
이 기술들을 한 번에 적용하기 보다는 &lt;a href=&quot;https://spring.io/blog/2015/03/22/using-google-protocol-buffers-with-spring-mvc-based-rest-services&quot;&gt;JSON을 proto로 바꿔보는 단계&lt;/a&gt;를 밟는것도 좋을 것 같다.&lt;/p&gt;
&lt;p&gt;해당 프로젝트에는 처음 접하는 기술적인 키워드들이 많이 포함되어 있었고, 네트워크 지식이 부족하다고 느꼈다.&lt;br&gt;
&lt;strong&gt;무지 목록&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Netty에 대한 이해&lt;/li&gt;
&lt;li&gt;TCP 소켓 프로그래밍에 대한 이해&lt;/li&gt;
&lt;li&gt;HTTP/2에 대한 이해&lt;/li&gt;
&lt;li&gt;Armeria 아키텍처&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[Iterator?Enumerator?Iterable?]]></title><description><![CDATA[해당 글은 백준 - 키로거문제를 풀며 로 풀면 시간초과가 났었는데 를 사용하여 해결하였었다. (으로도 풀 수 있다)
이 문제를 계기로 와 를 정리해보려 한다. 참고 링크  Iterator  Enumeration  difference-between…]]></description><link>https://jdalma.github.io/2022y/iterator/</link><guid isPermaLink="false">https://jdalma.github.io/2022y/iterator/</guid><pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;해당 글은 &lt;a href=&quot;https://github.com/PowerAlgorithm/Algorithm-Study/blob/main/%5BWeek5%20-%20Data%20Structure%5D/%EC%A0%95%ED%98%84%EC%A4%80/C_5397.java&quot;&gt;백준 - 키로거&lt;/a&gt;문제를 풀며 &lt;code class=&quot;language-text&quot;&gt;LinkedList&lt;/code&gt;로 풀면 시간초과가 났었는데 &lt;code class=&quot;language-text&quot;&gt;ListIterator&lt;/code&gt;를 사용하여 해결하였었다. (&lt;code class=&quot;language-text&quot;&gt;Stack&lt;/code&gt;으로도 풀 수 있다)&lt;br&gt;
이 문제를 계기로 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;Enumerator&lt;/code&gt;를 정리해보려 한다.&lt;br&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;참고 링크
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;java 8 docs&lt;/code&gt; Iterator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;java 8 docs&lt;/code&gt; Enumeration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/948194/difference-between-java-enumeration-and-iterator&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;stackoverflow&lt;/code&gt; difference-between-java-enumeration-and-iterator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id=&quot;iterator-enumerator-iterable-&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;Iterator, Enumerator, Iterable&lt;/strong&gt; ?&lt;a href=&quot;#iterator-enumerator-iterable-&quot; aria-label=&quot;iterator enumerator iterable  permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;의 공식 문서에 따르면 Collection Framework에서 &lt;code class=&quot;language-text&quot;&gt;Enumeration&lt;/code&gt;을 대체하며 두 가지의 차이점이 있다고 한다.&lt;br&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 사용하면 반복하는 동안 기본 컬렉션에서 요소를 제거할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Enumeration&lt;/code&gt;의 이름을 개선하였다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Enumeration&lt;/code&gt;의 공식 문서에는 해당 인터페이스는 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;와 중복되며, 선택적 제거를 할 수 있고 메서드 이름이 더 짧다&lt;br&gt;
&quot;&lt;code class=&quot;language-text&quot;&gt;Enumeration&lt;/code&gt;보다 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 사용하는 것을 고려해라&quot; 라고 적혀있다.&lt;br&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;는 원소를 삭제하는 메소드도 지원되고 메소드 이름도 짧게 변경되었으니 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 사용해라 라고 이해할 수 있다.&lt;br&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이 글을 작성하게 된 이유인 &lt;code class=&quot;language-text&quot;&gt;ListIterator&lt;/code&gt;인터페이스도 예상하듯이 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 확장하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;추가로 &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/language/foreach.html&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;java 8 docs&lt;/code&gt; Iterable For-Each Loop&lt;/a&gt;를 확인해보자&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public interface Collection&amp;lt;E&amp;gt; extends Iterable&amp;lt;E&amp;gt;

...

// 이 인터페이스를 구현하면 개체가 &amp;quot;for-each 루프&amp;quot; 문의 대상이 될 수 있습니다
public interface Iterable&amp;lt;T&amp;gt; {
    Iterator&amp;lt;T&amp;gt; iterator();
    ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;Collection 인터페이스는 &lt;code class=&quot;language-text&quot;&gt;Iterable&lt;/code&gt;을 확장하고 있으며,&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Iterable&lt;/code&gt;은 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 제공한다.&lt;/li&gt;
&lt;li&gt;아래는 &lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt;의 구현체인 &lt;code class=&quot;language-text&quot;&gt;ArrayList&lt;/code&gt;를 &lt;strong&gt;For-Each&lt;/strong&gt;로 작성한 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public static void enhancedForLoop() {
    List&amp;lt;String&amp;gt; test = new ArrayList&amp;lt;&amp;gt;(Arrays.asList(&amp;quot;A&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;C&amp;quot;, &amp;quot;D&amp;quot;, &amp;quot;E&amp;quot;));
    for (String e : test) {
        if (e.equals(&amp;quot;B&amp;quot;)) {
            continue;
        }
        System.out.println(e);
    }
}

...

// 디컴파일
public static void enhancedForLoop() {
    List&amp;lt;String&amp;gt; test = new ArrayList(Arrays.asList(&amp;quot;A&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;C&amp;quot;, &amp;quot;D&amp;quot;, &amp;quot;E&amp;quot;));
    Iterator var1 = test.iterator();

    while(var1.hasNext()) {
        String e = (String)var1.next();
        if (!e.equals(&amp;quot;B&amp;quot;)) {
            System.out.println(e);
        }
    }

}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;디컴파일된 부분을 보면 &lt;code class=&quot;language-text&quot;&gt;ArrayList&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 생성하여 작성하지도 않은 &lt;code class=&quot;language-text&quot;&gt;while&lt;/code&gt;문으로 반복된다.&lt;br&gt;
&lt;strong&gt;For-Each Loop&lt;/strong&gt;는 각 자료구조에 구현된 &lt;code class=&quot;language-text&quot;&gt;iterator()&lt;/code&gt;를 호출하여 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 사용한다라고 볼 수 있다.&lt;br&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;concurrentmodificationexception-&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;ConcurrentModificationException&lt;/strong&gt; ?&lt;a href=&quot;#concurrentmodificationexception-&quot; aria-label=&quot;concurrentmodificationexception  permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public static void main(String[] args) {
    List&amp;lt;String&amp;gt; chars = new ArrayList&amp;lt;&amp;gt;();
    chars.add(&amp;quot;A&amp;quot;);
    chars.add(&amp;quot;B&amp;quot;);
    chars.add(&amp;quot;C&amp;quot;);
    Iterator&amp;lt;String&amp;gt; iterator = chars.iterator();
    iterator.next();
    chars.remove(&amp;quot;A&amp;quot;);
    iterator.next(); // ConcurrentModificationException !!!
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위와 같이 작성하면 &lt;strong&gt;java.util.ConcurrentModificationException&lt;/strong&gt;예외를 던진다.&lt;br&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ArrayList&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;iterator()&lt;/code&gt;,&lt;code class=&quot;language-text&quot;&gt;remove(Object o)&lt;/code&gt;를 확인해보자
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;remove()&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;fastRemove()&lt;/code&gt;를 호출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved &amp;gt; 0)
        System.arraycopy(elementData, index+1, elementData, index,
                            numMoved);
    elementData[--size] = null; // clear to let GC do its work
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;modCount&lt;/code&gt;를 증가시킨다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;System.arraycopy&lt;/code&gt;를 통해 삭제 인덱스 기준으로 복사한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public Iterator&amp;lt;E&amp;gt; iterator() {
        return new Itr();
}
private class Itr implements Iterator&amp;lt;E&amp;gt; {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i &amp;gt;= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i &amp;gt;= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet &amp;lt; 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ArrayList&lt;/code&gt;의 inner class인 &lt;code class=&quot;language-text&quot;&gt;Itr&lt;/code&gt;을 생성한다.&lt;/li&gt;
&lt;li&gt;컬렉션이 요소를 추가하는 함수나 삭제하는 함수를 호출할 때마다 조작 횟수를 기억하는 &lt;code class=&quot;language-text&quot;&gt;modCount&lt;/code&gt;를 보유하고 있다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;가 생성될 때 &lt;code class=&quot;language-text&quot;&gt;expectedModCount&lt;/code&gt;에 &lt;code class=&quot;language-text&quot;&gt;modCount&lt;/code&gt;를 대입한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;remove()&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;checkForComodification&lt;/code&gt;를 통해 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;expectedModCount&lt;/code&gt;&lt;/strong&gt; 와 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;ArrayList&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;modCount&lt;/code&gt;&lt;/strong&gt; 는 다르게 되므로 해당 예외를 던지게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public static void main(String[] args) {
    List&amp;lt;String&amp;gt; chars = new ArrayList&amp;lt;&amp;gt;();
    chars.add(&amp;quot;A&amp;quot;);
    chars.add(&amp;quot;B&amp;quot;);
    chars.add(&amp;quot;C&amp;quot;);
    Iterator&amp;lt;String&amp;gt; iterator = chars.iterator();
    iterator.next();
    iterator.remove();
    iterator.remove(); // IllegalStateException !!!
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;remove()&lt;/code&gt;를 사용할 때도 지켜야할 규칙이 있다.&lt;br&gt;
위의 &lt;code class=&quot;language-text&quot;&gt;Itr&lt;/code&gt; 구현 클래스를 보면 &lt;code class=&quot;language-text&quot;&gt;next()&lt;/code&gt;가 호출될 때 마다 &lt;code class=&quot;language-text&quot;&gt;lastRet&lt;/code&gt;을 &lt;code class=&quot;language-text&quot;&gt;cursor&lt;/code&gt;로 업데이트하고, &lt;code class=&quot;language-text&quot;&gt;remove()&lt;/code&gt;가 호출될 때 마다 &lt;code class=&quot;language-text&quot;&gt;lastRet&lt;/code&gt;을 &lt;code class=&quot;language-text&quot;&gt;-1&lt;/code&gt;로 업데이트 하기 때문에 &lt;strong&gt;한 번의 next() 호출당 remove() 호출이 두 번 이상 존재하면 오류가 발생한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;반복자 클래스는 요소를 추가하는 메서드는 제공하지 않는다. 결국 반복자의 주요 기능은 &lt;code class=&quot;language-text&quot;&gt;순회&lt;/code&gt;이며, 요소를 추가하는 작업은 반복자에 적절하지 않다는 것을 유의하자.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Iterator 두 개를 만든 아래의 실행 결과는?&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public static void main(String[] args) {
    List&amp;lt;String&amp;gt; chars = new ArrayList&amp;lt;&amp;gt;();
    chars.add(&amp;quot;A&amp;quot;);
    chars.add(&amp;quot;B&amp;quot;);
    chars.add(&amp;quot;C&amp;quot;);
    Iterator&amp;lt;String&amp;gt; iterator1 = chars.iterator();
    Iterator&amp;lt;String&amp;gt; iterator2 = chars.iterator();
    iterator1.next();
    iterator1.remove();
    iterator2.next(); // 실행 결과는???
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;strong&gt;ConcurrentModificationException&lt;/strong&gt; 예외가 발생한다.&lt;br&gt;
그 이유는 최초 &lt;code class=&quot;language-text&quot;&gt;modCount&lt;/code&gt;는 4이지만, &lt;code class=&quot;language-text&quot;&gt;iterator1.remove()&lt;/code&gt;에서 &lt;code class=&quot;language-text&quot;&gt;ArraysList.this.remove(lastRet)&lt;/code&gt;을 통해, ArraysList 내부 속성의 &lt;code class=&quot;language-text&quot;&gt;modCount&lt;/code&gt;는 4로 증가되기 때문에 &lt;code class=&quot;language-text&quot;&gt;iterator2&lt;/code&gt;의 &lt;code class=&quot;language-text&quot;&gt;expectedModCount&lt;/code&gt;와 틀리게된다.&lt;/p&gt;
&lt;h3&gt;이 경우에는 removeIf와 replaceAll로 대체할 수 있다.&lt;/h3&gt;  
&lt;p&gt;꼭 내부 컬렉션을 직접 조작하고 싶다면 아래의 방법을 사용할 수 있다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;private List&amp;lt;Dish&amp;gt; menu;

@BeforeEach
void setUp() {
    menu = new ArrayList&amp;lt;&amp;gt;(){{
        this.add(new Dish(&amp;quot;pork&amp;quot;, false, 800, Dish.Type.MEAT));
        this.add(new Dish(&amp;quot;beef&amp;quot;, false, 700, Dish.Type.MEAT));
        this.add(new Dish(&amp;quot;chicken&amp;quot;, false, 400, Dish.Type.MEAT));
        this.add(new Dish(&amp;quot;french fries&amp;quot;, true, 530, Dish.Type.OTHER));
        this.add(new Dish(&amp;quot;rice&amp;quot;, true, 350, Dish.Type.OTHER));
        this.add(new Dish(&amp;quot;season fruit&amp;quot;, true, 120, Dish.Type.OTHER));
        this.add(new Dish(&amp;quot;pizza&amp;quot;, true, 550, Dish.Type.OTHER));
        this.add(new Dish(&amp;quot;prawns&amp;quot;, false, 300, Dish.Type.FISH));
        this.add(new Dish(&amp;quot;salmon&amp;quot;, false, 450, Dish.Type.FISH));
    }};
}

@Test
void removeIf() {
    Assertions.assertThat(menu.size()).isEqualTo(9);

    menu.removeIf(Dish::vegetarian);
    Assertions.assertThat(menu.size()).isEqualTo(5);
}

@Test
void replaceAll() {
    List&amp;lt;Dish&amp;gt; before = menu.stream().filter(dish -&amp;gt; dish.calories() &amp;lt;= 490).toList();
    Assertions.assertThat(before.size()).isEqualTo(5);

    menu.replaceAll(dish -&amp;gt; {
        if (dish.calories() &amp;gt; 500) {
            return new Dish(dish.name(), dish.vegetarian(), 490, dish.type());
        }
        return dish;
    });

    List&amp;lt;Dish&amp;gt; after = menu.stream().filter(dish -&amp;gt; dish.calories() &amp;lt;= 490).toList();
    Assertions.assertThat(after.size()).isEqualTo(9);
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;코틀린에서 Iterable과 Iterator를 구현해보기&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Iterable&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 구현했을 때&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Iterable&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;Iterator&lt;/code&gt;를 구현하지 않고 &lt;code class=&quot;language-text&quot;&gt;operator&lt;/code&gt;만 작성했을 때&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 두 가지를 테스트해보았다.&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;kotlin&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class IterableTest: BehaviorSpec ({

    val size = 5
    val sum = 15

    given(&amp;quot;Iterable과 Iterator를 implement한 클래스는&amp;quot;) {
        class ImplementIterable(private val size: Int): Iterable&amp;lt;Int&amp;gt; {
            override fun iterator(): Iterator&amp;lt;Int&amp;gt; = ImplementIterator(size)

            inner class ImplementIterator(private val size: Int) : Iterator&amp;lt;Int&amp;gt; {
                var number: Int = 0
                override fun hasNext(): Boolean = number++ &amp;lt; size
                override fun next(): Int = number
            }
        }


        `when`(&amp;quot;Iterable,Iterator 둘 다 for 문을 사용할 수 있다.&amp;quot;) {

            then(&amp;quot;for .. in&amp;quot;) {
                val iterable = ImplementIterable(size)
                val iterator = iterable.iterator()

                var test = 0
                for (i in iterable) { test += i }

                test shouldBeEqual sum
                iterable.iterator().next() shouldBeEqual 0

                var test2 = 0
                for (i in iterator) { test2 += i }
                test2 shouldBeEqual sum

                test shouldBeEqual test2
            }

            then(&amp;quot;forEach 블록&amp;quot;) {
                val iterable = ImplementIterable(size)
                val iterator = iterable.iterator()

                var test = 0
                iterable.forEach { test += it }
                test shouldBeEqual sum

                var test2 = 0
                iterator.forEach { test2 += it }
                test2 shouldBeEqual sum
            }

            then(&amp;quot;Iterable에만 sum()이 존재한다.&amp;quot;) {
                val iterable = ImplementIterable(size)

                iterable.sum() shouldBeEqual sum
            }
        }
    }

    given(&amp;quot;Iterable과 Iterator를 implement하지않고 operator만 작성한 클래스는&amp;quot;) {
        class JustIterable(private val size: Int) {
            operator fun iterator(): JustIterator = JustIterator(size)

            inner class JustIterator(private val size: Int) {
                private var number: Int = 0
                operator fun hasNext(): Boolean = number++ &amp;lt; size
                operator fun next(): Int = number
            }
        }

        `when`(&amp;quot;Iterable만 for 문을 사용할 수 있다.&amp;quot;) {
            val iterable = JustIterable(size)
            val iterator = iterable.iterator()

            then(&amp;quot;for .. in&amp;quot;) {
                var test = 0
                for (i in iterable) { test += i }
                test shouldBeEqual sum
            }

            then(&amp;quot;컴파일 에러&amp;quot;) {
//                for (i in iterator) { }
//                iterable.forEach { test += it }
//                iterator.forEach { test += it }
//                iterable.sum()
//                iterator.sum()
            }
        }
    }
})&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;</content:encoded></item><item><title><![CDATA[롬복 생성자 어노테이션 써도 될까?]]></title><description><![CDATA[아래의 링크를 참고하였습니다  Lombok 사용상 주의점  constructor  configuration @AllArgsConstructor, @RequiredArgsConstructor 사용금지  Lombok을 사용하면 IDE Generate…]]></description><link>https://jdalma.github.io/2022y/lombok/</link><guid isPermaLink="false">https://jdalma.github.io/2022y/lombok/</guid><pubDate>Mon, 03 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;아래의 링크를 참고하였습니다
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kwonnam.pe.kr/wiki/java/lombok/pitfall&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;kwonnam&lt;/code&gt; Lombok 사용상 주의점&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.projectlombok.org/features/constructor&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;projectlombok&lt;/code&gt; constructor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://projectlombok.org/features/configuration&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;projectlombok&lt;/code&gt; configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;compileOnly &amp;#39;org.projectlombok:lombok:1.18.16&amp;#39;
annotationProcessor &amp;#39;org.projectlombok:lombok:1.18.16&amp;#39;&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h1 id=&quot;allargsconstructor-requiredargsconstructor-사용금지&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;@AllArgsConstructor&lt;/strong&gt;, &lt;strong&gt;@RequiredArgsConstructor&lt;/strong&gt; 사용금지&lt;a href=&quot;#allargsconstructor-requiredargsconstructor-%EC%82%AC%EC%9A%A9%EA%B8%88%EC%A7%80&quot; aria-label=&quot;allargsconstructor requiredargsconstructor 사용금지 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 790px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4371b407df47a5a536719cf992851623/2e237/lombokReview.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 20.444444444444446%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAwklEQVR42k3PQXPCIBQE4Pz/P+dMR3voRTFp5QEBJQkSAllf0LEevll2Dww0Bwjs5h98e4EvRdgbiQM7RwNVLGRmnPQ6U/n37A5qde+tkcHgctfQwaEPAZb5lHAvCxZkpJeZ26fI65ZDmqqQI0KJaMZhhDi1OB4F2rZD1/3CWQeje2htam78zcPyfnU35jH4sXYhWpzZ5U9imgKadV1hHV/Q9yBSIKWeZ05JVJOUxry9OkbEOFeJ+7YtC/8j59pLKXgAsNsxbk8q8XoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;lombokReview&quot;
        title=&quot;&quot;
        src=&quot;/static/4371b407df47a5a536719cf992851623/2e237/lombokReview.png&quot;
        srcset=&quot;/static/4371b407df47a5a536719cf992851623/3684f/lombokReview.png 225w,
/static/4371b407df47a5a536719cf992851623/fc2a6/lombokReview.png 450w,
/static/4371b407df47a5a536719cf992851623/2e237/lombokReview.png 790w&quot;
        sizes=&quot;(max-width: 790px) 100vw, 790px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Lombok을 사용하면 IDE Generate보다 클래스위에 어노테이션을 자동완성 해왔다&lt;br&gt;
이 &lt;a href=&quot;https://kwonnam.pe.kr/wiki/java/lombok/pitfall&quot;&gt;글&lt;/a&gt;에 따르면 &lt;strong&gt;@AllArgsConstructor&lt;/strong&gt;, &lt;strong&gt;@RequiredArgsConstructor&lt;/strong&gt; 클래스 필드 순서대로 생성자를 생성해준다고 한다&lt;br&gt;
이 때, &lt;strong&gt;생성자 어노테이션이 작성된 클래스 필드들의 순서를 수정하면 이미 사용중인 영역에 영향이 간다&lt;/strong&gt;&lt;br&gt;
필드들의 타입이 달라서 컴파일 에러를 일으킨다면 다행이지만 , &lt;strong&gt;타입이 같다면 큰 문제다&lt;/strong&gt;&lt;br&gt;
직접 확인해보자&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ProductData&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@AllArgsConstructor
public class ProductData {
    private Long id;
    private String name;
    private String maker;
    private Integer price;
    private String imageUrl;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;컴파일된 클래스를 디컴파일한 &lt;strong&gt;ProductData&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class ProductData {
    private Long id;
    private String name;
    private String maker;
    private Integer price;
    private String imageUrl;

    public ProductData(Long id, String name, String maker, Integer price, String imageUrl) {
        this.id = id;
        this.name = name;
        this.maker = maker;
        this.price = price;
        this.imageUrl = imageUrl;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;필드들의 순서가 섞인 &lt;strong&gt;ProductData&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@AllArgsConstructor
public class ProductData {
    private Long id;
    private String imageUrl;
    private String maker;
    private Integer price;
    private String name;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;blockquote&gt;
&lt;p&gt;컴파일된 클래스를 디컴파일한 &lt;strong&gt;순서가 섞인 ProductData&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class ProductData {
    private Long id;
    private String imageUrl;
    private String maker;
    private Integer price;
    private String name;

    public ProductData(Long id, String imageUrl, String maker, Integer price, String name) {
        this.id = id;
        this.imageUrl = imageUrl;
        this.maker = maker;
        this.price = price;
        this.name = name;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;필드의 순서에 맞게 생성자가 재정의 되었다. 꼭 필요하다면 직접 작성하자&lt;/li&gt;
&lt;li&gt;특정 롬복 어노테이션을 막고 싶다면 &lt;code class=&quot;language-text&quot;&gt;lombok.(featureName).flagUsage&lt;/code&gt; 옵션을 사용하자&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/projectlombok/lombok/issues/2094#issuecomment-866351481&quot;&gt;어떤 분&lt;/a&gt;은 &lt;code class=&quot;language-text&quot;&gt;@AllArgsConsructor&lt;/code&gt;를 &lt;strong&gt;&quot;This annotation is simply a loaded gun..&quot;&lt;/strong&gt; 라고 말하기도 했다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id=&quot;builder-클래스에-작성하는-것과-메서드에-작성하는-것의-차이점&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;@Builder&lt;/strong&gt; 클래스에 작성하는 것과 메서드에 작성하는 것의 차이점&lt;a href=&quot;#builder-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%97%90-%EC%9E%91%EC%84%B1%ED%95%98%EB%8A%94-%EA%B2%83%EA%B3%BC-%EB%A9%94%EC%84%9C%EB%93%9C%EC%97%90-%EC%9E%91%EC%84%B1%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot; aria-label=&quot;builder 클래스에 작성하는 것과 메서드에 작성하는 것의 차이점 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 793px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a5c4c57f4d2203812b111c3ebea5dcd7/73fd0/builderReview.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWUlEQVR42n2SfY+UMBCH+f5fS3N/aWK808jpLgsLpS+Uty2grPA4sJ7ZSy42efJrZ6Yzk2mjrDUcqpzUleSV41BqEq1JjSKzJYnKOemC3GmqcMGFgWYa6OfpTaIPPPN+fuRTiFGzJbZHnuqUzzbmi//OYTzzY8g4ij4POfEgRWaNwlK8QfREwsdZLocDbq3JQkFyyXfSQXEKCr14CW2EGidYiTOr39lsG2a9EXVdh3MVvm5o+57uEljXlWVZX+lr+LfqpsVVnnGcmOcr0fxrplAKpUoacWpjsdZRSZD3XrSilmJO1FZ+t7/YrHMYa3dfXdcMw0i0Vell0F6STZL8uixsDdyzbGzdrvcKv7f9i/8ve8KgDW2h6I1BfYtps4z+nBOKQvTMVUaxjAPLEG6EjbvzbruwTNMtYdolpM0R0xc8fH0nXyVGV6lwonQJfpQn+Vn9l3pyXOaeP7c5YXpu8znfAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;builderReview&quot;
        title=&quot;&quot;
        src=&quot;/static/a5c4c57f4d2203812b111c3ebea5dcd7/73fd0/builderReview.png&quot;
        srcset=&quot;/static/a5c4c57f4d2203812b111c3ebea5dcd7/3684f/builderReview.png 225w,
/static/a5c4c57f4d2203812b111c3ebea5dcd7/fc2a6/builderReview.png 450w,
/static/a5c4c57f4d2203812b111c3ebea5dcd7/73fd0/builderReview.png 793w&quot;
        sizes=&quot;(max-width: 793px) 100vw, 793px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;아래의 링크를 참고하였습니다
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://johngrib.github.io/wiki/pattern/builder/#lombok-builder&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;johngrib&lt;/code&gt; Lombok @Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://projectlombok.org/features/Builder&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;lombok&lt;/code&gt; @Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@park2348190/Lombok-Builder%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC&quot;&gt;@Builder의 동작 원리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;@Builder&lt;/code&gt;를 직접 확인해보자&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
  ...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/RetentionPolicy.html&quot;&gt;&lt;strong&gt;RetentionPolicy&lt;/strong&gt;&lt;/a&gt;에 따르면 &lt;strong&gt;컴파일 단계에서 제거된다&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/ElementType.html&quot;&gt;&lt;strong&gt;ElementType&lt;/strong&gt;&lt;/a&gt;에 따르면 &lt;strong&gt;아래 위치에 선언할 수 있다&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;클래스&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;인터페이스&lt;/li&gt;
&lt;li&gt;enum&lt;/li&gt;
&lt;li&gt;메서드&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;생성자&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;클래스에 주석이 달린 경우 패키지 전용 생성자가 모든 필드를 인수로 사용하여 생성되며  
( @AllArgsConstructor(access = AccessLevel.PACKAGE)클래스에 있는 것처럼) 
이 생성자에 주석이 @Builder대신 추가된 것과 같습니다.&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://projectlombok.org/features/Builder&quot;&gt;출처&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;클래스에 작성&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;@Builder
public class Test {
    private int a;
    private int b;
}

...

public class Test {
    private int a;
    private int b;

    Test(int a, int b) {
        this.a = a;
        this.b = b;
    }
    // static Builder 클래스...
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Class 레벨&lt;/strong&gt;에 작성하면 기본 생성자는 &lt;code class=&quot;language-text&quot;&gt;default&lt;/code&gt;로 작성된다&lt;/li&gt;
&lt;li&gt;필드를 추가하게 되면 자동적으로 &lt;strong&gt;Builder&lt;/strong&gt;에 포함된다&lt;/li&gt;
&lt;li&gt;그리고 위에서말한 &lt;code class=&quot;language-text&quot;&gt;@AllArgsConstructor 금지&lt;/code&gt;를 지키지 못한 것과 같다&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;생성자에 작성&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class Test {
    private int a;
    private int b;
    private int c;

    @Builder
    private Test(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;위와 같이 사용하다가 &lt;code class=&quot;language-text&quot;&gt;int d&lt;/code&gt;를 추가했다고 가정해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;생성자에 작성&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;int d&lt;/code&gt;추가&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class Test {
    private int a;
    private int b;
    private int c;
    private int d;

    private Test(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public static Test.TestBuilder builder() {
        return new Test.TestBuilder();
    }

    public static class TestBuilder {
        private int a;
        private int b;
        private int c;

        // method ...
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;추가한 &lt;code class=&quot;language-text&quot;&gt;int d&lt;/code&gt;는 &lt;strong&gt;Builder&lt;/strong&gt;에 포함되지 않고 생성자도 직접 지정한 &lt;strong&gt;private&lt;/strong&gt;로 지정되어 있다&lt;/li&gt;
&lt;li&gt;그리고 아래와 같이 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;@Builder&lt;/code&gt;를 지정한 생성자의 필드에만 적용할 수도 있다&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;특정 생성자에 작성&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;public class Test {
    private int a;
    private int b;
    private int c;

    @Builder
    private Test(int a , int b){
        this.a = a;
        this.b = b;
    }

    private Test(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

...

public class Test {
    private int a;
    private int b;
    private int c;

    private Test(int a, int b) {
        this.a = a;
        this.b = b;
    }

    private Test(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public static Test.TestBuilder builder() {
        return new Test.TestBuilder();
    }

    public static class TestBuilder {
        private int a;
        private int b;

        // method ...
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;결론&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Builder&lt;/code&gt;는 클래스 레벨에 달면 필드가 추가될 때 자동으로 빌더에 포함되고 &lt;strong&gt;default&lt;/strong&gt;생성자를 생성한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;default&lt;/strong&gt;생성자는 &lt;code class=&quot;language-text&quot;&gt;@AllArgsConstructor&lt;/code&gt;를 사용한 것과 같으니 지양해야 한다&lt;/li&gt;
&lt;li&gt;생성자에 직접 달아주자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[클래스는 언제 로딩될까?]]></title><description><![CDATA[출처 Loading, Linking, and Initializing 클래스는 언제 로딩되고 초기화되는가? JVM 밑바닥까지 파헤치기 클래스 로딩 클래스의 완전한 이름을 보고 해당 클래스를 정의하는 바이너리 바이트 스트림을 JVM…]]></description><link>https://jdalma.github.io/2022y/classLoader/</link><guid isPermaLink="false">https://jdalma.github.io/2022y/classLoader/</guid><pubDate>Tue, 30 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;출처
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html&quot;&gt;Loading, Linking, and Initializing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@skyepodium/%ED%81%B4%EB%9E%98%EC%8A%A4%EB%8A%94-%EC%96%B8%EC%A0%9C-%EB%A1%9C%EB%94%A9%EB%90%98%EA%B3%A0-%EC%B4%88%EA%B8%B0%ED%99%94%EB%90%98%EB%8A%94%EA%B0%80&quot;&gt;클래스는 언제 로딩되고 초기화되는가?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.yes24.com/Product/Goods/126114513&quot;&gt;JVM 밑바닥까지 파헤치기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;클래스-로딩&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;클래스 로딩&lt;/strong&gt;&lt;a href=&quot;#%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%A1%9C%EB%94%A9&quot; aria-label=&quot;클래스 로딩 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;클래스의 완전한 이름을 보고 해당 클래스를 정의하는 바이너리 바이트 스트림을 &lt;strong&gt;JVM&lt;/strong&gt; 메모리에 올리는 작업을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JVM&lt;/strong&gt;은 실행될 때 모든 클래스를 메모리에 올리지 않고 &lt;strong&gt;필요할 때 마다 클래스를 메모리에 올린다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클래스 로딩의 메커니즘은 &lt;em&gt;로딩 → (검증 → 준비 → 해석) → 초기화 → 사용 → 언로딩&lt;/em&gt; 과정이 존재하며 이번 게시글에서는 &lt;strong&gt;로딩 단계&lt;/strong&gt;를 확인한다. (위에 작성한 과정의 순서는 시작 지점 기준이다.)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;java -verbose:class {파일 이름.java}&lt;/code&gt; 명령어를 통해 클래스 로딩에 대한 상세 정보를 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;그럼-언제-클래스를-로딩할까&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;그럼 언제 클래스를 로딩할까?&lt;/strong&gt;&lt;a href=&quot;#%EA%B7%B8%EB%9F%BC-%EC%96%B8%EC%A0%9C-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A5%BC-%EB%A1%9C%EB%94%A9%ED%95%A0%EA%B9%8C&quot; aria-label=&quot;그럼 언제 클래스를 로딩할까 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;h3&gt;첫 번째. initial class만 실행&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String[] args){ }
}

class Single{
    public Single() { }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bdf3bfdda4a9819ca023b15c241665b7/081ff/testcase0.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 16.888888888888886%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAwklEQVR42h2Oy26CUBRF+ZwG27RqUsUCcuE+uA9FBNtZTdtPME1n/vrqhcEe7J2cs1ai2x5pTjT6hGrP2HDBHd6RuqOqez6vgvt9TT/UZG+OvXDkZTunrKbu4655WRU8pK8krRtR6oiqLVJYdOPR0qMiREeACx3nocP5qQ8YG6F+ZBLZi8AuN2yyhuW6ZPGUkZR5jQgfmJ8/2u9f5PWG/bqh7EhemGgyWRzn48moqgOiOcxmu1zzvCxIH7fx2ZZ0seEfEOFtDf+khhYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;testcase0&quot;
        title=&quot;&quot;
        src=&quot;/static/bdf3bfdda4a9819ca023b15c241665b7/1cfc2/testcase0.png&quot;
        srcset=&quot;/static/bdf3bfdda4a9819ca023b15c241665b7/3684f/testcase0.png 225w,
/static/bdf3bfdda4a9819ca023b15c241665b7/fc2a6/testcase0.png 450w,
/static/bdf3bfdda4a9819ca023b15c241665b7/1cfc2/testcase0.png 900w,
/static/bdf3bfdda4a9819ca023b15c241665b7/21482/testcase0.png 1350w,
/static/bdf3bfdda4a9819ca023b15c241665b7/d61c2/testcase0.png 1800w,
/static/bdf3bfdda4a9819ca023b15c241665b7/081ff/testcase0.png 2182w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Main&lt;/code&gt;클래스만 로딩 되었고 &lt;code class=&quot;language-text&quot;&gt;Single&lt;/code&gt;클래스는 로딩되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;두 번째. 다른 클래스의 인스턴스 생성&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String[] args){
        Single single = new Single();
    }
}

class Single{
    public Single() { }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3a1b8b3bba692c32b535d0883b0287fb/081ff/testcase1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 17.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAwUlEQVR42h2OTU/DQAxE83OAClFApTTkO9v1psluUyjQgMSB3iAHLv39DzeHkeWx5o0jKz1NGDDuGaty7atqT2UCZd3zfhDGscAHR14GjFW/8nrzurdkxYbH2HBzm3Bx9UDkNnt2h2+68EaZC+u6xWlI3I61vLDtO06nlOOx1nA3gc5lZ1icWBbLivtFocCU2XVMlKUG4wfk6w/7OVIPP8jH7/RxkjmKSvBbgzSiAJm8NG+muXqyzO8yBa0mXc6W/ANI620iw9Yb8QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;testcase1&quot;
        title=&quot;&quot;
        src=&quot;/static/3a1b8b3bba692c32b535d0883b0287fb/1cfc2/testcase1.png&quot;
        srcset=&quot;/static/3a1b8b3bba692c32b535d0883b0287fb/3684f/testcase1.png 225w,
/static/3a1b8b3bba692c32b535d0883b0287fb/fc2a6/testcase1.png 450w,
/static/3a1b8b3bba692c32b535d0883b0287fb/1cfc2/testcase1.png 900w,
/static/3a1b8b3bba692c32b535d0883b0287fb/21482/testcase1.png 1350w,
/static/3a1b8b3bba692c32b535d0883b0287fb/d61c2/testcase1.png 1800w,
/static/3a1b8b3bba692c32b535d0883b0287fb/081ff/testcase1.png 2182w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Main&lt;/code&gt;이 실행되고 &lt;code class=&quot;language-text&quot;&gt;Single&lt;/code&gt;클래스가 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;세 번째. final 키워드가 없는 정적 변수만 호출&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String[] args){
        System.out.println(Single.a);
    }
}

class Single{
    public static int a;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d8080e57c19618ff4ab1ca07d13dc442/5b2ff/testcase2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 20.444444444444446%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAABEElEQVR42h3Pa0+CABTGcb5NZZelXRASvCByE0OSFI1kmtgsXcPY1Np603rdN/6HvTh7ds529uwn6IZHJ4jZZ1PvYFo+pn2HprtU6x4PUZ0su6LtNpErNrWGjVq1UFSTas3+H7nSolhSODi8RDCdAH80x+2NMd0+thfi3A4x8rvhDLgPPdJMZxi5aEYPPS/UDJ+m6aPU20iKxYWocVaqclpUEUbzd5LND5PVjnCyIkpS4kVGvNwSLXbM1mu+fxPSrxeCScogWRPmP/2nNN/f8MdL/HhF9/GV9vAZQS6rqFaAPvvAnH+iTbcY0w2NVpeypFGWW4iSm7McKjlzz1Vz5p4s3eic59TCiUjhWOSocM0fMtOaQ4I8t+UAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;testcase2&quot;
        title=&quot;&quot;
        src=&quot;/static/d8080e57c19618ff4ab1ca07d13dc442/1cfc2/testcase2.png&quot;
        srcset=&quot;/static/d8080e57c19618ff4ab1ca07d13dc442/3684f/testcase2.png 225w,
/static/d8080e57c19618ff4ab1ca07d13dc442/fc2a6/testcase2.png 450w,
/static/d8080e57c19618ff4ab1ca07d13dc442/1cfc2/testcase2.png 900w,
/static/d8080e57c19618ff4ab1ca07d13dc442/21482/testcase2.png 1350w,
/static/d8080e57c19618ff4ab1ca07d13dc442/d61c2/testcase2.png 1800w,
/static/d8080e57c19618ff4ab1ca07d13dc442/5b2ff/testcase2.png 2190w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Main&lt;/code&gt;이 실행되고 &lt;code class=&quot;language-text&quot;&gt;Single&lt;/code&gt;클래스가 실행됨&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;public static int a&lt;/code&gt;변수가 타입 기본값으로 출력됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;네 번째. final 키워드가 있는 정적 변수 호출&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String[] args){
        System.out.println(Single.b);
    }
}

class Single{
    public static int a;
    public static final int b = 1000;
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5db52770c2a7cc56719ff19cdb1853de/d5f92/testcase3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 17.77777777777778%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAABA0lEQVR42i2PXU+CAABF+TH6aDPDmICg8hE2kYmKuVI+VKxp5Udry2Xm/OEndD3cnd2z+3KFdj/CtD0Ms4XT9Gne+9w5HUzLpVb3mExUTscrkljhVrLQdQdNc5AVC7VqU9VstMzJsolY1hG8QUL7P/5jivcw5ezc7hgviPGHY4LR04VekO36MW4vzBjS6obY7pBGs49m+VR0FyFafhHO10TPW0azVcYN0eKTeLUnfN0Rve1J1scsB+L3b5LMpx9HZtvfSx8vdwxmW4Lpht5kjVCxOjReThjzA/X0Bz09YAwXqLJB8VpBvKlSLmtIUi2jjihql2tnnnuppFIoSOTzRXK5In9dOpzF0eDCNwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;testcase3&quot;
        title=&quot;&quot;
        src=&quot;/static/5db52770c2a7cc56719ff19cdb1853de/1cfc2/testcase3.png&quot;
        srcset=&quot;/static/5db52770c2a7cc56719ff19cdb1853de/3684f/testcase3.png 225w,
/static/5db52770c2a7cc56719ff19cdb1853de/fc2a6/testcase3.png 450w,
/static/5db52770c2a7cc56719ff19cdb1853de/1cfc2/testcase3.png 900w,
/static/5db52770c2a7cc56719ff19cdb1853de/21482/testcase3.png 1350w,
/static/5db52770c2a7cc56719ff19cdb1853de/d61c2/testcase3.png 1800w,
/static/5db52770c2a7cc56719ff19cdb1853de/d5f92/testcase3.png 2150w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Main&lt;/code&gt;클래스가 로딩되고 &lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;Single&lt;/code&gt;클래스가 로딩되지 않았지만 &lt;code class=&quot;language-text&quot;&gt;public static final int b&lt;/code&gt;가 출력됨&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;다섯 번째. 정적 메서드 호출&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String[] args){
        Single.test();
    }
}

class Single{
    public static void test(){
        System.out.println(&amp;quot;static method 호출&amp;quot;);
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f94a50ca0017a0e3aebcb53177f0c86e/88745/testcase4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 21.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAABCUlEQVR42h2Py07CUBRF+z0GwURBhba0tKW3D0opCMUEKQgSDbUhIEKicerAof+7vDJY2WcPTrKX0nZ7hMmElhPhuDGe30dInHaE2YoZpTaHw5VMg7rqY5g+mi7Qmx6axDADVM2lWjM4K12juPI57E8RUYobDvG6Y7z/OxjgiAHdXsLLWjDJglN3RB9bJFhyiGFF1DVB9damctmkVFFRRvOCrPhkPM+5l0yeNmTPO7L8yDT/YFbs+f5d8PWzYrjYMlpuSVe7U94tNiTZK8msoDfN6T6sUXTNwgyGuI/v+Msj7dle8obbSaWSQNV9qRrLjDFaoVzVwbSjE02pW7uxKF+onFcalMp1/gAY9JrIUg8CmwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;testcase4&quot;
        title=&quot;&quot;
        src=&quot;/static/f94a50ca0017a0e3aebcb53177f0c86e/1cfc2/testcase4.png&quot;
        srcset=&quot;/static/f94a50ca0017a0e3aebcb53177f0c86e/3684f/testcase4.png 225w,
/static/f94a50ca0017a0e3aebcb53177f0c86e/fc2a6/testcase4.png 450w,
/static/f94a50ca0017a0e3aebcb53177f0c86e/1cfc2/testcase4.png 900w,
/static/f94a50ca0017a0e3aebcb53177f0c86e/21482/testcase4.png 1350w,
/static/f94a50ca0017a0e3aebcb53177f0c86e/d61c2/testcase4.png 1800w,
/static/f94a50ca0017a0e3aebcb53177f0c86e/88745/testcase4.png 2148w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main과 Single 클래스 모두 로딩됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;여섯 번째. 클래스의 내부 정적 클래스의 정적 변수 호출&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String[] args){
        System.out.println(Single.INNER_INSTANCE.INSTANCE);
    }
}

class Single{
    public static class INNER_INSTANCE{
        public static Single INSTANCE;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bfdb12020b0af7e13cbca5f3cd517ece/39f45/testcase6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 23.111111111111114%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABOklEQVR42h2Q2UrDUBRF8z1OOIElNm3SZk5N0qRpnLGkE1TFqijO4oOCDyr46McuT/KwWJd7z91sjuL6PUw7xvFSvKAnTrCcmLYV4voptpvKe0o5F0Y5wU5GJ+zjdzI59wnE5f+aarGwVEMJkkO56GIYPpYdVqGuhJaBRjuiGNZ5fVknSXQ2Nn3UeoDW9NnWHLFHQ/dpCjXVZG29gdKRwCgv6O4NxQNsaeIIlptU3oliumkorWIOjnwOjx3cIEZvhzRbHbak2eZWi+XVOosrKsr04ZOztx/hm8n9ByezWwbndwwunijmz+IX4Y3h5SPvv2O+/qZyviEf3bA7uSYrLukPhdEVvWKO0rIjWtkEd3+GtX+KeXCOm4+rNWh6ULVolzNWjKp1hQTDjDEdWZMZst3wWN1osrKmVfwDT43CUDp8lXkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;testcase6&quot;
        title=&quot;&quot;
        src=&quot;/static/bfdb12020b0af7e13cbca5f3cd517ece/1cfc2/testcase6.png&quot;
        srcset=&quot;/static/bfdb12020b0af7e13cbca5f3cd517ece/3684f/testcase6.png 225w,
/static/bfdb12020b0af7e13cbca5f3cd517ece/fc2a6/testcase6.png 450w,
/static/bfdb12020b0af7e13cbca5f3cd517ece/1cfc2/testcase6.png 900w,
/static/bfdb12020b0af7e13cbca5f3cd517ece/21482/testcase6.png 1350w,
/static/bfdb12020b0af7e13cbca5f3cd517ece/d61c2/testcase6.png 1800w,
/static/bfdb12020b0af7e13cbca5f3cd517ece/39f45/testcase6.png 2184w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main 클래스와 Single$INNER_INSTANCE는 로딩되었지만, Single 클래스는 로딩되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;정리&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;정리&lt;/strong&gt;&lt;a href=&quot;#%EC%A0%95%EB%A6%AC&quot; aria-label=&quot;정리 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;위의 테스트를 통해 두 가지 의문점이 생긴다.&lt;/p&gt;
&lt;h3&gt;첫 번째. static final 클래스 변수는 클래스 로딩이 필요없는가?&lt;/h3&gt;
&lt;p&gt;세 번째 테스트인 final 키워드가 없는 정적 변수를 호출하면 Single 클래스가 로딩되는 것을 확인할 수 있었다.&lt;br&gt;
그 이유는 &lt;code class=&quot;language-text&quot;&gt;static final&lt;/code&gt; 클래스 변수는 컴파일 타임에 바이트 코드의 상수 풀에 채워지므로 클래스 로딩이 필요없는 것이다.&lt;/p&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;  public static int a;
    descriptor: I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC

  public static final int b;
    descriptor: I
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1000&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;위의 바이트 코드를 확인하면 &lt;code class=&quot;language-text&quot;&gt;ConstantValue&lt;/code&gt; 속성에 실제 선언한 값이 인라인화 되어있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;추가로 내부 정적 클래스의 클래스 변수에 접근하는 것도 Single 클래스가 로딩되지 않는 것을 알 수 있다. 실제로 &lt;code class=&quot;language-text&quot;&gt;Single$INNER_INSTANCE&lt;/code&gt;는 다른 클래스 파일로 컴파일되며, &lt;code class=&quot;language-text&quot;&gt;Single&lt;/code&gt; 클래스에 종속되지 않기 때문이다.&lt;/p&gt;
&lt;h3&gt;두 번째. 클래스는 한 번만 로딩됨을 보장한다.&lt;/h3&gt;
&lt;p&gt;자바 컴파일러는 변수 초기화와 정적 문장 블록, 부모 클래스의 인스턴스 생성자를 취합하여 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;clinit&gt;()&lt;/code&gt; 이라는 클래스 생성자를 자동으로 생성하며 작성된 순서의 영향을 받는다.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;&amp;lt;clinit&gt;()&lt;/code&gt;은 자바 언어에서 말하는 생성자와 다르며, 가상 머신 관점에서 인스턴스를 만드는 인스턴스 생성자는 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;init&gt;()&lt;/code&gt;이다.&lt;br&gt;
(지금부터 clinit은 클래스 생성자, init은 인스턴스 생성자라고 부르겠다.)&lt;/p&gt;
&lt;p&gt;자바 가상 머신은 클래스 생성자가 멀티스레드 환경에서 적절히 동기화되도록 해야 한다.&lt;br&gt;
여러 스레드가 한 클래스를 동시에 초기화하려 시도하면 그중 한 스레드만 클래스 생성자를 실행하고 다른 스레드는 모두 대기시켜야 한다.&lt;br&gt;
&lt;strong&gt;그렇기에 클래스 생성자에 오래 걸리는 작업이 포함되어 있다면 여러 스레드가 장시간 블록될 수 있기에 조심해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String args[]) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i &amp;lt; 10; i++) {
            service.submit(() -&amp;gt; {
                new Single();
            });
        }
        service.shutdown();
    }
}
class Single {
    static {
        System.out.println(Thread.currentThread() + &amp;quot;: static 블록 호출 &amp;quot; + System.currentTimeMillis());
    }

    public Single() {
        System.out.println(Thread.currentThread() + &amp;quot;: 생성자 호출 &amp;quot; + System.currentTimeMillis());
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Thread[#25,pool-1-thread-1,5,main]: static 블록 호출 1727681233985
Thread[#34,pool-1-thread-10,5,main]: 생성자 호출 1727681233989
Thread[#27,pool-1-thread-3,5,main]: 생성자 호출 1727681233989
Thread[#30,pool-1-thread-6,5,main]: 생성자 호출 1727681233989
Thread[#32,pool-1-thread-8,5,main]: 생성자 호출 1727681233989
Thread[#28,pool-1-thread-4,5,main]: 생성자 호출 1727681233989
Thread[#31,pool-1-thread-7,5,main]: 생성자 호출 1727681233989
Thread[#29,pool-1-thread-5,5,main]: 생성자 호출 1727681233989
Thread[#33,pool-1-thread-9,5,main]: 생성자 호출 1727681233989
Thread[#26,pool-1-thread-2,5,main]: 생성자 호출 1727681233989
Thread[#25,pool-1-thread-1,5,main]: 생성자 호출 1727681233989&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;인스턴스 생성자는 동일한 시간대에 여러 번 호출된 것을 볼 수 있지만 클래스 생성자는 단 한 번만 호출된 것을 확인할 수 있다.
실제로 한 개의 스레드만 클래스 생성자 영역에 들어가게 되며 클래스 로더는 로딩부터 초기화까지 원자적인 연산이 보장된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 411px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b11ad9e489eefaff260595e9a05d2c89/2a432/clinit.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 121.33333333333334%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAAsTAAALEwEAmpwYAAAD+0lEQVR42mWVe3PaRhTF+UBtzMMQwDyEXkjaXe3qCQjwxE36ykwybu02mcy0M/3kp/cuoJjxH3fW4NVv7znnruhM5gpBVEBkDUK1hzRbRKs5Yt9FltcQKsNNb4je4C3V6LxeV7c/aqszma3gx0fobEeAEirNUGiNLPKxbe5R1HssViF+6A5xQw/f9EZ4Q3+/oUN4PR02bqvT63UxnTpYzJYELJDqDEJIxGEAI2LI0IW3nMGfT+DOxgiDEFEi4NOaJBJhGKPXH9jq9wbo9L0PGIa/YOh/wNI8Imm+Iv/pX3jVM9LmGaZ5gtr+ger+LxSHJ6THryjp//r+G7Y//4fy4R/cid/x4+Ideu57AjoNLtVbbKHr39AcP6FuPiKvf8Xm+Bnm8CfBCfrwBbp5xObhb6jdZ5T3T8gPj8iOz3D1R9zF71lyH1xdqsFgAOnNodYuFEmtdYJdYSCVtpUXpbUkLyqYrEBZbazvFa37wxFRnBDwhaFcSeAhkQZrkcGUO5TbI7Jyi3p3RL09UHg1cvpcbfYoqp2t1JTntWLg9/j7t2+pMx/G5PTQDrFI7ebLA0oXMDRKXAyVaU4HVHaNBU1GsbkGckXeCqlUUMrQDJoWyJsNlYXQ5wvInGf18vcroAxclCJEkYRIFZ/K8hqUdWO75IdY+oYs4O/4UL4AbNNVh93+aY196pAkF/UBYazOoMZuZiB3wv7x0HPnfAB/x8XQFjgg/wbDMQE9pHRbeGOUnDxUOidgbaXyg2EkW1hMe/jKXTgdvp/D8RzSmUOnxkrkjdydShKywIOOAroxHkwcYJPG1g5J4RVkjZESOSngZ/gQC7wloFouYHRux4IN94LEAlOaR0P3Ol0TkGpTFKjJ07wkC6jq7R7j6YImZIzb0fRFhwTUZLCmk9hkLyQYAQV5yslzUCogf/VppJQprKfs52A4sS+Nk2TSP5osIJYsObNDXNLGgHxiOZz62l2hkiQvIvkisUFV9Smo5vAOjhvatw53aYHDydwCFY3JxYtgTcYLgch1LJBlS+pWqxTlpmnTZehoPGuDaT2UZw95eK2HJFkTMCZguHKQUSDSd5Bnue0ws0Oe2VF5OcethxwKS2YPGeiH4gT0zkBKmj002ligOQ8zd8lSX4/NGZgSjOeNU+ZQEgY6jpWsKfGMRqukIHRWtlfuusOrUIyVXNY7+GsORVBXHIqDWq6pS8/uKelngX1mINcr4PAMTNXpPpq8silfJPPYsIcsOTMnDzXtYckMvpLM6dyO78jD7x2yHE7ZKGlD4Q4ZyJIr+t25eMjd8SvtCsjvwNHUR+y4KOhNzDfFkI+LVYAoCOzY+M7SXj3uUNAPE0vmoea7HqzFFfB/5a522QdwubUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;clinit&quot;
        title=&quot;&quot;
        src=&quot;/static/b11ad9e489eefaff260595e9a05d2c89/2a432/clinit.png&quot;
        srcset=&quot;/static/b11ad9e489eefaff260595e9a05d2c89/3684f/clinit.png 225w,
/static/b11ad9e489eefaff260595e9a05d2c89/2a432/clinit.png 411w&quot;
        sizes=&quot;(max-width: 411px) 100vw, 411px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/openjdk/jdk/blob/988a531b097ccbd699d233059d73f41cae24dc5b/src/java.base/share/classes/java/lang/ClassLoader.java#L572&quot;&gt;ClassLoader의 loadClass 메소드&lt;/a&gt;를 참고하는 것도 좋을 것이다.&lt;/p&gt;
&lt;h2 id=&quot;싱글-톤-인스턴스-만들기&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;싱글 톤 인스턴스 만들기&lt;/strong&gt;&lt;a href=&quot;#%EC%8B%B1%EA%B8%80-%ED%86%A4-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0&quot; aria-label=&quot;싱글 톤 인스턴스 만들기 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color:red; font-weight:bold&quot;&gt;실패 사례&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String args[]) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i &amp;lt; 10; i++) {
            service.submit(() -&amp;gt; {
                SingleTon.getInstance();
            });
        }
        service.shutdown();
    }
}

class SingleTon {
    private SingleTon() {
        System.out.println(&amp;quot;싱글톤 인스턴스 생성&amp;quot;);
    }

    public static SingleTon instance;

    public static SingleTon getInstance() {
        if(instance == null){
            System.out.println(&amp;quot;인스턴스 생성&amp;quot;);
            return new SingleTon();
        }
        return instance;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/519be24ccfb27aade292a7dc6a936141/71ba4/singleTonTest.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.55555555555556%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABi0lEQVR42q2SW0sCcRDF9xsFvXVXouymFJGEJhQ97GYXu0gRmIplWZZWqwnZTS3LCkpLtHZNheylhyyrr3P672omFBHhw48zMwzDcGYohXIAGp0Bw1YXNCOz6BkYg5qegprRi6joSSgH9aD1DDyBTtg9SnT3M5CrGHT2DUFBVECuotGl0YKacO7DfBSF7SINZ/wRW6k3wmue5Gsx9qRz2HnIwpt5hjuZg+vupYRcgRdQ4449GH0RWIJx2MP3YPksNrmnH9m4zYqwJBb6WP4pD/cFNbbqheEgDMvxDVavHuBKPBea/0dxw/mT2/IM1Dl2MVfODXVrO2TgJfEwRjzM/OrhX6DGyZVNgWtYz5NwxMiV0+9wE0Ql13UnS8nlNfW9lv+EN1DC/02zAcys78K8HcLS4TUWfJew+iNYJL7aTnkshThRV84TotpOuYLyWD5LiPlC8AZmfxRUdY0E0iY5ZIwRbVoTOkYtaKaNaKUNaJH3oq6hDXWSdtRLOyBpVBQR8s+aoFW1MlRUNuADLHE4MbgVDjcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;singleTonTest&quot;
        title=&quot;&quot;
        src=&quot;/static/519be24ccfb27aade292a7dc6a936141/1cfc2/singleTonTest.png&quot;
        srcset=&quot;/static/519be24ccfb27aade292a7dc6a936141/3684f/singleTonTest.png 225w,
/static/519be24ccfb27aade292a7dc6a936141/fc2a6/singleTonTest.png 450w,
/static/519be24ccfb27aade292a7dc6a936141/1cfc2/singleTonTest.png 900w,
/static/519be24ccfb27aade292a7dc6a936141/21482/singleTonTest.png 1350w,
/static/519be24ccfb27aade292a7dc6a936141/d61c2/singleTonTest.png 1800w,
/static/519be24ccfb27aade292a7dc6a936141/71ba4/singleTonTest.png 1870w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;color:green; font-weight:bold&quot;&gt;LazyHolder를 이용한 사례&lt;/span&gt; (가장 권장되는 방법중 하나이다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Main {
    public static void main(String args[]) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i &amp;lt; 10; i++) {
            service.submit(() -&amp;gt; {
                SingleTon.getInstance();
            });
        }
        service.shutdown();
    }
}

class SingleTon {
    private SingleTon() {
        System.out.println(&amp;quot;싱글톤 인스턴스 생성&amp;quot;);
    }
    public static SingleTon getInstance() {
        return LazyHolder.INSTANCE;
    }
    // 클래스는 한 번만 로딩됨을 보장한다.
    private static class LazyHolder {
        private static final SingleTon INSTANCE = new SingleTon();
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 900px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a3bbc0aa34767cff59e4f60964ef8c92/093ec/lazyHolderTest.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABOElEQVR42jWQ2U7CYBBG+zB665LIvrU/paVAF0qBQkNZRIjEBbcYYyHiG3jtG/iSx98mXpycbyaTSWYUs+nTHS3pxRsMy8eQtdUOsN0Q2wtxuiGq8JgtBB+HHFGskym0Zc9G6I50h5rWTl0oGyhCtOWiHmZngGG4KbVak2rVRFUtVM2S2aLT0YkijSCoY5oCIRqUyyaVipGSzwvOzkoofnxNuNzSX9wxuXkjvkuYbXeSPbP7hKms59uE+HbPcJ3w+bXm+yfm8fCAP39huHqSPBOuX4g2ryi2P+EPq9XD8UbyzBEte4DrRzjBFLc/S+0N5wTjJX64oDtcpLk3ukztyBnLHVPRPZRctkRZa6GvdhirBP0qQb18pz55QJO/yWRVcvKcQlGnWGqkzhfqKf/5IlPj5LTI0fE5v3Rfwwxfxio0AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;lazyHolderTest&quot;
        title=&quot;&quot;
        src=&quot;/static/a3bbc0aa34767cff59e4f60964ef8c92/1cfc2/lazyHolderTest.png&quot;
        srcset=&quot;/static/a3bbc0aa34767cff59e4f60964ef8c92/3684f/lazyHolderTest.png 225w,
/static/a3bbc0aa34767cff59e4f60964ef8c92/fc2a6/lazyHolderTest.png 450w,
/static/a3bbc0aa34767cff59e4f60964ef8c92/1cfc2/lazyHolderTest.png 900w,
/static/a3bbc0aa34767cff59e4f60964ef8c92/21482/lazyHolderTest.png 1350w,
/static/a3bbc0aa34767cff59e4f60964ef8c92/d61c2/lazyHolderTest.png 1800w,
/static/a3bbc0aa34767cff59e4f60964ef8c92/093ec/lazyHolderTest.png 1930w&quot;
        sizes=&quot;(max-width: 900px) 100vw, 900px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;초기화는-언제-발생되나&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;초기화는 언제 발생되나?&lt;/strong&gt;&lt;a href=&quot;#%EC%B4%88%EA%B8%B0%ED%99%94%EB%8A%94-%EC%96%B8%EC%A0%9C-%EB%B0%9C%EC%83%9D%EB%90%98%EB%82%98&quot; aria-label=&quot;초기화는 언제 발생되나 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;자바 가상 머신 명세에서는 로딩이 정확히 어떤 상황에서 시작해야 하는지 명시하지 않았다. 하지만 &lt;strong&gt;초기화 단계는 아래의 상황에 대해서는 즉시 시작하도록 규정해 놓았다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;간단한 사례를 확인하면 아래와 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클래스 또는 인터페이스 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; 는 다음 중 하나가 처음 발생하기 직전에 초기화된다
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; 는 클래스이고 &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; 의 인스턴스가 생성 될 때&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; 의 &lt;code class=&quot;language-text&quot;&gt;static&lt;/code&gt; 메소드가 호출 될 때&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; 의 &lt;code class=&quot;language-text&quot;&gt;static&lt;/code&gt; 변수가 할당 될 때&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; 의 &lt;code class=&quot;language-text&quot;&gt;final&lt;/code&gt;이 아닌 &lt;code class=&quot;language-text&quot;&gt;static&lt;/code&gt; 변수가 사용될 떄 (final로 지정된 정적 필드는 컴파일 타임에 상수 풀에 채워지므로 예외)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이전에 초기화되지 않은 경우 클래스가 초기화되면 해당 슈퍼 클래스가 초기화된다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;static&lt;/code&gt; 필드에 대한 참조은 하위 클래스, 하위 인터페이스 또는 인터페이스를 구현하는 클래스의 이름을 통해 참조될 수 있지만 실제로 필드를 선언하는 클래스 또는 인터페이스만 초기화한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;java.lang.reflect&lt;/code&gt; 패키지 등 표준 클래스 라이브러리에서 제공하는 리플렉션을 사용할 때 해당 클래스가 초기화되어 있지 않다면 초기화를 촉발한다.&lt;/li&gt;
&lt;li&gt;인터페이스의 초기화는 그 자체로 그 슈퍼 인터페이스의 초기화를 일으키지 않으며, 인터페이스에 디폴트 메서드를 정의했다면, 해당 인터페이스를 직간접적으로 구현한 클래스가 초기화될 때 인터페이스부터 초기화한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;java.lang.invoke.MethodHandle&lt;/code&gt; 인스턴스를 호출할 때 해당하는 클래스가 초기화되어 있지 않았다면 초기화를 촉발&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;부모클래스는 자식클래스보다 먼저 초기화된다.&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Super {
    static { System.out.println(&amp;quot;Super Init&amp;quot;); }
}
class One {
    static { System.out.println(&amp;quot;One Init&amp;quot;); }
}
class Two extends Super {
    static { System.out.println(&amp;quot;Two Init&amp;quot;); }
}
class Main {
    public static void main(String[] args) {
        One o = null;
        Two t = new Two();
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;Super Init
Two Init&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h3&gt;부모 인터페이스는 자식 인터페이스가 호출하여도 초기화 하지 않는다.&lt;/h3&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;interface A {
    int A = 1;
    int AA = Main.out(&amp;quot;AA&amp;quot;, 2);
}
interface B extends A {
    int B = Main.out(&amp;quot;B&amp;quot;, 3);
    int BB = Main.out(&amp;quot;BB&amp;quot;, 4);
}
interface C extends B {
    int C = Main.out(&amp;quot;C&amp;quot;, 5);
    int CC = Main.out(&amp;quot;CC&amp;quot;, 6);
}
class Main {
    public static void main(String[] args) {
        System.out.println(&amp;quot;B.A : &amp;quot; + B.A);
        System.out.println(&amp;quot;C.B : &amp;quot; + C.B);
        System.out.println(&amp;quot;C.C : &amp;quot; + C.C);
    }
    static int out(String s, int i) {
        System.out.println(s + &amp;quot; = &amp;quot; + i);
        return i;
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;B.A : 1
B = 3
BB = 4
C.B : 3
C = 5
CC = 6
C.C : 5&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;h2 id=&quot;초기화-진행-순서&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;초기화 진행 순서&lt;/strong&gt;&lt;a href=&quot;#%EC%B4%88%EA%B8%B0%ED%99%94-%EC%A7%84%ED%96%89-%EC%88%9C%EC%84%9C&quot; aria-label=&quot;초기화 진행 순서 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;deckgo-highlight-code language=&quot;java&quot; terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;class Main {
    public static void main(String[] args) {
        new Single();
    }
}

class Single {
    static int A = 0;
    static int B = 0;
    public Single() {
        System.out.println(&amp;quot;3. 생성자&amp;quot;);
    }
    static {
        System.out.println(&amp;quot;A : &amp;quot; + A);
        System.out.println(&amp;quot;B : &amp;quot; + B);
        A = 10;
        B = 20;
        System.out.println(&amp;quot;1. 정적 블록&amp;quot;);
    }
    public static Temp temp = new Temp();

    public static void StaticClass(){
        System.out.println(&amp;quot;X. 정적 메서드&amp;quot;);
    }
    public static class InnerClass{
        static {
            System.out.println(&amp;quot;X. 내부 클래스&amp;quot;);
        }
    }
}

class Temp {
    public Temp () {
        System.out.println(&amp;quot;2. 정적 변수&amp;quot;);
    }
}&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;
&lt;deckgo-highlight-code  terminal=&quot;carbon&quot; theme=&quot;one-light&quot; line-numbers=&quot;true&quot;  &gt;
          &lt;code slot=&quot;code&quot;&gt;A : 0
B : 0
1. 정적 블록
2. 정적 변수
3. 생성자&lt;/code&gt;
        &lt;/deckgo-highlight-code&gt;</content:encoded></item><item><title><![CDATA[퇴사]]></title><description><![CDATA[동안 다니던 SI 회사를 퇴사하였다
수주 → 개발 → 납품의 과정을 거치는수익 구조로 인해 화면을 몇 개 완성했는지,…]]></description><link>https://jdalma.github.io/2022y/leave/</link><guid isPermaLink="false">https://jdalma.github.io/2022y/leave/</guid><pubDate>Sun, 31 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;2년 4개월&lt;/code&gt;동안 다니던 SI 회사를 퇴사하였다&lt;br&gt;
&lt;strong&gt;수주 → 개발 → 납품&lt;/strong&gt;의 과정을 거치는수익 구조로 인해&lt;br&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;화면을 몇 개 완성했는지, 1년에 몇 개의 프로젝트를 완료했는지 등 속도가 주 목표&lt;/li&gt;
&lt;li&gt;주 업무는 정부과제, 공공기관 대상으로 개발만하고 빠지는 구조로 운영 업무를 통한 피드백을 경험할 수 없는 환경&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;생각해보면 아쉬운 점도 많다&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사내 인트라넷이 형상관리가 안되고 있었는데 Git으로 도전해볼껄&lt;/li&gt;
&lt;li&gt;마지막 프로젝트에서 REST API를 적용해보면서 API문서화를 적용해볼껄&lt;/li&gt;
&lt;li&gt;사내 동료들과 책 스터디를 할껄&lt;/li&gt;
&lt;li&gt;TDD를 일찍 배워서 실무에 스스로 적용해볼껄&lt;/li&gt;
&lt;li&gt;코드리뷰 문화를 사내 동료들끼리 해볼껄&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이직에 성공하게 된다면 이런 아쉬운 점이 없도록 노력해야겠다.&lt;br&gt;
내가 개선할 수 있는 것들이 보인다면 적극적으로 나서자&lt;br&gt;&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고 스스로 노력했던 것은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;격주로 진행되는 사내 세미나에 교육자로 적극적으로 나선 것&lt;/li&gt;
&lt;li&gt;사내 세미나를 계기로 17주간 사내 스터디를 운영했던 것&lt;/li&gt;
&lt;li&gt;REST API를 도입하려 노력했던 것
&lt;ul&gt;
&lt;li&gt;API 문서화를 하지 못 해서 아쉽다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고객사가 원하는 DB의 종류가 바뀌면 매번 쿼리 컨버팅을 해야했었는데 JPA를 건의했던 것
&lt;ul&gt;
&lt;li&gt;건의만 하고 결과물을 보여드리지 않아 적용을 못 하지 않았나 싶다&lt;/li&gt;
&lt;li&gt;간단한 결과물을 만들어 보여드렸다면 적용할 수 있지 않았을까? 하는 생각에 제일 아쉽다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;서비스 회사에 가고 싶은 이유는 무엇일까?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;처음 맡았던 프로젝트가 근태관리였다. 휴가나 출장, 근무시간에 대해 개발하였다.&lt;br&gt;
개발을 다 끝내고 사람들이 휴가를 잘 쓰는지, 에러는 안나는지 궁금해서 다른 프로젝트 개발을 진행하면서 시간날 때 마다 로그를 확인했다.&lt;br&gt;
내가 개발한 부분을 사람들이 사용하는 것을 보면 뿌듯하고 보람을 느꼈다.&lt;br&gt;
옛날에 배달의 민족 영상을 본적이 있다 &lt;br&gt;
선착순 이벤트를 진행하면 서버가 터져나가고, 서비스를 마이크로서비스로 전환하면서 겪었던 문제들을 해결하면서 팀원들과 환호하고 박수쳤다라는 내용이 있었다 &lt;br&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;나도 저런 문제들을 팀원들과 해결해보고 싶다.&quot;&lt;br&gt;
&quot;좋은 아키텍처, 좋은 설계, 좋은 코드를 작성하기 위해 팀원들과 고민하고 얘기 나누고 싶다.&quot;&lt;br&gt;
&quot;개발 -&gt; 운영 -&gt; 피드백 사이클을 경험하고 싶다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;좋은 품질을 만들기 위해 노력하는 회사&lt;/li&gt;
&lt;li&gt;기술적 부채를 해결하려 노력하는 회사&lt;/li&gt;
&lt;li&gt;개발에 욕심이 있고 인사이트를 공유할 수 있는 동료들이 있는 회사&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;사용자들이 작던 말던 품질에 대해 고민하고 좋은 코드를 작성하기 위해 노력하는 서비스 회사를 가자&quot;&lt;/code&gt;라는 결론에 다다랐다.&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;strong&gt;이제 경험하고 싶거나 배우고 싶은 것은?&lt;/strong&gt;&lt;br&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;TDD, 테스트 코드에 대해 배우고 싶다&lt;/li&gt;
&lt;li&gt;분산환경, MSA, 메시지 큐 시스템을 경험해보고 싶다
&lt;ul&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&gt;
&lt;li&gt;데이터를 캐싱하는 기준? 적중률? 데이터 갱신은 어떻게,언제 할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분산 DB
&lt;ul&gt;
&lt;li&gt;읽고 쓰는 DB를 분리하던데 궁금하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OOP
&lt;ul&gt;
&lt;li&gt;객체간의 책임이나 관심사를 분리하는 방법에 대해&lt;/li&gt;
&lt;li&gt;확장에 유연한 객체를 설계하는 방법에 대해&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;코드숨&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;코드숨&lt;/strong&gt;&lt;a href=&quot;#%EC%BD%94%EB%93%9C%EC%88%A8&quot; aria-label=&quot;코드숨 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;코드 리뷰&lt;/strong&gt; , &lt;strong&gt;TDD&lt;/strong&gt;를 경험해보고 싶어 &lt;br&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;NEXTSTEP&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;코드숨&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2개를 고민하였다.&lt;br&gt;
교육 과정에 내 모든 시간을 투자하고 싶어 퇴사 후 일정을 고려하였을 때 &lt;strong&gt;코드숨&lt;/strong&gt;이 적당했다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;금액은 코드숨이 더 비쌋지만 , &lt;a href=&quot;https://github.com/johngrib&quot;&gt;이종립&lt;/a&gt;님이 리뷰어로 계시기도 하고 &lt;a href=&quot;https://www.youtube.com/watch?v=oFhN3EqrCwc&amp;#x26;ab_channel=%EC%BD%94%EB%94%A9%EC%9D%98%EC%8B%A0%EC%95%84%EC%83%AC&quot;&gt;아샬님의 OT 영상&lt;/a&gt;을 보고 적잖게 감명받아 신청하게 되었다
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;JVM을 공부하며 이종립님의 글들을 읽은적이 있는데 그 후로 공부할 것이 있으면 먼저 종립님 블로그를 확인한다&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;feedly에도 이종립님의 블로그를 등록해놓아서 신규 글이 작성되면 바로 보기도 한다&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;아샬님의-ot-영상&quot; style=&quot;position:relative;&quot;&gt;&lt;strong&gt;아샬님의 OT 영상&lt;/strong&gt;&lt;a href=&quot;#%EC%95%84%EC%83%AC%EB%8B%98%EC%9D%98-ot-%EC%98%81%EC%83%81&quot; aria-label=&quot;아샬님의 ot 영상 permalink&quot; class=&quot;custom-class after&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; height=&quot;20&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;인출 중심&lt;/code&gt;&lt;/strong&gt;(회상) 의 교육
&lt;ul&gt;
&lt;li&gt;배울려고 공부하는 것이 아니라 &lt;strong&gt;쓸려고 공부하는 것&lt;/strong&gt;이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;목표 → 강의 → 과제&lt;/strong&gt;순으로 흘러간다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;과제&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;코드 → 리뷰 → 풀이&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;영상 , 자료 또는 강의를 일단 한 번 본다&lt;/li&gt;
&lt;li&gt;무슨 일이 일어났는지 &lt;strong&gt;회상&lt;/strong&gt;을 해야한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;왜 이렇게 되지? 라는 의문을 가진다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;어떻게를 그냥 배울 수 없고 &lt;strong&gt;What&lt;/strong&gt;과 &lt;strong&gt;Why&lt;/strong&gt;를 거쳐 &lt;strong&gt;How&lt;/strong&gt;를 배우게 된다~&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feedback&lt;/strong&gt; , &lt;strong&gt;FeedForward&lt;/strong&gt;를 얻을 수 있다
&lt;ul&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;strong&gt;메타인지&lt;/strong&gt; : 내가 뭐를 알고 뭐를 모르는지
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;무의식적 무지&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;의식적 무지&lt;/code&gt; ↔︎ &lt;code class=&quot;language-text&quot;&gt;의식적 지식&lt;/code&gt; (반복)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;무의식적 지식&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color:red; font-weight:bold&quot;&gt;단순 반복 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Don&apos;t Study Hard&lt;/del&gt; &lt;strong&gt;Study SMART&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;&lt;strong&gt;2022년 7월 31일&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;코드숨 OT&lt;/code&gt; 관련 윤석님의 말씀 중 기록 해놓고 싶은 내용이 있다 &lt;br&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;완벽을 추구하지 말 것&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실수를 두려워하지 말기&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고통 주도 개발&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;p&gt;&lt;strong&gt;어떻게 해야 8주 동안 슬기롭게 과정을 소화해낼 수 있을까?&lt;/strong&gt; &lt;br&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;(짜증날 정도로) &lt;strong&gt;질문 많이 하기&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;무지를 드러내기&lt;/strong&gt; 📌
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;모르는 것을 모른다고 하지 않고, 모르는 것을 그냥 모르고 지나치는 것이 더 부끄러운 것 같다.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;완벽을 추구하지 않기&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;피드포워드를 자주 받기&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;자신이 설정한 행동 목표를 실천하기 위해 함께 일하는 사람들로 부터 아이디어를 받는 것&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다른 사람에게 지식을 전파하고 잘 받기&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;디스코드에 나눈 이야기들이나 , 이전 수강 기수들의 PR훔쳐보기&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=oFhN3EqrCwc&amp;#x26;ab_channel=%EC%BD%94%EB%94%A9%EC%9D%98%EC%8B%A0%EC%95%84%EC%83%AC&quot;&gt;아샬님의 OT 영상&lt;/a&gt;에서 시키는 대로 잘 하기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성장형 사고관 가지기&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/ko-kr/learn/modules/develop-growth-mindset/&quot;&gt;성장형 사고관이란?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;더 나은 방법이 존재한다는 믿음을 가질 것&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item></channel></rss>