<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개린이의 일기장</title>
    <link>https://hyune-c.tistory.com/</link>
    <description>정답이 아닌 내 경험과 생각을 기록합니다.</description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 07:38:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Hyunec</managingEditor>
    <image>
      <title>개린이의 일기장</title>
      <url>https://tistory1.daumcdn.net/tistory/4150471/attach/f6d94b3e24b84e40930e7956aff31608</url>
      <link>https://hyune-c.tistory.com</link>
    </image>
    <item>
      <title>git branch 전략 + commit 기록 방법 선택하기</title>
      <link>https://hyune-c.tistory.com/60</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;git branch 전략과 commit 방법은 이미 많은 레퍼런스가 존재합니다.&lt;br /&gt;하지만 지금 내 환경에 적합한 전략을 선택하는 것은 조금 다른 이야기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 사이드 프로젝트를 진행하면서 &lt;b&gt;내가 해본&lt;/b&gt; 선택 과정을 기록합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1696773859998&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Our-Class-Bank/core-backend: [side-project] 우리반은행&quot; data-og-description=&quot;[side-project] 우리반은행. Contribute to Our-Class-Bank/core-backend development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Our-Class-Bank/core-backend&quot; data-og-url=&quot;https://github.com/Our-Class-Bank/core-backend&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cn3VXx/hyT5XB8a3w/Lel7xH2lDpnYw88pLXksY1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Our-Class-Bank/core-backend&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Our-Class-Bank/core-backend&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cn3VXx/hyT5XB8a3w/Lel7xH2lDpnYw88pLXksY1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Our-Class-Bank/core-backend: [side-project] 우리반은행&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[side-project] 우리반은행. Contribute to Our-Class-Bank/core-backend development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;github repository 에서 1인 백엔드 개발&lt;/li&gt;
&lt;li&gt;프론트 개발은 별도의 repository 사용&lt;/li&gt;
&lt;li&gt;dev, prod 배포 환경 지원 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전략 선택 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 초기 전략&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVOdAo/btsxqvOtenO/72kOI3EEloeDhtGGWhp1zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVOdAo/btsxqvOtenO/72kOI3EEloeDhtGGWhp1zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVOdAo/btsxqvOtenO/72kOI3EEloeDhtGGWhp1zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVOdAo%2FbtsxqvOtenO%2F72kOI3EEloeDhtGGWhp1zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;656&quot; height=&quot;151&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;feature, dev, main 브랜치만 사용하는 약식 Git Flow 전략&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;local 에서 feature 단위로 완성된 기능을 remote feature 브랜치에 머지합니다.&lt;/li&gt;
&lt;li&gt;remote feature 브랜치를 dev 에 머지합니다.&lt;/li&gt;
&lt;li&gt;qa 가 끝나면 main 브랜치에 머지합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;github issue 번호 기반으로 commit 기록&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법으로 commit 이 어떤 github issue 와 연결되었는지 직관적으로 알 수 있었습니다.&lt;br /&gt;&lt;br /&gt;하지만 &lt;b&gt;큰 내용도 아닌 것을 매번 github issue 를 작성하는 게 귀찮았습니다.&lt;/b&gt;&lt;br /&gt;그리고 1인 개발이기 때문에 커밋 이상으로 이슈 트래킹 할 일은 없을 것 같았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0xoSn/btsxkhiWyyf/lMz0D3vfHKzIIYsTbVtqK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0xoSn/btsxkhiWyyf/lMz0D3vfHKzIIYsTbVtqK1/img.png&quot; data-alt=&quot;micro commit 관점에서 issue, pr 에 너무 많은 기록은 꽤 지저분해 보입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0xoSn/btsxkhiWyyf/lMz0D3vfHKzIIYsTbVtqK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0xoSn%2FbtsxkhiWyyf%2FlMz0D3vfHKzIIYsTbVtqK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;475&quot; height=&quot;274&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;micro commit 관점에서 issue, pr 에 너무 많은 기록은 꽤 지저분해 보입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. commit 기록 방법 변경&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UF40s/btsxto8ImgU/wkteZYBuEvgYFbfx1NE8Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UF40s/btsxto8ImgU/wkteZYBuEvgYFbfx1NE8Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UF40s/btsxto8ImgU/wkteZYBuEvgYFbfx1NE8Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUF40s%2Fbtsxto8ImgU%2FwkteZYBuEvgYFbfx1NE8Qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;195&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;feature, dev, main &lt;b&gt;브랜치만&lt;/b&gt; 사용하는 약식 Git Flow 전략&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;milestone 단위로 prefix 를 만들고, 중분류를 나누어 commit 기록&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github issue 대신 노션 보드로 작업을 관리했습니다.&lt;br /&gt;그리고 일정 기간 단위로 milestone 뭉쳤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AQI6y/btsxsUfF1Xc/gPgRLIQ4qo9po1LjcQw3K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AQI6y/btsxsUfF1Xc/gPgRLIQ4qo9po1LjcQw3K0/img.png&quot; data-alt=&quot;notion 으로 작업 관리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AQI6y/btsxsUfF1Xc/gPgRLIQ4qo9po1LjcQw3K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAQI6y%2FbtsxsUfF1Xc%2FgPgRLIQ4qo9po1LjcQw3K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1354&quot; height=&quot;641&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;notion 으로 작업 관리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;dev 브랜치만으로는 프론트 qa 와 배포 관리가 힘든 상황&lt;/b&gt;이 생겼습니다.&lt;br /&gt;qa 를 위한 브랜치가 한 개다 보니 &lt;b&gt;qa 완료 기능과 아닌 기능을 분리하기 힘들었기&lt;/b&gt; 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. release branch 도입&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;148&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0l1fX/btsxtlRDHjR/Cokeu7mrQrTtCnr1SjBQWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0l1fX/btsxtlRDHjR/Cokeu7mrQrTtCnr1SjBQWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0l1fX/btsxtlRDHjR/Cokeu7mrQrTtCnr1SjBQWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0l1fX%2FbtsxtlRDHjR%2FCokeu7mrQrTtCnr1SjBQWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;148&quot; height=&quot;225&quot; data-origin-width=&quot;148&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;feature, dev, release, main &lt;b&gt;브랜치만&lt;/b&gt; 사용하는 약식 Git Flow 전략&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;local 에서 feature 단위로 완성된 기능을 remote feature 브랜치에 머지합니다.&lt;/li&gt;
&lt;li&gt;remote feature 브랜치를 dev 에 머지합니다.&lt;/li&gt;
&lt;li&gt;qa 가 끝나면 versioning 후 release 브랜치에 머지합니다.&lt;/li&gt;
&lt;li&gt;약속된 시간에 release 브랜치를 main 브랜치에 머지합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;milestone 단위로 prefix 를 만들고, 중분류를 나누어 commit 기록&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;feature 브랜치를 꼭 써야 되나?&lt;/b&gt;라는 생각이 들었습니다.&lt;br /&gt;실무라면 특정 feature 만 테스트하거나 history, conflict 방지를 위해서라도 필요하지만, 1인 개발이다 보니 브랜치를 모두 컨트롤하는 게 어렵지 않았기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. feature branch 삭제&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;dev, release, main &lt;b&gt;브랜치만&lt;/b&gt;&amp;nbsp;사용하는 약식 Git Flow 전략&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;milestone 단위로 prefix 를 만들고, 중분류를 나누어 commit 기록&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;dev 브랜치에서 직접 개발을 진행하여 머지 회수를 줄였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buZfU2/btsxh9yATsg/7D223xkO8jl3pAjjERHjak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buZfU2/btsxh9yATsg/7D223xkO8jl3pAjjERHjak/img.png&quot; data-alt=&quot;v1.0.0 배포!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buZfU2/btsxh9yATsg/7D223xkO8jl3pAjjERHjak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuZfU2%2Fbtsxh9yATsg%2F7D223xkO8jl3pAjjERHjak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;504&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;v1.0.0 배포!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 1인 개발이라면 이런 전략들을 덜 고민해도 됩니다.&lt;br /&gt;conflict 가 발생하더라도 그 commit 이 무엇이었는지 다 내 머릿속에 있을 테니까요.&lt;br /&gt;혹자는 실무라면 여러 사람이 같이 일하고 그라운드 룰이 있기 때문에 이런 약식 전략은 별 의미가 없다고 말할 수도 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;저는 오히려 반대라고 생각합니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실무라고 할지라도 특정 프로젝트는 소수의 인원으로 진행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;신규 프로젝트를 셋업 하는 데는 컨텍스트를 이해하는 소수의 인원이 진행하는 것이 더 효율적일 수 있습니다.&lt;/li&gt;
&lt;li&gt;그라운드 룰이 있을지라도 각 스쿼드는 독립적인 전략을 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 모든 것을 실무라는 생각으로 &lt;b&gt;지금에 최소이면서도 확장 가능한 선택&lt;/b&gt;을 하려고 노력합니다.&lt;br /&gt;그래야만 실무에서 문제를 만났을 때 그 경험을 활용하여 더 좋은 선택을 할 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nSZ1G/btsxh9SSf52/DrqvBKWvNBOY4gR0YVFRak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nSZ1G/btsxh9SSf52/DrqvBKWvNBOY4gR0YVFRak/img.png&quot; data-alt=&quot;어쩌면 실무는 더 극한일지도..?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nSZ1G/btsxh9SSf52/DrqvBKWvNBOY4gR0YVFRak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnSZ1G%2Fbtsxh9SSf52%2FDrqvBKWvNBOY4gR0YVFRak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;361&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어쩌면 실무는 더 극한일지도..?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://hyeon9mak.github.io/git-branch-strategy/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hyeon9mak.github.io/git-branch-strategy/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/1558/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://yozm.wishket.com/magazine/detail/1558/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.mfort.co.kr/blog/2022-08-05-trunk-based-development/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.mfort.co.kr/blog/2022-08-05-trunk-based-development/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://udacity.github.io/git-styleguide/&quot;&gt;https://udacity.github.io/git-styleguide/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LUToz/btsxkCHdkT7/G5zTrVKBt0JBvUWMFhJOx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LUToz/btsxkCHdkT7/G5zTrVKBt0JBvUWMFhJOx1/img.png&quot; data-alt=&quot;개인적으로 정리한 커밋&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LUToz/btsxkCHdkT7/G5zTrVKBt0JBvUWMFhJOx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLUToz%2FbtsxkCHdkT7%2FG5zTrVKBt0JBvUWMFhJOx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;510&quot; height=&quot;422&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개인적으로 정리한 커밋&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>commit</category>
      <category>git branch</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/60</guid>
      <comments>https://hyune-c.tistory.com/60#entry60comment</comments>
      <pubDate>Mon, 9 Oct 2023 00:36:15 +0900</pubDate>
    </item>
    <item>
      <title>Spring cache 는 어떤 provider 를 사용하는 것이 좋을까?</title>
      <link>https://hyune-c.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 local cache 를 사용할 일이 생겼습니다.&lt;br /&gt;동료분이 guava 를 알려주셨고 처음 들어보았기에 일단 baeldung 을 검색해 보았는데 조금 이상했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/guava-cache&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.baeldung.com/guava-cache&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시를 사용할 때 익히 쓰던 @Cacheable 어노테이션이 보이지 않았습니다.&lt;br /&gt;그래서 익숙하던 ehcache 를 검색해 보면서 비교해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1680363136994&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;The difference between Ehcache and Guava Cache - actorsfit&quot; data-og-description=&quot;Recently, I have been doing some cache transformation scenarios, and there are some experience summaries as follows: Cached version: Ehcache: 2.8.3 Guava: 17.0 Ehcache supports persistence to the local disk, but Guava cannot;Ehcache has a ready-made cluste&quot; data-og-host=&quot;blog.actorsfit.com&quot; data-og-source-url=&quot;https://blog.actorsfit.com/a?ID=01250-d3d1718a-1629-447f-ac7b-d9afc18ef2c1&quot; data-og-url=&quot;https://blog.actorsfit.com/a?ID=01250-d3d1718a-1629-447f-ac7b-d9afc18ef2c1&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://blog.actorsfit.com/a?ID=01250-d3d1718a-1629-447f-ac7b-d9afc18ef2c1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.actorsfit.com/a?ID=01250-d3d1718a-1629-447f-ac7b-d9afc18ef2c1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The difference between Ehcache and Guava Cache - actorsfit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Recently, I have been doing some cache transformation scenarios, and there are some experience summaries as follows: Cached version: Ehcache: 2.8.3 Guava: 17.0 Ehcache supports persistence to the local disk, but Guava cannot;Ehcache has a ready-made cluste&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.actorsfit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;guava 는 가볍습니다.&lt;/li&gt;
&lt;li&gt;guava 는 내부적으로 ConCurrentHashMap 을 사용하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽다 보니 동시성은 안전하지만 왠지 성능이 떨어질 것 같다는 직감이 들었습니다.&lt;br /&gt;그래서 몇 개의 문서를 찾아본 결과 guava 는 읽기보다는 쓰기 작업이 중요한 상황에 쓰면 좋다는 내용을 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 꺼림칙한 느낌이 있어서 좀 더 찾아보었더니 guava 는 deprecated 되었습니다!&lt;br /&gt;심지어 꽤 오래 전인 1.5.1.RELEASE 에서였습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1680363344004&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;31.&amp;nbsp;Caching&quot; data-og-description=&quot;The Spring Framework provides support for transparently adding caching to an application. At its core, the abstraction applies caching to methods, reducing thus the number of executions based on the information available in the cache. The caching logic is &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/1.5.1.RELEASE/reference/html/boot-features-caching.html#_supported_cache_providers&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/1.5.1.RELEASE/reference/html/boot-features-caching.html#_supported_cache_providers&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/1.5.1.RELEASE/reference/html/boot-features-caching.html#_supported_cache_providers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/1.5.1.RELEASE/reference/html/boot-features-caching.html#_supported_cache_providers&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;31.&amp;nbsp;Caching&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Spring Framework provides support for transparently adding caching to an application. At its core, the abstraction applies caching to methods, reducing thus the number of executions based on the information available in the cache. The caching logic is&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 처음으로 돌아가서 조사하던 중 caffeine 캐시를 찾았습니다.&lt;br /&gt;ehcache 가 익숙하긴 하지만 기능이 많고 무거웠기에 가벼운 caffeine 을 사용하기로 최종 결정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/spring-boot-caffeine-cache&quot;&gt;https://www.baeldung.com/spring-boot-caffeine-cache&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680364087532&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;IO&quot; data-og-description=&quot;The Spring Framework provides support for transparently adding caching to an application. At its core, the abstraction applies caching to methods, thus reducing the number of executions based on the information available in the cache. The caching logic is &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/io.html#io.caching.provider.caffeine&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/io.html#io.caching.provider.caffeine&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/io.html#io.caching.provider.caffeine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/io.html#io.caching.provider.caffeine&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;IO&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Spring Framework provides support for transparently adding caching to an application. At its core, the abstraction applies caching to methods, thus reducing the number of executions based on the information available in the cache. The caching logic is&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Series/실전!</category>
      <category>caffeine</category>
      <category>Guava</category>
      <category>spring cache</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/59</guid>
      <comments>https://hyune-c.tistory.com/59#entry59comment</comments>
      <pubDate>Sun, 2 Apr 2023 00:30:13 +0900</pubDate>
    </item>
    <item>
      <title>code kata 를 하자! - Codewars</title>
      <link>https://hyune-c.tistory.com/58</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; background-color: #ffffff; color: #000000;&quot;&gt;&lt;b&gt;A kata is an exercise in karate where you repeat a form many, many times, making little improvements in each&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;카타는 하나의 양식을 여러 번 반복하면서 조금씩 개선하는 가라데의 운동입니다.&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;보통 코딩 테스트 통과를 위한 알고리즘 공부는 leetcode, 백준을 추천하곤 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;하지만 저는 실무에 있어 퍼즐에 가까운 알고리즘 풀이보다는, 범용적인&amp;nbsp;자료구조와 해당 언어의 Best Practice 학습이 더 중요하다고 생각합니다. 그러던 중 codewars 라는 사이트를 알게 되었고 제 목적에 부합하고 꽤 유용하다고 생각되어 간단히 소개해보고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676795584619&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Codewars - Achieve mastery through coding practice and developer mentorship&quot; data-og-description=&quot;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&quot; data-og-host=&quot;www.codewars.com&quot; data-og-source-url=&quot;https://www.codewars.com/dashboard&quot; data-og-url=&quot;https://www.codewars.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dIppsu/hyRF7NelfM/DTqve2xmL1e0gw6Db6fDx0/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/v9HCY/hyRF0N7kYy/kwJ2qU9fnUg1qLQckKPcE0/img.jpg?width=1310&amp;amp;height=964&amp;amp;face=0_0_1310_964,https://scrap.kakaocdn.net/dn/ba6gNU/hyRGbvjSOk/trH2285JsV8GyxE7CY0KK1/img.png?width=1512&amp;amp;height=832&amp;amp;face=0_0_1512_832&quot;&gt;&lt;a href=&quot;https://www.codewars.com/dashboard&quot; data-source-url=&quot;https://www.codewars.com/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dIppsu/hyRF7NelfM/DTqve2xmL1e0gw6Db6fDx0/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/v9HCY/hyRF0N7kYy/kwJ2qU9fnUg1qLQckKPcE0/img.jpg?width=1310&amp;amp;height=964&amp;amp;face=0_0_1310_964,https://scrap.kakaocdn.net/dn/ba6gNU/hyRGbvjSOk/trH2285JsV8GyxE7CY0KK1/img.png?width=1512&amp;amp;height=832&amp;amp;face=0_0_1512_832');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Codewars - Achieve mastery through coding practice and developer mentorship&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.codewars.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Pros&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7zZKk/btrZJCfZb18/pdoZIzvN58mTmDoh5w9911/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7zZKk/btrZJCfZb18/pdoZIzvN58mTmDoh5w9911/img.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;623&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.1907%; margin-right: 10px;&quot; data-widthpercent=&quot;40.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7zZKk/btrZJCfZb18/pdoZIzvN58mTmDoh5w9911/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7zZKk%2FbtrZJCfZb18%2FpdoZIzvN58mTmDoh5w9911%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;623&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rhuhh/btrZK3qm0L5/h57FfdrLkfymyTiDX4COj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rhuhh/btrZK3qm0L5/h57FfdrLkfymyTiDX4COj1/img.png&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;450&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;59.34&quot; style=&quot;width: 58.6465%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rhuhh/btrZK3qm0L5/h57FfdrLkfymyTiDX4COj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frhuhh%2FbtrZK3qm0L5%2Fh57FfdrLkfymyTiDX4COj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1054&quot; height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;1. 공부하고 싶은 언어와 목적을 선택할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;특히 문제 선택을 위해 참고할 수 있는 유형을 태그로 지원해 줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;1031&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3afTc/btrZK1TBaTX/st3S3z8XCDFw3QouhA7ndK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3afTc/btrZK1TBaTX/st3S3z8XCDFw3QouhA7ndK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3afTc/btrZK1TBaTX/st3S3z8XCDFw3QouhA7ndK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3afTc%2FbtrZK1TBaTX%2Fst3S3z8XCDFw3QouhA7ndK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1364&quot; height=&quot;1031&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;1031&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;2. Best Practice 를 볼 수 있는 UX 가 압도적으로 훌륭합니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;다른 사람의 코드를 볼 수 있는 기능은 다른 사이트에도 있지만, UX 가 압도적으로 훌륭합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;codewars 를 소개하는 가장 큰 이유입니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Cons&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;1. 커뮤니티 형태의 운영에서 생기는 단점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;codewars 는 문제를 사용자 스스로 올릴 수 있고, 커뮤니티에서 자체적으로 관리됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;그렇기에 참여가 적은 문제는 품질 관리 수준이 낮고, 난이도가 높아질수록 억지스러운 문제들도 존재합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dv1X6W/btrZJcuJdLo/6cxpNskFGSaYLKkHYhOkmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dv1X6W/btrZJcuJdLo/6cxpNskFGSaYLKkHYhOkmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dv1X6W/btrZJcuJdLo/6cxpNskFGSaYLKkHYhOkmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdv1X6W%2FbtrZJcuJdLo%2F6cxpNskFGSaYLKkHYhOkmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1450&quot; height=&quot;776&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;2. 분석 도구의 부족&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;효율성까지 분석해 주는 다른 사이트와 달리 성공과 실패만을 알려줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;b&gt;3. 앞의 2가지 이유로 코딩 테스트 준비 용도로는 추천하지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;코딩 테스트 준비가 목표라면 leetcode, 백준을 추천합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개인적인 활용&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgLPWx/btrZV8DLZEk/3ipKVtKh0FeAlSJUorYRuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgLPWx/btrZV8DLZEk/3ipKVtKh0FeAlSJUorYRuK/img.png&quot; data-alt=&quot;알지 못했던 String.replaceFirstChar 활용을 통한 개선&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgLPWx/btrZV8DLZEk/3ipKVtKh0FeAlSJUorYRuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgLPWx%2FbtrZV8DLZEk%2F3ipKVtKh0FeAlSJUorYRuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;152&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;알지 못했던 String.replaceFirstChar 활용을 통한 개선&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;1. 하루에 1~2개 정도, 낮은 레벨의 문제만 풉니다. (&lt;b&gt;kyu8 ~ kyu5)&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;낮은 레벨의 문제는 보통 요건이 단순하고 5줄 미만으로 해결할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;자료구조와 Best Practice 를 학습하려는 목표에 적합합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;2. 문제의 태그를 선별적으로 활용합니다.&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;선호 태그&lt;/b&gt; FUNDAMENTALS STRINGS LISTS&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;비선호 태그&lt;/b&gt; MATHEMATICS ALGORITHMS PUZZLES&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>잡담 &amp;amp; ETC</category>
      <category>codewars</category>
      <category>leetcode</category>
      <category>코딩 테스트</category>
      <category>코테</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/58</guid>
      <comments>https://hyune-c.tistory.com/58#entry58comment</comments>
      <pubDate>Mon, 20 Feb 2023 03:04:37 +0900</pubDate>
    </item>
    <item>
      <title>Spring Cloud OpenFeign + WireMock Test</title>
      <link>https://hyune-c.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Netflix OSS 에서 시작한 Feign 은 강력한 기능과 편의성으로 Spring Cloud 에 OpenFeign 으로 편입되었습니다.&lt;br /&gt;OpenFeign 은 다음과 같은 장점을 가지고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 연동 로직을 간소화 시킵니다.&lt;/li&gt;
&lt;li&gt;MSA 구성에 큰 도움을 줍니다. SAGA 패턴, &lt;span&gt;마이크로서비스 통신&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;탄력성 등 리액티브 시스템의 특성을 만족시키는데 유용한 패턴을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenFeign 은 이미 널리 알려진 기술이기에 좋은 글들이 많지만 Kotlin + Test 로 작성된 글은 별로 없었습니다.&lt;br /&gt;그래서 외부 연동을 준비하며 학습한 OpenFeign 의 기능을 구현과 테스트로 설명합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1675526513662&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-s-lab/openfeign-spring-tutorial: openfeign 연습하기 with spring cloud&quot; data-og-description=&quot;openfeign 연습하기 with spring cloud. Contribute to Hyune-s-lab/openfeign-spring-tutorial development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-s-lab/openfeign-spring-tutorial&quot; data-og-url=&quot;https://github.com/Hyune-s-lab/openfeign-spring-tutorial&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/openfeign-spring-tutorial&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Hyune-s-lab/openfeign-spring-tutorial&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-s-lab/openfeign-spring-tutorial: openfeign 연습하기 with spring cloud&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;openfeign 연습하기 with spring cloud. Contribute to Hyune-s-lab/openfeign-spring-tutorial development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baIZwS/btrX8inVffE/RXcA0y9PBwPwc56DAmyaK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baIZwS/btrX8inVffE/RXcA0y9PBwPwc56DAmyaK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baIZwS/btrX8inVffE/RXcA0y9PBwPwc56DAmyaK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaIZwS%2FbtrX8inVffE%2FRXcA0y9PBwPwc56DAmyaK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;371&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. OpenFeign 준비하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675527239056&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle.kts
extra[&quot;springCloudVersion&quot;] = &quot;2021.0.5&quot;

dependencies {
	implementation(&quot;org.springframework.cloud:spring-cloud-starter-openfeign&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1675530649951&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableFeignClients(&quot;com.example.openfeignspringtutorial.openfeign&quot;)
class OpenFeignConfig&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;feign 을 적용할 패키지를 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675527305484&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// application.yml
logging:
  level:
    com:
      example:
        openfeignspringtutorial:
          openfeign: DEBUG

feign:
  client:
    config:
      fake-api: // feign name
        logger-level: basic
        

// application-test.yml
logging:
  level:
    com:
      example:
        openfeignspringtutorial:
          openfeign: DEBUG

feign:
  client:
    config:
      fake-api: // feign name
        logger-level: full&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;openfeign 은 debug 레벨에서만 로깅이 되기 때문에 패키지를 지정해야 합니다.&lt;/li&gt;
&lt;li&gt;openfeign logger level 은 필요에 맞게 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 외부 연동하기 - OpenFeign&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675526802854&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
class PostController(private val fakeApiOpenFeign: FakeApiOpenFeign) {
    @GetMapping(&quot;/posts/{postId}&quot;)
    fun getPost(@PathVariable postId: Long): PostResponse = fakeApiOpenFeign.getPost(postId)
}


@FeignClient(name = &quot;fake-api&quot;, url = &quot;https://jsonplaceholder.typicode.com&quot;)
interface FakeApiOpenFeign {
    @GetMapping(&quot;/posts/{postId}&quot;)
    fun getPost(@PathVariable postId: Long): PostResponse
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PostController 로 들어온 GetPost 가 feign 을 통해 외부 url 을 호출하는 코드입니다.&lt;/li&gt;
&lt;li&gt;샘플 코드에서는 Fake Api 를 활용했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675527543932&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// application.yml
application:
  feign-url:
    fake-api: https://jsonplaceholder.typicode.com&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1675530686052&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FeignClient(name = &quot;fake-api&quot;, url = &quot;\${application.feign-url.fake-api}&quot;)
interface FakeApiOpenFeign {
    @GetMapping(&quot;/posts/{postId}&quot;)
    fun getPost(@PathVariable postId: Long): PostResponse
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;url 을 application.yml 에서 관리할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 테스트 준비하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675527808538&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle.kts
dependencies {
    // feign mock test
    testImplementation(&quot;com.github.tomakehurst:wiremock-jre8:2.35.0&quot;)
    
    // kotest, gson
    ...
}


// application-test.yml
application:
  feign-url:
    fake-api: http://localhost:9561&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1675530709594&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@TestConfiguration
class WireMockServerConfig : ApplicationListener&amp;lt;ApplicationReadyEvent&amp;gt; {
    @Bean
    fun wireMockServer(): WireMockServer =
        WireMockServer(9561)

    override fun onApplicationEvent(event: ApplicationReadyEvent) =
        wireMockServer().start()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;wiremock 은 stub server 를 기동시킬 수 있는 라이브러리로 외부 연동 테스트에 적합합니다.&lt;/li&gt;
&lt;li&gt;테스트 설정에서는 애플리케이션 기동 완료 후 9561 포트로 wireMock 을 기동시킵니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;@PostConstruct 보다는 순서를 고려하여 더 확실한 ApplicationReadyEvent 를 활용 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675528549966&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object WireMockServerConst {
    const val port = 9561
    const val rootResponsePath = &quot;src/test/resources/__files/&quot;

    object GetPosts {
        const val url = &quot;/posts/([1-99])&quot;
        const val responsePath = &quot;payload/post.json&quot;

        fun absoluteResponsePath(): String = rootResponsePath + responsePath
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1675530734405&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// resources/__files/payload/post.json
{
  &quot;userId&quot;: 1,
  &quot;id&quot;: 1,
  &quot;title&quot;: &quot;sunt aut facere repellat provident occaecati excepturi optio reprehenderit&quot;,
  &quot;body&quot;: &quot;quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 편의를 위한 상수 값들을 하나의 파일로 응집시킵니다.&lt;/li&gt;
&lt;li&gt;wiremock 은 파일 형태의 json response body 를 지원 합니다. 기본 경로는 resources/__files 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 정상 응답 테스트&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675528256439&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ActiveProfiles(&quot;test&quot;)
@SpringBootTest(classes = [WireMockServerConfig::class])
internal class FakeApiOpenFeignTest(
    private val wireMockServer: WireMockServer,
    private val fakeApiOpenFeign: FakeApiOpenFeign,
) : BehaviorSpec({
    Given(&quot;getPosts stub - success&quot;) {
        wireMockServer.stubGetPosts(
            WireMock.aResponse()
                .withStatus(HttpStatus.OK.value())
                .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .withBodyFile(WireMockServerConst.GetPosts.responsePath)
        )

        When(&quot;feign 으로 getPost 호출&quot;) {
            val then = fakeApiOpenFeign.getPost(1L)

            Then(&quot;response 일치&quot;) {
                val expected: PostResponse = Gson().fromJson(
                    JsonReader(FileReader(WireMockServerConst.GetPosts.absoluteResponsePath())),
                    PostResponse::class.java
                )
                then shouldBe expected
            }
        }
    }
})

private fun WireMockServer.stubGetPosts(responseDefinitionBuilder: ResponseDefinitionBuilder) {
    stubFor(
        WireMock.get(WireMock.urlPathMatching(WireMockServerConst.GetPosts.url))
            .willReturn(responseDefinitionBuilder)
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;wiremock stub server 와 gson 을 활용해 테스트할 수 있습니다.&lt;/li&gt;
&lt;li&gt;kotlin 의 확장 함수를 통해 stub 생성 메서드를 깔끔하게 분리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 예외 처리하기 - ErrorDecoder&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675528832916&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// application.yml
feign:
  client:
    config:
      fake-api:
        logger-level: basic
        error-decoder: com.example.openfeignspringtutorial.openfeign.FakeApiErrorDecoder&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1675530753832&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class FakeApiErrorDecoder : ErrorDecoder {
    private val log = LoggerFactory.getLogger(this.javaClass)

    override fun decode(methodKey: String, response: Response): Exception {
        log.info(&quot;### ${response.status()}, methodKey = $methodKey&quot;);

        if (response.status() == HttpStatus.BAD_REQUEST.value()) {
            return ResponseStatusException(
                HttpStatus.valueOf(response.status()),
                &quot;&amp;lt;You can add error message description here&amp;gt;&quot;
            )
        }

        if (response.status() == HttpStatus.NOT_FOUND.value()) {
        	...
        }

        if (response.status() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
        	...
       	}

        return Exception(response.reason())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;openfeign 은 ErrorDecoder 를 통해 응답코드에 맞는 예외처리를 지원합니다.&lt;/li&gt;
&lt;li&gt;기본적으로 ResponseStatusException 으로 반환하는 것을 추천합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675529101702&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    Given(&quot;getPosts stub - BAD_REQUEST&quot;) {
        wireMockServer.stubGetPosts(WireMock.badRequest())

        When(&quot;feign 으로 getPost 호출&quot;) {
            val then = shouldThrow&amp;lt;ResponseStatusException&amp;gt; {
                fakeApiOpenFeign.getPost(1L)
            }

            Then(&quot;BAD_REQUEST 확인&quot;) {
                then.status shouldBe HttpStatus.BAD_REQUEST
            }
        }
    }

    Given(&quot;getPosts stub - INTERNAL_SERVER_ERROR&quot;) {
        wireMockServer.stubGetPosts(WireMock.serverError())

        When(&quot;feign 으로 getPost 호출&quot;) {
            val then = shouldThrow&amp;lt;ResponseStatusException&amp;gt; {
                fakeApiOpenFeign.getPost(1L)
            }

            Then(&quot;INTERNAL_SERVER_ERROR 확인&quot;) {
                then.status shouldBe HttpStatus.INTERNAL_SERVER_ERROR
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;wiremock 은 간단하게 응답코드를 변경해서 내려보내줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 목표 서버 Connection refused 대응하기 - retry&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675529548665&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableFeignClients(&quot;com.example.openfeignspringtutorial.openfeign&quot;)
class OpenFeignConfig {
    @Bean
    fun retryer(): Retryer {
        return Retryer.Default(300, 2000, 3)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1675531400479&quot; class=&quot;isbl&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    Given(&quot;getPosts stub - Connection refused&quot;) {
        wireMockServer.stop()

        When(&quot;feign 으로 getPost 호출&quot;) {
            val then = shouldThrow&amp;lt;RetryableException&amp;gt; {
                fakeApiOpenFeign.getPost(1L)
            }

            Then(&quot;Connection refused 확인&quot;) {
                then.message shouldStartWith &quot;Connection refused&quot;
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RetryableException 발생시 등록된 retryer 로직이 동작 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openfeign 사용에 필요한 내용들을 어느 정도 정리했지만 실무를 위해서는 고민할 점이 남아있습니다.&lt;br /&gt;하지만 이번 글에서는 키워드만 공유하고 좀 더 실무에 적용해본 후 정리하겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Connection refused 외에도 재시도할 수 있을까? - ErrorDecoder Custom&lt;/li&gt;
&lt;li&gt;200 OK 예외는 어떻게 해야 될까? - ErrorDecoder Custom&lt;/li&gt;
&lt;li&gt;대상 서버의 장애가 장기화되면 어떻게 해야 될까? - Circuit Breaker, FallBack&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study</category>
      <category>fake api</category>
      <category>Feign</category>
      <category>neflix oss</category>
      <category>openfeign</category>
      <category>retry</category>
      <category>wiremock</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/57</guid>
      <comments>https://hyune-c.tistory.com/57#entry57comment</comments>
      <pubDate>Sun, 5 Feb 2023 02:26:47 +0900</pubDate>
    </item>
    <item>
      <title>RandomCode Generator 성능 측정하기</title>
      <link>https://hyune-c.tistory.com/56</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;일련번호는 총 16 자리의 숫자/영문 대문자로 이루어진 문자열이다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사 업무를 하던 중 기존에 작성된 무작위 일련번호를 만드는 로직을 보게 되었습니다.&lt;br /&gt;간단한 업무 내용에 Util 성격의 로직이었지만, 구현된 로직은 생각보다 복잡했습니다.&lt;br /&gt;그래서 기존 로직의 의도를 파악하고, 개선을 시도해 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;span&gt;결론을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;공유하고, 시도한 개선을 따라가 보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론 (각 1만, 10만, 50만, 100만 회 반복)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRfPfn/btrVZ211q7Z/4hEHdODGkDpkLh6EtIjYGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRfPfn/btrVZ211q7Z/4hEHdODGkDpkLh6EtIjYGk/img.png&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;950&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.84&quot; height=&quot;546&quot; style=&quot;width: 49.2621%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRfPfn/btrVZ211q7Z/4hEHdODGkDpkLh6EtIjYGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRfPfn%2FbtrVZ211q7Z%2F4hEHdODGkDpkLh6EtIjYGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5MUG7/btrVZudGQYh/K0e7VvjQLIKEW144JqI0Z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5MUG7/btrVZudGQYh/K0e7VvjQLIKEW144JqI0Z1/img.png&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;944&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.16&quot; height=&quot;542&quot; style=&quot;width: 49.5752%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5MUG7/btrVZudGQYh/K0e7VvjQLIKEW144JqI0Z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5MUG7%2FbtrVZudGQYh%2FK0e7VvjQLIKEW144JqI0Z1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ThreadLocalRandom 의 성능은 매우 뛰어나다. Kotlin Random 역시 뛰어나다.&lt;/li&gt;
&lt;li&gt;String 변환은 매우 비싼 작업이며 Sequence 가 효율적으로 작동하지 않을 수 있다.&lt;/li&gt;
&lt;li&gt;Coroutine 은 학습이 부족하여 적용하지 않았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1673422468298&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-s-lab/perf-16-digit-code-generator: 16자리 코드 생성 성능 테스트&quot; data-og-description=&quot;16자리 코드 생성 성능 테스트. Contribute to Hyune-s-lab/perf-16-digit-code-generator development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator&quot; data-og-url=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOhMRl/hyReJNt9D9/yOZhVsYnu6wOZNjsi5uodK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOhMRl/hyReJNt9D9/yOZhVsYnu6wOZNjsi5uodK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-s-lab/perf-16-digit-code-generator: 16자리 코드 생성 성능 테스트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;16자리 코드 생성 성능 테스트. Contribute to Hyune-s-lab/perf-16-digit-code-generator development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 로직 분석&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a title=&quot;CodeGenerator.kt&quot; href=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator/blob/main/src/main/kotlin/com/example/perf16digitcodegenerator/legacy/CodeGenerator.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CodeGenerator.kt&lt;/a&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1673423100648&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object CodeGenerator {
    private val numUpperChars = &quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;.toCharArray()
    private const val codeDigits = 16

    fun generate(count: Long): Set&amp;lt;String&amp;gt; {
        val codes = mutableSetOf&amp;lt;String&amp;gt;()
        val code = StringBuilder()
        do {
            repeat((1..codeDigits).count()) {
                getRandomIndex().apply {
                    code.append(numUpperChars[this])
                }
            }
            codes.add(code.toString())
            code.clear()
        } while (!isGeneratedCountEnough(codes, count))

        return codes
    }

    private fun getRandomIndex() = ThreadLocalRandom.current().nextInt(0, numUpperChars.size)
    private fun isGeneratedCountEnough(codes: Set&amp;lt;String&amp;gt;, count: Long) = codes.size &amp;gt;= count
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;숫자와 영문 대문자만을 가진 Character Pool 을 활용하여 낭비 없이 랜덤 문자를 가져옵니다.&lt;/li&gt;
&lt;li&gt;StringBuilder 를 통해 코드 생성 과정의 낭비를 없애고, Set 자료구조를 통해 완성된 코드의 중복을 방지합니다.&lt;/li&gt;
&lt;li&gt;ThreadLocalRandom 의 활용으로 안전하고 빠른 Random 을 구현했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1673423330101&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Random 대신 ThreadLocalRandom을 써야 하는 이유&quot; data-og-description=&quot;java.util.Random은 멀티 쓰레드 환경에서 하나의 인스턴스에서 전역적으로 의사 난수(pseudo random)를 반환한다. 따라서 같은 시간에 동시 요청이 들어올 경우 경합 상태에서 성능에 문제가 생길 수 있&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@sojukang/Random-%EB%8C%80%EC%8B%A0-ThreadLocalRandom%EC%9D%84-%EC%8D%A8%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; data-og-url=&quot;https://velog.io/@sojukang/Random-대신-ThreadLocalRandom을-써야-하는-이유&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bYTkxF/hyRfC0laSm/oWjexpDevBghDuKeZXtXX1/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/jDZst/hyRfzJjgOG/noXOyP26wS8BuD1WYL0iW0/img.png?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460&quot;&gt;&lt;a href=&quot;https://velog.io/@sojukang/Random-%EB%8C%80%EC%8B%A0-ThreadLocalRandom%EC%9D%84-%EC%8D%A8%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@sojukang/Random-%EB%8C%80%EC%8B%A0-ThreadLocalRandom%EC%9D%84-%EC%8D%A8%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bYTkxF/hyRfC0laSm/oWjexpDevBghDuKeZXtXX1/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/jDZst/hyRfzJjgOG/noXOyP26wS8BuD1WYL0iW0/img.png?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Random 대신 ThreadLocalRandom을 써야 하는 이유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;java.util.Random은 멀티 쓰레드 환경에서 하나의 인스턴스에서 전역적으로 의사 난수(pseudo random)를 반환한다. 따라서 같은 시간에 동시 요청이 들어올 경우 경합 상태에서 성능에 문제가 생길 수 있&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;숫자와 문자로만 이루어진 16자리라면 UUID 를 활용하면 어떨까?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator/blob/main/src/main/kotlin/com/example/perf16digitcodegenerator/refactor/strategy/UUIDStrategy.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UUIDStrategy.kt&lt;/a&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator/blob/main/src/main/kotlin/com/example/perf16digitcodegenerator/refactor/strategy/UUIDWithSequenceStrategy.kt&quot;&gt;UUIDWithSequenceStrategy.kt&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPdcWl/btrVWzNN9P5/m2lkfSgXMoZyv44sJpWPK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPdcWl/btrVWzNN9P5/m2lkfSgXMoZyv44sJpWPK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPdcWl/btrVWzNN9P5/m2lkfSgXMoZyv44sJpWPK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPdcWl%2FbtrVWzNN9P5%2Fm2lkfSgXMoZyv44sJpWPK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;252&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검증된 UUID 로직을 활용하기에 성능이 좋아질 것으로 기대했지만, 기존 대비 2배 수준의 성능 차이가 납니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1673435653710&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UUIDStrategy : CodeGenerationStrategy {
    override fun generate(size: Long): Set&amp;lt;String&amp;gt; =
        (1..size)
            .map { UUID.randomUUID().toString().uppercase().replace(&quot;-&quot;, &quot;&quot;) }
            .toSet()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가독성은 확실히 좋아졌지만, String 변환의 비용이 크기 때문인 것 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ThreadLacalRandom, Java Random, Kotlin Random 은 어떨까?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator/blob/main/src/main/kotlin/com/example/perf16digitcodegenerator/refactor/strategy/LegacyWithJavaRandomStrategy.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LegacyWithJavaRandomStrategy.kt&lt;/a&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator/blob/main/src/main/kotlin/com/example/perf16digitcodegenerator/refactor/strategy/LegacyWithKotlinRandomStrategy.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LegacyWithKotlinRandomStrategy.kt&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9It2J/btrVZ7bF9Lv/99d0jouMkqZXre9nwcByMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9It2J/btrVZ7bF9Lv/99d0jouMkqZXre9nwcByMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9It2J/btrVZ7bF9Lv/99d0jouMkqZXre9nwcByMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9It2J%2FbtrVZ7bF9Lv%2F99d0jouMkqZXre9nwcByMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;252&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java Random 은 꽤 느립니다.&lt;/li&gt;
&lt;li&gt;Kotlin Random 과 ThreadLocalRandom 은 큰 차이가 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1673449587063&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LegacyWithJavaRandomStrategy : CodeGenerationStrategy {
    override fun generate(size: Long): Set&amp;lt;String&amp;gt; {
	...
    }

	// companion object 에 선언한 random 사용
    private fun getRandomIndex() = random.nextInt(0, numUpperChars.size) 
    private fun isGeneratedCountEnough(codes: Set&amp;lt;String&amp;gt;, count: Long) = codes.size &amp;gt;= count
    
    companion object {
        private val numUpperChars = &quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;.toCharArray()
        private val random = Random() // Java Random 선언
        private const val codeDigits = 16
    }
}


class LegacyWithKotlinRandomStrategy : CodeGenerationStrategy {
    override fun generate(size: Long): Set&amp;lt;String&amp;gt; {
	...
	}

	// Kotlin Random 사용
    private fun getRandomIndex() = Random.nextInt(0, numUpperChars.size)
    private fun isGeneratedCountEnough(codes: Set&amp;lt;String&amp;gt;, count: Long) = codes.size &amp;gt;= count
    
    companion object {
        private val numUpperChars = &quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;.toCharArray()
        private const val codeDigits = 16
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java Random 은 사용 전 객체 생성을 해줘야 합니다. 본 코드에서는 companion object 에 선언하였습니다.&lt;/li&gt;
&lt;li&gt;Kotlin Random 은 바로 사용할 수 있었습니다. (이미 companion object)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UUID 를 그대로 쓰면 어떨까?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/perf-16-digit-code-generator/blob/main/src/test/kotlin/com/example/perf16digitcodegenerator/PerformanceTest.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PerformanceTest.kt&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u9qOE/btrVZ6qiOS2/3MSQnL8Qa2gE1AA9iXmPO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u9qOE/btrVZ6qiOS2/3MSQnL8Qa2gE1AA9iXmPO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u9qOE/btrVZ6qiOS2/3MSQnL8Qa2gE1AA9iXmPO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu9qOE%2FbtrVZ6qiOS2%2F3MSQnL8Qa2gE1AA9iXmPO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;252&quot; data-origin-width=&quot;698&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1673449609101&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun measureTimeMillis3(size: Long): Long {
    var codes: List&amp;lt;UUID&amp;gt;
    val elapsed = measureTimeMillis {
        codes = (1..size).asSequence()
            .map { UUID.randomUUID() }
            .toList()
    }

    codes.size shouldBe size

//    println(codes)
    return elapsed
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UUID 는 최적화가 잘되어서인지 아주 높은 성능을 보여주었습니다.&lt;/li&gt;
&lt;li&gt;Sequence 도 잘 적용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;커스텀 로직보다는 검증된 라이브러리를 활용하는 것의 성능이 더 좋을 것이다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 해보기 전까지는 위와 같이 생각했었습니다.&lt;br /&gt;하지만 직접 해보면서 업무 성격에 따라서는 직접 만드는 것이 더 유리할 수도 있다는 것을 알았고, 기존 코드를 학습하면서 ThreadLocalRandom 이라는 몰랐던 기능과 Java Random 과 Kotlin Random 의 차이도 알 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 가독성은 차이가 있지만, 기존 로직이 업무 요건을 만족하고 성능 차이가 크기에 그대로 둘 것 같습니다.&lt;/p&gt;</description>
      <category>Series/실전!</category>
      <category>Java Random</category>
      <category>kotlin random</category>
      <category>Random</category>
      <category>ThreadLocalRandom</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/56</guid>
      <comments>https://hyune-c.tistory.com/56#entry56comment</comments>
      <pubDate>Wed, 11 Jan 2023 16:40:49 +0900</pubDate>
    </item>
    <item>
      <title>Datagrip 에서 Datasource 공유하기</title>
      <link>https://hyune-c.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DB 접속 정보 세팅은 가장 기초적인 개발 환경 세팅의 하나입니다.&lt;br /&gt;보통은 애플리케이션이나 환경 변수 정보 모음에서 가져오거나, 별도로 관리되는 엑셀 파일을 활용하곤 합니다.&lt;br /&gt;하지만 균일한 개발 환경을 보장하기 힘들고, 접속해야 되는 DB 가 많은 경우 꽤나 번거로운 일입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Datagrip 은 &lt;b&gt;Export Settings&lt;/b&gt; 이라는 좀 더 똑똑한 방법을 지원합니다.&lt;/blockquote&gt;
&lt;figure id=&quot;og_1668848016805&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Manage data sources | DataGrip&quot; data-og-description=&quot; &quot; data-og-host=&quot;www.jetbrains.com&quot; data-og-source-url=&quot;https://www.jetbrains.com/help/datagrip/managing-data-sources.html#sharing-data-source-settings&quot; data-og-url=&quot;https://www.jetbrains.com/help/datagrip/managing-data-sources.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WCG9Q/hyQB4drHh8/E9J7o5hkfb9uHRr5Yad3R1/img.png?width=1281&amp;amp;height=800&amp;amp;face=0_0_1281_800,https://scrap.kakaocdn.net/dn/sgQWh/hyQDDFfIm5/1PzZpFIUykGKRB5jIuHbu1/img.png?width=960&amp;amp;height=1255&amp;amp;face=0_0_960_1255,https://scrap.kakaocdn.net/dn/cCKRjq/hyQBT31ISA/K2dihKgcNFNr2mlmqkau61/img.png?width=1412&amp;amp;height=874&amp;amp;face=0_0_1412_874&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/help/datagrip/managing-data-sources.html#sharing-data-source-settings&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jetbrains.com/help/datagrip/managing-data-sources.html#sharing-data-source-settings&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WCG9Q/hyQB4drHh8/E9J7o5hkfb9uHRr5Yad3R1/img.png?width=1281&amp;amp;height=800&amp;amp;face=0_0_1281_800,https://scrap.kakaocdn.net/dn/sgQWh/hyQDDFfIm5/1PzZpFIUykGKRB5jIuHbu1/img.png?width=960&amp;amp;height=1255&amp;amp;face=0_0_960_1255,https://scrap.kakaocdn.net/dn/cCKRjq/hyQBT31ISA/K2dihKgcNFNr2mlmqkau61/img.png?width=1412&amp;amp;height=874&amp;amp;face=0_0_1412_874');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Manage data sources | DataGrip&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jetbrains.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;datasource 세팅이 완료된 기존 직원&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. export 시키고 싶은 정보를 global datasource 로 설정&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etp3N6/btrRzg6i8cV/QXD3byXIRcgZxSLREaIMoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etp3N6/btrRzg6i8cV/QXD3byXIRcgZxSLREaIMoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etp3N6/btrRzg6i8cV/QXD3byXIRcgZxSLREaIMoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fetp3N6%2FbtrRzg6i8cV%2FQXD3byXIRcgZxSLREaIMoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;433&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. export - Database 2개 체크&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mCi8h/btrRC01F3x7/fx9qqQDIqmqi0CDduKgGBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mCi8h/btrRC01F3x7/fx9qqQDIqmqi0CDduKgGBK/img.png&quot; width=&quot;424&quot; height=&quot;459&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1384&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.1399%; margin-right: 10px;&quot; data-widthpercent=&quot;50.73&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mCi8h/btrRC01F3x7/fx9qqQDIqmqi0CDduKgGBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmCi8h%2FbtrRC01F3x7%2Ffx9qqQDIqmqi0CDduKgGBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1384&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OrPHZ/btrRBeF94Dm/bADJboLAgvl62T01wPz52k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OrPHZ/btrRBeF94Dm/bADJboLAgvl62T01wPz52k/img.png&quot; width=&quot;243&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1425&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.6973%;&quot; data-widthpercent=&quot;49.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OrPHZ/btrRBeF94Dm/bADJboLAgvl62T01wPz52k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOrPHZ%2FbtrRBeF94Dm%2FbADJboLAgvl62T01wPz52k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1425&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;datasource 정보를 받고 싶은 신규 직원&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. import - setting.zip 파일 선택&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/COljY/btrRCW559dA/QgO3kPwWNc3bdLSDiQecc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/COljY/btrRCW559dA/QgO3kPwWNc3bdLSDiQecc1/img.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2523&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;24.3&quot; style=&quot;width: 388px; margin-right: 10px;&quot; height=&quot;490&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/COljY/btrRCW559dA/QgO3kPwWNc3bdLSDiQecc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCOljY%2FbtrRCW559dA%2FQgO3kPwWNc3bdLSDiQecc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;2523&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE6yk1/btrRBfygFc1/9Z7G2c4QfPRodnOhddMYI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE6yk1/btrRBfygFc1/9Z7G2c4QfPRodnOhddMYI0/img.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;405&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;75.7&quot; data-filename=&quot;blob&quot; style=&quot;width: 383px;&quot; height=&quot;155&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE6yk1/btrRBfygFc1/9Z7G2c4QfPRodnOhddMYI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE6yk1%2FbtrRBfygFc1%2F9Z7G2c4QfPRodnOhddMYI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 성공!&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;3193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1xt6O/btrRACN6jv2/Hbq2ewRU54Q4QmlDZX5jjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1xt6O/btrRACN6jv2/Hbq2ewRU54Q4QmlDZX5jjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1xt6O/btrRACN6jv2/Hbq2ewRU54Q4QmlDZX5jjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1xt6O%2FbtrRACN6jv2%2FHbq2ewRU54Q4QmlDZX5jjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;556&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;3193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tip</category>
      <category>datagrip</category>
      <category>Datasource</category>
      <category>초기 세팅</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/55</guid>
      <comments>https://hyune-c.tistory.com/55#entry55comment</comments>
      <pubDate>Sat, 19 Nov 2022 18:41:29 +0900</pubDate>
    </item>
    <item>
      <title>스프링 마이크로서비스 코딩 공작소 (2)</title>
      <link>https://hyune-c.tistory.com/54</link>
      <description>&lt;figure id=&quot;og_1664981853581&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-s-lab/optima-growth: 스프링 마이크로서비스 코딩 공작소&quot; data-og-description=&quot;스프링 마이크로서비스 코딩 공작소. Contribute to Hyune-s-lab/optima-growth development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-s-lab/optima-growth&quot; data-og-url=&quot;https://github.com/Hyune-s-lab/optima-growth&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hvUMS/hyP2eUjFID/acBgOsHhJKc7bG16WUEz31/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/optima-growth&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Hyune-s-lab/optima-growth&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hvUMS/hyP2eUjFID/acBgOsHhJKc7bG16WUEz31/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-s-lab/optima-growth: 스프링 마이크로서비스 코딩 공작소&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스프링 마이크로서비스 코딩 공작소. Contribute to Hyune-s-lab/optima-growth development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Phase 1&amp;nbsp;&lt;/b&gt;&lt;br /&gt;3. 스프링 부트로 마이크로서비스 구축하기&lt;br /&gt;4. 도커&lt;br /&gt;&lt;a href=&quot;https://hyune-c.tistory.com/53&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hyune-c.tistory.com/53&lt;/a&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Phase 2&lt;/b&gt;&lt;br /&gt;5. 스프링 클라우드 컨피그 서버로 구성 관리&lt;br /&gt;6. 서비스 디스커버리&lt;br /&gt;&lt;b&gt;이 글입니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;eureka-server&amp;nbsp;구현&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfJFAu/btrNTBEThVJ/F1yvqeKrhidBi1OEBnk0Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfJFAu/btrNTBEThVJ/F1yvqeKrhidBi1OEBnk0Vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfJFAu/btrNTBEThVJ/F1yvqeKrhidBi1OEBnk0Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfJFAu%2FbtrNTBEThVJ%2FF1yvqeKrhidBi1OEBnk0Vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;321&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카는 클라이언트의 생존 여부를 체크하여 API 게이트웨이의 라우팅을 돕습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;eureka-server&lt;/h3&gt;
&lt;pre id=&quot;code_1664983490250&quot; class=&quot;kotlin&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle.kts
dependencies {
    implementation(&quot;org.springframework.cloud:spring-cloud-starter-netflix-eureka-server&quot;)
}


// EurekaServerApplication.kt
@EnableEurekaServer
@SpringBootApplication
class EurekaServerApplication

fun main(args: Array&amp;lt;String&amp;gt;) {
    runApplication&amp;lt;EurekaServerApplication&amp;gt;(*args)
}

// application.yml
server:
  port: 8761

eureka:
  instance:
    appname: eureka-server
  client:
    register-with-eureka: false
    fetch-registry: false&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;licensing-service&lt;/h3&gt;
&lt;pre id=&quot;code_1664983490250&quot; class=&quot;kotlin&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle.kts
dependencies {
    implementation(&quot;org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:3.1.4&quot;)
}


// LicensingServiceApplication.kt
@EnableEurekaClient
@SpringBootApplication
class LicensingServiceApplication

fun main(args: Array&amp;lt;String&amp;gt;) {
    runApplication&amp;lt;LicensingServiceApplication&amp;gt;(*args)
}


// application.yml
server:
  port: 8080

eureka:
  instance:
    appname: licensing-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;http://localhost:8761/&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2113&quot; data-origin-height=&quot;1256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkBA1x/btrNT92sYZL/vhF5TtKPrOlE2glIsK1Gk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkBA1x/btrNT92sYZL/vhF5TtKPrOlE2glIsK1Gk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkBA1x/btrNT92sYZL/vhF5TtKPrOlE2glIsK1Gk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkBA1x%2FbtrNT92sYZL%2FvhF5TtKPrOlE2glIsK1Gk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;416&quot; data-origin-width=&quot;2113&quot; data-origin-height=&quot;1256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카는 클라이언트를 알지 못합니다. &lt;br /&gt;클라이언트가 유레카의 url 을 알고 정보를 보내줍니다. (application.yml 의 defaultZone)&lt;br /&gt;유레카는 클라이언트 application 의 생존 여부를 알 수 있습니다. (LICENSING-SERVICE)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;http://localhost:8761/eureka/apps&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CERti/btrNTfa4p20/DDsIKepSTtIXn8Vu3YDK71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CERti/btrNTfa4p20/DDsIKepSTtIXn8Vu3YDK71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CERti/btrNTfa4p20/DDsIKepSTtIXn8Vu3YDK71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCERti%2FbtrNTfa4p20%2FDDsIKepSTtIXn8Vu3YDK71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;516&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카에서 관리되는 application 을 볼 수 있습니다. json 형태도 가능합니다.&lt;br /&gt;instanceId 의 ip 를 보면 172 대역대로 사설망임을 확인할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API 호출&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw3iNW/btrNTK9JBdM/M6vT5oAkmBoXk4CM9KDjI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw3iNW/btrNTK9JBdM/M6vT5oAkmBoXk4CM9KDjI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw3iNW/btrNTK9JBdM/M6vT5oAkmBoXk4CM9KDjI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw3iNW%2FbtrNTK9JBdM%2FM6vT5oAkmBoXk4CM9KDjI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;388&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ip 로 api 요청 시 성공합니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;docker setting&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;docker 는 시행 착오가 조금&amp;nbsp;있었습니다. 코드 소개 후 아래에서 설명 합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dockerfile&lt;/h3&gt;
&lt;pre id=&quot;code_1664985783409&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker/eureka-server/Dockerfile
FROM openjdk:17-jdk-slim-buster
ARG JAR_FILE=/eureka-server/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]


# docker/eureka-server/licensing-service
FROM openjdk:17-jdk-slim-buster
ARG JAR_FILE=/licensing-service/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 root 경로에 있던 Dockerfile 을 service 에 맞는 폴더로 분리했습니다.&lt;br /&gt;docker setting 후 이어질 환경 설정 분리에서 좀 더 활용해봅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker-compose.yml&lt;/h3&gt;
&lt;pre id=&quot;code_1664985907180&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3&quot;
services:
  eureka-server:
    image: eureka-server
    ports:
      - &quot;8761:8761&quot;
  licensing-service:
    image: licensing-service
    ports:
      - &quot;8080:8080&quot;
    links:
      - eureka-server&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;run.sh&lt;/h3&gt;
&lt;pre id=&quot;code_1664985963845&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# eureka-server
cd eureka-server
./gradlew bootjar
cd ..
docker build -f docker/eureka-server/Dockerfile -t eureka-server .

# licensing-service
cd licensing-service
./gradlew bootjar
cd ..
docker build -f docker/licensing-service/Dockerfile -t licensing-service .

# 백그라운드로 실행
nohup docker-compose up 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 서비스가 1개였기에 바로 실행했습니다.&lt;br /&gt;하지만 이제는 서비스가 2개인만큼 백그라운드로 실행하게 하고 로그를 남기지 않게 했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;./run.sh&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;933&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCbVu7/btrNSDRk1CZ/DZjx5ZnCZvpSYXtFjMpNL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCbVu7/btrNSDRk1CZ/DZjx5ZnCZvpSYXtFjMpNL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCbVu7/btrNSDRk1CZ/DZjx5ZnCZvpSYXtFjMpNL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCbVu7%2FbtrNSDRk1CZ%2FDZjx5ZnCZvpSYXtFjMpNL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;671&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;933&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2502&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXppkj/btrNSDRlcQG/G3IfF9xSWkNDOgQdtj87h1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXppkj/btrNSDRlcQG/G3IfF9xSWkNDOgQdtj87h1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXppkj/btrNSDRlcQG/G3IfF9xSWkNDOgQdtj87h1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXppkj%2FbtrNSDRlcQG%2FG3IfF9xSWkNDOgQdtj87h1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;181&quot; data-origin-width=&quot;2502&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker dashboard 에서 8761, 8080 포트로 정상실행 된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;1201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF4Zzk/btrNRCZoD0O/W9BpZBtIdd54L0TSAecNoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF4Zzk/btrNRCZoD0O/W9BpZBtIdd54L0TSAecNoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF4Zzk/btrNRCZoD0O/W9BpZBtIdd54L0TSAecNoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF4Zzk%2FbtrNRCZoD0O%2FW9BpZBtIdd54L0TSAecNoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;397&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;1201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(개별 서비스로 실행했을 때와 달리 docker 로 실행 시 30초 정도의 지연이 걸릴 수 있습니다.)&lt;br /&gt;정상적으로 클라이언트를 찾은 것 처럼 보이지만 IP 가 보였던 아까와는 달리 해시 값이 보입니다?&lt;br /&gt;API 호출도 되지 않습니다???&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시행 착오 1 - IP 가 왜 안 나올까?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;docker dashboard 를 보면 licensing-service-1 의 컨테이너 ID 와 유레카의 해시 값이 같음을 알 수 있습니다.&lt;br /&gt;하지만 IP 의 형태가 아니고 API 호출이 정상적으로 되지 않습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 설정의 문제로 생각해서 &lt;b&gt;eureka.instance.preferIpAddress=true&lt;/b&gt;&amp;nbsp;도 설정했지만 결과는 같았습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8IzZm/btrNSVYepLt/pKMwKaaz2iIpVSQeVkMYZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8IzZm/btrNSVYepLt/pKMwKaaz2iIpVSQeVkMYZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8IzZm/btrNSVYepLt/pKMwKaaz2iIpVSQeVkMYZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8IzZm%2FbtrNSVYepLt%2FpKMwKaaz2iIpVSQeVkMYZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;599&quot; height=&quot;441&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 책을 다시 보니 흐름상 클라이언트의 요청은 API 게이트웨이를 통해 서비스로 전달되고 있었습니다.&lt;br /&gt;그리고 8장에서 서비스 라우팅에 대해 나오는 것을 볼 때 굳이 지금 더 찾아봐야 될 이유가 없을 것 같아 보류했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시행 착오 2 - 도커 컨테이너에서 localhost 를 쓰면 어떻게 될까?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q7O0Q/btrNSNzmA2J/SeWAbJcE3VG5Kjr8JhLLr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q7O0Q/btrNSNzmA2J/SeWAbJcE3VG5Kjr8JhLLr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q7O0Q/btrNSNzmA2J/SeWAbJcE3VG5Kjr8JhLLr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ7O0Q%2FbtrNSNzmA2J%2FSeWAbJcE3VG5Kjr8JhLLr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1505&quot; height=&quot;543&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 설정은 그대로 둔 채 docker 세팅만 완료한 후 기동 했을 때, 기동은 되었지만 유레카에서 보이지 않았습니다?&lt;br /&gt;이상해서 로그를 보니 유레카의 url 을 찾지 못하고 있었습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wxHR9/btrNSM1wMOB/pDLwxgB4fxBnkFyZ0weik0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wxHR9/btrNSM1wMOB/pDLwxgB4fxBnkFyZ0weik0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wxHR9/btrNSM1wMOB/pDLwxgB4fxBnkFyZ0weik0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwxHR9%2FbtrNSM1wMOB%2FpDLwxgB4fxBnkFyZ0weik0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1107&quot; height=&quot;437&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 자료를 찾고 다시 생각해보니 당연한 것이었습니다.&lt;br /&gt;&lt;b&gt;docker 는 컨테이너 단위로 동작함&lt;/b&gt;으로 port 만으로는 접근이 불가능합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 host 의 ip 에 접근할 수 있는 방법이 있어 설정을 바꾸니 정상 접근되었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664987227160&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# licensing-service 
# application.yml
eureka:
  instance:
    appname: licensing-service
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      # defaultZone: http://localhost:8761/eureka/ # localhost 기동시
      defaultZone: http://host.docker.internal:8761/eureka/ # docker 기동시&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;환경 변수 관리 by Dockerfile&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;책에서는 Spring Cloud Config 의 장점을 말합니다.&lt;br /&gt;하지만 저는 강한 필요성을 느끼지 못했기에 Dockerfile 로 접근하였습니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스와 config 를 분리할 수 있다.&lt;/h3&gt;
&lt;pre id=&quot;code_1664989264149&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# application.yml
server:
  port: ${SERVER_PORT}
  

# Dockerfile
FROM openjdk:17-jdk-slim-buster
ENV SERVER_PORT=8080 # 환경 변수 주입
ARG JAR_FILE=/licensing-service/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 가장 좋아하는 방식으로 Dockerfile 을 활용하여 환경 변수를 관리합니다.&lt;br /&gt;dev, stg, prod 별로 &lt;span&gt;Dockerfile 을 &lt;/span&gt;관리할 수도 있고, 작은 프로젝트에서는 서비스 안에 둘 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소에는 설정 정보만 모아두는 git repository 를 운영합니다.&lt;br /&gt;그리고 배포 스크립트 작성 시 (git action) 서비스에 해당하는 Dockerfile 을 가져와 실행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lmFDr/btrNSLIkJ6U/Tfm4iZW976c18AqPNFN7l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lmFDr/btrNSLIkJ6U/Tfm4iZW976c18AqPNFN7l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lmFDr/btrNSLIkJ6U/Tfm4iZW976c18AqPNFN7l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlmFDr%2FbtrNSLIkJ6U%2FTfm4iZW976c18AqPNFN7l0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;361&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 사이드 프로젝트할 때는 배포에 elastic beanstalk 를 활용합니다.&amp;nbsp;&lt;br /&gt;elastic beanstalk 을 활용하면 AWS 에서 안전하게 환경 변수를 관리할 수 있어 참 편했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용해보지는 않았지만 AWS Systems Manager, Secrets Manager 역시 관련된 솔루션으로 알고 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스 인터페이스로의 접근 방식을 추상화할 수 있다.&lt;/h3&gt;
&lt;pre id=&quot;code_1664989730754&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# application.yml
spring:
  datasource:
    url: ${JDBC_URL:jdbc:h2:mem:testdb}
    driverClassName: org.h2.Driver
    username: ${JDBC_USERNAME:sa}
    password: ${JDBC_PASSWORD:}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것 역시 Dockerfile 을 통해 관리할 수 있습니다.&lt;br /&gt;기본 값을 활용하면 local 기동시 편하고 안전해 유용하게 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;config 를 중앙 관리할 수 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 git repository 에 &lt;span&gt;Dockerfile 을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;관리하는 방법으로 대체할 수 있습니다.&lt;br /&gt;이력 관리도 돼서 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;config 를 변경 감지할 수 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개인적인 경험으로 볼 때 config 가 바뀌면 코드의 변경은 필연적이었습니다.&amp;nbsp;&lt;br /&gt;(네트워크 설정이 바뀔 때는 코드의 변경이 없어도 되지만, 그런 경우는 AWS 의 영역이었습니다.)&lt;br /&gt;그렇기 때문에 변경 감지가 저에게는 그다지 유용한 기능으로 와닿지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 Phase 를 진행하면서 전 회사 생각이 많이 났습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;전 회사에서는 docker 를 적극적으로 쓰긴 했지만, 대부분 팀장님이 세팅한 것을 받아쓰기만 했었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번에 docker 를 세팅해보면서 &lt;b&gt;이렇게 좋고 편한 걸 왜 그동안 안쓴거지?&lt;/b&gt; 라는 생각이 많이 들었습니다.&lt;br /&gt;이미 현대의 배포는 모두 수평 확장을 기반하고 있고 컨테이너 기반으로 움직이기에 유용할 것 같습니다.&lt;br /&gt;좀 더 잘 쓸 수 있도록 공부해야겠습니다!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;config 에 대해서도 그렇습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile 로 관리되긴 했지만 서비스와 같은 repository 에 있었기에 보안 지적을 받았기에, s3 로 이관하고 배포 스크립트에서 가져와 활용하는 것을 지원했었습니다.&lt;br /&gt;그때도 말한 내용이지만 &lt;b&gt;s3 로 이관되면 이력 관리도 힘들어지고, 어차피 private repository 가 털린 상황이면 s3 도 위험하긴 마찬가지다.&lt;/b&gt; 라고 생각합니다. 별도의 git repository 로 이관하는 것 까지도 말했지만.. 결국은 s3 로 결정된 슬픈 기억이 떠올랐습니다. &lt;s&gt;보안은 참 어렵네요.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring cloud config 가 좋은 기술인 건 분명하지만, 아직 저에게는 와닿지 않고 있습니다.&lt;br /&gt;복잡도가 높아져 꼭 도입해야만 하는 시점이 오면 좋겠다는 생각이 들었습니다.&lt;/p&gt;</description>
      <category>Series/스프링 마이크로서비스 코딩 공작소</category>
      <category>Docker Compose</category>
      <category>dockerfile</category>
      <category>environment</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/54</guid>
      <comments>https://hyune-c.tistory.com/54#entry54comment</comments>
      <pubDate>Thu, 6 Oct 2022 02:38:44 +0900</pubDate>
    </item>
    <item>
      <title>스프링 마이크로서비스 코딩 공작소 (1)</title>
      <link>https://hyune-c.tistory.com/53</link>
      <description>&lt;figure id=&quot;og_1664981574780&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-s-lab/optima-growth: 스프링 마이크로서비스 코딩 공작소&quot; data-og-description=&quot;스프링 마이크로서비스 코딩 공작소. Contribute to Hyune-s-lab/optima-growth development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-s-lab/optima-growth&quot; data-og-url=&quot;https://github.com/Hyune-s-lab/optima-growth&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8fUqN/hyPZbR01P7/cyWCpkFtCTRrHfVksKinl0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-s-lab/optima-growth&quot; data-source-url=&quot;https://github.com/Hyune-s-lab/optima-growth&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8fUqN/hyPZbR01P7/cyWCpkFtCTRrHfVksKinl0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-s-lab/optima-growth: 스프링 마이크로서비스 코딩 공작소&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스프링 마이크로서비스 코딩 공작소. Contribute to Hyune-s-lab/optima-growth development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Phase 1&amp;nbsp;&lt;/b&gt;&lt;br /&gt;3. 스프링 부트로 마이크로서비스 구축하기&lt;br /&gt;4. 도커&lt;br /&gt;&lt;b&gt;이 글입니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Phase 2&lt;/b&gt;&lt;br /&gt;5. 스프링 클라우드 컨피그 서버로 구성 관리&lt;br /&gt;6. 서비스 디스커버리&lt;br /&gt;&lt;a href=&quot;https://hyune-c.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hyune-c.tistory.com/54&lt;/a&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 입사한 회사는 제가 생각한 것보다 훨씬 서비스 구분이 많았습니다.&lt;br /&gt;솔직히 &lt;b&gt;이렇게까지 세분화 해야되나?&lt;/b&gt; 라는 생각이 들 정도였습니다.&lt;br /&gt;마이크로서비스는 잘 쓰면 높은 생산성과 장애 격리를 가져갈 수 있지만, 높은 설계/운영 난이도와 흐름 추적이 어렵기 때문입니다.&lt;br /&gt;이 문제 때문에 전 회사에서는 api, batch 딱 2개의 서버만 운영하기도 했습니다.&lt;br /&gt;그리고 굳이 바꾸려고 하지도 않았습니다. 스테이지와 어울리는 아키텍처였기 때문이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 전부터 공부하고 싶었던 마이크로서비스이기에 책을 한 권 따라가 보면서 기록해봅니다.&lt;br /&gt;책에서는 java 11 과 maven 을 사용하지만, &lt;b&gt;이왕이면 최신으로! kotlin 과 gradle 로 따라가 봅니다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 시리즈는 책일 읽어가며 조금씩 작성됩니다.&lt;br /&gt;어쩌면 책을 다 읽는 시점에는 지금의 생각과 달라질 수도 있겠네요. ㅎㅎ&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 배경 지식은 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 마이크로서비스의 이론만 조금 알고 있다.&lt;br /&gt;- &lt;s&gt;간소화된&lt;/s&gt; Saga Orchestration 을 구현해봤다.&lt;br /&gt;- kotlin 연습만 해봤다. 비즈니스 개발 X&lt;br /&gt;- 이미 구성된 Docker 환경을 사용/운영만 해봤다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;licensing-service 구현&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이센스 발급, 수정, 삭제를 위한 REST API 를 만들었습니다.&lt;br /&gt;하지만 책과는 달리 국제화도 적용하지 않았고, 너무 쉽기에 따로 설명하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;docker-compose &amp;amp; boot script&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;알면 정말 쉽고 유용하지만, 모르면 손도 못 대는 docker 입니다.&lt;br /&gt;전 회사에서는 팀장님이 구성한 세팅을 사용만 했지만 이제는 조금씩 알려고 합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dockerfile&lt;/h3&gt;
&lt;pre id=&quot;code_1664640299590&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# java 17을 사용합니다.
FROM openjdk:17-jdk-slim-buster
# build 를 통해 생성되는 jar 파일 경로를 JAR_FILE 변수를 지정합니다. 
ARG JAR_FILE=licensing-service/build/libs/*.jar
# jar 파일을 복사해 옵니다.
COPY ${JAR_FILE} app.jar
# 실행 합니다.
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker 는 image 기반으로 작동합니다.&amp;nbsp;&lt;br /&gt;Dockerfile 은 이 image 를 만들기 위한 환경 파일이라고 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker-compose.yml&lt;/h3&gt;
&lt;pre id=&quot;code_1664640554937&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3&quot;
services:
  license-service:
    image: license-service
    ports:
      - &quot;8080:8080&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 설정을 매번 스크립트로 칠 필요 없이 yml 로 설정할 수 있습니다. &lt;br /&gt;특히&amp;nbsp;&lt;span&gt;여러 개의 서비스를 띄울 때 유용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;run.sh&lt;/h3&gt;
&lt;pre id=&quot;code_1664640452736&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# licensing-service
cd licensing-service
./gradlew bootjar
cd ..

docker build . --tag license-service
docker-compose up&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker 를 띄우는 일련의 과정을 편하게 하기 위한 script 입니다.&lt;br /&gt;git action 등을 사용할 때 활용하면 편합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;./run.sh&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1759&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGfgwA/btrNyFIBDWO/z84u1MKSiXOt6gaYKPSpX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGfgwA/btrNyFIBDWO/z84u1MKSiXOt6gaYKPSpX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGfgwA/btrNyFIBDWO/z84u1MKSiXOt6gaYKPSpX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGfgwA%2FbtrNyFIBDWO%2Fz84u1MKSiXOt6gaYKPSpX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;477&quot; data-origin-width=&quot;1759&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겨우 하나의 command 로 빌드부터 부팅에 성공했습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sMgMA/btrNyEW76q2/xRhtAu23XKFlUrfxxqMXeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sMgMA/btrNyEW76q2/xRhtAu23XKFlUrfxxqMXeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sMgMA/btrNyEW76q2/xRhtAu23XKFlUrfxxqMXeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsMgMA%2FbtrNyEW76q2%2FxRhtAu23XKFlUrfxxqMXeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;73&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 image 를 보고 싶다면 확인해볼 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;서비스 라우팅 &amp;amp; 서비스 디스커버리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 마이크로서비스를 접했을 때, 가장 크게 든 의문은 2개였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;내부 서비스 간에 어떻게 인증/인가를 하는 거지?&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스간 통신이 필요한 유스 케이스는 어떻게 처리하지?&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 실무를 하고 공부를 하면서 조금은 답이 생겼기에, 책의 내용과 함께 좀 더 얘기해보려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;1097&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt9oev/btrNwkqvxvB/TliGBas435v4SouYLk3MMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt9oev/btrNwkqvxvB/TliGBas435v4SouYLk3MMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt9oev/btrNwkqvxvB/TliGBas435v4SouYLk3MMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt9oev%2FbtrNwkqvxvB%2FTliGBas435v4SouYLk3MMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;359&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;1097&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스 라우팅 (API 게이트웨이)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리식 아키텍처에서는 접근 포인트가 단일 서버이기에 라우팅의 필요성이 낮았습니다.&lt;br /&gt;과거에는 웹서버가 분리되기도 했지만, 현대에는 AWS 에서 ALB, Shield 등의 솔루션을 제공해주면서 분리된 이유도 있습니다.&lt;br /&gt;하지만 &lt;b&gt;마이크로서비스에서의 API 게이트웨이는 한 단계 더 나아갑니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 그림에는 인증에 대한 영역만 있지만, 전 회사에서는 SSO 를 고려한 인가까지를 구상했었습니다.&lt;br /&gt;물론 인가까지 고려한다면 &lt;b&gt;화면번호나 도메인 등의 &lt;/b&gt;&lt;span&gt;&lt;b&gt;서비스 디펜던시&lt;/b&gt;가 생길 수 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;span&gt;하지만 각 서비스에서는 인가에 대한 부담을 덜 수 있어 더 좋은 아키텍처라고 생각합니다. (AWS IAM)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;혹자는 서비스 간의 통신 시간을 고민합니다.&lt;br /&gt;하지만 &lt;b&gt;API 게이트웨이 이후는 private VPC 로 묶고 gRPC 등의 통신방식 변경&lt;/b&gt;으로 해결할 수 있다고 생각합니다.&lt;br /&gt;현대의 개발은 더 많은 개발자가 더 많은 서비스를 병렬적으로 개발하는 것이 더 중요하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;마지막으로 API 게이트웨이는 필연적으로 이 시스템에서 가장 많은 트래픽을 받게 되는 서비스입니다.&lt;br /&gt;따라서 이벤트와 같이 일회성이거나 순간적 트랙픽이 몰리는 서비스는 분리하는 것도 좋다고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;서비스 디스커버리 (유레카)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이미 인증/인가가 끝난 요청을 포팅만 하는 쉬운 서비스라고 볼 수도 있지만, 헬스 체크와 서킷 브레이커의 아이디어가 필요할 것 같습니다. 그리고 비교적 자주 바뀌고 추가되는 개념인&amp;nbsp;&lt;span&gt;end-point 를 유기적으로 관리할 수 있는 방법도 궁금해졌습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;마치며&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;첫 번째 글이라서 조금 짧습니다.&lt;br /&gt;그래서 쓸까 말까 고민했지만, 하나의 큰 글보다 여러 개의 &lt;span&gt;작은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;글이 더 가치 있다고 생각하는 요즘입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;그리고 아키텍처를 고민하는 것은 항상 재미있다고 느끼는 요즘입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Series/스프링 마이크로서비스 코딩 공작소</category>
      <category>API 게이트웨이</category>
      <category>마이크로서비스</category>
      <category>서비스 디스커버리</category>
      <category>스프링 마이크로서비스 코딩 공작소</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/53</guid>
      <comments>https://hyune-c.tistory.com/53#entry53comment</comments>
      <pubDate>Sun, 2 Oct 2022 01:54:45 +0900</pubDate>
    </item>
    <item>
      <title>[트러블 슈팅] ECS 클러스터 안의 서비스간 네트워크 연결 불가</title>
      <link>https://hyune-c.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;글을 시작하기 전에 짧게 배경을 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 인프라 지식과 경험은 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- [On-premise] 서버 이중화 구성 &amp;amp; 운영 경험 O&lt;br /&gt;- [AWS] EC2 기반의 Elastic Beanstalk 구성 &amp;amp; 운영 경험 O&lt;br /&gt;- [AWS] ECS 운영 경험 O EKS X&lt;br /&gt;- [AWS] VPC, 보안그룹, 인바운드 규칙에 대한 이해&lt;br /&gt;- Docker 에 대한 이해&lt;br /&gt;- MSA 개발에 대한 이해 O MSA 인프라에 대한 이해 X&lt;br /&gt;- 그리고 이 정도로 거대한 규모의 인프라는 처음 만져봄&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;저는 9/1 일 신생 PG 회사로 이직하면서 모기업의&amp;nbsp;&lt;/span&gt;승인&amp;amp;정산 업무를 배정받았고, 이전 담당자는 퇴사가 예정되어 있었습니다. &lt;s&gt;이 글을 쓰는 시점에 이미 퇴사하신&lt;/s&gt;&lt;br /&gt;승인/정산은 어느 회사에서나 가장 민감한 도메인 중 하나이고, 그 중에서도 &lt;span&gt;본사의 트래픽은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;업계 탑 이었습니다.&lt;br /&gt;하지만 외주에 &lt;span&gt;개발을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;의존하고 있었고, &lt;b&gt;전임자는 팀장급의 도메인 지식에 보안 업무까지 할 정도로 인프라 실력자이신 분&lt;/b&gt;이었기에 상당한 부담이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이슈의 시작&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;본사의 정산 시스템 어드민을 통해 정산 마감한 데이터가 필자 회사의 정산 시스템의 어드민에서는 보이지 않는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 CS 가 들어왔을 때는 &lt;b&gt;어디 서버를 말하는 것인지도 모르겠고, 내가 AWS 권한이 있긴 한가?&lt;/b&gt; 조차 몰랐습니다.&lt;br /&gt;저는 이럴 때 일단 업무 흐름도를 그립니다. 이하의 글에서는 접두사로 시스템을 구분합니다. &lt;br /&gt;&lt;b&gt;본사 정산 시스템 settlement /&lt;/b&gt;&amp;nbsp;&lt;b&gt;필자 회사의 정산 시스템 ksettlement&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cebylk/btrMVdq63q8/c4xTA9HaGesj3Bff8myRg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cebylk/btrMVdq63q8/c4xTA9HaGesj3Bff8myRg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cebylk/btrMVdq63q8/c4xTA9HaGesj3Bff8myRg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcebylk%2FbtrMVdq63q8%2Fc4xTA9HaGesj3Bff8myRg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;202&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6ozSa/btrMVtm2B3U/lEbwucxtfTJaIEtZqYnW81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6ozSa/btrMVtm2B3U/lEbwucxtfTJaIEtZqYnW81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6ozSa/btrMVtm2B3U/lEbwucxtfTJaIEtZqYnW81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6ozSa%2FbtrMVtm2B3U%2FlEbwucxtfTJaIEtZqYnW81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1053&quot; height=&quot;591&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 그리고나니 확인할 포인트가 보이기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;settlement-scheduler 에서 ksettlement-interface 로 signal 이 전송되었는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본사의 정산 마감 처리는 성공 했습니다. 따라서 db 기록은 남았을 것이라고 생각했고 정상이었습니다.&lt;br /&gt;서버의 전송 기록을 확인해야되는데 아쉽게도 datadog 과 같은 모니터링 툴이 설치되어 있지 않아 cloudwatch 로 봐야 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4096&quot; data-origin-height=&quot;1201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7cpY1/btrMU500Nuw/CbfMakNE4S0Y7dPZBLyLQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7cpY1/btrMU500Nuw/CbfMakNE4S0Y7dPZBLyLQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7cpY1/btrMU500Nuw/CbfMakNE4S0Y7dPZBLyLQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7cpY1%2FbtrMU500Nuw%2FCbfMakNE4S0Y7dPZBLyLQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4096&quot; height=&quot;1201&quot; data-origin-width=&quot;4096&quot; data-origin-height=&quot;1201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 소스 분석을 통해 로깅을 남기는 부분을 확인하고, &lt;b&gt;settlement-scheduler 에서 signal 전송 성공 로그&lt;/b&gt;를 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ksettlement-interface 에서 settlement-interface 를 통해 데이터를 가져왔는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3730&quot; data-origin-height=&quot;1416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8mcHu/btrMU6y4S1r/SUl6KVzjKl8ODbBvAIEEyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8mcHu/btrMU6y4S1r/SUl6KVzjKl8ODbBvAIEEyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8mcHu/btrMU6y4S1r/SUl6KVzjKl8ODbBvAIEEyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8mcHu%2FbtrMU6y4S1r%2FSUl6KVzjKl8ODbBvAIEEyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3730&quot; height=&quot;1416&quot; data-origin-width=&quot;3730&quot; data-origin-height=&quot;1416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패 로그를 찾았습니다! 이것을 통해 문제를 추정할 수 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ksettlement-interface 와 settlement-interface 구간에 문제가 있다.&lt;/li&gt;
&lt;li&gt;로깅 간격이 매우 짧은 것을 볼 때 네트워크 이슈가 의심된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이상한 점도 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;settlement-scheduler 는 왜 성공으로 기록한거지? &lt;br /&gt;settlement-interface 로 전달했다는 것만으로 역할을 다 한 것인가?&lt;/li&gt;
&lt;li&gt;그렇다면 settlement-interface 는 왜 재시도를 하지 않은거지?&amp;nbsp;&lt;/li&gt;
&lt;li&gt;choreography 패턴이라고 보기에는 예외 처리/복구 전략이 너무 빈약하지 않나..?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 확인 과정에서 알게 되었지만 개발단의 버그가 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 이슈는 특정 에러코드로 기록하고, 재시도 대상에서 제외합니다. &lt;br /&gt;&lt;s&gt;저는 네트워크 이슈도 재시도 해야된다고 생각합니다.&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;실패한 트랜잭션임에도 불구하고 settlement-&lt;span&gt;scheduler 는 정상 건으로&lt;/span&gt;&amp;nbsp;기록하는 버그가 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;어쨋든 네트워크 이슈도 확인해야 될 것 같습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4096&quot; data-origin-height=&quot;1178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XEDz4/btrMUF9ulG0/iupsV4RVeks7ZOc3MnQqNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XEDz4/btrMUF9ulG0/iupsV4RVeks7ZOc3MnQqNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XEDz4/btrMUF9ulG0/iupsV4RVeks7ZOc3MnQqNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXEDz4%2FbtrMUF9ulG0%2FiupsV4RVeks7ZOc3MnQqNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4096&quot; height=&quot;1178&quot; data-origin-width=&quot;4096&quot; data-origin-height=&quot;1178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안을 위해 많은 정보를 가렸지만 &lt;b&gt;컨테이너 이름이 다르고, VPC/서브넷/보안 그룹은 모두 같음&lt;/b&gt;을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 여기까지 보고 &lt;b&gt;네트워크도 문제 없네&lt;/b&gt; 라고 생각했습니다.&lt;br /&gt;하지만 ECS 와 Fargate 에 대한 이해가 부족하니 확신이 없어 좀 더 찾아보고 있었습니다.&lt;br /&gt;그러던 중 운 좋게도 오늘부터 &lt;b&gt;메가존 엔지니어 분들이 상주 지원을 한다는 사실을 떠올리고 바로 물어보러&lt;/b&gt; 갔습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서로 다른 서비스는 서로 다른 fargate 에 올라가 있는 것이다. &lt;br /&gt;따라서 보안그룹에서 자기 자신을 호출하는 인바운드 규칙이 있어야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 같은 경우가 종종 있는지 바로 눈치를 채시더라구요..!&lt;br /&gt;다시 확인해보니 8080 의 인바운드 규칙이 있긴 하지만 211 대역인 것을 보면 테스트를 위해 임시 등록한 것 같습니다.&lt;br /&gt;&lt;s&gt;분명히 이 부분도 봤다고 생각했는데 포트만 보고 간과했습니다.. ㅠ&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3900&quot; data-origin-height=&quot;1144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQaXX8/btrMUTsRWsP/prW7417zLCX0qBJKzi1cJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQaXX8/btrMUTsRWsP/prW7417zLCX0qBJKzi1cJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQaXX8/btrMUTsRWsP/prW7417zLCX0qBJKzi1cJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQaXX8%2FbtrMUTsRWsP%2FprW7417zLCX0qBJKzi1cJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3900&quot; height=&quot;1144&quot; data-origin-width=&quot;3900&quot; data-origin-height=&quot;1144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안그룹에 8080 포트를 인바운드 규칙으로 추가하고 이슈를 해결했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2vRFP/btrMTLXbZft/Gr0iBM9JhMFi0Msoqr9Ep1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2vRFP/btrMTLXbZft/Gr0iBM9JhMFi0Msoqr9Ep1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2vRFP/btrMTLXbZft/Gr0iBM9JhMFi0Msoqr9Ep1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2vRFP%2FbtrMTLXbZft%2FGr0iBM9JhMFi0Msoqr9Ep1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;278&quot; height=&quot;454&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간이 많이 생략되었지만 처음에는 prod 환경이기에 네트워크를 의심하지 않았고, 완전히 다른 흐름을 보는 실수를 하기도 했습니다. ECS 지식이 부족했기에 중요한 포인트를 찾지 못해 헤맨 것도 있구요.&lt;br /&gt;해결하고 나니 창피할 정도로 쉽고 당연한 것이었네요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기성 대기업이었다면 담당자가 따로 있었고 관련 확인이 완전히 위임 됩니다.&lt;br /&gt;업무의 분리와 집중이라는 관점에서는 좋지만, 상대적으로 느리고 전체 업무 흐름에 대한 이해도가 떨어지게 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 제가 다니는 회사는 트래픽은 업계 상위권이지만 스타트업의 성격으로 일하고 있습니다.&lt;br /&gt;그렇기에 구성원에게도 AWS 권한이 있고, 다행히도 인프라 기본 지식이 있었기에 직접 트러블 슈팅할 수 있었습니다.&lt;br /&gt;들어온지 얼마 안되었음에도 이렇게 전체 흐름에 관련된 이슈를 처리할 수 있어서 보람차고 꽤 재미있는 경험이었습니다!&lt;br /&gt;&lt;s&gt;devops 님.. 어서 와주세요.. 제발..&lt;/s&gt;&lt;/p&gt;</description>
      <category>Series/실전!</category>
      <category>ECS</category>
      <category>fargate</category>
      <category>Security Group</category>
      <category>보안그룹</category>
      <category>인바운드 규칙</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/52</guid>
      <comments>https://hyune-c.tistory.com/52#entry52comment</comments>
      <pubDate>Sat, 24 Sep 2022 12:57:57 +0900</pubDate>
    </item>
    <item>
      <title>요즘 애들은 문해력이 낮아진걸까?</title>
      <link>https://hyune-c.tistory.com/51</link>
      <description>&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=X4KM96sxr24&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cP2R6m/hyPILlBsmI/Knk4DdS5xVkJwjtOo0JSgk/img.jpg?width=480&amp;amp;height=360&amp;amp;face=64_100_396_152&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/X4KM96sxr24&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;09:42 ~ 13:00&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;소위 요즘 학생들이 &lt;b&gt;단어를 모른다, 문해력이 떨어진다&lt;/b&gt;라고 말하고 이것이 큰 문제인 것으로 알려지고 있다.&lt;br /&gt;하지만 사실은 영상 독해력이 높아지는 등 &lt;b&gt;이전 세대와 다른 양상의 패러다임이 생기고&lt;/b&gt; 있는 것이다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술/업무와는 전혀 관계 없는 영상처럼 보이지만 느낀 것이 많습니다.&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시대는&amp;nbsp;지속적으로&amp;nbsp;바뀌어&amp;nbsp;가고&amp;nbsp;우리가&amp;nbsp;만드는&amp;nbsp;서비스를&amp;nbsp;소비하는&amp;nbsp;층은&amp;nbsp;점점&amp;nbsp;어리고&amp;nbsp;새로워져&amp;nbsp;갑니다.&amp;nbsp;&lt;br /&gt;하지만 우리는 비즈니스를 하는 사람들이기에 너무나도 어렵지만 패러다임의 변화를 캐치할 수 있어야 하고,&lt;br /&gt;최소한&amp;nbsp;변화를&amp;nbsp;인정하고&amp;nbsp;수용하는&amp;nbsp;자세를&amp;nbsp;가져야&amp;nbsp;된다고&amp;nbsp;생각합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잘못된 분석의 사례&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1199&quot; data-origin-height=&quot;893&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcqMDu/btrLLxd1lph/o4cXQzCCLHClDnSjNnoD3k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcqMDu/btrLLxd1lph/o4cXQzCCLHClDnSjNnoD3k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcqMDu/btrLLxd1lph/o4cXQzCCLHClDnSjNnoD3k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcqMDu%2FbtrLLxd1lph%2Fo4cXQzCCLHClDnSjNnoD3k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;343&quot; data-origin-width=&quot;1199&quot; data-origin-height=&quot;893&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;2차 대전 중, 미 해군분석센터 연구원들은 임무를 마치고 돌아온 전투기들의 총탄 자국에 대해 연구하였다. &lt;br /&gt;그 연구 결과는, 비행기의 총탄 자국이 가장 많은 부분을 더 튼튼하게 보강하여, 적의 공격을 받더라도 전투기의 손실을 최소화할 것을 권유하는 보고서를 남겼다. &lt;br /&gt;&lt;br /&gt;그런데 수학자로서 통계연구팀에 속해있던 에이브러햄 왈드(Abrahan Wald)는 전혀 다른 의견을 제시했다. &lt;br /&gt;그는 해군분석센터의 연구가 살아 돌아온 전투기만을 대상으로 하였다는 점을 지적했다. &lt;br /&gt;격추된 전투기는 돌아올 수 없으니, 외상 부위를 평가할 때 반영되지 못했다는 것이다. &lt;br /&gt;&lt;br /&gt;당초의 보고서에 따르면 총탄을 많이 맞은 부분을 보강하여야 한다. &lt;br /&gt;그러나, 실제로는 전투기가 그렇게 총탄을 많이 맞았음에도 무사히 돌아올 수 있었으므로, 해당 부분에 대해서는 보강의 필요가 없다. 오히려 귀환한 비행기의 총탄 맞지 않은 부분이 보강의 필요성이 더 크다. &lt;br /&gt;그 부분을 타격 당한 전투기는 돌아오지 못했기 때문이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;생존자 편향의 오류 Survivorship Bias&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 분석군의 선정으로 인해 문제의 본질을 파악하지 못하는 오류를 말하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1662737019012&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;직관만 믿고 까불었다가 망한 PM의 사연&quot; data-og-description=&quot;안녕하세요, 검색 프로덕트 매니저 Demi예요.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/daangn/%EC%A7%81%EA%B4%80%EB%A7%8C-%EB%AF%BF%EA%B3%A0-%EA%B9%8C%EB%B6%88%EC%97%88%EB%8B%A4%EA%B0%80-%EB%A7%9D%ED%95%9C-pm%EC%9D%98-%EC%82%AC%EC%97%B0-61946dc969eb&quot; data-og-url=&quot;https://medium.com/daangn/%EC%A7%81%EA%B4%80%EB%A7%8C-%EB%AF%BF%EA%B3%A0-%EA%B9%8C%EB%B6%88%EC%97%88%EB%8B%A4%EA%B0%80-%EB%A7%9D%ED%95%9C-pm%EC%9D%98-%EC%82%AC%EC%97%B0-61946dc969eb&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/U4cl2/hyPJ3LSQJE/x6aoQsJ286aIScVytaVtYk/img.png?width=1200&amp;amp;height=1000&amp;amp;face=0_0_1200_1000,https://scrap.kakaocdn.net/dn/bVj9ex/hyPJ8sSBUt/3fNltmacx5YKe9bzpbR09K/img.png?width=1400&amp;amp;height=1167&amp;amp;face=0_0_1400_1167,https://scrap.kakaocdn.net/dn/bt3CPy/hyPJSKlmy8/nOSjupn182ApuF3dQNKg9k/img.png?width=1400&amp;amp;height=772&amp;amp;face=0_0_1400_772&quot;&gt;&lt;a href=&quot;https://medium.com/daangn/%EC%A7%81%EA%B4%80%EB%A7%8C-%EB%AF%BF%EA%B3%A0-%EA%B9%8C%EB%B6%88%EC%97%88%EB%8B%A4%EA%B0%80-%EB%A7%9D%ED%95%9C-pm%EC%9D%98-%EC%82%AC%EC%97%B0-61946dc969eb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/daangn/%EC%A7%81%EA%B4%80%EB%A7%8C-%EB%AF%BF%EA%B3%A0-%EA%B9%8C%EB%B6%88%EC%97%88%EB%8B%A4%EA%B0%80-%EB%A7%9D%ED%95%9C-pm%EC%9D%98-%EC%82%AC%EC%97%B0-61946dc969eb&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/U4cl2/hyPJ3LSQJE/x6aoQsJ286aIScVytaVtYk/img.png?width=1200&amp;amp;height=1000&amp;amp;face=0_0_1200_1000,https://scrap.kakaocdn.net/dn/bVj9ex/hyPJ8sSBUt/3fNltmacx5YKe9bzpbR09K/img.png?width=1400&amp;amp;height=1167&amp;amp;face=0_0_1400_1167,https://scrap.kakaocdn.net/dn/bt3CPy/hyPJSKlmy8/nOSjupn182ApuF3dQNKg9k/img.png?width=1400&amp;amp;height=772&amp;amp;face=0_0_1400_772');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;직관만 믿고 까불었다가 망한 PM의 사연&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 검색 프로덕트 매니저 Demi예요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 경험과 데이터에 기반하지 못한 직관은 위험합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요즘 핫한 회사들은 소위 PO 라는 말을 많이 사용합니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PO&amp;nbsp;라&amp;nbsp;함은&amp;nbsp;자신이&amp;nbsp;속한&amp;nbsp;프로덕트에&amp;nbsp;무한한&amp;nbsp;책임감을&amp;nbsp;가지고&amp;nbsp;일해야&amp;nbsp;됩니다.&amp;nbsp;&lt;br /&gt;그리고 그 안에서 남들과는 다른 혁신을 가져올 수 있기를 기대합니다.&lt;br /&gt;과거에는 이것들이 기획자에게만 요구되었다면 지금은 개발자에게도 요구되고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;말이 좋아 혁신이지 정말정말 어려운 일입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌아보면 실무에서도 단편적인 결과만 보고 실수한 적이 많습니다. 많은 반성이 됩니다.&lt;br /&gt;올바른 데이터와 선정과 문제의 본질을 볼 수 있는 능력을 길러야 되겠습니다.&lt;/p&gt;</description>
      <category>잡담 &amp;amp; ETC</category>
      <category>생존자 편향의 오류</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/51</guid>
      <comments>https://hyune-c.tistory.com/51#entry51comment</comments>
      <pubDate>Sat, 10 Sep 2022 00:31:17 +0900</pubDate>
    </item>
    <item>
      <title>Repository 가볍게 관리하기</title>
      <link>https://hyune-c.tistory.com/50</link>
      <description>&lt;figure id=&quot;og_1659201459252&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-c/blogcode-repository-di: Repository 의존성을 가볍게 관리하기&quot; data-og-description=&quot;Repository 의존성을 가볍게 관리하기. Contribute to Hyune-c/blogcode-repository-di development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-c/blogcode-repository-di&quot; data-og-url=&quot;https://github.com/Hyune-c/blogcode-repository-di&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pmKBE/hyPf3fk2EQ/ZlDhhfi1Ef9A87WBWEc0hk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/blogcode-repository-di&quot; data-source-url=&quot;https://github.com/Hyune-c/blogcode-repository-di&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pmKBE/hyPf3fk2EQ/ZlDhhfi1Ef9A87WBWEc0hk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-c/blogcode-repository-di: Repository 의존성을 가볍게 관리하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Repository 의존성을 가볍게 관리하기. Contribute to Hyune-c/blogcode-repository-di development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작고 단순한 피쳐를 개발할 때는 크게 신경 쓰지 않던 것들이 공부하고 &lt;span&gt;실무를 &lt;/span&gt;할수록 의문이 생기곤 합니다.&lt;br /&gt;그중 하나인 Repository를 개발하면서 느낀 점을 기록해봅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 로직과 영속성 영역의 경계는 어디일까?&amp;nbsp;&lt;/li&gt;
&lt;li&gt;어떻게 하면 인적 실수를 줄이고 생산성을 높일 수 있을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지금의 회사에서 개발하는 애플리케이션은&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B2B 성격이기에 그리드 형태의 조회 로직이 많습니다.&lt;br /&gt;그렇기에 개발 편의성을 위해 포기한 부분이 있고, 그중 하나가 &lt;b&gt;Repository의 거대함&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 로직이 영속성 영역에 위치하고, 거대하고 복잡한 쿼리로 구현되어 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;4천 줄 이상의 클래스도 존재하며 로직 이해가 힘듭니다.&lt;/li&gt;
&lt;li&gt;DB의 용어를 그대로 사용해 가독성이 떨어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v1 - 기존의 실무에서 사용되던 방법&lt;/h2&gt;
&lt;pre id=&quot;code_1659203039821&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface V1StudentRepository extends
		JpaRepository&amp;lt;Student, Long&amp;gt;,
		V1StudentQuerydslRepository,
		V1StudentJooqRepository {

	// 이름으로 조회하는 경우 JpaRepository를 활용합니다.
	Student getByName(final String name);
}

interface V1StudentJooqRepository {

	// 이름과 나이로 조회하는 경우 Jooq를 활용합니다.
	Student getByNameAndAge(String name, Integer age);
}

interface V1StudentQuerydslRepository {

	// 나이와 주소로 조회하는 경우 Querydsl을 활용합니다.
	Student getByAgeAndAdress(Integer age, String adress);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쉬운&amp;nbsp;기능은&amp;nbsp;JpaRepository를&amp;nbsp;통해&amp;nbsp;생성되는&amp;nbsp;메서드를&amp;nbsp;활용합니다.&lt;/li&gt;
&lt;li&gt;일부&amp;nbsp;복잡한&amp;nbsp;쿼리는&amp;nbsp;Jooq&amp;nbsp;or&amp;nbsp;Querydsl을&amp;nbsp;활용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v2 - 복잡한 조회만 하고 싶습니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 조회 쿼리만 필요한 경우에는 Jooq와 Querydsl만 활용하고 싶습니다. &lt;br /&gt;JpaRepository를 활용하면 사용되지 않는 CRUD 메서드가 다수 생성되기 때문입니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface V2StudentRepository extends
		V2StudentQuerydslRepository,
		V2StudentJooqRepository {

}

interface V2StudentJooqRepository { ... }
interface V2StudentQuerydslRepository { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.blogcoderepositorydi.v2.repository.V2StudentRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JpaRepository를 빼면 되겠지라고 &lt;span&gt;단순하게&amp;nbsp;&lt;/span&gt;접근했지만, v2는 작동하지 않습니다.&lt;br /&gt;V2StudentRepository는 interface이기 때문에 &lt;b&gt;DI를 할 수 있는 구현체가 존재하지 않기 때문&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1659203462725&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Data JPA 는 어떻게 interface 만으로도 동작할까? (feat. reflection, proxy)&quot; data-og-description=&quot;Spring Data JPA를 공부하면서 궁금한 것이 있었습니다. public interface MemberRepository extends JpaRepository { List findAllByName(String name); } 위와 같이 MemberRepository는 인터페이스고, @Reposito..&quot; data-og-host=&quot;pingpongdev.tistory.com&quot; data-og-source-url=&quot;https://pingpongdev.tistory.com/25&quot; data-og-url=&quot;https://pingpongdev.tistory.com/25&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4Kf9D/hyPgar0Bn1/Y8JSJ6K20SknV6xHiN7SzK/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/rBMOT/hyPf1hxZ6h/8aUYsiO2x6PYZ6FaykCj4K/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/ckAPBG/hyPgcQUMXE/lQrWjAG3JK3q7vElCCiTW1/img.png?width=1317&amp;amp;height=826&amp;amp;face=0_0_1317_826&quot;&gt;&lt;a href=&quot;https://pingpongdev.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pingpongdev.tistory.com/25&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4Kf9D/hyPgar0Bn1/Y8JSJ6K20SknV6xHiN7SzK/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/rBMOT/hyPf1hxZ6h/8aUYsiO2x6PYZ6FaykCj4K/img.png?width=800&amp;amp;height=221&amp;amp;face=0_0_800_221,https://scrap.kakaocdn.net/dn/ckAPBG/hyPgcQUMXE/lQrWjAG3JK3q7vElCCiTW1/img.png?width=1317&amp;amp;height=826&amp;amp;face=0_0_1317_826');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JPA 는 어떻게 interface 만으로도 동작할까? (feat. reflection, proxy)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Data JPA를 공부하면서 궁금한 것이 있었습니다. public interface MemberRepository extends JpaRepository { List findAllByName(String name); } 위와 같이 MemberRepository는 인터페이스고, @Reposito..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pingpongdev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v3 - v2를 보완하기 위해 기본 구현체를 만듭니다&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface V3StudentRepository {

 Student getByNameAndAge(String name, Integer age);
 Student getByAgeAndAdress(Integer age, String adress);
}

@RequiredArgsConstructor
@Repository
public class V3StudentRepositoryImpl implements V3StudentRepository {

	private final V3StudentJooqRepository v3StudentJooqRepository;
	private final V3StudentQuerydslRepository v3StudentQuerydslRepository;

	@Override
	public Student getByNameAndAge(final String name, final Integer age) {
		return v3StudentJooqRepository.getByNameAndAge(name, age);
	}

	@Override
	public Student getByAgeAndAdress(final Integer age, final String adress) {
		return v3StudentQuerydslRepository.getByAgeAndAdress(age, adress);
	}
}

interface V3StudentJooqRepository { ... }
interface V3StudentQuerydslRepository { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;interface mixin 대신 delegate 패턴을 활용합니다.&lt;/li&gt;
&lt;li&gt;기능이 변경되어도 사용자 코드에 영향이 없는 느슨한 결합을 만족하지만, 코드 양이 늘어난 것이 조금 불편합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v4 - Spring Data Repository로 기본 구현체를 만듭니다&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface V4StudentRepository extends
		// org.springframework.data.repository.Repository
		Repository&amp;lt;Student, Long&amp;gt;, 
		V4StudentQuerydslRepository,
		V4StudentJooqRepository {

}

interface V4StudentJooqRepository { ... }
interface V4StudentQuerydslRepository { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;v3에 비해 코드 양이 줄어듭니다.&lt;/li&gt;
&lt;li&gt;기능이 늘어나도 mixin Repository에만 수정이 일어납니다.&lt;/li&gt;
&lt;li&gt;하지만 Repository&amp;lt;&amp;gt;는 스프링 프레임워크에 의존성을 가집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;혹자는 도메인 로직에서 프레임워크 의존성을 완전히 배제해야 된다고 말합니다.&lt;br /&gt;하지만 변경의 가능성이 매우 낮고, 개발 편의성이 명백히 향상된다면 팀의 예외로 정할 수 있다고 생각합니다.&lt;br /&gt;&lt;s&gt;스프링 없는 백엔드 개발이라니.. 끔찍하군요.&lt;/s&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;v5는 조금 더 학습 후 별도의 글을 작성 예정입니다. 지금의 내용은 참고만 해주세요.&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;s&gt;v5 - 인터페이스와 구현체를 패키지로 분리한 구조&lt;/s&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// v5.domain
public interface V5StudentRepository {

	Student getByNameAndAge(String name, Integer age);
	Student getByAgeAndAdress(Integer age, String adress);
}


// v5.infrastructure
public interface V5StudentJpaDao extends
		V5StudentRepository,
		Repository&amp;lt;Student, Long&amp;gt;,
		V5StudentJooqDao,
		V5StudentQuerydslDao {

}

interface V5StudentJooqDao { ... }
interface V5StudentQuerydslDao { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;Repository는 &lt;b&gt;객체의 상태를 관리하는 저장소&lt;/b&gt;로서 domain 레이어에 위치합니다.&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;Dao는 &lt;b&gt;영속성 상태를 관리하는 구현체&lt;/b&gt;로서 infrastructure 레이어에 위치합니다.&amp;nbsp;&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;DDD의 아이디어를 기반으로, 레이어를 엄격하게 구분해서 유연하고 모듈화에 강합니다.&lt;/s&gt;&lt;br /&gt;&lt;s&gt;하지만 러닝 커브가 높고 개발자마다 해석이 조금씩 다르기에 &lt;b&gt;팀에 실제로 적용하기까지는 꽤 어려움&lt;/b&gt;이 있습니다.&lt;/s&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1659205269124&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Repository와 Dao의 차이점.&quot; data-og-description=&quot;Repository와 Dao의 차이점에 대한 논쟁은 이전부터 끝없이 진행되어 왔다. 이번 포스팅에서는 Repository와 Dao의 차이에 대한 나의 생각을 논해보록한다. 필자가 생각하기에 Dao와 Repository의 차이점을 &quot; data-og-host=&quot;bperhaps.tistory.com&quot; data-og-source-url=&quot;https://bperhaps.tistory.com/entry/Repository%EC%99%80-Dao%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot; data-og-url=&quot;https://bperhaps.tistory.com/entry/Repository%EC%99%80-Dao%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/D3GBS/hyPgaFzaau/zf7UQPvFShkAuFnvGSHFrk/img.png?width=520&amp;amp;height=592&amp;amp;face=0_0_520_592,https://scrap.kakaocdn.net/dn/keHkA/hyPf6JVKBc/zJSi3KaeF9Aj77oHnutUq0/img.png?width=520&amp;amp;height=592&amp;amp;face=0_0_520_592,https://scrap.kakaocdn.net/dn/boQW3w/hyPf0XdWWs/F0Jl8aHVHfz93gvi6b9wv1/img.png?width=520&amp;amp;height=592&amp;amp;face=0_0_520_592&quot;&gt;&lt;a href=&quot;https://bperhaps.tistory.com/entry/Repository%EC%99%80-Dao%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bperhaps.tistory.com/entry/Repository%EC%99%80-Dao%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/D3GBS/hyPgaFzaau/zf7UQPvFShkAuFnvGSHFrk/img.png?width=520&amp;amp;height=592&amp;amp;face=0_0_520_592,https://scrap.kakaocdn.net/dn/keHkA/hyPf6JVKBc/zJSi3KaeF9Aj77oHnutUq0/img.png?width=520&amp;amp;height=592&amp;amp;face=0_0_520_592,https://scrap.kakaocdn.net/dn/boQW3w/hyPf0XdWWs/F0Jl8aHVHfz93gvi6b9wv1/img.png?width=520&amp;amp;height=592&amp;amp;face=0_0_520_592');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Repository와 Dao의 차이점.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Repository와 Dao의 차이점에 대한 논쟁은 이전부터 끝없이 진행되어 왔다. 이번 포스팅에서는 Repository와 Dao의 차이에 대한 나의 생각을 논해보록한다. 필자가 생각하기에 Dao와 Repository의 차이점을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bperhaps.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1659243324508&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;DAO vs Repository Patterns | Baeldung&quot; data-og-description=&quot;Understand the difference between the DAO and Repository patterns with a Java example.&quot; data-og-host=&quot;www.baeldung.com&quot; data-og-source-url=&quot;https://www.baeldung.com/java-dao-vs-repository&quot; data-og-url=&quot;https://www.baeldung.com/java-dao-vs-repository&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WkdNh/hyPgdCxOkZ/dyaMz6azTB4DVW0gK6VWXk/img.jpg?width=952&amp;amp;height=498&amp;amp;face=334_34_409_117,https://scrap.kakaocdn.net/dn/SaCI5/hyPhpg3mWV/osAKvduSOMhj6wKk1lyk8K/img.jpg?width=952&amp;amp;height=498&amp;amp;face=334_34_409_117&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/java-dao-vs-repository&quot; data-source-url=&quot;https://www.baeldung.com/java-dao-vs-repository&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WkdNh/hyPgdCxOkZ/dyaMz6azTB4DVW0gK6VWXk/img.jpg?width=952&amp;amp;height=498&amp;amp;face=334_34_409_117,https://scrap.kakaocdn.net/dn/SaCI5/hyPhpg3mWV/osAKvduSOMhj6wKk1lyk8K/img.jpg?width=952&amp;amp;height=498&amp;amp;face=334_34_409_117');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DAO vs Repository Patterns | Baeldung&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Understand the difference between the DAO and Repository patterns with a Java example.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.baeldung.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 고민을 해결하기 위해 공부한 이론입니다.&lt;br /&gt;하지만 실무에서의 개선은 조금 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무에서의 개선&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface BizSlipRepositoryJooq {

	/**
	 * [손익현황] 손익현황 조회
	 */
	List&amp;lt;ProfitAndLossStatus&amp;gt; findProfitAndLossStatus(ProfitAndLossStatusFilterInput input);

	... // 30개가 넘는 public 메서드가 존재함
}

@Repository
public class BizSlipRepositoryJooqImpl implements BizSlipRepositoryJooq {
	@Override
	public List&amp;lt;ProfitAndLossStatus&amp;gt; findProfitAndLossStatus(final ProfitAndLossStatusFilterInput input) { ... }
    
    ... // private 메서드를 포함하여 총 4000 line의 구현 클래스
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BizSlipRepositoryJooq는 Jooq로 &lt;span&gt;복잡한 조회 쿼리를&lt;span&gt; 처리하기 위해 만들어진 interface입니다.&lt;br /&gt;하지만 30개가 넘는 public 메서드를 구현하기 위해 4000 line의 구현 클래스가 존재합니다.&lt;br /&gt;&lt;b&gt;로직을 이해하는 것부터 개선과 기능 수정을 시도하는 것조차도 너무 힘든 일입니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// before
public interface SlipRepository extends JpaRepository&amp;lt;BizSlip, Long&amp;gt;, BizSlipRepositoryCustom, BizSlipRepositoryJooq {
	...	
}


// after
public interface BizSlipRepository extends
		JpaRepository&amp;lt;BizSlip, Long&amp;gt;,
		BizSlipRepositoryCustom,
		BizSlipFindProfitAndLossStatusRepositoryJooq,
		BizSlipRepositoryJooq {
	...
}

public interface BizSlipFindProfitAndLossStatusRepositoryJooq {

	/**
	 * [손익현황] 손익현황 조회
	 */
	List&amp;lt;ProfitAndLossStatus&amp;gt; findProfitAndLossStatus(ProfitAndLossStatusFilterInput input);
}

// 대상 메서드만 단일 interface로 분리
@Repository
public class BizSlipFindProfitAndLossStatusRepositoryJooqImpl implements BizSlipFindProfitAndLossStatusRepositoryJooq {

	@Override
	public List&amp;lt;ProfitAndLossStatus&amp;gt; findProfitAndLossStatus(final ProfitAndLossStatusFilterInput input) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 메서드만 단일 interface로 분리했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대상 로직이 하나의 클래스로 응집되었습니다.&lt;/li&gt;
&lt;li&gt;4000 line의 거대 Impl에서 300 line을 분리해 가독성을 높였습니다.&lt;/li&gt;
&lt;li&gt;로직의 수정 없이 단순히 클래스만을 분리했기에 가볍게 접근할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방식은 부족한 부분도 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기능의 개수만큼 클래스가 생기고 네이밍의 어려움이 생깁니다.&lt;/li&gt;
&lt;li&gt;접근 제한자 활용이 힘듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장황하게 설명한 이론과는 다르게 &lt;b&gt;실무는 협업이기에 충분한 의도와 근거를 가지고 점진적인 개선&lt;/b&gt;을 해야 됩니다.&lt;br /&gt;&lt;span&gt;그래서 실무에서는 &lt;/span&gt;&lt;b&gt;가독성 향상을 위한 Repository 분리&lt;/b&gt;만을 선택했고 가볍게 접근할 수 있었습니다.&lt;br /&gt;실무에서의 개선은 이렇게 접근해야 된다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 DDD가 좋다고 하여 v5의 구조를 바로 적용해서는 안됩니다.&lt;br /&gt;중요한 것은 레거시와 생산성, 러닝 커브를 고려한&amp;nbsp;&lt;b&gt;지속적인 개선을 시도할 수 있는 사이클을 만드는 것 &lt;/b&gt;입니다.&lt;br /&gt;이 관점에서 지금의 회사에는 v4의 구조가 적절하다고 생각하기에 이 방향으로 개선해 나갈 생각입니다.&lt;/p&gt;</description>
      <category>Series/실전!</category>
      <category>DAO</category>
      <category>repository</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/50</guid>
      <comments>https://hyune-c.tistory.com/50#entry50comment</comments>
      <pubDate>Mon, 1 Aug 2022 14:05:38 +0900</pubDate>
    </item>
    <item>
      <title>토스 SLASH 22 를 보고..</title>
      <link>https://hyune-c.tistory.com/48</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;토스 SLASH 22를 보고 관심 있는 주제인 Server/DevOps 위주로 정리했습니다.&lt;br /&gt;&lt;br /&gt;소위 말하는 네카라쿠배의 컨퍼런스를 보면서 느끼는 것 중 하나는 &lt;u&gt;대용량/동시성 처리를 위해서는 아키텍처와 생각의 패러다임 전환이 필요하고, 인프라의 재설계 및 운영 방법에 대한 고민도 필수적이라는 것&lt;/u&gt;입니다.&lt;br /&gt;그리고 &lt;u&gt;필연적으로 높은 운영 난이도가 요구되기에, 과도기의 회사에서는 어디까지를 트레이드 오프 해야 할지&lt;/u&gt; 항상 고민됩니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지속 성장 가능한 코드를 만들어가는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=RVO02Z1dLF8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bal92w/hyOUSdpa2s/nLSAUaPZYXrxpPeorJaaKk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=746_212_980_466&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/RVO02Z1dLF8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패키지를 잘 관리함으로써 코드의 응집성을 높일 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;import를 정리하는 것 또한 클린 코드의 영역입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZfuUY/btrGXhNeHkt/pV6DVEvaTGaJNkYS0VsBp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZfuUY/btrGXhNeHkt/pV6DVEvaTGaJNkYS0VsBp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZfuUY/btrGXhNeHkt/pV6DVEvaTGaJNkYS0VsBp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZfuUY%2FbtrGXhNeHkt%2FpV6DVEvaTGaJNkYS0VsBp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;523&quot; height=&quot;354&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;735&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레이어 참조는 한 방향으로 이루어져야 하며, 레이어 역류와 레이어 건너뛰기는 금지됩니다.&lt;/li&gt;
&lt;li&gt;멀티 모듈을 통해 레이어를 격리시킬 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;지금은 레이어 역류의 위험성을 알고 있지만, 처음에는 잘못된 줄도 모르고 반복하던 실수 중 하나입니다.&lt;br /&gt;이를 막기 위해 &lt;u&gt;모듈을 분리하거나 레이어 간의 메시지 전달을 엄격히 분리된 dto로 하는 방법&lt;/u&gt; 등이 있지만, &lt;u&gt;보일러 플레이트가 많아지고 지금의 규모에서 고민해야 될 문제인가?&lt;/u&gt; 를 고민하게 만드는 주제이기도 합니다.&lt;br /&gt;&lt;br /&gt;코드 리뷰어 활동을 하면서 남긴 글을 공유합니다.&lt;br /&gt;&lt;a href=&quot;https://hyune.gitbook.io/hyune-wiki/and/codesquad/22reviewer/undefined/service-dto&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hyune.gitbook.io/hyune-wiki/and/codesquad/22reviewer/undefined/service-dto&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;토스뱅크의 완전히 새로운 대출 시스템&lt;/b&gt;&lt;/h2&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=SLamxuykpnw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/tZ0H0/hyOUNJXai2/LYSgKLZqjdCx5gNOKwyxE0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=700_220_920_460&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/SLamxuykpnw&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSA 아키텍처 도입&lt;/li&gt;
&lt;li&gt;flyway를 통한 DB 스키마 관리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ddl-auto 옵션을 validate로 설정하여 prod 배포 시 2차 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;각각의 대외기관은 TPS가 다르기에 독립적인 유량 제어를 위해 카프카와 파이프라인을 활용합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확한 TPS 측정을 위해 API 통신은 동기로 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;resdis에 지표를 저장하고, rdb에 분 단위의 통계를 저장하는 방법으로 유동적인 서킷 브레이커를 구현했습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서킷 브레이커로도 감당되지 않는 경우 대기열을 활용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;유량 제어와 서킷 브레이커에 대해 다시 한번 정리할 수 있었습니다.&lt;br /&gt;특히 대기열까지 활용하는 것은 생각하지 못했는데 좋은 인사이트를 얻었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;title&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;왜 은행은 무한스크롤이 안되나요&lt;/b&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=v9rcKpUZw4o&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bdLs26/hyO0RyVaye/0YKBLRYWc1Y7NwccS41np0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=870_208_1100_458&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/v9rcKpUZw4o&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;계정계는 고객의 거래 데이터를 다루는 영역으로 높은 신뢰도가 필요합니다.&lt;br /&gt;채널계는 기능 요청의 포인트로 계정계에 비해서는 상대적으로 낮은 신뢰도를 가집니다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토스에서는 계정계의 정보를 채널계에서도 가지고 있는 방식을 선택했습니다.&lt;/li&gt;
&lt;li&gt;예외 처리 흐름을 상정하고 카프카를 활용해 거래내역을 동기화했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;요약&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빠른 거래내역 조회를 위해 채널계에 거래내역을 적재&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;blockquote data-ke-style=&quot;style3&quot;&gt;최근 회사에서도 비슷한 성격의 업무에 대해 고민하고 있기에 재미있게 봤습니다.&lt;br /&gt;다행히도 제가 생각해낸 아이디어와 영상에서 말하는 &lt;u&gt;계정계 원장의 정보를 채널계에서도 가지고 있는다&lt;/u&gt;가 같아 재미있었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;title&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;토스증권 실시간 시세 적용기&lt;/b&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=WKYE-QtzO6g&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/pra61/hyO0RMw6dA/7kcA2rjpybj3SI8pnGE7x0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=778_246_958_442&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/WKYE-QtzO6g&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwa2Nu/btrGQPjLCLq/lkHmAdvehGqV4j5HHIL4d0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwa2Nu/btrGQPjLCLq/lkHmAdvehGqV4j5HHIL4d0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwa2Nu/btrGQPjLCLq/lkHmAdvehGqV4j5HHIL4d0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwa2Nu%2FbtrGQPjLCLq%2FlkHmAdvehGqV4j5HHIL4d0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;396&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;921&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API Polling 방식으로&amp;nbsp;실시간 시세를&amp;nbsp;구현했지만, 좀 더 좋은 사용자 경험을 위해 WebSocket 방식을 선택.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;양방향 통신이 아닌 단방향만 필요하다면 SSE(Server Sent Event)도 좋은 선택입니다. &lt;br /&gt;(방화벽 등 보안 설정 용이)&lt;/li&gt;
&lt;li&gt;WebSocket 방식이 지연되는 경우 API Polling을 사용하는 방법으로 구축 초기에 아주 유용하게 사용되습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;트러블 슈팅&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Connection 부하 분산을 위해 Round Robin 방식과 Least Connection 방식을 혼합해서 사용.&lt;/li&gt;
&lt;li&gt;WebSocket 사용 시 비정상 종료에 대한 예외 처리를 잘해야 합니다.&lt;/li&gt;
&lt;li&gt;대용량 트래픽 처리 시 방화벽과 보안 장비의 처리량도 고려해야 합니다.&lt;/li&gt;
&lt;li&gt;스케일 아웃의 유연함을 위해 온프레미스 환경에서 클라우드 환경으로의 이전을 고려하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;처음에는 API Polling 방식으로 접근했다는 것에 놀랐고, 이미 완성된 기능이 있음에도 불구하고 좋은 사용자 경험을 위해 WebSocket 방식으로 재 구현했다는 점에서 두 번 놀랐습니다.&lt;br /&gt;만약 &lt;u&gt;내가 저 상황이라면 그렇게 선택할 수 있을까?&lt;/u&gt; 고민하게 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;title&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;애플 한 주가 고객에게 전달 되기까지&lt;/b&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=UOWy6zdsD-c&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/W9Oed/hyO12BwJmn/dUVzZyeWPuU0azuNks2uv0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=878_216_1116_476&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/UOWy6zdsD-c&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동시성을 다루기 위한 Lock 사용 방법&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보편적인 락 구현은 DBMS에 락을 위한 테이블을 생성하고 트랜잭션 시작 시 테이블에 락을 잡습니다.&lt;/li&gt;
&lt;li&gt;독립적인 DBMS를 사용하는 MSA 환경에서는 적절하지 않기에 Redis 기반의 분산 락을 설계했습니다.&lt;/li&gt;
&lt;li&gt;만약의 상황을 위해 낙관적인 락을 추가로 구현했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해외 구간 네트워크 지연을 보완하기 위한 브로커 전략&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매매 서버에서 고객의 요청을 받는 스레드와 브로커에게 요청하는 스레드를 분리하고 비동기 처리합니다.&lt;/li&gt;
&lt;li&gt;브로커로의 요청을 멱등하게 구현하고, 재시도 주기를 적절하게 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;브로커 의존성 격리하기&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매매 서버에서 브로커로의 연결을 인터페이스로서&amp;nbsp;매매 요청 서버로 분리합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이를 통해 도메인 로직은 매매 서버에 응집되고, 처리량과 스케일 아웃의 책임은 매매 요청 서버로 격리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마찬가지의 이유로 체결 수신의 서버 또한 분리되어 있습니다.&lt;/li&gt;
&lt;li&gt;체결 수신 서버는 매매 서버로 응답하기 전에 DB 기록을 함으로써 다양한 Fail Over 전략의 선택이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;동시성과 브로커는 최근 가장 관심 있는 주제이기에 아주 재미있게 봤습니다.&lt;br /&gt;특히 브로커로의 접근을 스레드 단위로 분리하고, 매매 서버와 매매 요청 서버를 물리적으로 분리한 것이 인상적입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Java Native Memory Leak 원인을 찾아서&lt;/b&gt;&lt;/h2&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=w4fWgLgop5U&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bgmj0V/hyO13f8wKU/RAJl3RFBIsKHQIAjW1RKf0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=750_222_954_444&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/w4fWgLgop5U&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;개인적으로 Memory Leak에 관심이 있지만 제가 일하는 환경과는 조금 거리가 있는 주제이기에 정리하지는 않습니다.&lt;br /&gt;하지만 글로만 봤었던&amp;nbsp;&lt;u&gt;소스 변경 없이도 GC나 컴파일러 변경만으로도 튜닝이 가능하다는 것에 대한 실 사례&lt;/u&gt;를 보니 조금 신기했습니다.&lt;br /&gt;&lt;br /&gt;번외로 Heap Dump 분석 툴인 VisualVM의 세팅과 분석한 기록을 남깁니다.&lt;br /&gt;&lt;a href=&quot;https://hyune.gitbook.io/hyune-wiki/framework-and-library/monitoring/visualvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hyune.gitbook.io/hyune-wiki/framework-and-library/monitoring/visualvm&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;어떻게 안정적인 서비스를 빠르게, 자주 출시할 것인가?&lt;/b&gt;&lt;/h2&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=oakvibIKToc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/f6AUW/hyO3rGurZy/K60ed7yj0HVo5bpK6t4hh1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=730_254_930_472&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/oakvibIKToc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSA 환경에서의 복잡한 프로젝트 세팅 시간을 줄이기 위해 Boilerplate Project를 운영합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사내 프로젝트의 세팅을 균일화하여 관리하였기에, 최근 log4j 취약점 이슈일 때도 일괄 수정 배포가 용이했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CI/CD는 yaml을 통해 관리하기 쉬운 GoCD와 Git Action을 활용합니다.&lt;/li&gt;
&lt;li&gt;배포 시 쿠버네티스와 Istio를 통해 비율 단위의 트래픽 조절이 가능합니다.&lt;/li&gt;
&lt;li&gt;MSA의 가장 큰 어려움 중 하나인 분산 추적 시스템을 자체 구현했고 Alert도 구현하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;익히 알고 있는 내용 위주였지만 빅테크의 인프라, CI/CD 관리는 새삼 대단하다고 느꼈습니다!&lt;br /&gt;아쉬운 점은 서버 단위의 설정 관리 방법에 대한 얘기가 없었습니다.&lt;br /&gt;(Spring Cloud Config Server 같은 것을 사용할까?)&lt;br /&gt;&lt;br /&gt;번외로 현재 재직 중인 회사에서는 분산 추적 시스템을 Datadog으로 관리합니다.&lt;br /&gt;&lt;a href=&quot;https://hyune-c.tistory.com/entry/Datadog-%EC%97%90%EC%84%9C-GraphQL-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%A7%9B%EB%B3%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hyune-c.tistory.com/entry/Datadog-%EC%97%90%EC%84%9C-GraphQL-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%A7%9B%EB%B3%B4%EA%B8%B0&lt;/a&gt;&lt;/blockquote&gt;</description>
      <category>Study</category>
      <category>Toss Slash 22</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/48</guid>
      <comments>https://hyune-c.tistory.com/48#entry48comment</comments>
      <pubDate>Mon, 11 Jul 2022 00:12:22 +0900</pubDate>
    </item>
    <item>
      <title>INSERT INTO SELECT SHARED LOCK은 레코드 락으로 작동하는가? with MySql</title>
      <link>https://hyune-c.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 수기 작업 간 발생한 이슈를 해결하면서 회사 시니어님이 공유해주신 자료를 보고 이론으로 공부만 했던 것이 문득 궁금해져 테스트해 본 기록을 남깁니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;환경&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySql 8.0&lt;/li&gt;
&lt;li&gt;Datagrip&lt;/li&gt;
&lt;li&gt;Transaction Mode - Manual&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 1&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Table 단위의 LOCK&lt;/b&gt;이 걸리는가? 아니면 &lt;b&gt;ROW 단위의 LOCK&lt;/b&gt;이 걸리는가? - 레코드 락&lt;/li&gt;
&lt;li&gt;특정 ROW에만 SHARED LOCK이 걸리고, 다른 ROW의 CRUD에는 영향이 없는가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SHARED LOCK -&lt;span data-offset-key=&quot;12e389c9d2d04129842eeaf62f993771:1&quot; data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMlNIQVJFRCUyMExPQ0slMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMm1hcmslMjIlMkMlMjJ0eXBlJTIyJTNBJTIyY29kZSUyMiUyQyUyMmRhdGElMjIlM0ElN0IlN0QlN0QlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTJDJTdCJTIyb2JqZWN0JTIyJTNBJTIybGVhZiUyMiUyQyUyMnRleHQlMjIlM0ElMjIlM0ElMjAlRUMlOUQlQkQlRUElQjglQjAlMjAlRUElQjAlODAlRUIlOEElQTUuJTIwJUVDJTg4JTk4JUVDJUEwJTk1JTJGJUVDJTgyJUFEJUVDJUEwJTlDJTIwJUVCJUI2JTg4JUVBJUIwJTgwJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDdjOTRmNmRjNTM0NDU0YzkyODE1NjcxNWYyNTZhNDglMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjJjNDVjOWNkYzFiZWM0ZDM0OGFhZjQxMTAyN2FlYjc5ZSUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmFkYjg0ODczZmNlMjQyZWU4OGNlNGRkZTEwMGY3YjUxJTIyJTdE&quot;&gt;&amp;nbsp;읽기 가능. 수정/삭제 불가&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-offset-key=&quot;12e389c9d2d04129842eeaf62f993771:1&quot; data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMlNIQVJFRCUyMExPQ0slMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMm1hcmslMjIlMkMlMjJ0eXBlJTIyJTNBJTIyY29kZSUyMiUyQyUyMmRhdGElMjIlM0ElN0IlN0QlN0QlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTJDJTdCJTIyb2JqZWN0JTIyJTNBJTIybGVhZiUyMiUyQyUyMnRleHQlMjIlM0ElMjIlM0ElMjAlRUMlOUQlQkQlRUElQjglQjAlMjAlRUElQjAlODAlRUIlOEElQTUuJTIwJUVDJTg4JTk4JUVDJUEwJTk1JTJGJUVDJTgyJUFEJUVDJUEwJTlDJTIwJUVCJUI2JTg4JUVBJUIwJTgwJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDdjOTRmNmRjNTM0NDU0YzkyODE1NjcxNWYyNTZhNDglMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjJjNDVjOWNkYzFiZWM0ZDM0OGFhZjQxMTAyN2FlYjc5ZSUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmFkYjg0ODczZmNlMjQyZWU4OGNlNGRkZTEwMGY3YjUxJTIyJTdE&quot;&gt;초기 세팅&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1657094310242&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create table test1 (
    col1 int primary key auto_increment,
    col2 char(10)
);
create table test2 select * from test1 limit 0;

insert into test1 (col2) values ('aaa'),('bbb'),('ccc');

## 현재 걸린 lock 조회
select * from performance_schema.data_locks&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-offset-key=&quot;12e389c9d2d04129842eeaf62f993771:1&quot; data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMlNIQVJFRCUyMExPQ0slMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMm1hcmslMjIlMkMlMjJ0eXBlJTIyJTNBJTIyY29kZSUyMiUyQyUyMmRhdGElMjIlM0ElN0IlN0QlN0QlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTJDJTdCJTIyb2JqZWN0JTIyJTNBJTIybGVhZiUyMiUyQyUyMnRleHQlMjIlM0ElMjIlM0ElMjAlRUMlOUQlQkQlRUElQjglQjAlMjAlRUElQjAlODAlRUIlOEElQTUuJTIwJUVDJTg4JTk4JUVDJUEwJTk1JTJGJUVDJTgyJUFEJUVDJUEwJTlDJTIwJUVCJUI2JTg4JUVBJUIwJTgwJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDdjOTRmNmRjNTM0NDU0YzkyODE1NjcxNWYyNTZhNDglMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjJjNDVjOWNkYzFiZWM0ZDM0OGFhZjQxMTAyN2FlYjc5ZSUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmFkYjg0ODczZmNlMjQyZWU4OGNlNGRkZTEwMGY3YjUxJTIyJTdE&quot;&gt;시나리오&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWTYwX/btrGB49uTAT/EJE0IR7wb9NPA5XHTGaU51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWTYwX/btrGB49uTAT/EJE0IR7wb9NPA5XHTGaU51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWTYwX/btrGB49uTAT/EJE0IR7wb9NPA5XHTGaU51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWTYwX%2FbtrGB49uTAT%2FEJE0IR7wb9NPA5XHTGaU51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;111&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657093008467&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 1
begin;

insert into test2
select *
from test1
where col1 = '1';&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col1=1&lt;/b&gt;&amp;nbsp;S락 획득 시도&lt;/li&gt;
&lt;li&gt;&lt;b&gt;primaryKey=1&lt;/b&gt;&amp;nbsp;S락 획득 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhY2lc/btrGzEcq5Fz/kEwZG2Y7IEFNcg21Ta3NJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhY2lc/btrGzEcq5Fz/kEwZG2Y7IEFNcg21Ta3NJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhY2lc/btrGzEcq5Fz/kEwZG2Y7IEFNcg21Ta3NJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhY2lc%2FbtrGzEcq5Fz%2FkEwZG2Y7IEFNcg21Ta3NJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;549&quot; height=&quot;154&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657093102779&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 2
update test1
set col2 = '1111'
where col1 = 2;&lt;/code&gt;&lt;/pre&gt;
&lt;div data-rnw-media-class=&quot;1474-195__1472-_b1430-195&quot;&gt;
&lt;div data-rnw-media-class=&quot;1473-__1471&quot;&gt;
&lt;div data-block-content=&quot;9a9c750c0d954aa78f59ca87912f8113&quot;&gt;
&lt;div data-key=&quot;42d51f6d21fd448fa4aea7ac806083ed&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col1=2&lt;/b&gt; X락 획득 시도&lt;/li&gt;
&lt;li&gt;&lt;b&gt;primaryKey=2&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; data-offset-key=&quot;96c6fb62c5f442b7965fd4cedda488f5:1&quot;&gt; 락이 존재하지 않았기에 X락 획득 성공&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nZcLc/btrGElCGVZI/g4OBfOejGqGATKNQHwyPI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZcLc/btrGElCGVZI/g4OBfOejGqGATKNQHwyPI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZcLc/btrGElCGVZI/g4OBfOejGqGATKNQHwyPI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZcLc%2FbtrGElCGVZI%2Fg4OBfOejGqGATKNQHwyPI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;172&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre id=&quot;code_1657093217763&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 2
update test1
set col2 = '1111'
where col1 = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-rnw-media-class=&quot;1474-195__1472-_b1430-195&quot;&gt;
&lt;div data-rnw-media-class=&quot;1473-__1471&quot;&gt;
&lt;div data-block-content=&quot;b193cf1e3c58447ebb66a13a11bce882&quot;&gt;
&lt;div data-key=&quot;96bd857d6aae481590ff2948dabb37f5&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col1=1&lt;/b&gt; X락 획득 시도&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;u&gt;하지만 Session 1 에서 S락을 걸고 있었기에 호환되지 않아 WAITING.&lt;/u&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락이 걸리지 않은 레코드에는 update가 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;즉 레코드락으로 작동하는 것을 확인할 수 있다.&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;S락이 걸린 레코드에 X락이 획득 요청이 들어오면 S락이 해제될 때까지 기다린다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;S락과 X락은 호환되지 않기 때문이다. timeout, deadlock의 주범.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하지만..&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-rnw-media-class=&quot;1474-195__1472-_b1430-195&quot; data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJsaXN0LWl0ZW0lMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMmJsb2NrJTIyJTJDJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmlzVm9pZCUyMiUzQWZhbHNlJTJDJTIyZGF0YSUyMiUzQSU3QiU3RCUyQyUyMm5vZGVzJTIyJTNBJTVCJTdCJTIyb2JqZWN0JTIyJTNBJTIydGV4dCUyMiUyQyUyMmxlYXZlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMmxlYWYlMjIlMkMlMjJ0ZXh0JTIyJTNBJTIyY29sMSUzRDIlMjIlMkMlMjJtYXJrcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMm1hcmslMjIlMkMlMjJ0eXBlJTIyJTNBJTIyY29kZSUyMiUyQyUyMmRhdGElMjIlM0ElN0IlN0QlN0QlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTJDJTdCJTIyb2JqZWN0JTIyJTNBJTIybGVhZiUyMiUyQyUyMnRleHQlMjIlM0ElMjIlMjBYJUVCJTlEJUJEJTIwJUVEJTlBJThEJUVCJTkzJTlEJTIwJUVDJThCJTlDJUVCJThGJTg0JTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyMzNmNzk2MTE0MTY3NGU2Yjg2OTA4ZDdmOWRiZWZlODMlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIwMmFkMjdkMWIwNTc0MDJjODRlMDc4N2M1Y2EzNWJhNiUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjZhZGZmNDE0ZGUwOTQxOTc5MzU4M2E3ZTQzMzE2ZGZhJTIyJTdEJTJDJTdCJTIyb2JqZWN0JTIyJTNBJTIyYmxvY2slMjIlMkMlMjJ0eXBlJTIyJTNBJTIybGlzdC1pdGVtJTIyJTJDJTIyaXNWb2lkJTIyJTNBZmFsc2UlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMnByaW1hcnlLZXklM0QyJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJtYXJrJTIyJTJDJTIydHlwZSUyMiUzQSUyMmNvZGUlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTdEJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCUyQyU3QiUyMm9iamVjdCUyMiUzQSUyMmxlYWYlMjIlMkMlMjJ0ZXh0JTIyJTNBJTIyJTIwJTIwWCVFQiU5RCVCRCUyMCVFRCU5QSU4RCVFQiU5MyU5RCUyMCVFQyU4NCVCMSVFQSVCMyVCNSUyMCUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjk2YzZmYjYyYzVmNDQyYjc5NjVmZDRjZWRkYTQ4OGY1JTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyYTVlNjYxOTUyYTBmNDZmMzhiZWRjNzU2MTljN2E1YzAlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjJkMTgzNDM0ZTkyOGQ0MWE2YmU3MjlhNTg0MzlhMGRhMyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmNjODFjOTA4NmIxNjRjMzViNDQ4ZTQzYjY3ZDE5YzgxJTIyJTdE&quot;&gt;
&lt;div&gt;
&lt;div data-rnw-media-class=&quot;1473-__1471&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면서 최근 읽은 REAL MySQL의 잠금 파트가 생각나서 다시 읽어봤습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 테이블에 인덱스가 하나도 없다면 어떻게 될까? &lt;br /&gt;이러한 경우에는 테이블을 풀 스캔 하면서 UPDATE 작업을 하는데, &lt;br /&gt;이 과정에서 테이블에 있는 30여만 건의 모든 레코드를 잠그게 된다. &lt;br /&gt;이것이 MySQL의 방식이며, MySQL에서 인덱스 설계가 중요한 이유 또한 이것이다. &lt;br /&gt;&lt;br /&gt;- REAL MySQL 1권. 5.3.2 인덱스와 잠금 (172p)&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 내용을 읽고 앞의 테스트를 다시 보니 의문이 생겼습니다.&lt;/li&gt;
&lt;li&gt;where 조건의 col1은 PK이기에 unique index로 이미 잡혀있어 단일 레코드 락이 걸립니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;하지만 index가 없는 col2라면?&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;col2에 non unique index를 걸고 조회한다면?&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 2&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk1JTg0JUVCJThCJThDJTIwY29sMiVFQiVBNSVCQyUyMHdoZXJlJTIwJUVDJUExJUIwJUVBJUIxJUI0JUVDJTlDJUJDJUVCJUExJTlDJTIwJUVDJThCJUE0JUVEJTk2JTg5JUVEJTk1JTk4JUVCJThBJTk0JTIwJUVBJUIyJUJEJUVDJTlBJUIwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVCJTlEJUJEJUVDJTlEJUI0JTIwJUVDJTk2JUI0JUVCJTk2JUJCJUVBJUIyJThDJTIwJUVDJTlFJUExJUVEJTlFJTg4JUVCJThBJTk0JUVBJUIwJTgwJTNGJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyMjE0Nzc5NTEwYzg2NDBjYzg3ZmU3YjY4MGFkNzVkMWElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIzYzdkMDQwZTI2Mjc0MjFjODI2MDNlOWYyZGMwNWI3NyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmUwMDI1MjM5MjNjMDRkZDQ4YTllN2U2YzkwOTAzNjYyJTIyJTdE&quot;&gt;&lt;b&gt;index가 아닌 col2&lt;/b&gt;를 where 조건으로 실행하는 경우 락이 어떻게 잡히는가?&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk1JTg0JUVCJThCJThDJTIwY29sMiVFQiVBNSVCQyUyMHdoZXJlJTIwJUVDJUExJUIwJUVBJUIxJUI0JUVDJTlDJUJDJUVCJUExJTlDJTIwJUVDJThCJUE0JUVEJTk2JTg5JUVEJTk1JTk4JUVCJThBJTk0JTIwJUVBJUIyJUJEJUVDJTlBJUIwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVCJTlEJUJEJUVDJTlEJUI0JTIwJUVDJTk2JUI0JUVCJTk2JUJCJUVBJUIyJThDJTIwJUVDJTlFJUExJUVEJTlFJTg4JUVCJThBJTk0JUVBJUIwJTgwJTNGJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyMjE0Nzc5NTEwYzg2NDBjYzg3ZmU3YjY4MGFkNzVkMWElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIzYzdkMDQwZTI2Mjc0MjFjODI2MDNlOWYyZGMwNWI3NyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmUwMDI1MjM5MjNjMDRkZDQ4YTllN2U2YzkwOTAzNjYyJTIyJTdE&quot;&gt;초기 세팅 동일&lt;/span&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk1JTg0JUVCJThCJThDJTIwY29sMiVFQiVBNSVCQyUyMHdoZXJlJTIwJUVDJUExJUIwJUVBJUIxJUI0JUVDJTlDJUJDJUVCJUExJTlDJTIwJUVDJThCJUE0JUVEJTk2JTg5JUVEJTk1JTk4JUVCJThBJTk0JTIwJUVBJUIyJUJEJUVDJTlBJUIwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVCJTlEJUJEJUVDJTlEJUI0JTIwJUVDJTk2JUI0JUVCJTk2JUJCJUVBJUIyJThDJTIwJUVDJTlFJUExJUVEJTlFJTg4JUVCJThBJTk0JUVBJUIwJTgwJTNGJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyMjE0Nzc5NTEwYzg2NDBjYzg3ZmU3YjY4MGFkNzVkMWElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIzYzdkMDQwZTI2Mjc0MjFjODI2MDNlOWYyZGMwNWI3NyUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmUwMDI1MjM5MjNjMDRkZDQ4YTllN2U2YzkwOTAzNjYyJTIyJTdE&quot;&gt;시나리오&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqRw6d/btrGEyaGZr3/owrOvGdEXhcvdnNarB6bdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqRw6d/btrGEyaGZr3/owrOvGdEXhcvdnNarB6bdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqRw6d/btrGEyaGZr3/owrOvGdEXhcvdnNarB6bdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqRw6d%2FbtrGEyaGZr3%2FowrOvGdEXhcvdnNarB6bdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;170&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657093646955&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 1
begin;

insert into test2
select *
from test1
where col2 = 'bbb';&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col2=bbb&lt;/b&gt;&amp;nbsp;S락&amp;nbsp;획득&amp;nbsp;시도.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;하지만&amp;nbsp;모든&amp;nbsp;레코드에&amp;nbsp;대해&amp;nbsp;S락&amp;nbsp;획득&amp;nbsp;성공.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RtOxC/btrGC8RptYH/x8OiwxwJtPJ51XCO1nRDL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RtOxC/btrGC8RptYH/x8OiwxwJtPJ51XCO1nRDL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RtOxC/btrGC8RptYH/x8OiwxwJtPJ51XCO1nRDL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRtOxC%2FbtrGC8RptYH%2Fx8OiwxwJtPJ51XCO1nRDL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;414&quot; height=&quot;213&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657093735487&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 2
update test1
set col2 = 'fff'
where col2 = 'ccc';&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col2=ccc&lt;/b&gt;&amp;nbsp;X락&amp;nbsp;획득&amp;nbsp;시도.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;하지만&amp;nbsp;Session&amp;nbsp;1&amp;nbsp;에서&amp;nbsp;모든&amp;nbsp;레코드에&amp;nbsp;S락을&amp;nbsp;걸고&amp;nbsp;있기에&amp;nbsp;호환되지&amp;nbsp;않아&amp;nbsp;WAITING.&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk3JTg2JUVCJThCJUE0JUVCJUE5JUI0JTIwJUVCJUFBJUE4JUVCJTkzJUEwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVBJUIwJTgwJTIwJUVCJTlEJUJEJUVDJTk3JTkwJTIwJUVBJUIxJUI4JUVCJUE2JUIwJUVCJThCJUE0LiUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjdmMGE3OTkyZmY5MTRjMWQ4NDZmZWRiNTM3ZmI3ZTkyJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDc4MGQzNjQ4MTY0NDE4YmE3MDdkYTAxYjUwMGI2ZTElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIxYjBhZWE1YWM3ODg0NmYyODYxNDUyNTFmNTM3MmNlNCUyMiU3RA==&quot;&gt;index가 없다면 모든 레코드가 락에 걸린다.&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk3JTg2JUVCJThCJUE0JUVCJUE5JUI0JTIwJUVCJUFBJUE4JUVCJTkzJUEwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVBJUIwJTgwJTIwJUVCJTlEJUJEJUVDJTk3JTkwJTIwJUVBJUIxJUI4JUVCJUE2JUIwJUVCJThCJUE0LiUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjdmMGE3OTkyZmY5MTRjMWQ4NDZmZWRiNTM3ZmI3ZTkyJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDc4MGQzNjQ4MTY0NDE4YmE3MDdkYTAxYjUwMGI2ZTElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIxYjBhZWE1YWM3ODg0NmYyODYxNDUyNTFmNTM3MmNlNCUyMiU3RA==&quot;&gt;테스트 3&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk3JTg2JUVCJThCJUE0JUVCJUE5JUI0JTIwJUVCJUFBJUE4JUVCJTkzJUEwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVBJUIwJTgwJTIwJUVCJTlEJUJEJUVDJTk3JTkwJTIwJUVBJUIxJUI4JUVCJUE2JUIwJUVCJThCJUE0LiUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjdmMGE3OTkyZmY5MTRjMWQ4NDZmZWRiNTM3ZmI3ZTkyJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDc4MGQzNjQ4MTY0NDE4YmE3MDdkYTAxYjUwMGI2ZTElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIxYjBhZWE1YWM3ODg0NmYyODYxNDUyNTFmNTM3MmNlNCUyMiU3RA==&quot;&gt;목표&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmNvbDIlRUMlOTclOTAlMjBub24lMjB1bmlxdWUlMjBpbmRleCVFQiVBNSVCQyUyMCVFQSVCMSVCOCVFQSVCMyVBMCUyMHdoZXJlJTIwJUVDJUExJUIwJUVBJUIxJUI0JUVDJTlDJUJDJUVCJUExJTlDJTIwJUVDJThCJUE0JUVEJTk2JTg5JUVEJTk1JTk4JUVCJThBJTk0JTIwJUVBJUIyJUJEJUVDJTlBJUIwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVCJTlEJUJEJUVDJTlEJUI0JTIwJUVDJTk2JUI0JUVCJTk2JUJCJUVBJUIyJThDJTIwJUVDJTlFJUExJUVEJTlFJTg4JUVCJThBJTk0JUVBJUIwJTgwJTNGJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyOWM4NjRjMGE2NGNiNDA0NzgzMGJmMGUyYmRmNzQ2ZjUlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI5MDkzNDViOWRjOTQ0ZTJlOTNlZGQ0MGRmNzRlZmZjZCUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjA1NDQ5ODEzZTMyYTRjM2ZhMWZhNjBjMGRkZDU5NzE2JTIyJTdE&quot;&gt;&lt;b&gt;col2에 non unique index&lt;/b&gt;를 걸고 where 조건으로 실행하는 경우 락이 어떻게 잡히는가?&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk3JTg2JUVCJThCJUE0JUVCJUE5JUI0JTIwJUVCJUFBJUE4JUVCJTkzJUEwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVBJUIwJTgwJTIwJUVCJTlEJUJEJUVDJTk3JTkwJTIwJUVBJUIxJUI4JUVCJUE2JUIwJUVCJThCJUE0LiUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjdmMGE3OTkyZmY5MTRjMWQ4NDZmZWRiNTM3ZmI3ZTkyJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDc4MGQzNjQ4MTY0NDE4YmE3MDdkYTAxYjUwMGI2ZTElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIxYjBhZWE1YWM3ODg0NmYyODYxNDUyNTFmNTM3MmNlNCUyMiU3RA==&quot;&gt;초기 세팅&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1657093873888&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create table test1
(
    col1 int primary key auto_increment,
    col2 char(10)
);

create table test2
select *
from test1
limit 0;

insert into test1 (col2)
values ('aaa'), ('aaa'),
       ('bbb'), ('bbb'),
       ('ccc'), ('ccc');

create index test1_col2_index on test1(col2); ## 인덱스 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMmluZGV4JUVBJUIwJTgwJTIwJUVDJTk3JTg2JUVCJThCJUE0JUVCJUE5JUI0JTIwJUVCJUFBJUE4JUVCJTkzJUEwJTIwJUVCJUEwJTg4JUVDJUJEJTk0JUVCJTkzJTlDJUVBJUIwJTgwJTIwJUVCJTlEJUJEJUVDJTk3JTkwJTIwJUVBJUIxJUI4JUVCJUE2JUIwJUVCJThCJUE0LiUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjdmMGE3OTkyZmY5MTRjMWQ4NDZmZWRiNTM3ZmI3ZTkyJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNDc4MGQzNjQ4MTY0NDE4YmE3MDdkYTAxYjUwMGI2ZTElMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjIxYjBhZWE1YWM3ODg0NmYyODYxNDUyNTFmNTM3MmNlNCUyMiU3RA==&quot;&gt;시나리오&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg88Rr/btrGBhH4iIn/1cY8KJGEG6QAD8Z43sRkzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg88Rr/btrGBhH4iIn/1cY8KJGEG6QAD8Z43sRkzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg88Rr/btrGBhH4iIn/1cY8KJGEG6QAD8Z43sRkzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg88Rr%2FbtrGBhH4iIn%2F1cY8KJGEG6QAD8Z43sRkzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;149&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657093908688&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 1
begin;

insert into test2
select *
from test1
where col2 = 'bbb';&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col2=bbb&lt;/b&gt; S락 획득 시도.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;primaryKey=3,4,5&lt;/b&gt;&amp;nbsp;&amp;nbsp;S락 획득 성공.&lt;/li&gt;
&lt;li&gt;col2는 non unique index이지만 &lt;b&gt;primaryKey=3,4&lt;/b&gt; S락 획득 성공.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;primaryKey=5 &lt;/b&gt;&amp;nbsp;S락이자 GAP락 획득.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf8Rm5/btrGFJ3ERld/pVyjKUlKDge505mmNv8760/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf8Rm5/btrGFJ3ERld/pVyjKUlKDge505mmNv8760/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf8Rm5/btrGFJ3ERld/pVyjKUlKDge505mmNv8760/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf8Rm5%2FbtrGFJ3ERld%2FpVyjKUlKDge505mmNv8760%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;539&quot; height=&quot;322&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657094124911&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 2
update test1
set col2 = 'fff'
where col2 = 'ccc';&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col2=ccc&lt;/b&gt; X락&amp;nbsp;획득&amp;nbsp;시도.&lt;/li&gt;
&lt;li&gt;&lt;b&gt; primaryKey=5,6&lt;/b&gt; &amp;nbsp;X락&amp;nbsp;획득&amp;nbsp;성공.&lt;/li&gt;
&lt;li&gt;하지만 unique index 때와는 다르게 REC_NOT_GAP, GAP 락들이 추가로 획득됩니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pILgg/btrGElW1xhG/8hjrVlAKOmlT6bXaaTtbNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pILgg/btrGElW1xhG/8hjrVlAKOmlT6bXaaTtbNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pILgg/btrGElW1xhG/8hjrVlAKOmlT6bXaaTtbNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpILgg%2FbtrGElW1xhG%2F8hjrVlAKOmlT6bXaaTtbNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;341&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1657094243478&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Session 2
update test1
set col2 = 'ggg'
where col2 = 'bbb';&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;col2=bbb&lt;/b&gt; X락 획득 시도.&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;하지만 Session 1에서 S락을 걸고 있기에 호환되지 않아 WAITING.&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMiVFRCU4NSU4QyVFQyU4QSVBNCVFRCU4QSVCOCUyMDElRUElQjMlQkMlMjAlRUElQjIlQjAlRUElQjMlQkMlRUIlOEElOTQlMjAlRUElQjAlOTklRUMlQTclODAlRUIlQTclOEMlMjAlRUIlOUQlQkQlRUMlOUQlQjQlMjAlRUElQjElQjglRUIlQTYlQUMlRUIlOEElOTQlMjAlRUQlOTglOTUlRUQlODMlOUMlRUElQjAlODAlMjAlRUIlQTclQTQlRUMlOUElQjAlMjAlRUIlOEIlQTQlRUIlQTYlODUlRUIlOEIlODglRUIlOEIlQTQuJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNWE4MDQyZjIzYzFiNGNjN2JhM2RiYzkyOTQ2MTg3M2MlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI1NDkzZGJmY2JmMjA0MTQzYTBiMzJmZWQ4Zjg0MGMzZiUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmU5NWEwODEwZDMzZDRmNmJiYTMxYmVjYzJmZThhNWExJTIyJTdE&quot;&gt;테스트 1과 결과는 같지만 락이 걸리는 형태가 매우 다릅니다.&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMiVFRCU4NSU4QyVFQyU4QSVBNCVFRCU4QSVCOCUyMDElRUElQjMlQkMlMjAlRUElQjIlQjAlRUElQjMlQkMlRUIlOEElOTQlMjAlRUElQjAlOTklRUMlQTclODAlRUIlQTclOEMlMjAlRUIlOUQlQkQlRUMlOUQlQjQlMjAlRUElQjElQjglRUIlQTYlQUMlRUIlOEElOTQlMjAlRUQlOTglOTUlRUQlODMlOUMlRUElQjAlODAlMjAlRUIlQTclQTQlRUMlOUElQjAlMjAlRUIlOEIlQTQlRUIlQTYlODUlRUIlOEIlODglRUIlOEIlQTQuJTIyJTJDJTIybWFya3MlMjIlM0ElNUIlNUQlMkMlMjJzZWxlY3Rpb25zJTIyJTNBJTVCJTVEJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyNWE4MDQyZjIzYzFiNGNjN2JhM2RiYzkyOTQ2MTg3M2MlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI1NDkzZGJmY2JmMjA0MTQzYTBiMzJmZWQ4Zjg0MGMzZiUyMiU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMmU5NWEwODEwZDMzZDRmNmJiYTMxYmVjYzJmZThhNWExJTIyJTdE&quot;&gt;결론 요약&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;MySQL(InnoDB)에서 &lt;b&gt;&lt;b&gt;INSERT INTO SELECT SHARED LOCK은&lt;span&gt; &lt;/span&gt;&lt;/b&gt;&lt;/b&gt;index가 있는 경우에만&amp;nbsp; 레코드 락으로 작동합니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;non unique index의 경우 락이 걸리는 형태가 매우 다릅니다.&lt;/li&gt;
&lt;li&gt;심지어 no index 인 경우는 전체 레코드 락으로 테이블 락과 같은 상태가 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;하지만&amp;nbsp;실무에서는&amp;nbsp;큰&amp;nbsp;문제가&amp;nbsp;없을&amp;nbsp;것으로&amp;nbsp;예상됩니다.&amp;nbsp;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 이슈는 일반적이지 않은 업무 흐름에서 수기 작업 간 조작을 잘못한 인적 실수입니다. (영업의 요청)&lt;/li&gt;
&lt;li&gt;실무에서 사용되는 테이블과 대상 컬럼은 최소한 non unique index가 잡혀 있을 것으로 기대합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 IS, IX, GAP Lock 등에 대해서는 좀 더 공부가 필요합니다.&lt;br /&gt;관련되어 잘 정리되어 있는 블로그를 공유합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1657094677308&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Lock의 종류 (Shared Lock, Exclusive Lock, Record Lock, Gap Lock, Next-key Lock)&quot; data-og-description=&quot;Lock의 종류 (Shared Lock, Exclusive Lock, Record Lock, Gap Lock, Next-key Lock)&quot; data-og-host=&quot;jaeseongdev.github.io&quot; data-og-source-url=&quot;https://jaeseongdev.github.io/development/2021/06/16/Lock%EC%9D%98-%EC%A2%85%EB%A5%98-(Shared-Lock,-Exclusive-Lock,-Record-Lock,-Gap-Lock,-Next-key-Lock)/&quot; data-og-url=&quot;https://jaeseongdev.github.io/development/2021/06/16/Lock%EC%9D%98-%EC%A2%85%EB%A5%98-(Shared-Lock,-Exclusive-Lock,-Record-Lock,-Gap-Lock,-Next-key-Lock)/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c4o3pK/hyO0QlRavM/MLjYLbejAIEFs3jAgGajtK/img.jpg?width=445&amp;amp;height=445&amp;amp;face=122_107_290_291,https://scrap.kakaocdn.net/dn/fog9D/hyO0Nimi1l/wKp1yitK6mc0BuvTIBkPVk/img.png?width=1064&amp;amp;height=810&amp;amp;face=0_0_1064_810,https://scrap.kakaocdn.net/dn/dfe4Gz/hyO0Ig3VZm/MZRGHkyMYcRRdTYiRwM3pK/img.jpg?width=445&amp;amp;height=445&amp;amp;face=122_107_290_291&quot;&gt;&lt;a href=&quot;https://jaeseongdev.github.io/development/2021/06/16/Lock%EC%9D%98-%EC%A2%85%EB%A5%98-(Shared-Lock,-Exclusive-Lock,-Record-Lock,-Gap-Lock,-Next-key-Lock)/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jaeseongdev.github.io/development/2021/06/16/Lock%EC%9D%98-%EC%A2%85%EB%A5%98-(Shared-Lock,-Exclusive-Lock,-Record-Lock,-Gap-Lock,-Next-key-Lock)/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c4o3pK/hyO0QlRavM/MLjYLbejAIEFs3jAgGajtK/img.jpg?width=445&amp;amp;height=445&amp;amp;face=122_107_290_291,https://scrap.kakaocdn.net/dn/fog9D/hyO0Nimi1l/wKp1yitK6mc0BuvTIBkPVk/img.png?width=1064&amp;amp;height=810&amp;amp;face=0_0_1064_810,https://scrap.kakaocdn.net/dn/dfe4Gz/hyO0Ig3VZm/MZRGHkyMYcRRdTYiRwM3pK/img.jpg?width=445&amp;amp;height=445&amp;amp;face=122_107_290_291');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Lock의 종류 (Shared Lock, Exclusive Lock, Record Lock, Gap Lock, Next-key Lock)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Lock의 종류 (Shared Lock, Exclusive Lock, Record Lock, Gap Lock, Next-key Lock)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jaeseongdev.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Study</category>
      <category>index</category>
      <category>innodb</category>
      <category>mysql</category>
      <category>record lock</category>
      <category>shared lock</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/49</guid>
      <comments>https://hyune-c.tistory.com/49#entry49comment</comments>
      <pubDate>Wed, 6 Jul 2022 20:09:17 +0900</pubDate>
    </item>
    <item>
      <title>ErrorMessage를 관리하는 방법</title>
      <link>https://hyune-c.tistory.com/47</link>
      <description>&lt;figure id=&quot;og_1654612670434&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-c/blogcode-errormessage&quot; data-og-description=&quot;Contribute to Hyune-c/blogcode-errormessage development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-c/blogcode-errormessage&quot; data-og-url=&quot;https://github.com/Hyune-c/blogcode-errormessage&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCyV2D/hyOGNXsfRA/SawRIRdT0HObQVKONrGLJ1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/blogcode-errormessage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Hyune-c/blogcode-errormessage&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCyV2D/hyOGNXsfRA/SawRIRdT0HObQVKONrGLJ1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-c/blogcode-errormessage&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to Hyune-c/blogcode-errormessage development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP status code는 3자리의 코드와 설명으로 표현됩니다.&lt;br /&gt;하지만 실무에서는 더 많고 다양한 종류의 에러 표현을 위해 &lt;b&gt;name, code, reason&lt;/b&gt;을 활용하곤 합니다.&lt;br /&gt;그리고 간결함과 생산성을 위해 프로젝트 내에서도 code와 reason이 직관적으로 연결되기를 기대합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;439&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9MF0R/btrEc3dxlMc/sYkfPJKG7xqSoqXACHFMcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9MF0R/btrEc3dxlMc/sYkfPJKG7xqSoqXACHFMcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9MF0R/btrEc3dxlMc/sYkfPJKG7xqSoqXACHFMcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9MF0R%2FbtrEc3dxlMc%2FsYkfPJKG7xqSoqXACHFMcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;439&quot; height=&quot;337&quot; data-origin-width=&quot;439&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 1. Enum으로 관리하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum Errorcode {

 NOT_EXIST_PRODUCT(&quot;1001&quot;, &quot;대상 상품 없음&quot;),
 OUT_OF_STOCK(&quot;1200&quot;, &quot;상품 재고 없음&quot;),
 UNKNOWN(&quot;9999&quot;, &quot;알 수 없는 에러&quot;),
 ;

 private final String code;
 private final String reason;
}


@Entity
public class Product {

 ...
 
 public void order(final Integer quantity) {
  if (this.quantity &amp;lt; quantity) {
   throw new ProductException(Errorcode.OUT_OF_STOCK);
  }

  this.quantity -= quantity;
 }
}


@RestControllerAdvice
public class ControllerAdvice {

 @ExceptionHandler(ProductException.class)
 @ResponseStatus(HttpStatus.BAD_REQUEST)
 protected ErrorResponse handleBusinessException(final ProductException ex) {
  return new ErrorResponse(ex.getErrorcode());
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작은 프로젝트에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name, code 변경 시 배포가 필요합니다.&lt;/li&gt;
&lt;li&gt;admin 개발이 필요한 경우 코드 중복이 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로젝트 밖에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전체 에러 목록을 알기 힘듭니다.&lt;/li&gt;
&lt;li&gt;메시지 국제화가 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;구현 방법이 아주 쉬워 사이드 프로젝트를 할 때 많이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;썼던&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;관리 방법이지만 단점이 꽤 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 2. MessageSource로 관리하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum Errorcode {

 NOT_EXIST_PRODUCT(&quot;1001&quot;, &quot;product.not_exist_product&quot;),
 OUT_OF_STOCK(&quot;1200&quot;, &quot;product.out_of_stock&quot;),
 UNKNOWN(&quot;9999&quot;, &quot;product.unknown&quot;),
 ;

 private final String code;
 private final String propertiesCode;
 private String reason;

 @RequiredArgsConstructor
 @Component
 public static class ErrorreasonInjector {

  private final MessageSource messageSource;

  @Value(&quot;${application.locale}&quot;)
  private String locale;

  @PostConstruct
  public void postConstruct() {
   Arrays.stream(Errorcode.values())
     .forEach(errorcode -&amp;gt; errorcode.reason = messageSource.getMessage(errorcode.getPropertiesCode(), null, Locale.forLanguageTag(this.locale)));
  }
 }
}


// messages.properties
product.not_exist_product=대상 상품 없음
product.out_of_stock=상품 재고 없음
product.unknown=알 수 없는 에러&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작은 프로젝트에 적합합니다.&lt;/li&gt;
&lt;li&gt;메시지 국제화가 용이합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name과 code 변경 시 배포가 필요합니다.&lt;/li&gt;
&lt;li&gt;admin 개발이 필요한 경우 코드 중복이 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로젝트 밖에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전체 에러 목록을 알기 힘듭니다.&lt;/li&gt;
&lt;li&gt;MessageSource는 Map 자료구조와 같이 key, value 한 쌍만을 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실무 코드와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;인프런의 김영한님 스프링 MVC 2편 - 메시지, 국제화&lt;/b&gt;를 보고 구현했습니다.&lt;br /&gt;&lt;span&gt;하지만 여전히 STEP&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;1의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단점을 가지고 있으며, 에러 표현에 필요한 name, code, reason의 값을 보여주기 힘듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 3. DB에서 가져오기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum Errorcode {

 NOT_EXIST_PRODUCT,
 OUT_OF_STOCK,
 UNKNOWN;

 private String code;
 private String reason;

 @RequiredArgsConstructor
 @Component
 public static class ErrorreasonInjector {

  private final ErrorcodeDataRepository repository;

  @Value(&quot;${application.locale}&quot;)
  private String locale;

  @PostConstruct
  public void postConstruct() {
   Arrays.stream(Errorcode.values())
     .forEach(errorcode -&amp;gt;
       repository.findByNameAndLocale(errorcode.name(), locale)
         .ifPresent(e -&amp;gt; {
            errorcode.code = e.getCode();
            errorcode.reason = e.getReason();
           }
         )
     );
  }
 }
}


// data.sql
insert into errorcode_data(locale, name, code, reason)
values ('ko', 'NOT_EXIST_PRODUCT', '1001', '대상 상품 없음'),
       ('ko', 'OUT_OF_STOCK', '1200', '상품 재고 없음'),
       ('ko', 'UNKNOWN', '9999', '알 수 없는 에러');&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name, code, reason을 모두 표현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;메시지 국제화가 용이합니다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로젝트 밖에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전체 에러 목록을 알기 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name 변경시 배포가 필요합니다.&lt;/li&gt;
&lt;li&gt;DB 관리가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 문제로 있던 것들을 조합하여 DB를 통해 구현했습니다.&lt;br /&gt;name은 의도적으로 프로젝트 내에 두어 직관성을 높였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드로 구현하지는 않았지만 더 생각해볼 것도 있습니다.&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;EnumMap&lt;/b&gt;을 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;DB 검색을 하는 STEP 3에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;캐시&lt;/b&gt;를 활용할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빠른 변경 반영을 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;evict 정책, 백도어 API, 또는 pub/sub를 통한 갱신&lt;/b&gt;을 도입할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;에러 메시지를 동적으로 관리하고 싶다면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;String.format()&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태로 만들고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;CustomException&lt;/b&gt;을 만들 때 매개변수를 받을 수 있습니다.&lt;/li&gt;
&lt;li&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;CustomException&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;생성 시 별도의 필드를 활용할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;완벽한 국제화는 &lt;b&gt;BCP47&lt;/b&gt; 등 고려해야될 점이 더 많습니다.&lt;/li&gt;
&lt;li&gt;백엔드는 &lt;b&gt;code&lt;/b&gt;만 관리하고 사용자에게 노출되는 &lt;b&gt;reason&lt;/b&gt;은 프론트엔드가 관리하게 하는 방법도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;지금의 회사는 조금 다른 형태로 사용하고 있습니다.&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 에러코드만으로 예외를 throw
throw new CustomException(&quot;4012&quot;);

// Supplier를 통해 네이밍은 했지만 일괄 검색은 힘듬
public static final Supplier&amp;lt;CustomException&amp;gt; ERROR_SLIP_DTL_NOT_FOUND = CustomException.supply(&quot;4012&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String의 4자리 숫자 code를 MessageSource의 find code 값으로 활용하고 있습니다.&lt;br /&gt;&lt;b&gt;MessageSource&lt;/b&gt; 기술을 활용하고 있긴 애매한 부분이 있다고 생각합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금의 회사는 국제화를 고려하는 단계가 아닙니다.&lt;/li&gt;
&lt;li&gt;code가 String이기에 &lt;b&gt;type safe&lt;/b&gt;하지 않습니다.&lt;/li&gt;
&lt;li&gt;프로젝트 내에서 직관적인 &lt;b&gt;code - reason 매핑&lt;/b&gt;이 힘듭니다.&lt;/li&gt;
&lt;li&gt;매핑을 위해서는 별도 관리되는 엑셀이나 DB를 직접 검색해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;제가 선호하는 방식은 STEP 2와 STEP 3입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 코드는 일반적인 도메인과 다른 성격을 가지고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 한번 만들어진 code는 변경되지 않습니다.&lt;/li&gt;
&lt;li&gt;새로운 에러 유형이 생기면 새로운 code를 생성합니다.&lt;/li&gt;
&lt;li&gt;반면 reason은 좀 더 구체화되거나 에러를 나누는 등 변경될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 관점에서 볼 때 STEP 2의 방법은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;reason만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;messages.properties&lt;/b&gt;로 관리됩니다.&lt;/li&gt;
&lt;li&gt;properties 파일을 공유하는 방식으로 프로젝트 외부에서도 접근할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 STEP 3의 방법은 DB 관리가 필요하긴 하지만 간결함과 생산성을 모두 가져갈 수도 있습니다.&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>error message</category>
      <category>MessageSource</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/47</guid>
      <comments>https://hyune-c.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 15 Jun 2022 02:29:16 +0900</pubDate>
    </item>
    <item>
      <title>레거시 코드 개선하기 with delegate pattern</title>
      <link>https://hyune-c.tistory.com/46</link>
      <description>&lt;figure id=&quot;og_1654398132625&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-c/blogcode-delegate&quot; data-og-description=&quot;Contribute to Hyune-c/blogcode-delegate development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-c/blogcode-delegate&quot; data-og-url=&quot;https://github.com/Hyune-c/blogcode-delegate&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cI9mbI/hyOEoR6jTZ/kyY2Pc5E5gbh8DrVvU4TCK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/blogcode-delegate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Hyune-c/blogcode-delegate&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cI9mbI/hyOEoR6jTZ/kyY2Pc5E5gbh8DrVvU4TCK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-c/blogcode-delegate&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to Hyune-c/blogcode-delegate development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 1. 개선 전 코드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/blogcode-delegate/commit/7ac445974e1fc497509813303d80151ce705ce1c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Hyune-c/blogcode-delegate/commit/7ac445974e1fc497509813303d80151ce705ce1c&lt;/a&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AWS S3 조작을 담당하는&lt;span&gt;&amp;nbsp;서비스로&amp;nbsp;&lt;/span&gt;&lt;/span&gt;개선 전 코드는 중복 코드와 확장성에 큰 단점을 가지고 있었습니다.&lt;br /&gt;초기에는 연관된 로직이 적어 괜찮았지만, 그것을 감안하더라도 가독성과 확장에 취약한 코드였습니다.&lt;br /&gt;그러던 중 S3 버킷 추가의 요건이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 2. 코드 개선&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btQiPj/btrDYe0WmLV/NDkKAeMLNjm6GaLeoYbnT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btQiPj/btrDYe0WmLV/NDkKAeMLNjm6GaLeoYbnT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btQiPj/btrDYe0WmLV/NDkKAeMLNjm6GaLeoYbnT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtQiPj%2FbtrDYe0WmLV%2FNDkKAeMLNjm6GaLeoYbnT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;391&quot; height=&quot;278&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버킷 추가를 하기 전 기존 코드의 개선을 진행했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Properties&amp;nbsp;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기존의 코드는 service 내에 accessKey와 secretKey가 노출되어 있었고, 매 요청마다 amazonS3 오브젝트를 생성했습니다.&lt;/blockquote&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
@RequiredArgsConstructor
public class AmazonS3Beans {

 public static final Regions CLIENT_REGION = Regions.AP_NORTHEAST_2;

 @Value(&quot;${cloud.aws.credentials.accessKey}&quot;)
 private String accessKey;

 @Value(&quot;${cloud.aws.credentials.secretKey}&quot;)
 private String secretKey;

 @Bean
 public AmazonS3 amazonS3() {
  final BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
  return AmazonS3ClientBuilder.standard()
    .withCredentials(new AWSStaticCredentialsProvider(credentials))
    .withRegion(CLIENT_REGION)
    .build();
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;amazonS3을 재사용하기 위해 bean으로 선언했습니다.&lt;/li&gt;
&lt;li&gt;이후 코드에서는 accessKey, secretKey가 노출되지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface AmazonS3Properties {

 String getStagePath();
 String getBucketName();

 default String getProvendorContentUrl() {
  throw new UnsupportedOperationException(&quot;잘못된 호출 입니다.&quot;);
 }
}


@Component
public class AmazonS3PublicProperties implements AmazonS3Properties {

 @Value(&quot;${env.profile}&quot;)
 private String stagePath;

 @Value(&quot;${cloud.aws.provendor.content}&quot;)
 private String provendorContentUrl;

 @Value(&quot;${cloud.aws.bucket.public}&quot;)
 private String bucketName;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;public 버킷은 url 연결이 가능한 도메인을 가집니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;인터페이스를 통해 버킷의 설정 값을 다르게 설정하고 가져올 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Request 생성 책임 Provider&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기존의 코드는 request object의 생성 책임 구분이 없었고, 비슷한 구현이 반복되었습니다.&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public abstract class AbstractAmazonS3UploadRequestProvider {

 private final String stagePath;
 private final String bucketName;

 protected AbstractAmazonS3UploadRequestProvider(final AmazonS3Properties properties) {
  this.stagePath = properties.getStagePath();
  this.bucketName = properties.getBucketName();
 }

 public PutObjectRequest createRequest(final File file) {
  return createRequest(file.getName(), file);
 }

 public abstract PutObjectRequest createRequest(final String sourceName, final File file);

 protected PutObjectRequest createRequest(final String objectKey, final File file, final CannedAccessControlList cannedAclHeader) {
  final PutObjectRequest request = new PutObjectRequest(bucketName, objectKey, file);
  request.setMetadata(ObjectMetadataType.getMetadata(file));
  request.withCannedAcl(cannedAclHeader);

  return request;
 }

 protected String generateObjectKey(final String sourceName) {
  final LocalDateTime now = LocalDateTime.now();
  final String hash = LocalDateTimeUtils.toIsoFormat(now) + &quot;_&quot; + DigestUtils.md5Hex(String.valueOf(now.getNano()));
  return stagePath + &quot;/&quot; + hash + &quot;/&quot; + sourceName;
 }

 @Getter
 @RequiredArgsConstructor
 public enum ObjectMetadataType {

  CSV(&quot;text/csv; charset=UTF-8&quot;),
  XLSX(&quot;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8&quot;),
  XLS(&quot;application/vnd.ms-excel; charset=UTF-8&quot;),
  HTML(&quot;text/html; charset=UTF-8&quot;);

  ...
 }
}


@Component
public class AmazonS3PublicUploadRequestProvider extends AbstractAmazonS3UploadRequestProvider {

 private static final CannedAccessControlList CANNED_ACL_HEADER = CannedAccessControlList.PublicRead;

 public AmazonS3PublicUploadRequestProvider(final AmazonS3PublicProperties properties) {
  super(properties);
 }

 @Override
 public PutObjectRequest createRequest(final String sourceName, final File file) {
  final String objectKey = generateObjectKey(sourceName);
  return createRequest(objectKey, file, CANNED_ACL_HEADER);
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 업로드를 위한 Request 생성 책임을 Provider로 모았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;생성자를 통해 설정을 주입하며, 중복 코드를 줄였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;사용자는 오픈된 2개의 createRequest() 메서드를 통해 PutObjectRequest를 생성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;나노초 값을 시드로 한 해시를 통해 중복되지 않는 object path를 구현했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;공개 여부는(CANNED_ACL_HEADER) 구현 클래스에서 정의합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;3. Delegate 클래스&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기존의 코드는 특정 목적을 위한 메서드를 새로 만들었습니다.&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public abstract class AbstractAmazonS3Service {

 private final AmazonS3 amazonS3;
 private final String bucketName;

 protected AbstractAmazonS3Service(final AmazonS3Properties properties, final AmazonS3 amazonS3) {
  this.amazonS3 = amazonS3;
  this.bucketName = properties.getBucketName();
 }

 public PutObjectResult putObject(final PutObjectRequest request) {
  try {
   return amazonS3.putObject(request);
  } catch (final SdkClientException e) {
   log.error(e.getClass().getName(), e);
   throw new IllegalStateException();
  }
 }

 public String getObjectFullPath(final String objectKey) {
  return amazonS3.getUrl(bucketName, objectKey).getPath();
 }

 public void getObject(final String objectKey, final File targetFile) {
  amazonS3.getObject(new GetObjectRequest(bucketName, objectKey), targetFile);
 }
}


public class AmazonS3PublicService extends AbstractAmazonS3Service {

 public AmazonS3PublicService(final AmazonS3PublicProperties properties, final AmazonS3 amazonS3) {
  super(properties, amazonS3);
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3를 조작을 위임하는 클래스를 구현했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;생성자를 통해 설정을 주입하며, 중복 코드를 줄였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;S3만 조작하도록 수정하고, 특화 로직처럼&amp;nbsp;보이던 네이밍을 수정했습니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오브젝트 추가, 오브젝트 주소 가져오기, 오브젝트 가져오기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 3. 버킷 추가&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TjBPD/btrD1gcoG95/0xwnnwZDqsSTPkblhGCfY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TjBPD/btrD1gcoG95/0xwnnwZDqsSTPkblhGCfY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TjBPD/btrD1gcoG95/0xwnnwZDqsSTPkblhGCfY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTjBPD%2FbtrD1gcoG95%2F0xwnnwZDqsSTPkblhGCfY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;422&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class AmazonS3PrivateProperties implements AmazonS3Properties {

 @Value(&quot;${env.profile}&quot;)
 private String stagePath;

 @Value(&quot;${cloud.aws.bucket.private}&quot;)
 private String bucketName;
}


@Component
public class AmazonS3PrivateUploadRequestProvider extends AbstractAmazonS3UploadRequestProvider {

 private static final CannedAccessControlList CANNED_ACL_HEADER = CannedAccessControlList.Private;

 public AmazonS3PrivateUploadRequestProvider(final AmazonS3PrivateProperties properties) {
  super(properties);
 }

 @Override
 public PutObjectRequest createRequest(final String sourceName, final File file) {
  final String objectKey = generateObjectKey(sourceName);
  return createRequest(objectKey, file, CANNED_ACL_HEADER);
 }
}


@Service
public class AmazonS3PrivateService extends AbstractAmazonS3Service {

 public AmazonS3PrivateService(final AmazonS3PrivateProperties properties, final AmazonS3 amazonS3) {
  super(properties, amazonS3);
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 버킷이 추가되어도&amp;nbsp;&lt;b&gt;Properties, Provider, Service&lt;/b&gt;만 추가하면 되어 영향도를 최소화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 4. 사용 코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class SomethingWithExcelService {

 private final AbstractAmazonS3UploadRequestProvider amazonS3DelegateProvider;
 private final AbstractAmazonS3Service amazonS3Delegator;

 public SomethingWithExcelService(final AmazonS3CommonPrivateUploadRequestProvider amazonS3DelegateProvider, final AmazonS3PrivateService amazonS3Delegator) {
  this.amazonS3DelegateProvider = amazonS3DelegateProvider;
  this.amazonS3Delegator = amazonS3Delegator;
 }

 public void doSomething(final File file) {
  // 전처리 ...

  final PutObjectRequest request = amazonS3DelegateProvider.createRequest(file);
  amazonS3Delegator.putObject(request);

  // 후처리 ...
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 업로드에 필요한 버킷을 생성자 주입하기에 유연한 변경이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;조금 더 생각해 볼 것&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에는 파일 조작 방법으로 S3 이외의 것이 들어올 수 있다고 생각해 Facade 패턴도 고려했습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Facade 패턴을 사용하면 사용되는 service를 package-private으로 숨길 수 있습니다.&lt;/li&gt;
&lt;li&gt;하지만 S3 이외의 파일 조작을 하는 것은 너무 먼 미래일 것 같아 지금의 개선을 유지했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Provider를 Service의 inner class로 이동하면 좀 더 간결한 코드가 될 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 코드 리뷰 결과 클래스가 커지는 것보다는 분리하는 것이 좋겠다는 의견을 수렴했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;220611&lt;/b&gt; 아래의 내용은 피드백을 받아 수정한 내용으로 회사코드까지는 적용되지 않았습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;STEP 5. 다중&amp;nbsp;상속을&amp;nbsp;활용한&amp;nbsp;Properties&amp;nbsp;구조화&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eKdNR3/btrExYPkqsT/KHZhlfoJBtvizKvbizUF90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eKdNR3/btrExYPkqsT/KHZhlfoJBtvizKvbizUF90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eKdNR3/btrExYPkqsT/KHZhlfoJBtvizKvbizUF90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeKdNR3%2FbtrExYPkqsT%2FKHZhlfoJBtvizKvbizUF90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;202&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;STEP 3의 &lt;b&gt;properties&lt;/b&gt;는 하나의 타입과 2개의 구현체로 설계했었습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;슈퍼 타입과 서브 타입을 통해 개선했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;STEP 3는 &lt;b&gt;AmazonS3Properties&lt;/b&gt;가 3개의 메서드를 가지고 있었습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;getProvendorContentUrl()&lt;/b&gt; 메서드의 사용을 public 서브 타입에서만 접근할 수 있도록 개선했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;조금 더 생각해볼 것 2&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AmazonS3Client는 singleton bean으로 생성되지만, ThreadSafe하기에 지금의 코드를 유지해도 괜찮습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8hqpK/btrEuVMCSva/glr0XaTsFz7xMvgm7HH6l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8hqpK/btrEuVMCSva/glr0XaTsFz7xMvgm7HH6l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8hqpK/btrEuVMCSva/glr0XaTsFz7xMvgm7HH6l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8hqpK%2FbtrEuVMCSva%2Fglr0XaTsFz7xMvgm7HH6l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;61&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;figure id=&quot;og_1654881204811&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;AmazonS3Client (AWS SDK for Java - 1.12.237)&quot; data-og-description=&quot;This action filters the contents of an Amazon S3 object based on a simple structured query language (SQL) statement. In the request, along with the SQL expression, you must also specify a data serialization format (JSON, CSV, or Apache Parquet) of the obje&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AmazonS3Client (AWS SDK for Java - 1.12.237)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This action filters the contents of an Amazon S3 object based on a simple structured query language (SQL) statement. In the request, along with the SQL expression, you must also specify a data serialization format (JSON, CSV, or Apache Parquet) of the obje&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Series/실전!</category>
      <category>delegate</category>
      <category>레거시 개선</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/46</guid>
      <comments>https://hyune-c.tistory.com/46#entry46comment</comments>
      <pubDate>Sun, 5 Jun 2022 11:32:16 +0900</pubDate>
    </item>
    <item>
      <title>for vs stream</title>
      <link>https://hyune-c.tistory.com/45</link>
      <description>&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=by8hb75i9X4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/AjvWM/hyOCD1oeu0/u7KhyBtO5eH4H4ZsSa799k/img.jpg?width=480&amp;amp;height=360&amp;amp;face=305_141_415_186&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/by8hb75i9X4&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 공부했던 내용이지만 잘 설명해준 유튜브를 보고 내용을 정리해 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기본 정보&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;for, foreach&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구체적인 로직이 외부에 노출되는 외부 반복의 형태입니다.&lt;/li&gt;
&lt;li&gt;for문은 java 1, foreach문은 java 5부터 등장했습니다.&lt;/li&gt;
&lt;li&gt;foreach문은 컴파일 시 for문으로 변환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;stream&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구체적인 로직이 외부에 노출되지 않는 내부 반복의 형태입니다.&lt;/li&gt;
&lt;li&gt;함수형 프로그래밍 패러다임이 도입된 java 8부터 등장했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;익숙함의 차이에서 오는 &lt;span&gt;가독성의&amp;nbsp;&lt;/span&gt;호불호가 있습니다.&lt;/li&gt;
&lt;li&gt;디버깅 난이도의 차이가 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;for&lt;/b&gt;&amp;nbsp;에러 발생 위치가 바로 노출됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;stream&lt;/b&gt; 지연 연산을 통해 실행되기에 에러 발생 시 위치를 추적해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;병렬 처리 구현의 차이가 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;for&lt;/b&gt; 구체적인 로직을 나누어 구현하고 합쳐야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;stream&lt;/b&gt; parallelStream()으로 쉽게 처리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;1072&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRurJr/btrDF9Svvud/ROILW8PmN9XtKD5k3fedAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRurJr/btrDF9Svvud/ROILW8PmN9XtKD5k3fedAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRurJr/btrDF9Svvud/ROILW8PmN9XtKD5k3fedAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRurJr%2FbtrDF9Svvud%2FROILW8PmN9XtKD5k3fedAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1888&quot; height=&quot;1072&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;1072&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;성능 차이&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;공부했을 때 가장 고민한 부분으로 &lt;b&gt;primitive/array&lt;/b&gt;에서는 차이가 있지만, &lt;b&gt;wrapper/collection&lt;/b&gt;에서는 큰 차이가 없습니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;stream은 for문에 비해 최적화가 잘 이루어지지 않았습니다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stream이 for문보다 늦게 나왔기 때문입니다.&lt;/li&gt;
&lt;li&gt;하지만 이전과 다르게 java가 빠르게 업데이트되고 있기에 개선을 기대합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;a title=&quot;https://www.oracle.com/java/technologies/java-se-support-roadmap.html&quot; href=&quot;https://www.oracle.com/java/technologies/java-se-support-roadmap.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.oracle.com/java/technologies/java-se-support-roadmap.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;- 매년 두 번 non-LTS 릴리즈, 날짜는 3월이랑 9월&lt;br /&gt;- 2년마다 LTS 릴리즈&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;stream은 for문에 비해 오버헤드가 발생합니다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stream 사용을 위해서는 boxing이 필요하지만, for문은 인덱스에 바로 접근하여 오버헤드가 발생하지 않습니다.&lt;/li&gt;
&lt;li&gt;하지만 프로덕션 코드에서는 boxing된 collection이 주로 사용되기에 큰 차이는 아니라고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;나의 사용법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 &lt;b&gt;프로덕션 코드에서 stream과 boxing-unboxing 최소화를 위해 &lt;span&gt;wrapper type 사용을&lt;/span&gt;&amp;nbsp;선호&lt;/b&gt;합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개인적으로는 가독성이 훨씬 좋다고 생각합니다.&lt;/li&gt;
&lt;li&gt;JDK 발전에 따라 지원되는 API와 최적화 혜택을 받을 수 있습니다.&lt;/li&gt;
&lt;li&gt;코딩 스타일의 일관성을 가져갈 수 있습니다.&lt;/li&gt;
&lt;li&gt;나아가 메서드/클래스 분리나 &lt;span&gt;함수형 프로그래밍으로의 전환이&lt;/span&gt;&amp;nbsp;용이합니다.&lt;/li&gt;
&lt;li&gt;primitive 사용이 메모리나 속도면에서 좋다는 주장도 있지만,&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;type을 일관되게 사용함으로 인해 boxing을 줄입니다.&lt;/li&gt;
&lt;li&gt;현대 개발은 변경에 강하도록 가독성을 올리고 비용을 더 들이는 것이 낫다고 생각합니다.&lt;/li&gt;
&lt;li&gt;조금의 속도 차이는 감수할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일부 최적화된 성능이 필요하거나, 공통 모듈에서는 primitive와 for문을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study</category>
      <category>FOR</category>
      <category>Java</category>
      <category>Stream</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/45</guid>
      <comments>https://hyune-c.tistory.com/45#entry45comment</comments>
      <pubDate>Wed, 1 Jun 2022 18:33:48 +0900</pubDate>
    </item>
    <item>
      <title>Entity의 field type은 무엇이 적합할까?</title>
      <link>https://hyune-c.tistory.com/44</link>
      <description>&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Entity
public class Accommodation {

    @Id
    private Long id;

    private String locationName;
    private String description;
    private String imagePath;

    private int pricePerDay; // primitive type?
    private int positionX;
    private int positionY;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 리뷰를 한 코드 중 일부입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 entity의 필드는 항상 wrapper type으로만 사용했기에 이 코드를 보았을 때 어색함을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 근거 있는 설득을 하기 위해 관련 자료를 조사했고 그 기록을 남겨봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ID&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The primary key, or the property or field of a composite primary key, must be one of the following Java language types:&lt;br /&gt;- Java primitive types&lt;br /&gt;- Java primitive wrapper type&lt;br /&gt;- sjava.lang.String&lt;br /&gt;- java.util.Date (the temporal type should be DATE)&lt;br /&gt;- java.sql.Date&lt;br /&gt;- java.math.BigDecimal&lt;br /&gt;- java.math.BigInteger&lt;br /&gt;&lt;br /&gt;&lt;a title=&quot;https://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#bnbqf&quot; href=&quot;https://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#bnbqf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#bnbqf&lt;/a&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA 공식 문서를 보면 primitive types 도 허용하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;We recommend that you declare consistently-named identifier attributes on persistent classes and that you use a nullable (i.e., non-primitive) type.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#entity-pojo-identifier&quot;&gt;https://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#entity-pojo-identifier&lt;/a&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-slate-fragment=&quot;JTdCJTIyb2JqZWN0JTIyJTNBJTIyZG9jdW1lbnQlMjIlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTJDJTIybm9kZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJibG9jayUyMiUyQyUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJpc1ZvaWQlMjIlM0FmYWxzZSUyQyUyMmRhdGElMjIlM0ElN0IlN0QlMkMlMjJub2RlcyUyMiUzQSU1QiU3QiUyMm9iamVjdCUyMiUzQSUyMnRleHQlMjIlMkMlMjJsZWF2ZXMlMjIlM0ElNUIlN0IlMjJvYmplY3QlMjIlM0ElMjJsZWFmJTIyJTJDJTIydGV4dCUyMiUzQSUyMlNwcmluZyVFQyU5RCU5OCUyMCVFQSVCMCU4MCVFQyU5RSVBNSUyMCVFQyU5RCVCQyVFQiVCMCU5OCVFQyVBMCU4MSVFQyU5RCVCOCUyMCVFQSVCNSVBQyVFRCU5OCU4NCVFQyVCMiVCNCVFQyU5RCVCOCUyMEhpYmVybmF0ZSVFQyU5RCU5OCUyMCVFQiVBQyVCOCVFQyU4NCU5QyVFQiVBNSVCQyUyMCVFQiVCMyVCNCVFQiVBOSVCNCUyMCVFQiVBQSU4NSVFQyU4QiU5QyVFQyVBMCU4MSVFQyU5QyVCQyVFQiVBMSU5QyUyMG5vbi1wcmltaXRpdmUlMjB0eXBlJUVDJTlEJTk4JTIwJUVDJTgyJUFDJUVDJTlBJUE5JUVDJTlEJTg0JTIwJUVBJUI2JThDJUVDJTlFJUE1JUVEJTk1JTk4JUVBJUIzJUEwJTIwJUVDJTlFJTg4JUVDJThBJUI1JUVCJThCJTg4JUVCJThCJUE0LiUyMiUyQyUyMm1hcmtzJTIyJTNBJTVCJTVEJTJDJTIyc2VsZWN0aW9ucyUyMiUzQSU1QiU1RCU3RCU1RCUyQyUyMmtleSUyMiUzQSUyMjUyMGQ1MTliZDk5NjQ4Y2Y4OTJiNGQ1YTQ0ZGE3YmUxJTIyJTdEJTVEJTJDJTIya2V5JTIyJTNBJTIyZmE3MGQ2NDgxZWQ2NGRjNzk0MzFkZjM3NjA2NjM4NTAlMjIlN0QlNUQlMkMlMjJrZXklMjIlM0ElMjI0YjUyYWM5NDc3MTM0NDlhYTMzZTE0MzAzYzZhMzFhNCUyMiU3RA==&quot;&gt;가장 일반적인 구현체인 Hibernate의 문서를 보면 명시적으로 non-primitive type의 사용을 권장하고 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;id를 primitive type으로 하면 0과 null을 구분할 수 없습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;primitive type의 기본 값이 0이기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그렇기 때문에 null이 허용되는 wrapper type을 활용해야 제대로 구분할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a id=&quot;user-content-except-id&quot; href=&quot;https://github.com/Hyune-c/study-gitbook/blob/master/framework-and-library/jpa/entity-field-type.md#except-id&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;&lt;b&gt;ID가 아닌 값&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &lt;b&gt;ID가 아닌 값&lt;/b&gt;은 제 평소 사용에 대한 명백한 근거를 찾기 힘들었습니다.&lt;br /&gt;하지만 저와 비슷한 고민을 한 스택 오버 플로우의 질문을 찾았고 그것을 기반으로 제 생각을 좀 더 정리해보았습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터의 null 여부를 판단할 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;null 데이터도 가져올 수 있어야 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;혹자는 null 데이터의 존재 자체를 문제로 말할 수 있지만, 경험상 실무 DB에는 null인 컬럼이 꽤 존재했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;소수 필드의&amp;nbsp;&lt;b&gt;boxing-unboxing&lt;/b&gt;은 배제합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨팅 능력이 충분히 저렴해지고 높아졌습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;boxing-unboxing&lt;/b&gt; 이슈는 반복문과 같이 사용될 때 발생한다고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코딩 스타일의 일관성을 가져갈 수 있습니다.&lt;/li&gt;
&lt;li&gt;일부&amp;nbsp;&lt;b&gt;@Ttransient&lt;/b&gt;&amp;nbsp;필드에 대해서는 사용하기도 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 이유로 저는 &lt;b&gt;Entity의 모든 필드를 wrapper type으로 사용&lt;/b&gt;하는 걸 선호합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1653489174379&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Entity members should they be primitive data types or java data types?&quot; data-og-description=&quot;Is there a difference in declaring the enabled variable as Boolean or boolean? Which is preferable from a memory footprint perspective. @Entity class User { @Column Boolean enabled; }&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/2331630/entity-members-should-they-be-primitive-data-types-or-java-data-types&quot; data-og-url=&quot;https://stackoverflow.com/questions/2331630/entity-members-should-they-be-primitive-data-types-or-java-data-types&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NYAxj/hyOxkIu4L6/HmmccDw5CKXDg1MJ3cK9pK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2331630/entity-members-should-they-be-primitive-data-types-or-java-data-types&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/2331630/entity-members-should-they-be-primitive-data-types-or-java-data-types&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NYAxj/hyOxkIu4L6/HmmccDw5CKXDg1MJ3cK9pK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Entity members should they be primitive data types or java data types?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Is there a difference in declaring the enabled variable as Boolean or boolean? Which is preferable from a memory footprint perspective. @Entity class User { @Column Boolean enabled; }&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Study</category>
      <category>Entity</category>
      <category>Field type</category>
      <category>JPA</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/44</guid>
      <comments>https://hyune-c.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 25 May 2022 23:15:43 +0900</pubDate>
    </item>
    <item>
      <title>현재 환경에 맞는 값을 가져오기 with enum</title>
      <link>https://hyune-c.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로직은 같지만 개발과 운영에서 필요한 값이 다른 경우가 있는데, 대표적으로 url이 있습니다.&lt;br /&gt;지금의 회사에서는 enum을 통해 관리하고 있지만, 그 방식이 enum 스럽지 않다고 생각하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 기존&lt;span&gt; &lt;/span&gt;&lt;b&gt;test - dev - stage - prod&lt;/b&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;구성되었던 환경이&lt;span&gt; &lt;/span&gt;&lt;b&gt;dev - qa - stage - prod&lt;/b&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;변경되었고, 이 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;과정에서 코드를 개선한 내용을 기록해 봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Before&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 변수 enum&lt;/h3&gt;
&lt;pre id=&quot;code_1652883157544&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum DeploymentEnvironment {

	DEV, QA, STAGE, PROD;

	private static final String AUTH_SERVER_NAME = Optional.ofNullable(System.getenv(&quot;AUTH_SERVER_NAME&quot;))
			.orElse(&quot;&quot;);

	public static boolean isDev() {
		return AUTH_SERVER_NAME.equals(DEV.name().toLowerCase(Locale.ROOT));
	}

	public static boolean isQA() {
		return AUTH_SERVER_NAME.equals(QA.name().toLowerCase(Locale.ROOT));
	}

	public static boolean isStage() {
		return AUTH_SERVER_NAME.equals(STAGE.name().toLowerCase(Locale.ROOT));
	}

	public static boolean isProd() {
		return AUTH_SERVER_NAME.equals(PROD.name().toLowerCase(Locale.ROOT));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 구분이 enum으로 존재하긴 하지만 모든 환경을 확인하기 위해서는 4개나 되는 if-else가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시 1&lt;/h3&gt;
&lt;pre id=&quot;code_1652883324844&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// swagger 설정
public class OpenApiConfig {

	private OpenAPI getOpenAPI() {
		if (DeploymentEnvironment.isDev()) {
			return new OpenAPI()
					.addServersItem(new Server().url(&quot;https://dev.aaa.co.kr/&quot;))
					.addServersItem(new Server().url(&quot;http://localhost:8820/&quot;));
		}

		if (DeploymentEnvironment.isQA()) {
			return new OpenAPI()
					.addServersItem(new Server().url(&quot;https://qa.aaa.co.kr/&quot;))
					.addServersItem(new Server().url(&quot;http://localhost:8820/&quot;));
		}

		if (DeploymentEnvironment.isStage()) {
			return new OpenAPI()
					.addServersItem(new Server().url(&quot;https://stage.aaa.co.kr/&quot;))
					.addServersItem(new Server().url(&quot;http://localhost:8820/&quot;));
		}

		if (DeploymentEnvironment.isProd()) {
			return new OpenAPI()
					.addServersItem(new Server().url(&quot;https://api.aaa.com/&quot;))
					.addServersItem(new Server().url(&quot;http://localhost:8820/&quot;));
		}

		return new OpenAPI()
				.addServersItem(new Server().url(&quot;http://localhost:8820/&quot;));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경별로 다른 url을 설정하기 위해서는 4번의 if-else를 사용해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시 2&lt;/h3&gt;
&lt;pre id=&quot;code_1652883497701&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum BBBCallbackRequest {

	UPDATE_USER_ID(HttpMethod.PATCH, () -&amp;gt; getBaseUrl() + &quot;/api/shop/user&quot;),
	;

	private final HttpMethod httpMethod;
	private final Supplier&amp;lt;String&amp;gt; urlSupplier;

	private static String getBaseUrl() {
		if (DeploymentEnvironment.isProd()) {
			return &quot;https://shop.bbb.com&quot;;
		}

		if (DeploymentEnvironment.isStage()) {
			return &quot;https://shopstage.bbb.co.kr&quot;;
		}

		if (DeploymentEnvironment.isQA()) {
			return &quot;https://shopqa.bbb.co.kr&quot;;
		}

		throw new IllegalStateException(&quot;local, dev 에서는 콜백하지 않습니다.&quot;);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어는 환경에 따라 예외를 발생시켜야 되는 경우도 있었습니다.&lt;br /&gt;무엇보다도 url이 곳곳에 분산되어 관리하기 힘들었고 변경에 들어가는 공수도 컸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;After&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 변수 enum&lt;/h3&gt;
&lt;pre id=&quot;code_1652884193095&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum DeploymentEnvironment {

	LOCAL(&quot;&quot;, &quot;&quot;),
	DEV(&quot;&quot;, &quot;https://dev.aaa.co.kr/&quot;),
	QA(&quot;https://shopqa.bbb.co.kr&quot;, &quot;https://qa.aaa.co.kr/&quot;),
	STAGE(&quot;https://shopstage.bbb.co.kr&quot;, &quot;https://stage.aaa.co.kr/&quot;),
	PROD(&quot;https://shop.bbb.com&quot;, &quot;https://api.aaa.com/&quot;);

	public static final DeploymentEnvironment CURRENT_DEPLOYMENT_ENVIRONMENT;

	private final String bbbCallbackUrl; 
	private final String baseUrl;

	static {
		final String authServerName = Optional.ofNullable(System.getenv(&quot;AUTH_SERVER_NAME&quot;))
				.orElse(&quot;&quot;)
				.toUpperCase(Locale.ROOT);
		CURRENT_DEPLOYMENT_ENVIRONMENT = Arrays.stream(DeploymentEnvironment.values())
				.filter(environment -&amp;gt; environment.name().equals(authServerName))
				.findFirst()
				.orElseThrow(() -&amp;gt; new IllegalArgumentException(&quot;알 수 없는 환경 변수&quot;));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 환경을 &lt;b&gt;CURRENT_DEPLOYMENT_ENVIRONMENT&lt;/b&gt;로 정의하고 정적 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;pre id=&quot;code_1652884368415&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 사용 예시 1 
public class OpenApiConfig {

	private static OpenAPI getOpenAPI() {
		if (CURRENT_DEPLOYMENT_ENVIRONMENT.getBaseUrl().isBlank()) {
			return new OpenAPI().addServersItem(new Server().url(&quot;http://localhost:8820/&quot;));
		}

		return new OpenAPI().addServersItem(new Server().url(CURRENT_DEPLOYMENT_ENVIRONMENT.getBaseUrl()));
	}
}

// 사용 예시 2
public enum BBBCallbackRequest {

	UPDATE_USER_ID(
			HttpMethod.PATCH, () -&amp;gt; CURRENT_DEPLOYMENT_ENVIRONMENT.getBbbCallbackUrl() + &quot;/api/v2/shop/user&quot;),
	;
    
	private final HttpMethod httpMethod;
	private final Supplier&amp;lt;String&amp;gt; urlSupplier;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 예시가 극단적으로 간결해졌습니다.&lt;br /&gt;무엇보다도 enum을 통해 코드 응집도를 높였기에 환경이 변경되어도 enum만 수정하면 되는 것이 가장 큰 장점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시는 2개뿐이지만 실제로는 더 많은 사용이 있었고, 이 수정을 통해 미래의 변경 영향도도 축소할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 환경 변수 관리가 더 민감했다면 &lt;b&gt;Spring Cloud Config&lt;/b&gt; 등을 활용할 수 있겠지만, 그것 또한 구성과 운영의 어려움이 있고 지금의 회사에서도 &lt;b&gt;Dockerfile&lt;/b&gt;을 통한 관리 정도는 하기에 괜찮다고 이 정도의 개선만 진행했습니다.&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>Enum</category>
      <category>환경변수</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/43</guid>
      <comments>https://hyune-c.tistory.com/43#entry43comment</comments>
      <pubDate>Wed, 18 May 2022 23:25:25 +0900</pubDate>
    </item>
    <item>
      <title>분산 트랜잭션 설계하기 (초급)</title>
      <link>https://hyune-c.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;분산 트랜잭션 관리는 MSA 운영의 핵심 중 하나로 설계도 어렵지만 제반되는 기술도 러닝 커브가 상당히 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 관련되어 실무에서 경험해본 내용과 F-Lab의 twitch-clone 프로젝트를 진행하면서 고민해본 기록을 남겨봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2PC 패턴과 (2 Phase Commit) SAGA 패턴에 대한 배경 지식이 있다면 글을 이해하는데 더 좋습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1651395812911&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;마이크로서비스 분산 트랜잭션 관리 (2Phase Commit)&quot; data-og-description=&quot;개요 앞선 포스팅에서 마이크로서비스 분산DB 환경에서 고려되어야 할 사항에 대해 살펴보았다. 자세한 내용은 아래 포스팅을 참고하기 바란다. 마이크로서비스 Schema 분리 설계 (테이블 분리, &quot; data-og-host=&quot;waspro.tistory.com&quot; data-og-source-url=&quot;https://waspro.tistory.com/734&quot; data-og-url=&quot;https://waspro.tistory.com/734&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/D2S4j/hyOfayz3SL/18GjT2VG0zqEHNycRwHuNK/img.png?width=800&amp;amp;height=580&amp;amp;face=0_0_800_580,https://scrap.kakaocdn.net/dn/bpIpM2/hyOdGTw9xo/c9uGMSzIEkga3umtGUUp3k/img.png?width=800&amp;amp;height=580&amp;amp;face=0_0_800_580,https://scrap.kakaocdn.net/dn/biDx7f/hyOdCDqMLi/JtAThNdLSus08F3F7pzUz1/img.png?width=848&amp;amp;height=615&amp;amp;face=0_0_848_615&quot;&gt;&lt;a href=&quot;https://waspro.tistory.com/734&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://waspro.tistory.com/734&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/D2S4j/hyOfayz3SL/18GjT2VG0zqEHNycRwHuNK/img.png?width=800&amp;amp;height=580&amp;amp;face=0_0_800_580,https://scrap.kakaocdn.net/dn/bpIpM2/hyOdGTw9xo/c9uGMSzIEkga3umtGUUp3k/img.png?width=800&amp;amp;height=580&amp;amp;face=0_0_800_580,https://scrap.kakaocdn.net/dn/biDx7f/hyOdCDqMLi/JtAThNdLSus08F3F7pzUz1/img.png?width=848&amp;amp;height=615&amp;amp;face=0_0_848_615');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;마이크로서비스 분산 트랜잭션 관리 (2Phase Commit)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요 앞선 포스팅에서 마이크로서비스 분산DB 환경에서 고려되어야 할 사항에 대해 살펴보았다. 자세한 내용은 아래 포스팅을 참고하기 바란다. 마이크로서비스 Schema 분리 설계 (테이블 분리,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;waspro.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1651395819040&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;마이크로서비스 분산 트랜잭션 관리 (Saga Pattern)&quot; data-og-description=&quot;개요 앞선 포스팅에서 마이크로서비스 분산DB 환경에서 고려되어야 할 사항에 대해 살펴보았다. 자세한 내용은 아래 포스팅을 참고하기 바란다. 마이크로서비스&amp;nbsp;분산&amp;nbsp;트랜잭션&amp;nbsp;관리&amp;nbsp;(2Phase&amp;nbsp;Co&quot; data-og-host=&quot;waspro.tistory.com&quot; data-og-source-url=&quot;https://waspro.tistory.com/735&quot; data-og-url=&quot;https://waspro.tistory.com/735&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qgV42/hyOfgS6bZE/ESNSyRDr60ynZ2h3Hwyao0/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/ncM2S/hyOdxPFAXl/zKt308qosgPk5ltqItijZ1/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/dBew6D/hyOdzzV0Kb/uuGN8A9NtnurrtjKKQMpsK/img.png?width=1747&amp;amp;height=650&amp;amp;face=0_0_1747_650&quot;&gt;&lt;a href=&quot;https://waspro.tistory.com/735&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://waspro.tistory.com/735&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qgV42/hyOfgS6bZE/ESNSyRDr60ynZ2h3Hwyao0/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/ncM2S/hyOdxPFAXl/zKt308qosgPk5ltqItijZ1/img.png?width=800&amp;amp;height=459&amp;amp;face=0_0_800_459,https://scrap.kakaocdn.net/dn/dBew6D/hyOdzzV0Kb/uuGN8A9NtnurrtjKKQMpsK/img.png?width=1747&amp;amp;height=650&amp;amp;face=0_0_1747_650');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;마이크로서비스 분산 트랜잭션 관리 (Saga Pattern)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요 앞선 포스팅에서 마이크로서비스 분산DB 환경에서 고려되어야 할 사항에 대해 살펴보았다. 자세한 내용은 아래 포스팅을 참고하기 바란다. 마이크로서비스&amp;nbsp;분산&amp;nbsp;트랜잭션&amp;nbsp;관리&amp;nbsp;(2Phase&amp;nbsp;Co&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;waspro.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 초기 설계와 최종 모델&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GhCcW/btrAVr95ENs/rbLs7lwsEMWnV5vDsfPxNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GhCcW/btrAVr95ENs/rbLs7lwsEMWnV5vDsfPxNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GhCcW/btrAVr95ENs/rbLs7lwsEMWnV5vDsfPxNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGhCcW%2FbtrAVr95ENs%2FrbLs7lwsEMWnV5vDsfPxNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;150&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;twitch-clone 프로젝트는 domain이 분리된 모듈로 개발되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 api-module로 들어오는 사용자의 구독 요청에 user-module의 구독 usecase를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WKf2m/btrATcMxUMt/AYMCvYMZcNLop84T8VAXV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WKf2m/btrATcMxUMt/AYMCvYMZcNLop84T8VAXV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WKf2m/btrATcMxUMt/AYMCvYMZcNLop84T8VAXV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWKf2m%2FbtrATcMxUMt%2FAYMCvYMZcNLop84T8VAXV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;196&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독에 필요한 부가 기능인 결제를 추가했지만 user-module에서 한 번에 처리되는 게 불편합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OPsTQ/btrAV3f60zY/Zvj9WUirhheZuMyZJDOcq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OPsTQ/btrAV3f60zY/Zvj9WUirhheZuMyZJDOcq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OPsTQ/btrAV3f60zY/Zvj9WUirhheZuMyZJDOcq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOPsTQ%2FbtrAV3f60zY%2FZvj9WUirhheZuMyZJDOcq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;930&quot; height=&quot;151&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;payment-module로 결제를&amp;nbsp;분리하고 usecase를 별도로 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 구독에 트랜잭션을 걸어 구독과 결제를 하나의 업무 단위로 움직이게 설계했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제가 추가되었음에도 불구하고 api-module에서는&amp;nbsp;이전의 usecase를 동일하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;구독에 필요한 로직이 변경되더라도 외부에 변화가 없는 느슨한 결합&lt;/b&gt;의 로직이 완성되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blEESY/btrAWzNvfty/KDD1QgKG4LaUYzUdeBSPy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blEESY/btrAWzNvfty/KDD1QgKG4LaUYzUdeBSPy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blEESY/btrAWzNvfty/KDD1QgKG4LaUYzUdeBSPy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblEESY%2FbtrAWzNvfty%2FKDD1QgKG4LaUYzUdeBSPy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;255&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 단점에도 불구하고 우리는 &lt;b&gt;api-module에서 &lt;b&gt;2PC 패턴으로&amp;nbsp;&lt;/b&gt;&lt;/b&gt;&lt;b&gt;트랜잭션을&lt;/b&gt;&lt;b&gt; 관리하는 모델&lt;/b&gt;을 최종 선택 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 왜 2PC 패턴인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xfRm7/btrAVtz4zTm/pkRa8OaknCGzDkYIqqa3s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xfRm7/btrAVtz4zTm/pkRa8OaknCGzDkYIqqa3s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xfRm7/btrAVtz4zTm/pkRa8OaknCGzDkYIqqa3s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxfRm7%2FbtrAVtz4zTm%2FpkRa8OaknCGzDkYIqqa3s0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1211&quot; height=&quot;420&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택의 이유를 좀 더 쉽게 설명하기 위해 외부 서비스 로직이 있다고 가정하고 확장했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제를 하기 위해서는 부정 방지 여부를 확인해야 하고, 구독이 성공하면 기록을 남겨야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 모델은 Choreographed SAGA 패턴과 유사합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지 브로커 활용 대신 트랜잭션을 잡고 있는 점이 달라 장애 처리는 비교적 용이하지만,&lt;br /&gt;복수 개의 서비스에 연쇄적인 트랜잭션이 생기고 있어 관리와 추적이 힘들다는 단점이 같습니다.&lt;/li&gt;
&lt;li&gt;반면&amp;nbsp;2PC&amp;nbsp;패턴은&amp;nbsp;트랜잭션의&amp;nbsp;관리&amp;nbsp;주체가&amp;nbsp;한&amp;nbsp;곳으로&amp;nbsp;집중되어&amp;nbsp;트랜잭션&amp;nbsp;관리가&amp;nbsp;용이합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;api-module만이 Spring 의존도를&amp;nbsp;가지며 그 외 module은 도메인 로직만을 가지도록 설계했습니다.&lt;br /&gt;따라서 usecase에 따라 각 module에서 생성된 결과를 api-module에서 영속시킵니다.&lt;/li&gt;
&lt;li&gt;사이드 프로젝트이기에 복잡도가 높지 않기에 로직의 추가가 api-module에 영향을 주어도 괜찮다고 생각했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 이유로 느슨한 결합을 일부 포기하더라도 2PC 패턴을 선택하는 게 좋다고 판단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 패턴에는 장단점이 존재하기에 최신의 이상적인 모델만을 고수하는 것은 정답이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 &lt;b&gt;주어진 업무 성격과 인프라 제반 사항, 인력 분배와 러닝 커브, 운영까지를 고려하여 트레이드오프 하고 지금에 맞는 베스트 프렉티스를 선별&lt;/b&gt;하는 것이고, 그런 능력을 가져야 합니다.입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 위해서는 많은 패턴을 학습하고 설계해봄으로써 배경 지식을 쌓아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 설계가 그러했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 실무였다면 &lt;b&gt;트랜잭션 타임을 줄이기 위해 MQ를 도입하고 Orchestration Saga 패턴을 고려&lt;/b&gt;했을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사이드 프로젝트에 MQ까지 고려하는 것은 힘들었고, 학습 목표인 &lt;b&gt;도메인 분리와 모듈 프로그래밍&lt;/b&gt;에 맞게 좀 더 쉬운 아키텍처를 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 극도의 성능과 분산 트랜잭션이 필요하다고 알려진 게임 분야에서도 필요에 따라서는 2PC를 구현하여 성공적으로 운영하는 사례를 공유하며 마무리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=FUEZibcZEkg&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bTFOzx/hyOe7Ppkyr/UBUiu0nWmK92rn2dQjSfZ1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=980_384_1076_488&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/FUEZibcZEkg&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>Study</category>
      <category>2PC</category>
      <category>MSA</category>
      <category>Saga</category>
      <category>분산 트랜잭션</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/42</guid>
      <comments>https://hyune-c.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 9 May 2022 10:53:05 +0900</pubDate>
    </item>
    <item>
      <title>'AWS와 토스페이먼츠를 통해 E-Commerce 스타트업 혁신하기' 웨비나 후기</title>
      <link>https://hyune-c.tistory.com/41</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;(22.04.27) 웨비나에서 소개한 기술 중 중요하거나 당사에 적합하다고 생각하는 내용을 회사 내부에 소개하는 글을 작성했습니다.&lt;br /&gt;이 중 일부 민감 항목을 제외한 간단한 후기를 작성하며, 일부 내용은 재직 중인 회사의 시선에서 작성되었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;저는 이전 경력으로 VAN과 페이의 운영 업무를 했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 자연스럽게 결제 서비스의 선두 주자인 토스에 관심이 있었고, 특히 브랜드 페이는 웨비나 전부터 긍정적으로 생각하는 모델 중 하나였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 AWS의 주관으로 &lt;b&gt;무신사에 브랜드 페이를 적용한 대표 사례&lt;/b&gt;를 소개하는 웨비나를 들을 수 있는 기회를 얻었고, 이미 큰 트래픽을 처리하는 회사의 설계를 보며 최근 설계하고 있는 당사의 MSA에도 큰 영감을 얻었기에 내용을 기록해봅니다.&lt;/p&gt;
&lt;figure id=&quot;og_1651161647189&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;OO페이, 우리 회사도 만들 수 있을까?&quot; data-og-description=&quot;우리 쇼핑몰 이름 딴 간편결제, OO페이를 만들고 싶다면, 토스페이먼츠 간편결제 솔루션 브랜드페이를 도입해 보세요. 적은 시간과 비용으로 결제전환율 95% 이상의 간편결제를 구축할 수 있어요&quot; data-og-host=&quot;blog.tosspayments.com&quot; data-og-source-url=&quot;https://blog.tosspayments.com/articles/brandpay&quot; data-og-url=&quot;https://blog.tosspayments.com/articles/brandpay&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tg9Gi/hyOcx9GjDp/WzoHG9SsN8lMyR6SzLPAzK/img.jpg?width=1145&amp;amp;height=585&amp;amp;face=0_0_1145_585,https://scrap.kakaocdn.net/dn/FEEEE/hyOcqbHux0/KdiOPSOUmEdTjJ5WTjeb9K/img.jpg?width=1920&amp;amp;height=1280&amp;amp;face=0_0_1920_1280,https://scrap.kakaocdn.net/dn/ckNXs8/hyOcCb8reI/dMGlCP0Ymmj8E6nRVfLd10/img.png?width=2100&amp;amp;height=1074&amp;amp;face=0_0_2100_1074&quot;&gt;&lt;a href=&quot;https://blog.tosspayments.com/articles/brandpay&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.tosspayments.com/articles/brandpay&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tg9Gi/hyOcx9GjDp/WzoHG9SsN8lMyR6SzLPAzK/img.jpg?width=1145&amp;amp;height=585&amp;amp;face=0_0_1145_585,https://scrap.kakaocdn.net/dn/FEEEE/hyOcqbHux0/KdiOPSOUmEdTjJ5WTjeb9K/img.jpg?width=1920&amp;amp;height=1280&amp;amp;face=0_0_1920_1280,https://scrap.kakaocdn.net/dn/ckNXs8/hyOcCb8reI/dMGlCP0Ymmj8E6nRVfLd10/img.png?width=2100&amp;amp;height=1074&amp;amp;face=0_0_2100_1074');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;OO페이, 우리 회사도 만들 수 있을까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;우리 쇼핑몰 이름 딴 간편결제, OO페이를 만들고 싶다면, 토스페이먼츠 간편결제 솔루션 브랜드페이를 도입해 보세요. 적은 시간과 비용으로 결제전환율 95% 이상의 간편결제를 구축할 수 있어요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.tosspayments.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;발표 섹션은 4개였지만, 기록은 기술 내용이 들어가 있는 1, 3번째 만을 대상으로 합니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bsRYgc/btrAJTFTkIG/B8EZfkySDrKSeWO7w8Gfj0/1.%20E-commerce%E1%84%85%E1%85%B3%E1%86%AF%20%E1%84%8B%E1%85%B1%E1%84%92%E1%85%A1%E1%86%AB%20AWS%20%E1%84%8B%E1%85%A1%E1%84%8F%E1%85%B5%E1%84%90%E1%85%A6%E1%86%A8%E1%84%8E%E1%85%A5%20%E1%84%8C%E1%85%B5%E1%86%AB%E1%84%92%E1%85%AA%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;1. E-commerce를 위한 AWS 아키텍처 진화하기.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;5.17MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/ccFGrs/btrAMtyY2dd/wOvuscc7I5PepbnOUCm5SK/2.%20AWS%20%E1%84%80%E1%85%A9%E1%86%BC%E1%84%83%E1%85%A9%E1%86%BC%20%E1%84%8B%E1%85%B0%E1%84%87%E1%85%B5%E1%84%82%E1%85%A1%20%E1%84%90%E1%85%A9%E1%84%89%E1%85%B3%E1%84%91%E1%85%A6%E1%84%8B%E1%85%B5%20%E1%84%87%E1%85%A1%E1%86%AF%E1%84%91%E1%85%AD%E1%84%8C%E1%85%A1%E1%84%85%E1%85%AD_v7_220404.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;2. AWS 공동 웨비나 토스페이 발표자료_v7_220404.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;2.38MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/nMvhi/btrALUjhRFm/gZq5b5BnTlKS2LKu4cwTP1/3.%20AWS%2B%ED%86%A0%EC%8A%A4%ED%8E%98%EC%9D%B4%EB%A8%BC%EC%B8%A0%20%EB%B0%9C%ED%91%9C%EC%9E%90%EB%A3%8C%20%28%EB%B9%8C%EB%A7%81%EA%B0%9C%EB%B0%9C%ED%8C%80%20%EC%B5%9C%EC%A7%84%ED%98%B8%29_%EC%B5%9C%EC%A2%85_Font_rev2.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;3. AWS+토스페이먼츠 발표자료 (빌링개발팀 최진호)_최종_Font_rev2.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;1.59MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bSmcmn/btrANgTGZO0/YutEA3K7rMTPbGCmPDxSbK/4.%20%E1%84%89%E1%85%B3%E1%84%90%E1%85%A1%E1%84%90%E1%85%B3%E1%84%8B%E1%85%A5%E1%86%B8%E1%84%8B%E1%85%B3%E1%86%AF%20%E1%84%8B%E1%85%B1%E1%84%92%E1%85%A1%E1%86%AB%20AWS%20x%20Toss%20Payments%E1%84%8B%E1%85%B4%20%E1%84%90%E1%85%B3%E1%86%A8%E1%84%87%E1%85%A7%E1%86%AF%20%E1%84%92%E1%85%A8%E1%84%90%E1%85%A2%E1%86%A8.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;4. 스타트업을 위한 AWS x Toss Payments의 특별 혜택.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.53MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;1.-E-commerce를-위한-AWS-아키텍처-진화하기&quot; data-renderer-start-pos=&quot;70&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. E-commerce를 위한 AWS 아키텍처 진화하기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ALbWr/btrAKjw5MyX/exx3M1kIUIahVGNrYlYAU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ALbWr/btrAKjw5MyX/exx3M1kIUIahVGNrYlYAU0/img.png&quot; data-alt=&quot;데이터 흐름을 분석하여 인사이트 도출까지 고려한 모델을 말하고 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ALbWr/btrAKjw5MyX/exx3M1kIUIahVGNrYlYAU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FALbWr%2FbtrAKjw5MyX%2Fexx3M1kIUIahVGNrYlYAU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1133&quot; height=&quot;639&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;639&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터 흐름을 분석하여 인사이트 도출까지 고려한 모델을 말하고 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;Auto-Scaling-Group&quot; data-renderer-start-pos=&quot;157&quot; data-ke-size=&quot;size23&quot;&gt;Auto Scaling Group&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ecs에 설정할 수 있는 고가용성 기능 중 하나&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;예정된 이벤트라면 수동으로 스케일 업을 지정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;하지만 보통은 자동으로 스케일 업 &amp;amp; 스케일 아웃되기를 기대합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;최소 24시간 이상의 데이터가 있는 경우&lt;/b&gt;, 지난 14일 동안의 metric을 분석해 향후 48시간의 지표를 예상하여 자동 설정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;Amazon-Aurora&quot; data-renderer-start-pos=&quot;404&quot; data-ke-size=&quot;size23&quot;&gt;Amazon Aurora&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmeRxt/btrANgsDyw5/0pD2XCxQ6sOJyozO8icq61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmeRxt/btrANgsDyw5/0pD2XCxQ6sOJyozO8icq61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmeRxt/btrANgsDyw5/0pD2XCxQ6sOJyozO8icq61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmeRxt%2FbtrANgsDyw5%2F0pD2XCxQ6sOJyozO8icq61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1163&quot; height=&quot;548&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;당사에서는 RDS만을 레플리카 없이 모두 마스터로 운영하고 있습니다.&lt;/li&gt;
&lt;li&gt;보통은 레플리카의 적극적인 활용을 말하지만, 당사는 B2B 성격이 강하며 그리드 조회 기능이 대부분입니다.&lt;br /&gt;따라서 CQRS 패턴과 데이터웨어하우스의 도입이 선행되는 것이 좋다고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;같이-쓰면-좋은-서비스&quot; data-renderer-start-pos=&quot;595&quot; data-ke-size=&quot;size23&quot;&gt;같이 쓰면 좋은 서비스&lt;/h3&gt;
&lt;p data-renderer-start-pos=&quot;595&quot; data-ke-size=&quot;size18&quot;&gt;Performance Insights (성능 개선 도우미)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-renderer-start-pos=&quot;595&quot;&gt;당사는 AWS의 관제 시스템보다는 datadog을 적극적으로 사용하려 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;RDS Proxy&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'AWS Secrets Manager과 통합되어 Credentials를 안전하게 관리할 수 있다'라고 하지만 지금은 필요성이 와닿지 않았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;분석-시작하기&quot; data-renderer-start-pos=&quot;780&quot; data-ke-size=&quot;size23&quot;&gt;분석 시작하기&lt;/h3&gt;
&lt;p id=&quot;Amazon-Kinesis-Data-Streams&quot; data-renderer-start-pos=&quot;789&quot; data-ke-size=&quot;size18&quot;&gt;Amazon Kinesis Data Streams&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sHETk/btrAJ1DrAgW/KnUBIEuoY9kJVE7vhPi8vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sHETk/btrAJ1DrAgW/KnUBIEuoY9kJVE7vhPi8vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sHETk/btrAJ1DrAgW/KnUBIEuoY9kJVE7vhPi8vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsHETk%2FbtrAJ1DrAgW%2FKnUBIEuoY9kJVE7vhPi8vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1120&quot; height=&quot;570&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발표에서는 사용자 흐름의 통계화를 위해 Kinesis를 말합니다.&lt;/li&gt;
&lt;li&gt;하지만 사용자 흐름에 대한 트래킹은 datadog을 활용할 수 있다고 생각합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 당사의 일부 시스템은 사용자 흐름을 datadog으로 추적 가능합니다.&lt;/li&gt;
&lt;li&gt;하지만 발표 자료 수준의 인사이트를 얻을 수 있는 정형화된 데이터는 아닙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;Amazon-Athena&quot; data-renderer-start-pos=&quot;1009&quot; data-ke-size=&quot;size18&quot;&gt;Amazon Athena&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQXFIa/btrAK3OzYrC/2irbrICIzoZSsyTtaVdKn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQXFIa/btrAK3OzYrC/2irbrICIzoZSsyTtaVdKn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQXFIa/btrAK3OzYrC/2irbrICIzoZSsyTtaVdKn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQXFIa%2FbtrAK3OzYrC%2F2irbrICIzoZSsyTtaVdKn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1163&quot; height=&quot;527&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞의 이슈들을 해결하기 위한 방법으로 사내에서 선택한 기술 중 하나입니다. (+ AWS Glue)&lt;/li&gt;
&lt;li&gt;당사는 아직 MSA를 준비하는 초창기이기에 같은 도메인이 각 모놀리식 서비스마다 조금씩 다른 시선으로 개발되어 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이것을 공통 도메인으로 분리하는 것이 MSA의 최종 목표이며, 이 과정은 매우 힘들고 긴 호흡이 필요합니다.&lt;/li&gt;
&lt;li&gt;그렇기에 &lt;b&gt;과도기인 상황에서 필요한 데이터를 추출함에 있어서는 ETL을 활용하는 것이 가장 적절하다&lt;/b&gt;고 판단했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;Amazon-QuickSight&quot; data-renderer-start-pos=&quot;1278&quot; data-ke-size=&quot;size18&quot;&gt;Amazon QuickSight&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J7NIm/btrAKvxNTbi/3kpxjFPgfktjH9ku5E1QaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J7NIm/btrAKvxNTbi/3kpxjFPgfktjH9ku5E1QaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J7NIm/btrAKvxNTbi/3kpxjFPgfktjH9ku5E1QaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ7NIm%2FbtrAKvxNTbi%2F3kpxjFPgfktjH9ku5E1QaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1229&quot; height=&quot;714&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 내부에서 시각화된 지표 대시보드 개발했습니다.&lt;/li&gt;
&lt;li&gt;Amazon QuickSight는 Saas 형태이기에 편리하겠지만, 메트릭 집계가 선행되어야 하기에 와닿지 않았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;머신-러닝-활용하기&quot; data-renderer-start-pos=&quot;1390&quot; data-ke-size=&quot;size23&quot;&gt;머신 러닝 활용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신 러닝에 대해 잘 모르기도 하고, 기초 자료를 위한 메트릭 집계가 선행되어야 하기에 와닿지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;3.-AWS+토스페이먼츠-발표자료&quot; data-renderer-start-pos=&quot;1904&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. AWS+토스 페이먼츠 발표자료&lt;/b&gt;&lt;/h2&gt;
&lt;h3 id=&quot;모놀리식-&amp;rarr;-MSA&quot; data-renderer-start-pos=&quot;1924&quot; data-ke-size=&quot;size23&quot;&gt;Monolithic &amp;rarr; Microservice&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPL16f/btrALtlGHvv/JAKxmCZ8YcKLLvP8RfgQ51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPL16f/btrALtlGHvv/JAKxmCZ8YcKLLvP8RfgQ51/img.png&quot; data-alt=&quot;당사의 레거시와 비슷합니다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPL16f/btrALtlGHvv/JAKxmCZ8YcKLLvP8RfgQ51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPL16f%2FbtrALtlGHvv%2FJAKxmCZ8YcKLLvP8RfgQ51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;478&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;당사의 레거시와 비슷합니다!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-mode=&quot;wide&quot;&gt;
&lt;div data-layout-section=&quot;true&quot;&gt;
&lt;div data-layout-column=&quot;true&quot; data-column-width=&quot;50&quot;&gt;
&lt;div&gt;
&lt;div data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div&gt;
&lt;div data-context-id=&quot;2211479609&quot; data-type=&quot;file&quot; data-node-type=&quot;media&quot; data-width=&quot;939&quot; data-height=&quot;478&quot; data-id=&quot;4f337a36-a363-4f22-a9c7-c9d990baec4f&quot; data-collection=&quot;contentId-2211479609&quot; data-file-name=&quot;image-20220427-062940.png&quot; data-file-size=&quot;93037&quot; data-file-mime-type=&quot;image/png&quot; data-alt=&quot;&quot;&gt;
&lt;div data-testid=&quot;media-card-view&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUrrd/btrAJ1XMZyv/EJJkvtaS7iD1FsntPTK9kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUrrd/btrAJ1XMZyv/EJJkvtaS7iD1FsntPTK9kk/img.png&quot; data-alt=&quot;MSA! 제가 생각하는 모델은 이것과 비슷하지만 BFF를 곁들입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUrrd/btrAJ1XMZyv/EJJkvtaS7iD1FsntPTK9kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUrrd%2FbtrAJ1XMZyv%2FEJJkvtaS7iD1FsntPTK9kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;881&quot; height=&quot;479&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MSA! 제가 생각하는 모델은 이것과 비슷하지만 BFF를 곁들입니다.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무신사에서 브랜드 페이를 위해 사용하는 서비스들&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpDR1b/btrAJ8v5mEU/H1gf91zDkXPpkKoHBghZV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpDR1b/btrAJ8v5mEU/H1gf91zDkXPpkKoHBghZV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpDR1b/btrAJ8v5mEU/H1gf91zDkXPpkKoHBghZV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpDR1b%2FbtrAJ8v5mEU%2FH1gf91zDkXPpkKoHBghZV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;969&quot; height=&quot;536&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아쉽게도 당사는 아직 &lt;b&gt;MQ조차 사용하지 않는 동기식 처리가 대부분이며, 배치가 아닌 스케줄러의 형태만 운영&lt;/b&gt;되고 있습니다.&lt;/li&gt;
&lt;li&gt;자료에서는 카프카를 말하지만, 러닝 커브가 높고 당사의 트래픽 수준에서는 큰 이점 없이 운영 난이도만 높아진다고 생각합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비교적 쉽게 사용할 수 있는 AWS SNS/SQS를 생각하고 있습니다.&lt;/li&gt;
&lt;li&gt;발표에서도 &lt;b&gt;초기에는 Amazon MQ와 AWS SNS/SQS를 병행 사용하다가 이후 kafka로 넘어갔다&lt;/b&gt;고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study</category>
      <category>AWS</category>
      <category>무신사</category>
      <category>브랜드 페이</category>
      <category>토스페이먼츠</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/41</guid>
      <comments>https://hyune-c.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 29 Apr 2022 18:44:32 +0900</pubDate>
    </item>
    <item>
      <title>대량 데이터 조회와 유지보수는 어떻게 해야될까?</title>
      <link>https://hyune-c.tistory.com/40</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GC에 익숙한 사람이라면 모두 Generational GC가 최고라고 이야기합니다. &lt;br /&gt;'generational hypothesis는 대부분 상황에서 Young Object는 Old Object 보다 죽을 가능성이 클 것으로 내다본다.' 2008년에는 맞는 말이었죠. 하지만 지금은 설득력이 떨어집니다.&lt;br /&gt;&lt;br /&gt;Red Hat 개발자 Christine Flood - DevNation 2016 中&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;기술의 발전과 &lt;/b&gt;시장의 변화에 따라 과거에는 옳았던 것이 지금은 재해석되고 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;클라우드의 도입으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;어려웠던 확장/고가용의 기술이 쉬워지고, 하드웨어의 비용이 낮아진 것이 하나의 예입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 성장에 따라 다뤄야 하는 데이터는 기하급수적으로 늘어나고 기존의 방법으로는 해결하기 힘든 순간이 옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 다니는 회사에도 그 시점이 다가오고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 좋은 아이디어들이 많이 있지만, 우선 순위가 다른 지금의 회사에 그대로 적용하는 것은 꽤 어려운 일 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 기술 부채는 계속 쌓이고 있고, 응급조치만으로는 할 수 없는 시점이 다가오고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 백엔드 엔지니어로서 그때를 위해 &lt;b&gt;기술을 재해석하고 지금에 적절한 기술을 준비하는 과정&lt;/b&gt;을 기록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지금의 개발 &amp;amp; 조회 환경&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각각의 플랫폼들은 모놀리식으로 개발되어 있음&lt;/li&gt;
&lt;li&gt;비슷한 데이터가 독립적으로 존재하고 있으며 호환되지 않음&lt;/li&gt;
&lt;li&gt;대량 데이터를 다룰 수 있는 인력 부재&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내가 소속되어 있는 플랫폼 P의 환경&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;입점된 대형 업체는 1개뿐임에도 불구하고 간헐적인 속도 이슈 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 수정, 최대 조회 기간을 1달로 제한하는 등의 단발성 응대로 해결&lt;/li&gt;
&lt;li&gt;상반기 내 다수의 대영 업체 입점 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대부분의 조회 로직이 영속성 레이어에 집중되어 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초대형 쿼리로 구성된 로직은 수정 난이도가 높고 높은 업무 이해도가 요구됨&lt;/li&gt;
&lt;li&gt;데이터의 상태가 개별 column으로 관리되며 비슷한 상태가 중복되기도 함 (ex. SYSTEM_USER_YN)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인 로직 정의보다 DB 테이블 정의가 선행되고 그것에 맞추는 개발을 하고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성보다는 성능과 개발 속도를 중시한 단일 테이블 형태가 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;read only DB에 대한 필요성은 공감되고 있지만 실 적용하지 못하고 있음&lt;/li&gt;
&lt;li&gt;GraphQL의 특성을 살리지 못하고 화면 별로 특화된 대형 조회 쿼리 형태로 운영되고 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오히려 학습과 문제 해결 난이도만 높아짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개선을 위한 아이디어&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 알려진 아이디어는 많습니다. &lt;br /&gt;하지만 내가 일하는 환경에 적용할 수 있도록 재해석하는 것은 완전히 다른 영역입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 영속성 레이어에 집중된 로직을 분리&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9lu7P/btrtKGZQcUD/kCeeU5KUwainKIYApMLY7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9lu7P/btrtKGZQcUD/kCeeU5KUwainKIYApMLY7k/img.png&quot; data-alt=&quot;지금의 구성. 출처: 만들면서 배우는 클린 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9lu7P/btrtKGZQcUD/kCeeU5KUwainKIYApMLY7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9lu7P%2FbtrtKGZQcUD%2FkCeeU5KUwainKIYApMLY7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;407&quot; height=&quot;297&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지금의 구성. 출처: 만들면서 배우는 클린 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가&amp;nbsp;생각하는&amp;nbsp;초대형&amp;nbsp;쿼리가&amp;nbsp;생겨나는&amp;nbsp;원인은&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구 사항의 변경을 &lt;b&gt;도메인이 아닌 쿼리, 즉 영속성 레이어에서 해결&lt;/b&gt;하고 있습니다.&lt;/li&gt;
&lt;li&gt;보통 높은 이해도가 필요한 복잡한 업무 환경에서 나오는 현상이며, 지금의 빠른 개발을 위해 쿼리의 일부만 수정하거나 인덱스를 통해 해결하기에 해당 기능에 특화된 대형 쿼리로 작성되곤 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 로직은 &lt;b&gt;재사용이 불가능하고 높은 결합도를 가집니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;수정을 위해서는 &lt;b&gt;문맥 이해와 영향도 체크 후 분리, 추상화 비용이 개발에 준하는 수준&lt;/b&gt;으로 발생됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;과거에는 맞습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어는 비교적 짧은 수명을 가지고 워터폴 방식으로 개발되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경은 비교적 적거나 보수적으로 수용되었고, 변경 난이도가 일정 수치를 넘어서면 차세대 개발로 대체되었습니다.&lt;/li&gt;
&lt;li&gt;자연스럽게 비교적 긴 수명의 DB와 높은 비용의 하드웨어 집중된 개발이 되었습니다.&lt;/li&gt;
&lt;li&gt;한 가지 예로 성능 향상을 위해 join을 줄이는 단일 테이블 전략이 많이 사용되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;지금은 다릅니다.&amp;nbsp;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;높아진 개발 비용과&lt;/b&gt; &lt;b&gt;니즈가 다양해진 지금의 환경에서는 변경 난이도를 낮추는 것이 더 중요&lt;/b&gt;해졌습니다.&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;도메인 레이어에서 범용 로직을 조립하는 DDD 이론이 나왔습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;클라우드가 등장하면서 확장성이 용이해지고 인프라 의존도가 줄었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발 측면에서 단적인 예를 들자면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과거에는 높은 컴퓨팅 비용이 드는 stream 대신 for문을 사용해야 된다는 의견도 있었습니다.&lt;/li&gt;
&lt;li&gt;지금은 하드웨어 활용으로 개발 난이도를 낮추고, 나아가 함수형 패러다임으로의 변경까지 일어나고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저와 비슷한 생각으로 레이어를 나눈 좋은 글이 있어 공유합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1645279583378&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Layered Architecture Deep Dive&quot; data-og-description=&quot;최근 진행 중인 프로젝트는 Layered Architecture를 선택하여 패키지를 구성하고 개발하였습니다.&quot; data-og-host=&quot;msolo021015.medium.com&quot; data-og-source-url=&quot;https://msolo021015.medium.com/layered-architecture-deep-dive-c0a5f5a9aa37&quot; data-og-url=&quot;https://msolo021015.medium.com/layered-architecture-deep-dive-c0a5f5a9aa37&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ssmIC/hyNsUKBZ1a/USzx82k7TFcN3nFzxwoZlk/img.png?width=1137&amp;amp;height=758&amp;amp;face=0_0_1137_758,https://scrap.kakaocdn.net/dn/fXLZH/hyNsYsH10T/iDc3KXpGPkpYBjPW0iKOAK/img.png?width=838&amp;amp;height=876&amp;amp;face=0_0_838_876,https://scrap.kakaocdn.net/dn/bkTGjD/hyNs1CYAlV/HEWAHv9Gv5JOD0HUblUWY1/img.png?width=745&amp;amp;height=666&amp;amp;face=0_0_745_666&quot;&gt;&lt;a href=&quot;https://msolo021015.medium.com/layered-architecture-deep-dive-c0a5f5a9aa37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://msolo021015.medium.com/layered-architecture-deep-dive-c0a5f5a9aa37&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ssmIC/hyNsUKBZ1a/USzx82k7TFcN3nFzxwoZlk/img.png?width=1137&amp;amp;height=758&amp;amp;face=0_0_1137_758,https://scrap.kakaocdn.net/dn/fXLZH/hyNsYsH10T/iDc3KXpGPkpYBjPW0iKOAK/img.png?width=838&amp;amp;height=876&amp;amp;face=0_0_838_876,https://scrap.kakaocdn.net/dn/bkTGjD/hyNs1CYAlV/HEWAHv9Gv5JOD0HUblUWY1/img.png?width=745&amp;amp;height=666&amp;amp;face=0_0_745_666');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Layered Architecture Deep Dive&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근 진행 중인 프로젝트는 Layered Architecture를 선택하여 패키지를 구성하고 개발하였습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;msolo021015.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 성능 개선을 위한 방법&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;WAS 스케일 업, 스케일 아웃(Scale-up,&amp;nbsp;Scale-out)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 환경이라면 가장 영향도가 적은 방법으로 인프라 성능을 확장시키는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2 개별 구성-&amp;gt; Elastic BeanStalk -&amp;gt; ECS - Fargate -&amp;gt; Kubernetes 순으로 기술을 도입할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Elastic Load Balancer를 통해 트래픽을 분산시키고, Auto Scaling을 통해 스케일 업을 자동화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;업무 성격에 따라 T 타입 인스턴스를 통한 버스트 기능을 활용하는 방법도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;DB &lt;b&gt;스케일 업, 스케일 아웃(Scale-up, Scale-out)&lt;/b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Replication DB
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CUD 로직을 수행하는 master DB와 R 로직을 수행하는 복제 DB를 구성하는 방법으로 확장성이 용이합니다.&lt;/li&gt;
&lt;li&gt;단점으로는 동기화에 시간이 걸리며, DB 연결의 추가 개발과 인프라 구성이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RDB를 사용하고 있다면 Amazon Aurora 도 좋은 선택입니다. (2021.11부터 MySql 8.0 지원)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;파티셔닝(Partitioning),&amp;nbsp;샤딩(Sharding)&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과거에는 직접 구성해야 됐지만, 지금은 DB나 프레임워크에서 지원해주기도 하는 전통적인 분산 처리 기술입니다.&lt;/li&gt;
&lt;li&gt;하지만 운영 난이도가 높고 RDB가 대세였던 시기에 분산 처리를 하기 위해 나온 기술이기에, 최근에는 분산 처리를 전제로 한 기술이 권장되고 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NoSQL, CDC(Changed Data Capture), ETL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;ETL (Extract, Transform, Load), Data&amp;nbsp;warehouse&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비슷하지만 다른 데이터를 의미있게 재구성해야하는 현대의 개발에 가장 활용성 높은 기술 중 하나입니다.&lt;/li&gt;
&lt;li&gt;과거에는 배치성의 개발이 필요했지만 현재는 AWS Redshift, Athena 등을 통해 편리하게 작업할 수 있습니다.&lt;/li&gt;
&lt;li&gt;단점으로는 학습과 운영 난이도가 높으며 데이터를 집계, 재배열하는 시간이 필요해 실시간 처리는 힘든 기술입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;CQRS (Command and Query Responsibility Segregation)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령과 조회의 책임 분리를 하는 패턴으로 앞에 소개한 방법 중 가장 적은 영향도와 개발 비용이 듭니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CUD 로직은(등록, 수정, 삭제) Command로 모아 도메인 로직에 응집시키고 ORM 기술을 활용합니다.&lt;/li&gt;
&lt;li&gt;R 로직은(조회) Query로 모으며 필요에 따라 native query를 사용하기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 지금에 적절한 기술을 선별하는 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알려진 기술들은 이미 많습니다. 중요한 것은 &lt;b&gt;지금에 적절한 기술을 선별&lt;/b&gt;하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;플랫폼 P는 사용량이 적음에도 불구하고 성능 이슈가 발생하고 있으며, 대형 업체의 추가 입점이 예정되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이대로라면 이미 제한한 최대 조회 범위를 더 줄여야 하고, 적절하게 조절하지 못했다면 장애로 번질 수도 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;백엔드 아키텍처만을 수정합니다&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 영향도가 낮은 방법으로 CQRS의 분리와 도메인 레이어의 도입이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발의 수정 난이도는 낮춰주지만, 이미 부하가 발생하고 있는 환경에 드라마틱한 효과를 기대하기는 힘듭니다.&lt;/li&gt;
&lt;li&gt;아키텍처의 복잡도가 높아지는 만큼 혼란을 막기 위해 컨벤션 정의가 선행되어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;대형 업체별로 테이블을 분리합니다&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 몇 개의 거대한 테이블에 데이터와 복잡한 조회 로직이 모여 있었기 때문에 조회에 문제가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 대형 업체별로 테이블을 분리하는 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이미 구현된 거미줄 같은 로직에 연관된 테이블을 대형 업체 별로 추가해야 되기에 개발 복잡도가 급격하게 높아짐으로 장기적인 해결책이라고 볼 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 문제는 &lt;b&gt;CRUD가(Create, Read, Update, Delete) 모두 하나의 서비스&lt;/b&gt;에서 일어나고 있다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 지금의 구성은 동기화에는 좋지만 확장과 부하에 약하고, 유지보수의 용이성의 고려도 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;성능 향상과 유지보수성을 모두 만족시키기 위해서는 패러다임을 변경해야 됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;CQRS의 서비스 단위 분리&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 업무를 분석하고 재해석하여 CUD와 대용량 R 업무를 구분합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 CUD는 기존의 서비스에서 진행하고, 대용량 R 업무는 서비스를 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;테이블 설계 방식 변경&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUD 서비스의 테이블은 확장성이 좋고 ORM 기술 사용에 용이한 조인 테이블 전략으로 설계합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능이 필요한 대용량 R 업무가 분리되었기에 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Data warehouse 도입&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data warehouse를 도입하여 대용량 R 업무의 조회를 위한 테이블을 독립적인 공간에 구성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS의 기술을 사용한다면 최근 공개된&amp;nbsp;AWS Glue Studio의 사용을 고려하는 것도 좋습니다.&lt;/li&gt;
&lt;li&gt;배치로 구현한다면 NoSQL의 도입도 고려할 수 있습니다.&lt;br /&gt;RDB 수준의 ACID를 준수하지는 않지만 대용량 조회라는 업무 성격상 적합하기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;b&gt;개선 방향&lt;/b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kAE36/btrv3Ov8k56/krpwxn4oF1s3c5YSuVuj0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAE36/btrv3Ov8k56/krpwxn4oF1s3c5YSuVuj0k/img.png&quot; data-alt=&quot;1. 모든 CRUD 가 하나의 서비스에서 처리되는 지금의 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAE36/btrv3Ov8k56/krpwxn4oF1s3c5YSuVuj0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAE36%2Fbtrv3Ov8k56%2Fkrpwxn4oF1s3c5YSuVuj0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;204&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1. 모든 CRUD 가 하나의 서비스에서 처리되는 지금의 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmNVv4/btrwaf7uZRm/P7bDuLSv3q323WcMHXNwUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmNVv4/btrwaf7uZRm/P7bDuLSv3q323WcMHXNwUK/img.png&quot; data-alt=&quot;2. 대용량 R 은 분리되었지만 여전히 하나의 서비스에서 처리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmNVv4/btrwaf7uZRm/P7bDuLSv3q323WcMHXNwUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmNVv4%2Fbtrwaf7uZRm%2FP7bDuLSv3q323WcMHXNwUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;374&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2. 대용량 R 은 분리되었지만 여전히 하나의 서비스에서 처리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KETez/btrv9JAXOvM/0mTXUftzihj6wcVcN89fKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KETez/btrv9JAXOvM/0mTXUftzihj6wcVcN89fKk/img.png&quot; data-alt=&quot;3. 대용량 R 을 독립적인 서비스로 분리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KETez/btrv9JAXOvM/0mTXUftzihj6wcVcN89fKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKETez%2Fbtrv9JAXOvM%2F0mTXUftzihj6wcVcN89fKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;421&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;421&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;3. 대용량 R 을 독립적인 서비스로 분리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나아가 이기종 플랫폼의 정보를 Data warehouse로 통합할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;기타&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETL/Data warehouse 운영의 가장 큰 단점은 실시간 데이터와 집계 과정에서 발생하는 간극입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;대용량 조회는 D-2 이전의 데이터만 지원한다&lt;/b&gt;와 같은 업무적 합의가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 운영했던 업무 중에 하나를 예로 들면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카드사 별 카드 이용내역을 배치를 통해 시간 단위로&amp;nbsp;별도 시스템으로 집계합니다.&lt;/li&gt;
&lt;li&gt;카드 이용내역은 즉시 확인 가능하지만, 매입내역은 익일 확인 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 말한 것들은 새로운 패러다임을 도입하는 만큼 구성원 모두가 공부해야 되는 양도 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 아직 이해가 깊지 못하기에 개인적으로 공부하며 최근 맡은 개발과 사이드 프로젝트에서 자체적으로 도입해보고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 생각하는 좋은 엔지니어란 &lt;b&gt;정석을 알고 뒤처지지 않으면서도 &lt;b&gt;지금에 적절한&lt;/b&gt; 기술을 선택할 수 있는 사람&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이런 사람들이 모여 좋은 자극을 자발적으로 공유할 때, 모두의 실력이 상향 평준화되고 높은 퀄리티의 결과물이 나올 수 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 자극을 지속적으로 받을 수 있는 환경을 기대하며 성능 개선에 관련된 영상을 하나 남기고 글을 마칩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=JJJ4LReZ5q4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/l3peE/hyNuhFwPES/kYG6CDzY0eFSyHyllv0JR0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/JJJ4LReZ5q4&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;초식 - 서버 성능 올리기 (처리량, 응답시간) - 최범균&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>Study</category>
      <category>CQRS</category>
      <category>ETL</category>
      <category>대량 데이터 조회</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/40</guid>
      <comments>https://hyune-c.tistory.com/40#entry40comment</comments>
      <pubDate>Sat, 2 Apr 2022 16:18:49 +0900</pubDate>
    </item>
    <item>
      <title>마이크로 서비스도 리소스 동기화가 필요할까?</title>
      <link>https://hyune-c.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 회사 최초로 시작하는 MSA 개발의 첫 삽을 제가 뜨게 되었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 개발을 하면서 제일 큰 걱정은 &lt;b&gt;MSA 책 한 개도 안 봤는데 내가 설계해도 되나......... &lt;/b&gt;였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설상가상으로 설계기간도 충분하지 못했죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 틈틈이 읽은&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt; 서적과 블로그들이 큰 도움이 되었고, 부족하지만 제가 설계하고 개발한 기록을 공유합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발한 내용&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/errEyj/btrs3TSV3q6/WoeZ5mOgseF9y0n9yLn8Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/errEyj/btrs3TSV3q6/WoeZ5mOgseF9y0n9yLn8Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/errEyj/btrs3TSV3q6/WoeZ5mOgseF9y0n9yLn8Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FerrEyj%2Fbtrs3TSV3q6%2FWoeZ5mOgseF9y0n9yLn8Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;444&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래되고 사용자가 많은 플랫폼 B와 비교적 최근에 개발되었지만 사용자가 적은 플랫폼 P가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 환경에서 운영되고 있던 &lt;b&gt;플랫폼의 각 DB와 회원 등록/수정 로직이 통합 중에도 유지되어야&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현된 언어와 프레임워크도 달랐고, 통합 대상인 회원의 식별자와 업무 내용도 미묘하게 달랐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해를 돕기 위해 업무 내용의 일부를 먼저 설명하며, 해결하기 위한 설계로 2가지 플랜을 구상했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원은 거래처(매장)와 같은 의미를 가집니다.&lt;/li&gt;
&lt;li&gt;거래처는(B: shop, P: cust) 복수 개의 유통사를(B:vendor, P: biz) 가질 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PLAN A&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8c4BS/btrs2m9oWBi/OCh499nrN4AfzDmJYgPMBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8c4BS/btrs2m9oWBi/OCh499nrN4AfzDmJYgPMBK/img.png&quot; data-alt=&quot;PLAN A. 통합회원에 대리키 userNo 를 만들고, 각 플랫폼의 PK 값을 테이블 관계로 관리합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8c4BS/btrs2m9oWBi/OCh499nrN4AfzDmJYgPMBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8c4BS%2Fbtrs2m9oWBi%2FOCh499nrN4AfzDmJYgPMBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;648&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PLAN A. 통합회원에 대리키 userNo 를 만들고, 각 플랫폼의 PK 값을 테이블 관계로 관리합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;장점&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통합회원에는 회원 상세 정보가 없기에 각 플랫폼에서 회원 정보가 변경되어도 알 필요가 없습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증과 직결되는 일부 항목은 필요하지만 개수를 최소화합니다. (ex. 비활성화 여부)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로그인 ID(userId)와 password가 통합되고, 인증에 필수적인 필드를 한 곳에서 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;기존 플랫폼의 DB 변경이 최소화됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;단점&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통합회원에서 회원 상세 정보가 알고 싶다면 각 플랫폼으로 회원 PK를 통해 요청해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PLAN B&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l6i6V/btrsXg3iS3H/Fzkp9BKDiKzUrFNdgyF2Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l6i6V/btrsXg3iS3H/Fzkp9BKDiKzUrFNdgyF2Wk/img.png&quot; data-alt=&quot;PLAN B. 통합회원의 대리키 userNo 를 각 플랫폼에 추가합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l6i6V/btrsXg3iS3H/Fzkp9BKDiKzUrFNdgyF2Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl6i6V%2FbtrsXg3iS3H%2FFzkp9BKDiKzUrFNdgyF2Wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;717&quot; height=&quot;595&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;595&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PLAN B. 통합회원의 대리키 userNo 를 각 플랫폼에 추가합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;장점&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통합회원에는 회원 상세정보가 없기에 각 플랫폼에서 회원 정보가 변경되어도 알 필요가 없습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증과 직결되는 일부 항목은 필요하지만 개수를 최소화합니다. (ex. 비활성화 여부)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로그인 ID(userId)와 password가 통합되고, 인증에 필수적인 필드를 한 곳에서 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;각 플랫폼의 회원 상세정보를 원하는 대로 가져올 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;단점&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원 상세정보를 가져오는 네트워크 비용이 비교적 큽니다.&lt;/li&gt;
&lt;li&gt;기존 플랫폼 DB에 userNo 가 추가됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;상위 도메인은 하위 도메인의 키만을 가지도록 함으로써&lt;br /&gt;리소스 관리의 이슈를 최소화할 수 있도록 설계했습니다.&lt;br /&gt;&lt;br /&gt;하지만..&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PLAN C&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daReU1/btrs3R1KSPF/lqz9YiLIycnP0GCahVyNvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daReU1/btrs3R1KSPF/lqz9YiLIycnP0GCahVyNvK/img.png&quot; data-alt=&quot;PLAN C. 회원상세정보가 플랫폼과 통합회원에 모두 존재합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daReU1/btrs3R1KSPF/lqz9YiLIycnP0GCahVyNvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaReU1%2Fbtrs3R1KSPF%2Flqz9YiLIycnP0GCahVyNvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;595&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;595&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PLAN C. 회원상세정보가 플랫폼과 통합회원에 모두 존재합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 PLAN C의 설계로 개발을 진행하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 MSA를 경험해본 분이라면 PLAN C의 그림이나 이 글의 제목에서 의아함을 느끼셨을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;리소스 관리는 도메인 별로 분리된 하나의 서비스에서 하면 되는데, 왜 동기화를 고민하지?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 &lt;b&gt;특수한 업무 요건과 글로 작성하기 힘든 내부의 이슈로&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;&amp;nbsp;인해 과도기적인 설계&lt;/b&gt;를 했어야 되기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증 진행 간 회원 상세 정보를 보여줘야 하며, 통합인증의 테이블을 사용해야 한다. (스냅샷)&lt;/li&gt;
&lt;li&gt;각 플랫폼의 회원 등록/수정을 통합 회원에서도 알아야 한다. (동기화)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요건 만족을 우선한 설계를 했다고 하기에는 부족한 통찰로 합리적인 설득을 하지 못하고 안일하게 넘어간 점이 몹시 아쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마이크로&amp;nbsp;서비스도&amp;nbsp;리소스&amp;nbsp;동기화가&amp;nbsp;필요할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본론을 말하기 위한 배경 설명이 너무 길어졌네요. 다시 처음으로 돌아가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 좋은 것은&amp;nbsp;&lt;b&gt;동기화가 필요하지 않은 설계를 하는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 리얼 월드에서는 앞에서처럼 필요한 경우가 생기곤 하기에, 동기화 방법에 대한 생각을 기록해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;신뢰할 수 있는 리소스는 플랫폼에 있습니다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동기화는 여러 가지 이유로 실패할 수 있습니다. &lt;br /&gt;최악의 경우에는 수동 작업을 해야 될 수도 있는데, 이때를 위한 신뢰할 수 있는 대상을 고려해야 합니다.&lt;br /&gt;신뢰할 수 있는 대상을 정하는 방법은 &lt;b&gt;리소스의 관리 책임 주체는 누구인가?&lt;/b&gt; 를 생각하면 될 것 같습니다.&lt;/li&gt;
&lt;li&gt;각 플랫폼에는 상대적으로 많은 회원정보 수정 로직이 있고 수정이 시작되는 지점입니다.&lt;br /&gt;따라서 &lt;b&gt;리소스의 관리 책임이 각 플랫폼에 있다고 간주할 수 있음으로 신뢰할 수 있는 대상은 각 플랫폼&lt;/b&gt;이 되어야 하며, 반대로 통합인증의 리소스는 스냅샷으로 간주합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;회원정보 수정은 누가 해야 될까?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PLAN C에서는 같은 리소스의 관리 대상이 2곳입니다.&lt;br /&gt;따라서 양쪽의 변경이 동시에 하기 위해 수동으로 트랜잭션을 구현해야 합니다.&lt;br /&gt;여기에서 &lt;b&gt;JPA의 양방향 매핑의&amp;nbsp;연관 관계의 주인에서&lt;/b&gt; 인사이트를 얻었습니다.&lt;br /&gt;한쪽을 주인으로 설정하는 것이죠.&lt;/li&gt;
&lt;li&gt;1번을 통해 신뢰할 수 있는 리소스가 플랫폼에 있다는 것을 알 수 있습니다.&lt;br /&gt;따라서 &lt;b&gt;플랫폼이 주인으로서 트랜잭션의 주체로 설계하며 통합인증에서는 키 값과 최소한의 상태만 가지도록&lt;/b&gt; 합니다.&lt;/li&gt;
&lt;li&gt;하지만 그럼에도 불구하고 업무 요건에 따라 통합회원에서 정보를 수정해야 되는 경우도 있습니다.&lt;br /&gt;아쉽게도 처음의 아이디어에는 위배되지만 요건 만족을 위해 어쩔 수 없이 예외 로직을 개발했습니다.&lt;br /&gt;이때 트랜잭션의 주체는 반대가 되어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동기화 간 지연과 유실을 대비해야 합니다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보편적으로 좋은 해결책은 큐의 도입입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;큐가 대상으로 push&lt;/b&gt; - 재시도는 용이하지만 큐를 를 지속적으로 관리해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대상이 큐를 polling&lt;/b&gt;&amp;nbsp;- 실패 시 이미 소비된 큐가 유실될 위험이 존재한다.&lt;/li&gt;
&lt;li&gt;보통&amp;nbsp;&lt;b&gt;큐가 대상으로 push&lt;/b&gt; 하는 방식으로 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AWS SQS를 통해 권한 제어를 도입하면 보안 향상도 기대할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어떤 도메인은 자주 바뀌지 않기에 실시간 동기화에 관대할 수도 있습니다.&amp;nbsp;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경우 실시간 동기화보다는 배치를 활용하는 방법이 있습니다.&lt;/li&gt;
&lt;li&gt;실시간 동기화를 하는 경우에도 배치를 사용하여 안정성을 높일 수 있습니다.&lt;/li&gt;
&lt;li&gt;또는 스냅샷을 캐시로 저장하는 방법도 있습니다. (ex. redis)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;충분한 환경에서 성숙 단계의 마이크로 서비스를 지향한다면 SAGA 패턴을 고려합니다.&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;Choreography와 Orchestration은 구성은 어렵지만 MSA 트랜잭션 관리에 가장 좋은 구조입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure id=&quot;og_1646385946464&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Saga pattern - Azure Design Patterns&quot; data-og-description=&quot;Use the Saga design pattern to ensure data consistency on distributed transactions in microservices architectures.&quot; data-og-host=&quot;docs.microsoft.com&quot; data-og-source-url=&quot;https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga&quot; data-og-url=&quot;https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blKGGW/hyNzR9nLje/mHGw7SIPy8HtllSSjleaQK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga&quot; data-source-url=&quot;https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blKGGW/hyNzR9nLje/mHGw7SIPy8HtllSSjleaQK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Saga pattern - Azure Design Patterns&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Use the Saga design pattern to ensure data consistency on distributed transactions in microservices architectures.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6MFNh/btrs0JxczlF/fq23y4rEY5pAVUKvFdKTF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6MFNh/btrs0JxczlF/fq23y4rEY5pAVUKvFdKTF1/img.png&quot; data-alt=&quot;출처: https://waspro.tistory.com/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6MFNh/btrs0JxczlF/fq23y4rEY5pAVUKvFdKTF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6MFNh%2Fbtrs0JxczlF%2Ffq23y4rEY5pAVUKvFdKTF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;446&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://waspro.tistory.com/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 회사의 구성을 MSA로 전환한다면 어떻게 해야 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막연하게 생각했을 때 Step 1처럼 복수의 프로젝트가 하나의 DB를 공유하는 것에서 시작할 것이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현실은 많이 달랐고 공부했던 개념들을 모아 독자적인 흐름을 설계했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 과정에서 발생한 개발의 어려움은 반대로 좋은 설계의 필요성에 대한 공감대를 형성하기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 최종 목표는 &lt;b&gt;플랫폼에서 독립적인 마이크로 서비스와 각 플랫폼에 특화된 Backend for Front-end&lt;/b&gt;로 설계하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 이 과정에서도 트레이드 오프를 반복하겠지만 지금의 경험이 도움이 되고, 나아가 이 모든 경험들이 더 좋은 설계의 기반이 되기를 기대하며 글을 마칩니다.&lt;/p&gt;
&lt;figure id=&quot;og_1646385824948&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;오늘의집 MSA Phase 1. 백엔드 분리작업 - 오늘의집 블로그&quot; data-og-description=&quot;마이크로서비스 열차 탑승을 위한 백엔드 분리 과정&quot; data-og-host=&quot;www.bucketplace.co.kr&quot; data-og-source-url=&quot;https://www.bucketplace.co.kr/post/2022-01-14-%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-msa-phase-1-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%B6%84%EB%A6%AC%EC%9E%91%EC%97%85/&quot; data-og-url=&quot;https://www.bucketplace.co.kr/post/2022-01-14-오늘의집-msa-phase-1-백엔드-분리작업/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.bucketplace.co.kr/post/2022-01-14-%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-msa-phase-1-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%B6%84%EB%A6%AC%EC%9E%91%EC%97%85/&quot; data-source-url=&quot;https://www.bucketplace.co.kr/post/2022-01-14-%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-msa-phase-1-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%B6%84%EB%A6%AC%EC%9E%91%EC%97%85/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;오늘의집 MSA Phase 1. 백엔드 분리작업 - 오늘의집 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;마이크로서비스 열차 탑승을 위한 백엔드 분리 과정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.bucketplace.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1646385573536&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;오늘의집 MSA Phase 1. Aggregator 공통모듈 - 오늘의집 블로그&quot; data-og-description=&quot;1년차 백엔드 개발자의 MSA 공통모듈 개발기&quot; data-og-host=&quot;www.bucketplace.co.kr&quot; data-og-source-url=&quot;https://www.bucketplace.co.kr/post/2022-02-22-%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-msa-phase-1-aggregator-%EA%B3%B5%ED%86%B5%EB%AA%A8%EB%93%88/&quot; data-og-url=&quot;https://www.bucketplace.co.kr/post/2022-02-22-오늘의집-msa-phase-1-aggregator-공통모듈/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.bucketplace.co.kr/post/2022-02-22-%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-msa-phase-1-aggregator-%EA%B3%B5%ED%86%B5%EB%AA%A8%EB%93%88/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.bucketplace.co.kr/post/2022-02-22-%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-msa-phase-1-aggregator-%EA%B3%B5%ED%86%B5%EB%AA%A8%EB%93%88/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;오늘의집 MSA Phase 1. Aggregator 공통모듈 - 오늘의집 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1년차 백엔드 개발자의 MSA 공통모듈 개발기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.bucketplace.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study</category>
      <category>MSA</category>
      <category>리소스 동기화</category>
      <category>마이크로서비스</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/39</guid>
      <comments>https://hyune-c.tistory.com/39#entry39comment</comments>
      <pubDate>Fri, 4 Mar 2022 18:46:20 +0900</pubDate>
    </item>
    <item>
      <title>Custom LocalDateUtils 리팩토링</title>
      <link>https://hyune-c.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 회사 업무의 일부를 확장/분리하는 업무를 맡았습니다. &lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;프로젝트의 세팅부터 해야 되는 작업이었기에 제가 주도적으로 개발할 수 있었는데요. &lt;br /&gt;이 과정에서 작업한 Legacy의 공통 Util 리팩토링의 일부를 기록해봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AS-IS의 문제점&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 0. 이 프로젝트에 의존적인 로직이 존재합니다.
//  ex) 입력 값이 파싱할 수 없는 isEmpty() 라면 빈 값을 반환. (에러가 아니라)
@UtilityClass
public class LocalDateUtils {

 // 1. LOCAL_DATE_FORMAT_WITH_HYPHEN, LOCAL_DATE_FORMAT 이 String 변수로 존재합니다.
 public static final String LOCAL_DATE_FORMAT_WITH_HYPHEN = &quot;yyyy-MM-dd&quot;;
 public static final String LOCAL_DATE_FORMAT = &quot;yyyyMMdd&quot;;

 // 2. NO_HYPHEN 은 매개 변수를 통해 받고 있지만,
 //  WITH_HYPHEN 은 명시된 메소드 이름을 사용하기에 일관성을 해칩니다.
 public static String toString(final LocalDate attribute, final String format) {
  if (attribute == null) {
   return &quot;&quot;;
  }
  // 3. DateTimeFormatter 객체가 매번 재생성되고 있습니다.
  return attribute.format(DateTimeFormatter.ofPattern(format));
 }
 
 public static String toStringWithNoHyphen(final LocalDate attribute) {
  if (attribute == null) {
   return &quot;&quot;;
  }
  return attribute.format(DateTimeFormatter.ofPattern(LOCAL_DATE_FORMAT));
 }

 public static LocalDate from(final String attribute) {
  // 4. isEmpty() 와 같기에 스페이스가 존재하는 문자열을 체크하지 못합니다. (ex. &quot;  &quot;)
  if (!StringUtils.hasLength(attribute)) {
   return null;
  }
  final String format = getFormat(attribute);
  validationCheck(format, attribute);
  return LocalDate.parse(attribute, DateTimeFormatter.ofPattern(format));
 }

 private static String getFormat(final String attribute) {
  // 5. 매직 넘버를 통해 비교하고 있으며, 가독성이 좋지 못합니다.
  return attribute.length() == 10 ? LOCAL_DATE_FORMAT_WITH_HYPHEN : LOCAL_DATE_FORMAT;
 }

 // 6. validation 목적이 명시적이지 않습니다.
 private static void validationCheck(final String format, final String localDate) {
  // 7. SimpleDateFormat 은 다중 스레드 환경에서 권장되지 않습니다.
  final SimpleDateFormat df = new SimpleDateFormat(format);
  df.setLenient(false);
  try {
   df.parse(localDate);
  } catch (final ParseException e) {
   log.error(&quot;LocalDateParse Error : &quot;, e);
   throw new RuntimeException();
  }
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개선 1&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AS-IS의 문제점을 고려해서 첫 번째 개선을 적용했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클린 코딩에 맞춰 최신 컨벤션을 적용합니다.&lt;/li&gt;
&lt;li&gt;불필요한 객체 생성과 validation을 최소화합니다.&lt;/li&gt;
&lt;li&gt;public 메소드 개수와 사용법을 표준화합니다,
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성을 고려하여 메소드 이름보다는 enum을 매개 변수로 활용하여 로직을 분리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class LocalDateUtils {

 @Getter
 @RequiredArgsConstructor
 public enum LocalDateFormatterType {
  WITH_HYPHEN(DateTimeFormatter.ISO_LOCAL_DATE),
  NO_HYPHEN(DateTimeFormatter.ofPattern(&quot;yyyyMMdd&quot;)),
  ;

  private final DateTimeFormatter formatter;
 }


 public static String toString(final LocalDate attribute, final LocalDateFormatterType type) {
  if (attribute == null) {
   return &quot;&quot;;
  }

  return attribute.format(type.formatter);
 }

 public static LocalDate from(final String attribute) {
  if (!StringUtils.hasLength(attribute)) {
   return null;
  }

  try {
   return LocalDate.parse(attribute);
  } catch (final DateTimeParseException ex) {
   return LocalDate.from(LocalDateFormatterType.NO_HYPHEN.formatter.parse(attribute));
  } catch (final Exception ex) {
   log.error(&quot;### LocalDateParse Error : &quot;, ex);
   throw new RuntimeException(); // 차후 적절한 에러로 치환
  }
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개선 2&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게 적용하고 보니 Fommatter 로직도 enum에&amp;nbsp;응집시키고 싶었습니다.&lt;br /&gt;그리고 &lt;b&gt;Util 은 공통 라이브러리로 분리할 수 있도록 범용적으로 설계&lt;/b&gt;되어야 한다고 생각하기에 두 가지 관점을 추가했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;null과 공백 문자열의 후속 처리는 메소드를 호출하는 곳에서 합니다.&lt;/li&gt;
&lt;li&gt;프로젝트에 의존적인 커스텀 에러보다는, 사용하는 곳에서 핸들링할 수 있는 범용 에러를 반환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class LocalDateUtils {

 @RequiredArgsConstructor
 public enum LocalDateFormatter {
  WITH_HYPHEN(
    DateTimeFormatter.ISO_LOCAL_DATE::format,
    input -&amp;gt; LocalDate.parse(input)
  ),
  NO_HYPHEN(
    input -&amp;gt; DateTimeFormatter.ofPattern(&quot;yyyyMMdd&quot;).format(input),
    input -&amp;gt; LocalDate.from(DateTimeFormatter.ofPattern(&quot;yyyyMMdd&quot;).parse(input))
  );

  private final Function&amp;lt;LocalDate, String&amp;gt; toString;
  private final Function&amp;lt;String, LocalDate&amp;gt; toLocaldate;
 }

 public static String toString(final LocalDate input, final LocalDateFormatter formatter) {
  return Optional.ofNullable(input)
    .map(formatter.toString)
    .orElseThrow(IllegalArgumentException::new);
 }

 public static LocalDate toLocalDate(final String text) {
  try {
   return WITH_HYPHEN.toLocaldate.apply(text);
  } catch (final DateTimeParseException ex) {
   return NO_HYPHEN.toLocaldate.apply(text);
  } catch (final Exception ex) {
   log.error(&quot;### toLocalDate Error : &quot;, ex);
   throw ex;
  }
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 개선간 사용한 테스트 케이스를 남기며 글을 마칩니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@DisplayName(&quot;LocalDateUtils&quot;)
class LocalDateUtilsTest {

 @DisplayName(&quot;문자열 -&amp;gt; LocalDate&quot;)
 @ParameterizedTest
 @CsvSource(
   value = {
     &quot;2020-01-01&quot;,
     &quot;2020-01-31&quot;,
     &quot;2021-12-01&quot;,
     &quot;2021-12-31&quot;,
     &quot;20200101&quot;,
     &quot;20200131&quot;,
     &quot;20211201&quot;,
     &quot;20211231&quot;
   }
 )
 void toLocalDate(final String input) {
  final LocalDate localDate = LocalDateUtils.toLocalDate(input);
  assertThat(localDate).isNotNull();
  log.info(&quot;### {}&quot;, localDate);
 }

 @DisplayName(&quot;문자열 -&amp;gt; LocalDate 오류&quot;)
 @ParameterizedTest
 @CsvSource(
   value = {
     &quot;''&quot;,
     &quot;'  '&quot;,
     &quot;2020-01-00&quot;,
     &quot;2020-01-32&quot;,
     &quot;2021-1201&quot;,
     &quot;202112-31&quot;,
     &quot;20200100&quot;,
     &quot;20200132&quot;,
     &quot;202112011&quot;,
     &quot;2021123&quot;
   }
 )
 void toLocalDate_error(final String input) {
  assertThrows(DateTimeParseException.class, () -&amp;gt; LocalDateUtils.toLocalDate(input));
 }

 @DisplayName(&quot;문자열 -&amp;gt; LocalDate null 오류&quot;)
 @ParameterizedTest
 @CsvSource(value = &quot;,&quot;)
 void toLocalDate_error_null(final String input) {
  assertThrows(NullPointerException.class, () -&amp;gt; LocalDateUtils.toLocalDate(input));
 }

 public static Stream&amp;lt;LocalDate&amp;gt; LOCAL_DATES() {
  return Stream.of(
    LocalDate.of(2020, 1, 1),
    LocalDate.of(2020, 1, 31),
    LocalDate.of(2021, 12, 1),
    LocalDate.of(2021, 12, 31)
  );
 }

 @DisplayName(&quot;LocalDate -&amp;gt; 문자열 WITH_HYPHEN&quot;)
 @ParameterizedTest
 @MethodSource(&quot;LOCAL_DATES&quot;)
 void toString1(final LocalDate input) {
  final String string = LocalDateUtils.toString(input, LocalDateFormatter.WITH_HYPHEN);
  assertThat(string).isNotBlank();
  log.info(&quot;### {}&quot;, string);
 }

 @DisplayName(&quot;LocalDate -&amp;gt; 문자열 NO_HYPHEN&quot;)
 @ParameterizedTest
 @MethodSource(&quot;LOCAL_DATES&quot;)
 void toString2(final LocalDate input) {
  final String string = LocalDateUtils.toString(input, LocalDateFormatter.NO_HYPHEN);
  assertThat(string).isNotBlank();
  log.info(&quot;### {}&quot;, string);
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Series/실전!</category>
      <category>LocalDateUtils</category>
      <category>리팩토링</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/38</guid>
      <comments>https://hyune-c.tistory.com/38#entry38comment</comments>
      <pubDate>Tue, 4 Jan 2022 01:08:06 +0900</pubDate>
    </item>
    <item>
      <title>'수십억건에서 QUERYDSL 사용하기' 를 보고..</title>
      <link>https://hyune-c.tistory.com/36</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9SS1u/btrodAR3fWS/OfkkLNmlEr5XwqtuzckGsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9SS1u/btrodAR3fWS/OfkkLNmlEr5XwqtuzckGsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9SS1u/btrodAR3fWS/OfkkLNmlEr5XwqtuzckGsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9SS1u%2FbtrodAR3fWS%2FOfkkLNmlEr5XwqtuzckGsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;705&quot; height=&quot;440&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 영상의 요약이나 후기는 웬만하면 작성하지 않는 편입니다. 하지만 우아콘 2020의 이동욱 님 발표 영상은 하나하나가 너무나도 실무적이고 주옥같은 내용들 뿐이라서 꼭 기억하고 싶은 마음에 정리해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=zMAX7g6rO_Y&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/YNU2z/hyMKMlAVCP/t4UO7WqEMdYiWCSKeGujR0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=546_152_686_304&quot; data-video-width=&quot;700&quot; data-video-height=&quot;394&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/zMAX7g6rO_Y&quot; width=&quot;700&quot; height=&quot;394&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;[우아콘2020] 수십억건에서 QUERYDSL 사용하기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 환경&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenJDK 1. 8.0_252&lt;/li&gt;
&lt;li&gt;Querydls_JPA 4.2.1&lt;/li&gt;
&lt;li&gt;AWS Aurora MySQL 5.6 1.19.6&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 워밍업&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;extends / implements 사용하지 않기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OjTTP/btrodz6uDtx/mVYmzZY4U3Y8QhTj1Fumh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OjTTP/btrodz6uDtx/mVYmzZY4U3Y8QhTj1Fumh1/img.png&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;660&quot; style=&quot;width: 62.1463%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OjTTP/btrodz6uDtx/mVYmzZY4U3Y8QhTj1Fumh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOjTTP%2Fbtrodz6uDtx%2FmVYmzZY4U3Y8QhTj1Fumh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1658&quot; height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbzyzL/btrogvJny7l/Oo3Al2ES86WOZXfJBrEki0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbzyzL/btrogvJny7l/Oo3Al2ES86WOZXfJBrEki0/img.png&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;890&quot; style=&quot;width: 36.6909%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbzyzL/btrogvJny7l/Oo3Al2ES86WOZXfJBrEki0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbzyzL%2FbtrogvJny7l%2FOo3Al2ES86WOZXfJBrEki0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1320&quot; height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;JPAQueryFactory 를 Bean 으로 사용함으로서 extends/implemets 를 제거할 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표 자료에는 없지만 &lt;b&gt;JPAQueryFactory를 Bean으로 설정해주는 작업&lt;/b&gt;이 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class QueryDslConfig {

   @PersistenceContext
   private EntityManager entityManager;

   @Bean
   public JPAQueryFactory jpaQueryFactory() {
      return new JPAQueryFactory(entityManager);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상을 보면서 회사 코드와 사이드 프로젝트들을 점검해보니, 모두 extends/implements를 사용하고 있었습니다. 아마 김영한 님의 강의에서 가져온 것 같습니다. &lt;br /&gt;그리고 사이드 프로젝트에서는 &lt;b&gt;Configuration&lt;/b&gt;으로 했지만, 회사코드에서는 각 Impl에서 &lt;b&gt;new JPAQueryFactory(em); &lt;/b&gt;를 하고 있었어 리팩토링 해야겠다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아무리 생각해봐도 &lt;b&gt;영상 내용으로의 개선이 더 낫다는 생각이 들지 않았습니다. &lt;br /&gt;&lt;/b&gt;Custom으로 선언하고, Impl에서 구현하는 것이 클래스는 많아질지라도 유지보수 편의성을 위해 역할을 나누고 가독성을 향상하는 것이 더 좋다고 생각해서였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 댓글을 보니 저와 비슷한 생각을 하신 분이 있어 남깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2544&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgBe8B/btroeAqAtbr/qvScKeFUL0EFvlgCulrNnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgBe8B/btroeAqAtbr/qvScKeFUL0EFvlgCulrNnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgBe8B/btroeAqAtbr/qvScKeFUL0EFvlgCulrNnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgBe8B%2FbtroeAqAtbr%2FqvScKeFUL0EFvlgCulrNnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2544&quot; height=&quot;1272&quot; data-origin-width=&quot;2544&quot; data-origin-height=&quot;1272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동적 쿼리&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ZTxx/btrodAde4oH/KlsmkhfMvxoBo0P1JJGVak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ZTxx/btrodAde4oH/KlsmkhfMvxoBo0P1JJGVak/img.png&quot; data-alt=&quot;BooleanExpression 을 사용하면 조건문의 가독성이 높아지고, null 을 반환하게 되더라도 Where 절에서 조건이 무시되기 때문에 안전합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ZTxx/btrodAde4oH/KlsmkhfMvxoBo0P1JJGVak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ZTxx%2FbtrodAde4oH%2FKlsmkhfMvxoBo0P1JJGVak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;706&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BooleanExpression 을 사용하면 조건문의 가독성이 높아지고, null 을 반환하게 되더라도 Where 절에서 조건이 무시되기 때문에 안전합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;링크의 블로그에 좀 더 많은 내용이 있습니다.&lt;/blockquote&gt;
&lt;figure id=&quot;og_1639829976136&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Querydsl] 다이나믹 쿼리 사용하기&quot; data-og-description=&quot;안녕하세요! 이번 시간에는 Querydsl에서의 다이나믹 쿼리를 어떻게 작성하면 좋을지에 대해 진행합니다. 처음 Querydsl을 쓰시는 분들이 가장 많이 실수하는 부분이니 그럼 시작합니다! 모든 코드&quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/394&quot; data-og-url=&quot;https://jojoldu.tistory.com/394&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKvLv3/hyMJDKAJPK/wU5GYYmvl711fnzi4hrf5k/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/TuwlD/hyMKIXPvgZ/fakJM5etmzTaGjqFyOrrlK/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/bNwALx/hyMJMOkfs7/YeZaJ8s83S9IqPy0h2Tqvk/img.png?width=1678&amp;amp;height=720&amp;amp;face=0_0_1678_720&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/394&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jojoldu.tistory.com/394&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKvLv3/hyMJDKAJPK/wU5GYYmvl711fnzi4hrf5k/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/TuwlD/hyMKIXPvgZ/fakJM5etmzTaGjqFyOrrlK/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/bNwALx/hyMJMOkfs7/YeZaJ8s83S9IqPy0h2Tqvk/img.png?width=1678&amp;amp;height=720&amp;amp;face=0_0_1678_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Querydsl] 다이나믹 쿼리 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요! 이번 시간에는 Querydsl에서의 다이나믹 쿼리를 어떻게 작성하면 좋을지에 대해 진행합니다. 처음 Querydsl을 쓰시는 분들이 가장 많이 실수하는 부분이니 그럼 시작합니다! 모든 코드&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jojoldu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 성능개선 - Select&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Querydsl의 exist 금지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exist는 조건에 맞는 값을 찾으면 바로 반환하지만 count 쿼리는 전체 행을 모두 조회함으로 성능이 떨어지며, 이 차이는 스캔 대상이 앞에 있을수록 커집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctsIPg/btroi5pUlcu/mYUuYTkFsKW6359IBLU0z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctsIPg/btroi5pUlcu/mYUuYTkFsKW6359IBLU0z0/img.png&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;530&quot; style=&quot;width: 53.5302%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctsIPg/btroi5pUlcu/mYUuYTkFsKW6359IBLU0z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctsIPg%2Fbtroi5pUlcu%2FmYUuYTkFsKW6359IBLU0z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WisoS/btrohTpIzxI/SvjsfIj6o6M6w8Xmb12g61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WisoS/btrohTpIzxI/SvjsfIj6o6M6w8Xmb12g61/img.png&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;736&quot; style=&quot;width: 45.3071%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WisoS/btrohTpIzxI/SvjsfIj6o6M6w8Xmb12g61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWisoS%2FbtrohTpIzxI%2FSvjsfIj6o6M6w8Xmb12g61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1488&quot; height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Querydsl 의 exist 는 내부적으로 count 쿼리를 사용하고 있기에, limit(1) 을 직접 구현해서 사용하는 것이 좋습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cross join 회피&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/leu0h/btrodSd0icA/6sKxCDyHb904Rw9z5QpL90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/leu0h/btrodSd0icA/6sKxCDyHb904Rw9z5QpL90/img.png&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;808&quot; style=&quot;width: 50.5577%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/leu0h/btrodSd0icA/6sKxCDyHb904Rw9z5QpL90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fleu0h%2FbtrodSd0icA%2F6sKxCDyHb904Rw9z5QpL90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pOZPR/btroi54vyl3/S8rrX6hBCcUZQdOdK5v5P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pOZPR/btroi54vyl3/S8rrX6hBCcUZQdOdK5v5P0/img.png&quot; data-origin-width=&quot;1486&quot; data-origin-height=&quot;752&quot; style=&quot;width: 48.2795%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pOZPR/btroi54vyl3/S8rrX6hBCcUZQdOdK5v5P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpOZPR%2Fbtroi54vyl3%2FS8rrX6hBCcUZQdOdK5v5P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1486&quot; height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Hibernate&amp;nbsp;의 문제이기에 Spring Data JPA 도 동일하게 발생합니다. 명시적 join 을 통해 해결합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Entity 보다는 Dto를 우선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity를 직접 조회하면 단순 조회 기능에서의 성능 이슈 요소가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하이버네이트 1차, 2차 캐시 문제 발생&lt;/li&gt;
&lt;li&gt;불필요한 칼럼을 조회합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@OneToOne&lt;/b&gt; N+1 쿼리 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서는 이슈를 해결할 수 있는 몇 가지 방법들을 소개합니다. 결론적으로 &lt;b&gt;실시간으로 Entity 변경이 필요한 경우엔 Entity&lt;/b&gt;를,&amp;nbsp;&lt;b&gt;성능 개선이나 대량의 데이터 조회가 필요한 경우엔 Dto&lt;/b&gt;를 조회하는 것을 추천합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pZL8I/btrohUa6DdQ/6JdxJOMHy6669HkeQNWRG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pZL8I/btrohUa6DdQ/6JdxJOMHy6669HkeQNWRG0/img.png&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;532&quot; style=&quot;width: 59.9411%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pZL8I/btrohUa6DdQ/6JdxJOMHy6669HkeQNWRG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpZL8I%2FbtrohUa6DdQ%2F6JdxJOMHy6669HkeQNWRG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1544&quot; height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xjEjr/btrodAj1qTd/DofEPqkNQ0gy5mnSIWkWaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xjEjr/btrodAj1qTd/DofEPqkNQ0gy5mnSIWkWaK/img.png&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;754&quot; style=&quot;width: 38.8961%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xjEjr/btrodAj1qTd/DofEPqkNQ0gy5mnSIWkWaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxjEjr%2FbtrodAj1qTd%2FDofEPqkNQ0gy5mnSIWkWaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1420&quot; height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;#1 조회컬럼 최소화하기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2tVor/btroibcIdpN/2oEsVvkIwvUydPlRBcg0V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2tVor/btroibcIdpN/2oEsVvkIwvUydPlRBcg0V0/img.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;630&quot; style=&quot;width: 47.8425%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2tVor/btroibcIdpN/2oEsVvkIwvUydPlRBcg0V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2tVor%2FbtroibcIdpN%2F2oEsVvkIwvUydPlRBcg0V0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1526&quot; height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCsdhL/btroecb850l/8IyU8fb4ByQpOnicGrRHxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCsdhL/btroecb850l/8IyU8fb4ByQpOnicGrRHxk/img.png&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;550&quot; style=&quot;width: 50.9947%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCsdhL/btroecb850l/8IyU8fb4ByQpOnicGrRHxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCsdhL%2Fbtroecb850l%2F8IyU8fb4ByQpOnicGrRHxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1420&quot; height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;#2 불필요한 컬럼 조회 하지 않기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y61J9/btrogwn0eYx/fHU1MXk5ohNd2PxBSkQP1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y61J9/btrogwn0eYx/fHU1MXk5ohNd2PxBSkQP1K/img.png&quot; data-alt=&quot;#3 @OneToOne 에서의 N+1 문제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y61J9/btrogwn0eYx/fHU1MXk5ohNd2PxBSkQP1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy61J9%2Fbtrogwn0eYx%2FfHU1MXk5ohNd2PxBSkQP1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;559&quot; height=&quot;299&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;#3 @OneToOne 에서의 N+1 문제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N+1 문제는 &lt;b&gt;@OneToOne(fetch = FetchType.LAZY)&lt;/b&gt; 설정으로도 해결되지 않으며, &lt;b&gt;Fetch Join&lt;/b&gt; 의 사용을 추천합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;링크의 블로그에 좀 더 많은 내용이 있습니다.&lt;/blockquote&gt;
&lt;figure id=&quot;og_1639833560277&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[JPA] @OneToOne에서 Fetch 전략을 Lazy로 설정했을때 발생하는 이슈&quot; data-og-description=&quot;Lazy Loading JPA의 유일한 단점은 사용하기 쉬운만큼 성능적인 측면에서 발생할 수 있는 이슈를 간과하기 쉽다는 것인데, 성능이 안나올때 가장 먼저 고려해봐야할 부분이 즉시로딩(EAGER LOADING)으로&quot; data-og-host=&quot;1-7171771.tistory.com&quot; data-og-source-url=&quot;https://1-7171771.tistory.com/143&quot; data-og-url=&quot;https://1-7171771.tistory.com/143&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uimuS/hyMKFmxxsj/OYJlQKYP3hlrTuWlHf3dzK/img.png?width=646&amp;amp;height=304&amp;amp;face=0_0_646_304,https://scrap.kakaocdn.net/dn/bkTdtg/hyMKTd0YZl/QSeMCArv0dfrCuGdqJKWzK/img.png?width=646&amp;amp;height=304&amp;amp;face=0_0_646_304,https://scrap.kakaocdn.net/dn/Hblhj/hyMKGZ2KJH/JfUo18V1jqkcEJvs8mhZb0/img.png?width=646&amp;amp;height=304&amp;amp;face=0_0_646_304&quot;&gt;&lt;a href=&quot;https://1-7171771.tistory.com/143&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://1-7171771.tistory.com/143&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uimuS/hyMKFmxxsj/OYJlQKYP3hlrTuWlHf3dzK/img.png?width=646&amp;amp;height=304&amp;amp;face=0_0_646_304,https://scrap.kakaocdn.net/dn/bkTdtg/hyMKTd0YZl/QSeMCArv0dfrCuGdqJKWzK/img.png?width=646&amp;amp;height=304&amp;amp;face=0_0_646_304,https://scrap.kakaocdn.net/dn/Hblhj/hyMKGZ2KJH/JfUo18V1jqkcEJvs8mhZb0/img.png?width=646&amp;amp;height=304&amp;amp;face=0_0_646_304');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[JPA] @OneToOne에서 Fetch 전략을 Lazy로 설정했을때 발생하는 이슈&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Lazy Loading JPA의 유일한 단점은 사용하기 쉬운만큼 성능적인 측면에서 발생할 수 있는 이슈를 간과하기 쉽다는 것인데, 성능이 안나올때 가장 먼저 고려해봐야할 부분이 즉시로딩(EAGER LOADING)으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;1-7171771.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lDO27/btrodlgrxhz/uKWkkfiD7Ynuho5JOk0DVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lDO27/btrodlgrxhz/uKWkkfiD7Ynuho5JOk0DVK/img.png&quot; data-alt=&quot;#4 연관된 Entity의 save를 위해서는 반대편 Entity의 ID만 있으면 된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lDO27/btrodlgrxhz/uKWkkfiD7Ynuho5JOk0DVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlDO27%2Fbtrodlgrxhz%2FuKWkkfiD7Ynuho5JOk0DVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;368&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;#4 연관된 Entity의 save를 위해서는 반대편 Entity의 ID만 있으면 된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSZ6ku/btrocdQD9Ji/UBUDT3ZrMPaxL0VmiPiBaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSZ6ku/btrocdQD9Ji/UBUDT3ZrMPaxL0VmiPiBaK/img.png&quot; data-alt=&quot;#5 distinct 사용시에는 필요한 컬럼만 조회해서 사용합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSZ6ku/btrocdQD9Ji/UBUDT3ZrMPaxL0VmiPiBaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSZ6ku%2FbtrocdQD9Ji%2FUBUDT3ZrMPaxL0VmiPiBaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;205&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;#5 distinct 사용시에는 필요한 컬럼만 조회해서 사용합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Group By 최적화&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buvK0Z/btrodmGrjed/sKRTU8rVYfV6BHKoX2nZj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buvK0Z/btrodmGrjed/sKRTU8rVYfV6BHKoX2nZj1/img.png&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;852&quot; style=&quot;width: 53.0902%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buvK0Z/btrodmGrjed/sKRTU8rVYfV6BHKoX2nZj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuvK0Z%2FbtrodmGrjed%2FsKRTU8rVYfV6BHKoX2nZj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1478&quot; height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/okpBj/btrocTxJaOD/PunrkeV80RG0KqglouEZHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/okpBj/btrocTxJaOD/PunrkeV80RG0KqglouEZHK/img.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;962&quot; style=&quot;width: 45.747%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/okpBj/btrocTxJaOD/PunrkeV80RG0KqglouEZHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FokpBj%2FbtrocTxJaOD%2FPunrkeV80RG0KqglouEZHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1438&quot; height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;order by null 을 통해 Filesort 를 제거할 수 있지만 Querydsl 에서는 지원되지 않습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Flo5q/btroezefXtK/qZAEkBaIW6UhhlQRTo3wG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Flo5q/btroezefXtK/qZAEkBaIW6UhhlQRTo3wG1/img.png&quot; data-alt=&quot;OrderByNull 을 직접 구현함으로서 해결할 수 있습니다. 하지만..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Flo5q/btroezefXtK/qZAEkBaIW6UhhlQRTo3wG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFlo5q%2FbtroezefXtK%2FqZAEkBaIW6UhhlQRTo3wG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;276&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OrderByNull 을 직접 구현함으로서 해결할 수 있습니다. 하지만..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Using filesort는 &lt;b&gt;정렬이 필요한 데이터를 메모리에 올리고 정렬 작업을 수행&lt;/b&gt;한다는 의미로, 이미 &lt;b&gt;정렬된 인덱스를 사용함&lt;/b&gt;으로써 해결할 수 있습니다. 영상에서는 우리가 사용하는 모든 Group By 가 index를 탄다는 보장이 없기에 말하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 8.0 이상을 사용하는 환경이라면 더 이상 고려하지 않아도 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Previously (MySQL 5.7 and lower), &lt;b&gt;GROUP BY&lt;/b&gt; sorted implicitly under certain conditions. In MySQL 8.0, that no longer occurs, so specifying&amp;nbsp;&lt;b&gt;ORDER BY NULL&lt;/b&gt; at the end to suppress implicit sorting (as was done previously) is no longer necessary. However, query results may differ from previous MySQL versions. To produce a given sort order, provide an&amp;nbsp;&lt;b&gt;ORDER BY&lt;/b&gt;&amp;nbsp;clause.&lt;/blockquote&gt;
&lt;figure id=&quot;og_1639835735475&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: MySQL 8.0 Reference Manual :: 8.2.1.16 ORDER BY Optimization&quot; data-og-description=&quot;8.2.1.16&amp;nbsp;ORDER BY Optimization This section describes when MySQL can use an index to satisfy an ORDER BY clause, the filesort operation used when an index cannot be used, and execution plan information available from the optimizer about ORDER BY. An ORDER&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html&quot; data-og-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: MySQL 8.0 Reference Manual :: 8.2.1.16 ORDER BY Optimization&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;8.2.1.16&amp;nbsp;ORDER BY Optimization This section describes when MySQL can use an index to satisfy an ORDER BY clause, the filesort operation used when an index cannot be used, and execution plan information available from the optimizer about ORDER BY. An ORDER&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;Paging 이 필요하지 않고 정렬 건수가 적다면 WAS에서 정렬&lt;/b&gt;하는 것을 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 WAS 리소스가 DB 리소스보다는 여유 있고 저렴하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커버링 인덱스&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b97pgG/btrokrs57f2/X5jV5UabLEXlNnxTvvAlkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b97pgG/btrokrs57f2/X5jV5UabLEXlNnxTvvAlkk/img.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;614&quot; style=&quot;width: 56.8109%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b97pgG/btrokrs57f2/X5jV5UabLEXlNnxTvvAlkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb97pgG%2Fbtrokrs57f2%2FX5jV5UabLEXlNnxTvvAlkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sippS/btrodk95nL8/Fk6kRcjqgFgXm6MOwaTI01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sippS/btrodk95nL8/Fk6kRcjqgFgXm6MOwaTI01/img.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;830&quot; style=&quot;width: 42.0263%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sippS/btrodk95nL8/Fk6kRcjqgFgXm6MOwaTI01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsippS%2Fbtrodk95nL8%2FFk6kRcjqgFgXm6MOwaTI01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Cluster Key 인 PK 로 key 를 조회하고 SELECT 컬럼을 조회한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커버링 인덱스를 사용할 땐 inline view (from 절의&amp;nbsp;subQuery)에서&amp;nbsp;커버링 인덱스를 통해 필터를 하도록 하는 게 일반적이지만,&amp;nbsp; JPQL은 from 절의 서브 쿼리를 지원하지 않아 직접 구현해야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;링크의 블로그에 좀 더 많은 내용이 있습니다.&lt;/blockquote&gt;
&lt;figure id=&quot;og_1639890078060&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;1. 페이징 성능 개선하기 - No Offset 사용하기&quot; data-og-description=&quot;일반적인 웹 서비스에서 페이징은 아주 흔하게 사용되는 기능입니다. 그래서 웹 백엔드 개발자분들은 기본적인 구현 방법을 다들 필수로 익히시는데요. 다만, 그렇게 기초적인 페이징 구현 방&quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/528&quot; data-og-url=&quot;https://jojoldu.tistory.com/528&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/D2oci/hyMKMM1X65/NZIMzYobsEcAFG5nCVwS6k/img.png?width=800&amp;amp;height=589&amp;amp;face=0_0_800_589,https://scrap.kakaocdn.net/dn/bbd08Y/hyMJKQOwjM/3ZLslFYYFctiYhYvKtiW71/img.png?width=800&amp;amp;height=589&amp;amp;face=0_0_800_589,https://scrap.kakaocdn.net/dn/o8Yyy/hyMKLHkVBR/9BbY8xkwhEOrRyLRBmtm1K/img.png?width=1314&amp;amp;height=968&amp;amp;face=0_0_1314_968&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/528&quot; data-source-url=&quot;https://jojoldu.tistory.com/528&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/D2oci/hyMKMM1X65/NZIMzYobsEcAFG5nCVwS6k/img.png?width=800&amp;amp;height=589&amp;amp;face=0_0_800_589,https://scrap.kakaocdn.net/dn/bbd08Y/hyMJKQOwjM/3ZLslFYYFctiYhYvKtiW71/img.png?width=800&amp;amp;height=589&amp;amp;face=0_0_800_589,https://scrap.kakaocdn.net/dn/o8Yyy/hyMKLHkVBR/9BbY8xkwhEOrRyLRBmtm1K/img.png?width=1314&amp;amp;height=968&amp;amp;face=0_0_1314_968');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;1. 페이징 성능 개선하기 - No Offset 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 웹 서비스에서 페이징은 아주 흔하게 사용되는 기능입니다. 그래서 웹 백엔드 개발자분들은 기본적인 구현 방법을 다들 필수로 익히시는데요. 다만, 그렇게 기초적인 페이징 구현 방&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jojoldu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1639889873344&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;1. 커버링 인덱스 (기본 지식 / WHERE / GROUP BY)&quot; data-og-description=&quot;일반적으로 인덱스를 설계한다고하면 WHERE 절에 대한 인덱스 설계를 이야기하지만 사실 WHERE 뿐만 아니라 쿼리 전체에 대해 인덱스 설계가 필요합니다. 인덱스의 전반적인 내용은 이전 포스팅을&quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/476?category=761883&quot; data-og-url=&quot;https://jojoldu.tistory.com/476&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cwbfNh/hyMKIcMSED/oKV5p53m33ksl8aRmxGsvK/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/bretfC/hyMJJEm9jE/8GdmdEmmoaPgrzyEMq6nmk/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/bg31na/hyMJPdyLFo/bB2dBCyivHciyWBtgUsmAK/img.png?width=2220&amp;amp;height=1200&amp;amp;face=0_0_2220_1200&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/476?category=761883&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jojoldu.tistory.com/476?category=761883&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cwbfNh/hyMKIcMSED/oKV5p53m33ksl8aRmxGsvK/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/bretfC/hyMJJEm9jE/8GdmdEmmoaPgrzyEMq6nmk/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/bg31na/hyMJPdyLFo/bB2dBCyivHciyWBtgUsmAK/img.png?width=2220&amp;amp;height=1200&amp;amp;face=0_0_2220_1200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;1. 커버링 인덱스 (기본 지식 / WHERE / GROUP BY)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 인덱스를 설계한다고하면 WHERE 절에 대한 인덱스 설계를 이야기하지만 사실 WHERE 뿐만 아니라 쿼리 전체에 대해 인덱스 설계가 필요합니다. 인덱스의 전반적인 내용은 이전 포스팅을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jojoldu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 성능 개선 - Update/Insert&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일괄 Update 최적화&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I7mCM/btrofGxt8mI/twmI0mKQir8GLvoIYVSd61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I7mCM/btrofGxt8mI/twmI0mKQir8GLvoIYVSd61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I7mCM/btrofGxt8mI/twmI0mKQir8GLvoIYVSd61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI7mCM%2FbtrofGxt8mI%2FtwmI0mKQir8GLvoIYVSd61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;233&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dirty Checking - 실시간 비즈니스 처리, 실시간 단건 처리&lt;/li&gt;
&lt;li&gt;Querydsl.update - 대량의 데이터를 일괄로 Update 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 일괄 업데이트는 &lt;b&gt;영속성 컨택스트의 1차 캐시 갱신이 안됨으로 Cache Eviction 이 필요&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JPA로 Bulk Insert는 자제한다.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d36Yvx/btrocSMGtAR/lFck78KG2AHcSObYxm55wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d36Yvx/btrocSMGtAR/lFck78KG2AHcSObYxm55wK/img.png&quot; data-alt=&quot;JPA 에서는 Insert 합치기가 적용되지 않는다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d36Yvx/btrocSMGtAR/lFck78KG2AHcSObYxm55wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd36Yvx%2FbtrocSMGtAR%2FlFck78KG2AHcSObYxm55wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;298&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JPA 에서는 Insert 합치기가 적용되지 않는다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1639891180852&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 3번의 DB 연결이 필요한 비효율적인 insert
INSERT INTO table_a (subject, content)
VALUES ('sub1', 'con1');
INSERT INTO table_a (subject, content)
VALUES ('sub2', 'con2');
INSERT INTO table_a (subject, content)
VALUES ('sub3', 'con3');

// 1번의 DB 연결로 개선한 효율적인 insert
INSERT INTO table_a (subject, content)
VALUES ('sub1', 'con1'),
       ('sub2', 'con2'),
       ('sub3', 'con3');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 위와 같은 성능 개선을 할 수 없다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상에서 JdbcTemplate를 말하지만 type safe 하지 않은 단점과 함께 EntityQL를 소개합니다. 하지만 이도 설정과 사용법에 단점을 가지고 있으므로 선택적으로 사용해야 된다고 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Spring 진영에서 공식적으로 밀어주는&amp;nbsp;&lt;b&gt;JOOQ&lt;/b&gt;를 추천합니다. starter 가 존재하기에 세팅도 쉽고, 사용 경험도 매우 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JPA, QueryDSL은 편리하지만 결국 DB에 쿼리를 날려서 조회하는 것이기에, 그 과정을 잘 이해하고 고민해야 합니다.&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보니 지금까지는 데이터가 적은 환경에서만 일하고 공부했기에, JPA를 심도 있게 사용하지 못했었습니다. 그러다 보니 문법과 기본적인 사용법은 알지만, 실무에서의 고급 활용을 위한 이해가 부족합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 지금의 회사에서도 JPA, Querydsl을 사용하긴 하지만, 아직은 JOOQ의 사용이 더 많습니다. DB 구성이 ORM 기술에 적합하지 않게 설계되어있고, Procedure도 많이 존재하기 때문이라고 생각합니다. 무엇보다도 &lt;b&gt;사용하는 사람이 기술에 대한 이해도가 높고 잘 사용해야 되는데&lt;/b&gt; 저부터도 이해도가&amp;nbsp;부족하다는 것을 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 내년의 회사 과제 중 하나로 MSA 구성이 있습니다. 이때 성능 분석도 도입하고, 열심히 공부해서 JPA를 좀 더 적극적으로 사용해보고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이팅!!&lt;/p&gt;</description>
      <category>Study</category>
      <category>mysql</category>
      <category>QueryDSL</category>
      <category>대용량 처리</category>
      <category>우아콘2020</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/36</guid>
      <comments>https://hyune-c.tistory.com/36#entry36comment</comments>
      <pubDate>Sun, 19 Dec 2021 18:30:56 +0900</pubDate>
    </item>
    <item>
      <title>Datadog 에서 GraphQL 모니터링 맛보기</title>
      <link>https://hyune-c.tistory.com/35</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLn5C0/btrnWR1HJeC/8Zod8mCHGuhJO32NGmA0A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLn5C0/btrnWR1HJeC/8Zod8mCHGuhJO32NGmA0A0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLn5C0/btrnWR1HJeC/8Zod8mCHGuhJO32NGmA0A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLn5C0%2FbtrnWR1HJeC%2F8Zod8mCHGuhJO32NGmA0A0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;150&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 널리 쓰이는 모니터링 솔루션 중 하나인 datadog을 적용하면서 알게 된 정보를 기록해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에 있는 내용은 언급만 하고 넘어가며, 완결되지 않은 이슈들도 있기에&amp;nbsp;&lt;b&gt;맛보기로&lt;/b&gt; 생각하고 읽어주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AS-IS&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;자동화된 모니터링 룰이 없으며, cloudwatch로 사후 모니터링만 가능.&lt;/li&gt;
&lt;li&gt;datadog를 도입하기로 했으나, graphql이 제대로 지원되지 않음.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;datadog 은 공식 문서가 잘 나와있는 편이기에 설치나 일반적인 세팅에는 어려움이 없었습니다. 특히 resource를 기반으로 제공되는 기능들이 강력해서 REST API를 쓰는 환경이라면 크게 어려움 없이 세팅이 가능했습니다. 하지만 당사에서 사용하는 graphql 은 resource 가 모두&amp;nbsp;&lt;b&gt;POST /graphql로&lt;/b&gt; 통일되어 버리는 문제가 있어 커스텀 설정이 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 설정으로 넘어가기 전 환경 변수로서 제공하는 기능들을 먼저 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;환경 변수로 사용 가능한 기능&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1639577765639&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Tracing Java Applications&quot; data-og-description=&quot;Datadog, the leading service for cloud-scale monitoring.&quot; data-og-host=&quot;docs.datadoghq.com&quot; data-og-source-url=&quot;https://docs.datadoghq.com/tracing/setup_overview/setup/java/?tab=containers&quot; data-og-url=&quot;https://docs.datadoghq.com/tracing/setup_overview/setup/java/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KHfYB/hyMG2Yc7Cv/KzmY0aRjjYJxIx2Lw1td9K/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/tracing/setup_overview/setup/java/?tab=containers&quot; data-source-url=&quot;https://docs.datadoghq.com/tracing/setup_overview/setup/java/?tab=containers&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KHfYB/hyMG2Yc7Cv/KzmY0aRjjYJxIx2Lw1td9K/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Tracing Java Applications&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Datadog, the leading service for cloud-scale monitoring.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.datadoghq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 id=&quot;1.-요청-고유-값-(by-MDC-key-injection)&quot; data-renderer-start-pos=&quot;203&quot; data-ke-size=&quot;size23&quot;&gt;1. 요청 고유 값&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-Ddd.logs.injection=true&lt;/li&gt;
&lt;li&gt;default=false&lt;/li&gt;
&lt;li&gt;Enable automatic MDC key injection for Datadog trace and span IDs.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2.-Query-Param&quot; data-renderer-start-pos=&quot;337&quot; data-ke-size=&quot;size23&quot;&gt;2. Query Param&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-Ddd.http.server.tag.query-string=true&lt;/li&gt;
&lt;li&gt;default=false&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn9HOB/btrnQ5MtoCT/mYSbnk1hfp25T6fnMEIWRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn9HOB/btrnQ5MtoCT/mYSbnk1hfp25T6fnMEIWRk/img.png&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;314&quot; style=&quot;width: 54.1589%; margin-right: 10px;&quot; data-widthpercent=&quot;54.82&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn9HOB/btrnQ5MtoCT/mYSbnk1hfp25T6fnMEIWRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn9HOB%2FbtrnQ5MtoCT%2FmYSbnk1hfp25T6fnMEIWRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rd34L/btrnVxHLxsK/1LWi4iyt6ldIJSkpySK6pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rd34L/btrnVxHLxsK/1LWi4iyt6ldIJSkpySK6pK/img.png&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;376&quot; style=&quot;width: 44.6333%;&quot; data-widthpercent=&quot;45.18&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rd34L/btrnVxHLxsK/1LWi4iyt6ldIJSkpySK6pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frd34L%2FbtrnVxHLxsK%2F1LWi4iyt6ldIJSkpySK6pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;375&quot; height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-renderer-start-pos=&quot;426&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3.-Header&quot; data-renderer-start-pos=&quot;426&quot; data-ke-size=&quot;size23&quot;&gt;3. Header&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-Ddd.trace.header.tags={header_name}:{datagod_display_name}
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-Ddd.trace.header.tags=Content-Type:Content-Type-Name&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;default=null&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyUdsK/btrnR224LlG/5wTjQoYZzrSd7HDu3Pyqk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyUdsK/btrnR224LlG/5wTjQoYZzrSd7HDu3Pyqk1/img.png&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;290&quot; style=&quot;width: 50.5834%; margin-right: 10px;&quot; data-widthpercent=&quot;51.18&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyUdsK/btrnR224LlG/5wTjQoYZzrSd7HDu3Pyqk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyUdsK%2FbtrnR224LlG%2F5wTjQoYZzrSd7HDu3Pyqk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;364&quot; height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AmgZy/btrnWRyYZhC/FD1g7xJQAjSl3T5Eko5i0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AmgZy/btrnWRyYZhC/FD1g7xJQAjSl3T5Eko5i0K/img.png&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;304&quot; style=&quot;width: 48.2539%;&quot; data-widthpercent=&quot;48.82&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AmgZy/btrnWRyYZhC/FD1g7xJQAjSl3T5Eko5i0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAmgZy%2FbtrnWRyYZhC%2FFD1g7xJQAjSl3T5Eko5i0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;364&quot; height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Body&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 기능 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1639578035967&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Can trace http request / response? &amp;middot; Issue #2236 &amp;middot; DataDog/dd-trace-java&quot; data-og-description=&quot;My company use datadog Java Trace Agent. Can trace http request / response? I can't manual at that.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/DataDog/dd-trace-java/issues/2236&quot; data-og-url=&quot;https://github.com/DataDog/dd-trace-java/issues/2236&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DzI7w/hyMH2DMlqK/f8bzmHLetXrbk6VhIuAzu0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/DataDog/dd-trace-java/issues/2236&quot; data-source-url=&quot;https://github.com/DataDog/dd-trace-java/issues/2236&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DzI7w/hyMH2DMlqK/f8bzmHLetXrbk6VhIuAzu0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Can trace http request / response? &amp;middot; Issue #2236 &amp;middot; DataDog/dd-trace-java&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;My company use datadog Java Trace Agent. Can trace http request / response? I can't manual at that.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;커스텀 설정으로 사용 가능한 기능 (CustomTag)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Custom&amp;nbsp;Tag&amp;nbsp;개발을&amp;nbsp;위한&amp;nbsp;기본&amp;nbsp;이해&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF4SGU/btrn064wGRx/vDkKNC9y0iEqYowrYeKKMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF4SGU/btrn064wGRx/vDkKNC9y0iEqYowrYeKKMK/img.png&quot; data-alt=&quot;APM 화면과 매칭하여 용어 설명&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF4SGU/btrn064wGRx/vDkKNC9y0iEqYowrYeKKMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF4SGU%2Fbtrn064wGRx%2FvDkKNC9y0iEqYowrYeKKMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1581&quot; height=&quot;833&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;833&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;APM 화면과 매칭하여 용어 설명&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAD9W4/btrnUPa4qF2/rfE49AL397lUxRpScfMvM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAD9W4/btrnUPa4qF2/rfE49AL397lUxRpScfMvM1/img.png&quot; data-alt=&quot;Span 관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAD9W4/btrnUPa4qF2/rfE49AL397lUxRpScfMvM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAD9W4%2FbtrnUPa4qF2%2FrfE49AL397lUxRpScfMvM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;833&quot; height=&quot;143&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Span 관계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;childSpan(Span) &amp;harr;︎ rootSpan(MutableSpan)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;datadog의 모니터링 단위는 span으로 이루어져 있습니다.&lt;/li&gt;
&lt;li&gt;span을 사용하는 곳에 @Trace 어노테이션 사용이 필수입니다.&lt;/li&gt;
&lt;li&gt;custom tag는 항상 rootSpan 에도 기록하는 것이 모니터링 편의상 좋습니다.&lt;/li&gt;
&lt;li&gt;Span과 MutableSpan 은 캐스팅이 필요하며, 서로 다른 라이브러리를 참조하고 있기에 미묘하게 사용법이 다릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;final Span childSpan = GlobalTracer.get().activeSpan();
final MutableSpan rootSpan = ((MutableSpan) GlobalTracer.get().activeSpan()).getLocalRootSpan();

// error 처리를 위한 사용법이 서로 다름
childSpan.setTag(Tags.ERROR,true);
rootSpan.setError(true);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;customTag &amp;harr;︎ hostTag&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;hostTag&lt;/b&gt; - 대시보드, 메트릭 등에서 감지되는 tag로 사전 정의되었기에 환경 변수를 통해 사용 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;customTag&lt;/b&gt; - 임의로 주입할 수 있지만, 대시보드, 메트릭 등에서 감지되는 않는 tag입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Custom Tag 사용을 위한 Component 만들기&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class DatadogSender {

 private static final String GRAPHQL_RESOURCE_PREFIX = &quot;/graphql&quot;;
 private static final String ERROR_CODE_TAG_KEY = &quot;error.error_code&quot;;
 private static final String USERNAME_TAG_KEY = &quot;user.username&quot;;

 @Trace
 public static void setTag(final String key, final Object value) {
  final Span childSpan = GlobalTracer.get().activeSpan();
  final MutableSpan rootSpan = ((MutableSpan) GlobalTracer.get().activeSpan()).getLocalRootSpan();

  childSpan.setTag(key, String.valueOf(value));
  rootSpan.setTag(key, String.valueOf(value));
 }

 @Trace
 public static void setErrorTag(final GraphQLError error) {
  final Span childSpan = GlobalTracer.get().activeSpan();
  final MutableSpan rootSpan = ((MutableSpan) GlobalTracer.get().activeSpan()).getLocalRootSpan();

  childSpan.setTag(Tags.ERROR, true);
  rootSpan.setError(true);

  // errorMsg 와 errorCode 는 항상 기록됩니다.
  setTag(DDTags.ERROR_MSG, error.getMessage());
  setTag(ERROR_CODE_TAG_KEY, GraphQLExceptionUtils.errorCode(error));

  // stackTrace, ResourceName, Principal 은 가져올 수 있는 경우만 기록됩니다.
  setTag(DDTags.ERROR_STACK, GraphQLExceptionUtils.stackTrace(error));
  setResourceNameTag(GraphQLExceptionUtils.resourceName(error));
  setPrincipalTag();
 }

 public static void setResourceNameTag(final String value) {
  final String resourceName = GRAPHQL_RESOURCE_PREFIX + (value.startsWith(&quot;/&quot;) ? value : &quot;/&quot; + value);
  setTag(DDTags.RESOURCE_NAME, resourceName);
 }

 public static void setPrincipalTag() {
  Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication().getPrincipal())
    .filter(UserPrincipal.class::isInstance)
    .map(UserPrincipal.class::cast)
    .ifPresent(principal -&amp;gt; setTag(USERNAME_TAG_KEY, principal.getUsername()));
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 편의상 rootSpan에도 항상 기록해주는 것이 좋기 때문에 헬프성 Component를 구현했습니다.&lt;br /&gt;실무에서 사용하는 Component에서 일부 기능만을 가져와 각색한 것이기에 흐름만 이해해주시길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요점은 &lt;b&gt;childSpan과 rootSpan을 항상 같이 사용&lt;/b&gt;해주고, &lt;b&gt;error 처리는 handler에서 GraphQLError를 통해&lt;/b&gt; 한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. GraphQL에서의 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 제일 고생했던 GraphQL에 적용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글의 처음에 쓴 것처럼 datadog 은 아직 GraphQL을 지원하지 않고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;97&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PlmcN/btrn1yfrGt9/SalG1nqqIa5SJGnFVuzbxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PlmcN/btrn1yfrGt9/SalG1nqqIa5SJGnFVuzbxk/img.png&quot; data-alt=&quot;공식 support 채널에도 요청했지만 준비 중이라고 기달려달라는 답변만..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PlmcN/btrn1yfrGt9/SalG1nqqIa5SJGnFVuzbxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPlmcN%2Fbtrn1yfrGt9%2FSalG1nqqIa5SJGnFVuzbxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1145&quot; height=&quot;97&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;97&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공식 support 채널에도 요청했지만 준비 중이라고 기달려달라는 답변만..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP를 쓰면 되겠지라는 생각으로 접근해서 &lt;b&gt;requestJson&lt;/b&gt;의 기록은 성공했지만, &lt;b&gt;resourceName&lt;/b&gt; 은 쉽지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL의 특성상 &lt;b&gt;requestBody&lt;/b&gt;를 파싱해야 &lt;b&gt;resourceName&lt;/b&gt; 이 나오는데 AOP를 설정이 힘들었습니다. &lt;br /&gt;처음에는 프론트의 도움을 받아 &lt;b&gt;resourceName&lt;/b&gt;을 별도의 key로 빼려고 생각했지만 프론트의 작업량이 너무 많아 취소했고, 어쩔 수 없이 우회 안을 생각해야만 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 로그인 요청을 제외한 모든 요청이 인가가 필요하다는 것에 착안해서 &lt;b&gt;PreAuthorize&lt;/b&gt;에 AOP를 거는 생각을 했고 성공했습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Aspect
@Component
@RequiredArgsConstructor
public class GraphQLDatadogAspect {

 @Pointcut(&quot;@annotation(org.springframework.security.access.prepost.PreAuthorize)&quot;)
 public void preAuthorizePointCut() {
 }

 @Before(&quot;preAuthorizePointCut()&quot;)
 public void beforePreAuthorize(final JoinPoint joinPoint) {
  DatadogSender.setResourceNameTag(joinPoint.getSignature().getName());
 }

 @Before(value = &quot;execution(* graphql.kickstart.execution.GraphQLObjectMapper.readGraphQLRequest(..))&quot;)
 public void graphqlRequest(final JoinPoint joinPoint) {
   ...
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1439&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3xYyv/btrn21nW7qX/cY81kOq8msacEzrQPcAswK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3xYyv/btrn21nW7qX/cY81kOq8msacEzrQPcAswK/img.png&quot; data-alt=&quot;성공과 실패건 모두 resourceName 이 보입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3xYyv/btrn21nW7qX/cY81kOq8msacEzrQPcAswK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3xYyv%2Fbtrn21nW7qX%2FcY81kOq8msacEzrQPcAswK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1089&quot; height=&quot;95&quot; data-origin-width=&quot;1439&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;성공과 실패건 모두 resourceName 이 보입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실패 건의 경우 &lt;b&gt;status_code&lt;/b&gt;는 200이고 좌측에 빨간 불이 들어온 것을 볼 수 있습니다. 이 이유는 예외 처리에서 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 예외 처리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Error Span 처리 흐름&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lWygY/btrnWRUXLcj/V0K6aq9ZlAYNk1tj4Hc760/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lWygY/btrnWRUXLcj/V0K6aq9ZlAYNk1tj4Hc760/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lWygY/btrnWRUXLcj/V0K6aq9ZlAYNk1tj4Hc760/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlWygY%2FbtrnWRUXLcj%2FV0K6aq9ZlAYNk1tj4Hc760%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;725&quot; height=&quot;544&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;제가 다니는 회사를 포함하여 많은 회사들은 RESTful API를 사용하지 못하고 있습니다.&lt;br /&gt;대표적인 것 중 하나가 status_code의 별도 관리 입니다. &lt;br /&gt;보통 요청 결과가 에러이더라도 status_code는 200으로 받고 respone body에 별도의 code를 사용합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시퀀스 다이어그램을 보면 내부 에러 코드가 9999 인 경우에만 error로 간주하여 setErrorTag()를 거치게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 과정에서 status_code는 200으로 유지되지만 span 은 error로 처리되어 모니터링 대상이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vePo1/btrnWRgifSD/7rk70b4nPpbZ3ttGLkebZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vePo1/btrnWRgifSD/7rk70b4nPpbZ3ttGLkebZk/img.png&quot; data-alt=&quot;200 ok 이지만 error span 으로 체크되고, stackTrace 가 기록 됩니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vePo1/btrnWRgifSD/7rk70b4nPpbZ3ttGLkebZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvePo1%2FbtrnWRgifSD%2F7rk70b4nPpbZ3ttGLkebZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;553&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;200 ok 이지만 error span 으로 체크되고, stackTrace 가 기록 됩니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모니터링 편의성 &amp;amp; 자동화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작업한 errorSpan 은 datadog에서 지원하는 Monitors 기능을 통해 slack 알림이 가능합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1639583538995&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Create Monitors&quot; data-og-description=&quot;Create monitors&quot; data-og-host=&quot;docs.datadoghq.com&quot; data-og-source-url=&quot;https://docs.datadoghq.com/monitors/create/&quot; data-og-url=&quot;https://docs.datadoghq.com/monitors/create/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bz3h2C/hyMH24UEew/nIno4015SNnq4AvWPIgCnK/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://docs.datadoghq.com/monitors/create/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.datadoghq.com/monitors/create/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bz3h2C/hyMH24UEew/nIno4015SNnq4AvWPIgCnK/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Create Monitors&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Create monitors&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.datadoghq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dconr/btrn0X080pX/0CgchOGL8RdiQ4RcNjD4UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dconr/btrn0X080pX/0CgchOGL8RdiQ4RcNjD4UK/img.png&quot; data-alt=&quot;slack 알림&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dconr/btrn0X080pX/0CgchOGL8RdiQ4RcNjD4UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDconr%2Fbtrn0X080pX%2F0CgchOGL8RdiQ4RcNjD4UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;394&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;slack 알림&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 백엔드 개발자로서 개발보다 중요한 게 운영이고, 그러기 위해 모니터링 자동화가 필수적이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 한글 자료가 적어 설정하는 데에는 정말 고생했지만, 목표한 바를 이뤄 조금 만족했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 datadog 은 정말 많은 integration을 제공해주고 있고 이것들을 조금씩 설정해나가야 하기에 아직 할 것은 많아 보입니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 작업하거나 시행착오 겪은 것들이 많지만 모두 기록하기는 힘들어 키워드만 남기면서 글을 마무리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cloudwatch와 datadog logs의 연동&lt;/li&gt;
&lt;li&gt;datadog agent의 한계상 response body의 기록은 5000byte&amp;nbsp;&lt;/li&gt;
&lt;li&gt;유저의 액션을 모두 추적 - RUM(프런트)과 APM(백엔드)의 연계&lt;/li&gt;
&lt;li&gt;Session Replay 기능&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Series/실전!</category>
      <category>datadog</category>
      <category>graphql</category>
      <category>monitoring</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/35</guid>
      <comments>https://hyune-c.tistory.com/35#entry35comment</comments>
      <pubDate>Tue, 14 Dec 2021 23:44:25 +0900</pubDate>
    </item>
    <item>
      <title>어느 새벽에 작성하는 첫 회고록</title>
      <link>https://hyune-c.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자 커리어를 시작한 지 1년 4개월이 된 어느 날 새벽, 소용돌이치는 생각들을 정리하려 쓴다 쓴다 생각만 했던 회고를 기록해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발자가 되기 위해..&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열정적이지 못했던 인프라/기술지원을 그만두면서 개발자가 되기 위한 방법을 찾았습니다. 늦은 나이에 시작한 개발이기에 돈보다 중요한 건 시간이었습니다. 그래서 국비 지원보다는 부트캠프가 좋다고 판단했고, 몇 개의 비교 끝에 코드스쿼드 교육기관을 선택했습니다. &lt;b&gt;그리고&amp;nbsp; 그것은 저에게 최고의 선택이었습니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 주입식 교육이 아닌 자기 학습을 위한 교육 방법과 몰입할 수 있는 가이드라인을 제시해주었고, 저에게는 꽤나 맞는 교육법이었습니다. 교육기간 내내 교육장에서 가장 늦게 나가는 학생 중 한 명이었고 배수진을 치고 온 만큼 절박하게 공부했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;그리고 수료와 동시에 백엔드 개발자로서 커리어를 시작했습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이전 회사의 경험으로 IT 전반에 대한 지식이 있었기에, 혼자 하는 공부보다는 실무의 사이클을 경험하는 것이 훨씬 중요하다고 생각했습니다. 그렇기에 준비를 더 해야 되는 대기업보다는 지금 갈 수 있는 가장 빠른 회사를 선택했고, 처음에는 작은 실무의 사이클을 개발하고 경험할 수 있다는 것만으로도 너무 만족스러웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 작은 스타트업의 단점들이 나오기 시작했고, 제대로 된&amp;nbsp;시니어가 없는 상황에서의 몇 번의 개선 시도는 실패하기 일쑤였습니다. 성장이 고팠던 저는 불만이 쌓였고,&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;그렇게 1년 동안 2개의 회사를 거쳤습니다.&amp;nbsp;&lt;/span&gt;짧은 기간에 2번의 이직을 하면서 힘들기도 했지만 하루하루를 코드스쿼드 때보다 더 몰입해서 공부했고, 어떻게 성장할 것인가에 대한 작은 단서를 찾기도 했습니다.&lt;s&gt;라고&amp;nbsp;생각했습니다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;슬럼프가 왔습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전의 회사는 구성원들의 연차도 적고, 아직 아이템도 뚜렷하지 않은 작은 스타트업이었습니다. 그렇다 보니 기획의 변경도 잦고, 기술의 깊이보다는 시간을 쏟아 해결할 수 있는 피쳐를 다수 개발하는 게 주 업무였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 &lt;b&gt;지금의 환경에 필요한 최소한의 기술을 &lt;b&gt;&lt;b&gt;&lt;s&gt;베스트 프랙티스가 아닐지라도 &lt;/s&gt;&lt;/b&gt;&lt;/b&gt;행할 수 있는 개발자가 좋은 개발자 &lt;/b&gt;라고 생각합니다. 그렇기에 야근이 강요되는 회사가 아니었음에도 항상 제가 원하여 막차를 타는 생활을 하면서 지금 필요한 피쳐 개발에 몰입했습니다. 아마 부족한 제 실력으로도 회사에 기여하고 있다는 소속감과 성장에 대한 기대감 덕분에 즐기며 일할 수 있었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 지금의 회사는 팀원들도 훌륭하고 깊이를 고려하며 성장할 수 있는 기반과 조금의 여유도 있는 훌륭한 환경입니다. 그러다 보니 개발 속도만 생각했던 이전과 다르게 깊이를 고민하면서 제 기술적 부족함을 직시할 수 있었습니다. 하지만 회사의 업무에서 &lt;b&gt;새로운 기술의 학습과 적용, 일정&lt;/b&gt;을 저울질하다 보니 자괴감이 들었고, 그동안 잘 실천하지 못했던 &lt;b&gt;설계의 중요성&lt;/b&gt;도 크게 느끼게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 가장 해서는 안 되는 것 중 하나인&amp;nbsp;&lt;b&gt;타인과의 비교&lt;/b&gt;를 하기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;xxx는 MSA를 하면서 대량 트래픽을 경험하고 있다던데 나는 뭐 하고 있는 거지..?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;yyy는 연봉을 zzz만원이나 받는다는데..&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사실 해결 방법은 알고 있습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저는 저 자신을 나태하고 수동적이라고 생각합니다. 그래서 몰입을 위해 강한 제약 사항을 걸곤 합니다.&lt;/b&gt; 이전의 회사들에서 걸었던 빠른 개발 일정이 그것 입니다. 하지만 지금의 회사에서는 다른 시야를 가져야 되면서 오는 차이와 그 사이에서 오는 나에 대한 실망감과 괴리감이 슬럼프의 원인일 것입니다. 하지만 이것은 커리어 몇 년차쯤 겪는 번아웃 중 하나이고, 업무에 익숙하지 않은 지금이 지나면 괜찮아질 과도기입니다. &lt;b&gt;&lt;s&gt;근데 난 아직 그 시기도 아니고, 다 알면서 왜 이럴까.. ㅠㅠ&amp;nbsp;&lt;/s&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다시 몰입을 위한 방법을 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 성장을 위해서 순수 기술에 대한 공부만, 시간을 쏟는 방법으로만 했습니다. 특히 '함께 자라기' 같은 책은 실무와는 먼 이론이라고만 생각했습니다. 하지만 슬럼프를 겪으면서 순수 기술에 대한 내용이 아니면 보지도 않던 유튜브를 보면서 마음을 정리했고, 개발바닥의 유튜브가 참 많은 도움이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Pb69UQ6f8n0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cINrce/hyMEgPqCNM/kTVjSAkPy91PkA4kOHr2mk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=130_74_1168_622&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Pb69UQ6f8n0&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;몰입과 함께 자라기에 대해 생각하게 해준 영상&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 옛날이었다면 이런 주제의 글을 쓰는 것을 굉장히 두려워했을 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 공부하면서 느낀 것은 모두 나와 같은 시기를 겪었거나 겪을 것이고, 약점이라고 생각하는 것을 공개하는 것이 창피하다고 생각할 필요가 없다는 것입니다. 오히려 하나의 팀이나 동료로서 더 좋은 방향으로 빌드업할 수 있도록 공유하는 것이 더 가치 있다는 것을 깨달았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 또한 이렇게 글을 쓰면서 소용돌이치는 생각들을 정리하는 좋은 시간이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 해결하지 못한 슬럼프이지만 곧 나아질 것을 기대하며 글을 마칩니다.&lt;/p&gt;</description>
      <category>잡담 &amp;amp; ETC</category>
      <category>백엔드 2년차</category>
      <category>성장 방법</category>
      <category>회고</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/34</guid>
      <comments>https://hyune-c.tistory.com/34#entry34comment</comments>
      <pubDate>Fri, 10 Dec 2021 06:57:24 +0900</pubDate>
    </item>
    <item>
      <title>느슨한 결합도의 설계를 위해! (2)</title>
      <link>https://hyune-c.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요구 사항 정리와 AS-IS 분석은 끝났지만 설계하기 전에 개발 범위 산정과 제약 조건을 먼저 생각해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 달성을 위한 리팩토링은 끝이 없기에 일정과 범위를 조율해야 하고, 개인의 이해도는 차이가 있기에 실무의 설계는 범용적인 구성을 해야 합니다. &lt;s&gt;디자인 패턴 공부의 필요성을 느끼는 요즘입니다.&lt;/s&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 생각을 가지고 개발한 과정을 기록해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발 범위 산정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8LYS4/btrmZJh367U/lItMBTLpvKgPnMpDPmYDYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8LYS4/btrmZJh367U/lItMBTLpvKgPnMpDPmYDYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8LYS4/btrmZJh367U/lItMBTLpvKgPnMpDPmYDYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8LYS4%2FbtrmZJh367U%2FlItMBTLpvKgPnMpDPmYDYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1081&quot; height=&quot;610&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 개발 범위는 심플하지만 앞으로 알림 톡의 종류가 많아질 것을 대비하면 AlarmGqlService의 호출도 interface로 만들고 내부 서비스들도 표준화시키는 것이 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개발 범위를 AlarmGqlService까지 확장하면 입력부에 대한 검증과 interface 설계에 따른 리팩토링도 추가되어야 해서 '알림 톡 호출 주소 변경'이라는 컴팩트한 목표에 맞지 않다고 생각했습니다. 그리고 사실 최소 범위도 이미 강한 결합도를 가졌기에 이 것을 해결하는 것이 우선이라고 생각했고, SysSmsSendService 정도는 가져갈 수 있다고 판단해서 개발 범위를 확정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제약 조건&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;2 영업일 이내의 개발 기간&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 data-ke-size=&quot;size16&quot;&gt;개발 기간이라는 단어에는 설계와 QA도 포함되어야 한다고 생각한 때가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실무를 하다 보니 개발과 배포는 직결되지 않고, QA 후 돌아오거나 기타 이슈로 늘어지는 일정은 예상하기 힘들기에 분리해야 된다는 것을 느꼈습니다. 좀 더 복잡한 업무라면 분석과 설계 기간도 분리했겠지만 이번 업무는 같이 해도 될만한 분량이라고 판단했고, 최소한의 개발 기간으로서 2일을 산정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AS-IS를 분석하면서 공통부로 분리해야 될 로직이 보였고, 사이드 프로젝트에서 연습한 JWT 적용의 패턴에서의 Provider 가 생각났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1638599981887&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Security 로 회원 가입을 구현해보자 (2)&quot; data-og-description=&quot;Step 3. JWT 적용 1. 준비 // build.gradle dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.2' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11..&quot; data-og-host=&quot;hyune-c.tistory.com&quot; data-og-source-url=&quot;https://hyune-c.tistory.com/entry/Spring-Security-%EB%A1%9C-%ED%9A%8C%EC%9B%90-%EA%B0%80%EC%9E%85%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-2?category=887017&quot; data-og-url=&quot;https://hyune-c.tistory.com/entry/Spring-Security-%EB%A1%9C-%ED%9A%8C%EC%9B%90-%EA%B0%80%EC%9E%85%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Td3jr/hyMAmHDZzd/T4XUAXuPvucA6IziA4sNTK/img.png?width=430&amp;amp;height=174&amp;amp;face=0_0_430_174,https://scrap.kakaocdn.net/dn/bezCQm/hyMAvdvWBK/Gin7X2gtuLMsSJyguv3o01/img.png?width=430&amp;amp;height=174&amp;amp;face=0_0_430_174,https://scrap.kakaocdn.net/dn/CtouA/hyMzjMxVDn/QNzeyXb5gIY6nuwZDmQ0d0/img.png?width=466&amp;amp;height=375&amp;amp;face=0_0_466_375&quot;&gt;&lt;a href=&quot;https://hyune-c.tistory.com/entry/Spring-Security-%EB%A1%9C-%ED%9A%8C%EC%9B%90-%EA%B0%80%EC%9E%85%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-2?category=887017&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hyune-c.tistory.com/entry/Spring-Security-%EB%A1%9C-%ED%9A%8C%EC%9B%90-%EA%B0%80%EC%9E%85%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-2?category=887017&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Td3jr/hyMAmHDZzd/T4XUAXuPvucA6IziA4sNTK/img.png?width=430&amp;amp;height=174&amp;amp;face=0_0_430_174,https://scrap.kakaocdn.net/dn/bezCQm/hyMAvdvWBK/Gin7X2gtuLMsSJyguv3o01/img.png?width=430&amp;amp;height=174&amp;amp;face=0_0_430_174,https://scrap.kakaocdn.net/dn/CtouA/hyMzjMxVDn/QNzeyXb5gIY6nuwZDmQ0d0/img.png?width=466&amp;amp;height=375&amp;amp;face=0_0_466_375');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Security 로 회원 가입을 구현해보자 (2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Step 3. JWT 적용 1. 준비 // build.gradle dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.2' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hyune-c.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVNIv9/btrm1jwrLHH/7SAURAViBwq7cyq9PFmuIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVNIv9/btrm1jwrLHH/7SAURAViBwq7cyq9PFmuIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVNIv9/btrm1jwrLHH/7SAURAViBwq7cyq9PFmuIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVNIv9%2Fbtrm1jwrLHH%2F7SAURAViBwq7cyq9PFmuIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1446&quot; height=&quot;725&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 설계한 모델입니다. 전송을 위한 책임을 ApiGetwaySender라는 정보 전문가에게 위임함으로써 일련 과정을 모듈화 시켰습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 환경 변수 관리&lt;/h4&gt;
&lt;pre id=&quot;code_1638605476399&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// application.yml
aws:
  lambda:
    apiGateway:
      baseUrl: ${API_GATEWAY_BASE_URL}
      username: ${API_GATEWAY_USERNAME}
      password: ${API_GATEWAY_PASSWORD}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;민감정보는 환경 변수로 관리합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Configuration
public class ApiGatewayConfiguration {

 @Value(&quot;${aws.lambda.apiGateway.baseurl}&quot;)
 private String baseUrl;

 @Value(&quot;${aws.lambda.apiGateway.username}&quot;)
 private String username;

 @Value(&quot;${aws.lambda.apiGateway.password}&quot;)
 private String password;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것을 주입받는 주체로서 Configuration을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 공통 변수, 로직 관리&lt;/h4&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@Component
@RequiredArgsConstructor
public class ApiGatewayProvider {

 // API GATEWAY 통신 오류
 public static final Supplier&amp;lt;CustomGraphQLException&amp;gt; BAD_RESPONSE_EXCEPTION =
   CustomGraphQLException.supply(&quot;M9999&quot;);

 private String baseUrl;
 private String username;
 private String password;

 public static final String SUCCESS_TEXT = &quot;Success&quot;;
 public static final String SENDING_NUMBER = &quot;15000000&quot;;

 private final ApiGatewayConfiguration configuration;
 private final RestTemplate restTemplate;

 @PostConstruct
 private void postConstruct() {
  baseUrl = configuration.getBaseUrl();
  username = configuration.getUsername();
  password = configuration.getPassword();
 }

 public String generateJwt() {
  final Request request = Request.of(GET_SESSION.getUrl(), username, password);
  final HttpHeaders headers = GET_SESSION.getHeaderType().getHttpHeadersFunction().apply(&quot;&quot;);
  final HttpEntity&amp;lt;Request&amp;gt; httpEntity = new HttpEntity&amp;lt;&amp;gt;(request, headers);

  final Response response = restTemplate.exchange(
      baseUrl + GET_SESSION.getUrl(),
      GET_SESSION.getHttpMethod(),
      httpEntity,
      Response.class
    )
    .getBody();

  return Optional.ofNullable(response)
    .map(Response::getToken)
    .orElseThrow(BAD_RESPONSE_EXCEPTION);
 }

 @Data
 @AllArgsConstructor(staticName = &quot;of&quot;)
 private static class Request {

  private String url;
  private String username;
  private String password;
 }

 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 private static class Response {

  private String token;
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Provider는 이 모듈에서 공통으로 사용되는 변수와 로직을 모아놓은 것으로 v2에서는 모든 요청에 jwt 가 필요합니다. 따라서 jwt를 가져올 수 있는 로직을 만들고 필요한 곳에서 호출할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 각 구현 Service 별 발송과 검증을 위한 값을 enum으로 관리&lt;/h4&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum ApiGatewayRequestType {

 GET_SESSION(&quot;/sessions&quot;, WITHOUT_AUTHORIZATION, POST, &quot;&quot;, Integer.MAX_VALUE),
 xxxxx1(&quot;/kakao/xxxxx1&quot;, WITH_AUTHORIZATION, POST, &quot;xxxxx1&quot;, 50),
 xxxxx2(&quot;/kakao/xxxxx2&quot;, WITH_AUTHORIZATION, POST, &quot;xxxxx2&quot;, 10),
 xxxxx3(&quot;/kakao/xxxxx3&quot;, WITH_AUTHORIZATION, POST, &quot;xxxxx3&quot;, 1000),
 RESET_PASSWORD(&quot;/kakao/resetpw&quot;, WITH_AUTHORIZATION, POST, &quot;reset_pw&quot;, 10),
 ;

 private final String url;
 private final ApiGatewayHeaderType headerType;
 private final HttpMethod httpMethod;
 private final String kakaoTemplateCode;
 private final int limit;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public enum ApiGatewayHeaderType {

 WITH_AUTHORIZATION(jwt -&amp;gt; {
  final HttpHeaders headers = new HttpHeaders();
  headers.setContentType(MediaType.APPLICATION_JSON);
  headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
  headers.setBearerAuth(jwt);
  return headers;
 }),
 WITHOUT_AUTHORIZATION(ignored -&amp;gt; {
  final HttpHeaders headers = new HttpHeaders();
  headers.setContentType(MediaType.APPLICATION_JSON);
  headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
  return headers;
 }),
 ;

 private final Function&amp;lt;String, HttpHeaders&amp;gt; httpHeadersFunction;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 jwt를 가져올 때는 jwt 가 없는 header 가 필요하며, 혹여 이후에도 jwt 가 필요 없는 요청이 생기는 경우를 위해 enum으로 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 검증, 후처리 로직과 발송 로직의 분리&lt;/h4&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ApiGatewaySender {

 private final ApiGatewayProvider apiGatewayProvider;
 private final RestTemplate restTemplate;

 public ResponseEntity&amp;lt;Response&amp;gt; request(final ApiGatewayRequestType requestType, final Object body) {
  final String jwt = apiGatewayProvider.generateJwt();
  final HttpHeaders headers = requestType.getHeaderType().getHttpHeadersFunction().apply(jwt);
  final HttpEntity&amp;lt;?&amp;gt; httpEntity = new HttpEntity&amp;lt;&amp;gt;(body, headers);
  final String url = apiGatewayProvider.getBaseUrl() + requestType.getUrl();

  return restTemplate.exchange(
    url,
    requestType.getHttpMethod(),
    httpEntity,
    Response.class);
 }

 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
 public static class Response {

  private String messageId;
  private String to;
  private String status;
  private String text;

  public boolean isSuccess() {
   return ApiGatewayProvider.SUCCESS_TEXT.equals(text);
  }
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발송의 책임을 ApiGetwaySender로 모으고 공통 응답에서 성공 여부를 검증하는 메서드도 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 기존 구현 Service를 검증, 후처리 로직만 남을 수 있도록 개선&lt;/h4&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ResetPasswordService {

 // 1일 최대 10개까지 발송하실 수 있습니다.
 private static final Supplier&amp;lt;CustomGraphQLException&amp;gt; RESET_PASSWORD_EXCEPTION =
   CustomGraphQLException.supply(&quot;M0001&quot;);
 private static final ApiGatewayRequestType REQUEST_TYPE = RESET_PASSWORD;

 private final SysSmsSendService sysSmsSendService;
 private final ApiGatewaySender apiGatewaySender;

 public void send(final String receiveNumber, final String paramsPassword) {
  final Request request = Request.of(receiveNumber, paramsPassword);
  sendValidation(request.getReceiveNumber());
  final ResponseEntity&amp;lt;Response&amp;gt; exchange = apiGatewaySender.request(REQUEST_TYPE, request);

  Optional.ofNullable(exchange)
    .map(HttpEntity::getBody)
    .filter(Response::isSuccess)
    .ifPresentOrElse(
      response -&amp;gt; sysSmsSendService.insertSysSmsSend(
        null,
        response.getMessageId(),
        response.getTo(),
        REQUEST_TYPE.getUrl(),
        response.getStatus()
      ),
      () -&amp;gt; {
       throw BAD_RESPONSE_EXCEPTION.get();
      }
    );
 }

 private void sendValidation(final String receiveNumber) {
  final int sendCount = sysSmsSendService.getResetPasswordLimitCount(receiveNumber);
  if (sendCount &amp;gt;= REQUEST_TYPE.getLimit()) {
   throw RESET_PASSWORD_EXCEPTION.get();
  }
 }

 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
 public static class Request {

  private String templateCode;
  private String receiveNumber;
  private String sendingNumber;
  private String paramsPassword;
  private String paramsLoginUrl;

  public static Request of(final String receiveNumber, final String paramsPassword) {
   final String paramsLoginUrl = generateParamsLoginUrl();

   return new Request(
     REQUEST_TYPE.getKakaoTemplateCode(),
     SplitAndJoinUtils.joinCountryContactsNumber(receiveNumber),
     SENDING_NUMBER,
     paramsPassword,
     paramsLoginUrl
   );
  }

  private static String generateParamsLoginUrl() {
   if (isStageServer()) {
    return &quot;https://stage.company.com/login&quot;;
   }

   if (isDevServer()) {
    return &quot;https://dev.company.com/login&quot;;
   }

   return &quot;https://prod.company.com/login&quot;;
  }
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 발송은 ApiGatewaySender에게 위임하고 검증, 후처리, 발송에 필요한 값 역시 ApiGatewayRequestType를 통해 가져와 로직을 단순화 시킬 수 있습니다. 그리고 ApiGatewayRequestType를 클래스 변수로 관리함으로써 성능의 향상도 기대할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 테스트&lt;/h4&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 실제 알림톡을 받으려면 @disable 를 풀고 receiveNumber 를 기록합니다.
@SpringBootTest
class ResetPasswordServiceTest {

 @Autowired ResetPasswordService passwordService;

 @DisplayName(&quot;알림톡 - 비밀번호 초기화&quot;)
 @Test
 void getJwt() {
  // given

  // when
  passwordService.send(&quot;010-****-****&quot;, &quot;12345&quot;);

  // then

 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영향도를 줄이기 위해서는 mock 을 활용하여야겠지만, 이미 mock을 활용한 통합 테스트가 존재하고 있었습니다. 그리고 개발간 매번 postman을 활용하는 것은 힘들었기에 샘플 테스트를 만들고 merge 시에는 @Disabled로 변경했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞의 과정들을 통해 목표한 수준의 느슨한 결합도는 달성했지만, 실무라는 제한적인 환경이다 보니 좀 더 만족스럽게 하지 못한 부분이 있습니다. 예를 들면 jwt 캐싱, 인터페이스를 통한 설계, 역할을 분리해서 조합으로 구성, 특히 이 글을 작성하게 영감을 준 로치의 글에서 나온 Validator의 분리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그러기 위해서는 목표 범위 이상을 개선해야 했고, 무엇보다 제 디자인 패턴 지식이 부족함을 느꼈습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 마치며 제가 생각하는 다음 스텝의 설계를 남겨봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hyune-c.tistory.com/entry/느슨한-결합도의-설계를-위해-1&quot;&gt;2021.12.03 - [Computer Science/Software Architecture] - 느슨한 결합도의 설계를 위해! (1)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pp8b5/btrmXvk58K8/8GGDcgbI5TfEF7yro1mgY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pp8b5/btrmXvk58K8/8GGDcgbI5TfEF7yro1mgY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pp8b5/btrmXvk58K8/8GGDcgbI5TfEF7yro1mgY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpp8b5%2FbtrmXvk58K8%2F8GGDcgbI5TfEF7yro1mgY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1369&quot; height=&quot;725&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Series/실전!</category>
      <category>느슨한 결합도</category>
      <category>디자인 패턴</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/33</guid>
      <comments>https://hyune-c.tistory.com/33#entry33comment</comments>
      <pubDate>Sat, 4 Dec 2021 16:39:12 +0900</pubDate>
    </item>
    <item>
      <title>느슨한 결합도의 설계를 위해! (1)</title>
      <link>https://hyune-c.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;높은 응집도와 느슨한 결합도의 설계는 객체 지향을 공부하는 사람이라면 누구나 고민하는 주제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부는 했었지만 부끄럽게도 잘 활용하지 못하던 중 같이 공부하는 로치의 글을 보고, 또 운 좋게 설계가 필요한 업무를 맡게 되면서 개발한 내용을 기록해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1638538089067&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;응집도와 결합도&quot; data-og-description=&quot;응집도와 결합도는 소프트웨어 품질을 결정 짓는 요소이다. 대다수의 사람들은 코드를 작성할때 &amp;quot;응집도&amp;quot; 와 &amp;quot;결합도&amp;quot; 를 생각하지 않은채 관성적으로 코드를 적고는 한다. 응집도와 결합도는 &quot; data-og-host=&quot;devroach.tistory.com&quot; data-og-source-url=&quot;https://devroach.tistory.com/58&quot; data-og-url=&quot;https://devroach.tistory.com/58&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cWSNTZ/hyMAzfyY1c/8PmxRF4c05Kn8CxmUpK8hK/img.png?width=800&amp;amp;height=672&amp;amp;face=0_0_800_672,https://scrap.kakaocdn.net/dn/ngwej/hyMzq5C1Ks/kZ1vBTkmaRZ5K2T6kEYGa1/img.png?width=800&amp;amp;height=672&amp;amp;face=0_0_800_672,https://scrap.kakaocdn.net/dn/bmC3hZ/hyMzvZ8a2W/C0f94tUZEtAslNrBqtzoP1/img.png?width=1298&amp;amp;height=856&amp;amp;face=0_0_1298_856&quot;&gt;&lt;a href=&quot;https://devroach.tistory.com/58&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devroach.tistory.com/58&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cWSNTZ/hyMAzfyY1c/8PmxRF4c05Kn8CxmUpK8hK/img.png?width=800&amp;amp;height=672&amp;amp;face=0_0_800_672,https://scrap.kakaocdn.net/dn/ngwej/hyMzq5C1Ks/kZ1vBTkmaRZ5K2T6kEYGa1/img.png?width=800&amp;amp;height=672&amp;amp;face=0_0_800_672,https://scrap.kakaocdn.net/dn/bmC3hZ/hyMzvZ8a2W/C0f94tUZEtAslNrBqtzoP1/img.png?width=1298&amp;amp;height=856&amp;amp;face=0_0_1298_856');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;응집도와 결합도&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;응집도와 결합도는 소프트웨어 품질을 결정 짓는 요소이다. 대다수의 사람들은 코드를 작성할때 &quot;응집도&quot; 와 &quot;결합도&quot; 를 생각하지 않은채 관성적으로 코드를 적고는 한다. 응집도와 결합도는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devroach.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;업무 배경&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;알림 톡 발송의 주체는 외부 업체로 Lambda를 통해 연동되어 있습니다.&lt;/li&gt;
&lt;li&gt;발송 기록은 저장되고 있습니다. (백엔드 - RDB, Lambda - DynamoDB)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발 요건&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Lambda API의 주소가 변경되었습니다. (v1 -&amp;gt; v2)&lt;/li&gt;
&lt;li&gt;v2에는 jwt 인증이 추가되었습니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;jwt 만료 시간은 5분이며 refresh token 은 없습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AS-IS 분석&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dq7SYs/btrm2oqXVBA/GRlemFXZOcB4mMKFRgs7e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dq7SYs/btrm2oqXVBA/GRlemFXZOcB4mMKFRgs7e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dq7SYs/btrm2oqXVBA/GRlemFXZOcB4mMKFRgs7e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdq7SYs%2Fbtrm2oqXVBA%2FGRlemFXZOcB4mMKFRgs7e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1081&quot; height=&quot;630&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개행을 좀 더 의미 있게&amp;nbsp;할 수 있을 것 같습니다.&lt;/li&gt;
&lt;li&gt;모든 환경 변수가 @Value로 각 서비스에 반복되어 주입되고 있습니다.&lt;/li&gt;
&lt;li&gt;환경 변수 외의 공통 변수는 ApiGatewayCommon에 관리되고 있지만 좀 더 잘 관리할 수 있을 것 같습니다.&lt;/li&gt;
&lt;li&gt;상수들이 public으로 선언되어 있습니다. 그리고 좀 더 일관성 있게 관리하고 싶습니다.&lt;/li&gt;
&lt;li&gt;validation 로직이 반복되고 있습니다.&lt;/li&gt;
&lt;li&gt;모든 서비스에서 Lambda 호출 로직이 반복되고 있습니다. 그리고 실패 로직이 너무 복잡합니다.&lt;/li&gt;
&lt;li&gt;Request는 각 service 별로 다르기에 inner class로 만든 것은 좋지만, 좀 더 정제할 수 있을 것 같습니다.&lt;/li&gt;
&lt;li&gt;Response는 모든 service에서 같기에 중복을 제거할 수 있을 것 같습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AS-IS service 중 하나를 각색한 코드를 첨부하며 다음 글에서는 개선 과정을 기록해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hyune-c.tistory.com/entry/느슨한-결합도의-설계를-위해-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2021.12.03 - [Computer Science/Software Architecture] - 느슨한 결합도의 설계를 위해! (1)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ResetPasswordService {

 // 1. 환경 변수로 관리되는 baseUrl
 @Value(&quot;${aws.lambda.apiGateway}&quot;)
 public String BASE_URL;

 // 2. public static final 변수들
 public static final String TEMPLATE_CODE = &quot;/company/resetpw&quot;;

 public static final Supplier&amp;lt;CustomGraphQLException&amp;gt; RESET_PASSWORD_EXCEPTION =
   CustomGraphQLException.supply(&quot;M0001&quot;);

 public static final Integer RESET_PASSWORD_LIMIT = 10;

 private final RestTemplate restTemplate;

 private final SysSmsSendService sysSmsSendService;

 public void send(String receiveNumber, String paramsPassword) {
  Request request = Request.of(receiveNumber, paramsPassword);

  // 3. validation 로직과 일치하지 않는 메소드명
  int sendCount = sysSmsSendService.getResetPassword(request.getReceiveNumber());

  if (sendCount &amp;gt;= RESET_PASSWORD_LIMIT) {
   throw RESET_PASSWORD_EXCEPTION.get();
  }

  HttpEntity&amp;lt;?&amp;gt; entity = new HttpEntity&amp;lt;&amp;gt;(request, getHttpHeaders());

  ResponseEntity&amp;lt;Response&amp;gt; exchange =
    restTemplate.exchange(BASE_URL + TEMPLATE_CODE, POST, entity, Response.class);

  // 4. 복잡한 API 호출 정상 여부 판단
  if (isNull(exchange.getBody()) || isNull(exchange.getBody().getText())
    || !SUCCESS.equals(exchange.getBody().getText())) {
   throw CustomGraphQLException.supply(&quot;API GATEWAY 통신 오류&quot;).get();
  }

  // 5. 전송 기록
  sysSmsSendService.insertSysSmsSend(...);
 }

 public static class Request {

  private String templateCode;
  private String receiveNumber;
  private String sendingNumber;
  private String paramsPassword;
  private String paramsLoginUrl;

  public static Request of(String receiveNumber, String paramsPassword) {
   // 6. 카카오톡 templateCode
   String templateCode = &quot;company_reset_pw&quot;;
   // 7. 작동 환경에 따라 달라지는 redirect url
   String paramsLoginUrl = &quot;https://prod.company.com/login&quot;;
   if (isStageServer()) {
    paramsLoginUrl = &quot;https://stage.company.co.kr/login&quot;;
   }
   if (isDevServer()) {
    paramsLoginUrl = &quot;https://dev.company.co.kr/login&quot;;
   }
   return new Request(
     templateCode,
     SplitAndJoinUtils.joinCountryContactsNumber(receiveNumber),
     // 8. 공통으로 관리되는 발송 번호
     ApiGatewayCommon.SENDING_NUMBER,
     paramsPassword,
     paramsLoginUrl
   );
  }
 }

 // 9. 공통 응답
 public static class Response {

  private String messageId;
  private String to;
  private String status;
  private String text;
 }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Series/실전!</category>
      <category>느슨한 결합도</category>
      <category>디자인 패턴</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/32</guid>
      <comments>https://hyune-c.tistory.com/32#entry32comment</comments>
      <pubDate>Fri, 3 Dec 2021 22:29:39 +0900</pubDate>
    </item>
    <item>
      <title>DB 값을 enum 으로 표현해보자</title>
      <link>https://hyune-c.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;enum 은 서로 연관된 상수들의 집합으로 반복적으로 사용되는 코드 값을 표현할 때 유용합니다.&lt;br /&gt;실무에서는 개발 편의성을 위해 DB에 있는 업무 코드들을 표현하기도 합니다. (등급 코드, 사용 여부 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프로젝트가 나눠지면 매번 복사해줘야 되고, 변경점이 생기면 반영이 필요하기에 썩 좋은 아이디어는 아닙니다.&lt;br /&gt;때문에 쉬운 방법으로는 코드 테이블을 만들거나, 최신의 개발에서는 코드 정보를 MSA로 나누기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 &lt;b&gt;업무 성격상 변경이 매우 적은 경우에는 개발 편의성과 성능 향상을 위해 사용할 수 있기에&lt;/b&gt; 실무에서 해본 리팩토링 내용을 기록해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1637390010562&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java Enum 활용기 | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2527/&quot; data-og-url=&quot;https://techblog.woowahan.com/2527/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nLDGx/hyMoZ1qZ6V/DtohKRExC2NRrBrb6zoHgk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bRQWxd/hyMpbOi2xf/wVscPXhyBwAky6iQdelmsk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2527/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2527/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nLDGx/hyMoZ1qZ6V/DtohKRExC2NRrBrb6zoHgk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bRQWxd/hyMpbOi2xf/wVscPXhyBwAky6iQdelmsk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Enum 활용기 | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기존 코드의 문제&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1637391242484&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@AllArgsConstructor
public enum RowStatusCd {

	U(&quot;USE&quot;), // 사용
	N(&quot;UNUSE&quot;), // 미사용
	D(&quot;DELETE&quot;); // 삭제

	private String status;

	public static String USE = U.name();
	public static String UNUSE = N.name();
	public static String DEL = D.name();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 사용 여부와 soft delete를 표현하는 실무 코드의 일부를 각색하여 리팩토링 했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;U, N, D 은 DB 값이며, 그 의미를 enum으로 접근해야지 알 수 있습니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;향후 DB 값이 바뀐다면 enum 명 자체가 변경되어야 하므로 변경 여파가 큽니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;1번의 문제를 보완하기 위해 static 변수를 사용했습니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 이 것이 DB 값과 직결된다는 것을 코드에서는 알 수 없습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DB 값을 dataValue 로 명시한 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1637404928525&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@AllArgsConstructor
public enum RowStatusCode {

	USE(&quot;U&quot;), 
	UNUSE(&quot;N&quot;), 
	DELETE(&quot;D&quot;), 
	;

	private final String dataValue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB에서는 RowStatusCD로 축약되어 있었지만 애플리케이션으로 가져오면서는 축약하지 않고 RowStatusCode를 사용했습니다.&lt;/li&gt;
&lt;li&gt;enum의 네이밍을 명시적으로 바꿈으로써 주석을 달 필요가 없어지고 사용하는 곳에서 의도를 바로 알 수 있게 하였습니다.&lt;/li&gt;
&lt;li&gt;dataValue를 사용함으로써 static 변수를 사용하지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum을 사용하는 곳에서 코드의 길이는 큰 차이가 없지만, DB 값이라는 명시성을 보여줄 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1637397481598&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AS-IS
SYS_USER.ROW_STS_CD.in(RowStatusCode.USE, RowStatusCode.UNUSE),

// TO-BE
SYS_USER.ROW_STS_CD.in(USE.getDataValue(), UNUSE.getDataValue()),&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;좀 더 복잡한 실무 예시&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1637405228564&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@AllArgsConstructor
public enum CreditCard {

	KB(&quot;02&quot;, &quot;국민&quot;, &quot;3455&quot;),
	HANA(&quot;11&quot;, &quot;하나&quot;, &quot;3600&quot;),
	NH(&quot;08&quot;, &quot;농협&quot;, &quot;3700&quot;),
	;

	private final String dataValue; // DB 에 저장된 값
	private final String name;  // 카드명
	private final String bin;   // 카드빈 (카드사별로 할당되는 카드의 시작 값)

	public static CreditCard findByValue(final String value) {
		return Arrays.stream(CreditCard.values())
				.filter(e -&amp;gt; e.dataValue.equals(value))
				.findFirst()
				.orElseThrow(() -&amp;gt; new IllegalStateException(&quot;유효하지 않은 카드 정보 입니다.&quot;));
	}

	public static CreditCard findByCardNo(final String cardNo) {
		final String cardBin = cardNo.substring(0, 4);
		return Arrays.stream(CreditCard.values())
				.filter(e -&amp;gt; e.dataValue.equals(cardBin))
				.findFirst()
				.orElseThrow(() -&amp;gt; new IllegalStateException(&quot;유효하지 않은 카드 정보 입니다.&quot;));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 코드보다 좀 더 복잡한 실무에서의 제가 생각하는 best practice를 준비해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 enum 이름은 쉽게 이해할 수 있는 업무 내용을 사용합니다.&lt;/li&gt;
&lt;li&gt;각 enum에 매칭 되는 DB 값은 dataValue로 네이밍 합니다. (value로 직역하기에는 모호함이 있었습니다.)&lt;/li&gt;
&lt;li&gt;필요에 따라 Arrays.stream 대신 EnumMap을 구성합니다.&lt;/li&gt;
&lt;li&gt;실제 코드에서는 주석 내용을 팀 컨벤션으로 공유한 후 모두 삭제합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링 전의 코드와 같은 형태를 취하려면, enum 이름에 dataValue가 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법적으로 불가능하거니와 enum 이름이 '02' 면 업무 로직을 파악하는데 직관적이지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 클린 코드와 컨벤션을 준수하지만 기존 코드와의 일관성을 위해서는 일부 트레이드오프 해야 되는 부분도 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 위의 코드를 실무에 적용하는 것은 조금 더 고민해보겠지만, &lt;b&gt;일반적인 enum과 DB값으로서의 enum을 구분해서 사용해야 된다&lt;/b&gt;는 것에 대한 고민을 이 글을 통해 말하고 싶습니다.&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>Enum</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/31</guid>
      <comments>https://hyune-c.tistory.com/31#entry31comment</comments>
      <pubDate>Sat, 20 Nov 2021 19:57:15 +0900</pubDate>
    </item>
    <item>
      <title>좋은 개발 문화란? - (2) 준비</title>
      <link>https://hyune-c.tistory.com/27</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;소프트웨어 장인정신은 소프트웨어 개발자가 스스로가 선택한 커리어에 책임감을 가지고, 지속적으로 새로운 도구와 기술을 익히며 발전하겠다는 마음가짐이다. 소프트웨어 장인정신은 책임감, 프로페셔널리즘, 실용주의 그리고 소프트웨어 개발자로서의 자부심을 의미한다.&lt;br /&gt;&lt;br /&gt;즉 소프트웨어 장인 정신은 소프트웨어 개발의 프로페셔널리즘에 대한 것이다.&lt;br /&gt;&lt;br /&gt;- 소프트웨어 장인. 3장 소프트웨어 장인정신&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 실패와 개발 문화의 중요성에 대해 이야기했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;지금 제 환경에서 시도할 수 있는 좋은 개발 문화의 준비를 기록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 92px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 9.76748%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;업무&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.7674%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.8604%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;기간 산정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.093%; text-align: center;&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 9.76748%; height: 18px; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 19.7674%; height: 18px; text-align: center;&quot;&gt;외부 솔루션 도입&lt;/td&gt;
&lt;td style=&quot;width: 21.8604%; height: 18px; text-align: center;&quot;&gt;연구가 필요하기에 단순 표기가 어려움&lt;/td&gt;
&lt;td style=&quot;width: 17.093%; text-align: center;&quot;&gt;모니터링 솔루션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 9.76748%; height: 18px; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 19.7674%; height: 18px; text-align: center;&quot;&gt;단순 화면 기능 개발&lt;/td&gt;
&lt;td style=&quot;width: 21.8604%; height: 18px; text-align: center;&quot;&gt;단순 표기 가능&lt;/td&gt;
&lt;td style=&quot;width: 17.093%; text-align: center;&quot;&gt;API response 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 9.76748%; height: 18px; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 19.7674%; height: 18px; text-align: center;&quot;&gt;업무 이해도가 필요한 개발&lt;/td&gt;
&lt;td style=&quot;width: 21.8604%; height: 18px; text-align: center;&quot;&gt;리팩터링 범위에 따라 다름&lt;br /&gt;(테스트, 구조 수정 등)&lt;/td&gt;
&lt;td style=&quot;width: 17.093%; text-align: center;&quot;&gt;상품 구매 후처리 로직 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 9.76748%; text-align: center; height: 18px;&quot;&gt;D&lt;/td&gt;
&lt;td style=&quot;width: 19.7674%; text-align: center; height: 18px;&quot;&gt;기 개발 건의 QA 처리&lt;/td&gt;
&lt;td style=&quot;width: 21.8604%; text-align: center; height: 18px;&quot;&gt;단순 표기 가능&lt;/td&gt;
&lt;td style=&quot;width: 17.093%; text-align: center;&quot;&gt;B, C 의 QA 오류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.76748%; text-align: center;&quot;&gt;E&lt;/td&gt;
&lt;td style=&quot;width: 19.7674%; text-align: center;&quot;&gt;기능 변화에 영향이 없는 개선&lt;br /&gt;(보통 개발팀 한정)&lt;/td&gt;
&lt;td style=&quot;width: 21.8604%; text-align: center;&quot;&gt;범위에 따라 다름&lt;/td&gt;
&lt;td style=&quot;width: 17.093%; text-align: center;&quot;&gt;형상 관리 도입,&lt;br /&gt;테스트 케이스 작성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;위의 제 루틴 업무를 정리한 것입니다.&lt;br /&gt;저는 기획의 요청에 따라&amp;nbsp;'A~C'의 업무를 병행하면서 'D'의 업무도 처리하고 있습니다.&lt;br /&gt;&lt;br /&gt;이 중에서 주로 문제가 되는 것은 'C' 겠죠.&amp;nbsp;&lt;br /&gt;과연 어느 정도까지 테스트를 작성해야 되고 구조를 수정해야 'D'를 원활히 처리할 수 있을까요?&lt;br /&gt;어쩌면 'D'가 발생하지 않는다고 생각하고 개발해야 될까요?&lt;br /&gt;&lt;br /&gt;'E'는 어떨까요?&amp;nbsp;&lt;br /&gt;가시화하기 힘든 개선이기 때문에 어려운 설득을 준비해야 되는데 '설득을 위한 준비'를 하는 시간을 어떻게 할애해야 할까요?&lt;br /&gt;&lt;br /&gt;일반적으로는 이슈가 있어도 제대로 관리되지 않다가 터진 후에야 숙련된 개발자들이 일정 기간을 공들여 개선안을 적용합니다.&lt;br /&gt;이후에도 이슈를 미리 감지하고 개선하는 것이 좋겠지만, 보통은 또 다음 이슈가 터질 때쯤에야 하게 됩니다.&lt;br /&gt;루틴 업무를 하면서 가시화되지 않은 이슈 개선을 준비하고 설득하는 일은 보통의 모티베이션으로는 매우 힘든 일이기 때문입니다.&lt;br /&gt;혹자는 '조금 불편하지만 잘되고 있는데 뭐 어때요?' 또는 '지금 문제가 되는 것이 아니니 당신의 개인 시간을 할애해서라도 하시면 됩니다.'라고 할 수 있습니다. 그리고 보통은 루틴 업무로 돌아가죠.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 이전 글에서 정답에 가까운 개발 문화를 가져왔는데도 실패했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 이유 중 하나를 아래와 같이 생각했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;당면한 회사의 비즈니스 목표는 상용화된 프로덕트였고 그렇기 위해서는 빠른 개발이 우선이었는데, 이런 상황에서 단기적으로 제 개선안은 걸리적거릴 뿐이었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 좋은 품질의 소프트웨어 개발은 시간이 오래 걸릴까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'소프트웨어 장인' 은 그렇지 않다고 말합니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;작은 코드 조각에서는 XP 실행 관례를 마스터한 소프트웨어 장인과 일반 개발자 간의 업무 능력 차이가 별로 없을 수 있다. 하지만 다루어야 할 문제가 복잡해지면 차이가 어마어마하게 벌어진다. 애플리케이션의 크기가 크면 XP 실행 관례를 몸에 익힌 소프트웨어 장인이 XP 실행 관례를 전혀 수용하지 않은 보통의 개발자보다 훨씬 더 빨리 작업을 완료한다.&lt;br /&gt;&lt;br /&gt;- 소프트웨어 장인. 15장 실용주의 장인정신 - 좋은 품질은 비싸고 시간이 오래 걸릴까?&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제는 개선안 그 자체가 아니라 팀의 러닝 커브를 높이기 위한 접근 방법이었다는 것을 깨달았습니다.&lt;br /&gt;그렇다면 팀의 러닝 커브를 높이기 위한 접근 방법에는 무엇이 있을까요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 모든 개발자가 소프트웨어 장인정신을 가지고 일하며, 주변으로는 선을 행하고 있다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개개인의 표현 방식은 다릅니다. 또한 회사와 개인의 발전은 일치하지 않기에 개인 시간을 할애하는 것을 거부할 수도 있고, 사람이기에 슬럼프에 빠질 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 개인에게서 독립적인 문화로서 &lt;u&gt;&lt;b&gt;자주적, 심리적 안정감, 지속성이라는&lt;/b&gt;&lt;/u&gt; 키워드를 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWkx6b/btrhfyU1Bzd/Pusg8usBW3Fvx3aMWAxhB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWkx6b/btrhfyU1Bzd/Pusg8usBW3Fvx3aMWAxhB1/img.png&quot; data-alt=&quot;'왓챠' 의 짬 데이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWkx6b/btrhfyU1Bzd/Pusg8usBW3Fvx3aMWAxhB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWkx6b%2FbtrhfyU1Bzd%2FPusg8usBW3Fvx3aMWAxhB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;647&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'왓챠' 의 짬 데이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'소프트웨어 장인: 13. 배움의 문화'에서는 '회사에서의 펫 프로젝트 시간을 허용하기'를 말하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통의 회사에서는 허용되지 않는 일이지만 이미 '왓챠'라는 회사에서는 '짬 데이'라는 문화가 운영되고 있는 것을 알았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #000000;&quot;&gt;저는 이 것이 건전한 개발 문화를 성장시킬 수 있는 라이프 사이클의 시작이라고 생각합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;많은 회사에서는 개발 문화를 조직할 때 단발성의 의견을 수집받지만 별 소득이 없는 경우가 대부분입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;평소에 생각하는 불만이나 개선 사항은 많겠지만 막상 써내라고 하면 추상적이고, 정리도 안되고 무엇보다도 귀찮기 때문이죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반면에&amp;nbsp; 주기적으로 시간을 할애해준다면, 루틴 업무와 별개로 생각하고 준비할 수 있기 때문에 본 업무를 먼저 해야 된다는 강박이나 죄책감에서 벗어나 심리적 안정감을 가지고 더 좋은 방법을 귀찮지 않게 준비할 수 있을 것입니다. 예를 들자면 아래와 같은 것 들입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저번 주에 개발한 업무를 페어 프로그래밍으로 리팩터링&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 케이스 정비&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모니터링 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;새로운 프레임워크, DB 연구&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쩌면 리프레쉬?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 문화의 반복은 생산성의 향상만을 목표하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오히려 기반이 될 수 있는 구성원 간의 신뢰와 공감, 그리고 여러&amp;nbsp;기술의 러닝 커브를 완만하게 올려 생산성을 향상하고 비즈니스의 성공을 도울 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 회사의 입장에서도 최소한의 준비는 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주기적인 회고와 세미나를 통해 구성원 모두의 수준을 높일 수 있도록 하고, 무엇보다도 실패의 책임을 개인에게 묻지 않는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다행히도 지금 다니는 회사에서는 2주 단위의 회고와 팀장님 재량 하에 리팩터링과 기술 연구를 지원받고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사실 이 정도의 문화도 훌륭하다고 생각하지만, 제도화된 것과 재량 하의 것은 심리적 안정감과 지속성에서 큰 차이가 있다고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'소프트웨어 장인'을 읽고&amp;nbsp;이 글을 쓰면서 느낀 건데 이 모든 것이 결국 '애자일 프로세스'로 귀결된다는 것을 느꼈습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1년 전까지는 그냥 새로운 패러다임이라고 생각했지만, 실무를 겪으면 겪을수록 그 필요성을 체감하고 대단하다고 느끼고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 글은 여기에서 끝나지만 다음에는 '좋은 개발 문화란? - (3) 정착'으로 글을 작성해보고 싶습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1633927960411&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;WATCHA WEB 팀의 2020년 회고&quot; data-og-description=&quot;지난 1년간 무엇이 바뀌었을까?&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/watcha/watcha-web-%ED%8C%80%EC%9D%98-2020%EB%85%84-%ED%9A%8C%EA%B3%A0-c26e411843ba&quot; data-og-url=&quot;https://medium.com/watcha/watcha-web-%ED%8C%80%EC%9D%98-2020%EB%85%84-%ED%9A%8C%EA%B3%A0-c26e411843ba&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bl7AO5/hyLUmipEYZ/97zkmUmlDf4e9zI0d3tJlk/img.png?width=1200&amp;amp;height=410&amp;amp;face=0_0_1200_410,https://scrap.kakaocdn.net/dn/hV5xG/hyLUitvuDo/nflIBijgYss0xJX9LS3f2K/img.png?width=2500&amp;amp;height=855&amp;amp;face=0_0_2500_855,https://scrap.kakaocdn.net/dn/bnzs7I/hyLUqZpttm/LHPnF1jb3Ol4PBu4p3UeKK/img.png?width=60&amp;amp;height=47&amp;amp;face=0_0_60_47&quot;&gt;&lt;a href=&quot;https://medium.com/watcha/watcha-web-%ED%8C%80%EC%9D%98-2020%EB%85%84-%ED%9A%8C%EA%B3%A0-c26e411843ba&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/watcha/watcha-web-%ED%8C%80%EC%9D%98-2020%EB%85%84-%ED%9A%8C%EA%B3%A0-c26e411843ba&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bl7AO5/hyLUmipEYZ/97zkmUmlDf4e9zI0d3tJlk/img.png?width=1200&amp;amp;height=410&amp;amp;face=0_0_1200_410,https://scrap.kakaocdn.net/dn/hV5xG/hyLUitvuDo/nflIBijgYss0xJX9LS3f2K/img.png?width=2500&amp;amp;height=855&amp;amp;face=0_0_2500_855,https://scrap.kakaocdn.net/dn/bnzs7I/hyLUqZpttm/LHPnF1jb3Ol4PBu4p3UeKK/img.png?width=60&amp;amp;height=47&amp;amp;face=0_0_60_47');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;WATCHA WEB 팀의 2020년 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지난 1년간 무엇이 바뀌었을까?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1633927965555&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;왓챠 개발자 이스터에그의 비법은? 짬데이?!&quot; data-og-description=&quot;[기업분석보고서] [인터뷰] &amp;quot;공부하고 공유하는 짬데이, 소통의 스쿼드&amp;quot;&quot; data-og-host=&quot;www.jobplanet.co.kr&quot; data-og-source-url=&quot;https://www.jobplanet.co.kr/contents/news-1493&quot; data-og-url=&quot;https://www.jobplanet.co.kr/contents/news-1493&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rTzNh/hyLUq6bpWs/kKeeAAkS5xWHmGG4JPxwck/img.jpg?width=750&amp;amp;height=422&amp;amp;face=227_46_511_189&quot;&gt;&lt;a href=&quot;https://www.jobplanet.co.kr/contents/news-1493&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jobplanet.co.kr/contents/news-1493&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rTzNh/hyLUq6bpWs/kKeeAAkS5xWHmGG4JPxwck/img.jpg?width=750&amp;amp;height=422&amp;amp;face=227_46_511_189');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;왓챠 개발자 이스터에그의 비법은? 짬데이?!&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[기업분석보고서] [인터뷰] &quot;공부하고 공유하는 짬데이, 소통의 스쿼드&quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jobplanet.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>잡담 &amp;amp; ETC</category>
      <category>개발 문화</category>
      <category>소프트웨어 장인</category>
      <category>왓챠</category>
      <category>짬 데이</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/27</guid>
      <comments>https://hyune-c.tistory.com/27#entry27comment</comments>
      <pubDate>Mon, 15 Nov 2021 15:43:46 +0900</pubDate>
    </item>
    <item>
      <title>좋은 개발 문화란? - (1) 실패</title>
      <link>https://hyune-c.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;과거의 애플리케이션은 &lt;s&gt;아마도&lt;/s&gt; 충분한 계획을 기반으로 개발되었으며, 변경점도 &lt;s&gt;비교적 &lt;/s&gt;많지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 대부분 SI 식 개발로 비즈니스가 분리되었기에, 지속성보다는 요구 사항 충족과 납기일이 우선 되었었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때에는 단어조차 생소한 개발 문화는 중요하지 않았으며, 위에서 내려온 계획에 맞는 개발을 하나라도 더 하는 게 미덕이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현대의 애플리케이션은 긴 생명 주기와 모호해진 개발과 운영의 경계로 개발보다는 운영하면서 더 많은 이슈를 만나게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 자연스럽게 개발의 지속성에 대해 생각하게 되고 그것들을 아우르는 것으로 개발 문화가 나오게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 개발이 비즈니스의 성공에 직결적인 요소가 되었기에&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt; 개발 문화의 중요성이 업계 전반에 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt; 퍼져 있고 채용에도 중요한 화두가 되곤 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 개발 문화에 대한 제 생각과 경험을 기록합니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;아래에서 말하는 비즈니스는 업무 영역만이 아닌,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;새로운 일 -&amp;gt; 기획 -&amp;gt; 개발 -&amp;gt; 시장 검증 -&amp;gt; 운영까지의 일련의 프로세스를 말합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 비교적 늦게 개발을 시작했지만 그 덕분에 최신의 개발론을 배울 수 있었고,  그것들을 회사에 적용하고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원활한 비즈니스를 위한 전반적인 프로세스 작성부터 PR리뷰, 레이어 관리, 메타 단어, 배포 전략 등 코드 레벨까지를 문서화하여 팀에 공유하고 도입하려고 노력했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 제가 작성한 문서들은 잘 관리되지 못했고, &lt;br /&gt;개선안들은 대부분 공감을 얻지 못했습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;무엇이 문제였을까 생각해봤습니다.&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;무엇을 위한 개선인지를 잃어버렸습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발과 비즈니스가 직결되어 있다고 말해놓고, 비즈니스를 고려하지 않은 개선을 하려고 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 다니던 회사는 아직 상용화된 프로덕트를 개발하지 못했고, 총원 10~15명, 백엔드 개발자 2~3인으로 구성원 대부분이 2년 차 이하의 스타트업이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당면한 회사의 비즈니스 목표는 상용화된 프로덕트였고 그렇기 위해서는 빠른 개발이 우선이었는데, 단기적인 상황에서 제 개선안은 걸리적거릴 뿐이었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;더 큰 문제는 경험 부족이었습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 '이미 검증된 방법을 왜 수용하려고 하지 않지?'라는 생각이 들었지만, 조금만 운영해봐도 허점이 드러났고 심지어 만든 저 조차도 누락하고 실수했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상태로 팀을 설득하려고 했으니 제대로 적용했더라도 제가 먼저 떨어져 나갔을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 제가 더 경험이 풍부했다면 팀원 모두의 경험치와 의견을 수용해서 좀 더 완화된 개선안을 제안했겠지만 부족했습니다.&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결국 제가 작성한 개선안들은 대부분 폐기됐고 처음으로 돌아갔습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보니 제가 했던 방법은 레이싱 경기 중에 엔진을 교체하는 것과 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋은 엔진을 사용하는 것이 장기적으로는 좋지만, 그것을 레이싱 경기 중간에 해서는 안됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소한 경기가 끝난 후, 좀 더 많은 레퍼런스로 팀을 충분히 설득해서 교체했어야 했죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 문제를 찾는 과정에서 저의 부족함을 깨닫고, 제가 찾은 개선안들이 부채로서 해결되어야 한다는 것은 팀에 공유할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 개선안을 다시 준비할 때는 조금 더 나은 공감을 얻을 수 있겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;무엇보다도 제 마음의 안정을 찾을 수 있었습니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런 시도를 하지 않았다면 일을 하면서도 한편으로는 '왜 이런 낙후된 방법으로 일하는 거지? 왜 개선하려 하지 않는 거지?'라는 불만이 가득했을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 시도해봤기에 간과한 점과 부족한 점을 알 수 있었고, 지금 내가 해야 할 일에 대해 완전히 공감하고 몰입할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 개발 문화의 좋은 적용 사례는 이미 회사 소개나 블로그, 책 등에 정답에 가까운 내용들은 많이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 앞선 실패에서 일반적인 정답보다는 '지금 내가 속한 조직에 적합한지? 구성원들의 합의를 이끌어 낼 수 있을지?'가 더 중요하다는 것을 경험했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서는 지금을 분석해야 되고 경험과 신뢰가 쌓이는 시간이 해결해줄 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 지금까지의 경험을 돌이켜 볼 때 그렇지 않았습니다.&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무 경험이 쌓이면 당연히 개선에 대한 아이디어가 떠오르지만, 의도적으로 그것을 구체화시키고 설득하는 일은 완전히 다른 영역입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 회사의 개발자로 일하는 이상 다양한 요구 사항을 빠르게 개발해야 되는 강박과, 리팩터링에 대한 죄책감이 조금씩은 있습니다. 그리고 그 중간 어디쯤을 찾는 것이 개발자의 역량 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이렇게 보면 개발자가 알아서 그 중간 어디쯤을 잘 찾고 잘 조율하면 될 것 같습니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만..&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 쓰게 된 이유는 회사에 있던 '소프트웨어 장인'을&amp;nbsp;읽으면서 '14장. 기술적 변화의 실행'에서 영감을 얻어서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 아는 내용이고, 너무나도 당연한 내용이지만 정작 실무에서는 제대로 활용하지 못했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글은 개발 문화를 '준비' 하는 것에 대해 기록해보겠습니다.&lt;/p&gt;</description>
      <category>잡담 &amp;amp; ETC</category>
      <category>개발 문화</category>
      <category>소프트웨어 장인</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/26</guid>
      <comments>https://hyune-c.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 15 Nov 2021 15:43:30 +0900</pubDate>
    </item>
    <item>
      <title>업무에 적절한 pagination 선택하기</title>
      <link>https://hyune-c.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;pagination은 백엔드 구현에서 가장 많이 고려해야 되는 기술 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 알려진 pagination 기술에는 page, slice 가 있고 특수한 경우에 사용되는 noOffset과 Covering Index 기술이 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 실무에서 만난 특이한 pagination 사례에 대해 각색하여 기록해봅니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;업무 요건&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXr3lO/btrjJz4gTh4/ZuaySFXLFcY1fFRBuNZWmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXr3lO/btrjJz4gTh4/ZuaySFXLFcY1fFRBuNZWmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXr3lO/btrjJz4gTh4/ZuaySFXLFcY1fFRBuNZWmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXr3lO%2FbtrjJz4gTh4%2FZuaySFXLFcY1fFRBuNZWmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;833&quot; height=&quot;537&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전체고 조회가 필요합니다.&lt;/li&gt;
&lt;li&gt;상품군이 신설되었습니다.&lt;/li&gt;
&lt;li&gt;기존에는 회사와 상품이 존재했으며 M:N 관계입니다.&lt;/li&gt;
&lt;li&gt;상품군과 상품은 1:N 관계입니다.&lt;/li&gt;
&lt;li&gt;상품군과 회사의 관계는 M:N 관계입니다.&lt;/li&gt;
&lt;li&gt;서로 다른 재고처에 있는 재고를 합쳐야 합니다.&lt;/li&gt;
&lt;li&gt;각 상품군별, 회사별로 소계를 보여주어야 합니다.&lt;/li&gt;
&lt;li&gt;페이지당 300개 안팎은 허용 범위입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술적 배경&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 도메인별 조인이 많이 일어나 성능 이슈가 있습니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상품군과 상품, 회사는 서로 다른 테이블에서 관리되고 있습니다.&lt;/li&gt;
&lt;li&gt;재고의 총수량은 결과 모든 재고처의 합으로 실시간 계산됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;레거시 로직의 개선은 힘듭니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체고 조회는 신설된 요건이지만 기존에는 비슷한 로직을 '한방 쿼리'라는 거대한 SQL와 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;slice 방식으로 처리하고 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;slice 방식은 cursor와 size를 통해 다음 데이터를 가져오는데, 소계가 생기면서 그 처리가 힘들어졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;업무 요건의 엑셀을 예로 들어&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;size = 5 일 때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 페이지(1 ~ 5): A 마켓의 식용유 상품군이 소계까지 정상적으로 출력됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;1 페이지(6 ~ 10): B 마켓의 식용유 상품군은 소계까지 보이지만 A 마켓의 커피 상품군은 첫 줄만 보이고 잘려버립니다.&lt;/li&gt;
&lt;li&gt;2 페이지(11 ~ 15): A 마켓의 커피 상품군이 나오지만 limit 가 소계까지 가지 못합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트의 관점에서는 스크롤하면 다음 페이지가 나오기에 큰 문제가 없어 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 백엔드의 관점에서는 전체 데이터를 생성한 후에야 cursor와 limit를 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 종류 수를 통해 rowCount를 계산하여한다고 해도 테이블에 존재하지 않는 소계를 염두에 두고 로직을 구현해야 되기에 복잡도가 상승합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 상품과 회사가 늘어나고, 특히 재고 처가 늘어날수록 쿼리 성능이 저하될 것임이 확실해 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 조금 다른 접근법을 생각해봤습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술적 배경 확인&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특정 회사와 특정 상품군에 M:N으로 존재하는 상품은 50개 이하입니다.&lt;/li&gt;
&lt;li&gt;상품군 - goods, 회사 - company, 상품 - product&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 화면에서 호출할 API를 설계해봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lQGDC/btrjRfYcgKU/f159mV5jomEtxO7Rkj0480/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lQGDC/btrjRfYcgKU/f159mV5jomEtxO7Rkj0480/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lQGDC/btrjRfYcgKU/f159mV5jomEtxO7Rkj0480/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlQGDC%2FbtrjRfYcgKU%2Ff159mV5jomEtxO7Rkj0480%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;836&quot; height=&quot;538&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1635939710786&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// A. 전체 회사 목록
GET /companies

// B. 전체 상품군 목록
GET /goods

// C. 대상 회사들의 모든 상품 개수 (총 row 수)
GET /products/count?companyId=10,20

// D. 특정 회사 &amp;amp; 특정 상품군에 해당하는 상품 목록
GET /products?goodsId=10&amp;amp;companyId=10
GET /products?goodsId=10&amp;amp;companyId=20
GET /products?goodsId=20&amp;amp;companyId=10
GET /products?goodsId=20&amp;amp;companyId=20

// E. 특정 회사 &amp;amp; 특정 상품군에 해당하는 상품 소계
GET /products/sum?goodsId=10&amp;amp;companyId=20

// F. 대상 회사들의 모든 상품 총계
GET /products/sum?companyId=10,20

// G. 앞의 API 를 조합하여 모든 자료를 한번에 반환
GET /products/total?companyId=10,20&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;해당 페이지에 접근하면 A, B API를 통해 전체 회사와 상품군 목록을 가져오고 List로 관리합니다.&lt;/li&gt;
&lt;li&gt;C API 를 통해 전체 개수를 보여줄 수 있습니다.&lt;/li&gt;
&lt;li&gt;프론트에서는 그리드 화면의 최하단에 위치할 때마다 1번에서 생성한 List를 통해 D, E API를 동시에 호출합니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 회사와 특정 상품군에 속하는 상품은 50개 미만이고, 페이지 단위는 300 임으로 6쌍씩 호출해도 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;D API 호출은 매번 같은 row 수가 반환되지는 않기에 페이지 단위는 유동적일 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;1번의 모든 List 소비가 끝나면 F API를 통해 총계를 가져옵니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;또는 최초에 호출해 항상 보여주는 방법도 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 한방 쿼리에 비해 API 가 많아졌기에 프론트의 로직이 복잡해지긴 했지만, 로직의 분리로 각각의 부담이 줄고 부분적인 리팩터링이 가능한 구조입니다. 무엇보다도 사용자의 입장에서는 이전과 차이가 없거나, 좀 더 빠른 속도를 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기능이 부분적으로 구현되었기에 사용자에 맞춘 커스텀이 쉬워집니다. (ex. 소계 노출 여부)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하지만 여전히 문제는 남아 있습니다.&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상품의 상태를 실시간으로 계산하기에 스크롤 중 상품에 변화가 있으면 총계와 맞지 않을 위험이 있습니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업무 성격상 전체고 조회를 할 때 상품에 변화가 없을 것으로 기대됩니다.&lt;/li&gt;
&lt;li&gt;업무 성격상 최종본은 엑셀이나 출력자료를 사용할 텐데 이때는 속도가 느리더라도 G API를 사용할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 로직을 유지하면서 성능을 가져갈 수 있는 방법도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;repository 레이어의 결과를 캐싱합니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REPEATABLE READ에서 착안한 방법으로 해당 페이지를 이탈할 때 캐시를 지워주는 로직이 필요합니다.&lt;/li&gt;
&lt;li&gt;로직이 분리되지 않았으므로 유지보수의 어려움은 존재합니다.&lt;/li&gt;
&lt;li&gt;JPA를 사용하지는 않음으로 영속성 콘텍스트가 아닌 수동 캐싱을 구현해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무적으로 아직 결론을 내지 못한 주제이지만 전체 List를 반환하는 것 밖에 몰랐던 처음에서 업무 요건에 맞는 pagination 기술을 선택하고, 이제는 커스텀한 pagination 기술을 고민해야 되는 시기가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 기술에는 끝이 없고 필요에 의한 공부를 하는 것이 재미있다고 느꼈습니다.&lt;/p&gt;</description>
      <category>Series/실전!</category>
      <category>Covering Index</category>
      <category>noOffset</category>
      <category>page</category>
      <category>pagination</category>
      <category>PAGING</category>
      <category>slice</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/30</guid>
      <comments>https://hyune-c.tistory.com/30#entry30comment</comments>
      <pubDate>Sun, 7 Nov 2021 03:31:17 +0900</pubDate>
    </item>
    <item>
      <title>Java 백엔드 개발 환경을 세팅해보자</title>
      <link>https://hyune-c.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;처음 개발을 시작할 때를 돌이켜보니 잘 모르기도 했고, 가격적인 부담이 있어 제대로 세팅하지 못해 버벅거렸던 기억이 있습니다.&lt;br /&gt;그러다 이번에 코드 스쿼드 TA를 하게 되면서 초보 개발자 분들에게 도움이 되고자 기록을 남겨봅니다.&lt;br /&gt;지금 제가 사용 중이거나 좋다고 생각하는 조합을 개인적인 관점에서 작성한 내용이니 참고해서 세팅하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 개발 장비&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MacBook&amp;nbsp;사용을 권장합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네카라쿠배의 백엔드 개발자를 목표로 하신다면 매우 높은 확률로 MacBook을 사용하실 것이고, IDE 사용법이나 애플 생태계의 앱을 사용하는 것에 있어 Window와는 조금 차이가 있습니다. 생산성 차이도 있고 특히 Shell은 지금부터 익숙해지는 것이 좋습니다.&lt;/li&gt;
&lt;li&gt;M1부터는 가격적인 부담도 내려갔고 학습을 위해서는 &lt;b&gt;MacBook Air&lt;/b&gt;면 충분합니다. 또는 가격대가 떨어진 구형 모델을 구매하고 취직 후 판매를 하셔도 됩니다. 저도 공부하면서 구매한 MacBook을 구매가와 거의 같은 가격에   했습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;별도의 키보드 사용을 권장합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바른 자세를 위해, 그리고 개인적으로는 인텔 MacBook을 사용하면서 저온 화상을 입은 &lt;span&gt;경험이 있어&lt;/span&gt; 별도의 키보드를 사용했습니다.&lt;/li&gt;
&lt;li&gt;저는 무소음, 무선 키보드를 선호하기에 선택지가 많지 않은데요.&lt;br /&gt;지금 사용하는 키보드는 &lt;b&gt;텐키리스 애플 매직 키보드&lt;/b&gt;입니다. (13만 원)&lt;/li&gt;
&lt;li&gt;하지만 공부할 때는 가격적인 부담으로 &lt;b&gt;카넥스 키보드&lt;/b&gt;를 사용했습니다. (6만 원) &lt;br /&gt;외형과 타건감이 매직 키보드와 비슷해서 좋았지만, 결과적으로 내구성이 부족하고 블루투스 인식이 불안정해서 갈아탔습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트랙패드 사용을 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 마우스를 선호하시는 분도 있고, Pro라면 기본 트랙 패드가 충분히 커서 별도로 사용하지 않기도 합니다. (14만 원)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 개발 외 사용하는 앱&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;karabiner&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;윈도우에 익숙한 분들을 위해 키패드를 커스텀하게 변경해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구름 입력기&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MacBook의 기본 한글 자판은 쌍자음이 존재하는 자음을 두 번 누르면 쌍자음으로 자동 완성됩니다. 이것을 개선해줍니다.&lt;/li&gt;
&lt;li&gt;한영 전환에 구분 없이 &lt;b&gt;`&lt;/b&gt; 를 쉽게 사용해주는 기능을 옵션으로 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PhotoScape X&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 제공되는 사진 앱의 부족함을 보완합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Macs Fan control&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발열을 잡기 위한 쿨러 컨트롤을 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Notion, Obsidion&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마크다운 지원으로 자체 문서를 작성하는데 큰 도움이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Itsycal&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;달력 관련 어플 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kiwi for Gmail&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메일 관리를 도와주는 어플 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Screen Brush&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면 공유시 자신의 화면에 그림을 그릴 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Alfred (부분 유료)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전 사용하지 않지만 Mac의 전반적인 사용 생산성을 높여줍니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;magnet (유료)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면 분할을 더 편하게 하고 싶은 분에게 추천합니다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;전 spectacle 을 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 개발 관련 앱&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상 환경을 편하게 구성할 수 있도록 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WorkBench&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 계획을 시각화해서 보여주는 Visual Explain 기능 때문에라도 설치하시는 것을 추천합니다.&lt;/li&gt;
&lt;li&gt;저는 Datagrip 을 사용하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iTerm&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널을 커스터마이징하고 편하게 사용할 수 있게 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Postman&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 사용하기 쉬운 API 테스트 도구 중 하나입니다.&lt;/li&gt;
&lt;li&gt;저는 언젠가부터 Swagger 와 Intellij http 에 익숙해져서 사용하지 않고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Intellij&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Community 버전으로라도 사용을 권장합니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;plugin&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Material Theme UI, Atom Material Icons&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다소 칙칙한 intellij 환경을 비주얼 하게 바꿔줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CamelCase&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수명 작성 시 case 전환을 편하게 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitToolBox&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;blame을 보여줍니다. 그 외의 기능은 딱히 신경을 안 써봤네요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Rainbow Brackets&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;괄호에 색깔을 줘서 좀 더 보기 편하게 만들어 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SonarLint&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코딩의 best practice를 도와주는 플러그인으로 사용을 권장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Grep Console&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디버깅할 때 가독성을 높일 수 있습니다.&lt;/li&gt;
&lt;li&gt;저는 로깅시 '###' 를 키워드로 붙이고, 하이라이트 키워드로 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Preferences&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;keymap&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Split Right 단축키 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Build, Execution, Deployment &amp;gt; Build Tools &amp;gt; Gradle&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Build and run using, Run tests using을 Gradle에서 Intellij IDEA로 변경합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Editor &amp;gt; Code Style&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GoogleStyle 또는 naver-coding-convention Scheme 사용을 추천합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Editor &amp;gt; Code Style &amp;gt; Java &amp;gt; Code Generation&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Final Modifier 사용을 권장합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Tip</category>
      <category>개발환경</category>
      <category>백엔드</category>
      <category>초기세팅</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/29</guid>
      <comments>https://hyune-c.tistory.com/29#entry29comment</comments>
      <pubDate>Sat, 30 Oct 2021 04:11:47 +0900</pubDate>
    </item>
    <item>
      <title>String 을 잘 써보자</title>
      <link>https://hyune-c.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;String 은 Java에서 가장 오래되고 많이 다루는 자료형 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만큼 다루는 방법도 여러 가지인데요. 실무를 하면서 헷갈리기 Best Practice를 기록해&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt; 봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;단 String의 반복 연산은 일반적이지 않기에 다른 개선에 비해 드라마틱하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;그리고 실무에서는 팀 컨벤션으로서의 코드 일관성이 우선이기에 무조건적인 적용은 권장하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;각 항목별 제목으로 Best Practice가 기록되어 있습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;그래프의 수치는 높을수록 좋은 성능을 의미합니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. String 은 Immutable 합니다.&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워낙에 많은 블로그에 잘 소개되어 있기에 이어질 내용의 배경으로서 기록은 하지만 내용은 생략합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String과 Mutable, Immutable에 대해서 헷갈린다면 검색해보시길 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 숫자 자료형의 String 변환은 '+' 연산자를 사용합니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOcyYH/btrhy1hI1WH/Qm3cmYq5xcVbPl3slKuBv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOcyYH/btrhy1hI1WH/Qm3cmYq5xcVbPl3slKuBv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOcyYH/btrhy1hI1WH/Qm3cmYq5xcVbPl3slKuBv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOcyYH%2Fbtrhy1hI1WH%2FQm3cmYq5xcVbPl3slKuBv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;755&quot; height=&quot;393&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 올드하거나 C++에 익숙한 개발자는 String.format()을 즐겨 쓰는데 &lt;s&gt;그게 나예요.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String.format()가 제일 낮은 성능을 가지고 있고, 그 외에는 비슷한 성능을 가지고 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 코드의 간결성을 위해 '+' 연산자를 사용을 권장합니다.&lt;/p&gt;
&lt;figure id=&quot;og_1634182917559&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;How to convert int to String in Java &amp;ndash; the fastest way&quot; data-og-description=&quot;What is the fastest way to convert an int into a string in Java? We compare four methods using benchmark tests, running on Java 7 to 14.&quot; data-og-host=&quot;www.happycoders.eu&quot; data-og-source-url=&quot;https://www.happycoders.eu/java/how-to-convert-int-to-string-fastest/&quot; data-og-url=&quot;https://www.happycoders.eu/java/how-to-convert-int-to-string-fastest/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cEB6kA/hyLXFWeuUo/LHlBWpLZrtEPUK9TEq8GQ1/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/doVYzf/hyLXvziAK2/wAWNKgEII96HA0quMXKjM1/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/AFWmb/hyLXwLJ4WZ/A46TJIEwlhmtrbvrcPDXxk/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450&quot;&gt;&lt;a href=&quot;https://www.happycoders.eu/java/how-to-convert-int-to-string-fastest/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.happycoders.eu/java/how-to-convert-int-to-string-fastest/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cEB6kA/hyLXFWeuUo/LHlBWpLZrtEPUK9TEq8GQ1/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/doVYzf/hyLXvziAK2/wAWNKgEII96HA0quMXKjM1/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/AFWmb/hyLXwLJ4WZ/A46TJIEwlhmtrbvrcPDXxk/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to convert int to String in Java &amp;ndash; the fastest way&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What is the fastest way to convert an int into a string in Java? We compare four methods using benchmark tests, running on Java 7 to 14.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.happycoders.eu&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. String 결합은 '+' 연산자를 사용합니다.&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String 결합은 흔하게 반복적인 작업으로 보통 4가지 방법을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;+ 연산자&lt;/li&gt;
&lt;li&gt;String.format()&lt;/li&gt;
&lt;li&gt;StringBuilber, StringBuffer&lt;/li&gt;
&lt;li&gt;드물게 Stream &amp;amp; String.join()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 성능을 비교해봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GBFqS/btrhtUjyXDy/I7YOkiIwJdFGf7NeSd32XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GBFqS/btrhtUjyXDy/I7YOkiIwJdFGf7NeSd32XK/img.png&quot; data-alt=&quot;두 개의 문자열&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GBFqS/btrhtUjyXDy/I7YOkiIwJdFGf7NeSd32XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGBFqS%2FbtrhtUjyXDy%2FI7YOkiIwJdFGf7NeSd32XK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;482&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;두 개의 문자열&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l3zSn/btrhqLUh1zT/1AdEFjSkP51iroGvM95Hz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l3zSn/btrhqLUh1zT/1AdEFjSkP51iroGvM95Hz0/img.png&quot; data-alt=&quot;10개의 개별 문자열을 연결할 때의 성능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l3zSn/btrhqLUh1zT/1AdEFjSkP51iroGvM95Hz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl3zSn%2FbtrhqLUh1zT%2F1AdEFjSkP51iroGvM95Hz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;482&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;10개의 개별 문자열을 연결할 때의 성능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 StringBuilder의 성능이 높지만, 그 차이가 적고 코드의 간결성을 고려할 때 '+' 연산자 사용을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NbDqQ/btrhzrmWi5W/KxX7kU6y2QjDVWMXKxmor1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NbDqQ/btrhzrmWi5W/KxX7kU6y2QjDVWMXKxmor1/img.png&quot; data-alt=&quot;100개의 문자열&amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NbDqQ/btrhzrmWi5W/KxX7kU6y2QjDVWMXKxmor1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNbDqQ%2FbtrhzrmWi5W%2FKxX7kU6y2QjDVWMXKxmor1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;482&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;100개의 문자열&amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외적으로 대량의 문자열을 결합하는 경우는 '+' 연산자의 성능이 현저히 줄어듬으로 StringBuilder를 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 대량의 문자열을 결합하는 대부분의 경우는 Stream을 사용할 것임으로 Stream 사용을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 올드하거나 C++에 익숙한 개발자는 String.format()을 즐겨 쓰거나 String 은 Immutable 함으로 '+' 연산자가 느리다고 말할 수 있습니다. &lt;s&gt;이것도 나&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;과거에는 맞았지만 최신의 개발을 하고 있다면 다릅니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'+' 연산자는 컴파일 시점에 Java 컴파일러에 따라 최적화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 1.5는 StringBuilder로 최적화되며, Java 9는 아예 바이트 코드 단위의 최적화가 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘의 개발 환경은 대부분 Java 8 이상임으로 '+' 연산자의 사용을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세한 변천사는 Java 버전별로 'The Java Language Specification'의 'String Concatenation Operator +'를 찾아볼 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1634055772890&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java Compiler Optimization for String Concatenation&quot; data-og-description=&quot;String concatenation was a costly affair in the early Sun Java versions(till JDK1.4 to be precise). Even though later JDK&amp;rsquo;s brought the&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/javarevisited/java-compiler-optimization-for-string-concatenation-7f5237e5e6ed#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY0MTk2YWVlMTE5ZmUyMTU5M2Q0OGJmY2ZiNWJmMDAxNzdkZDRhNGQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MzQwNTUzOTEsImF1ZCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExMjg1MDUzNDcwMjYxMzY5NTExMyIsImVtYWlsIjoiY2hvaTg2MDhAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsIm5hbWUiOiJCeXVuZyBIeXVuIENob2kiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUFUWEFKeDRGUURiRGJlbTRsbkdCZnh5WUxRRXhGUW9tS2l2MU1MRGZuMDg9czk2LWMiLCJnaXZlbl9uYW1lIjoiQnl1bmcgSHl1biIsImZhbWlseV9uYW1lIjoiQ2hvaSIsImlhdCI6MTYzNDA1NTY5MSwiZXhwIjoxNjM0MDU5MjkxLCJqdGkiOiIyMmUyMTBiNDc5NjU5MWQzZDE1MGY1N2QyNzg5ODgyMWJkZDhlMGJlIn0.kHDDRAanCd3mHD4jCWPjxfGPeMWLKbYjWWeFSHhTkH0_Be7sz-Ca5RIK2ZsEiNHw6glKhIfZvUtarIkp2cAJJrYYUBgfugwq7mhOtn8bsJS_fJYCK3u_DeAi-gKARpupaRWCw9BCyog-5EA-T15oh3YjNu5QTSTjYN-6QMzZiAEBF0C14oK5BTc0forNrQabKZcFzKeZ-OcXbuMzAoZEBekrPT_KzGCCKpLAzd5diiUW18AFcy6gou7Nq6Zx_EiT3zJYABZyx_WeqDHUKZJBQEN6pzWOosqiSrcZFqwjMomEuaKm_uu32mqM4n3Hi5cHThqcTCaUM8EGTvdDJkA0Nw&quot; data-og-url=&quot;https://medium.com/javarevisited/java-compiler-optimization-for-string-concatenation-7f5237e5e6ed&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDVSPT/hyLVVSyert/LlZtg4NEuJSJuLYDpDGgV1/img.png?width=906&amp;amp;height=629&amp;amp;face=0_0_906_629,https://scrap.kakaocdn.net/dn/VtSWA/hyLV423cRU/plAk9PWsMGvqF8DLlsMeZ1/img.png?width=60&amp;amp;height=41&amp;amp;face=0_0_60_41&quot;&gt;&lt;a href=&quot;https://medium.com/javarevisited/java-compiler-optimization-for-string-concatenation-7f5237e5e6ed#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY0MTk2YWVlMTE5ZmUyMTU5M2Q0OGJmY2ZiNWJmMDAxNzdkZDRhNGQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MzQwNTUzOTEsImF1ZCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExMjg1MDUzNDcwMjYxMzY5NTExMyIsImVtYWlsIjoiY2hvaTg2MDhAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsIm5hbWUiOiJCeXVuZyBIeXVuIENob2kiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUFUWEFKeDRGUURiRGJlbTRsbkdCZnh5WUxRRXhGUW9tS2l2MU1MRGZuMDg9czk2LWMiLCJnaXZlbl9uYW1lIjoiQnl1bmcgSHl1biIsImZhbWlseV9uYW1lIjoiQ2hvaSIsImlhdCI6MTYzNDA1NTY5MSwiZXhwIjoxNjM0MDU5MjkxLCJqdGkiOiIyMmUyMTBiNDc5NjU5MWQzZDE1MGY1N2QyNzg5ODgyMWJkZDhlMGJlIn0.kHDDRAanCd3mHD4jCWPjxfGPeMWLKbYjWWeFSHhTkH0_Be7sz-Ca5RIK2ZsEiNHw6glKhIfZvUtarIkp2cAJJrYYUBgfugwq7mhOtn8bsJS_fJYCK3u_DeAi-gKARpupaRWCw9BCyog-5EA-T15oh3YjNu5QTSTjYN-6QMzZiAEBF0C14oK5BTc0forNrQabKZcFzKeZ-OcXbuMzAoZEBekrPT_KzGCCKpLAzd5diiUW18AFcy6gou7Nq6Zx_EiT3zJYABZyx_WeqDHUKZJBQEN6pzWOosqiSrcZFqwjMomEuaKm_uu32mqM4n3Hi5cHThqcTCaUM8EGTvdDJkA0Nw&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/javarevisited/java-compiler-optimization-for-string-concatenation-7f5237e5e6ed#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY0MTk2YWVlMTE5ZmUyMTU5M2Q0OGJmY2ZiNWJmMDAxNzdkZDRhNGQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MzQwNTUzOTEsImF1ZCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExMjg1MDUzNDcwMjYxMzY5NTExMyIsImVtYWlsIjoiY2hvaTg2MDhAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsIm5hbWUiOiJCeXVuZyBIeXVuIENob2kiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUFUWEFKeDRGUURiRGJlbTRsbkdCZnh5WUxRRXhGUW9tS2l2MU1MRGZuMDg9czk2LWMiLCJnaXZlbl9uYW1lIjoiQnl1bmcgSHl1biIsImZhbWlseV9uYW1lIjoiQ2hvaSIsImlhdCI6MTYzNDA1NTY5MSwiZXhwIjoxNjM0MDU5MjkxLCJqdGkiOiIyMmUyMTBiNDc5NjU5MWQzZDE1MGY1N2QyNzg5ODgyMWJkZDhlMGJlIn0.kHDDRAanCd3mHD4jCWPjxfGPeMWLKbYjWWeFSHhTkH0_Be7sz-Ca5RIK2ZsEiNHw6glKhIfZvUtarIkp2cAJJrYYUBgfugwq7mhOtn8bsJS_fJYCK3u_DeAi-gKARpupaRWCw9BCyog-5EA-T15oh3YjNu5QTSTjYN-6QMzZiAEBF0C14oK5BTc0forNrQabKZcFzKeZ-OcXbuMzAoZEBekrPT_KzGCCKpLAzd5diiUW18AFcy6gou7Nq6Zx_EiT3zJYABZyx_WeqDHUKZJBQEN6pzWOosqiSrcZFqwjMomEuaKm_uu32mqM4n3Hi5cHThqcTCaUM8EGTvdDJkA0Nw&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDVSPT/hyLVVSyert/LlZtg4NEuJSJuLYDpDGgV1/img.png?width=906&amp;amp;height=629&amp;amp;face=0_0_906_629,https://scrap.kakaocdn.net/dn/VtSWA/hyLV423cRU/plAk9PWsMGvqF8DLlsMeZ1/img.png?width=60&amp;amp;height=41&amp;amp;face=0_0_60_41');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Compiler Optimization for String Concatenation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;String concatenation was a costly affair in the early Sun Java versions(till JDK1.4 to be precise). Even though later JDK&amp;rsquo;s brought the&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. 문자열 분리는 split()을 사용합니다.&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 분리는 보통 2가지 방법을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StringTokenizer&lt;/li&gt;
&lt;li&gt;split&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;StringTokenizer는 legacy 클래스입니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;legacy는 더 이상 사용할 수 없는 deprecated와는 다르게 사용은 할 수 있지만, 권장되지 않습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;StringTokenizer&amp;nbsp;is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the&amp;nbsp;split&amp;nbsp;method of&amp;nbsp;String&amp;nbsp;or the java.util.regex package instead.&lt;br /&gt;&lt;br /&gt;- StringTokenizer javadoc&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;StringTokenizer는 정규식을 사용할 수 없습니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StringTokenizer 가 성능이 조금 더 좋지만 Legacy 된 이유라고 생각합니다.&lt;br /&gt;반면 split은 Java 버전이 올라갈수록 개선되고 있으며, 정규식을 미리 컴파일하는 방법으로도 보완할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 극도의 성능이 필요한 예외적인 경우가 아니라면 split 사용을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1678342107749&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;String Performance Hints | Baeldung&quot; data-og-description=&quot;Learn about the performance aspects of the Java String API&quot; data-og-host=&quot;www.baeldung.com&quot; data-og-source-url=&quot;https://www.baeldung.com/java-string-performance&quot; data-og-url=&quot;https://www.baeldung.com/java-string-performance&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bw0Wng/hyRSOTp5Q2/T63STyd8W7RJXdtRynvBvk/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498,https://scrap.kakaocdn.net/dn/tabww/hyRSTAqKOu/qoLgNIsKszI66ErgLfbPk1/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/java-string-performance&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.baeldung.com/java-string-performance&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bw0Wng/hyRSOTp5Q2/T63STyd8W7RJXdtRynvBvk/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498,https://scrap.kakaocdn.net/dn/tabww/hyRSTAqKOu/qoLgNIsKszI66ErgLfbPk1/img.jpg?width=952&amp;amp;height=498&amp;amp;face=0_0_952_498');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;String Performance Hints | Baeldung&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn about the performance aspects of the Java String API&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.baeldung.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study</category>
      <category>Java</category>
      <category>string</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/28</guid>
      <comments>https://hyune-c.tistory.com/28#entry28comment</comments>
      <pubDate>Sat, 16 Oct 2021 15:08:16 +0900</pubDate>
    </item>
    <item>
      <title>@Component vs @Configuration</title>
      <link>https://hyune-c.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발하면서 Component와 Configuration 어노테이션은 많이 만났었지만 이 글을 작성하기 전에는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-style=&quot;style3&quot;&gt;@Configuration 은 설정 클래스에 사용하는 어노테이션&lt;/li&gt;
&lt;li data-ke-style=&quot;style3&quot;&gt;@Component는 Bean 클래스에 사용하는 어노테이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정도로만 알고 있었습니다. 하지만 공부하다 보니 모호하게 알고 있던 부분을 좀 더 알게 되었고, 이 내용을 기록해 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 비교&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@Component&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@ComponentScan 을 통해 검색됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SpringBootApplication에 래핑 되어 있기에 일반적으로는 신경 쓰지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;용도에 맞춘 스테레오 타입의 어노테이션을 가지고 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Controller @Service @Repository @Configuration&lt;/li&gt;
&lt;li&gt;가능하면 스테레오 타입을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Bean 과는 차이점이 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;둘다 Bean을 생성한다는 것에서는 같지만 @Component는 class level에서, @Bean 은 method level에서의 어노테이션입니다.&lt;/li&gt;
&lt;li&gt;@Component는 Bean의 클래스 소스가 편집 가능할 때, @Bean 은 인스턴스 화할 코드가 수동으로 작성됨으로 외부 라이브러리나 써드파티 클래스도 Bean으로 등록이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@Configuration&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@ComponentScan 을 통해 검색됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Configuration 도 @Component의 한 종류이기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;각종 스프링 인터페이스의 구현에 함께 활용됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지만 보면 잘 모르고 혼용해서 써도 애플리케이션이 &lt;b&gt;돌아는&lt;/b&gt; 갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Bean 을 생성하는 방법에서는 차이가 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;# Bean을 생성하는 방법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. @Component&lt;/h4&gt;
&lt;pre id=&quot;code_1632496695282&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class SimpleComponent {

}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. @Bean (in @Configuration)&lt;/h4&gt;
&lt;pre id=&quot;code_1632496685472&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class SimpleConfigBean {

	@Bean
	public SimpleClass1 simpleComponent1() {
		return new SimpleClass1();
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. @Bean&amp;nbsp;(in&amp;nbsp;@Component):&amp;nbsp;Lite&amp;nbsp;Mode&lt;/h4&gt;
&lt;pre id=&quot;code_1632495303973&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class SimpleComponentBean {

	@Bean
	public SimpleClass2 simpleComponent2() {
		return new SimpleClass2();
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tscJf/btrfXzlQF2O/b1Ia4IY95tzYX7avE8c6cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tscJf/btrfXzlQF2O/b1Ia4IY95tzYX7avE8c6cK/img.png&quot; data-alt=&quot;proxy 된 simpleConfigBean 과 인스턴스화된 simpleComponentBean 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tscJf/btrfXzlQF2O/b1Ia4IY95tzYX7avE8c6cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtscJf%2FbtrfXzlQF2O%2Fb1Ia4IY95tzYX7avE8c6cK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;50&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;proxy 된 simpleConfigBean 과 인스턴스화된 simpleComponentBean 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서는 기본적으로 Bean을 등록할 때 proxy를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Lite Mode 는 실제 인스턴스를 생성하기에 proxy 가 필요한 Spring 기능들을 사용할 수 없습니다. (AOP 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 차이를 알고 필요한 곳에서만 특정 목적으로 사용해야 합니다.&lt;/p&gt;</description>
      <category>Study</category>
      <category>Component</category>
      <category>Configuration</category>
      <category>spring</category>
      <category>spring boot</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/25</guid>
      <comments>https://hyune-c.tistory.com/25#entry25comment</comments>
      <pubDate>Sat, 25 Sep 2021 00:14:38 +0900</pubDate>
    </item>
    <item>
      <title>Elastic Beanstalk 구성 삽질기 - 글쑤시개</title>
      <link>https://hyune-c.tistory.com/24</link>
      <description>&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 초기에는 AWS 구성을 하는 것도 일이기에, 지금까지는 프론트 개발자가 백엔드 소스를 받아서 local에서 서버를 띄우는 방식으로 진행했습니다. 물론 매번 DB를 올리지 않아도 되도록 local h2 구성을 해주고, 실행 편의 방법과 README를 제공해줬지만 프론트 개발자 입장에서는 불편한 게 현실이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 슬슬 목표한 앱의 한바퀴가 성공했기에 AWS 구성을 해보았고 그 기록을 남겨봅니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 주의!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 사이드 프로젝트 구성을 위한 삽질기이기에 접근 제어 및 보안이 실무 수준에 미치지 못합니다. &lt;br /&gt;실무에서 고려해야될 사항이나 도움이 될만한 내용은 코멘트했지만, &lt;br /&gt;개인적인 경험에서 나온 기록이기에 선별적으로 이해해주시면 감사하겠습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;계획&quot; data-ke-size=&quot;size26&quot;&gt;# 계획&lt;/h2&gt;
&lt;h4 id=&quot;시작하기-전-배경-지식&quot; data-ke-size=&quot;size20&quot;&gt;작업 전 배경 지식&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실물 장비 기반의 인프라 구성/운영 경험 (IDC)&lt;/li&gt;
&lt;li&gt;이미 구성된 AWS 시스템의 운영 경험&lt;/li&gt;
&lt;li&gt;EC2 - RDS 의 단일 구성 및 배포 경험&lt;/li&gt;
&lt;li&gt;VPC - 보안그룹 - 라우팅 테이블 등 네트워크에 대한 포괄적인 이해&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;목표&quot; data-ke-size=&quot;size20&quot;&gt;목표&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Elastic Beanstalk 으로 EC2 - RDS 로 구성하자&lt;/li&gt;
&lt;li&gt;개발 편의성을 우선하며, 고가용성과 보안 등은 차후 과제로 남겨둡니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 알기 전에는 기초적인 파티션 나누기와 웹 서버 설정부터 심지어 자바 설치까지 직접 했었는데요. &lt;br /&gt;AWS에서는 이런 기본적인 것들은 디폴트 설정으로 제공하거나 GUI 환경에서 간단히 수정할 수 있게 도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부할 때는 EC2 - RDS를 각각 구성했었지만, 이번 기회에 Elastic Beanstalk을 설정해보려고 합니다.&lt;/p&gt;
&lt;h2 id=&quot;-elastic-beanstalk&quot; data-ke-size=&quot;size26&quot;&gt;# Elastic Beanstalk&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 진행해도 되지만, 저는 서버에 들어가는 것을 좋아하기에 서버에 들어갈 수 있는 '키 페어' 생성을 먼저 해보겠습니다.&lt;/p&gt;
&lt;h3 id=&quot;1-ec2-키-페어-만들기&quot; data-ke-size=&quot;size23&quot;&gt;1. EC2 키 페어 만들기&lt;/h3&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8GdR1/btrceb3aB5z/K9ugMDM7ltfOOVXE4oVRwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8GdR1/btrceb3aB5z/K9ugMDM7ltfOOVXE4oVRwK/img.png&quot; data-alt=&quot;모든 서비스 &amp;amp;gt; 컴퓨팅 &amp;amp;gt; EC2 &amp;amp;gt; &amp;amp;gt; 네트워크 및 보안 &amp;amp;gt; 키 페어&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8GdR1/btrceb3aB5z/K9ugMDM7ltfOOVXE4oVRwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8GdR1%2Fbtrceb3aB5z%2FK9ugMDM7ltfOOVXE4oVRwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1088&quot; height=&quot;668&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모든 서비스 &amp;gt; 컴퓨팅 &amp;gt; EC2 &amp;gt; &amp;gt; 네트워크 및 보안 &amp;gt; 키 페어&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xJQUf/btrb3mMhJ7R/mEYq0FKpGE71rqt7pA7sQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xJQUf/btrb3mMhJ7R/mEYq0FKpGE71rqt7pA7sQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xJQUf/btrb3mMhJ7R/mEYq0FKpGE71rqt7pA7sQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxJQUf%2Fbtrb3mMhJ7R%2FmEYq0FKpGE71rqt7pA7sQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;867&quot; height=&quot;170&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;&lt;/article&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 EC2 에 직접 연결하는 몇 가지 방법 중 'SSH 클라이언트'로 접근하는 방법을 선호합니다. &lt;br /&gt;익숙한 환경이기도 하고 제일 빠르게 작업할 수 있기 때문이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버로 직접 연결할 일이 없다면 사용하지 않아도 좋지만 사용을 적극 권장합니다.&lt;/p&gt;
&lt;h3 id=&quot;2-elastic-beanstalk-웹-앱-생성하기&quot; data-ke-size=&quot;size23&quot;&gt;2. Elastic Beanstalk 웹 앱 생성하기&lt;/h3&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VfDZV/btrb8hwAgQn/g6W7dGklQaSzdLPvbwMUr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VfDZV/btrb8hwAgQn/g6W7dGklQaSzdLPvbwMUr1/img.png&quot; data-alt=&quot;AWS 에 로그인하면 바로 첫 화면에 있는&amp;amp;amp;nbsp; 웹 앱 구축 &amp;amp;amp;nbsp;을 따라가서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VfDZV/btrb8hwAgQn/g6W7dGklQaSzdLPvbwMUr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVfDZV%2Fbtrb8hwAgQn%2Fg6W7dGklQaSzdLPvbwMUr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;745&quot; height=&quot;570&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS 에 로그인하면 바로 첫 화면에 있는&amp;amp;nbsp; 웹 앱 구축 &amp;amp;nbsp;을 따라가서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;1238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xvhnC/btrb8gEqwhN/uVEX5xm0kVFibhOFYoHcC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xvhnC/btrb8gEqwhN/uVEX5xm0kVFibhOFYoHcC1/img.png&quot; data-alt=&quot;기본적인 애플리케이션 정보를 입력하고 하단의&amp;amp;amp;nbsp; 추가 옵션 구성 &amp;amp;amp;nbsp;을 선택합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xvhnC/btrb8gEqwhN/uVEX5xm0kVFibhOFYoHcC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxvhnC%2Fbtrb8gEqwhN%2FuVEX5xm0kVFibhOFYoHcC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1230&quot; height=&quot;1238&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;1238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기본적인 애플리케이션 정보를 입력하고 하단의&amp;amp;nbsp; 추가 옵션 구성 &amp;amp;nbsp;을 선택합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qU8h3/btrb5owGLZJ/U47o1J8uCu4D7JXO1PL3Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qU8h3/btrb5owGLZJ/U47o1J8uCu4D7JXO1PL3Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qU8h3/btrb5owGLZJ/U47o1J8uCu4D7JXO1PL3Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqU8h3%2Fbtrb5owGLZJ%2FU47o1J8uCu4D7JXO1PL3Lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;812&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 옵션 구성에서 신경 써야 하는 것은 총 4개 입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;용량 - EC2&lt;/li&gt;
&lt;li&gt;보안 - 키 페어 설정 (중요!)&lt;/li&gt;
&lt;li&gt;알림 - 선택 사항으로 추후 Cloudwatch Alert 를 구성해도 됩니다.&lt;/li&gt;
&lt;li&gt;데이터베이스 - 데이터베이스는 분리해서 관리하는게 좋다고 생각합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oKb92/btrb5opW3LW/kzPYVoP7Udy8wchvxMH721/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oKb92/btrb5opW3LW/kzPYVoP7Udy8wchvxMH721/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oKb92/btrb5opW3LW/kzPYVoP7Udy8wchvxMH721/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoKb92%2Fbtrb5opW3LW%2FkzPYVoP7Udy8wchvxMH721%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;183&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lyYCk/btrb24LZoPa/8p6siKz7BoUe1FnGJ1IMF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lyYCk/btrb24LZoPa/8p6siKz7BoUe1FnGJ1IMF1/img.png&quot; data-alt=&quot;하나의 애플리케이션에는 복수개의 환경을 구성할 수 있습니다.&amp;amp;amp;nbsp;dev/prod 가 분리되어야 하는 시점에 추가 환경을 구성 예정입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lyYCk/btrb24LZoPa/8p6siKz7BoUe1FnGJ1IMF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlyYCk%2Fbtrb24LZoPa%2F8p6siKz7BoUe1FnGJ1IMF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;133&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;하나의 애플리케이션에는 복수개의 환경을 구성할 수 있습니다.&amp;amp;nbsp;dev/prod 가 분리되어야 하는 시점에 추가 환경을 구성 예정입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GpfcA/btrb83rgTIk/TkBN3e59N9lx5t1KedCLCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GpfcA/btrb83rgTIk/TkBN3e59N9lx5t1KedCLCk/img.png&quot; data-alt=&quot;Beanstalk 상태 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GpfcA/btrb83rgTIk/TkBN3e59N9lx5t1KedCLCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGpfcA%2Fbtrb83rgTIk%2FTkBN3e59N9lx5t1KedCLCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;566&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Beanstalk 상태 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8osOk/btrb9Qyus4r/yF7OBMwrDaYNkC66Cq21U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8osOk/btrb9Qyus4r/yF7OBMwrDaYNkC66Cq21U1/img.png&quot; data-alt=&quot;샘플 페이지 접속 성공!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8osOk/btrb9Qyus4r/yF7OBMwrDaYNkC66Cq21U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8osOk%2Fbtrb9Qyus4r%2FyF7OBMwrDaYNkC66Cq21U1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;504&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;샘플 페이지 접속 성공!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;h3 id=&quot;3-ec2-확인--접속&quot; data-ke-size=&quot;size23&quot;&gt;3. EC2 확인 &amp;amp; 접속&lt;/h3&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2021-08-14-15-57-30.png&quot; data-origin-width=&quot;2069&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1icyq/btrcb9dtB12/Oy1C89RUKpEwPWiZAvX9mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1icyq/btrcb9dtB12/Oy1C89RUKpEwPWiZAvX9mK/img.png&quot; data-alt=&quot;탄력적 IP 사용 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1icyq/btrcb9dtB12/Oy1C89RUKpEwPWiZAvX9mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1icyq%2Fbtrcb9dtB12%2FOy1C89RUKpEwPWiZAvX9mK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2069&quot; height=&quot;159&quot; data-filename=&quot;2021-08-14-15-57-30.png&quot; data-origin-width=&quot;2069&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;탄력적 IP 사용 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IKZ3t/btrb7uQiJah/3lPtKwYVvDdVKuvhkYhWl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IKZ3t/btrb7uQiJah/3lPtKwYVvDdVKuvhkYhWl1/img.png&quot; data-alt=&quot;모든 서비스 &amp;amp;gt; 컴퓨팅 &amp;amp;gt; EC2 &amp;amp;gt; 인스턴스 &amp;amp;gt; 연결&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IKZ3t/btrb7uQiJah/3lPtKwYVvDdVKuvhkYhWl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIKZ3t%2Fbtrb7uQiJah%2F3lPtKwYVvDdVKuvhkYhWl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;827&quot; height=&quot;649&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모든 서비스 &amp;gt; 컴퓨팅 &amp;gt; EC2 &amp;gt; 인스턴스 &amp;gt; 연결&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1628937384457&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; chmod 700 Gssg-prod.pem
&amp;gt; ls -lrt Gssg-prod.pem
-rwx------@ 1 choibyunghyeon  staff  1678  8 14 12:18 Gssg-prod.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pem의 권한은 700으로 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628937405584&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; ssh -i &quot;Gssg-prod.pem&quot; ec2-user@{'인스턴스 연결' 의 캡쳐 참조}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정한 키 페어가 있는 경로에서 ssh 명령을 통해 서버에 접속할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGRsm/btrb9HakJfa/kXXAEBKRAKsY45ldPINT4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGRsm/btrb9HakJfa/kXXAEBKRAKsY45ldPINT4K/img.png&quot; data-alt=&quot;서버 접속 성공!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGRsm/btrb9HakJfa/kXXAEBKRAKsY45ldPINT4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGRsm%2Fbtrb9HakJfa%2FkXXAEBKRAKsY45ldPINT4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;315&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버 접속 성공!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;-rds&quot; data-ke-size=&quot;size26&quot;&gt;# RDS&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무라면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마스터 사용자 이름과 port를 변경해주는 것이 좋습니다.&lt;/li&gt;
&lt;li&gt;private zone에 DB를 위치시키고 ec2를 통해서만 접근 가능하게 포트 포워딩할 것을 권장합니다.&lt;br /&gt;AWS DocumentDB에서는 Cloud9를 사용할 것을 권장하기도 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;1-데이터베이스-생성&quot; data-ke-size=&quot;size23&quot;&gt;1. 데이터베이스 생성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GOAWm/btrcecnutmv/kpIfi0BQGhsAFyBoRAdLkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GOAWm/btrcecnutmv/kpIfi0BQGhsAFyBoRAdLkK/img.png&quot; data-alt=&quot;모든 서비스 &amp;amp;gt; 데이터베이스 &amp;amp;gt; RDS &amp;amp;gt; 데이터베이스 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GOAWm/btrcecnutmv/kpIfi0BQGhsAFyBoRAdLkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGOAWm%2Fbtrcecnutmv%2FkpIfi0BQGhsAFyBoRAdLkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;1236&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;1236&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모든 서비스 &amp;gt; 데이터베이스 &amp;gt; RDS &amp;gt; 데이터베이스 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;2-추가-구성-설정&quot; data-ke-size=&quot;size23&quot;&gt;2. 추가 구성 설정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2021-08-14-15-39-15.png&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/batoIh/btrb81NMMRl/VykHnhpDVws2RcBDV6r8R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/batoIh/btrb81NMMRl/VykHnhpDVws2RcBDV6r8R1/img.png&quot; data-alt=&quot;추가 구성에서&amp;amp;amp;nbsp; '퍼블릭 엑세스 가능' 을 설정해줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/batoIh/btrb81NMMRl/VykHnhpDVws2RcBDV6r8R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbatoIh%2Fbtrb81NMMRl%2FVykHnhpDVws2RcBDV6r8R1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;663&quot; data-filename=&quot;2021-08-14-15-39-15.png&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;추가 구성에서&amp;amp;nbsp; '퍼블릭 엑세스 가능' 을 설정해줍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;3-보안-그룹-설정&quot; data-ke-size=&quot;size23&quot;&gt;3. 보안 그룹 설정&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FDsnu/btrb7aEHmxn/PltAFapKfNlzgv6sTZcZ60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FDsnu/btrb7aEHmxn/PltAFapKfNlzgv6sTZcZ60/img.png&quot; data-alt=&quot;ec2 에서의 접근 외에도 개발 편의상 내 집에서는 바로 접근할 수 있게 설정합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FDsnu/btrb7aEHmxn/PltAFapKfNlzgv6sTZcZ60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFDsnu%2Fbtrb7aEHmxn%2FPltAFapKfNlzgv6sTZcZ60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;533&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ec2 에서의 접근 외에도 개발 편의상 내 집에서는 바로 접근할 수 있게 설정합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;4-rds-확인--접속&quot; data-ke-size=&quot;size23&quot;&gt;4. RDS 확인 &amp;amp; 접속&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfowN1/btrb5nkfUrd/FMg6ztOS5ZabYAUNUy9XQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfowN1/btrb5nkfUrd/FMg6ztOS5ZabYAUNUy9XQ0/img.png&quot; data-alt=&quot;datagrip 등의 툴로 접속 여부를 확인할 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfowN1/btrb5nkfUrd/FMg6ztOS5ZabYAUNUy9XQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfowN1%2Fbtrb5nkfUrd%2FFMg6ztOS5ZabYAUNUy9XQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1167&quot; height=&quot;537&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;datagrip 등의 툴로 접속 여부를 확인할 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class=&quot;markdown-body entry-content&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-size: 1.62em; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;# 애플리케이션 배포&lt;/span&gt;&lt;/h2&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;h3 id=&quot;1-jaror-war-준비&quot; data-ke-size=&quot;size23&quot;&gt;1. jar(or war) 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Beanstalk 은 환경별로 외부 환경 변수를 편리하게 관리할 수 있습니다. &lt;br /&gt;저는 Spring Boot를 사용하기에 yml로 local/prod profile을 구분하고 외부 환경 변수를 주입받게 구성했습니다.&lt;/p&gt;
&lt;h3 id=&quot;2-모니터링&quot; data-ke-size=&quot;size23&quot;&gt;2. 모니터링&lt;/h3&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;&amp;gt; cd /var/log
&amp;gt; tail -f web.stdout.log&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS의 이벤트나 기본 모니터링이 존재하긴 하지만 콘솔이 더 익숙하기에 서버에서의 모니터링을 준비합니다.&lt;/p&gt;
&lt;h3 id=&quot;3-beanstalk-외부-환경-변수-설정&quot; data-ke-size=&quot;size23&quot;&gt;3. Beanstalk 외부 환경 변수 설정&lt;/h3&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkw3EV/btrb24LZBjs/0VCy6u7kYwEYirIe8n5LR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkw3EV/btrb24LZBjs/0VCy6u7kYwEYirIe8n5LR0/img.png&quot; data-alt=&quot;Elastic Beanstalk &amp;amp;gt; 환경 &amp;amp;gt; {환경 이름} &amp;amp;gt; 구성 &amp;amp;gt; 소프트웨어 수정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkw3EV/btrb24LZBjs/0VCy6u7kYwEYirIe8n5LR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkw3EV%2Fbtrb24LZBjs%2F0VCy6u7kYwEYirIe8n5LR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;412&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Elastic Beanstalk &amp;gt; 환경 &amp;gt; {환경 이름} &amp;gt; 구성 &amp;gt; 소프트웨어 수정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Beanstalk 환경별로 서로 다른 외부 환경 변수를 운영할 수 있습니다.&lt;br /&gt;위에서는 JDBC 연결을 외부 환경 변수로서 설정했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E9s9Q/btrb3ocgaaQ/Dm1Ll4OpR97f6kIgLUYS5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E9s9Q/btrb3ocgaaQ/Dm1Ll4OpR97f6kIgLUYS5K/img.png&quot; data-alt=&quot;서버 기동 성공! 'Started GssgBeApplication in 19.228 seconds'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E9s9Q/btrb3ocgaaQ/Dm1Ll4OpR97f6kIgLUYS5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE9s9Q%2Fbtrb3ocgaaQ%2FDm1Ll4OpR97f6kIgLUYS5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;731&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버 기동 성공! 'Started GssgBeApplication in 19.228 seconds'&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1093&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8QdT1/btrb7uis6BW/tkRTCBJQXlO03nVth17BVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8QdT1/btrb7uis6BW/tkRTCBJQXlO03nVth17BVK/img.png&quot; data-alt=&quot;Swagger 접속 성공!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8QdT1/btrb7uis6BW/tkRTCBJQXlO03nVth17BVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8QdT1%2Fbtrb7uis6BW%2FtkRTCBJQXlO03nVth17BVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1093&quot; height=&quot;864&quot; data-origin-width=&quot;1093&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Swagger 접속 성공!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;h2 id=&quot;-마무리&quot; data-ke-size=&quot;size26&quot;&gt;# 마무리&lt;/h2&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M1U2x/btrb6TJXgEj/vFPsQedsaPylBLFq4CzmBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M1U2x/btrb6TJXgEj/vFPsQedsaPylBLFq4CzmBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M1U2x/btrb6TJXgEj/vFPsQedsaPylBLFq4CzmBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM1U2x%2Fbtrb6TJXgEj%2FvFPsQedsaPylBLFq4CzmBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;90&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지는 공통된 서버가 없었으니 에러 대응하기가 참 힘들었는데 이제는 서버가 올라갔으니 편리하게 에러를 봐줄 수 있을 것 같습니다. AWS 덕분에 인프라 작업이 많이 편해지긴 했는데, 그래도 헷갈리는 건 어쩔 수 없네요. &lt;s&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;인프라도 디버그가 됐으면 좋겠다&lt;/span&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성도와 다음 계획을 마지막으로 기록하며 글을 마칩니다.&lt;/p&gt;
&lt;h3 id=&quot;구성도&quot; data-ke-size=&quot;size23&quot;&gt;구성도&lt;/h3&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHqZ3V/btrb9QebfoJ/C1mxbEXJgKQkqxk38KwcHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHqZ3V/btrb9QebfoJ/C1mxbEXJgKQkqxk38KwcHK/img.png&quot; data-alt=&quot;DB 는 private subnet 에 위치 시켜야겠지만 이번 버전에서는 보안 그룹으로만 제어 합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHqZ3V/btrb9QebfoJ/C1mxbEXJgKQkqxk38KwcHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHqZ3V%2Fbtrb9QebfoJ%2FC1mxbEXJgKQkqxk38KwcHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;441&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DB 는 private subnet 에 위치 시켜야겠지만 이번 버전에서는 보안 그룹으로만 제어 합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;&lt;/article&gt;
&lt;h3 id=&quot;다음-인프라-작업들&quot; data-ke-size=&quot;size23&quot;&gt;다음 인프라 작업들..&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CI/CD 구성 - git branch에 대응하는 CodePipeline
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재는 circleCI로 CI&amp;nbsp; 만 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;관제 구성 - slack webhook, Cloudwatch Alert&lt;/li&gt;
&lt;li&gt;접근 제어 - 지정된 자사 앱에서만 접근할 수 있도록 제어&lt;/li&gt;
&lt;li&gt;TimeZone 관리&lt;/li&gt;
&lt;li&gt;HTTPS 설정&lt;/li&gt;
&lt;li&gt;코드 품질 관리 - SonarQube 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재는 Codecov로 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB 형상 관리 - flyway&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Series/내가 해본</category>
      <category>AWS</category>
      <category>Beanstalk</category>
      <category>elastic beanstalk</category>
      <category>VPC</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/24</guid>
      <comments>https://hyune-c.tistory.com/24#entry24comment</comments>
      <pubDate>Sat, 14 Aug 2021 17:46:50 +0900</pubDate>
    </item>
    <item>
      <title>Slack Bot 으로 채널에 글쓰기</title>
      <link>https://hyune-c.tistory.com/22</link>
      <description>&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;옛날에 채널에 글을 작성하는 슬랙 연동을 했을 때는 webhook으로 했던 것 같은데, 다시 해보니 잘 안됐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;정확히는 local 에서는 됐지만 github에 올리면 webhook url 이 바뀌는 현상이 발생했습니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;이상해서 검색해보니 요즘은 slack API 를 연동하는 것이 최신이기도 하고 webhook 보다 다양하게 쓸 수 있을 것 같아서 연동해본 내용을 기록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/article&gt;
&lt;h2 id=&quot;사전-조사&quot; data-ke-size=&quot;size26&quot;&gt;사전 조사&lt;/h2&gt;
&lt;figure id=&quot;og_1639500498470&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Using Slack APIs&quot; data-og-description=&quot;Every Slack app has access to a bag of tricks&amp;mdash;a range of APIs that provide access to read, write, and update all kinds of data in Slack.&quot; data-og-host=&quot;api.slack.com&quot; data-og-source-url=&quot;https://api.slack.com/apis&quot; data-og-url=&quot;https://slack.com/apis&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/V3rOG/hyMH5eKI2O/GKc2Nyj31tD1onJUwXk011/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://api.slack.com/apis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.slack.com/apis&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/V3rOG/hyMH5eKI2O/GKc2Nyj31tD1onJUwXk011/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Using Slack APIs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Every Slack app has access to a bag of tricks&amp;mdash;a range of APIs that provide access to read, write, and update all kinds of data in Slack.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.slack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 slack API 사용을 위해서는 어떤 것을 해야 되는지 살펴봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biOysm/btra9XsoKpi/js4LRqwx4sCMTAIDjDYnm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biOysm/btra9XsoKpi/js4LRqwx4sCMTAIDjDYnm1/img.png&quot; data-alt=&quot;Post bodies 에서 확인 가능한 example&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biOysm/btra9XsoKpi/js4LRqwx4sCMTAIDjDYnm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiOysm%2Fbtra9XsoKpi%2Fjs4LRqwx4sCMTAIDjDYnm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1209&quot; height=&quot;192&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Post bodies 에서 확인 가능한 example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 example 에서 header의 Authorization에 token 이 들어가야 된다는 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tuoD7/btra9W72DlT/eqk43HZPKxlkikyRPoTts1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tuoD7/btra9W72DlT/eqk43HZPKxlkikyRPoTts1/img.png&quot; data-alt=&quot;사용 가능한 API들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tuoD7/btra9W72DlT/eqk43HZPKxlkikyRPoTts1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtuoD7%2Fbtra9W72DlT%2Feqk43HZPKxlkikyRPoTts1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;523&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사용 가능한 API들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkZdC7/btra34ltGu7/KKowVbhy2eRg7FNJRsAzWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkZdC7/btra34ltGu7/KKowVbhy2eRg7FNJRsAzWk/img.png&quot; data-alt=&quot;chat.postMessage: Sends a message to a channel.&amp;amp;amp;amp;nbsp; 가 제가 원하는 API라는 것을 찾을 수 있었습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkZdC7/btra34ltGu7/KKowVbhy2eRg7FNJRsAzWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkZdC7%2Fbtra34ltGu7%2FKKowVbhy2eRg7FNJRsAzWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;687&quot; height=&quot;513&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;chat.postMessage: Sends a message to a channel.&amp;amp;amp;nbsp; 가 제가 원하는 API라는 것을 찾을 수 있었습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;api-발송-준비&quot; data-ke-size=&quot;size26&quot;&gt;API 발송 준비&lt;/h2&gt;
&lt;h3 id=&quot;1-api-스펙-확인&quot; data-ke-size=&quot;size23&quot;&gt;1. API 스펙 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6S8Js/btrbbSK3liE/hJpg5g3DlHxCc5iqPfIja1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6S8Js/btrbbSK3liE/hJpg5g3DlHxCc5iqPfIja1/img.png&quot; data-alt=&quot;token과 함께 scope 도 필요한 것을 볼 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6S8Js/btrbbSK3liE/hJpg5g3DlHxCc5iqPfIja1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6S8Js%2FbtrbbSK3liE%2FhJpg5g3DlHxCc5iqPfIja1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;622&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;token과 함께 scope 도 필요한 것을 볼 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 기존에 네이버나 카카오를 연동시켜보신 분이라면 developer 등록하는 절차가 기억나실 겁니다.&lt;br /&gt;slack 도 비슷한 방식으로 내 앱을 등록하고 앱을 통해 토큰을 생성할 수 있습니다. &lt;br /&gt;그러면 bot token 을 만들기 위해 &lt;a href=&quot;https://api.slack.com/apps&quot;&gt;Your Apps&lt;/a&gt; 에 가서 app을 만들어봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/article&gt;
&lt;h3 id=&quot;2-내-app-생성&quot; data-ke-size=&quot;size23&quot;&gt;2. 내 app 생성&lt;/h3&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3ro2/btra10cxFVg/4sFf6L1qNnsvuXOKr7NFT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3ro2/btra10cxFVg/4sFf6L1qNnsvuXOKr7NFT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3ro2/btra10cxFVg/4sFf6L1qNnsvuXOKr7NFT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3ro2%2Fbtra10cxFVg%2F4sFf6L1qNnsvuXOKr7NFT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;975&quot; height=&quot;454&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWgKWl/btra6Ibrncy/4xKGB6UxKJdYvKPaRUHtV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWgKWl/btra6Ibrncy/4xKGB6UxKJdYvKPaRUHtV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWgKWl/btra6Ibrncy/4xKGB6UxKJdYvKPaRUHtV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWgKWl%2Fbtra6Ibrncy%2F4xKGB6UxKJdYvKPaRUHtV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;945&quot; height=&quot;735&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;735&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/corkw1/btraZLmsV5x/fSzGqnNaukza56yLtmtyK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/corkw1/btraZLmsV5x/fSzGqnNaukza56yLtmtyK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/corkw1/btraZLmsV5x/fSzGqnNaukza56yLtmtyK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcorkw1%2FbtraZLmsV5x%2FfSzGqnNaukza56yLtmtyK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;377&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 만드실 때 주의할 점을 2개입니다.&lt;br /&gt;Bots으로 선택하고, Permission에서 scope를 주는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/article&gt;
&lt;h3 id=&quot;3-워크스페이스--채널에-연결하기&quot; data-ke-size=&quot;size23&quot;&gt;3. 워크스페이스 &amp;amp; 채널에 연결하기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lnq3m/btra8zFjJqK/ZsFTihI4fz0j3IOKO7ExU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lnq3m/btra8zFjJqK/ZsFTihI4fz0j3IOKO7ExU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lnq3m/btra8zFjJqK/ZsFTihI4fz0j3IOKO7ExU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flnq3m%2Fbtra8zFjJqK%2FZsFTihI4fz0j3IOKO7ExU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;972&quot; height=&quot;222&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnH1Kv/btra9XFRx7V/J79OKRuqNs0V2QVSkrovpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnH1Kv/btra9XFRx7V/J79OKRuqNs0V2QVSkrovpK/img.png&quot; data-alt=&quot;Install to Workspace를 통해 원하는 워크스페이스에 연결&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnH1Kv/btra9XFRx7V/J79OKRuqNs0V2QVSkrovpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnH1Kv%2Fbtra9XFRx7V%2FJ79OKRuqNs0V2QVSkrovpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;466&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Install to Workspace를 통해 원하는 워크스페이스에 연결&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccTx7G/btrbh2MQ72Y/pz3Tj1DdPPZZja6BBDefE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccTx7G/btrbh2MQ72Y/pz3Tj1DdPPZZja6BBDefE0/img.png&quot; data-alt=&quot;xoxb로 시작하는 bot token 생성 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccTx7G/btrbh2MQ72Y/pz3Tj1DdPPZZja6BBDefE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccTx7G%2Fbtrbh2MQ72Y%2Fpz3Tj1DdPPZZja6BBDefE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;945&quot; height=&quot;231&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;xoxb로 시작하는 bot token 생성 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chF6X5/btraZMexzmp/BjKuwpcy8nM3sLVkifSq91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chF6X5/btraZMexzmp/BjKuwpcy8nM3sLVkifSq91/img.png&quot; data-alt=&quot;앱을 채널에 초대&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chF6X5/btraZMexzmp/BjKuwpcy8nM3sLVkifSq91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchF6X5%2FbtraZMexzmp%2FBjKuwpcy8nM3sLVkifSq91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;226&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;앱을 채널에 초대&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;4-발송&quot; data-ke-size=&quot;size23&quot;&gt;4. 발송&lt;/h3&gt;
&lt;pre id=&quot;code_1628004625668&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST https://slack.com/api/chat.postMessage
Content-Type: application/json
Authorization: Bearer xoxb-2198502468292-***

{
&quot;channel&quot;: &quot;C029T&quot;,
&quot;text&quot;: &quot;I hope the tour went well, Mr. Wonka.&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m85aG/btra9Wtrgk2/PzfBgqrdyPVbRAPrfLdjCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m85aG/btra9Wtrgk2/PzfBgqrdyPVbRAPrfLdjCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m85aG/btra9Wtrgk2/PzfBgqrdyPVbRAPrfLdjCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm85aG%2Fbtra9Wtrgk2%2FPzfBgqrdyPVbRAPrfLdjCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;369&quot; height=&quot;171&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;마무리&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 연동하고 나서 안 것이지만 기존의 webhook 방식에 문제가 있던 것이 아니었습니다.&lt;br /&gt;문제가 된 부분은 local에서 테스트한 후 webhook url을 public repository에 그대로 push 했고, 그것이 GitGuardian과 slack의 감지 시스템을 통해 차단이 된 것이었습니다. (기존에 했던 작업은 private repository 이어서 생각하지 못한 부분이었습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이슈는 AWS Elastic Beanstalk 에 환경 변수로 넣으면 해결되는 문제이기에 더 이상 개선하지는 않았습니다.&lt;br /&gt;마지막으로 연동한 코드 중 일부를 공유합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628041727641&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Service
public class PostMessageService {

    private final String slackBotToken;
    private final WebClient webClient;

    private static final String chatPostMessage = &quot;https://slack.com/api/chat.postMessage&quot;;

    public Mono&amp;lt;String&amp;gt; sendChatPostMessage(PostMessageDto postMessageDto) {
        return webClient.post()
            .uri(chatPostMessage)
            .headers(headers -&amp;gt; headers.add(AUTHORIZATION, &quot;Bearer &quot; + slackBotToken))
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(postMessageDto)
            .retrieve()
            .bodyToMono(String.class);
    }
}


@Test
public void send() {
    // given
    String webhookTestUrl = &quot;C029******&quot;;
    PostMessageDto postMessageDto = PostMessageDto.of(webhookTestUrl, &quot;Test Message&quot;);

    // when
    String block = postMessageService
    .sendChatPostMessage(postMessageDto)
    .block();

    // then
    assertThat(block).startsWith(&quot;{\&quot;ok\&quot;:true&quot;);
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Series/내가 해본</category>
      <category>bot</category>
      <category>Slack</category>
      <category>webhook</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/22</guid>
      <comments>https://hyune-c.tistory.com/22#entry22comment</comments>
      <pubDate>Wed, 4 Aug 2021 00:15:10 +0900</pubDate>
    </item>
    <item>
      <title>DynamicUpdate 활용기</title>
      <link>https://hyune-c.tistory.com/21</link>
      <description>&lt;figure id=&quot;og_1655949888615&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Hyune-c/blog-code: https://hyune-c.tistory.com/ 의 예제 code&quot; data-og-description=&quot;https://hyune-c.tistory.com/ 의 예제 code. Contribute to Hyune-c/blog-code development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Hyune-c/blog-code/tree/master/dynamic-update&quot; data-og-url=&quot;https://github.com/Hyune-c/blog-code&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ckTuiZ/hyORQGpAkg/ySIOeB1HCFcicrIPgnPHL1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/blog-code/tree/master/dynamic-update&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Hyune-c/blog-code/tree/master/dynamic-update&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ckTuiZ/hyORQGpAkg/ySIOeB1HCFcicrIPgnPHL1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Hyune-c/blog-code: https://hyune-c.tistory.com/ 의 예제 code&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://hyune-c.tistory.com/ 의 예제 code. Contribute to Hyune-c/blog-code development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;팀으로 진행하고 있는 사이드 프로젝트에서는 회원 관리를 자체적으로 구현했습니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;로그인도 별 문제없고 잘 되는 줄 알았는데, 어느 순간 로그인이 되지 않는 현상이 발생했습니다.&lt;br /&gt;&lt;/span&gt;다행히도 문제는 금방 해결했지만 생각할게 많아진 주제였기에 기록을 남겨봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 회원 구현&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Entity
public class Member {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;
 private String email;
 
 @Convert(converter = PasswordEncryptConverter.class)
 private String password;
 private String introduce;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호를 BCryptPasswordEncoder로 암호화한, 별로 특별할 게 없어 보이는 회원 구현입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
public void update_withoutDynamicUpdate() {
 // 1. member 생성
 String password = &quot;1234&quot;;
 Member member = new Member(&quot;choi8608@gmail.com&quot;, password, &quot;변경전 - withoutDynamicUpdate&quot;);
 memberRepository.save(member);
 long memberId = member.getId();
 
 // 2. 영속성 컨텍스트 초기화 후 member 조회
 em.clear();
 member = memberRepository.findById(memberId).get();
 
 // 3. 암호화된 비밀번호 확인
 System.out.println(&quot;### &quot; + member.getPassword());
 assertThat(passwordEncoder.matches(password, member.getPassword())).isTrue();
 
 // 4. '자기소개' 만 수정 (dirtyCheck)
 // 4-1. 모든 컬럼이 업데이트되면서 password 가 또 암호화됨
 memberService.updateIntroduce(member.getId(), &quot;변경후 - withoutDynamicUpdate&quot;);
 
 // 5. 영속성 컨텍스트 초기화 후 member 조회
 em.clear();
 member = memberRepository.findById(memberId).get();
 
 // 6. 비밀번호가 틀려짐
 System.out.println(&quot;### &quot; + member.getPassword());
 assertThat(passwordEncoder.matches(password, member.getPassword())).isFalse();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wtCI0/btrnWSzvfta/wHvrMUqAzhWENYsTjgthN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wtCI0/btrnWSzvfta/wHvrMUqAzhWENYsTjgthN0/img.png&quot; data-alt=&quot;1번에서 회원이 생성되고, 암호화된 비밀번호가 들어갔음을 확인할 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wtCI0/btrnWSzvfta/wHvrMUqAzhWENYsTjgthN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwtCI0%2FbtrnWSzvfta%2FwHvrMUqAzhWENYsTjgthN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;76&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1번에서 회원이 생성되고, 암호화된 비밀번호가 들어갔음을 확인할 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LhPce/btrn1hkInoo/Yn95XScq2SGGjl16sGwKT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LhPce/btrn1hkInoo/Yn95XScq2SGGjl16sGwKT1/img.png&quot; data-alt=&quot;4번에서는 자기소개만 수정했는데 비밀번호도 같이 update 됨을 볼 수 있습니다. 그리고 insert 의 비밀번호와 update 의 비밀번호 해시값이 서로 다릅니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LhPce/btrn1hkInoo/Yn95XScq2SGGjl16sGwKT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLhPce%2Fbtrn1hkInoo%2FYn95XScq2SGGjl16sGwKT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;73&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;4번에서는 자기소개만 수정했는데 비밀번호도 같이 update 됨을 볼 수 있습니다. 그리고 insert 의 비밀번호와 update 의 비밀번호 해시값이 서로 다릅니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;의도하지 않게 비밀번호가 update 된 것도 문제지만, 비밀번호가 달라진 게 더 큰 문제였습니다!&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA의 구현체인 hibernate는 애플리케이션이 처음 로드될 때 entity 들을 모두 스캔하여 update 쿼리를 캐시 해놓고 사용합니다. 그렇기에 dirtyCheck 된 entity가 통째로 update 된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 여기에 password가 있었고, convert 되어버렸기 때문이었습니다.&lt;br /&gt;즉 영속성 초기화 후 조회된 entity는 이미 암호화된 비밀번호를 가지고 있었고, 전체 update를 통해 암호화된 비밀번호가 또 한 번 암호화되어 저장된 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;3-테스트---dynamicupdate&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 테스트 -&amp;nbsp;&lt;/b&gt;&lt;b&gt;@DynamicUpdate&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 쉽습니다.&lt;br /&gt;entity에&amp;nbsp;@DynamicUpdate를 달아주면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@DynamicUpdate
@Entity
public class MemberWIthDynamicUpdate {
 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UQSZq/btrn1YEU0C5/WIRsmY9IJ0648yHlfGbIu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UQSZq/btrn1YEU0C5/WIRsmY9IJ0648yHlfGbIu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UQSZq/btrn1YEU0C5/WIRsmY9IJ0648yHlfGbIu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUQSZq%2Fbtrn1YEU0C5%2FWIRsmY9IJ0648yHlfGbIu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;74&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dstq1t/btrnX0YlA80/DMPZsRQyjQhGzwABkvLjN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dstq1t/btrnX0YlA80/DMPZsRQyjQhGzwABkvLjN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dstq1t/btrnX0YlA80/DMPZsRQyjQhGzwABkvLjN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdstq1t%2FbtrnX0YlA80%2FDMPZsRQyjQhGzwABkvLjN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;72&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까의 테스트와 다르게 의도한 자기소개 필드만 update 된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;4-언제-써야될까&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 언제 써야 될까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼핏 보기에는 영향받는 칼럼도 적고 의도하지 않은 변경도 일어나지 않아 더 좋을 것 같아 보이지만 기본 설정이 아닌 이유가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 것처럼 JPA의 구현체인 hibernate는 애플리케이션이 처음 로드될 때 entity 들을 모두 스캔하여 업데이트할 쿼리를 캐시 해놓고 사용합니다. 즉 &lt;b&gt;DynamicUpdate를 사용하면 캐시 된 쿼리가 아닌 동적 쿼리를 새로이 생성하고, 이 과정에서 오버헤드가 발생&lt;/b&gt;해 더 적은 필드만 수정됨에도 오히려 성능이 떨어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 entity의 필드 개수가 많고 그 중 소수의 필드만 업데이트 되는 일이 잦을 때는 유용합니다.&lt;br /&gt;(물론 이런 경우는 entity 가 적절하게 분리된 것이 맞느냐를 먼저 의심해야 됩니다.)&lt;br /&gt;마지막으로 격리 수준을 낙관적 락으로 @Version 어노테이션을 같이 사용할 경우 사용하는 것이 좋습니다. &lt;br /&gt;(주로 @Version 필드만 업데이트하기 때문에)&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>dynamicupdate</category>
      <category>JPA</category>
      <category>transaction</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/21</guid>
      <comments>https://hyune-c.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 27 Jul 2021 18:10:52 +0900</pubDate>
    </item>
    <item>
      <title>표준 예외 처리에서 로깅까지 (2)</title>
      <link>https://hyune-c.tistory.com/20</link>
      <description>&lt;h2 id=&quot;step-3-비동기-로직&quot; data-ke-size=&quot;size26&quot;&gt;Step 3. 비동기 로직&lt;/h2&gt;
&lt;pre class=&quot;less&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 비동기 사용을 위한 설정을 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RestController
public class AsyncTestController {

    private final AsyncService asyncService;

    @GetMapping(&quot;/api/test/async&quot;)
    public void asyncTest() throws InterruptedException {
        for (int i = 1; i &amp;lt;= 100; i++) {
            MDC.put(&quot;tid&quot;, String.valueOf(i));
            asyncService.async(i);
        }

        Thread.sleep(3000L);
    }
}

@Service
public class AsyncService {

    @Async
    public void async(Integer tid) throws InterruptedException {
        Thread.sleep((long) (Math.random() * 1000 + 1));
        log.info(&quot;### mdc.tid={}, inputValue={}&quot;, MDC.get(&quot;tid&quot;), tid);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 controller와 service를 만듭니다.&lt;br /&gt;&lt;br /&gt;controller에서는 모든 비동기 처리가 끝나기 전 서버 종료를 방지하기 위해 3 초의 sleep을 적용합니다.&lt;br /&gt;service에서는 무작위 처리를 위해 1초 이하의 sleep을 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;여기까지가 전 글의 내용입니다.&lt;br /&gt;이제 문제를 해결하기 전에 원인을 알아보고 진행하겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Spring에서는 webRequest 당 하나의 쓰레드를 할당해서 작업을 처리합니다.&lt;br /&gt;이 정보는 ThreadLocal에 저장되며, MDC는 MDCAdapter의 구현에 따라 이 정보를 가져와 처리하기에&lt;br /&gt;정보를 공유할 수 있습니다.&lt;br /&gt;&lt;br /&gt;하지만 비동기 처리를 위해서 사용하는 TaskExecutor는 새로운 쓰레드를 만드는데,&lt;br /&gt;이때 &lt;b&gt;기존 쓰레드의 context 가 공유되지 않습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;step-3-1-생성되는-쓰레드에-context-공유하기&quot; data-ke-size=&quot;size26&quot;&gt;Step 3-1. 생성되는 쓰레드에 context 공유하기&lt;/h2&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;public class MDCTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        Map&amp;lt;String, String&amp;gt; copyOfContextMap = MDC.getCopyOfContextMap();

        return () -&amp;gt; {
            if (copyOfContextMap != null) {
                MDC.setContextMap(copyOfContextMap);
            }

            runnable.run();
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 생성되는 쓰레드에 context를 복사하기 위한 TaskDecorator를 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class AsyncConfiguration implements AsyncConfigurer {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

        taskExecutor.setTaskDecorator(new MDCTaskDecorator());
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(100);

        return taskExecutor;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현한 TaskDecorator를 AsyncConfiguration에 추가합니다.&lt;br /&gt;이때 비동기 처리를 위한 적절히 배압 설정을 합니다.&lt;br /&gt;본 예제에서는 100 건이 동시에 들어옴으로 QueueCapacity를 100으로 설정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bobFcH/btrdd8fk8Gp/6DLf9uKGoVNk31CWQJQKTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bobFcH/btrdd8fk8Gp/6DLf9uKGoVNk31CWQJQKTK/img.png&quot; data-alt=&quot;비동기 로직에도 MDC 를 활용한 로깅이 적용된 것을 확인할 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bobFcH/btrdd8fk8Gp/6DLf9uKGoVNk31CWQJQKTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbobFcH%2Fbtrdd8fk8Gp%2F6DLf9uKGoVNk31CWQJQKTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1212&quot; height=&quot;310&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비동기 로직에도 MDC 를 활용한 로깅이 적용된 것을 확인할 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;마치며&quot; data-ke-size=&quot;size26&quot;&gt;마치며..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 UUID로 logId를 남기면서 잘 만들었다고 생각했는데, 지금 보니 전혀 실용적이지 않은 구현이었네요.&lt;br /&gt;&lt;s&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;어쩌면 지금도 전혀 실용적이지 않을 수 있지만&lt;br /&gt;&lt;/span&gt;&lt;/s&gt;아마 다음 버전을 작성한다면 레이어별 수행 시간을 체크하고 slack 알림을 하는 것까지는 구현할 것 같습니다.&lt;br /&gt;같이 처리할 수 있을지 모르겠지만 slowquery 모니터링까지도 고려하고 있습니다.&lt;br /&gt;&lt;br /&gt;사실 요즘 구성이 잘 되어 있는 회사는 kafka로 메시지 큐를 구성하고 elastic과 kibana로 로그를 관리합니다.&lt;br /&gt;하지만 지금 회사의 규모에서는 오버 스펙이기도 하고 기반 기술의 공부가 부족하기에 이 정도만 적용해도 충분할 것 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;언젠가는 전부 설계하고 구현할 수 있게 되기를..&lt;/b&gt;&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>exception</category>
      <category>logging</category>
      <category>MDC</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/20</guid>
      <comments>https://hyune-c.tistory.com/20#entry20comment</comments>
      <pubDate>Sat, 3 Jul 2021 00:51:47 +0900</pubDate>
    </item>
    <item>
      <title>표준 예외 처리에서 로깅까지 (1)</title>
      <link>https://hyune-c.tistory.com/19</link>
      <description>&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이전에 코드스쿼드에 공개해서 반응이 뜨거웠던(?) &lt;/span&gt;&lt;b&gt;표준 예외 처리&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;가 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하지만 실무를 하다 보니 부족한 점이 보이고, 고도화하는 김에 로깅까지 해야겠다고 생각해서 글을 작성해봅니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이전의 repository 도 남기지만 굳이 참고하지 않고 본 코드만 따라오셔도 무방합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/article&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/exception-handling&quot;&gt;[deprecated] 표준 예외 처리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/blog-code/tree/master/exception-logging&quot;&gt;표준 예외 처리에서 로깅까지&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-1-리팩토링&quot; data-ke-size=&quot;size26&quot;&gt;Step 1. 리팩터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 작성한&amp;nbsp;&lt;b&gt;표준 예외 처리&lt;/b&gt;를 리팩터링 하는 것으로 시작하겠습니다.&lt;/p&gt;
&lt;h3 id=&quot;1-exceptionadvice-분리&quot; data-ke-size=&quot;size23&quot;&gt;1. ExceptionAdvice 분리&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL7CSZ/btrdeXkpMXp/CFALcfqEarKV7lAvk3hm8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL7CSZ/btrdeXkpMXp/CFALcfqEarKV7lAvk3hm8k/img.png&quot; data-alt=&quot;한개로 운영하던 ExceptionAdvice 에서 공통으로 사용하는 메소드를 BaseExceptionAdvice 로 분리했습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL7CSZ/btrdeXkpMXp/CFALcfqEarKV7lAvk3hm8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL7CSZ%2FbtrdeXkpMXp%2FCFALcfqEarKV7lAvk3hm8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;244&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;한개로 운영하던 ExceptionAdvice 에서 공통으로 사용하는 메소드를 BaseExceptionAdvice 로 분리했습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-size: 1.44em; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. 테스트 케이스&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZHERr/btrdkO0yEOR/ITtJeReTlyR3E9geXgREJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZHERr/btrdkO0yEOR/ITtJeReTlyR3E9geXgREJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZHERr/btrdkO0yEOR/ITtJeReTlyR3E9geXgREJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZHERr%2FbtrdkO0yEOR%2FITtJeReTlyR3E9geXgREJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1115&quot; height=&quot;192&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKotCE/btrdesZhwDP/7724YswldMsJ9I3M1HcE00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKotCE/btrdesZhwDP/7724YswldMsJ9I3M1HcE00/img.png&quot; data-alt=&quot;오류 발생시 logId 로 원인을 추적할 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKotCE/btrdesZhwDP/7724YswldMsJ9I3M1HcE00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKotCE%2FbtrdesZhwDP%2F7724YswldMsJ9I3M1HcE00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1138&quot; height=&quot;461&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;오류 발생시 logId 로 원인을 추적할 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 정도로 장애 원인을 추적할 수 있나?&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kElDL/btrdd8l2Bdg/7bIiq1GF9M8afABNma8bG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kElDL/btrdd8l2Bdg/7bIiq1GF9M8afABNma8bG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kElDL/btrdd8l2Bdg/7bIiq1GF9M8afABNma8bG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkElDL%2Fbtrdd8l2Bdg%2F7bIiq1GF9M8afABNma8bG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1134&quot; height=&quot;121&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 장애를 추적하기 위해 장애 난 시점만이 아닌 연관된 흐름을 봐야 합니다.&lt;br /&gt;하지만 Step 1 에서는 logId 가 에러 발생 시점에만 존재하기 때문에 동시에 요청이 들어온다면 추적이 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 Step에서 MDC를 활용해서 개선해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Step 2. MDC로 개선하기&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MDC (Mapped Diagnostic Context)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 쓰레드 로깅에 활용되는 기술인 NDC (Nested Diagnostic Context)에 java.util.map의 적용으로 사용성을 높인 기술로&lt;br /&gt;각 쓰레드 별로 별도의 로깅 쓰레드를 정적 메서드로 관리할 수 있습니다.&lt;br /&gt;현재 log4j와 logback에서만 MDC 기능을 제공하고 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;1-mdc-적용&quot; data-ke-size=&quot;size23&quot;&gt;1. MDC 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 쉽습니다!&lt;br /&gt;Filter에서 transactionId를 UUID로 생성하고 map을 사용하듯이 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 역시 map과 같습니다.&lt;br /&gt;하지만 여전히 필요한 곳에서 MDC를 호출해줘야 되기에 불편합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S8sTH/btrdni04M73/lXSGVuLpXCvL3sf4w5vKzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S8sTH/btrdni04M73/lXSGVuLpXCvL3sf4w5vKzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S8sTH/btrdni04M73/lXSGVuLpXCvL3sf4w5vKzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS8sTH%2Fbtrdni04M73%2FlXSGVuLpXCvL3sf4w5vKzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1146&quot; height=&quot;166&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
public class MdcLogEnhancerFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        MDC.put(&quot;transactionId&quot;, UUID.randomUUID().toString());
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        MDC.clear();
        Filter.super.destroy();
    }
}


@GetMapping(&quot;/api/test&quot;)
public void test() {
    System.out.println(&quot;system out&quot;);
    log.info(&quot;### log info&quot;);
    log.info(&quot;### MDC transactionId={}&quot;, MDC.get(&quot;transactionId&quot;));
    throw new RuntimeException(&quot;test!! &quot; + MDC.get(&quot;transactionId&quot;));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 예제에서는&amp;nbsp;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt;Interceptor에서 구현했지만, 저는 요청의 시작인 Filter부터 관리를 하고 싶어 Filter에 구현했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2-console-logging-pattern-설정&quot; data-ke-size=&quot;size23&quot;&gt;2. console logging pattern 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logging pattern 은 logback의 기본 pattern을 참고했습니다.&amp;nbsp;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/blob/v2.4.1/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqs9XG/btrdfImZV9m/u7GX3knKBmaJaVmVoPYWq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqs9XG/btrdfImZV9m/u7GX3knKBmaJaVmVoPYWq0/img.png&quot; data-alt=&quot;중요한 부분은 [%mdc{transactionId:--}] 입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqs9XG/btrdfImZV9m/u7GX3knKBmaJaVmVoPYWq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqs9XG%2FbtrdfImZV9m%2Fu7GX3knKBmaJaVmVoPYWq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;147&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중요한 부분은 [%mdc{transactionId:--}] 입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;// application.yml

logging:
  pattern:
    console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} [%mdc{transactionId}] %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3-예외-처리-개선&quot; data-ke-size=&quot;size23&quot;&gt;3. 예외 처리 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Step 1 에서는 BaseExceptionAdvice에서 UUID를 만들었습니다.&lt;br /&gt;하지만 이제는 MDC를 활용해서 개선할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ErrorResponse {

    private final String code;
    private final String message;
    private final LocalDateTime time = LocalDateTime.now();
    private final String transactionId = MDC.get(&quot;transactionId&quot;);
    private final List&amp;lt;FieldError&amp;gt; errors;

    public static ErrorResponse of(ErrorCode errorCode, BindingResult bindingResult) {
        return ErrorResponse.builder()
            .code(errorCode.name())
            .message(errorCode.getReason())
            .errors(FieldError.of(bindingResult))
            .build();
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 BaseExceptionAdvice에서 UUID 생성 대신 exception trace를 출력하도록 변경합니다.&lt;br /&gt;필요에 따라 excetption을 모두 출력하지 않고 getStackTrace()를 가져올 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public abstract class BaseExceptionAdvice {

    protected void preHandle(Exception ex) {
        log.error(&quot;### message={}, cause={}&quot;, ex.getMessage(), ex.getCause(), ex);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 개선된 생성자와 preHandle에 맞게 exceptionAdvice를 변경해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class BasicExceptionAdvice extends BaseExceptionAdvice {

    ...

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    protected ErrorResponse handleException(Exception ex) {
        preHandle(ex);
        return ErrorResponse.of(UNKNOWN);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3osO/btrdfH9t28G/KHDZtMuqmiYTxKYxH4xHB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3osO/btrdfH9t28G/KHDZtMuqmiYTxKYxH4xHB1/img.png&quot; data-alt=&quot;에러가 발생해도 깔끔하게 나옵니다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3osO/btrdfH9t28G/KHDZtMuqmiYTxKYxH4xHB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3osO%2FbtrdfH9t28G%2FKHDZtMuqmiYTxKYxH4xHB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;149&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에러가 발생해도 깔끔하게 나옵니다!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;하지만&quot; data-ke-size=&quot;size26&quot;&gt;하지만..&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KYqIu/btrdmBfl74N/ty5AZxFPNOBK58S1GN2rt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KYqIu/btrdmBfl74N/ty5AZxFPNOBK58S1GN2rt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KYqIu/btrdmBfl74N/ty5AZxFPNOBK58S1GN2rt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKYqIu%2FbtrdmBfl74N%2Fty5AZxFPNOBK58S1GN2rt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1217&quot; height=&quot;163&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 로직에서는 의도와는 다르게 MDC에 넣은 값이 출력되지 않습니다!&lt;br /&gt;무슨 설정이 빠진 걸까요?&lt;br /&gt;&lt;br /&gt;비동기에서의 MDC 적용은 다음으로 이어집니다.&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>exception</category>
      <category>logging</category>
      <category>MDC</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/19</guid>
      <comments>https://hyune-c.tistory.com/19#entry19comment</comments>
      <pubDate>Fri, 2 Jul 2021 17:10:15 +0900</pubDate>
    </item>
    <item>
      <title>사이드 프로젝트의 기록 - 글쑤시개 (1)</title>
      <link>https://hyune-c.tistory.com/18</link>
      <description>&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/article&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;돌아보니 저는 백엔드 역량에만 치우진 사이드 프로젝트만 하고 있었던 것 같습니다.&lt;br /&gt;&lt;/span&gt;&lt;span&gt;그러다보니 항상 반쪽짜리 프로젝트만 만들게된 것이 아쉬웠고, 이즈음에서 제대로된 한 사이클의 어플리케이션을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;만들어봐야겠다는 결심을 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 시리즈는 프로젝트를 진행하면서 겪는 어려움과 회고가 기록할만큼의 쌓이면 작성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Dogcument/gssg-be&quot;&gt;글쑤시개 BE&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;사이드-프로젝트-선택&quot; data-ke-size=&quot;size26&quot;&gt;사이드 프로젝트 선택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 내가 풀스택으로 개발할 것인가? 아니면 팀을 구할 것인가? 를 고민했는데, 실무와 병행하면서 실력을 키우려면&lt;br /&gt;백엔드에 더 집중해야된다는 결론을 냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 번의 사이드 프로젝트와 실무를 경험하면서 개발자의 관점에서 느낀 점은 3가지 였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 안다고 생각하던 기술도 노베이스에서 적용하려고 하면 헤멘다.&lt;/li&gt;
&lt;li&gt;공부한 기술을 실무에 적용하고 싶지만, 피쳐 개발이 우선인 스타트업의 특성 상 쉽지 않다.&lt;/li&gt;
&lt;li&gt;사이드 프로젝트 아이템의 참신함은 중요하지 않다. 중요한 것은 기술을 녹이는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드의 가장 흔한 프로젝트인 게시판 만들기도 기술과 설계를 적용하기 시작하면 얼마든지 도전적인&lt;br /&gt;프로젝트가 될 수 있다는 것을 알고있기에, 실력 향상에 적합한 팀을 찾기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 잡은 최소 기준은 4가지 였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경력 있는 프론트 개발자가 있는 팀.&lt;/li&gt;
&lt;li&gt;프로토 타입이 나와있거나, 최소한 기획안이 확정된 팀.&lt;/li&gt;
&lt;li&gt;주 1.5 ~ 2 영업일을 기술적으로 투자할 분량이 있는 프로젝트.&lt;/li&gt;
&lt;li&gt;사이드 프로젝트임을 인정하고 비지니스 모델이 없는/서두르지 않는 팀.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 기준에 부합하는 팀에 합류 할 수 있었습니다.&lt;/p&gt;
&lt;h2 id=&quot;팀-구성&quot; data-ke-size=&quot;size26&quot;&gt;팀 구성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기획 1&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경력 0년&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디자인 1&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경력 0년&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FE &amp;amp; 앱 1&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경력 0년 (C++ 게임 개발 경력 n년)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BE 1&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;경력 1년 (인프라 &amp;amp; 기술지원 경력 n년)&lt;/li&gt;
&lt;li&gt;주 1회 오프라인 모임&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;개인-목표&quot; data-ke-size=&quot;size26&quot;&gt;개인 목표&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도전적인 기술 적용&lt;/li&gt;
&lt;li&gt;흐름도, 객체 관계에 입각한, 변경에 강한 설계&lt;/li&gt;
&lt;li&gt;CI/CD 구성&lt;/li&gt;
&lt;li&gt;테스트 설계&lt;/li&gt;
&lt;li&gt;모니터링 설계&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;위 모든 과정을 기록&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;주요-진행-내용--어려움&quot; data-ke-size=&quot;size26&quot;&gt;주요 진행 내용 &amp;amp; 어려움&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxLac/btrnV5MBP5r/FJdNTIBpvEwWpZCNhpgAO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxLac/btrnV5MBP5r/FJdNTIBpvEwWpZCNhpgAO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxLac/btrnV5MBP5r/FJdNTIBpvEwWpZCNhpgAO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxLac%2FbtrnV5MBP5r%2FFJdNTIBpvEwWpZCNhpgAO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;382&quot; height=&quot;32&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 세팅 및 FE 와 API 연동 &amp;amp; Swagger 제공&lt;/li&gt;
&lt;li&gt;spring security 를 활용한 JWT 로그인 &amp;amp; 권한 관리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://hyune-c.tistory.com/entry/%ED%9A%8C%EC%9B%90-%EA%B0%80%EC%9E%85%EC%9D%84-%ED%95%B4%EB%B3%B4%EC%9E%90-%EC%95%94%ED%98%B8%ED%99%94%EC%97%90%EC%84%9C-spring-security-%EA%B9%8C%EC%A7%80&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;기획에서-기술-리뷰까지&quot; data-ke-size=&quot;size26&quot;&gt;기획에서 기술 리뷰까지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자의 일정 관리를 위해서 가장 중요하다고 생각하는 것은, 기획자의 전체 의도를 파악하고 MVP 에 맞는 (또는 일정에 맞는)&lt;br /&gt;기술 범위를 산정하는 것이라고 생각합니다.&lt;br /&gt;하지만 팀원의 절반이 팀 프로젝트 경험이 없고, 체계가 잡히지 않았기에 커뮤니케이션 낭비는 필연적일 것 같습니다.&lt;br /&gt;회사라면 오프라인 미팅과 잦은 슬랙을 통해 의견을 좁힐 수 있지만, 본업과 병행해야 되는 사이드 프로젝트의&lt;br /&gt;특성상 커뮤니케이션 포인트를 줄이면서 해결할 수 있는 방법을 고민해야만 합니다.&lt;/p&gt;
&lt;h2 id=&quot;마치며&quot; data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만료 기한이 짧은 JWT 를 통한 로그인은 구현했지만, 기획에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;기 로그인자는 로그인 과정을 생략한다&lt;/b&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;는&lt;br /&gt;요구 사항을 전달해주었습니다. 직관적으로 드는 생각은 refresh token 을 활용하는 것이지만,&lt;br /&gt;이것을 FE 에도 설명하기 위해서는 공부하고 구성도를 그려야 할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧은 2 주의 개발에서도 도전적인 기술을 적용했고, 앞으로도 회사에서는 해보지 못한,&lt;br /&gt;하지만 도움이 될 기술을 연습할 수 있을 것이라는 생각에 즐겁습니다.&lt;/p&gt;</description>
      <category>잡담 &amp;amp; ETC</category>
      <category>side project</category>
      <category>toy project</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/18</guid>
      <comments>https://hyune-c.tistory.com/18#entry18comment</comments>
      <pubDate>Tue, 22 Jun 2021 01:14:29 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security 로 회원 가입을 구현해보자 (3)</title>
      <link>https://hyune-c.tistory.com/17</link>
      <description>&lt;h2 id=&quot;step-4-custom-annotation-을-통해-로그인-정보-가져오기&quot; data-ke-size=&quot;size26&quot;&gt;Step 4. Custom Annotation 을 통해 로그인 정보 가져오기&lt;/h2&gt;
&lt;h3 id=&quot;1-custom-annotation-구현&quot; data-ke-size=&quot;size23&quot;&gt;1. Custom Annotation 구현&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface LoginMember {

  boolean required() default true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;annotation 은 기본 값을 true 로 두고 필요한 api 에서만 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

  private static final String AUTHORIZATION_HEADER = &quot;authorization&quot;;

  private final JwtAuthTokenProvider jwtAuthTokenProvider;
  private final MemberRepository memberRepository;

  @Override
  public boolean supportsParameter(MethodParameter methodParameter) {
    return methodParameter.hasParameterAnnotation(LoginMember.class);
  }

  @Override
  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
    LoginMember loginUserAnnotation = parameter.getParameterAnnotation(LoginMember.class);
    if (!loginUserAnnotation.required()) {
      return null;
    }

    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);

    return Optional.ofNullable(request.getHeader(AUTHORIZATION_HEADER))
        .map(authorization -&amp;gt; authorization.split(&quot;bearer&quot;)[1])
        .map(jwtAuthTokenProvider::convertAuthToken)
        .map(JwtAuthToken::getEmail)
        .map(email -&amp;gt; memberRepository.findByEmail(email)
            .orElseThrow(() -&amp;gt; new CustomAuthrizationException(ErrorCode.NOT_EXIST_MEMBER)))
        .orElseThrow(() -&amp;gt; new CustomAuthrizationException(ErrorCode.FORBIDDEN));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resolver 는 jwt 를 해석해서 이메일 정보를 가져오고, 회원이 존재하는지를 검증합니다.&lt;/p&gt;
&lt;h3 id=&quot;2-resolver-설정&quot; data-ke-size=&quot;size23&quot;&gt;2. Resolver 설정&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  private final LoginMemberArgumentResolver loginMemberArgumentResolver;

  @Override
  public void addArgumentResolvers(List&amp;lt;HandlerMethodArgumentResolver&amp;gt; resolvers) {
    resolvers.add(loginMemberArgumentResolver);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-jwt-에서-로그인-정보-가져오기&quot; data-ke-size=&quot;size23&quot;&gt;3. JWT 에서 로그인 정보 가져오기&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@RestController
public class LoginController {

  @GetMapping(&quot;/api/v1/my&quot;)
  public void my(@LoginMember Member loginMember) {
    log.info(&quot;### loginMember.getEmail()={}, loginMember.getRole()={}&quot;, loginMember.getEmail(), loginMember.getRole());
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHVPaw/btrbId9Ejrz/Y1A12GSN9Q9E2fRBjkguO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHVPaw/btrbId9Ejrz/Y1A12GSN9Q9E2fRBjkguO1/img.png&quot; data-alt=&quot;controller 에서 jwt 에 없던 id 정보를 확인할 수 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHVPaw/btrbId9Ejrz/Y1A12GSN9Q9E2fRBjkguO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHVPaw%2FbtrbId9Ejrz%2FY1A12GSN9Q9E2fRBjkguO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;58&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;controller 에서 jwt 에 없던 id 정보를 확인할 수 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;마치며&quot; data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현을 하면서 배운 것도 많았지만, 반대로 미숙한 부분도 많았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;filter 레이어에서의 에러 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증/인가 예외 처리를 제대로 하고 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AuthTokenProvider 에 Authentication 을 가져오는 메소드가 있는게 적절한가?&lt;/li&gt;
&lt;li&gt;UsernamePasswordAuthenticationToken 외를 이용해서 인증을 하려면 어떻게 해야할까?&lt;/li&gt;
&lt;li&gt;SecurityContextHolder 를 더 잘 이용할 수 있는 방법은 무엇일까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 예제에서는 회원 정보를 가져올 때 SecurityContextHolder 를 통한 것이 아닌 별도의 Resolver 를 만들었습니다.&lt;br /&gt;조금 구현해본 바로는 Authentication - User - principal 와 Adapter 를 이해해야되는데,&lt;br /&gt;이 과정에서 Member entity 에 extends 를 해줘야하는 등 종속성이 생기는 것 같아 이상한 느낌이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 공부가 부족해서 잘못 이해했거나 best practice 를 찾지 못한 것이라고 생각합니다.&lt;br /&gt;하지만 security 는 워낙 양이 방대하고 사이드 프로젝트를 하는데는 이 정도의 구현으로 충분하기에&lt;br /&gt;고도화는 좀 더 공부하고 해볼 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;금방 할 수 있기를..&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>bcrypt</category>
      <category>Custom Annotation</category>
      <category>JWT</category>
      <category>Spring Security</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/17</guid>
      <comments>https://hyune-c.tistory.com/17#entry17comment</comments>
      <pubDate>Sat, 19 Jun 2021 20:10:31 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security 로 회원 가입을 구현해보자 (2)</title>
      <link>https://hyune-c.tistory.com/16</link>
      <description>&lt;h2 id=&quot;step-3-jwt-적용&quot; data-ke-size=&quot;size26&quot;&gt;Step 3. JWT 적용&lt;/h2&gt;
&lt;h3 id=&quot;1-준비&quot; data-ke-size=&quot;size23&quot;&gt;1. 준비&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;// build.gradle

dependencies {
  implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
  runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
  runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;// application.yml
application:
  jwt:
    secreat: ${SECREAT:341242FF3617E8D58D7A4F6FEED56FEB8619CF82B9B00829A02E161A4418BFA2}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 예제에서는 jwt 생성시 HS256 알고리즘을 사용합니다.&lt;br /&gt;HS256 알고리즘을 위해서는 secreat 이 256 bit 이상이어야하는데, 이를 위해 'TEST_KEY' 문자열을 SHA256 으로 해시합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Configuration
public class JwtConfiguration {

  @Value(&quot;${application.jwt.secreat}&quot;)
  private String secreat;

  @Bean
  public JwtAuthTokenProvider jwtAuthTokenProvider() {
    return new JwtAuthTokenProvider(secreat);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;secreat 을 환경 변수에서 가져오고, 다음 step 에서 만들 provider 를 bean 으로 등록합니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public enum Role {
  ADMIN(&quot;ROLE_ADMIN&quot;, &quot;관리자 권한&quot;),
  MEMBER(&quot;ROLE_MEMBER&quot;, &quot;회원 권한&quot;),
  UNKNOWN(&quot;UNKNOWN&quot;, &quot;알 수 없는 권한&quot;);

  private final String code;
  private final String description;
}


@Entity
public class Member {

  private String role;

  public Member(String email, String password) {
    this.email = email;
    this.password = password;
    this.role = Role.MEMBER.getCode();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Member entity 에 role 을 추가하고, 생성자에 Member 권한을 부여하는 로직을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외 처리는 최소화해서 구현했기에 생략합니다. repository 를 참고해주세요.&lt;/p&gt;
&lt;h3 id=&quot;2-jwtauthtoken--provider-구현&quot; data-ke-size=&quot;size23&quot;&gt;2. JwtAuthToken &amp;amp; Provider 구현&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class JwtAuthToken implements AuthToken&amp;lt;Claims&amp;gt; {

  private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

  @Getter
  private final String token;
  private final Key secreatKey;

  JwtAuthToken(String token, Key secreatKey) {
    this.token = token;
    this.secreatKey = secreatKey;
  }

  JwtAuthToken(String email, String role, Date expiredDate, Key secreatKey) {
    Map&amp;lt;String, Object&amp;gt; claims = new HashMap&amp;lt;&amp;gt;() {
      {
        put(&quot;email&quot;, email);
        put(&quot;role&quot;, role);
      }
    };
    this.token = Jwts.builder()
        .setClaims(claims)
        .signWith(secreatKey, signatureAlgorithm)
        .setExpiration(expiredDate)
        .compact();
    this.secreatKey = secreatKey;
  }

  @Override
  public boolean validate() {
    return getClaims() != null;
  }

  @Override
  public Claims getClaims() {
    try {
      return Jwts.parserBuilder().setSigningKey(secreatKey).build().parseClaimsJws(token).getBody();
    } catch (... exception) {
      ... 예외 처리
    }

    return null;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;token 종류가 늘어날 것을 고려해 AuthToken interface 를 활용합니다.&lt;/li&gt;
&lt;li&gt;JwtAuthToken 은 token string 과 secreat 를 관리하는 객체입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;신규 jwt 생성시 claim 에 이메일 주소와 권한을 넣습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;claims 의 예외 처리는 구현하지 못했습니다. (마지막에 설명합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class JwtAuthTokenProvider implements AuthTokenProvider&amp;lt;JwtAuthToken&amp;gt; {

  private static final String AUTHORITIES_KEY = &quot;role&quot;;
  private final Key secreat;

  public JwtAuthTokenProvider(String secreat) {
    byte[] keyBytes = Decoders.BASE64.decode(secreat);
    this.secreat = Keys.hmacShaKeyFor(keyBytes);
  }

  @Override
  public JwtAuthToken createAuthToken(String email, String role, Date expiredDate) {
    return new JwtAuthToken(email, role, expiredDate, secreat);
  }

  @Override
  public JwtAuthToken convertAuthToken(String token) {
    return new JwtAuthToken(token, secreat);
  }

  @Override
  public Authentication getAuthentication(JwtAuthToken authToken) {
    if (authToken.validate()) {
      Claims claims = authToken.getClaims();
      Collection&amp;lt;? extends GrantedAuthority&amp;gt; authorities = Collections.singleton(
          new SimpleGrantedAuthority(claims.get(AUTHORITIES_KEY).toString()));
      User principal = new User(claims.get(&quot;email&quot;).toString(), &quot;&quot;, authorities);

      return new UsernamePasswordAuthenticationToken(principal, authToken, authorities);
    } else {
      throw new CustomAuthenticationException(ErrorCode.FAILED_GENERATE_TOKEN);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;token 종류가 늘어날 것을 고려해 AuthTokenProvider interface 를 활용합니다.&lt;/li&gt;
&lt;li&gt;JwtAuthToken 을 만드는 역할과 Authentication 을 가져오는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-로그인을-구현합니다&quot; data-ke-size=&quot;size23&quot;&gt;3. 로그인을 구현합니다&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@RestController
public class LoginController {

  private final LoginService loginService;

  @PostMapping(&quot;/api/v1/login&quot;)
  public String login(@RequestBody @Valid LoginMemberRequest request) {
    JwtAuthToken jwtAuthToken = loginService.login(request.getLoginId(), request.getPassword());

    return jwtAuthToken.getToken();
  }
}


@Service
public class LoginService {

  private final static long LOGIN_RETENTION_MINUTES = 300;

  private final AuthenticationManagerBuilder authenticationManagerBuilder;
  private final JwtAuthTokenProvider jwtAuthTokenProvider;
  private final PasswordEncoder passwordEncoder;

  private final MemberRepository memberRepository;

  public JwtAuthToken login(String loginId, String password) {
    Member member = memberRepository.findByEmail(loginId)
        .orElseThrow(() -&amp;gt; new CustomAuthenticationException(NOT_EXIST_MEMBER));

    if (!passwordEncoder.matches(password, member.getPassword())) {
      throw new CustomAuthenticationException(NOT_VALID_PASSWORD);
    }

    // 중요! SecurityContextHolder
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(member.getEmail(), password);
    Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
    SecurityContextHolder.getContext().setAuthentication(authentication);

    Role role = authentication.getAuthorities().stream()
        .map(GrantedAuthority::getAuthority)
        .findFirst()
        .map(Role::of)
        .orElseThrow(() -&amp;gt; new CustomAuthrizationException(NOT_EXIST_AUTHORIZATION));
    Date expiredDate = Date.from(
        LocalDateTime.now().plusMinutes(LOGIN_RETENTION_MINUTES).atZone(ZoneId.systemDefault()).toInstant());

    return jwtAuthTokenProvider.createAuthToken(member.getEmail(), role.getCode(), expiredDate);
  }
}


@Service
public class CustomUserDetailsService implements UserDetailsService {

  private final MemberRepository memberRepository;

  @Override
  public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    return memberRepository.findByEmail(email)
        .map(this::createSpringSecurityUser)
        .orElseThrow(() -&amp;gt; new CustomAuthenticationException(ErrorCode.NOT_EXIST_MEMBER));
  }

  private User createSpringSecurityUser(Member member) {
    List&amp;lt;GrantedAuthority&amp;gt; grantedAuthorities = Collections.singletonList(new SimpleGrantedAuthority(member.getRole()));
    return new User(member.getEmail(), member.getPassword(), grantedAuthorities);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 과정에서 SecurityContextHolder 에 인증 정보를 등록하고, jwt 를 반환합니다.&lt;/p&gt;
&lt;h3 id=&quot;3-1-securitycontextholder-흐름&quot; data-ke-size=&quot;size23&quot;&gt;3-1. SecurityContextHolder 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LoginService.login() 에서는 고작 3줄로 표현되었지만, 이 흐름이 security 에서 제공하는 마법같은 인증 기능입니다.&lt;br /&gt;아래는 그 중 일부를 설명한 것으로 디버그 모드를 통해 흐름을 따라가보시는 것을 추천합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class LoginService {

  // 1. UsernamePasswordAuthenticationToken 를 가져오기 위해 DaoAuthenticationProvider.retrieveUser() 가 호출됩니다.
  UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(member.getEmail(), password);
  Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

  // 4. 인증된 Authentication 를 SecurityContextHolder 에 등록합니다.
  SecurityContextHolder.getContext().setAuthentication(authentication);
}


public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider{

  // 0. BCrypt 로 정의한 PasswordEncoder 가 적용됩니다.
  public DaoAuthenticationProvider() {
    setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
  }

  @Override
  protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
    // 2. loadUserByUsername() 를 CustomUserDetailsService 로 Override 하였기에 우리가 찾는 회원의 UserDetails 를 가져올 수 있습니다.
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
  }

  @Override
 protected void additionalAuthenticationChecks(UserDetails userDetails,
   UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

  // 3. 비밀번호를 비교하는 부분으로 passwordEncoder 가 적용됩니다.
  String presentedPassword = authentication.getCredentials().toString();
  if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
   throw new BadCredentialsException(this.messages
     .getMessage(&quot;AbstractUserDetailsAuthenticationProvider.badCredentials&quot;, &quot;Bad credentials&quot;));
  }
 }
}


@Service
public class CustomUserDetailsService implements UserDetailsService {

  // 2-1. UserDetails 을 가져오는 로직을 Override 합니다.
  @Override
  public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    ... UserDetails 가져오기
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-로그인-테스트&quot; data-ke-size=&quot;size23&quot;&gt;4. 로그인 테스트&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class LoginControllerTest {

  @DisplayName(&quot;[web] 로그인&quot;)
  @TestFactory
  public Stream&amp;lt;DynamicTest&amp;gt; login() {
    String email = &quot;hyune@gmail.com&quot;;
    String password = &quot;1q2w3e4r*&quot;;

    return Stream.of(
        dynamicTest(&quot;[성공] 회원 가입&quot;, () -&amp;gt; {
          ... 회원 가입
        }),
        dynamicTest(&quot;[성공] 로그인&quot;, () -&amp;gt; {
          ... 로그인
        })
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpUJIM/btrbwgS302Y/x7Kx524ekh72Q5x7bI6ms1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpUJIM/btrbwgS302Y/x7Kx524ekh72Q5x7bI6ms1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpUJIM/btrbwgS302Y/x7Kx524ekh72Q5x7bI6ms1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpUJIM%2FbtrbwgS302Y%2Fx7Kx524ekh72Q5x7bI6ms1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;174&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rP9YL/btrbtneiWcw/DBmv35kZHv9x4fMXffwjlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rP9YL/btrbtneiWcw/DBmv35kZHv9x4fMXffwjlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rP9YL/btrbtneiWcw/DBmv35kZHv9x4fMXffwjlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrP9YL%2FbtrbtneiWcw%2FDBmv35kZHv9x4fMXffwjlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1145&quot; height=&quot;209&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t1fc2/btrbCuwenag/kFcixKzpAYrQn6707oilb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t1fc2/btrbCuwenag/kFcixKzpAYrQn6707oilb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t1fc2/btrbCuwenag/kFcixKzpAYrQn6707oilb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft1fc2%2FbtrbCuwenag%2FkFcixKzpAYrQn6707oilb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;375&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인에 성공하면 정상적인 jwt 가 반환됨을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 정보 가져오기는 다음으로 이어집니다.&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>bcrypt</category>
      <category>Custom Annotation</category>
      <category>JWT</category>
      <category>Spring Security</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/16</guid>
      <comments>https://hyune-c.tistory.com/16#entry16comment</comments>
      <pubDate>Sat, 19 Jun 2021 18:44:35 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security 로 회원 가입을 구현해보자 (1)</title>
      <link>https://hyune-c.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보니 저는 이미 구현되어 있는 인증을 가져다 쓰기만 할 뿐, 그 내부 구조는 이해하지 못하고 개발해온 것 같습니다.&lt;br /&gt;그래서 이번 사이드 프로젝트에는 Spring Security 를 활용해 암호화부터 JWT 까지의 회원 가입을 구현했고 그 기록을 남겨봅니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 Spring Security 는 이해가 깊지 못해 일부 저의 방식으로 구현한 내용이 있습니다. 내용을 읽는데 참고해주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 일부 각색되었습니다.&lt;br /&gt;각 Step 별 자세한 코드는 repository 의 서브 branch 를 참고해주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/blog-code/tree/master/signup-security&quot;&gt;Spring Security 로 회원 가입을 구현해보자&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-1-회원-가입&quot; data-ke-size=&quot;size26&quot;&gt;Step 1. 회원 가입&lt;/h2&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
public class Member {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String email;
  private String password;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xWfnb/btrbABoSebp/LROr6qU5K1aDJK88EsCq1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xWfnb/btrbABoSebp/LROr6qU5K1aDJK88EsCq1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xWfnb/btrbABoSebp/LROr6qU5K1aDJK88EsCq1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxWfnb%2FbtrbABoSebp%2FLROr6qU5K1aDJK88EsCq1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;213&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소한의 정보를 가진 회원 entity 를 만들고 정상 가입 됬지만,&lt;br /&gt;비밀번호는 평문 그대로 저장되어 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;step-2-비밀번호-암호화&quot; data-ke-size=&quot;size26&quot;&gt;Step 2. 비밀번호 암호화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 암호화를 위해 spring security 에서 제공하는 BCrypt 암호화를 사용합니다.&lt;/p&gt;
&lt;h3 id=&quot;1-security-설정&quot; data-ke-size=&quot;size23&quot;&gt;1. Security 설정&lt;/h3&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-security'
}


@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  ... security 기본 설정
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle dependencies 와 WebSecurityConfig 를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class PasswordConfiguration {

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BCrypt 로 PasswordEncoder bean 을 생성합니다.&lt;/p&gt;
&lt;h3 id=&quot;2-비밀번호-암호화-적용&quot; data-ke-size=&quot;size23&quot;&gt;2. 비밀번호 암호화 적용&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Converter
public class PasswordEncryptConverter implements AttributeConverter&amp;lt;String, String&amp;gt; {

  private final PasswordEncoder passwordEncoder;

  @Override
  public String convertToDatabaseColumn(String entityAttribute) {
    return passwordEncoder.encode(entityAttribute);
  }

  @Override
  public String convertToEntityAttribute(String databaseColumn) {
    // DB 에서 가져올 때는 복호화하지 않습니다.
    return databaseColumn;
  }
}


@Entity
public class Member {

  @Convert(converter = PasswordEncryptConverter.class)
  private String password;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Member entity 의 password 에 PasswordEncryptConverter 를 적용합니다.&lt;/p&gt;
&lt;h3 id=&quot;3-암호화-적용-테스트&quot; data-ke-size=&quot;size23&quot;&gt;3. 암호화 적용 테스트&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@DisplayName(&quot;[성공] 회원 가입&quot;)
@Test
public void success_create() throws Exception {
  // given
  String password = &quot;1q2w3e4r*&quot;;

  // when
  ... create

  // then
  assertThat(passwordEncoder.matches(password, member.getPassword())).isTrue();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yzWLA/btrbHcIt53b/yXiOiE7Z6JBb8zjePCAMNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yzWLA/btrbHcIt53b/yXiOiE7Z6JBb8zjePCAMNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yzWLA/btrbHcIt53b/yXiOiE7Z6JBb8zjePCAMNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyzWLA%2FbtrbHcIt53b%2FyXiOiE7Z6JBb8zjePCAMNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;993&quot; height=&quot;215&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 적용은 다음 글로 이어집니다.&lt;/p&gt;</description>
      <category>Series/내가 해본</category>
      <category>bcrypt</category>
      <category>Custom Annotation</category>
      <category>JWT</category>
      <category>Spring Security</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/15</guid>
      <comments>https://hyune-c.tistory.com/15#entry15comment</comments>
      <pubDate>Sat, 19 Jun 2021 01:26:54 +0900</pubDate>
    </item>
    <item>
      <title>RSS 로 블로그를 모아보자</title>
      <link>https://hyune-c.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 많은 회사들이 기술 블로그를 운영하고, 감사한 개발자분들이 양질의 블로그를 운영해주시고 있습니다.&lt;br /&gt;하지만 반대로 너무 많다 보니 모아보기가 힘들고, 모아주는 서비스는 내 입맛에 맞지 않거나 알람 확인이 힘들 때가 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저는 사설 Slack 채널에 RSS 피드 구독 설정을 했고 이 과정에서의 작은 팁을 공유합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;구독 방법은 공식 레퍼런스를 참고해주세요.&lt;/blockquote&gt;
&lt;figure id=&quot;og_1639501299232&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Slack에 RSS 피드 추가&quot; data-og-description=&quot;즐겨 찾는 블로그나 새 사이트가 있나요?&amp;nbsp;Slack을 사용하여 RSS 및 Atom 피드를 모두 구독하고 선택한 Slack 채널에서 업데이트를 받을 수 있습니다. 참고...&quot; data-og-host=&quot;slack.com&quot; data-og-source-url=&quot;https://slack.com/intl/ko-kr/help/articles/218688467-Slack%EC%97%90-RSS-%ED%94%BC%EB%93%9C-%EC%B6%94%EA%B0%80&quot; data-og-url=&quot;https://slack.com/intl/ko-kr/help/articles/218688467-Slack%EC%97%90-RSS-%ED%94%BC%EB%93%9C-%EC%B6%94%EA%B0%80&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Gkd4b/hyMH0R3dv5/nTjoZNWikIJpI8H4jij2ok/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/qDtso/hyMIcrqOmC/GWwZIrV5mM9VPR6d0pC111/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256&quot;&gt;&lt;a href=&quot;https://slack.com/intl/ko-kr/help/articles/218688467-Slack%EC%97%90-RSS-%ED%94%BC%EB%93%9C-%EC%B6%94%EA%B0%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://slack.com/intl/ko-kr/help/articles/218688467-Slack%EC%97%90-RSS-%ED%94%BC%EB%93%9C-%EC%B6%94%EA%B0%80&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Gkd4b/hyMH0R3dv5/nTjoZNWikIJpI8H4jij2ok/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/qDtso/hyMIcrqOmC/GWwZIrV5mM9VPR6d0pC111/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Slack에 RSS 피드 추가&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;즐겨 찾는 블로그나 새 사이트가 있나요?&amp;nbsp;Slack을 사용하여 RSS 및 Atom 피드를 모두 구독하고 선택한 Slack 채널에서 업데이트를 받을 수 있습니다. 참고...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;slack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 id=&quot;rss-피드-가져오기&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RSS 피드 가져오기&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1639501247993&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;기억보단 기록을&quot; data-og-description=&quot;Java, TypeScript, ORM, RDBMS, AWS 를 주로 다루고 공유합니다.&quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/&quot; data-og-url=&quot;https://jojoldu.tistory.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dSMhQY/hyMH7p7H1a/ejV5bFsMPBW9zzIKkAf1J0/img.png?width=234&amp;amp;height=233&amp;amp;face=0_0_234_233,https://scrap.kakaocdn.net/dn/bYy0o8/hyMHaWiuww/Zi3HcXtavL1c0RKLqKnz0k/img.png?width=234&amp;amp;height=233&amp;amp;face=0_0_234_233,https://scrap.kakaocdn.net/dn/bLGtsh/hyMHbOsLu3/RJnr9UlspK2IhtUXneHgjk/img.png?width=230&amp;amp;height=300&amp;amp;face=21_10_208_209&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/&quot; data-source-url=&quot;https://jojoldu.tistory.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dSMhQY/hyMH7p7H1a/ejV5bFsMPBW9zzIKkAf1J0/img.png?width=234&amp;amp;height=233&amp;amp;face=0_0_234_233,https://scrap.kakaocdn.net/dn/bYy0o8/hyMHaWiuww/Zi3HcXtavL1c0RKLqKnz0k/img.png?width=234&amp;amp;height=233&amp;amp;face=0_0_234_233,https://scrap.kakaocdn.net/dn/bLGtsh/hyMHbOsLu3/RJnr9UlspK2IhtUXneHgjk/img.png?width=230&amp;amp;height=300&amp;amp;face=21_10_208_209');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;기억보단 기록을&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java, TypeScript, ORM, RDBMS, AWS 를 주로 다루고 공유합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jojoldu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양질의 블로깅을 빠른 주기로 올려주셔서 항상 감사하게 보고 있는 이동욱 님의 블로그입니다.&lt;br /&gt;블로그에서는 RSS 피드를 찾을 수 없지만, 다행히 대부분의 플랫폼에서는 RSS 기능을 자체 지원해주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 운영이 잘 되고 있는 대부분의 블로그는 플랫폼에서 제공하는 RSS 설정을 했거나 별도로 제공하고 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Github&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://{아이디}.github.io/atom.xml&quot;&gt;https://{아이디}.github.io/atom.xml&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://{아이디}.github.io/feed.xml&quot;&gt;https://{아이디}.github.io/feed.xml&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tistory&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://hyune-c.tistory.com/rss&quot;&gt;https://hyune-c.tistory.com/rss&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://medium.com/feed/@&quot;&gt;https://medium.com/feed/@&lt;/a&gt;&lt;a href=&quot;https://medium.com/feed/@{아이디}&quot;&gt;{아이디}&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brunch&lt;/td&gt;
&lt;td&gt;프로필에서 '페이지 소스 보기' 후 RSS 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbrcX0/btrnV4Fib2A/218uGBHtN9N99sKL1G21G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbrcX0/btrnV4Fib2A/218uGBHtN9N99sKL1G21G1/img.png&quot; data-alt=&quot;잘못된 RSS 피드는 에러가 발생하니 꼭 확인하고 구독해주세요!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbrcX0/btrnV4Fib2A/218uGBHtN9N99sKL1G21G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbrcX0%2FbtrnV4Fib2A%2F218uGBHtN9N99sKL1G21G1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;154&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잘못된 RSS 피드는 에러가 발생하니 꼭 확인하고 구독해주세요!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;구독-중인-rss-피드&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구독 중인 RSS 피드&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Yun Blog&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://cheese10yun.github.io/feed.xml&quot;&gt;https://cheese10yun.github.io/feed.xml&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D2 Blog&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://d2.naver.com/d2.atom&quot;&gt;https://d2.naver.com/d2.atom&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자바캔(Java Can Do IT) (최범균)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://javacan.tistory.com/rss&quot;&gt;https://javacan.tistory.com/rss&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기억보단 기록을 (이동욱)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://jojoldu.tistory.com/rss&quot;&gt;https://jojoldu.tistory.com/rss&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개린이의 일기장&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://hyune-c.tistory.com/rss&quot;&gt;https://hyune-c.tistory.com/rss&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;우아한형제들 기술 블로그&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://techblog.woowahan.com/feed/&quot;&gt;https://techblog.woowahan.com/feed/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컬리 기술 블로그&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://helloworld.kurly.com/feed.xml&quot;&gt;https://helloworld.kurly.com/feed.xml&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NHN Cloud Meetup&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://meetup.toast.com/rss&quot;&gt;https://meetup.toast.com/rss&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MUSINSA tech&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://medium.com/feed/musinsa-tech&quot;&gt;https://medium.com/feed/musinsa-tech&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에디의 기술 블로그&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://brunch.co.kr/rss/@@2MrI&quot;&gt;https://brunch.co.kr/rss/@@2MrI&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eternity's Chit-Chat (조영호)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;http://rss.egloos.com/blog/aeternum&quot;&gt;http://rss.egloos.com/blog/aeternum&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포스타입 팀&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://team.postype.com/rss&quot;&gt;https://team.postype.com/rss&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tip</category>
      <category>RSS</category>
      <category>Slack</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/14</guid>
      <comments>https://hyune-c.tistory.com/14#entry14comment</comments>
      <pubDate>Sun, 23 May 2021 03:50:44 +0900</pubDate>
    </item>
    <item>
      <title>mongoDB tutorials</title>
      <link>https://hyune-c.tistory.com/13</link>
      <description>&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/article&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 회사의 새로운 아이템을 프로토타입으로 만들 일이 생겼는데 회사에서 주로 사용하는 툴에 익숙해질 겸 가볍게 NCP와 mongoDB 를 사용해서 구성해보기로 했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Naver Cloud Playform - 처음 만져봄.&lt;/li&gt;
&lt;li&gt;mongoDB - select 문도 할 줄 모름.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;머신러닝으로 돌린 데이터를 mongoDB 에 적재합니다.&lt;/li&gt;
&lt;li&gt;이미 운영 중인 AWS EC2 서버에서 NCP로 연결합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;ncp---cloud-db-for-mongodb-구성&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;NCP - Cloud DB for MongoDB 구성&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;AWS 가 아닌 NCP를 사용한 이유는 크레디트를 투자받았기 때문입니다.&lt;br /&gt;퀵스타트 가이드가 잘 돼있어서, 캡처보다는 한 작업들을 서술형으로 작성합니다.&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 62.9844px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;Dev&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 80.4688px;&quot;&gt;Prod&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;Dev(계획)&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 75.9531px;&quot;&gt;Prod(계획)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 62.9844px;&quot;&gt;서버 구성&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;Stand Alone&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 80.4688px;&quot;&gt;Stand Alone&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;Stand Alone&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 75.9531px;&quot;&gt;Replica Set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 62.9844px;&quot;&gt;VPC&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;Public&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 80.4688px;&quot;&gt;Public&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;Private&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 75.9531px;&quot;&gt;Private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 62.9844px;&quot;&gt;Sub User&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 80.4688px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 78.7969px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 75.9531px;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 사용하고 있던 AWS EC2를 사용하기로 했기에 APP 서버는 따로 구성하지 않았습니다.&lt;br /&gt;하지만 이 경우 VPC를 public으로 생성할 수밖에 없었습니다.&lt;/li&gt;
&lt;li&gt;sharding 은 아니더라도 arbiter를 포함한 replica set을 구성하려고 했으나, NCP에서는 public 구성의 경우 stand alone 만이 지원되었습니다. &lt;br /&gt;이미 운영 중인 EC2를 옮기거나, 별도 운영은 공수가 크다고 판단되어 stand alone으로 구성하게 되었습니다.&lt;/li&gt;
&lt;li&gt;public VPC를 생성하고, ACG (AWS의 보안 그룹)의 inbound 규칙을 회사 IP와 변경한 포트로 적용했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud DB for MongoDB 는 나온 지 얼마 안 되어서인지 지원되지 않는 서비스가 있어 아쉬웠지만, 퀵스타트가 잘 돼있어서 구성은 생각보다 쉽게 되었습니다. (대신 비용은..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후속 작업은 블로그에 쓰기보다는 내부 지식 공유도 겸하기 위해서 docker를 통한 세팅과 튜토리얼을 만들어 봤습니다.&lt;br /&gt;새로 시작하는 아이템이라서 애정도 가고, 잘돼서 mongoDB 를 좀 더 적극적으로 써봤으면 좋겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/mongodb&quot;&gt;https://github.com/Hyune-c/mongodb&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study</category>
      <category>mongodb</category>
      <category>NCP</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/13</guid>
      <comments>https://hyune-c.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 18 May 2021 00:02:19 +0900</pubDate>
    </item>
    <item>
      <title>기술 '잘' 선택하기</title>
      <link>https://hyune-c.tistory.com/12</link>
      <description>&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;최근 회사에 DB 형상 관리 라이브러리를 새로 적용할 일이 생겼습니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;익숙한 것은 flyway 였지만 요즘은 오픈소스가 워낙에 많고, 내가 모르는 더 좋은 라이브러리가 있지 않을까?&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하는 생각에 찾아보기 시작했고, 그 과정이 생각보다 재미있었기에 글을 작성해봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;/article&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면 flyway를 선택했습니다.&lt;br /&gt;이하에서는 라이브러리 대신 기술이라는 단어를 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술을 검색하기 시작했을 때 제가 알던 키워드는 형상관리, flyway 단 2개였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;1-구글-검색하기&quot; data-ke-size=&quot;size26&quot;&gt;1. 구글 검색하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;661&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biX8gV/btrnX0ibSkp/kK0eX4X5QnKNbS2TrviqS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biX8gV/btrnX0ibSkp/kK0eX4X5QnKNbS2TrviqS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biX8gV/btrnX0ibSkp/kK0eX4X5QnKNbS2TrviqS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiX8gV%2FbtrnX0ibSkp%2FkK0eX4X5QnKNbS2TrviqS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;851&quot; height=&quot;661&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;661&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발의 알파이자 오메가 구글 검색입니다.&lt;br /&gt;1 페이지에 모두 같은 기술로 도배되어있기에 사실상 여기에서 flyway를 선택하고 끝내도 이상하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIBcFL/btrnUPoENoB/32N8lvtv5mrZXEFGk0eeY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIBcFL/btrnUPoENoB/32N8lvtv5mrZXEFGk0eeY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIBcFL/btrnUPoENoB/32N8lvtv5mrZXEFGk0eeY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIBcFL%2FbtrnUPoENoB%2F32N8lvtv5mrZXEFGk0eeY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;699&quot; height=&quot;309&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 관련 검색어를 보면 다른 키워드도 보입니다.&lt;br /&gt;조금 더 검색해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;2-stackshare&quot; data-ke-size=&quot;size26&quot;&gt;2. stackshare&lt;/h2&gt;
&lt;figure id=&quot;og_1639501747934&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Software and technology stacks used by top companies&quot; data-og-description=&quot;All the best Open Source &amp;amp; Software as a Service (SaaS) tools in one place, ranked by developers and companies using them. Compare and browse tech stacks from thousands of companies and software developers from around the world.&quot; data-og-host=&quot;stackshare.io&quot; data-og-source-url=&quot;https://stackshare.io/&quot; data-og-url=&quot;https://stackshare.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/megfL/hyMG85gb4v/rQ6lPjsPG2JOtIqX4UW73k/img.jpg?width=1425&amp;amp;height=812&amp;amp;face=0_0_1425_812&quot;&gt;&lt;a href=&quot;https://stackshare.io/&quot; data-source-url=&quot;https://stackshare.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/megfL/hyMG85gb4v/rQ6lPjsPG2JOtIqX4UW73k/img.jpg?width=1425&amp;amp;height=812&amp;amp;face=0_0_1425_812');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Software and technology stacks used by top companies&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;All the best Open Source &amp;amp; Software as a Service (SaaS) tools in one place, ranked by developers and companies using them. Compare and browse tech stacks from thousands of companies and software developers from around the world.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackshare.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhp6xU/btrnWSSdCHb/8klTZDUg3FwHg3et5k2EP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhp6xU/btrnWSSdCHb/8klTZDUg3FwHg3et5k2EP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhp6xU/btrnWSSdCHb/8klTZDUg3FwHg3et5k2EP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdhp6xU%2FbtrnWSSdCHb%2F8klTZDUg3FwHg3et5k2EP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;247&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;247&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 뜨는 기술 소개 (?) 사이트입니다.&lt;br /&gt;기술별 카테고리 구분과 연관 비교까지 해줘서 내가 모르던 키워드를 인지하는데 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서도 Liquibase 가 보이네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biCYm5/btrnWSdDvpd/Mv09xJzDmC14mVdvD5KZM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biCYm5/btrnWSdDvpd/Mv09xJzDmC14mVdvD5KZM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biCYm5/btrnWSdDvpd/Mv09xJzDmC14mVdvD5KZM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiCYm5%2FbtrnWSdDvpd%2FMv09xJzDmC14mVdvD5KZM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;350&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack 수도 거의 비슷한 것을 보면, 어쩌면 내가 알지 못하던 liquibase 가 더 뜨고 있고 배워야 할 기술일 수도 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;3-google-trands&quot; data-ke-size=&quot;size26&quot;&gt;3. Google Trands&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tLSft/btrnVxA2dj3/Jmm9qskiZfJjZ8KFaLsyy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tLSft/btrnVxA2dj3/Jmm9qskiZfJjZ8KFaLsyy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tLSft/btrnVxA2dj3/Jmm9qskiZfJjZ8KFaLsyy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtLSft%2FbtrnVxA2dj3%2FJmm9qskiZfJjZ8KFaLsyy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1183&quot; height=&quot;631&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 구글입니다.&lt;br /&gt;5년 추세를 볼 때 flyway 가 7:3 정도로 우세함을 알 수 있습니다.&lt;br /&gt;(솔직히 liquibase 처음 들어봤는데 3이나 차지하다니.. 반성합니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A4rFn/btrnQ6xWgri/dIU9j1ZlqFlwBpk8QqSJs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A4rFn/btrnQ6xWgri/dIU9j1ZlqFlwBpk8QqSJs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A4rFn/btrnQ6xWgri/dIU9j1ZlqFlwBpk8QqSJs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA4rFn%2FbtrnQ6xWgri%2FdIU9j1ZlqFlwBpk8QqSJs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1180&quot; height=&quot;411&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLCY2a/btrnX0JfQ28/MEP1ad45lZnNR6OiKAGvXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLCY2a/btrnX0JfQ28/MEP1ad45lZnNR6OiKAGvXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLCY2a/btrnX0JfQ28/MEP1ad45lZnNR6OiKAGvXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLCY2a%2FbtrnX0JfQ28%2FMEP1ad45lZnNR6OiKAGvXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;465&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌로 보면 5:5이고 flyway는 북미, 중국에서 주로 쓰이는 것에 비해, liquibase는 유럽권에서 많이 쓰이고 있음을 알 수 있습니다.&lt;br /&gt;개인적으로 신기술을 적용함에 있어 중국, 한국에서 주로 쓰이는 기술보다는 유럽권에서 쓰이는 기술을 선호하기에&lt;br /&gt;이쯤 되면 liquibase를 선택하는 것도 괜찮겠다는 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;4-github-repository&quot; data-ke-size=&quot;size26&quot;&gt;4. Github Repository&lt;/h2&gt;
&lt;figure id=&quot;og_1639501783289&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - flyway/flyway: Flyway by Redgate &amp;bull; Database Migrations Made Easy.&quot; data-og-description=&quot;Flyway by Redgate &amp;bull; Database Migrations Made Easy. - GitHub - flyway/flyway: Flyway by Redgate &amp;bull; Database Migrations Made Easy.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/flyway/flyway&quot; data-og-url=&quot;https://github.com/flyway/flyway&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bAvj9g/hyMH9VLSti/LRGHvoisTp2D0MLiDX12K0/img.png?width=640&amp;amp;height=320&amp;amp;face=0_0_640_320&quot;&gt;&lt;a href=&quot;https://github.com/flyway/flyway&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/flyway/flyway&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bAvj9g/hyMH9VLSti/LRGHvoisTp2D0MLiDX12K0/img.png?width=640&amp;amp;height=320&amp;amp;face=0_0_640_320');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - flyway/flyway: Flyway by Redgate &amp;bull; Database Migrations Made Easy.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flyway by Redgate &amp;bull; Database Migrations Made Easy. - GitHub - flyway/flyway: Flyway by Redgate &amp;bull; Database Migrations Made Easy.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1639501799459&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - liquibase/liquibase: Main Liquibase Source&quot; data-og-description=&quot;Main Liquibase Source. Contribute to liquibase/liquibase development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/liquibase/liquibase&quot; data-og-url=&quot;https://github.com/liquibase/liquibase&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dNUhgu/hyMH1KbT2M/aA9WThbQ5iH5zE6CqDnouK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/liquibase/liquibase&quot; data-source-url=&quot;https://github.com/liquibase/liquibase&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dNUhgu/hyMH1KbT2M/aA9WThbQ5iH5zE6CqDnouK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - liquibase/liquibase: Main Liquibase Source&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Main Liquibase Source. Contribute to liquibase/liquibase development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 정말 많은 라이브러리들이 오픈소스로 공개되어 있고, README 가 작성되어 있습니다.&lt;br /&gt;기술적인 면에서는 오히려 공식 홈페이지보다 더 많은 정보를 얻을 수도 있죠.&lt;br /&gt;제가 레포지토리에서 주로 보는 항목은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Star 개수, 최근 커밋 일시, 최근 이슈 작성 일시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교해본 결과 liquibase의 레포지토리의 활성도가 더 높았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;시작은 flyway로 했지만 점점 찾아볼수록 liquibase로 선택이 기울어가고 있습니다.&lt;br /&gt;하지만 결론적으로 flyway를 선택하게 되었습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;5-실무&quot; data-ke-size=&quot;size26&quot;&gt;5. 실무&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글의 서두에 썼다시피 이런 과정을 거친 이유는 회사 할 기술을 찾기 위해서입니다.&lt;br /&gt;그리고 회사에 적용하기 위해서는 성능도 중요하지만 다른 생각해야 될 것들도 많죠.&lt;/p&gt;
&lt;h4 id=&quot;레퍼런스의-접근성&quot; data-ke-size=&quot;size20&quot;&gt;레퍼런스의 접근성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 성숙된 기술이니만큼 충분한 레퍼런스가 있지만, 한글화 자료는 상대적으로 flyway 가 훨씬 많았습니다.&amp;nbsp;&lt;s&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;영어 잘하고 싶다.&lt;/span&gt;&lt;/s&gt;&lt;/p&gt;
&lt;h4 id=&quot;익숙함--러닝-커브&quot; data-ke-size=&quot;size20&quot;&gt;익숙함 &amp;amp; 러닝 커브&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 좋은 기술이라도 내가 속한 조직이 그 기술에 익숙하지 않고, 성능이 그것을 습득하는데 걸리는 수고를 압도할 수 없다면 선택을 위한 설득력이 없습니다.&lt;/p&gt;
&lt;h4 id=&quot;팀장님-오더&quot; data-ke-size=&quot;size20&quot;&gt;&lt;s&gt;팀장님 오더&lt;/s&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;6-그외&quot; data-ke-size=&quot;size26&quot;&gt;6. 그 외&lt;/h2&gt;
&lt;h4 id=&quot;버전-선택&quot; data-ke-size=&quot;size20&quot;&gt;버전 선택&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b29aU0/btrnV5YAEOq/nrkv8tTzl0AtSdyTUfKYL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b29aU0/btrnV5YAEOq/nrkv8tTzl0AtSdyTUfKYL1/img.png&quot; data-alt=&quot;https://mvnrepository.com/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b29aU0/btrnV5YAEOq/nrkv8tTzl0AtSdyTUfKYL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb29aU0%2FbtrnV5YAEOq%2Fnrkv8tTzl0AtSdyTUfKYL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;595&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;595&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://mvnrepository.com/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring을 사용하는 프로젝트는 대부분 maven이나 gradle을 사용하기에 쉽게 찾을 수 있습니다.&lt;br /&gt;개인적으로는 최신 버전보다는 stable에서도 1~2 단계 아래 버전을 사용하곤 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1639501857869&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Boot Reference Documentation&quot; data-og-description=&quot;The reference documentation consists of the following sections: Legal Legal information. Getting Help Resources for getting help. Documentation Overview About the Documentation, First Steps, and more. Getting Started Introducing Spring Boot, System Require&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/index.html&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/index.html&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/index.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('&amp;quot;&amp;quot;');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot Reference Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The reference documentation consists of the following sections: Legal Legal information. Getting Help Resources for getting help. Documentation Overview About the Documentation, First Steps, and more. Getting Started Introducing Spring Boot, System Require&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;51&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH04SZ/btrnVaTDots/AEPixRcneFatbRPvGxqwXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH04SZ/btrnVaTDots/AEPixRcneFatbRPvGxqwXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH04SZ/btrnVaTDots/AEPixRcneFatbRPvGxqwXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH04SZ%2FbtrnVaTDots%2FAEPixRcneFatbRPvGxqwXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;51&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;51&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RVIDh/btrnQj5lkEj/J984n1Yyx9b4aXEsolrXkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RVIDh/btrnQj5lkEj/J984n1Yyx9b4aXEsolrXkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RVIDh/btrnQj5lkEj/J984n1Yyx9b4aXEsolrXkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRVIDh%2FbtrnQj5lkEj%2FJ984n1Yyx9b4aXEsolrXkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;716&quot; height=&quot;53&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 버전의 직접 명시보다는 Spring Boot의 검증된 라이브러리 버전 관리를 사용하는 것을 추천합니다.&lt;br /&gt;대부분의 성숙된 라이브러리는 이미 Spring에서 관리를 해주고 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 Spring에서 관리해주지 않는 기술이라면 이전의 단계를 모두 통과하였더라도 보수적으로 생각해볼 것을 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;마무리&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폐쇄적이고 유료이던 라이브러리, 설루션 시장이 점점 오픈되면서 이렇게 기술을 선택하고 설득하는 과정도&lt;br /&gt;개발자의 역할 중 하나라고 생각합니다.&lt;br /&gt;예시는 영향도가 적은 형상 관리 툴이기에 이 정도의 조사로도 문제가 없겠지만,&lt;br /&gt;영향도가 큰 기술을 선택함에 있어서는 벤치마크와 같은 성능 테스트도 추가해서 설득력을 높여야 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 flyway를 샘플 구현해본 레포지토리를 남깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Hyune-c/simple-flyway&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Hyune-c/simple-flyway&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Series/실전!</category>
      <category>library</category>
      <category>opensource</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/12</guid>
      <comments>https://hyune-c.tistory.com/12#entry12comment</comments>
      <pubDate>Fri, 7 May 2021 18:58:48 +0900</pubDate>
    </item>
    <item>
      <title>API Versioning 전략</title>
      <link>https://hyune-c.tistory.com/11</link>
      <description>&lt;article class=&quot;markdown-body entry-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;API Versioning은 불특정 다수의 컨슈머가 접속하는 OpenAPI를 지원하기 위해 필수적인 요소 중 하나로, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;설령 컨슈머가 한 곳이라고 하더라도 원활한 적용과 장애 시간 최소화를 기대할 수 있습니다. (물론 훌륭한 배포 전략을 준비하면 더 좋습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;/article&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;아래는 &lt;b&gt;&lt;u&gt;일상 속 사물이 알려주는 웹 API 디자인&lt;/u&gt;&amp;nbsp;&lt;/b&gt;을 읽고 해결한 고민과 경험을 기록해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;브레이킹 체인지 - 호환성을 지원하지 않게 되는 변경 (특히 하위 호환성)&lt;br /&gt;컨슈머 - API 를 사용하는 클라이언트 (프론트)&amp;nbsp;&amp;nbsp;&lt;br /&gt;프로바이더 - API를 제공하는 서버 (백엔드)&lt;/span&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1641384585852&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;일상 속 사물이 알려주는 웹 API 디자인 - YES24&quot; data-og-description=&quot;웹 API는 새로운 서비스나 앱을 만들 때 기존에 존재하던 서비스가 제공하는 기능을 활용할 수 있도록 해준다. 굳이 기존 서비스에 대한 자세한 소스 코드를 알지 않더라도 개발자가 만드는 프로&quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;http://www.yes24.com/Product/Goods/94462254&quot; data-og-url=&quot;http://www.yes24.com/Product/Goods/94462254&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dB3csX/hyMYTsW3op/lS5hCHuQNAz4wsO3jwpf3k/img.jpg?width=959&amp;amp;height=1200&amp;amp;face=0_0_959_1200,https://scrap.kakaocdn.net/dn/bND7Ft/hyMZNq4d5T/KgcMiIDcqbkvzGkjG6qQmk/img.jpg?width=959&amp;amp;height=1200&amp;amp;face=0_0_959_1200&quot;&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/94462254&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://www.yes24.com/Product/Goods/94462254&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dB3csX/hyMYTsW3op/lS5hCHuQNAz4wsO3jwpf3k/img.jpg?width=959&amp;amp;height=1200&amp;amp;face=0_0_959_1200,https://scrap.kakaocdn.net/dn/bND7Ft/hyMZNq4d5T/KgcMiIDcqbkvzGkjG6qQmk/img.jpg?width=959&amp;amp;height=1200&amp;amp;face=0_0_959_1200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;일상 속 사물이 알려주는 웹 API 디자인 - YES24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹 API는 새로운 서비스나 앱을 만들 때 기존에 존재하던 서비스가 제공하는 기능을 활용할 수 있도록 해준다. 굳이 기존 서비스에 대한 자세한 소스 코드를 알지 않더라도 개발자가 만드는 프로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;versioning-전략&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Versioning 전략&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 제공하다 보면 &lt;b&gt;브레이킹 체인지를 만나는 것은&amp;nbsp;필연적&lt;/b&gt;입니다.&lt;br /&gt;다만 운영 방법에 따라 컨슈머의 인지를 돕고, 프로바이더는 더 적은 변화의 개발을 기대할 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 280px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;v1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;v2&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;&lt;b&gt;경로&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;GET /v1/transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;GET /v2/transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;&lt;b&gt;도메인&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;GET /transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;GET /transfers&lt;br /&gt;Host: apiv2.bankingcompany.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;&lt;b&gt;쿼리 파라미터&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;GET /transfers?version=1&lt;br /&gt;Host: api.bankingcompany.com&lt;/td&gt;
&lt;td style=&quot;height: 36px;&quot;&gt;GET /transfers?version=2&lt;br /&gt;Host: api.bankingcompany.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;&lt;b&gt;커스텀 헤더&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;GET /transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;br /&gt;Version: 1&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;GET /transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;br /&gt;Version: 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;&lt;b&gt;콘텐츠 네고시에이션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;GET /transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;br /&gt;Accept: application/vnd.bank.1+json&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;GET /transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;br /&gt;Accept: application/vnd.bank.2+json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;&lt;b&gt;컨슈머의 설정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;GET /transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;br /&gt;Authorization: Bearer 4R57TD78&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;GET /transfers&lt;br /&gt;Host: api.bankingcompany.com&lt;br /&gt;Authorization: Bearer 4R57TD78&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;선호하지 않는 버저닝 전략&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인 전략과 쿼리 파라미터 전략은 가장 선호하지 않는 전략입니다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 버저닝 전략은 작은 변경에도 도메인이라는 비교적 큰 단위의 변경이 필요합니다.&lt;/li&gt;
&lt;li&gt;쿼리 파라미터 버저닝 전략은 패스와 파라미터의 역할을 침범한다고 생각합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커스텀 헤드 전략과 콘텐츠 네고시에이션은 좀 더 RESTful 한 설계가 가능하기에 좋아 보였습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;하지만 RESTful 함보다는 컨슈머 친화적인 설계를 지향하기에 선택하지 않았습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨슈머의 사용이 어렵고, 무엇보다 변경을 인지하는 것이 어렵습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(드물지만) 헤더 사용이 제한된 컨슈머가 존재할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내부용으로만 사용된다면 충분히 좋은 선택이라고 생각합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨슈머의 설정 전략은 컨슈머 친화적이지만 프로바이더에서 고려할 것이 너무 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;경로 버저닝 전략&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저는 경로 전략을 추천합니다.&lt;/b&gt;&lt;br /&gt;그리고 API 사용에 대한 철학을 포함하여 단점을 보완합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의미 있는 에러 메시지를 구현합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Deprecate 된, 또는 예정을 알려줄 수 있습니다.&lt;/li&gt;
&lt;li&gt;응답 코드를 활용하는 것도 좋습니다. (301 Moved Permanently, 302 Found)&lt;/li&gt;
&lt;li&gt;HATEOAS를 구현하는 경우 link를 고려합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;버저닝은 리소스 단위로 관리합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비록 일부 API는 브레이킹 체인지가 없다고 하더라도, 컨슈머의 직관적인 인지를 돕습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하위 호환성을 고려합니다.&lt;/li&gt;
&lt;li&gt;낱개의 필드보다는 네이밍 된 객체를 사용합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null 인 객체/필드는 내려보내지 않음으로써 컨슈머가 옵셔널 처리를 할 수 있도록 유도합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단순 조회성 API 라면 요건 변경에 따른 필드는 추가하되 기존의 필드도 남겨두는 것을 고려합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(비록 response의 크기는 커지지만) 일부 값의 부재로 생기는 전체 에러를 방지하고, 나머지는 정상적으로 보여줄 수 있습니다.&lt;/li&gt;
&lt;li&gt;단 컨슈머가 응답으로 추가적인 연산을 하는 경우는 안됩니다. 오히려 의도하지 않은 문제가 발생하고 추적이 더 어려워집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변경이 잦은 리소스는 분리를 고려합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경이 적은 핵심 리소스와 변경이 많은 유동적인 리소스로 분리합니다. 또는 재설계합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컨슈머에게 친절한 API를 만듭니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨슈머가 넣어야 하는 param에 디폴트 값을 넣어줍니다.&lt;/li&gt;
&lt;li&gt;하위 리소스가 유일할 경우 id를 생략합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 영화에는 별점이 1개만 존재합니다.&lt;/li&gt;
&lt;li&gt;Good - PUT /movie/{movieId}/rating&lt;/li&gt;
&lt;li&gt;Bad - PUT /movie/{movieId}/rating/{ratingId}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;후기&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;후기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹자는 원칙과 기술에 입각한 설계와 개발만이 서비스의 수명을 길게 해 준다고 말합니다. &lt;br /&gt;하지만 발전하는 기술 앞에 저는 항상 비숙련자이고, 컨슈머의 숙련도는 예측할 수 없습니다. 그리고 우리의 시간은 항상 부족하며 요건은 계속해서 변하기에 완벽함을 보증하는 것은 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 &lt;b&gt;최선을 학습하되 현재의 상황에 맞는 선택으로 트레이드오프 해야 한다&lt;/b&gt;고 생각합니다.&lt;/p&gt;</description>
      <category>Study</category>
      <category>API</category>
      <category>versioning</category>
      <category>브레이킹 체인지</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/11</guid>
      <comments>https://hyune-c.tistory.com/11#entry11comment</comments>
      <pubDate>Wed, 14 Apr 2021 16:59:53 +0900</pubDate>
    </item>
    <item>
      <title>토끼책을 읽고 - 메시지를 믿을 수 있을까? (2)</title>
      <link>https://hyune-c.tistory.com/8</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;[v4] 조금 다른 예를 들어보겠습니다.&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;/*
  1. 제품을 만들기 위한

  부품 (인자로서 사용됨을 표시하기 위해 Dto 표기)
 */
@Setter
@Getter
@NoArgsConstructor
public class PartDto {

  // 모든 곳에서 필요합니다.
  private Boolean steal;

  // 컴퓨터와 핸드폰에서 필요합니다.
  private Boolean semiconductor;

  // 선풍기에서 필요합니다.
  private Boolean propellers;

  // 선풍기와 핸드폰에서 필요합니다.
  private Boolean button;
}

/*
  1. 제품을 만들기 위한 부품이 있는지 확인하고

  있으면 만드는 서비스
 */
public class ProductService {

  public boolean createComputer(PartDto partDto) {
    if (partDto.getSteal() &amp;amp;&amp;amp; partDto.getSemiconductor()) {
      // do work...
      return true;
    }

    return false;
  }

  public boolean createFan(PartDto partDto) {
    if (partDto.getSteal() &amp;amp;&amp;amp; partDto.getPropellers() &amp;amp;&amp;amp; partDto.getButton()) {
      // do work...
      return true;
    }

    return false;
  }

  public boolean createPhone(PartDto partDto) {
    if (partDto.getSteal() &amp;amp;&amp;amp; partDto.getSemiconductor() &amp;amp;&amp;amp; partDto.getButton()) {
      // do work...
      return true;
    }

    return false;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으악! 요즘 누가 이렇게 코드를 짜나!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;s&gt;하지만 종종 이런 코드를 보곤 하죠&lt;/s&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상적인 프로그래밍과는 조금 거리가 있지만 Dto를 공통적으로 쓰고, Dto 생성 후 부분적으로 Setter 사용, (전 글에서 나온 것과 같이) 메시지 인자를 수신받는 곳에서 검증합니다. 만약 제품이 늘어난다면 검증 로직을 어디에 만들어야 될까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[v5] 저는 검증 로직을 모으는 것을 선호합니다.&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;/*
  1. 제품을 만들기 위한

  부품 (인자로서 사용됨을 표시하기 위해 Dto 표기)
 */
@Setter
@Getter
@NoArgsConstructor
public class PartDto {

  public enum PartOf {
    COMPUTER,
    FAN,
    PHONE,
    UNKNOWN
  }

  private PartDto.PartOf partOf;

...

  public boolean naming(PartDto.PartOf partOf) {
    if (partOf.equals(PartOf.COMPUTER) &amp;amp;&amp;amp; this.getSteal() &amp;amp;&amp;amp; this.getSemiconductor()) {
      this.partOf = PartOf.COMPUTER;
    }

    if (partOf.equals(PartOf.FAN) &amp;amp;&amp;amp; this.getSteal() &amp;amp;&amp;amp; this.getPropellers() &amp;amp;&amp;amp; this.getButton()) {
      this.partOf = PartOf.FAN;
    }

    if (partOf.equals(PartOf.PHONE) &amp;amp;&amp;amp; this.getSteal() &amp;amp;&amp;amp; this.getSemiconductor() &amp;amp;&amp;amp; this.getButton()) {
      this.partOf = PartOf.PHONE;
    }

    if (this.partOf == null) {
      this.partOf = PartOf.UNKNOWN;
      return false;
    }

    return true;
  }
}

/*
  1. 제품을 만들기 위한 부품이 있는지 확인하고

  있으면 만드는 서비스
 */
public class ProductService {

  public boolean create(PartDto partDto) {
    // do work...
    return true;
  }
}

/*
  1. 제품을 만드는 서비스를 호출하는

  객체
 */
@RequiredArgsConstructor
public class Factory {

  private final ProductService productService;

  public void createProduct() {
    PartDto part1 = new PartDto();
    part1.setSteal(true);
    part1.setPropellers(true);
    part1.setButton(true);

    if (part1.naming(PartOf.FAN)) {
      System.out.println(&quot;part1 로 FAN 만들기: &quot; + productService.create(part1));
    }

    PartDto part2 = new PartDto();
    part2.setSteal(true);
    part2.setPropellers(true);
    part2.setButton(true);

        // 검증에 실패해 제품 생산을 위한 요청 (메시지) 를 보내지 않습니다.
    if (part2.naming(PartOf.COMPUTER)) {
      System.out.println(&quot;part2 로 COMPUTER 만들기: &quot; + productService.create(part2));
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JuNT2/btqZEA87mVv/DnjeLCLwUcrxDPDrN8HrR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JuNT2/btqZEA87mVv/DnjeLCLwUcrxDPDrN8HrR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JuNT2/btqZEA87mVv/DnjeLCLwUcrxDPDrN8HrR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJuNT2%2FbtqZEA87mVv%2FDnjeLCLwUcrxDPDrN8HrR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;266&quot; height=&quot;117&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Dto로 모으는 것을 선호합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 객체에 역할이 있는 것처럼, 메시지도 역할을 줄 수 있다.
2. 메시지를 보내는 것도 비용이다. 사전에 검증되어 전달된 메시지는 신뢰한다. (내부용 로직일시에만)
3. 비슷한 성격의 로직만 모음으로서 생각의 범위를 줄일 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 다른 해결 방법도 여러 가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dto를 더 분류할 수도 있고, 검증만을 하는 서비스를 만들 수도 있고, 만약 API 호출 형태였다면 수신 측에서 검증하는 전 구조가 더 나았겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇이 되었든 중요한 건 약속된 구조로 레이어를 나누고 팀에 공유하는 것이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 지속 가능한 개발이니까요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 글을 쓰게 된 계기는 대가의 이론과 내 능력 사이의 괴리, 거기서 오는 설득력의 부족, 배움이 필요한 기술과 한정된 일정에서 오는 이런저런 안 좋은 생각 중에 문득 눈에 들어와서였습니다. 만약 대가의 이론이기에 문자 그대로를 적용해야 된다는 게 기저에 있다면, 재해석과 설득은 너무 힘든 과정이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 옛날이었으면 무심코 지나쳐갔을 내용을 코드로 설명할 수 있는 능력이 생겼고, 내용을 정리하는 과정에서 나쁜 생각은 많이 사라진 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속 가능한 개발을 유지하되 그 기준을 상향시키자!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;s&gt;회고의 결론은 항상 같은 느낌.&lt;/s&gt;&lt;/h4&gt;</description>
      <category>Study</category>
      <category>객체지향의 사실과 오해</category>
      <category>검증</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/8</guid>
      <comments>https://hyune-c.tistory.com/8#entry8comment</comments>
      <pubDate>Mon, 8 Mar 2021 22:14:16 +0900</pubDate>
    </item>
    <item>
      <title>토끼책을 읽고 - 메시지를 믿을 수 있을까? (1)</title>
      <link>https://hyune-c.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 회사에서는 객체 지향적인&amp;nbsp;설계보다는&amp;nbsp;일단 되는 프로그램을 만드는 게 먼저였는데, 이번에 옮긴 회사에서는 객체 지향에 대한 투자를 많이 하는 것 같습니다. 이론의 부족함을 느꼈기에 이전에 읽으려다 만 토끼 책 (객체지향의 사실과 오해 - 조용호 저)을 읽어봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;역할, 책임 &amp;amp; 메시지, 협력&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 좋은 내용이고 이전에 구현한 &lt;s&gt;어설픈&lt;/s&gt;&amp;nbsp;OOP, ORM에 대한 고민을 해결해줬지만, 하나가 계속 걸려서 이 글을 쓰게 되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메시지를 믿을 수 있을까?&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;책에서 언급되지 않은 부분은 각색하고, 관련된 코드의 브랜치를 스텝별로 대괄호 표시했습니다. 
https://github.com/Hyune-c/Trust-Message/tree/master&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[v1] 책에서는 판사-토끼-증인을 객체로서 책임과 메시지를 설명하고 있습니다.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KviMG/btqZyv1M2fG/PkfY8WLtrSN1EJKEXZHNCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KviMG/btqZyv1M2fG/PkfY8WLtrSN1EJKEXZHNCk/img.png&quot; data-alt=&quot;메시지 (메소드) 를 통해 재판이 진행됩니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KviMG/btqZyv1M2fG/PkfY8WLtrSN1EJKEXZHNCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKviMG%2FbtqZyv1M2fG%2FPkfY8WLtrSN1EJKEXZHNCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;281&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메시지 (메소드) 를 통해 재판이 진행됩니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/*
  1. 전달자 - 토끼를 알고 있고
  2. 증언이 필요하지만 아직 증인이 누구인지 모른채로
  3. 재판을 진행할 책임이 있는

  판사
 */
public class Judge {

    ...
  // 1. 재판하라.
  public String doJudge() {
    // 2. 목격자를 불러오라.
    witness = communicator.findWitness();
    // 4. 증언하라.
    // 증언할 것이 있으면 유죄. 없으면 무죄.
    return (witness.doTestimony().isEmpty())
        ? &quot;Innocence&quot;
        : &quot;Guilty&quot;;
  }
}

@Test
public void 정상_재판하라() {
  // given
  String testimony = &quot;저는 보잘 것 없는 사람입니다.&quot;;
  // 1. 증언할 말이 있는 증인 - 모자장수
  Witness madHatter = new Witness(testimony);
  // 2. 모자장수를 알고 있는 전달자 - 토끼
  Communicator rabbit = new Communicator(madHatter);
  // 3. 토끼를 알고 있는 판사 - 왕
  Judge king = new Judge(rabbit);

  // when
  // 4. 재판하라!
  String judgeResult = king.doJudge();

  // then
  System.out.println(judgeResult);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[v2] 증언 방식이 바뀌었습니다!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판사는 증인에게&amp;nbsp;언제, 어디서&amp;nbsp;본 것인지 증언하라고 합니다. (메시지 인자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 증인은&amp;nbsp;어제 광장의 일을 증언할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjbiar/btqZBdmhPGh/DYW5FZL00SGtzEabkKhhI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjbiar/btqZBdmhPGh/DYW5FZL00SGtzEabkKhhI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjbiar/btqZBdmhPGh/DYW5FZL00SGtzEabkKhhI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcjbiar%2FbtqZBdmhPGh%2FDYW5FZL00SGtzEabkKhhI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;268&quot; height=&quot;140&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Judge {
    ...
  // 1. 재판하라.
  public String doJudge() {
    // 2. 목격자를 불러오라.
    witness = communicator.findWitness();
    // 4. 증언하라.
    // 증언할 것이 있으면 유죄. 없으면 무죄.
    String testimonyKey = &quot;어제왕국&quot;;
    String testimony = witness.doTestimony(testimonyKey);
    System.out.println(testimony);
    return (testimony.isEmpty())
        ? &quot;Innocence&quot;
        : &quot;Guilty&quot;;
  }
}

@Test
  public void 정상_재판하라() {
    // given
    Map&amp;lt;String, String&amp;gt; testimonyMap = Map.of(
        &quot;어제왕국&quot;, &quot;엘리스를 보았어요!&quot;,
        &quot;오늘광장&quot;, &quot;저는 보잘 것 없는 사람입니다.&quot;);
    // 1. 증언할 말이 있는 증인 - 모자장수
    Witness madHatter = new Witness(testimonyMap);
    // 2. 모자장수를 알고 있는 전달자 - 토끼
    Communicator rabbit = new Communicator(madHatter);
    // 3. 토끼를 알고 있는 판사 - 왕
    Judge king = new Judge(rabbit);

    // when
    // 4. `어제왕국` 의 일을 재판하라!
    String judgeResult = king.doJudge();

    // then
    System.out.println(judgeResult);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[v3] 이번에는&amp;nbsp;어제 분수의 일을 물었습니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불행히도 증인은&amp;nbsp;어제 분수의 일을 알지 못합니다! (NPE...?)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5MDYS/btqZBdGCNGO/0qAqYPWJsyxCdYZ92nXq6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5MDYS/btqZBdGCNGO/0qAqYPWJsyxCdYZ92nXq6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5MDYS/btqZBdGCNGO/0qAqYPWJsyxCdYZ92nXq6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5MDYS%2FbtqZBdGCNGO%2F0qAqYPWJsyxCdYZ92nXq6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;183&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요지는 '메시지를 묻기 전에 (증언하라) 메시지를 처리할 수 있는지 (증언할 수 있는지)를 확인하는 것'입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 책에서는 이미 답을 주었습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;메시지를 수신받은 객체는 우선 자신이 해당 메시지를 처리할 수 있는지 확인한다. 148p&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 인자를 수신받은 객체인 증인이 검증하도록 코드를 수정해, 증언이 없는 이 재판은 무죄입니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bthbKy/btqZFuHCnlh/Ol390fm5xa1P4RiZGaLZq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bthbKy/btqZFuHCnlh/Ol390fm5xa1P4RiZGaLZq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bthbKy/btqZFuHCnlh/Ol390fm5xa1P4RiZGaLZq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbthbKy%2FbtqZFuHCnlh%2FOl390fm5xa1P4RiZGaLZq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;256&quot; height=&quot;143&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Witness{
...
public String doTestimony(String testimonyKey) {
return(testimonyMap.containsKey(testimonyKey))
        ? testimonyMap.get(testimonyKey)
        : &quot;&quot;;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 수신받은 객체에서 일을 하는 것으로 행복하게 끝났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만..&lt;/b&gt;&lt;/p&gt;</description>
      <category>Study</category>
      <category>객체지향의 사실과 오해</category>
      <category>검증</category>
      <author>Hyunec</author>
      <guid isPermaLink="true">https://hyune-c.tistory.com/7</guid>
      <comments>https://hyune-c.tistory.com/7#entry7comment</comments>
      <pubDate>Mon, 8 Mar 2021 21:51:55 +0900</pubDate>
    </item>
  </channel>
</rss>