<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>TaxFree</title>
    <link>https://cotak.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 11 Jul 2026 08:12:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코택</managingEditor>
    <image>
      <title>TaxFree</title>
      <url>https://tistory1.daumcdn.net/tistory/4426357/attach/8d8efe890e324fffb1c9f6dbc86f0eb6</url>
      <link>https://cotak.tistory.com</link>
    </image>
    <item>
      <title>[ATDD] 인수테스트 주도 개발에 대한 짧은 고찰</title>
      <link>https://cotak.tistory.com/383</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 인수테스트의 의미&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명세나 계약의 요구사항이 충족되는지를 확인하는 테스트&lt;/li&gt;
&lt;li&gt;작업을 종료시켜도 되는지 검증하는 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 인수테스트 주도 개발의 의미와 목적&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인수테스트 주도 개발은 기획, 개발, 테스트 등 다양한 관점을 가진 팀원들과 협업을 위한 애자일 방법론 중 하나이다&lt;/li&gt;
&lt;li&gt;다양한 관점(기획, 개발, 테스트 등)을 지닌 팀원이 함께 사전 기획 단계에서 유저스토리와 인수 기준을 도출하면, 개발단계에서 자동화된 인수테스트를 먼저 작성하고 소프트웨어 개발을 이어나간다&lt;/li&gt;
&lt;li&gt;프로젝트에 참여하는 인원들이 다양한 관점(기획, 개발, 테스트 등)을 지닌 만큼, 커뮤니케이션이 부족하다면 서로 다른 결과물을 상상하면서 작업을 진행할 수 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경우 결과물이 나오는 시점에야 서로의 생각이 다름을 인지하게 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인수테스트 주도 개발은 이러한 문제를 사전에 방지하고, 프로젝트 참여 인원 간에 이해를 공유하면서 안정적인 소프트웨어를 제공하는 것을 목적으로 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 테스트 주도 개발 vs 인수테스트 주도 개발&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 주도 개발은 비교적 작은 요구사항을 검증하는 테스트 코드를 먼저 만들고, 그것을 구현하는 코드를 작성한 뒤 리팩토링을 수행한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실패하는 테스트 작성 (RED)&lt;/li&gt;
&lt;li&gt;테스트를 통과하는 최소한의 구현 (GREEN)&lt;/li&gt;
&lt;li&gt;리팩토링 (REFACTOR)&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;431&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNgSwg/btsHaWQojqx/COQ2VWTz2E3QPKNHooynG0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNgSwg/btsHaWQojqx/COQ2VWTz2E3QPKNHooynG0/img.jpg&quot; data-alt=&quot;https://boorownie.github.io/3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNgSwg/btsHaWQojqx/COQ2VWTz2E3QPKNHooynG0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNgSwg%2FbtsHaWQojqx%2FCOQ2VWTz2E3QPKNHooynG0%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;431&quot; height=&quot;282&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://boorownie.github.io/3&lt;/figcaption&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;b&gt;시나리오 레벨을 구현 대상&lt;/b&gt;으로 정하고 인수테스트를 먼저 작성한 후, 그것을 구현하는 코드를 작성해간다,
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실패하는 인수테스트 작성&lt;/li&gt;
&lt;li&gt;테스트를 통과하기 위해 기능 개발 (TDD)&lt;/li&gt;
&lt;li&gt;인수테스트 수행 미치 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oFH15/btsHa4HyT95/NJdoiDnjGNT0oD6X1fjzfk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oFH15/btsHa4HyT95/NJdoiDnjGNT0oD6X1fjzfk/img.jpg&quot; data-alt=&quot;https://boorownie.github.io/3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oFH15/btsHa4HyT95/NJdoiDnjGNT0oD6X1fjzfk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoFH15%2FbtsHa4HyT95%2FNJdoiDnjGNT0oD6X1fjzfk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;277&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://boorownie.github.io/3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 인수테스트 작성&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 개발에서 UI 기반의 인수테스트는 작성 및 수행이 어려우므로, 대개 API 레벨로 인수테스트를 진행한다&lt;/li&gt;
&lt;li&gt;인수테스트는 일반적으로 Given(setup) - When(trigger) - Then(verification)의 구조로 이뤄짐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Given(setup): 시스템의 지정된 상태 (테스트를 위한 환경 구성)&lt;/li&gt;
&lt;li&gt;When(trigger): 동작이나 이벤트가 발생 (테스트 동작 수행)&lt;/li&gt;
&lt;li&gt;Then(verification): 변경된 시스템의 상태나 발생한 출력을 확인 (결과 확인)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1714838686282&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Given 대출되지 않은 도서가 있고
And 시스템에 등록된 사용자가 있을 때
When 사용자가 책을 대출하면
Then 책은 대출된 것으로 표시된다&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보다 구체적인 예시는 아래 링크를 참고하자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://boorownie.github.io/4&quot;&gt;https://boorownie.github.io/4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 실무에 적용해본 후기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 좋은 점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 시나리오를 기반으로 테스트하기 떄문에 전체적인 컴포넌트를 연결하는 통합 테스트가 가능하며, 단위테스트만 작성했을 때보다 코드의 안정성이 높아진다&lt;/li&gt;
&lt;li&gt;한 번 구현해놓으면 유사한 기능은 확장이 용이하다&lt;/li&gt;
&lt;li&gt;인수테스트를 먼저 작성해두면 단위테스트 작성도 용이해진다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 TDD는 작은 기능 단위로 수행되므로, 진행하다보면 개발 목표를 놓칠 때가 있음&lt;/li&gt;
&lt;li&gt;인수테스트를 먼저 작성하고 TDD를 수행하면 방향성 잡기가 보다 수월하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 아쉽거나 어려운 점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에 구현해놓는 게 어려움&lt;/li&gt;
&lt;li&gt;회사 환경에 따라 프로젝트에 참여하는 다양한 직군의 인원들이 함께 인수조건을 도출하는 게 어려울 수 있음&lt;/li&gt;
&lt;li&gt;MSA 환경에서 블랙박스 테스트를 구성하기 어려움
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타 도메인에 대한 C, U, D가 불가능한 상황이 많음&lt;/li&gt;
&lt;li&gt;외부 의존성은 모두 다 Mock 객체로 대체하면?&amp;nbsp;&amp;rarr; 구현이 노출되고 테스트 유지보수가 어려워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>테스트코드</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/383</guid>
      <comments>https://cotak.tistory.com/383#entry383comment</comments>
      <pubDate>Sun, 5 May 2024 01:06:07 +0900</pubDate>
    </item>
    <item>
      <title>직접 Stub 객체 만들어서 유연한 테스트 코드 작성하기</title>
      <link>https://cotak.tistory.com/382</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Mockk 등의 라이브러리 대신 직접 Stub 객체를 만들어서 테스트 코드를 개선한 경험을 공유하고자 한다. 참고로 &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;기존 레거시 코드를 리팩토링 하기 전에 앞서 단위 테스트를 먼저 작성하고 있었고, 이때 의존성을 테스트 대역으로 대체했다. 테스트 코드를 작성하다 보니 애매한 부분이 생겼다. 테스트하려는 코드는 대략 이런 느낌이었고, 다양한 상황을 세팅하고 getUsableCoupons를 호출했을 때 적절한 쿠폰이 반환되는지를 확인하려고 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1711539789620&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
class CouponService(
    // ...
    private val couponRepository: CouponRepository
) {

    fun getUsableCoupons(): List&amp;lt;Coupon&amp;gt; {
        // ...
        val usableCouponIds = getUsableCouponIds(targetCoupon)
        return couponRepository.findAllByCouponIdsIn(usableCouponIds) // 여기를 Stubbing 해야 됨
    }

    private fun getUsableCouponIds(targetCouponIds: List&amp;lt;Long&amp;gt;): List&amp;lt;Long&amp;gt; {
        // 모든 쿠폰 중 사용 가능한 쿠폰만 추리는 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 해당 상품에 사용 가능한 대상이 되는 쿠폰을 모두 구한다.&lt;/li&gt;
&lt;li&gt;쿠폰 중 현재 상품에 대해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사용 가능한 쿠폰번호만 추출&lt;/b&gt;&lt;/span&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;추출한 쿠폰번호를 바탕으로 Repository에서 사용 가능한 쿠폰을 조회&lt;/b&gt;&lt;/span&gt;한다.&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;문제는 여기서 2번과 3번이었다. 그 이유는 &lt;b&gt;쿠폰번호를 추출하는 로직이 private 메서드로 내부 구현으로 감추어져 있었다&lt;/b&gt;. 그걸 Stubbing하자니 테스트 코드 상에서 구현이 너무 깊게 노출되는 문제가 발생한다. 물론 Mock 객체를 쓰는 순간 구현이 노출되는 것은 어쩔 수 없지만, 개인적으로 가능한 한 줄이는 게 가독성이나 유지보수 측면에서 좋다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드와 같은 방식으로 Stubbing을 하게 될 텐데, &lt;b&gt;input으로 어떤 값이 들어갈 지 자체를 정의해주는 게 굉장히 껄끄러운 상황&lt;/b&gt;이었다. 사실 해당 Repository 메서드의 실제 기능은 매우 단순하다. 그냥 입력으로 주어진 쿠폰 번호를 terms 쿼리로 Elastic Search에 날리는 것이다. 즉, 주어진 쿠폰 번호에 대한 쿠폰만 반환해주는 기능이다.&lt;/p&gt;
&lt;pre id=&quot;code_1711540021202&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Input으로 들어가는 쿠폰번호 list가 private 메서드에 의해 추출되는 상황
// Output은 단순히 Input으로 주어지는 쿠폰번호에 해당하는 쿠폰 list
every { couponRepository.findAllByCouponIdsIn(listOf(1L, 2L, 3L, ...)) } returns
    listOf(
        CouponFixture(couponId = 1L),
        CouponFixture(couponId = 2L),
        CouponFixture(couponId = 3L),
    )&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;어떻게 할 지 고민하던 도중 Stub 객체를 직접 만들고, 재사용하는 방법을 떠올렸다. 기존의 Repository의 메서드 인터페이스를 그대로 옮겨 인터페이스를 선언하고, Repository가 이것을 구현하게 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711540595543&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AS-IS
@Repository
class CouponRepository(
    // ...
) {
    fun findAllByCouponIdsIn(couponIds: List&amp;lt;Long&amp;gt;): List&amp;lt;Coupon&amp;gt; {
        //...
    }
}



// TO-BE
interface CouponRepository {
    fun findAllByCouponIdsIn(couponIds: List&amp;lt;Long&amp;gt;): List&amp;lt;Coupon&amp;gt;
}

@Repository
class CouponRepositoryImpl : CouponRepository (
    // ...
) {
    override fun findAllByCouponIdsIn(couponIds: List&amp;lt;Long&amp;gt;): List&amp;lt;Coupon&amp;gt; {
        //...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 테스트 패키지 내에 Stub Repository를 선언하고, 마찬가지로 Stub Repository가 이를 구현하게 한다. 여기선 픽스쳐를 사용했지만, Map을 활용해 동적으로 객체를 넣어주는 방식도 가능하다(Fake).&lt;/p&gt;
&lt;pre id=&quot;code_1711541094478&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class StubCouponRepository: CouponRepository {
    override fun findAllByCouponIdsIn(couponIds: List&amp;lt;Long&amp;gt;): List&amp;lt;Coupon&amp;gt; {
        return couponFixtures.filter { couponIds.contains(it.couponId) }
    }
}&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;테스트 클래스 내에서 Stub 객체를 생성해서 주입해주면 별도의 추가적인 Stubbing 없이 테스트가 가능해진다.&lt;/p&gt;
&lt;pre id=&quot;code_1711541228983&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AS-IS
CouponServiceTest {
    private val couponRepository: CouponRepository = mockk()
    //...
}


// TO-BE
CouponServiceTest {
    private val couponRepository: CouponRepository = StubCouponRepository()
    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>테스트코드</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/382</guid>
      <comments>https://cotak.tistory.com/382#entry382comment</comments>
      <pubDate>Wed, 27 Mar 2024 21:23:43 +0900</pubDate>
    </item>
    <item>
      <title>[트러블슈팅] IntelliJ Gradle Background Tasks 멈추는 문제 해결하기</title>
      <link>https://cotak.tistory.com/381</link>
      <description>&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;IntelliJ에서 gradle build를 수행했는데, 다음과 같이 background task가 멈추는 상황이 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (98).png&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CXPpa/btsFoHft9uY/TeqTvZcGs3euHEa26Na091/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CXPpa/btsFoHft9uY/TeqTvZcGs3euHEa26Na091/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CXPpa/btsFoHft9uY/TeqTvZcGs3euHEa26Na091/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCXPpa%2FbtsFoHft9uY%2FTeqTvZcGs3euHEa26Na091%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1510&quot; height=&quot;200&quot; data-filename=&quot;image (98).png&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 방법을 시도했으나 모두 실패했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계속 기다려보기&lt;/li&gt;
&lt;li&gt;브랜치 바꿔서 빌드해보기&lt;/li&gt;
&lt;li&gt;IntelliJ Repair IDE 실행&lt;/li&gt;
&lt;li&gt;PC 재부팅&lt;/li&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;최후의 수단으로 로컬 터미널에서 직접 빌드해보니 &quot;IDLE&quot;이라는 메시지를 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qNpWR/btsFmjlZaHH/y6HOTZ1BZJkOt3fGisWj6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qNpWR/btsFmjlZaHH/y6HOTZ1BZJkOt3fGisWj6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qNpWR/btsFmjlZaHH/y6HOTZ1BZJkOt3fGisWj6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqNpWR%2FbtsFmjlZaHH%2Fy6HOTZ1BZJkOt3fGisWj6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;182&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;182&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;뭔가 gradle 프로세스가 유휴상태에 빠진 것으로 보여서 공식 문서에 &quot;gradle IDLE&quot;이라고 검색해본 결과, gradle 대몬 프로세스가 문제인 것을 파악할 수 있었다. &lt;b&gt;jps&lt;/b&gt; 명령어를 통해 대몬 프로세스의 PID를 찾아내고, &lt;b&gt;kill -9 PID &lt;/b&gt;명령어를 통해 프로세스를 제거했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vdsrH/btsFqYASDOa/5uOxEQYl6J7Ithn4OKM2xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vdsrH/btsFqYASDOa/5uOxEQYl6J7Ithn4OKM2xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vdsrH/btsFqYASDOa/5uOxEQYl6J7Ithn4OKM2xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvdsrH%2FbtsFqYASDOa%2F5uOxEQYl6J7Ithn4OKM2xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;416&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;416&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;그리고 다시 IntelliJ에서 빌드를 실행시켰더니 정상적으로 빌드가 수행되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Wq9U/btsFoLB4ZZs/V44kBxEx0NchHJSSkX2gA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Wq9U/btsFoLB4ZZs/V44kBxEx0NchHJSSkX2gA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Wq9U/btsFoLB4ZZs/V44kBxEx0NchHJSSkX2gA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Wq9U%2FbtsFoLB4ZZs%2FV44kBxEx0NchHJSSkX2gA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;994&quot; height=&quot;200&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;200&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;참고로 gradle 대몬의 유휴 상태는 최대 3시간까지 유지될 수 있다고 한다. 공식문서에서는 대몬 프로세스를 멈추기 위해 &lt;b&gt;gradle --stop&lt;/b&gt; 명령을 사용하라고 했는데, 내가 해봤을 땐 대몬 프로세스가 멈춰지지 않아서 그냥 죽이는 방법을 선택했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lMwRS/btsFm8dsl2s/BDPQJ48lbY2M4xZkF7qPsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lMwRS/btsFm8dsl2s/BDPQJ48lbY2M4xZkF7qPsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lMwRS/btsFm8dsl2s/BDPQJ48lbY2M4xZkF7qPsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlMwRS%2FbtsFm8dsl2s%2FBDPQJ48lbY2M4xZkF7qPsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1498&quot; height=&quot;968&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결방법을 정리하자면 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로컬 터미널에서 gradle task를 돌리고, IDLE 메시지가 나오는지 확인한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;jps&lt;/b&gt; 명령어로 대몬 프로세스 PID를 확인한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;kill -9 PID&lt;/b&gt; 명령어로 프로세스를 종료시키고, 다시 task를 수행한다&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;&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;a href=&quot;https://docs.gradle.org/current/userguide/gradle_daemon.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.gradle.org/current/userguide/gradle_daemon.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709220144038&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;Gradle Daemon&quot; data-og-description=&quot;The Gradle JVM client sends the Daemon build information such as command line arguments, project directories, and environment variables so that it can run the build. The Wrapper is responsible for resolving dependencies, executing build scripts, creating a&quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/gradle_daemon.html&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/gradle_daemon.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/gradle_daemon.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/gradle_daemon.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;Gradle Daemon&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Gradle JVM client sends the Daemon build information such as command line arguments, project directories, and environment variables so that it can run the build. The Wrapper is responsible for resolving dependencies, executing build scripts, creating a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>트러블슈팅</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/381</guid>
      <comments>https://cotak.tistory.com/381#entry381comment</comments>
      <pubDate>Fri, 1 Mar 2024 00:29:09 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 2023년 회고</title>
      <link>https://cotak.tistory.com/380</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;작년 이맘때쯤 2022년 회고를 올렸었는데 벌써 2023년 회고를 올릴 때가 되었다. 처음에 회고는 2023년 마지막 주에 올릴 예정이었지만 올해 진행했던 프로젝트를 완전히 마무리하고 올리고 싶어서 부득이하게 며칠 미뤘다.&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;h3 data-ke-size=&quot;size23&quot;&gt;1) 프로젝트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 회사에 관한 이야기를 먼저 하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올 한 해 동안은 '쿠폰적용가' 프로젝트를 계속해서 성장시키고 확장해가는 데 많은 시간을 할애했다. 기존 지마켓 사이트에선 상품 상세 페이지 진입 시 고객이 실제로 할인받을 수 있는 금액에 대한 정보를 확인할 수 없었다. 그와 더불어 쿠폰 다운로드 시 실제로 할인받을 수 있는 금액이 노출되지 않는 것과 쿠폰함에서 최적쿠폰이 자동적으로 선택되지 않는 것 또한 큰 불편요소였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠폰적용가&lt;span&gt; &lt;/span&gt;프로젝트는&lt;span&gt; &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;불편함을&lt;span&gt; &lt;/span&gt;개선하기&lt;span&gt; &lt;/span&gt;위해&lt;span&gt; &lt;/span&gt;시작됐고&lt;span&gt;, &lt;/span&gt;타&lt;span&gt; &lt;/span&gt;사이트에서&lt;span&gt; &lt;/span&gt;제공되는&lt;span&gt; &lt;/span&gt;쿠폰적용가&lt;span&gt; &lt;/span&gt;기능에&lt;span&gt; &lt;/span&gt;더해&lt;span&gt; &lt;/span&gt;다운로드&lt;span&gt; &lt;/span&gt;및&lt;span&gt; &lt;/span&gt;적용&lt;span&gt; &lt;/span&gt;과정에서도&lt;span&gt; &lt;/span&gt;고객에게&lt;span&gt; &lt;/span&gt;편리함을&lt;span&gt; &lt;/span&gt;주는&lt;span&gt; &lt;/span&gt;것을&lt;span&gt; &lt;/span&gt;목적으로&lt;span&gt; &lt;/span&gt;진행됐다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;2596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E6HTQ/btsCWMpV8BY/p8vu82eL6kyeKcu6J1XCJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E6HTQ/btsCWMpV8BY/p8vu82eL6kyeKcu6J1XCJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E6HTQ/btsCWMpV8BY/p8vu82eL6kyeKcu6J1XCJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE6HTQ%2FbtsCWMpV8BY%2Fp8vu82eL6kyeKcu6J1XCJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1394&quot; height=&quot;2596&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;2596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&lt;span&gt; &lt;/span&gt;하반기에&lt;span&gt; &lt;/span&gt;진행된&lt;span&gt; &lt;/span&gt;후속&lt;span&gt; &lt;/span&gt;프로젝트의&lt;span&gt; &lt;/span&gt;결과로&lt;span&gt; &lt;/span&gt;검색&lt;span&gt; &lt;/span&gt;결과&lt;span&gt; &lt;/span&gt;페이지&lt;span&gt; &lt;/span&gt;및&lt;span&gt; &lt;/span&gt;홈&lt;span&gt; &lt;/span&gt;화면에서도&lt;span&gt; &lt;/span&gt;쿠폰적용가를&lt;span&gt; &lt;/span&gt;노출할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;1418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kT3cU/btsCUyeYTOE/uYEPOE0MWoQwQ19vmM2qL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kT3cU/btsCUyeYTOE/uYEPOE0MWoQwQ19vmM2qL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kT3cU/btsCUyeYTOE/uYEPOE0MWoQwQ19vmM2qL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkT3cU%2FbtsCUyeYTOE%2FuYEPOE0MWoQwQ19vmM2qL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1398&quot; height=&quot;1418&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;1418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 검색 결과 페이지 쿠폰적용가는 대상 상품도 많으면서 타임아웃도 짧은 탓에 단기간 내에 응답속도를 일정 수준 이상으로 높여야 하는 미션이 주어져서 어렵기도 했지만, 문제를 해결하는 과정이 굉장히 재밌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 가지 지표들을 살펴보며 문제들을 추적해간 끝에 최종적으론 응답속도를 약 40% 이상 향상시킬 수 있었다. 이전까지는 조금 생각 없이(?) 코루틴을 사용했는데 트러블 슈팅 과정에서 코루틴에 대한 이해도가 조금이나마 더 높아졌다는 점도 하나의 큰 소득이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 일련의 프로젝트를 통해 사용자 경험 측면에서 큰 발전을 이뤄낼 수 있었다. 개발적인 부분에서도 RDB에 의존하던 할인 시스템을 캐시 기반으로 옮기면서 코어 데이터베이스에 대한 부하도 줄이고, 적재한 캐시 데이터를 판매자 툴 등 다양한 곳에 활용하는 성과도 거둘 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠폰적용가&lt;span&gt; &lt;/span&gt;프로젝트와&lt;span&gt; &lt;/span&gt;동시에&lt;span&gt; &lt;/span&gt;할인&lt;span&gt; &lt;/span&gt;펀딩&lt;span&gt; &lt;/span&gt;프로젝트도&lt;span&gt; &lt;/span&gt;진행됐다&lt;span&gt;. &lt;/span&gt;말&lt;span&gt; &lt;/span&gt;그대로&lt;span&gt; &lt;/span&gt;판매자로부터&lt;span&gt; &lt;/span&gt;할인&lt;span&gt; &lt;/span&gt;예산을&lt;span&gt; &lt;/span&gt;펀딩받는&lt;span&gt; &lt;/span&gt;것인데&lt;span&gt;, &lt;/span&gt;나는&lt;span&gt; &lt;/span&gt;여기서&lt;span&gt; &lt;/span&gt;판매자의&lt;span&gt; &lt;/span&gt;예산을&lt;span&gt; &lt;/span&gt;관리해주는&lt;span&gt; &lt;/span&gt;기능을&lt;span&gt; &lt;/span&gt;개발했다&lt;span&gt;. &lt;/span&gt;주문이&lt;span&gt; &lt;/span&gt;발생하면&lt;span&gt; &lt;/span&gt;판매자의&lt;span&gt; &lt;/span&gt;예산을&lt;span&gt; &lt;/span&gt;차감하고&lt;span&gt; &lt;/span&gt;주문이&lt;span&gt; &lt;/span&gt;실패하거나&lt;span&gt; &lt;/span&gt;취소되면&lt;span&gt; &lt;/span&gt;예산을&lt;span&gt; &lt;/span&gt;복구해주는&lt;span&gt; &lt;/span&gt;것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rT6c9/btsCZgrnuez/To3o6vOlZ9ASW0s6wpNzu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rT6c9/btsCZgrnuez/To3o6vOlZ9ASW0s6wpNzu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rT6c9/btsCZgrnuez/To3o6vOlZ9ASW0s6wpNzu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrT6c9%2FbtsCZgrnuez%2FTo3o6vOlZ9ASW0s6wpNzu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;574&quot; height=&quot;379&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&lt;span&gt; &lt;/span&gt;프로젝트에서&lt;span&gt; &lt;/span&gt;개인적으로&lt;span&gt; &lt;/span&gt;의미가&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&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;/span&gt;시스템&lt;span&gt; &lt;/span&gt;설계에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;권한을&lt;span&gt; &lt;/span&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;/span&gt;감사하게도&lt;span&gt; &lt;/span&gt;레거시나&lt;span&gt; &lt;/span&gt;백오피스와&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;영역들을&lt;span&gt; &lt;/span&gt;맡아주신&lt;span&gt; &lt;/span&gt;덕분에&lt;span&gt; &lt;/span&gt;온전히&lt;span&gt; &lt;/span&gt;해당&lt;span&gt; &lt;/span&gt;기능&lt;span&gt; &lt;/span&gt;설계에만&lt;span&gt; &lt;/span&gt;집중할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있었고&lt;span&gt;, &lt;/span&gt;팀원들에게&lt;span&gt; &lt;/span&gt;피드백을&lt;span&gt; &lt;/span&gt;받으며&lt;span&gt; &lt;/span&gt;무사히&lt;span&gt; &lt;/span&gt;기능을&lt;span&gt; &lt;/span&gt;완성해갈&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;span&gt; &lt;/span&gt;비교적&lt;span&gt; &lt;/span&gt;일정이&lt;span&gt; &lt;/span&gt;널널한&lt;span&gt; &lt;/span&gt;편이어서&lt;span&gt; &lt;/span&gt;남는&lt;span&gt; &lt;/span&gt;시간동안&lt;span&gt; &lt;/span&gt;기술&lt;span&gt; &lt;/span&gt;부채로&lt;span&gt; &lt;/span&gt;남아있던&lt;span&gt; &lt;/span&gt;프로젝트의&lt;span&gt; &lt;/span&gt;버전업을&lt;span&gt; &lt;/span&gt;진행했다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot 2.3.8 &amp;rarr; 2.7.6 으로 업그레이드&lt;/li&gt;
&lt;li&gt;Kotlin 1.3 &amp;rarr; 1.8로 업그레이드&lt;/li&gt;
&lt;li&gt;JUnit5 제거 및 Kotest 도입&lt;/li&gt;
&lt;li&gt;hystrix 제거 및 resilience4j / spring-retry 도입&lt;/li&gt;
&lt;li&gt;기타&lt;span&gt; &lt;/span&gt;사내&lt;span&gt; &lt;/span&gt;라이브러리&lt;span&gt; &lt;/span&gt;업그레이드&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔&lt;span&gt; &lt;/span&gt;스프링&lt;span&gt; &lt;/span&gt;부트랑&lt;span&gt; &lt;/span&gt;코틀린&lt;span&gt; &lt;/span&gt;정도만&lt;span&gt; &lt;/span&gt;버전업을&lt;span&gt; &lt;/span&gt;하고&lt;span&gt; &lt;/span&gt;싶었지만&lt;span&gt;, &lt;/span&gt;하다보니&lt;span&gt; &lt;/span&gt;전부&lt;span&gt; &lt;/span&gt;다&lt;span&gt; &lt;/span&gt;통째로&lt;span&gt; &lt;/span&gt;뜯어내야만&lt;span&gt; &lt;/span&gt;하는&lt;span&gt; &lt;/span&gt;상황에&lt;span&gt; &lt;/span&gt;직면해서&lt;span&gt; &lt;/span&gt;어쩔&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;없이&lt;span&gt; &lt;/span&gt;다&lt;span&gt; &lt;/span&gt;바꿔버렸다&lt;span&gt;. &lt;/span&gt;작년부터&lt;span&gt; &lt;/span&gt;하고&lt;span&gt; &lt;/span&gt;싶었던&lt;span&gt; &lt;/span&gt;작업이라&lt;span&gt; &lt;/span&gt;후련하긴&lt;span&gt; &lt;/span&gt;했는데&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;소스를&lt;span&gt; &lt;/span&gt;두고&lt;span&gt; &lt;/span&gt;작업하시던&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;팀원분께서&lt;span&gt; &lt;/span&gt;코드를&lt;span&gt; &lt;/span&gt;머지할&lt;span&gt; &lt;/span&gt;때&lt;span&gt; &lt;/span&gt;고생하셔서&lt;span&gt; &lt;/span&gt;동시에&lt;span&gt; &lt;/span&gt;죄송스러운&lt;span&gt; &lt;/span&gt;마음도&lt;span&gt; &lt;/span&gt;들었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czMboS/btsCTAxofCW/trTxUhrekqPunZQWQkkWCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czMboS/btsCTAxofCW/trTxUhrekqPunZQWQkkWCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czMboS/btsCTAxofCW/trTxUhrekqPunZQWQkkWCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczMboS%2FbtsCTAxofCW%2FtrTxUhrekqPunZQWQkkWCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;390&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해&lt;span&gt; &lt;/span&gt;했던&lt;span&gt; &lt;/span&gt;프로젝트들은&lt;span&gt; &lt;/span&gt;타&lt;span&gt; &lt;/span&gt;부서의&lt;span&gt; &lt;/span&gt;담당자들과&lt;span&gt; &lt;/span&gt;소통할&lt;span&gt; &lt;/span&gt;일이&lt;span&gt; &lt;/span&gt;많았고&lt;span&gt; &lt;/span&gt;당연하게도&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;이들에게&lt;span&gt; &lt;/span&gt;정보를&lt;span&gt; &lt;/span&gt;전달하거나&lt;span&gt; &lt;/span&gt;설득하는&lt;span&gt; &lt;/span&gt;일&lt;span&gt; &lt;/span&gt;또한&lt;span&gt; &lt;/span&gt;많았다&lt;span&gt;. &lt;/span&gt;이&lt;span&gt; &lt;/span&gt;과정에서&lt;span&gt; &lt;/span&gt;커뮤니케이션&lt;span&gt; &lt;/span&gt;능력을&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;키울&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있던&lt;span&gt; &lt;/span&gt;점도&lt;span&gt; &lt;/span&gt;개발&lt;span&gt; &lt;/span&gt;외적으로&lt;span&gt; &lt;/span&gt;좋은&lt;span&gt; &lt;/span&gt;부분이었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) 운영 업무&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년&lt;span&gt; &lt;/span&gt;상반기에&lt;span&gt; &lt;/span&gt;입사한&lt;span&gt; &lt;/span&gt;후&lt;span&gt; &lt;/span&gt;백오피스&lt;span&gt; &lt;/span&gt;관련&lt;span&gt; &lt;/span&gt;작업만&lt;span&gt; &lt;/span&gt;하다가&lt;span&gt; &lt;/span&gt;하반기에&lt;span&gt; &lt;/span&gt;팀이&lt;span&gt; &lt;/span&gt;통째로&lt;span&gt; &lt;/span&gt;선물하기&lt;span&gt; TF&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;투입된&lt;span&gt; &lt;/span&gt;상황이었고&lt;span&gt;, &lt;/span&gt;팀&lt;span&gt; &lt;/span&gt;내&lt;span&gt; &lt;/span&gt;신규&lt;span&gt; &lt;/span&gt;입사자도&lt;span&gt; &lt;/span&gt;많아서&lt;span&gt; R&amp;amp;R&lt;/span&gt;이&lt;span&gt; &lt;/span&gt;불분명한&lt;span&gt; &lt;/span&gt;상황이었다&lt;span&gt;. &lt;/span&gt;올해&lt;span&gt; &lt;/span&gt;초&lt;span&gt; &lt;/span&gt;팀장님께서&lt;span&gt; &lt;/span&gt;다시&lt;span&gt; R&amp;amp;R&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;지정해주셔서&lt;span&gt; &lt;/span&gt;나는&lt;span&gt; &lt;/span&gt;상품&lt;span&gt; &lt;/span&gt;상세&lt;span&gt; &lt;/span&gt;페이지&lt;span&gt; &lt;/span&gt;내&lt;span&gt; &lt;/span&gt;쿠폰&lt;span&gt; &lt;/span&gt;다운로드&lt;span&gt; &lt;/span&gt;기능의&lt;span&gt; &lt;/span&gt;제&lt;span&gt;1 &lt;/span&gt;담당자가&lt;span&gt; &lt;/span&gt;되었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFPROD/btsC1ovG0KA/RKeKlefJWwB5IAdSYrT9M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFPROD/btsC1ovG0KA/RKeKlefJWwB5IAdSYrT9M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFPROD/btsC1ovG0KA/RKeKlefJWwB5IAdSYrT9M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFPROD%2FbtsC1ovG0KA%2FRKeKlefJWwB5IAdSYrT9M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;221&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;628&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;908&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DAXSA/btsC4H2g4Yc/wunhrP3FWTFFcEwJJQLu9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DAXSA/btsC4H2g4Yc/wunhrP3FWTFFcEwJJQLu9k/img.png&quot; data-alt=&quot;상품 상세 페이지 내에서 바로 쿠폰을 다운로드할 수 있게끔 쿠폰 정보를 내려주는 역할이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DAXSA/btsC4H2g4Yc/wunhrP3FWTFFcEwJJQLu9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDAXSA%2FbtsC4H2g4Yc%2FwunhrP3FWTFFcEwJJQLu9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;386&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;상품 상세 페이지 내에서 바로 쿠폰을 다운로드할 수 있게끔 쿠폰 정보를 내려주는 역할이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 현업에서 많이 사용하는 기능이라 문의가 일주일에 서너 번 이상은 들어와서 응대할 일이 많았다. 처음엔 나에게 익숙한 용어로 설명을 하다 보니 상대방이 이해하기 힘들어 했고, 비슷한 문의를 여러 번 하는 상황이 발생했다. 팀장님께서 우리가 당연하게 사용하는 도메인 용어들이 그분들에겐 낯설 수 있다는 피드백을 주셔서 그 이후로는 최대한 쉬운 말로 풀어서 설명하려고 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응대&lt;span&gt; &lt;/span&gt;업무&lt;span&gt; &lt;/span&gt;외에&lt;span&gt; &lt;/span&gt;성능적인&lt;span&gt; &lt;/span&gt;이슈도&lt;span&gt; &lt;/span&gt;있었다&lt;span&gt;. &lt;/span&gt;쿠폰&lt;span&gt; &lt;/span&gt;다운로드&lt;span&gt; &lt;/span&gt;시스템은&lt;span&gt; &lt;/span&gt;대략&lt;span&gt; &lt;/span&gt;아래와&lt;span&gt; &lt;/span&gt;같이&lt;span&gt; &lt;/span&gt;구성되어&lt;span&gt; &lt;/span&gt;있는데&lt;span&gt;, &lt;/span&gt;컨슈머에서&lt;span&gt; ElasticSearch&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;조회할&lt;span&gt; &lt;/span&gt;때&lt;span&gt; &lt;/span&gt;성능이&lt;span&gt; &lt;/span&gt;너무&lt;span&gt; &lt;/span&gt;안&lt;span&gt; &lt;/span&gt;나왔다&lt;span&gt;. &lt;/span&gt;쿠폰적용가&lt;span&gt; &lt;/span&gt;프로젝트를&lt;span&gt; &lt;/span&gt;하면서&lt;span&gt; &lt;/span&gt;로컬&lt;span&gt; &lt;/span&gt;캐시를&lt;span&gt; &lt;/span&gt;넣어서&lt;span&gt; &lt;/span&gt;조회&lt;span&gt; &lt;/span&gt;성능을&lt;span&gt; &lt;/span&gt;올리긴&lt;span&gt; &lt;/span&gt;했지만&lt;span&gt;, &lt;/span&gt;근본적인&lt;span&gt; &lt;/span&gt;해결책은&lt;span&gt; &lt;/span&gt;되지&lt;span&gt; &lt;/span&gt;못했다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3T4VL/btsCWJUSzBb/kMKHR4FSS0IjEjlqnYDhvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3T4VL/btsCWJUSzBb/kMKHR4FSS0IjEjlqnYDhvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3T4VL/btsCWJUSzBb/kMKHR4FSS0IjEjlqnYDhvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3T4VL%2FbtsCWJUSzBb%2FkMKHR4FSS0IjEjlqnYDhvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;937&quot; height=&quot;445&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 할인 행사가 있거나 해서 트래픽이 몰리면 컨슈머 랙이 치솟게 되었다. 항상 이때마다 카프카 담당자분께 연락을 드려 오프셋을 초기화시키는 작업을 진행하거나 메시지 발행주기를 늘리는 방식으로 대응했다. (카프카 오프셋은 함부로 초기화하면 안 되는데, 해당 데이터의 경우 유실돼도 괜찮은 데이터여서 가능한 부분이었다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (84).png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWMonT/btsC4qzwlXP/kpW1EwCh6CKxb9U11hFPFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWMonT/btsC4qzwlXP/kpW1EwCh6CKxb9U11hFPFk/img.png&quot; data-alt=&quot;조용필 올라가는 추신수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWMonT/btsC4qzwlXP/kpW1EwCh6CKxb9U11hFPFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWMonT%2FbtsC4qzwlXP%2FkpW1EwCh6CKxb9U11hFPFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;250&quot; data-filename=&quot;image (84).png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;조용필 올라가는 추신수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히&lt;span&gt; &lt;/span&gt;이번에&lt;span&gt; &lt;/span&gt;리팩토링을&lt;span&gt; &lt;/span&gt;하면서&lt;span&gt; &lt;/span&gt;쿼리도&lt;span&gt; &lt;/span&gt;개선했고&lt;span&gt;, Elastic Search&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;적재되어&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;데이터의&lt;span&gt; &lt;/span&gt;리텐션&lt;span&gt; &lt;/span&gt;정책도&lt;span&gt; &lt;/span&gt;수립하면서&lt;span&gt; &lt;/span&gt;컨슈머&lt;span&gt; &lt;/span&gt;속도가&lt;span&gt; &lt;/span&gt;엄청나게&lt;span&gt; &lt;/span&gt;빨라졌다&lt;span&gt;. &lt;/span&gt;그&lt;span&gt; &lt;/span&gt;덕에&lt;span&gt; &lt;/span&gt;요동치던&lt;span&gt; &lt;/span&gt;컨슈머&lt;span&gt; &lt;/span&gt;랙도&lt;span&gt; &lt;/span&gt;잠잠해졌고&lt;span&gt;, &lt;/span&gt;메시지&lt;span&gt; &lt;/span&gt;발행주기도&lt;span&gt; &lt;/span&gt;줄일&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있어서&lt;span&gt; &lt;/span&gt;캐시&lt;span&gt; &lt;/span&gt;데이터의&lt;span&gt; &lt;/span&gt;정확성도&lt;span&gt; &lt;/span&gt;한층&lt;span&gt; &lt;/span&gt;높아졌다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (85).png&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2gIBv/btsCZgkCwHI/OjX3RsTJdWVqkUbjidvoy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2gIBv/btsCZgkCwHI/OjX3RsTJdWVqkUbjidvoy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2gIBv/btsCZgkCwHI/OjX3RsTJdWVqkUbjidvoy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2gIBv%2FbtsCZgkCwHI%2FOjX3RsTJdWVqkUbjidvoy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;224&quot; data-filename=&quot;image (85).png&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&lt;span&gt; R&amp;amp;R&lt;/span&gt;이&lt;span&gt; &lt;/span&gt;확립되면서&lt;span&gt; &lt;/span&gt;빅스마일데이&lt;span&gt; &lt;/span&gt;모니터링에도&lt;span&gt; &lt;/span&gt;참여했다&lt;span&gt;. &lt;/span&gt;특정&lt;span&gt; &lt;/span&gt;시점에&lt;span&gt; &lt;/span&gt;몰리는&lt;span&gt; &lt;/span&gt;대규모&lt;span&gt; &lt;/span&gt;트래픽에&lt;span&gt; &lt;/span&gt;대응하는&lt;span&gt; &lt;/span&gt;경험은&lt;span&gt; &lt;/span&gt;나같은&lt;span&gt; &lt;/span&gt;초짜&lt;span&gt; &lt;/span&gt;개발자에겐&lt;span&gt; &lt;/span&gt;귀한&lt;span&gt; &lt;/span&gt;경험인지라&lt;span&gt; &lt;/span&gt;행사&lt;span&gt; &lt;/span&gt;기간마다&lt;span&gt; &lt;/span&gt;힘들지만서도&lt;span&gt; &lt;/span&gt;배우는&lt;span&gt; &lt;/span&gt;부분이&lt;span&gt; &lt;/span&gt;많았다&lt;span&gt;. &lt;/span&gt;물론&lt;span&gt; &lt;/span&gt;쿠폰&lt;span&gt; &lt;/span&gt;발급에&lt;span&gt; &lt;/span&gt;문제가&lt;span&gt; &lt;/span&gt;생겨서&lt;span&gt; &lt;/span&gt;매&lt;span&gt; &lt;/span&gt;정각마다&lt;span&gt; &lt;/span&gt;대기를&lt;span&gt; &lt;/span&gt;했다거나&lt;span&gt;.. &lt;/span&gt;배포&lt;span&gt; &lt;/span&gt;이슈로&lt;span&gt; &lt;/span&gt;서버가&lt;span&gt; &lt;/span&gt;다운되거나&lt;span&gt; &lt;/span&gt;하는&lt;span&gt; &lt;/span&gt;기억은&lt;span&gt; &lt;/span&gt;잊고&lt;span&gt; &lt;/span&gt;싶다&lt;span&gt;.. 2024&lt;/span&gt;년에는&lt;span&gt; &lt;/span&gt;별&lt;span&gt; &lt;/span&gt;일&lt;span&gt; &lt;/span&gt;없길&lt;span&gt;..&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oihyv/btsC4tKkWh1/EpYLsaVdDP8KGKAdaJ7OEK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oihyv/btsC4tKkWh1/EpYLsaVdDP8KGKAdaJ7OEK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oihyv/btsC4tKkWh1/EpYLsaVdDP8KGKAdaJ7OEK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foihyv%2FbtsC4tKkWh1%2FEpYLsaVdDP8KGKAdaJ7OEK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;262&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3) 부가 활동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년&lt;span&gt; &lt;/span&gt;회고글에서&lt;span&gt; &lt;/span&gt;기술블로그에&lt;span&gt; &lt;/span&gt;글을&lt;span&gt; &lt;/span&gt;작성하겠다는&lt;span&gt; &lt;/span&gt;다짐을&lt;span&gt; &lt;/span&gt;했었다&lt;span&gt;. &lt;/span&gt;다행히&lt;span&gt; &lt;/span&gt;상반기&lt;span&gt; &lt;/span&gt;테크&lt;span&gt; &lt;/span&gt;에디터에&lt;span&gt; &lt;/span&gt;선정됐고&lt;span&gt; &lt;/span&gt;동료&lt;span&gt; &lt;/span&gt;에디터분들이&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;다듬어주신&lt;span&gt; &lt;/span&gt;덕분에&lt;span&gt; &lt;/span&gt;무사히&lt;span&gt; 2&lt;/span&gt;개의&lt;span&gt; &lt;/span&gt;글을&lt;span&gt; &lt;/span&gt;기고하게&lt;span&gt; &lt;/span&gt;되었다&lt;span&gt;. &lt;/span&gt;작년&lt;span&gt; &lt;/span&gt;선물하기&lt;span&gt; &lt;/span&gt;때&lt;span&gt; &lt;/span&gt;클라이언트&lt;span&gt;/&lt;/span&gt;서버에서&lt;span&gt; Validation &lt;/span&gt;처리하는&lt;span&gt; &lt;/span&gt;걸&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;했어서&lt;span&gt; Spring Bean Validation&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;대해&lt;span&gt; &lt;/span&gt;글을&lt;span&gt; &lt;/span&gt;쓰고&lt;span&gt; &lt;/span&gt;싶었는데&lt;span&gt;, &lt;/span&gt;어쩌다&lt;span&gt; &lt;/span&gt;보니&lt;span&gt; &lt;/span&gt;타이밍을&lt;span&gt; &lt;/span&gt;놓쳐버려서&lt;span&gt; 2&lt;/span&gt;개밖에&lt;span&gt; &lt;/span&gt;쓰지&lt;span&gt; &lt;/span&gt;못한&lt;span&gt; &lt;/span&gt;부분은&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;아쉽다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1704366812398&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;BigDecimal A to Z: 정확한 계산을 위한 숫자 처리 클래스&quot; data-og-description=&quot;안녕하세요. Club &amp;amp; Discount Engineering 팀에서 지마켓 할인/쿠폰 개발 업무를 맡고 있는 윤영택입니다. 저희 팀은 할인/쿠폰/수수료 등 돈과 관련된 도메인을 다루다 보니 코드를 작성할 때 BigDecimal &quot; data-og-host=&quot;dev.gmarket.com&quot; data-og-source-url=&quot;https://dev.gmarket.com/75&quot; data-og-url=&quot;https://dev.gmarket.com/75&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4rOZ7/hyU2eOikAP/K8lI8QNoZeycHKNIPxPClK/img.jpg?width=467&amp;amp;height=163&amp;amp;face=0_0_467_163,https://scrap.kakaocdn.net/dn/sWYvV/hyUXS0q3b3/pR4UCtmkuX1ThknmhEgMR1/img.jpg?width=467&amp;amp;height=163&amp;amp;face=0_0_467_163,https://scrap.kakaocdn.net/dn/bWD7Ue/hyUXN5Tp4U/KS4iSmM8ESs71bMLKMGswK/img.png?width=1874&amp;amp;height=398&amp;amp;face=0_0_1874_398&quot;&gt;&lt;a href=&quot;https://dev.gmarket.com/75&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.gmarket.com/75&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4rOZ7/hyU2eOikAP/K8lI8QNoZeycHKNIPxPClK/img.jpg?width=467&amp;amp;height=163&amp;amp;face=0_0_467_163,https://scrap.kakaocdn.net/dn/sWYvV/hyUXS0q3b3/pR4UCtmkuX1ThknmhEgMR1/img.jpg?width=467&amp;amp;height=163&amp;amp;face=0_0_467_163,https://scrap.kakaocdn.net/dn/bWD7Ue/hyUXN5Tp4U/KS4iSmM8ESs71bMLKMGswK/img.png?width=1874&amp;amp;height=398&amp;amp;face=0_0_1874_398');&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;BigDecimal A to Z: 정확한 계산을 위한 숫자 처리 클래스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. Club &amp;amp; Discount Engineering 팀에서 지마켓 할인/쿠폰 개발 업무를 맡고 있는 윤영택입니다. 저희 팀은 할인/쿠폰/수수료 등 돈과 관련된 도메인을 다루다 보니 코드를 작성할 때 BigDecimal&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.gmarket.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1704366838819&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;G마켓 쿠폰적용가 도입기 - 캐시를 중심으로&quot; data-og-description=&quot;안녕하세요. 저는 Club &amp;amp; Discount Engineering 팀에서 지마켓 할인/쿠폰 개발 업무를 맡고 있는 윤영택입니다. 저는 올해 상반기에 G마켓 쿠폰적용가 개발에 참여했으며, 그 과정을 본 포스팅을 통해 나&quot; data-og-host=&quot;dev.gmarket.com&quot; data-og-source-url=&quot;https://dev.gmarket.com/89&quot; data-og-url=&quot;https://dev.gmarket.com/89&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vNbE4/hyU2tq9nmD/mSgMEsALUinLhYymJFXyIk/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/cJt9Mm/hyU2s6QzZi/WkVzCBWSKjnjEpZwr5iULK/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/dN45J8/hyU2rfNzaV/k4GLwUWLhsF0KG5sDSZjuk/img.png?width=1301&amp;amp;height=671&amp;amp;face=0_0_1301_671&quot;&gt;&lt;a href=&quot;https://dev.gmarket.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.gmarket.com/89&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vNbE4/hyU2tq9nmD/mSgMEsALUinLhYymJFXyIk/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/cJt9Mm/hyU2s6QzZi/WkVzCBWSKjnjEpZwr5iULK/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/dN45J8/hyU2rfNzaV/k4GLwUWLhsF0KG5sDSZjuk/img.png?width=1301&amp;amp;height=671&amp;amp;face=0_0_1301_671');&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;G마켓 쿠폰적용가 도입기 - 캐시를 중심으로&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 저는 Club &amp;amp; Discount Engineering 팀에서 지마켓 할인/쿠폰 개발 업무를 맡고 있는 윤영택입니다. 저는 올해 상반기에 G마켓 쿠폰적용가 개발에 참여했으며, 그 과정을 본 포스팅을 통해 나&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.gmarket.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;span&gt; &lt;/span&gt;써서&lt;span&gt; &lt;/span&gt;결과물을&lt;span&gt; &lt;/span&gt;내는&lt;span&gt; &lt;/span&gt;것도&lt;span&gt; &lt;/span&gt;좋은&lt;span&gt; &lt;/span&gt;성과였지만&lt;span&gt;, &lt;/span&gt;리뷰를&lt;span&gt; &lt;/span&gt;받거나&lt;span&gt; &lt;/span&gt;해주는&lt;span&gt; &lt;/span&gt;과정&lt;span&gt; &lt;/span&gt;그&lt;span&gt; &lt;/span&gt;자체만으로도&lt;span&gt; &lt;/span&gt;즐거운&lt;span&gt; &lt;/span&gt;경험이었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-04 오후 8.25.26.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf2ALO/btsC2CgJ8ys/gNTkCM9CalBScfaXFiHMK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf2ALO/btsC2CgJ8ys/gNTkCM9CalBScfaXFiHMK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf2ALO/btsC2CgJ8ys/gNTkCM9CalBScfaXFiHMK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf2ALO%2FbtsC2CgJ8ys%2FgNTkCM9CalBScfaXFiHMK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;662&quot; data-filename=&quot;스크린샷 2024-01-04 오후 8.25.26.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-02 오후 10.34.28.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXFUc1/btsCZ88b9UX/hW0XzBNfw8owhANQqCjXD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXFUc1/btsCZ88b9UX/hW0XzBNfw8owhANQqCjXD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXFUc1/btsCZ88b9UX/hW0XzBNfw8owhANQqCjXD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXFUc1%2FbtsCZ88b9UX%2FhW0XzBNfw8owhANQqCjXD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;200&quot; data-filename=&quot;스크린샷 2024-01-02 오후 10.34.28.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는&lt;span&gt; &lt;/span&gt;신기하게&lt;span&gt;(?) &lt;/span&gt;발표할&lt;span&gt; &lt;/span&gt;기회가&lt;span&gt; &lt;/span&gt;많았다&lt;span&gt;. &lt;/span&gt;상반기엔&lt;span&gt; &lt;/span&gt;전사&lt;span&gt; &lt;/span&gt;올핸즈&lt;span&gt;, &lt;/span&gt;하반기엔&lt;span&gt; &lt;/span&gt;지식&lt;span&gt; &lt;/span&gt;공유&lt;span&gt; &lt;/span&gt;세션에서&lt;span&gt; &lt;/span&gt;발표를&lt;span&gt; &lt;/span&gt;진행했다&lt;span&gt;. &lt;/span&gt;보통&lt;span&gt; &lt;/span&gt;전사&lt;span&gt; &lt;/span&gt;올핸즈에서&lt;span&gt; &lt;/span&gt;프로젝트&lt;span&gt; &lt;/span&gt;성과를&lt;span&gt; &lt;/span&gt;보고할&lt;span&gt; &lt;/span&gt;땐&lt;span&gt; PM&lt;/span&gt;분들이&lt;span&gt; &lt;/span&gt;하시는데&lt;span&gt;, &lt;/span&gt;왠지&lt;span&gt; &lt;/span&gt;모르게&lt;span&gt; &lt;/span&gt;개발자인&lt;span&gt; &lt;/span&gt;나에게&lt;span&gt; &lt;/span&gt;차례가&lt;span&gt; &lt;/span&gt;돌아왔다&lt;span&gt;. &lt;/span&gt;솔직히&lt;span&gt; &lt;/span&gt;처음엔&lt;span&gt; &lt;/span&gt;너무&lt;span&gt; &lt;/span&gt;부담이&lt;span&gt; &lt;/span&gt;커서&lt;span&gt; &lt;/span&gt;하기&lt;span&gt; &lt;/span&gt;싫었지만&lt;span&gt;.. &lt;/span&gt;어쩔&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;없이&lt;span&gt; &lt;/span&gt;해야만&lt;span&gt; &lt;/span&gt;했다&lt;span&gt;. &lt;/span&gt;그렇게&lt;span&gt; &lt;/span&gt;발표&lt;span&gt; &lt;/span&gt;당일이&lt;span&gt; &lt;/span&gt;돼서&lt;span&gt; &lt;/span&gt;앞에&lt;span&gt; &lt;/span&gt;서니&lt;span&gt; &lt;/span&gt;심박수가&lt;span&gt; &lt;/span&gt;급속도로&lt;span&gt; &lt;/span&gt;올라갔는데&lt;span&gt;, &lt;/span&gt;팀원분들이&lt;span&gt; &lt;/span&gt;만들어주신&lt;span&gt; &lt;/span&gt;팜플랫&lt;span&gt; &lt;/span&gt;덕분에&lt;span&gt; &lt;/span&gt;빵&lt;span&gt; &lt;/span&gt;터져서&lt;span&gt; &lt;/span&gt;다행히&lt;span&gt; &lt;/span&gt;긴장이&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;풀렸고&lt;span&gt; &lt;/span&gt;잘&lt;span&gt; &lt;/span&gt;마무리할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-01-04-20-47-09.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;933&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EnLhV/btsCZ8f3i69/Ihm0BBtPKVBxyC1KO0T81k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EnLhV/btsCZ8f3i69/Ihm0BBtPKVBxyC1KO0T81k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EnLhV/btsCZ8f3i69/Ihm0BBtPKVBxyC1KO0T81k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEnLhV%2FbtsCZ8f3i69%2FIhm0BBtPKVBxyC1KO0T81k%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;1440&quot; height=&quot;933&quot; data-filename=&quot;KakaoTalk_Photo_2024-01-04-20-47-09.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;933&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지식&lt;span&gt; &lt;/span&gt;공유&lt;span&gt; &lt;/span&gt;세션은&lt;span&gt; HR&lt;/span&gt;의&lt;span&gt; &lt;/span&gt;제의로&lt;span&gt; &lt;/span&gt;진행하게&lt;span&gt; &lt;/span&gt;되었다&lt;span&gt;. &lt;/span&gt;아는&lt;span&gt; &lt;/span&gt;게&lt;span&gt; &lt;/span&gt;별로&lt;span&gt; &lt;/span&gt;없어서&lt;span&gt; &lt;/span&gt;주제&lt;span&gt; &lt;/span&gt;선정을&lt;span&gt; &lt;/span&gt;어떻게&lt;span&gt; &lt;/span&gt;할&lt;span&gt; &lt;/span&gt;지&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;고민하다가&lt;span&gt; &lt;/span&gt;주력&lt;span&gt; &lt;/span&gt;언어이기도&lt;span&gt; &lt;/span&gt;하고&lt;span&gt; &lt;/span&gt;팀&lt;span&gt; &lt;/span&gt;스터디도&lt;span&gt; &lt;/span&gt;해봤던&lt;span&gt; &lt;/span&gt;코틀린을&lt;span&gt; &lt;/span&gt;주제로&lt;span&gt; &lt;/span&gt;세션을&lt;span&gt; &lt;/span&gt;진행했다&lt;span&gt;. &lt;/span&gt;하고나니&lt;span&gt; &lt;/span&gt;스스로에게&lt;span&gt; &lt;/span&gt;아쉬운&lt;span&gt; &lt;/span&gt;부분이&lt;span&gt; &lt;/span&gt;많아서&lt;span&gt; &lt;/span&gt;세션의&lt;span&gt; &lt;/span&gt;크리에이터로&lt;span&gt; &lt;/span&gt;참여할&lt;span&gt; &lt;/span&gt;기회가&lt;span&gt; &lt;/span&gt;다시&lt;span&gt; &lt;/span&gt;주어진다면&lt;span&gt; &lt;/span&gt;욕심을&lt;span&gt; &lt;/span&gt;버리고&lt;span&gt; &lt;/span&gt;쉬운&lt;span&gt; &lt;/span&gt;내용으로&lt;span&gt; &lt;/span&gt;알차게&lt;span&gt; &lt;/span&gt;준비하고&lt;span&gt; &lt;/span&gt;싶다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-04 오후 8.37.29.png&quot; data-origin-width=&quot;2798&quot; data-origin-height=&quot;1564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HLw5g/btsCWJOMJsQ/eYVH9AzkajKAmgVmpKXT1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HLw5g/btsCWJOMJsQ/eYVH9AzkajKAmgVmpKXT1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HLw5g/btsCWJOMJsQ/eYVH9AzkajKAmgVmpKXT1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHLw5g%2FbtsCWJOMJsQ%2FeYVH9AzkajKAmgVmpKXT1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2798&quot; height=&quot;1564&quot; data-filename=&quot;스크린샷 2024-01-04 오후 8.37.29.png&quot; data-origin-width=&quot;2798&quot; data-origin-height=&quot;1564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4) 해커톤&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사&lt;span&gt; &lt;/span&gt;후에&lt;span&gt; &lt;/span&gt;가장&lt;span&gt; &lt;/span&gt;하고&lt;span&gt; &lt;/span&gt;싶었던&lt;span&gt; &lt;/span&gt;한&lt;span&gt; &lt;/span&gt;가지를&lt;span&gt; &lt;/span&gt;뽑자면&lt;span&gt; &lt;/span&gt;해커톤이었다&lt;span&gt;. &lt;/span&gt;작년엔&lt;span&gt; &lt;/span&gt;선물하기&lt;span&gt; &lt;/span&gt;프로젝트&lt;span&gt; &lt;/span&gt;때문에&lt;span&gt; &lt;/span&gt;참여를&lt;span&gt; &lt;/span&gt;못&lt;span&gt; &lt;/span&gt;해서&lt;span&gt; &lt;/span&gt;올해는&lt;span&gt; &lt;/span&gt;꼭&lt;span&gt; &lt;/span&gt;참여하겠다는&lt;span&gt; &lt;/span&gt;다짐&lt;span&gt; &lt;/span&gt;속에&lt;span&gt; &lt;/span&gt;해커톤에&lt;span&gt; &lt;/span&gt;참여했다&lt;span&gt;. &lt;/span&gt;팀원들이랑&lt;span&gt; &lt;/span&gt;숙소를&lt;span&gt; &lt;/span&gt;하나&lt;span&gt; &lt;/span&gt;구해서&lt;span&gt; &lt;/span&gt;출퇴근하면서&lt;span&gt; &lt;/span&gt;거기서&lt;span&gt; &lt;/span&gt;먹고&lt;span&gt; &lt;/span&gt;자고&lt;span&gt; &lt;/span&gt;했는데&lt;span&gt;, &lt;/span&gt;개인적으로는&lt;span&gt; &lt;/span&gt;회사&lt;span&gt; &lt;/span&gt;다니면서&lt;span&gt; &lt;/span&gt;제일&lt;span&gt; &lt;/span&gt;재밌었던&lt;span&gt; 3&lt;/span&gt;일이었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-01-04-21-16-28 001.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1081&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMFFuY/btsC1nYnaik/SIR8TTjh1Cd2ZJwN6lE7u1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMFFuY/btsC1nYnaik/SIR8TTjh1Cd2ZJwN6lE7u1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMFFuY/btsC1nYnaik/SIR8TTjh1Cd2ZJwN6lE7u1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMFFuY%2FbtsC1nYnaik%2FSIR8TTjh1Cd2ZJwN6lE7u1%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;640&quot; height=&quot;480&quot; data-filename=&quot;KakaoTalk_Photo_2024-01-04-21-16-28 001.jpeg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1081&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해커톤은&lt;span&gt; 12&lt;/span&gt;월&lt;span&gt; 5&lt;/span&gt;일부터&lt;span&gt; 7&lt;/span&gt;일까지&lt;span&gt; 3&lt;/span&gt;일에&lt;span&gt; &lt;/span&gt;걸쳐&lt;span&gt; &lt;/span&gt;남산&lt;span&gt; &lt;/span&gt;연수원에서&lt;span&gt; &lt;/span&gt;진행되었다&lt;span&gt;. &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;팀을&lt;span&gt; 5&lt;/span&gt;개의&lt;span&gt; &lt;/span&gt;그룹으로&lt;span&gt; &lt;/span&gt;나뉘고&lt;span&gt; &lt;/span&gt;그룹별&lt;span&gt; 1&lt;/span&gt;등과&lt;span&gt; &lt;/span&gt;점수가&lt;span&gt; &lt;/span&gt;높은&lt;span&gt; 2&lt;/span&gt;등&lt;span&gt; &lt;/span&gt;팀들이&lt;span&gt; &lt;/span&gt;결선으로&lt;span&gt; &lt;/span&gt;진출하는&lt;span&gt; &lt;/span&gt;방식이었다&lt;span&gt;. &lt;/span&gt;우리&lt;span&gt; &lt;/span&gt;팀은&lt;span&gt; &lt;/span&gt;아쉽게&lt;span&gt; &lt;/span&gt;결선&lt;span&gt; &lt;/span&gt;진출을&lt;span&gt; &lt;/span&gt;하지&lt;span&gt; &lt;/span&gt;못했지만&lt;span&gt;, 2&lt;/span&gt;박&lt;span&gt; 3&lt;/span&gt;일&lt;span&gt; &lt;/span&gt;동안&lt;span&gt; &lt;/span&gt;팀원들이랑&lt;span&gt; &lt;/span&gt;함께&lt;span&gt; &lt;/span&gt;즐거운&lt;span&gt; &lt;/span&gt;시간을&lt;span&gt; &lt;/span&gt;보낼&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있어서&lt;span&gt; &lt;/span&gt;행복했다&lt;span&gt;. &lt;/span&gt;그리고&lt;span&gt; &lt;/span&gt;정말&lt;span&gt; &lt;/span&gt;좋은&lt;span&gt; &lt;/span&gt;아이디어들이&lt;span&gt; &lt;/span&gt;많아서&lt;span&gt; &lt;/span&gt;스스로&lt;span&gt; &lt;/span&gt;자극이&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;되었다&lt;span&gt;. (&lt;/span&gt;우리&lt;span&gt; &lt;/span&gt;팀은&lt;span&gt; &lt;/span&gt;아니지만&lt;span&gt;, &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;팀으로&lt;span&gt; &lt;/span&gt;출전한&lt;span&gt; &lt;/span&gt;팀원분들이&lt;span&gt; &lt;/span&gt;우승해서&lt;span&gt; &lt;/span&gt;대리만족할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있었다&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;MicrosoftTeams-image (2) 복사본.png&quot; data-origin-width=&quot;3008&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djUJsC/btsC6p2BZc8/Z3tAr7Vn199tIKPRvGF55K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djUJsC/btsC6p2BZc8/Z3tAr7Vn199tIKPRvGF55K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djUJsC/btsC6p2BZc8/Z3tAr7Vn199tIKPRvGF55K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjUJsC%2FbtsC6p2BZc8%2FZ3tAr7Vn199tIKPRvGF55K%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;640&quot; height=&quot;426&quot; data-filename=&quot;MicrosoftTeams-image (2) 복사본.png&quot; data-origin-width=&quot;3008&quot; data-origin-height=&quot;2000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 개인&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1) 공부&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자잘한&lt;span&gt; &lt;/span&gt;개인&lt;span&gt; &lt;/span&gt;공부&lt;span&gt; &lt;/span&gt;외에&lt;span&gt; &lt;/span&gt;올해엔&lt;span&gt; &lt;/span&gt;크게&lt;span&gt; 3&lt;/span&gt;가지&lt;span&gt; &lt;/span&gt;스터디를&lt;span&gt; &lt;/span&gt;했다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 고급과 배치&lt;/li&gt;
&lt;li&gt;이펙티브 코틀린&lt;/li&gt;
&lt;li&gt;도메인 주도 설계 첫걸음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에도 사내 스터디를 했지만, 올해는 뭔가 좀 더 체득이 잘 된 느낌이다. 실무를 조금이나마 경험하면서 쌓인 개념들이 이론을 이해하는 데 도움이 됐던 덕분이 아닐까 싶다. 그리고 스터디 주제 자체도 실무와 밀접한 것들이기도 했다. 2024년에는 개인적으로 데이터베이스나 컨테이너 기술에 관련된 스터디를 해보고 싶고, 팀원들과는 테스트를 주제로 공부해보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참여한&lt;span&gt; &lt;/span&gt;스터디&lt;span&gt; &lt;/span&gt;모두&lt;span&gt; &lt;/span&gt;즐거운&lt;span&gt; &lt;/span&gt;마음으로&lt;span&gt; &lt;/span&gt;참여했고&lt;span&gt; &lt;/span&gt;배운&lt;span&gt; &lt;/span&gt;게&lt;span&gt; &lt;/span&gt;많았지만&lt;span&gt; &lt;/span&gt;돌이켜보면&lt;span&gt; &lt;/span&gt;팀원들과&lt;span&gt; &lt;/span&gt;함께했던&lt;span&gt; &lt;/span&gt;이펙티브&lt;span&gt; &lt;/span&gt;코틀린&lt;span&gt; &lt;/span&gt;스터디가&lt;span&gt; &lt;/span&gt;그래도&lt;span&gt; &lt;/span&gt;기억에&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;남는다&lt;span&gt;. &lt;/span&gt;보통&lt;span&gt; &lt;/span&gt;스터디를&lt;span&gt; &lt;/span&gt;하면&lt;span&gt; &lt;/span&gt;한&lt;span&gt; &lt;/span&gt;명을&lt;span&gt; &lt;/span&gt;뽑아서&lt;span&gt; &lt;/span&gt;발표하는&lt;span&gt; &lt;/span&gt;방식으로&lt;span&gt; &lt;/span&gt;진행하는&lt;span&gt; &lt;/span&gt;게&lt;span&gt; &lt;/span&gt;일반적인데&lt;span&gt;, &lt;/span&gt;이&lt;span&gt; &lt;/span&gt;스터디는&lt;span&gt; &lt;/span&gt;깃헙&lt;span&gt; Repository&lt;/span&gt;에서&lt;span&gt; &lt;/span&gt;자유롭게&lt;span&gt; &lt;/span&gt;질문&lt;span&gt;/&lt;/span&gt;공유하고&lt;span&gt; &lt;/span&gt;싶은&lt;span&gt; &lt;/span&gt;내용&lt;span&gt;/&lt;/span&gt;토론하고&lt;span&gt; &lt;/span&gt;싶은&lt;span&gt; &lt;/span&gt;내용&lt;span&gt; &lt;/span&gt;등을&lt;span&gt; &lt;/span&gt;이슈로&lt;span&gt; &lt;/span&gt;올리는&lt;span&gt; &lt;/span&gt;방식으로&lt;span&gt; &lt;/span&gt;진행됐다&lt;span&gt;.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2446&quot; data-origin-height=&quot;1516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXRYmI/btsC1qgu20O/FbyZf1IcLuYr7knr5KgJyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXRYmI/btsC1qgu20O/FbyZf1IcLuYr7knr5KgJyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXRYmI/btsC1qgu20O/FbyZf1IcLuYr7knr5KgJyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXRYmI%2FbtsC1qgu20O%2FFbyZf1IcLuYr7knr5KgJyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2446&quot; height=&quot;1516&quot; data-origin-width=&quot;2446&quot; data-origin-height=&quot;1516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MHYKV/btsC6npuCvY/aYDq4ODbkUFSO40qiW6zdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MHYKV/btsC6npuCvY/aYDq4ODbkUFSO40qiW6zdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MHYKV/btsC6npuCvY/aYDq4ODbkUFSO40qiW6zdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMHYKV%2FbtsC6npuCvY%2FaYDq4ODbkUFSO40qiW6zdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1876&quot; height=&quot;1650&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표&lt;span&gt; &lt;/span&gt;방식의&lt;span&gt; &lt;/span&gt;스터디의&lt;span&gt; &lt;/span&gt;장점도&lt;span&gt; &lt;/span&gt;있지만&lt;span&gt; &lt;/span&gt;개인적으로는&lt;span&gt; &lt;/span&gt;이렇게&lt;span&gt; &lt;/span&gt;자유롭게&lt;span&gt; &lt;/span&gt;토론하는&lt;span&gt; &lt;/span&gt;방식의&lt;span&gt; &lt;/span&gt;스터디가&lt;span&gt; &lt;/span&gt;나에겐&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;잘&lt;span&gt; &lt;/span&gt;맞았다&lt;span&gt;. &lt;/span&gt;발표는&lt;span&gt; &lt;/span&gt;복습의&lt;span&gt; &lt;/span&gt;느낌인&lt;span&gt; &lt;/span&gt;반면&lt;span&gt;, &lt;/span&gt;토론&lt;span&gt; &lt;/span&gt;방식으로&lt;span&gt; &lt;/span&gt;진행하면&lt;span&gt; &lt;/span&gt;확장성&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;공부를&lt;span&gt; &lt;/span&gt;하는&lt;span&gt; &lt;/span&gt;느낌이었다&lt;span&gt;. &lt;/span&gt;또&lt;span&gt; &lt;/span&gt;엔터프라이즈&lt;span&gt; &lt;/span&gt;버전이&lt;span&gt; &lt;/span&gt;아닌&lt;span&gt; &lt;/span&gt;퍼블릭&lt;span&gt; &lt;/span&gt;깃헙에서&lt;span&gt; &lt;/span&gt;레포를&lt;span&gt; &lt;/span&gt;만든&lt;span&gt; &lt;/span&gt;덕분에&lt;span&gt; &lt;/span&gt;언젠가&lt;span&gt; &lt;/span&gt;회사를&lt;span&gt; &lt;/span&gt;떠나게&lt;span&gt; &lt;/span&gt;되더라도&lt;span&gt; &lt;/span&gt;자료로서&lt;span&gt; &lt;/span&gt;남길&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;점도&lt;span&gt; &lt;/span&gt;좋았다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;span&gt; &lt;/span&gt;스탭&lt;span&gt; &lt;/span&gt;강의도&lt;span&gt; &lt;/span&gt;참여했다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ATDD, 클린 코드 with Spring&lt;/li&gt;
&lt;li&gt;DDD 세레나데&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의를&lt;span&gt; &lt;/span&gt;신청한&lt;span&gt; &lt;/span&gt;이유는&lt;span&gt; &lt;/span&gt;현재&lt;span&gt; &lt;/span&gt;업무환경에서&lt;span&gt; &lt;/span&gt;경험할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;없는&lt;span&gt; &lt;/span&gt;주제를&lt;span&gt; &lt;/span&gt;실습해보기&lt;span&gt; &lt;/span&gt;위함이었고&lt;span&gt;, &lt;/span&gt;코드&lt;span&gt; &lt;/span&gt;리뷰를&lt;span&gt; &lt;/span&gt;받으면서&lt;span&gt; &lt;/span&gt;객체&lt;span&gt; &lt;/span&gt;지향이나&lt;span&gt; &lt;/span&gt;코드의&lt;span&gt; &lt;/span&gt;가독성에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;고민을&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;해볼&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있던&lt;span&gt; &lt;/span&gt;점&lt;span&gt; &lt;/span&gt;또한&lt;span&gt; &lt;/span&gt;부가적으로&lt;span&gt; &lt;/span&gt;크게&lt;span&gt; &lt;/span&gt;도움이&lt;span&gt; &lt;/span&gt;되었다&lt;span&gt;. &lt;/span&gt;여기서&lt;span&gt; &lt;/span&gt;배운&lt;span&gt; &lt;/span&gt;내용들을&lt;span&gt; &lt;/span&gt;실무에&lt;span&gt; &lt;/span&gt;적용해보면서&lt;span&gt; &lt;/span&gt;기존&lt;span&gt; &lt;/span&gt;코드들을&lt;span&gt; &lt;/span&gt;리팩토링해가는&lt;span&gt; &lt;/span&gt;과정이&lt;span&gt; &lt;/span&gt;소소한&lt;span&gt; &lt;/span&gt;즐거움이&lt;span&gt; &lt;/span&gt;되기도&lt;span&gt; &lt;/span&gt;했다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;1380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btOYDI/btsC4tjj1tc/85e97TgO1PSdKU0cY84s0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btOYDI/btsC4tjj1tc/85e97TgO1PSdKU0cY84s0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btOYDI/btsC4tjj1tc/85e97TgO1PSdKU0cY84s0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtOYDI%2FbtsC4tjj1tc%2F85e97TgO1PSdKU0cY84s0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1380&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;1380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dje4AH/btsC7no5Zdw/1liuYgbEmZMDoxMz7Q5sJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dje4AH/btsC7no5Zdw/1liuYgbEmZMDoxMz7Q5sJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dje4AH/btsC7no5Zdw/1liuYgbEmZMDoxMz7Q5sJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdje4AH%2FbtsC7no5Zdw%2F1liuYgbEmZMDoxMz7Q5sJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1580&quot; height=&quot;1022&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) 운동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재택근무를&lt;span&gt; &lt;/span&gt;하면서&lt;span&gt; &lt;/span&gt;살이&lt;span&gt; &lt;/span&gt;너무&lt;span&gt; &lt;/span&gt;쪘기도&lt;span&gt; &lt;/span&gt;했고&lt;span&gt; &lt;/span&gt;작년&lt;span&gt; &lt;/span&gt;말부터&lt;span&gt; &lt;/span&gt;시작된&lt;span&gt; &lt;/span&gt;목&lt;span&gt; &lt;/span&gt;디스크가&lt;span&gt; &lt;/span&gt;점점&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;심해지는&lt;span&gt; &lt;/span&gt;관계로&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;이상&lt;span&gt; &lt;/span&gt;운동을&lt;span&gt; &lt;/span&gt;미룰&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;없었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wdcol/btsC1nKUs8z/oxmdJXjUDDoUMl9nxkDil1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wdcol/btsC1nKUs8z/oxmdJXjUDDoUMl9nxkDil1/img.png&quot; data-alt=&quot;거북목 돼지 그 자체&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wdcol/btsC1nKUs8z/oxmdJXjUDDoUMl9nxkDil1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwdcol%2FbtsC1nKUs8z%2FoxmdJXjUDDoUMl9nxkDil1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;341&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;거북목 돼지 그 자체&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;8&lt;/span&gt;월부터&lt;span&gt; &lt;/span&gt;헬스장에&lt;span&gt; &lt;/span&gt;등록하고&lt;span&gt; &lt;/span&gt;회사에서&lt;span&gt; &lt;/span&gt;열린&lt;span&gt; &lt;/span&gt;인바디&lt;span&gt; &lt;/span&gt;챌린지도&lt;span&gt; &lt;/span&gt;참여하기로&lt;span&gt; &lt;/span&gt;했다&lt;span&gt;. &lt;/span&gt;그리고&lt;span&gt; &lt;/span&gt;함께&lt;span&gt; &lt;/span&gt;운동하는&lt;span&gt; &lt;/span&gt;팀원분들과&lt;span&gt; &lt;/span&gt;운동&lt;span&gt; &lt;/span&gt;인증&lt;span&gt; &lt;/span&gt;챌린지를&lt;span&gt; &lt;/span&gt;시작해서&lt;span&gt; &lt;/span&gt;서로의&lt;span&gt; &lt;/span&gt;러닝메이트가&lt;span&gt; &lt;/span&gt;되어&lt;span&gt; &lt;/span&gt;주기로&lt;span&gt; &lt;/span&gt;했다&lt;span&gt;. &lt;/span&gt;그&lt;span&gt; &lt;/span&gt;결과&lt;span&gt; &lt;/span&gt;인바디&lt;span&gt; &lt;/span&gt;점수도&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;올랐고&lt;span&gt;, &lt;/span&gt;건강검진&lt;span&gt; &lt;/span&gt;결과&lt;span&gt; &lt;/span&gt;작년에&lt;span&gt; &lt;/span&gt;비해&lt;span&gt; &lt;/span&gt;훨씬&lt;span&gt; &lt;/span&gt;몸이&lt;span&gt; &lt;/span&gt;건강해져서&lt;span&gt; &lt;/span&gt;만족스러웠다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-01-05-00-19-10.jpeg&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQvSaW/btsCZ7uJZ2A/xXO0bYvPlRU0ahtqCWG0HK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQvSaW/btsCZ7uJZ2A/xXO0bYvPlRU0ahtqCWG0HK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQvSaW/btsCZ7uJZ2A/xXO0bYvPlRU0ahtqCWG0HK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQvSaW%2FbtsCZ7uJZ2A%2FxXO0bYvPlRU0ahtqCWG0HK%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;640&quot; height=&quot;561&quot; data-filename=&quot;KakaoTalk_Photo_2024-01-05-00-19-10.jpeg&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난&lt;span&gt; 2023&lt;/span&gt;년&lt;span&gt; &lt;/span&gt;한&lt;span&gt; &lt;/span&gt;해의&lt;span&gt; &lt;/span&gt;삶을&lt;span&gt; &lt;/span&gt;되돌아보면&lt;span&gt; &lt;/span&gt;다음과&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;부분에서&lt;span&gt; &lt;/span&gt;만족스러웠다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업무 수행 능력의 발전&lt;/li&gt;
&lt;li&gt;짧은 주기로 목표를 설정하고 달성해나간 점&lt;/li&gt;
&lt;li&gt;건강이 많이 좋아진 점&lt;/li&gt;
&lt;/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;li&gt;일과 삶의 분리가 잘 안된 점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 일과 삶이 잘 분리가 되지 않는 점은 동료 피드백에서 받았던 내용이기도 해서 반드시 개선해야 할 사항이다. 재택근무를 많이 하다 보니 좋은 점도 많지만 삶의 공간 = 업무 공간이 되어 버려서 삶과 일의 경계가 너무 무너져버렸다. 다행히(?) 이제는 재택근무가 줄어들고 출근 횟수가 많아져서 자연스레 이 부분은 해결되리라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2024년에는 밖으로 뻗어나가는 성장보다는 내적인 성장에 집중하면서 좀 더 밀도 있는 사람이 되고 싶다. 기본기를 좀 더 연마하고, 많은 것들을 시도하는 것보다 선택과 집중을 잘해야겠다는 생각이 든다. 마지막으로 작은 목표 몇 가지 남기고 글을 마친다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운전 연습을 많이 해서 아버지 대신 운전하기&lt;/li&gt;
&lt;li&gt;영어 회화 꾸준하게 연습하기&lt;/li&gt;
&lt;li&gt;해외여행 (이건 작년에도 설정했던 목표인데 실패해서..)&lt;/li&gt;
&lt;li&gt;한 달마다 2권씩 책읽기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>회고</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/380</guid>
      <comments>https://cotak.tistory.com/380#entry380comment</comments>
      <pubDate>Fri, 5 Jan 2024 01:04:01 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 지식공유자 데뷔전</title>
      <link>https://cotak.tistory.com/379</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자라는 직업을 선택하기 전 하고 싶은 직업이 여럿 있었는데 그 중 하나가 선생님이었다. 어렸을 적부터 친구들과 공부하다가 모르는 부분을 서로 알려주는 걸 좋아했었는데 그 영향이 아닐까 싶다. 개발자로 진로를 완전히 정한 후에도 현재 회사에 입사하기 전 면접을 봤던 회사에서 한 임원분이 커리어를 어떻게 이어가고 싶냐고 질문했을 때 사내에서 신입 개발자 교육을 전문으로 하는 사람이 되고 싶다고 막연하게 얘기했었다. 지금 생각하면 부끄럽기도 하지만, 교육이라는 큰 틀 안에서 책을 쓰거나 강의를 만들어보겠다는 꿈은 항상 가지고 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;여하튼 회사에 들어오고 나서는 나의 부족함을 매순간 절실히 깨달았고, 지식을 나누긴 커녕 지식의 구멍들을 채우기 위해 노력해왔다. 그렇게 일종의 갈급함을 채워가던 와중, HR쪽에서 팀스터디를 한 내용을 바탕으로 지식 공유 세션을 진행해줄 수 있냐는 제의가 들어왔다. 처음엔 한사코 거절하려 했다. 최근 업무가 너무 바쁘기도 했고, 무엇보다도 내 자신의 부족함을 스스로 잘 알기 때문이었다. 그럼에도 불구하고 이 회사에서 정말 후회없이 내가 할 수 있는 모든 것을 해보겠다는 그 각오 때문에 무모하게 도전하게 되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;팀 스터디는 '이펙티브 코틀린' 책으로 진행했으므로 마땅히 주제도 이펙티브 코틀린이 되었어야 했으나.. 책의 구성상 한 시간 남짓의 세션에서 책의 내용을 온전히 녹여내기는 힘들다고 생각했다. 우리 회사는 C# 기반의 레거시가 굉장히 많은 회사이고, 최근엔 모두 자바로 프로젝트를 진행하고 있으나 코틀린을 주언어로 사용하는 팀은 우리 팀을 제외하고는 거의 없다. 이러한 점을 고려해봤을 때 어느 정도의 코틀린 실무 경험이 있어야 제대로 읽을 수 있는 이펙티브 코틀린에 대해 열심히 떠들어봤자 의미없는 일이었을 것이다. 따라서 기초적이면서도 꼭 알면 좋겠다 싶은 코틀린의 내용을 주로 다루되, 이펙티브 코틀린 각 장의 핵심 내용에 대해서 설명하고자 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8EzTP/btsBrengHvj/bz0b1S1u6AzQ7OJJK0oZ71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8EzTP/btsBrengHvj/bz0b1S1u6AzQ7OJJK0oZ71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8EzTP/btsBrengHvj/bz0b1S1u6AzQ7OJJK0oZ71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8EzTP%2FbtsBrengHvj%2Fbz0b1S1u6AzQ7OJJK0oZ71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;492&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 크게 1부/2부로 나눠 1부에서 코틀린 기초에 대해 다루고, 2부에서 이펙티브 코틀린을 다루고자 했으나... 실제로 준비하는 과정에서 실무에서 경험했던 것들을 더 녹이려 하다보니 1부의 양이 생각보다 많아졌고, 준비한 이펙티브 코틀린의 내용 자체가 1부의 내용과 연결성이 없는 느낌도 들었다. 그래서 방향을 바꿔 이펙티브 코틀린의 책 내용은 앞의 1부에서 설명한 내용으로 충분히 이해가 가능한 수준으로 2~3개 아이템 정도의 내용을 짧게 요약했고, 책의 내용보다는 어떤 사람이 이 책을 읽어야 하는지, 어떻게 읽거나 공부하면 좋을지에 대한 내용을 이야기 했다. 그리고 마지막으로 3부를 추가해서 코틀린 기본기를 쌓는 데 도움이 됐던 강의, 책, 사이트 등을 소개했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;전날에 여러 번 연습하다 보니 오히려 당일에 목 컨디션이 안 좋았고, 목감기까지 겹쳐버렸다. 거기에 너무 긴장해버린 탓에 1L 텀블러에 담아둔 물을 모조리 마셔버렸다. 하고나니 스스로 아쉬운 부분이 너무 많았고, 버벅였던 순간들만이 떠올랐다. 팀원분들이 화상회의 속의 내 모습을 캡쳐해서 보여주셨는데, 긴장해서 내내 입술에 손을 올리고 있었다..ㅎ&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아쉬움을 뒤로 한 채 세션을 마치고 나니 굉장히 후련했다. 동시에 안 좋은 피드백에 대한 두려움도 생겼다. 그런데 의외로 많은 분들이 좋게 말씀해주셨고, 다음에 코루틴 강의도 해달라는 말씀을 주셔서 굉장히 뿌듯했다(물론 코루틴 강의는 하고 싶어도 너무 어려워서 하려면 한참 뒤에나 할 수 있을 것이다..). 반대로 함께 업무를 하는 팀원께서는 내용 구성의 측면이나 말하는 속도에서 조금은 아쉬웠다는 의견도 주셨다. 이런&amp;nbsp;피드백들 하나하나가 정말 큰 도움이 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-05 오전 12.26.23.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCWbA/btsKdw8WPkY/187dWYDIjBmO6iIWdKbbF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCWbA/btsKdw8WPkY/187dWYDIjBmO6iIWdKbbF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCWbA/btsKdw8WPkY/187dWYDIjBmO6iIWdKbbF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCWbA%2FbtsKdw8WPkY%2F187dWYDIjBmO6iIWdKbbF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1090&quot; height=&quot;494&quot; data-filename=&quot;스크린샷 2023-12-05 오전 12.26.23.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;앞으로 이렇게 사람들 앞에서 강의 비스무레한 것을 할 기회가 언제 또 있을지 모르겠지만은, 이번 경험을 발판삼아 더 나은 지식 공유자가 되고 싶다!&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/379</guid>
      <comments>https://cotak.tistory.com/379#entry379comment</comments>
      <pubDate>Tue, 5 Dec 2023 00:38:58 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 쿠폰적용가 성능 개선기</title>
      <link>https://cotak.tistory.com/378</link>
      <description>&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;지난 5월 지마켓 사이트 내 상품 상세 페이지에 쿠폰적용가(이하 VIP 쿠폰적용가)를 도입했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.gmarket.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.gmarket.com/89&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700308477841&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;G마켓 쿠폰적용가 도입기 - 캐시를 중심으로&quot; data-og-description=&quot;안녕하세요. 저는 Club &amp;amp; Discount Engineering 팀에서 지마켓 할인/쿠폰 개발 업무를 맡고 있는 윤영택입니다. 저는 올해 상반기에 G마켓 쿠폰적용가 개발에 참여했으며, 그 과정을 본 포스팅을 통해 나&quot; data-og-host=&quot;dev.gmarket.com&quot; data-og-source-url=&quot;https://dev.gmarket.com/89&quot; data-og-url=&quot;https://dev.gmarket.com/89&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/w4uuD/hyUynTFmx6/XaLHQwECUkz5UBR8vd35bK/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/biosZB/hyUypcS89D/MUVMsWrYzHTZR79Bg9vLeK/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/5dWfG/hyUylO6gZk/XgdEFhNJRP4MvCTrh93HQK/img.png?width=1283&amp;amp;height=797&amp;amp;face=0_0_1283_797&quot;&gt;&lt;a href=&quot;https://dev.gmarket.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.gmarket.com/89&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/w4uuD/hyUynTFmx6/XaLHQwECUkz5UBR8vd35bK/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/biosZB/hyUypcS89D/MUVMsWrYzHTZR79Bg9vLeK/img.png?width=299&amp;amp;height=589&amp;amp;face=0_0_299_589,https://scrap.kakaocdn.net/dn/5dWfG/hyUylO6gZk/XgdEFhNJRP4MvCTrh93HQK/img.png?width=1283&amp;amp;height=797&amp;amp;face=0_0_1283_797');&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;G마켓 쿠폰적용가 도입기 - 캐시를 중심으로&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 저는 Club &amp;amp; Discount Engineering 팀에서 지마켓 할인/쿠폰 개발 업무를 맡고 있는 윤영택입니다. 저는 올해 상반기에 G마켓 쿠폰적용가 개발에 참여했으며, 그 과정을 본 포스팅을 통해 나&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.gmarket.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;사이트 내 쿠폰적용가를 노출하고자 하는 니즈가 늘어나면서 검색 결과 페이지에도 쿠폰적용가(이하 SRP 쿠폰적용가)를 도입하게 되었는데, 프로젝트를 진행하며 병목현상을 제거하여 성능을 향상한 경험을 공유하고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2536&quot; data-origin-height=&quot;1914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B1cC4/btsAAMdXNaK/E0eVoL4EAK5xjtwvdIkUWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B1cC4/btsAAMdXNaK/E0eVoL4EAK5xjtwvdIkUWk/img.png&quot; data-alt=&quot;SRP 쿠폰적용가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B1cC4/btsAAMdXNaK/E0eVoL4EAK5xjtwvdIkUWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB1cC4%2FbtsAAMdXNaK%2FE0eVoL4EAK5xjtwvdIkUWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2536&quot; height=&quot;1914&quot; data-origin-width=&quot;2536&quot; data-origin-height=&quot;1914&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SRP 쿠폰적용가&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;기존의 VIP 쿠폰적용가에 비해 SRP 쿠폰적용가는 타임아웃이 훨씬 짧았다. 구체적인 수치를 말할 순 없지만 대략 1/4 수준이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 검색 결과 페이지 자체가 불러들이는 데이터가 훨씬 많기 때문이 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이런 이유로 SRP 쿠폰적용가는 기존 VIP 쿠폰적용가보다 빠른 성능이 요구됐지만, 실제 성능 테스트 결과 목표에 훨씬 못 미치는 결과가 나왔다. 목표는 평균 100ms이었으나, 실제로는 2배 정도인 200ms를 상회하는 수준이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APM에서 확인해본 결과 병렬/동시성 처리가 되지 않고, 순차적으로 작업이 이뤄져서 병목현상이 발생하는 포인트를 발견할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1차적인 문제는 레디스 연산이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (79) (1).png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC4wCl/btsAySyZLp7/idVeE2ENkletjqWcf1cvOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC4wCl/btsAySyZLp7/idVeE2ENkletjqWcf1cvOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC4wCl/btsAySyZLp7/idVeE2ENkletjqWcf1cvOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC4wCl%2FbtsAySyZLp7%2FidVeE2ENkletjqWcf1cvOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;169&quot; data-filename=&quot;image (79) (1).png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;169&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ac391d6f-e3f5-40f6-852e-d6ccb709e850 (1).png&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lYaz0/btsAAQgnckc/Kaz0iIFISqaf4jM3rHyYsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lYaz0/btsAAQgnckc/Kaz0iIFISqaf4jM3rHyYsK/img.png&quot; data-alt=&quot;LRANGE&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lYaz0/btsAAQgnckc/Kaz0iIFISqaf4jM3rHyYsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlYaz0%2FbtsAAQgnckc%2FKaz0iIFISqaf4jM3rHyYsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;843&quot; height=&quot;115&quot; data-filename=&quot;ac391d6f-e3f5-40f6-852e-d6ccb709e850 (1).png&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LRANGE&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 해결&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) SISMEMBER&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbQKsW/btsAyUXRuPC/N4uti2VPPygpWZEa8ija1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbQKsW/btsAyUXRuPC/N4uti2VPPygpWZEa8ija1K/img.png&quot; data-alt=&quot;SISMEMBER&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbQKsW/btsAyUXRuPC/N4uti2VPPygpWZEa8ija1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbQKsW%2FbtsAyUXRuPC%2FN4uti2VPPygpWZEa8ija1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1384&quot; height=&quot;780&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SISMEMBER&lt;/figcaption&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;Set 타입에서 사용되는 연산으로, 특정 값이 Set 안에 존재하는지 여부를 반환한다&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;비노출하기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; &lt;/span&gt;예외조건&lt;span&gt; &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;시&lt;span&gt; 호출되고 있었는데, 이때 로컬 캐싱이 되어 있었다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700310288043&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Cacheable(value = [CacheName.EXCLUDED_ITEM], key = &quot;#itemNo&quot;)
fun isExcludedItem(itemNo: Long): Boolean {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기까지는 괜찮았는데, 문제는 Spring Webflux + Kotlin Coroutine 환경의 프로젝트 내에서 Mono나 Flux와 같은 리액티브 타입 대신 논 리액티브 타입을 로컬 캐싱하려다 보니 runBlocking 내에서 레디스 연산을 호출하고 있었던 것이었다....&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700310495682&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun existsExcludedItem(itemNo: Long): Boolean {
    // 여기서 메인 쓰레드가 블락됨..
    return runBlocking { 
        reactiveRedisTemplate.opsForSet().isMember(key, itemNo.toString()).awaitFirstOrDefault(false) 
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking은 코루틴 빌더의 하나로서, 내부의 코루틴 연산이 끝날 때까지 메인 쓰레드를 blocking시키는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특징 때문에 공식문서에서도 메인 함수나 테스트 내에서만 사용할 것을 권장하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2406&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YqlGq/btsAzBqbHYS/QArifF4J54g9Ci8TkXgeaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YqlGq/btsAzBqbHYS/QArifF4J54g9Ci8TkXgeaK/img.png&quot; data-alt=&quot;runBlocking&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YqlGq/btsAzBqbHYS/QArifF4J54g9Ci8TkXgeaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYqlGq%2FbtsAzBqbHYS%2FQArifF4J54g9Ci8TkXgeaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2406&quot; height=&quot;696&quot; data-origin-width=&quot;2406&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;runBlocking&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;로컬 캐싱을 통한 성능적 이점은 분명 있었지만, 가용 쓰레드가 적어서 blocking 연산이 치명적인 Webflux의 특성상 제거하는 편이 좋겠다고 판단했다. 동시에 단건 조회 대신 다건 조회를 지원하는 SMISMEMBER라는 좋은 대체제가 있기에 로컬 캐싱 대신 레디스 상에서 다건 조회하는 방식으로 문제를 해결했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfWXDC/btsAxVbKbm7/mofszIvEzKymkyJvnANsnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfWXDC/btsAxVbKbm7/mofszIvEzKymkyJvnANsnK/img.png&quot; data-alt=&quot;SMISMEMBER&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfWXDC/btsAxVbKbm7/mofszIvEzKymkyJvnANsnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfWXDC%2FbtsAxVbKbm7%2FmofszIvEzKymkyJvnANsnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1386&quot; height=&quot;980&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SMISMEMBER&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FqYnG/btsAuep6AIa/VxSzQCfL9LpgNivq6tGy60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FqYnG/btsAuep6AIa/VxSzQCfL9LpgNivq6tGy60/img.png&quot; data-alt=&quot;레디스에 나가는 쿼리의 개수가 확연히 줄었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FqYnG/btsAuep6AIa/VxSzQCfL9LpgNivq6tGy60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFqYnG%2FbtsAuep6AIa%2FVxSzQCfL9LpgNivq6tGy60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;412&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;412&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 data-ke-size=&quot;size23&quot;&gt;2) LLEN &amp;amp; LRANGE&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmMOWC/btsAAPV3eJ4/Nwr8I3eaQ64Zx1Q9McvkS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmMOWC/btsAAPV3eJ4/Nwr8I3eaQ64Zx1Q9McvkS1/img.png&quot; data-alt=&quot;LLEN&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmMOWC/btsAAPV3eJ4/Nwr8I3eaQ64Zx1Q9McvkS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmMOWC%2FbtsAAPV3eJ4%2FNwr8I3eaQ64Zx1Q9McvkS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1382&quot; height=&quot;886&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LLEN&lt;/figcaption&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;span&gt;List &lt;/span&gt;타입에서&lt;span&gt; &lt;/span&gt;사용되는&lt;span&gt; &lt;/span&gt;연산으로&lt;span&gt;, &lt;/span&gt;리스트의&lt;span&gt; &lt;/span&gt;사이즈를&lt;span&gt; &lt;/span&gt;반환한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;1174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5rlzG/btsAzSrPcvv/lSTyIbAbIAt6SyefxvPav0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5rlzG/btsAzSrPcvv/lSTyIbAbIAt6SyefxvPav0/img.png&quot; data-alt=&quot;LRANGE&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5rlzG/btsAzSrPcvv/lSTyIbAbIAt6SyefxvPav0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5rlzG%2FbtsAzSrPcvv%2FlSTyIbAbIAt6SyefxvPav0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1386&quot; height=&quot;1174&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;1174&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LRANGE&lt;/figcaption&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;List 타입에서 사용되는 연산으로, 특정 오프셋을 기준으로 데이터를 반환한다&lt;/li&gt;
&lt;li&gt;쉽게&lt;span&gt; &lt;/span&gt;말해&lt;span&gt; List&lt;/span&gt;의&lt;span&gt; &lt;/span&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;p data-ke-size=&quot;size16&quot;&gt;쿠폰적용가 API 내부적으로 상품별로 쿠폰의 사용 가/불가 여부를 리스트로 캐싱하고 있다. 이는 리스트에서 오래된 쿠폰을 밀어내는(pop) 형태로 동작하는데, 여기서 리스트의 현재 사이즈를 계산하기 위해 LLEN 연산이 호출되었다. 그리고 상품별 쿠폰 리스트의 요소들을 조회할 때 LRANGE 연산이 사용되었다. (이와 관련된 자세한 내용은 &lt;a href=&quot;https://dev.gmarket.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 참고)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwAU12/btsAzASm8yg/BAfSMeu5l9kUTP5BrZYAF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwAU12/btsAzASm8yg/BAfSMeu5l9kUTP5BrZYAF1/img.png&quot; data-alt=&quot;상품별 쿠폰&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwAU12/btsAzASm8yg/BAfSMeu5l9kUTP5BrZYAF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwAU12%2FbtsAzASm8yg%2FBAfSMeu5l9kUTP5BrZYAF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1974&quot; height=&quot;570&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;상품별 쿠폰&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 결론부터 말하자면 루프 안에서 레디스를 조회한 것이 화근이었다. 쿠폰적용가는 크게 두 가지 파트(쿠폰적용가 계산을 위한 정보 조회 + 쿠폰적용가 계산)로 나눠서 계산되는데, 상품별로 루프를 돌며 쿠폰적용가를 계산하는 부분에서 상품별 쿠폰을 조회한 것이었다. 이미 상품번호를 들고 있기 때문에 상품별 쿠폰은 사실 루프 바깥에서 조회해도 되는 정보였다.&lt;/p&gt;
&lt;pre id=&quot;code_1700312189975&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AS-IS

// 1. 쿠폰적용가 계산을 위한 정보 조회 (할인, 쿠폰, 배송비 등)
// (생략...)

items.map {
    // 상품별 쿠폰 조회 (루프 안에서 반복 조회)
    val couponsPerItem = redisService.findCouponsPerItem(it.no)
    // 2. 상품별 쿠폰적용가 계산
    // (생략...)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 바깥으로 빼고, 비동기로 수행되도록 코드를 수정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1700311982835&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TO-BE

// 1. 쿠폰적용가 계산을 위한 정보 조회 (할인, 쿠폰, 배송비 등)
// (생략...)

// 상품별 쿠폰 조회 (루프를 돌기 전에 한 번에 조회)
val couponsPerItem = coroutineScope {
    val job = items.assciate {
        it.itemNo to async {
            redisService.findCouponsPerItem(it.no)
        }
    }
    job.await()
}

items.map { 
    // 2. 상품별 쿠폰적용가 계산
    // (생략...)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깔끔하게 비동기로 처리되는 것을 확인할 수 있었다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp1VaP/btsAxXm52i5/1DX0L3TnvhklQ2PvyjtGu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp1VaP/btsAxXm52i5/1DX0L3TnvhklQ2PvyjtGu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp1VaP/btsAxXm52i5/1DX0L3TnvhklQ2PvyjtGu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp1VaP%2FbtsAxXm52i5%2F1DX0L3TnvhklQ2PvyjtGu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;826&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이 정도면 어느 정도 해결된 게 아닐까하고 생각했지만, 끝이 아니었다...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHCKi/btsAvlhZpUu/iRrqdsK9Uv5hAQnFh6PNB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHCKi/btsAvlhZpUu/iRrqdsK9Uv5hAQnFh6PNB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHCKi/btsAvlhZpUu/iRrqdsK9Uv5hAQnFh6PNB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHCKi%2FbtsAvlhZpUu%2FiRrqdsK9Uv5hAQnFh6PNB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;562&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 코루틴 스코프 함수 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 레디스 연산을 개선했음에도 불구하고 쿠폰적용가를 계산하는 부분이 순차 처리가 되고 있음을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;coroutine (1).png&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uXCHY/btsAzdiEZKW/dlKXMEYNGyOLGEual0VVs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uXCHY/btsAzdiEZKW/dlKXMEYNGyOLGEual0VVs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uXCHY/btsAzdiEZKW/dlKXMEYNGyOLGEual0VVs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuXCHY%2FbtsAzdiEZKW%2FdlKXMEYNGyOLGEual0VVs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1041&quot; height=&quot;154&quot; data-filename=&quot;coroutine (1).png&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 코루틴 스코프 내에서 비동기로 처리했다고 생각했는데 의아했다.&lt;/p&gt;
&lt;pre id=&quot;code_1700313493427&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;items.map {
    val res = coroutineScope {
        // 쿠폰 적용가 계산을 비동기로 수행
        async {
            calculateCouponPrice(...)
        }
    }
}&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;이 문제는 coroutineScope 함수의 잘못된 이해에서 비롯됐다. &lt;span&gt;coroutineScope &lt;/span&gt;함수는&lt;span&gt; &lt;/span&gt;블럭&lt;span&gt; &lt;/span&gt;내의&lt;span&gt; &lt;/span&gt;코루틴이&lt;span&gt; &lt;/span&gt;모두&lt;span&gt; &lt;/span&gt;완료될&lt;span&gt; &lt;/span&gt;때까지&lt;span&gt; &lt;/span&gt;기다린다&lt;span&gt;. &lt;/span&gt;코루틴은&lt;span&gt;&amp;nbsp;&lt;/span&gt;경량화된&lt;span&gt; &lt;/span&gt;스레드라고&lt;span&gt;&amp;nbsp;간단하게&amp;nbsp;&lt;/span&gt;생각하면 된다&lt;span&gt;. &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;프로세스&lt;span&gt; &lt;/span&gt;안에&lt;span&gt; &lt;/span&gt;여러&lt;span&gt; &lt;/span&gt;스레드가&lt;span&gt; &lt;/span&gt;존재할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있듯이&lt;span&gt;, &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;스레드&lt;span&gt; &lt;/span&gt;안에&lt;span&gt; &lt;/span&gt;여러&lt;span&gt; &lt;/span&gt;코루틴이&lt;span&gt; &lt;/span&gt;존재하여&lt;span&gt; &lt;/span&gt;작업을&lt;span&gt; &lt;/span&gt;수행할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다. 자세한 내용은 &lt;a href=&quot;https://kotlinlang.org/docs/coroutines-overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;를 참고하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLbV43/btsAxXOif6g/SST2CaapNBzunCN1SVwF0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLbV43/btsAxXOif6g/SST2CaapNBzunCN1SVwF0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLbV43/btsAxXOif6g/SST2CaapNBzunCN1SVwF0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLbV43%2FbtsAxXOif6g%2FSST2CaapNBzunCN1SVwF0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1160&quot; height=&quot;272&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;272&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;즉, 아래와 같이 비동기로 처리하려고 해도 실제로는 blocking이 걸린다.&lt;/p&gt;
&lt;pre id=&quot;code_1700313701348&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;items.map {
    // 실제로는 blocking이 걸려서 순차적으로 수행
    val res = coroutineScope {
        // 쿠폰 적용가 계산을 비동기로 수행
        async {
            calculateCouponPrice(...)
        }
    }
}&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;반면, CoroutineScope 함수는 블럭 내의 코루틴이 모두 완료될 때까지 기다리지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP3wyk/btsAwqcrXN0/aGBiUTs0ERgYwzl4HXk5jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP3wyk/btsAwqcrXN0/aGBiUTs0ERgYwzl4HXk5jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP3wyk/btsAwqcrXN0/aGBiUTs0ERgYwzl4HXk5jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP3wyk%2FbtsAwqcrXN0%2FaGBiUTs0ERgYwzl4HXk5jK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2290&quot; height=&quot;504&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;504&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;기존에 coroutineScope 함수로 코루틴 스코프를 만들던 부분을, CoroutineScope 함수로 바꿔서 문제를 해결할 수 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1700313862041&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;items.map {
    // 기다리지 않는다! - 작업은 별도 쓰레드에서 실행
    val res = CoroutineScope(Dispatchers.Default) {
        // 쿠폰 적용가 계산을 비동기로 수행
        async {
            calculateCouponPrice(...)
        }
    }
}
 
// ...
 
res.await() // 코루틴 작업이 완료되어야 반환값을 얻을 수 있으므로 기다림&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 비동기 처리가 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;1554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xSYFB/btsAzVhK5sD/Y7Yqo1G7c6vnlipWSkGHNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xSYFB/btsAzVhK5sD/Y7Yqo1G7c6vnlipWSkGHNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xSYFB/btsAzVhK5sD/Y7Yqo1G7c6vnlipWSkGHNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxSYFB%2FbtsAzVhK5sD%2FY7Yqo1G7c6vnlipWSkGHNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;563&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;1554&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;결과적으로 개선 작업 전 보다 대략 41.7% 가량 향상된 응답속도를 갖게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 더불어 공통로직을 쓰는 다른 쿠폰적용가 API들의 경우에도 10% ~ 20% 내외로 응답속도가 향상되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_1.png&quot; data-origin-width=&quot;2740&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAPCcJ/btsAwrI8UyY/k5JWplkcsglS9WfcqB8Sh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAPCcJ/btsAwrI8UyY/k5JWplkcsglS9WfcqB8Sh0/img.png&quot; data-alt=&quot;성능 개선 전 - 170ms&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAPCcJ/btsAwrI8UyY/k5JWplkcsglS9WfcqB8Sh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAPCcJ%2FbtsAwrI8UyY%2Fk5JWplkcsglS9WfcqB8Sh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2740&quot; height=&quot;254&quot; data-filename=&quot;edited_1.png&quot; data-origin-width=&quot;2740&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;성능 개선 전 - 170ms&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_2.png&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRT69s/btsAuN0hRax/fwHMpoO8YWIDYm0AWvuhl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRT69s/btsAuN0hRax/fwHMpoO8YWIDYm0AWvuhl0/img.png&quot; data-alt=&quot;성능 개선 후 - 99.1ms&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRT69s/btsAuN0hRax/fwHMpoO8YWIDYm0AWvuhl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRT69s%2FbtsAuN0hRax%2FfwHMpoO8YWIDYm0AWvuhl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2668&quot; height=&quot;236&quot; data-filename=&quot;edited_2.png&quot; data-origin-width=&quot;2668&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;성능 개선 후 - 99.1ms&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 후기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 작업 같아 보이지만 APM 상의 trace만 보고 역추적해가는 과정이 쉽지만은 않았다. 비동기 작업이 워낙 많아서 작업의 흐름을 추적하는 것도 어려웠고, trace 상에 단순한 연산의 종류 말고는 정보가 거의 전무했기 때문이다. 함께 프로젝트를 진행했던 다른 팀원 한 분과 낮밤 가리지 않고 열심히 한 결과 생각보다 빠르게 문제를 해결할 수 있었다. 아직 원인불명의 오라클 쿼리가 튀는 이슈가 있어서 100% 성능이 향상된 것은 아니지만 단기간 내에 좋은 결과를 낼 수 있어서 좋은 경험이었다.&lt;/p&gt;</description>
      <category>프로그래밍 언어/Java + Kotlin</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/378</guid>
      <comments>https://cotak.tistory.com/378#entry378comment</comments>
      <pubDate>Sat, 18 Nov 2023 22:48:15 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 특정 프로퍼티 Json 직렬화/역직렬화 시 제외시키기</title>
      <link>https://cotak.tistory.com/377</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;data class로 레디스에 객체를 역직렬화하는 과정에서 다음과 같은 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 내용은 ttl이라는 필드가 non-nullable인데, 실제로는 null이 들어가서 객체 생성에 실패했다는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1699539728607&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Could not read JSON: Instantiation of [simple type, class ...] value failed for JSON property ttl due to missing (therefore NULL) value for creator parameter ttl which is a non-nullable type&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;class는 대략 다음과 같은 형태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 @JsonIgnore 애너테이션으로 직렬화/역직렬화에서 해당 프로퍼티를 제외했는데, 왜 직렬화/역직렬화에 포함된 건지 궁금했다.&lt;/p&gt;
&lt;pre id=&quot;code_1699539885091&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class RedisObject(
    val no: Long,
    val name: String,
    ...
    @JsonIgnore
    val ttl: Duration,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 스택오버플로우에서 찾을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/71034053/why-jsonignore-annotation-doesnt-work-during-deserializing-data&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/71034053/why-jsonignore-annotation-doesnt-work-during-deserializing-data&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 @JsonIgnore는 대응되는 JSON 필드를 무시한다. Java에서 Jackson은 역직렬화 시 JSON 필드가 존재하지 않는다면 대응되는 객체의 필드에 null을 세팅한다. 이는 Java에서 기본적으로 참조 타입의 변수엔 null이 담길 수 있기 때문이다. 그러나 Kotlin은 Java와 달리 non-nullable 타입이 존재한다. 여기에 null이 담기려고 해서 에러가 발생한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 @JsonIgnore를 프로퍼티에 붙여서 역직렬화 시 해당 프로퍼티 값으로 null이 세팅되려고 했으나, 프로퍼티의 타입이 non-nullable인 관계로 객체 생성이 실패한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 크게 3가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) default value 셋팅&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699540738906&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class RedisObject(
    val no: Long,
    val name: String,
    ...
    @JsonIgnore
    val ttl: Duration = Duration.ofMinutes(1),
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) nullable한 타입 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699540764988&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class RedisObject(
    val no: Long,
    val name: String,
    ...
    @JsonIgnore
    val ttl: Duration?,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 지연 초기화 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699540821538&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class RedisObject(
    val no: Long,
    val name: String,
    ...
) {
    @JsonIgnore
    lateinit var ttl: Duration
}&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;나는 default value를 넣어주거나, 타입을 바꿔주는 것은 싫어서 조금 번거롭더라도 지연 초기화(3번)를 사용해 문제를 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 따라 적절하게 선택하면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 lateinit 키워드를 사용하면 최초에 프로퍼티가 null로 초기화된다. 그래서 non-nullable한 타입으로 선언하더라도 null이 들어갈 수 있는 것이다. 실제 사용하는 시점에는 초기화가 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Java + Kotlin</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/377</guid>
      <comments>https://cotak.tistory.com/377#entry377comment</comments>
      <pubDate>Thu, 9 Nov 2023 23:43:35 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 실무에서 만난 N+1 문제 해결하기 (feat. LazyInitializationException)</title>
      <link>https://cotak.tistory.com/376</link>
      <description>&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;운영 과정에서 프론트에서 쿠폰 관련 API 호출 시 간헐적으로 타임아웃이 발생한다는 리포트가 들어왔다.&lt;br /&gt;APM을 통해 좀 더 면밀히 살펴보니 오라클 쿼리가 비정상적으로 많이 발생하고 있음을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1715&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uW7hs/btsy02XO1Bu/AKDlpjEuCGBirPw78TArmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uW7hs/btsy02XO1Bu/AKDlpjEuCGBirPw78TArmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uW7hs/btsy02XO1Bu/AKDlpjEuCGBirPw78TArmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuW7hs%2Fbtsy02XO1Bu%2FAKDlpjEuCGBirPw78TArmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1715&quot; height=&quot;221&quot; data-origin-width=&quot;1715&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;발급된 쿠폰을 조회하는 과정에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;N+1 문제&lt;/b&gt;&lt;/span&gt;와 leftjoin의 잘못된 사용으로 인한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;데이터 뻥튀기&lt;/b&gt;&lt;/span&gt;가 발생하면서 쿠폰 정책에 대한 조회가 불필요하게 이뤄진 게 화근이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIPClP/btsFWKPweaD/lLCRbzLfyEjm460lfDkWZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIPClP/btsFWKPweaD/lLCRbzLfyEjm460lfDkWZK/img.png&quot; data-alt=&quot;N+1 문제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIPClP/btsFWKPweaD/lLCRbzLfyEjm460lfDkWZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIPClP%2FbtsFWKPweaD%2FlLCRbzLfyEjm460lfDkWZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2046&quot; height=&quot;1066&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;N+1 문제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;1048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yCTNo/btsDAxyQpwa/iGOHRBBmcRNLQRCc56KgbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yCTNo/btsDAxyQpwa/iGOHRBBmcRNLQRCc56KgbK/img.png&quot; data-alt=&quot;데이터 뻥튀기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yCTNo/btsDAxyQpwa/iGOHRBBmcRNLQRCc56KgbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyCTNo%2FbtsDAxyQpwa%2FiGOHRBBmcRNLQRCc56KgbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1922&quot; height=&quot;1048&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;1048&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터 뻥튀기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 뻥튀기가 되는 문제는 원인이 분명했지만, N+1 문제는 의아했다. 분명 &lt;b&gt;Lazy Loading&lt;/b&gt;을 걸어놨는데, 왠지 모르게 &lt;b&gt;Eager Loading&lt;/b&gt;이 발생했다. 물론 Lazy Loading을 건다고 해서 N+1 문제가 모두 해결되는 것은 아니지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;적어도 쿠폰 정책을 참조하지 않는 경우엔 N+1이 발생하지 않았어야 했다&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;1008&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzM6MU/btsFUeYPWAm/aKJsDMgoLVRKin0XZXcqU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzM6MU/btsFUeYPWAm/aKJsDMgoLVRKin0XZXcqU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzM6MU/btsFUeYPWAm/aKJsDMgoLVRKin0XZXcqU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzM6MU%2FbtsFUeYPWAm%2FaKJsDMgoLVRKin0XZXcqU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1476&quot; height=&quot;1008&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;1008&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;코틀린은 기본적으로 모든 클래스가 final로 선언되어 있어서 &lt;b&gt;상속이 불가능&lt;/b&gt;하다. 따라서 프록시를 만들기 위해선 all open 플러그인에 어떤 클래스의 상속을 허용할 것인지 명시해줘야 한다. 바로 이 부분에서 설정이 빠져 있어서 Lazy Loading이 아닌, Eager Loading으로 동작한 것이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// build.gradle

allOpen {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;annotation(&quot;javax.persistence.Entity&quot;) // 누락된 부분
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;해당 설정을 추가하니 쿼리가 확연하게 줄어드는 것을 확인할 수 있었다. 무려 80% 이상 줄어드는 것이었다..!!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pMf8t/btsy4tf4yHM/VJbdumvJNe39QGmjjKgL0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pMf8t/btsy4tf4yHM/VJbdumvJNe39QGmjjKgL0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pMf8t/btsy4tf4yHM/VJbdumvJNe39QGmjjKgL0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpMf8t%2Fbtsy4tf4yHM%2FVJbdumvJNe39QGmjjKgL0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;186&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;여기까진 좋았다... 문제는 그 뒤에 &lt;a href=&quot;https://github.com/HomoEfficio/dev-tips/blob/master/Spring%20Data%20JPA%20-%20LazyInitialization%20%EC%97%90%EB%9F%AC%20-%20getOne().md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;LazyInitializationException&lt;/span&gt;&lt;/a&gt;가 함께 발생한 것이었다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;org.hibernate.LazyInitializationException: could not initialize proxy
	at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:170)
	...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;해당 프로젝트는 spring webflux + kotlin coroutine + JPA로 구성되어 있는 상황이었는데, R2DBC가 아닌 JDBC를 사용해서 새로운 코루틴 스코프를 열면 트랜잭션이 먹히지 않았다. 이는 JPA 트랜잭션 매니저는 트랜잭션의 상태를 쓰레드 로컬에 넣는 반면, 코루틴은 트랜잭션의 상태를 코루틴 컨텍스트에 넣기 때문이다. 좀 더 자세한 내용은 아래 링크들을 참고하되, 간단하게 요약하자면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;세션이 종료된 상태에서 JPA Lazy Loading이 발생하면서 LazyInitializationException이 발생&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;a href=&quot;https://stackoverflow.com/questions/68590209/spring-transactional-on-suspend-function&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://stackoverflow.com/questions/68590209/spring-transactional-on-suspend-function&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codeinlife.tistory.com/73&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://codeinlife.tistory.com/73&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 해결&amp;nbsp;&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 data-ke-size=&quot;size16&quot;&gt;근본적인 문제의 해결을 위해서 쿠폰 도메인을 이루고 있는 JPA Entity의 연관 관계가 무분별하게 사용된 것을 제거하고, 적절히 DTO 조회를 수행하고자 했다. 다음은 기존 JPA Entity의 관계를 간략하게 표현한 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;AS-IS&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1087&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZgX0q/btsAUpcSAVG/vhrMYhBbrPK7K5wWNJzbj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZgX0q/btsAUpcSAVG/vhrMYhBbrPK7K5wWNJzbj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZgX0q/btsAUpcSAVG/vhrMYhBbrPK7K5wWNJzbj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZgX0q%2FbtsAUpcSAVG%2FvhrMYhBbrPK7K5wWNJzbj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1087&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1087&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰이지&lt;span&gt; &lt;/span&gt;않는&lt;span&gt; Entity&lt;/span&gt;는&lt;span&gt; &lt;/span&gt;제거했고&lt;span&gt;, &lt;/span&gt;비즈니스적으로는&lt;span&gt; &lt;/span&gt;연관이&lt;span&gt; &lt;/span&gt;없으나&lt;span&gt; &lt;/span&gt;조회의&lt;span&gt; &lt;/span&gt;편리성을&lt;span&gt; &lt;/span&gt;위해&lt;span&gt; &lt;/span&gt;연관&lt;span&gt; &lt;/span&gt;관계가&lt;span&gt; &lt;/span&gt;맺어진&lt;span&gt; &lt;/span&gt;경우도&lt;span&gt; &lt;/span&gt;끊어냈다&lt;span&gt;. &lt;/span&gt;어느&lt;span&gt; &lt;/span&gt;정도&lt;span&gt; &lt;/span&gt;연관&lt;span&gt; &lt;/span&gt;관계가&lt;span&gt; &lt;/span&gt;정리된&lt;span&gt; &lt;/span&gt;후에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;레벨에서&lt;span&gt; Lazy Loading&lt;/span&gt;이&lt;span&gt; &lt;/span&gt;발생하지&lt;span&gt; &lt;/span&gt;않도록&lt;span&gt; Repository&lt;/span&gt;단에서&lt;span&gt; &lt;/span&gt;연관&lt;span&gt; &lt;/span&gt;객체를&lt;span&gt; &lt;/span&gt;함께&lt;span&gt; &lt;/span&gt;조회하게끔&lt;span&gt; &lt;/span&gt;쿼리를&lt;span&gt; &lt;/span&gt;수정했다&lt;span&gt;(&lt;/span&gt;단&lt;span&gt; &lt;/span&gt;실제&lt;span&gt; &lt;/span&gt;연관&lt;span&gt; &lt;/span&gt;객체가&lt;span&gt; &lt;/span&gt;사용되는&lt;span&gt; &lt;/span&gt;경우에&lt;span&gt; &lt;/span&gt;한해서&lt;span&gt;).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TO-BE&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rfJrd/btsy9TGJSij/TTRZcfXg5oht1SzLdrWACK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rfJrd/btsy9TGJSij/TTRZcfXg5oht1SzLdrWACK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rfJrd/btsy9TGJSij/TTRZcfXg5oht1SzLdrWACK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrfJrd%2Fbtsy9TGJSij%2FTTRZcfXg5oht1SzLdrWACK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1473&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 쿼리 수정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책, 발급, 결제수단 등을 한꺼번에 묶어서 조회해야 하는 등 좀 더 복잡한 쿼리는 DTO를 통해서 조회하는 방식으로 쿼리를 수정했다. 기존엔 아래와 같은 형태로 페치 조인을 사용해 연관 객체를 한꺼번에 조회하고 있었는데, 이런 방식으로 조회를 하면 실제론 연관 객체가 담기지 않게 될 뿐더러 데이터도 뻥튀기가 된다(사실상 이 녀석이 문제의 주범이었다!). 이러한 이유로 DTO로 조회하도록 수정해서 연관된 객체도 담아주고, 부모 객체 하나에 자식 객체들이 리스트로 담기게 해서 데이터 뻥튀기도 없앴다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;AS-IS&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Repository
class CouponPolicyRepository() {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 쿠폰정책 + 쿠폰정책옵션 + 쿠폰세부옵션 + 쿠폰결제수단을 페치 조인으로 한 번에 조회
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// But 실제론 한 번에 조회가 안 됨..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 별도의 distinct 기준도 없기 때문에 데이터도 뻥튀기가 됨..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun findAllFetch(): List&amp;lt;CouponPolicy&amp;gt;? {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return queryFactory
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.selectFrom(couponPolicy)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.innerJoin(couponPolicy.couponPolicyOption).fetchJoin()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.leftJoin(couponPolicy.couponDetailOption).fetchJoin()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.leftJoin(couponPolicy.couponPaymentMethod).fetchJoin()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.fetch()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;TO-BE&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@Repository
class CouponPolicyRepository(...) {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 쿠폰정책 + 쿠폰정책옵션 + 쿠폰세부옵션 + 쿠폰결제수단을 DTO로 한 번에 조회
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun findAllDto(): List&amp;lt;CouponPolicyDto&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return queryFactory
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.from(couponPolicy)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.leftJoin(couponPolicyOption)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.on(couponPolicyOptionEq())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.leftJoin(couponDetailOption)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.on(couponDetailOptionEq())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.leftJoin(couponPaymentMethod)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.on(couponPaymentMethodEq())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.transform(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;groupBy(couponPolicy.id).list(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;couponPolicyDto()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;) ?: emptyList()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;couponPolicy의 Id를 기준으로 연관 객체들을 매핑해주기 위해 QueryDSL transform과 list를 사용했다. 이 기능들은 이번에 처음 사용해봤는데, 무척 편리했다. 사실 QueryDSL을 사용할 일이 많이 없었지만 이번에 새롭게 쿼리를 짜면서 많은 공부가 됐던 것 같다. JPA는 뭔가 알다가도 깜빡깜빡하곤 하는데, 그럴 때마다 영한님 강의가 큰 도움이 된다..ㅋㅋㅋ&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) 결과&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 통해서 N+1 문제를 해결하면서 JPA LazyInitializationException도 함께 잡을 수 있었다.&lt;br /&gt;부수효과로 쿼리도 큰 폭으로 감소하고 성능 향상도 이루어졌다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;AS-IS&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1731&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/srKvS/btszcOEtfF2/fPcMkscLbvlMxhhFh2z9tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/srKvS/btszcOEtfF2/fPcMkscLbvlMxhhFh2z9tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/srKvS/btszcOEtfF2/fPcMkscLbvlMxhhFh2z9tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsrKvS%2FbtszcOEtfF2%2FfPcMkscLbvlMxhhFh2z9tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1731&quot; height=&quot;555&quot; data-origin-width=&quot;1731&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1725&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/26Tg2/btszbufEKa6/oDky04rXNShik1uGmm1Ls0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/26Tg2/btszbufEKa6/oDky04rXNShik1uGmm1Ls0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/26Tg2/btszbufEKa6/oDky04rXNShik1uGmm1Ls0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F26Tg2%2FbtszbufEKa6%2FoDky04rXNShik1uGmm1Ls0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1725&quot; height=&quot;211&quot; data-origin-width=&quot;1725&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;TO-BE&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1729&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVYqZA/btszejcVznG/ayEEDVw9kJQZwgLAqR8ShK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVYqZA/btszejcVznG/ayEEDVw9kJQZwgLAqR8ShK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVYqZA/btszejcVznG/ayEEDVw9kJQZwgLAqR8ShK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVYqZA%2FbtszejcVznG%2FayEEDVw9kJQZwgLAqR8ShK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1729&quot; height=&quot;556&quot; data-origin-width=&quot;1729&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ed8ism/btszbStQMm1/kkIFc0AqqxljiPxNvd31hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ed8ism/btszbStQMm1/kkIFc0AqqxljiPxNvd31hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ed8ism/btszbStQMm1/kkIFc0AqqxljiPxNvd31hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fed8ism%2FbtszbStQMm1%2FkkIFc0AqqxljiPxNvd31hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1722&quot; height=&quot;207&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쿼리 호출량&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;72.8M &amp;rarr; 31.6M (-56.59%)&lt;/li&gt;
&lt;li&gt;53.1M &amp;rarr; 22.2M (-58.24%)&lt;/li&gt;
&lt;li&gt;52.3M &amp;rarr; 17.8M (-65.9%)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성능 향상&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;쿠폰함 등에서 사용되는 발급된 쿠폰 List 조회 쿼리의 성능이 매우 빨라졌다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Average Latency 23.6ms -&amp;gt; 3.7ms (-84.32%)&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;DB 기반의 쿠폰함 API)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Average Latency 38.9ms -&amp;gt; 21.5ms (-44.62%)&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;Average Latency 58.1ms -&amp;gt; 40.1ms (-31.00%)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;확실히 캐시 기반으로 조회를 수행하는 API보다 DB 기반으로 조회를 수행하는 API에서 성능 향상이 두드러졌다.&lt;br /&gt;아무래도 쿠폰 정책보다는 발급된 쿠폰 데이터가 많다보니 관련한 쿼리/API에서 많은 개선이 이뤄진 것 같다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 이 작업을 처음 진행할 때 팀원 몇분께서 작업 영향이나 변경도에서 큰 우려를 표하셨는데, 다행히도 생각보다 짧은 시간 내에 안전하게 작업을 마칠 수 있었다. 업무 시간 외에 틈틈히 작업한 것 치고는 성과가 나름 괜찮아서 굉장히 뿌듯한 경험이었다 :)&lt;/p&gt;</description>
      <category>JPA</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/376</guid>
      <comments>https://cotak.tistory.com/376#entry376comment</comments>
      <pubDate>Wed, 25 Oct 2023 23:14:31 +0900</pubDate>
    </item>
    <item>
      <title>[도메인 주도 설계 첫걸음] Part 4. 다른 방법론 및 패턴과의 관계</title>
      <link>https://cotak.tistory.com/375</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;14장) 마이크로서비스&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마이크로서비스&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의미
&lt;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;미리 정의된 인터페이스를 사용해 하나 이상의 역량에 접근하기 위한 메커니즘 (OASIS)&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 서비스의 기능과 연동되는 다른 시스템 구성요소 모두를 쉽게 이해할 수 있음&lt;/li&gt;
&lt;li&gt;유지/보수/확장이 쉬워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;구성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스가 적고, 구현 복잡성이 높은 깊은 모듈로 구성하라&lt;/li&gt;
&lt;li&gt;즉, 외부에 노출되는 기능은 적게 하고 구현 복잡성은 높여라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마이크로서비스와 바운디드 컨텍스트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로서비스와 바운디드 컨텍스트의 관계는 비대칭이다&lt;/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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;15장) 이벤트 주도 아키텍처&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 주도 아키텍처&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의미
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 컴포넌트가 이벤트 메시지를 교환하면서 비동기적으로 서로 커뮤니케이션 하는 아키텍처 스타일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이벤트 소싱과의 차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 소싱은 상태의 변화를 일련의 이벤트로 캡처하는 방법&lt;/li&gt;
&lt;li&gt;이벤트 주도 아키텍처는 서비스 간 통신을 의미하는 반면, 이벤트 소싱은 서비스 내부에서 발생한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이벤트, 커맨드 메시지&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트는 메시지이지만, 메시지가 반드시 이벤트는 아니며 메시지에는 두 가지 유형(이벤트, 커맨드)이 있다&lt;/li&gt;
&lt;li&gt;이벤트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 발생한 변화를 설명하는 메시지&lt;/li&gt;
&lt;li&gt;이미 발생한 일을 설명하므로 과거 시제로 이벤트명을 표현해야 한다&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이벤트를 받는 쪽에서는 이벤트를 취소할 수 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;커맨드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수행돼야 할 작업을 설명하는 메시지&lt;/li&gt;
&lt;li&gt;커맨드는 거부될 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;구조&lt;/b&gt;&lt;/h4&gt;
&lt;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;메타 데이터에는 이벤트ID, 이벤트가 발생한 시간 등의 정보가 담긴다&lt;/li&gt;
&lt;li&gt;페이로드에는 이벤트가 전달하는 정보를 설명할 뿐만 아니라 이벤트의 유형도 정의한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 유형&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이벤트 알림&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 컴포넌트가 반응할 비즈니스 도메인의 변경에 관한 메시지&lt;/li&gt;
&lt;li&gt;이해 당사자에게 이벤트를 알리는 것이 목적이므로 장황하면 안 된다&lt;/li&gt;
&lt;li&gt;즉, &lt;b&gt;구독자가 이벤트에 반응하는 데 필요한 모든 정보를 이벤트에 포함해서는 안 된다&amp;nbsp;&lt;/b&gt;(명시적 질의가 추가적으로 요구됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이벤트를 통한 상태전송(ECST: Event-Carried State Transfer)&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;b&gt;상태 변경을 반영하는 모든 데이터를 포함한다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;완전한 스냅샷을 포함하거나 업데이트된 필드만을 포함할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인 이벤트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제공자의 비즈니스 도메인 내에서 발생하는 이벤트를 설명하는 메시지&lt;/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;모델링 의도에 차이가 있다 (컴포넌트 연동 vs 비즈니스 도메인 설명)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비즈니스 도메인을 모델링하고 설명하기 위한 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 이벤트는 풍부한 모델을 노출하면 안 된다&lt;/li&gt;
&lt;li&gt;도메인 이벤트에 포함된 데이터는 애그리게이트의 상태를 설명하기 위한 것이 아니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>책/도메인 주도 설계 첫걸음</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/375</guid>
      <comments>https://cotak.tistory.com/375#entry375comment</comments>
      <pubDate>Mon, 9 Oct 2023 18:17:51 +0900</pubDate>
    </item>
    <item>
      <title>[도메인 주도 설계 첫걸음] Part 3. 도메인 주도 설계 적용 실무</title>
      <link>https://cotak.tistory.com/374</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10장) 휴리스틱 설계&lt;/b&gt;&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;바운디드 컨텍스트&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;작은 바운디드 컨텍스트를 만드려는 목적으로 기능을 줄이는 방향으로 모델링하는 것보다는 그 반대로 하는 것이 낫다. 즉, &lt;b&gt;모델의 기능이 포함하는 크기 그대로 바운디드 컨텍스트를 다루는 것이 낫다&lt;/b&gt;.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;바운디드 컨텍스트를 너무 작게 설정하여 그 경계를 리팩토링하는 것은 비용이 많이 들고, 대부분의 경우 기술 부채로 남게 된다 (물리적 경계)&lt;/li&gt;&lt;li&gt;반면 넓은 바운디드 컨텍스트의 경계 또는 여러 하위 도메인에 걸친 경계는 보다 안전하다 (논리적 경계)&lt;/li&gt;&lt;li&gt;따라서 &lt;b&gt;초기에 바운디드 컨텍스트를 설계할 때는 경계를 넓게 설정하고, 추후 도메인이 쌓이게 되면 필요에 따라 작은 여러 경계로 나누어 가는 것이 좋다&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비즈니스 로직 구현 패턴&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직을 모델링하는 방법을 선택하는 휴리스틱은 다음과 같다. (아래부터 따라간다)&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;하위 도메인이 금전 또는 통화의 트랜잭션을 추적하거나, 일관된 감사 로그를 제공하거나, 또는 비즈니스에서 하위 도메인의 동작에 대한 심층적인 분석을 요청하는가? -&amp;gt; 이벤트 소싱 도메인 모델 적용&lt;/li&gt;&lt;li&gt;하위 도메인의 비즈니스 로직이 복잡한가? -&amp;gt; 도메인 모델 적용&lt;/li&gt;&lt;li&gt;하위 도메인이 복잡한 자료구조를 포함하는가? -&amp;gt; 액티브 레코드 패턴 적용&lt;/li&gt;&lt;li&gt;위 질문에 모두 해당하지 않는다면 트랜잭션 스크립트를 적용한다&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;아키텍처 패턴&lt;/b&gt;&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이벤트 소싱 도메인 모델 &amp;amp; 여러 영속 모델이 있는 경우 -&amp;gt; CQRS&lt;/li&gt;&lt;li&gt;도메인 모델 -&amp;gt; 포트와 어댑터&lt;/li&gt;&lt;li&gt;액티브 레코드 -&amp;gt; 계층형 아키텍처 (3계층)&lt;/li&gt;&lt;li&gt;트랜잭션 스크립트 -&amp;gt; 계층형 아키텍처 (4)&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트 전략&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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;단위 테스트가 가장 많고, 통합 테스트는 별로 없으며, E2E 테스트는 거의 없는 테스트 전략&lt;/li&gt; 
 &lt;li&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;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다이아몬드형 테스트&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;통합 테스트에 가장 집중하는 테스트 전략&lt;/li&gt; 
 &lt;li&gt;액티브 레코드 패턴에 적합 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;액티브 레코드 패턴의 경우 비즈니스 로직이 서비스 계층과 비즈니스 계층에 흩어지므로 두 계층의 연동에 중점을 둔다면 통합 테스트가 용이하다&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;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;E2E 테스트에 가장 집중하는 테스트 전략&lt;/li&gt; 
 &lt;li&gt;트랜잭션 스크립트에 적합 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;비즈니스 로직이 간단하고 계층의 수가 적으므로 시스템의 엔드-투-엔드 흐름을 검증하는 데 효과적이다&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;11장) 진화하는 설계 의사결정&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;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;&lt;b&gt;조직 변화&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;조직 구조의 변화에 따라 팀 간의 의사소통과 협력, 바운디드 컨텍스트를 통합하는 방식에 영향을 줄 수 있다&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인 지식&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;시간이 지남에 따라 더 많은 도메인 지식이 발견되면 그것을 전략적, 전술적 설계 의사결정을 발전시키는 데 활용해야 한다&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소프트웨어 성장&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;하위 도메인이 확장되면 더 세분화된 하위 도메인 경계를 식별하려고 노력하라&lt;/li&gt;&lt;li&gt;'여러 방면에 다재다능한' 바운디드 컨텍스트가 되는 것을 허용하는 대신 바운디드 컨텍스트에 포함된 모델이 특정 문제를 해결하는 데 중점을 두고 있는지 확인하라&lt;/li&gt;&lt;li&gt;애그리게이트의 경계를 가능한 한 작게 유지하라&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;13장) 실무에서의 도메인 주도 설계&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;회사의 목표와 전략을 파악하라&lt;/li&gt;&lt;li&gt;조직 구조와 기존 소프트웨어 설계 의사결정을 사용하여 조직의 하위 도메인과 해당 유형을 식별하라&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현대화&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;이러한 지식들을 바탕으로 현대화 전략을 계획하고, 관련 컴포넌트를 리팩터링하거나 교체함으로써 레거시 코드를 현대화라&lt;/li&gt;&lt;li&gt;이때, 반드시 점진적으로 수행해야 한다&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실용적인 도메인 주도 설계&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;DDD가 제공하는 모든 도구를 적용할 필요는 없다&lt;/li&gt;&lt;li&gt;&lt;b&gt;도메인 주도 설계는 비즈니스 도메인이 소프트웨어 설계 의사결정을 주도하게 하는 것&lt;/b&gt;이다&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인 주도 설계 확산&lt;/b&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;항상 DDD가 조직에서 널리 채택되지 않은 경우에도 도메인 주도 설계 도구를 사용할 수 있음을 명심하라&lt;/li&gt;&lt;li&gt;올바른 도구를 사용하고 동료와 논의할 때 항상 각 패턴 뒤에 있는 논리와 원칙을 사용하라&lt;/li&gt;&lt;/ul&gt;</description>
      <category>책/도메인 주도 설계 첫걸음</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/374</guid>
      <comments>https://cotak.tistory.com/374#entry374comment</comments>
      <pubDate>Sun, 8 Oct 2023 22:20:28 +0900</pubDate>
    </item>
    <item>
      <title>[이펙티브 코틀린] 아이템 50: 컬렉션 처리 단계 수를 제한하라</title>
      <link>https://cotak.tistory.com/373</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;본문은 Effective Kotlin을 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.&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;pre id=&quot;code_1695293928657&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Soso
fun List&amp;lt;Student&amp;gt;.getNames(): List&amp;lt;String&amp;gt; = this
    .map { it.name }
    .filter { it != null }
    .map { it!! }

// Better
fun List&amp;lt;Student&amp;gt;.getNames(): List&amp;lt;String&amp;gt; = this
    .map { it.name }
    .filterNotNull()

// Best
fun List&amp;lt;Student&amp;gt;.getNames(): List&amp;lt;String&amp;gt; = this
    .mapNotNull { it.name }&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;이때는 IDE의 추천 기능을 적극적으로 사용하는 것이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_KakaoTalk_Photo_2023-09-21-20-02-27.jpeg&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caaxsO/btsvbE6FFW7/3q6CvKOsy4EKMz17TQt1Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caaxsO/btsvbE6FFW7/3q6CvKOsy4EKMz17TQt1Bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caaxsO/btsvbE6FFW7/3q6CvKOsy4EKMz17TQt1Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaaxsO%2FbtsvbE6FFW7%2F3q6CvKOsy4EKMz17TQt1Bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;197&quot; data-filename=&quot;edited_KakaoTalk_Photo_2023-09-21-20-02-27.jpeg&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 표를 참고하면 자주 사용하는 컬렉션 처리 함수를 더욱 효과적으로 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_KakaoTalk_Photo_2023-09-21-20-02-27.jpeg&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u7JAj/btsvb4qDLjp/yUzQA6rhHFEKC8vJxykkH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u7JAj/btsvb4qDLjp/yUzQA6rhHFEKC8vJxykkH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u7JAj/btsvb4qDLjp/yUzQA6rhHFEKC8vJxykkH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu7JAj%2Fbtsvb4qDLjp%2FyUzQA6rhHFEKC8vJxykkH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;566&quot; data-filename=&quot;edited_KakaoTalk_Photo_2023-09-21-20-02-27.jpeg&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>책/Effective Kotlin</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/373</guid>
      <comments>https://cotak.tistory.com/373#entry373comment</comments>
      <pubDate>Thu, 21 Sep 2023 20:04:45 +0900</pubDate>
    </item>
    <item>
      <title>[이펙티브 코틀린] 아이템 43: API의 필수적이지 않은 부분을 확장 함수로 추출하라</title>
      <link>https://cotak.tistory.com/372</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;본문은 Effective Kotlin을 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.&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;1. 확장 함수의 특징&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) *확장 함수는 import를 통해 사용할 수 있다&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 함수는 멤버와 달리 별도의 import가 필요하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 이유로 대개 다른 패키지에 위치한다&lt;/li&gt;
&lt;li&gt;직접 멤버를 추가할 수 없는 경우에 사용하기 유용하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 멤버 함수보다 우선순위가 낮다&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 언급한 import해서 사용한다는 특징 때문에 같은 타입에 같은 이름으로 여러 개 만들 수 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 이름으로 다른 동작을 하는 확장 함수가 존재하는 것은 위험할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이러한 위험 가능성이 있다면 확장 함수 대신 멤버 함수를 사용하는 것이 좋다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멤버 함수는 확장 함수보다 우선순위가 높다&lt;/li&gt;
&lt;li&gt;따라서 같은 이름의 확장 함수가 존재하더라도 컴파일러는 멤버 함수를 우선적으로 호출한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) 확장 함수는 오버라이드할 수 없다&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 함수는 컴파일 시점에 정적으로 선택된다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 함수는 첫 번째 아규먼트로 리시버가 들어가는 일반 함수로 컴파일 된다&lt;/li&gt;
&lt;li&gt;&lt;i&gt;fun C.foo()&lt;/i&gt;&amp;nbsp;&amp;rarr; &lt;i&gt;fun foo('this$reciver': C)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;따라서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상속을 목적으로 설계된 요소는 확장 함수로 만들면 안 된다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4) 확장 함수는 클래스가 아닌 타입에 정의하는 것이다&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 인터페이스, nullable, 구체적인 제네릭 타입에 확장 함수를 정의할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5) *클래스 레퍼런스에서 멤버로 표시되지 않는다&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: #ee2323;&quot;&gt;&lt;b&gt;어노테이션 프로세서(annotation processor)가 따로 처리하지 않는다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;필수적이지 않은 요소를 확장 함수로 추출하면 어노테이션 프로세스로부터 숨겨진다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바와 코틀린은 많은 영역을 어노테이션 프로세서를 통해 처리한다&lt;/li&gt;
&lt;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;2. 필수적이지 않은 부분을 확장 함수로 추출하라?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 함수는 개발자로 하여금 많은 자유와 유연성을 주기 때문에 잘 사용하면 매우 유용하다&lt;/li&gt;
&lt;li&gt;또한, 확장 함수는 어노테이션 프로세스로부터 숨겨진다는 특징이 있다&lt;/li&gt;
&lt;li&gt;그러나 중복 정의를 통해 혼동을 줄 수 있다는 점과 상속을 지원하지 않다는 점에서 안전성이 필수로 요구되는 API의 필수적인 부분에서 사용하기는 꺼려진다&lt;/li&gt;
&lt;li&gt;따라서 API의 필수적이지 않은 부분을 확장 함수로 추출하면 공통적인 어노테이션 프로세싱을 피할 수 있을 뿐더러, 개발의 편리함 또한 취할 수 있다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>책/Effective Kotlin</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/372</guid>
      <comments>https://cotak.tistory.com/372#entry372comment</comments>
      <pubDate>Mon, 4 Sep 2023 23:44:13 +0900</pubDate>
    </item>
    <item>
      <title>[이펙티브 코틀린] 아이템 42: compareTo의 규약을 지켜라</title>
      <link>https://cotak.tistory.com/371</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;본문은 Effective Kotlin을 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.&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;1. compareTo의 동작원칙&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;비대칭적 동작: a &amp;gt;= b 이고, b &amp;gt;= a라면, a == b&lt;/li&gt;
&lt;li&gt;연속적 동작: a &amp;gt;= b이고, b &amp;gt;= c라면, a &amp;gt;= c&lt;/li&gt;
&lt;li&gt;코넥스적 동작: a &amp;gt;= b 또는 b &amp;gt;= a 중에 적어도 하나는 항상 true&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. compareTo의 대안&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 compareTo를 따로 정의해야 할 상황은 거의 없다. compareTo를 재정의하는 대신, 간단하게 다음의 &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;sortedBy: 하나의 프로퍼티를 기반으로 순서를 지정할 때 사용&lt;/li&gt;
&lt;li&gt;sortedWith: 하나의 프로퍼티를 기반으로 순서를 지정할 때 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sortedWith 함수의 인자로는 Compartor&amp;lt;in T&amp;gt; 타입이 필요하다&lt;/li&gt;
&lt;li&gt;compareBy 함수를 사용하면 간단하게 구현할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693834393196&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// sortedBy - 단일 프로퍼티를 기반으로 정렬
val list = listOf(&quot;aaa&quot;, &quot;cc&quot;, &quot;bbbb&quot;)
val sorted = list.sortedBy { it.length }

println(list) // [aaa, cc, bbbb]
println(sorted) // [cc, aaa, bbbb]


// sortedWith - 복수 프로퍼티를 기반으로 정렬
val sortedDates = myDates.sortedWith(compareBy { it.month }.thenBy { it.day })
println(myDates)
/*
MyDate(month=8, day=19)
MyDate(month=5, day=16)
MyDate(month=1, day=29)
MyDate(month=5, day=10)
MyDate(month=8, day=3)
*/

println(sortedDates)
/*
MyDate(month=1, day=29)
MyDate(month=5, day=10)
MyDate(month=5, day=16)
MyDate(month=8, day=3)
MyDate(month=8, day=19)
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체가 직접 Comparable&amp;lt;T&amp;gt; 인터페이스를 구현하는 형태&lt;/b&gt;로도 구현이 가능하다. 이때는 다음과 같은 규칙을 따른다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 프로퍼티를 기반으로 정렬하게끔 만든다&lt;/li&gt;
&lt;li&gt;비교에 대한 절대적인 기준이 없다면 비교하지 못하게 만든다&lt;/li&gt;
&lt;li&gt;자연스러운 순서를 갖는 객체(e.g. 측정단위, 날짜, 시간)인지 확실하지 않다면 비교기(comparator)를 사용하는 것이 좋다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자주 사용한다면 클래스의 companion 객체로 정의할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693834801664&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val name: String, val surname: String) {
    // ...
    
    companion object {
        val DISPLAY_ORDER = compareBy(User::surname, User::name)
    }
}

val sorted = names.sortedWith(User.DISPLAY_ORDER)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. compareTo 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compareTo는 다음과 같은 함수들을 통해 구현할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;compareValues: 두 값을 단순하게 비교할 때 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693835081446&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(
    val name: String,
    val surname: String
): Comparable&amp;lt;User&amp;gt; {
    override fun compareTo(other: User): Int = 
                compareValues(surname, other.surname)
}&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;compareValuesBy: 더 많은 값을 비교하거나, 선택기(selector)를 활용해서 비교할 때 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693835104593&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(
    val name: String,
    val surname: String
): Comparable&amp;lt;User&amp;gt; {
    override fun compareTo(other: User): Int = 
                compareValuesBy(this, other, { it.suername }, { it.name })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>책/Effective Kotlin</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/371</guid>
      <comments>https://cotak.tistory.com/371#entry371comment</comments>
      <pubDate>Mon, 4 Sep 2023 22:45:30 +0900</pubDate>
    </item>
    <item>
      <title>[도메인 주도 설계 첫걸음] Part 2. 전술적 설계</title>
      <link>https://cotak.tistory.com/370</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5장) 간단한 비즈니스 로직 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;트랜잭션 스크립트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프로시저&lt;/b&gt;를 기반으로 시스템의 비즈니스 로직을 구성하는 패턴&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입출력은 퍼블릭 인터페이스를 통하고 내부 구현은 프로시저로 구성된 스크립트&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로시저는 간단하고 쉬운 절차지향 스크립트(procedure script)로 구현&lt;/li&gt;
&lt;li&gt;절차지향 스크립트? 작업을 순차적으로 수행하는 것을 의미(&amp;harr; 객체지향 언어)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내부적으로는 데이터베이스에 직접 접근한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션&lt;/b&gt;을 적용해 작업이 모두 성공하거나 실패함을 보장해야 한다&lt;/li&gt;
&lt;li&gt;단순함이 최고의 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;e.g. 원천 시스템의 데이터를 변환해서 목적 시스템에 전달 (단순 DTO Query를 생각해보자)&lt;/li&gt;
&lt;li&gt;비즈니스 로직이 단순한 지원 하위 도메인에 적합&lt;/li&gt;
&lt;li&gt;어댑터 or 충돌 방지 계층에도 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비즈니스 로직이 복잡할 때는 사용하면 안 된다 (= &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;핵심 하위 도메인에는 사용하면 안 됨!&lt;/b&gt;&lt;/span&gt;)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직의 중복&lt;/li&gt;
&lt;li&gt;중복된 코드가 동기화되지 않을 때 일관성 없는 동작 발생할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;액티브 레코드&lt;/b&gt;&lt;/h3&gt;
&lt;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;데이터 접근 로직을 구현&lt;/b&gt;한다&lt;/li&gt;
&lt;li&gt;트랜잭션 스크립트로 시스템의 비즈니스 로직을 만든다&lt;/li&gt;
&lt;li&gt;액티브 레코드는 트랜잭션 스크립트와 달리 데이터베이스에 직접 접근하지 않는 대신, 트랜잭션 스크립트가 액티브 레코드 객체를 조작한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;간단한 CRUD 데이터 접근 방법을 제공하는 자료구조
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직이 포함될 수 있으나, CRUD 및 입력 유효성 검증과 같은 간단한 비즈니스 로직만 수행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;액티브 레코드는 데이터베이스 스키마 매핑의 복잡성을 해소한다는 것이 특징&lt;/li&gt;
&lt;li&gt;보다 자세한 예시는 아래 문서를 참고하자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Active_record_pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://en.wikipedia.org/wiki/Active_record_pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://guides.rubyonrails.org/active_record_basics.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://guides.rubyonrails.org/active_record_basics.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6장) 복잡한 비즈니스 로직 다루기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도메인 모델&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 비즈니스 로직을 다루기 위한 패턴&lt;/li&gt;
&lt;li&gt;CRUD 인터페이스 대신 복잡한 상태 전환, 항상 보호해야 하는 규칙인 비즈니스 규칙과 불변성을 다룬다&lt;/li&gt;
&lt;li&gt;도메인 모델 패턴은 &lt;b&gt;복잡한 비즈니스 로직을 갖는 하위 도메인에만 적용&lt;/b&gt;되므로 이를 핵심 하위 도메인으로 가정해도 좋다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도메인 모델의 구현&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;행동(behavior)과 데이터(data)를 모두 포함하는 도메인의 객체 모델&lt;/li&gt;
&lt;li&gt;DDD의 전술 패턴인 애그리게이트, 밸류 오브젝트, 도메인 이벤트, 도메인 서비스는 모두 객체 모델의 구성요소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복잡성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델에는 인프라 또는 기술적인 관심사를 피해야 한다 (e.g. 데이터베이스, 외부 API 호출 등)&lt;/li&gt;
&lt;li&gt;결국 모델은 플레인 올드 오브젝트(plain old object)가 된다 (=POJO)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유비쿼터스 언어&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 모델의 객체가 기술적 관심사가 아닌 &lt;b&gt;비즈니스 로직에 집중&lt;/b&gt;하게 해야 한다&lt;/li&gt;
&lt;li&gt;이로써 코드에서 유비쿼터스 언어를 사용하게 하고 도메인 전문가의 멘탈 모델을 따르게 하는 효과가 있다&lt;/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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 밸류 오브젝트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;값에 의해 식별&lt;/b&gt;되는 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693405691082&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data clss Color(
    val red: Int,
    val green: Int,
    val blue: Int
)&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 style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;불변 객체&lt;/b&gt;&lt;/span&gt;이다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필드 중 하나가 바뀌면 개념적으로 새로운 인스턴스가 생성되는 것이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;밸류 오브젝트를 사용하지 않고 &lt;b&gt;원시 데이터 타입에 전적으로 의존해서 비즈니스 도메인을 표현하는 것&lt;/b&gt;은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;원시 집착 코드 징후(primitive obsession code smell)&lt;/b&gt;&lt;/span&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;/li&gt;
&lt;li&gt;값이 사용되기 전에 유효성 검사 로직을 호출하게 하기 어려움&lt;/li&gt;
&lt;li&gt;유지보수가 어려움&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://refactoring.guru/ko/smells/primitive-obsession&quot;&gt;https://refactoring.guru/ko/smells/primitive-obsession&lt;/a&gt;&amp;nbsp;참고&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;밸류 오브젝트의 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명료성 향상 (변수의 의도가 명확해짐)&lt;/li&gt;
&lt;li&gt;값을 할당하기 전에 유효성 검사를 할 필요가 없어짐&lt;/li&gt;
&lt;li&gt;응집된 로직은 한 곳에서 구현되고 쉽게 테스트할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;코드에서 비즈니스 도메인의 개념을 표현&lt;/b&gt;&lt;/span&gt; &lt;b&gt;(코드에서 유비쿼터스 언어를 사용하므로!!)&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;/li&gt;
&lt;li&gt;다른 객체의 &lt;b&gt;속성을 표현하는 도메인 요소&lt;/b&gt;에 밸류 오브젝트를 사용하라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 엔티티&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;식별 필드에 의해 식별&lt;/b&gt;되는 객체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;식별 필드는 &lt;b&gt;각 엔티티 인스턴스마다 고유&lt;/b&gt;해야 한다&lt;/li&gt;
&lt;li&gt;엔티티의 식별 필드의 값은 엔티티의 &lt;b&gt;생애주기 내내 불변&lt;/b&gt;이어야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693406300030&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person(
    val personId: Id, // 식별 필드
    val name: Name
)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;밸류 오브젝트와 반대로 불변이 아니라 &lt;b&gt;변할 것으로 예상됨&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;앞서 소개한 밸류 오브젝트는 엔티티의 속성을 설명하는 것&lt;/li&gt;
&lt;li&gt;엔티티는 단독으로 구현되지 않고,&lt;b&gt; 애그리게이트 패턴의 컨텍스트에서만 구현&lt;/b&gt;된다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 애그리게이트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 + &amp;alpha;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애그리게이트는 기본적으로 엔티티&lt;/li&gt;
&lt;li&gt;따라서 명시적인 식별 필드가 필요하고, 상태가 변할 것으로 예상됨&lt;/li&gt;
&lt;li&gt;그러나 단순한 엔티티 그 이상의 존재다 (데이터의 일관성을 보호한다!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;데이터의 일관성을 보호&lt;/b&gt;&lt;/span&gt;하는 것이 존재 목적
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터의 일관성은 &lt;b&gt;애그리게이트의 비즈니스 로직을 통해서만 애그리게이트의 상태를 변경&lt;/b&gt;하게 해야 강화됨&lt;/li&gt;
&lt;li&gt;애그리게이트의 퍼블릭 인터페이스는 입력값의 유효성을 검사하고 관련된 모든 비즈니스 규칙과 불변성을 강화하는 것을 담당&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;외부 객체는 애그리게이트 내의 데이터 필드를 읽을 수만 있음&lt;/li&gt;
&lt;li&gt;결국 애그리거트는 &lt;b&gt;일관성을 강화하는 경계&lt;/b&gt;가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 경계&lt;/b&gt;로서의 애그리게이트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애그리게이트의 상태는 자신의 비즈니스 로직을 통해서만 수정될 수 있으므로 애그리게이트는 트랜잭션 경계의 역할을 함&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다중 애그리게이트 트랜잭션은 원칙적으로 지원되지 않는다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애그리게이트간 참조
&lt;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;b&gt;애그리게이트를 가능한 작게 유지하고 강력하게 일관적으로 상태를 유지할 필요가 있는 객체만 포함&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애그리게이트 외부의 객체 참조는 ID를 사용&lt;/b&gt;한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 객체가 애그리게이트 경계에 속하지 않음을 명확히 하고, 각 애그리게이트가 자신의 트랜잭션 경계를 갖게 보장함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애그리게이트 루트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애그리게이트는 엔티티의 계층 구조를 대표한다&lt;/li&gt;
&lt;li&gt;따라서 &lt;b&gt;그중 하나만 애그리게이트의 퍼블릭 인터페이스, 즉 애그리게이트 루트로 지정&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;/li&gt;
&lt;li&gt;이미 발생된 것이므로 과거형으로 명명&lt;/li&gt;
&lt;li&gt;비즈니스 도메인에서 일어난 일을 설명하고, 이벤트와 관련된 모든 필요한 데이터를 제공하는 것이 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) 도메인 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직을 구현한 상태가 없는 객체&lt;/li&gt;
&lt;li&gt;밸류 오브젝트에도 속하지 않거나 복수의 애그리게이트에 관련된 비즈니스 로직을 다루게 될 때 사용하는 패턴&lt;/li&gt;
&lt;li&gt;여러 애그리게이트의 데이터를 읽는 것이 필요한 계산 로직을 구현하는 것을 도와줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7장) 시간 차원의 모델링&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 소싱 도메인 모델 패턴&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 모델에 시간 차원을 도입&lt;/li&gt;
&lt;li&gt;애그리게이트 수명주기의 모든 변경사항을 분석하는 이벤트를 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 소싱 애그리게이트의 작업 단계&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애그리게이트의 도메인 이벤트를 로드&lt;/li&gt;
&lt;li&gt;분석을 목적으로 프로젝션해서 상태 표현을 재구성&lt;/li&gt;
&lt;li&gt;애그리게이트 명령을 실행하여 비즈니스 로직을 수행하고, 결과적으로 새로운 도메인 이벤트를 생성&lt;/li&gt;
&lt;li&gt;새 도메인 이벤트를 &lt;b&gt;이벤트 스토어&lt;/b&gt;에 커밋
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 스토어는 추가만 가능한 저장소 (수정 or 삭제 X)&lt;/li&gt;
&lt;li&gt;특정 비즈니스 엔티티에 속한 모든 이벤트를 가져오고 이벤트를 추가해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 소싱 도메인 모델 패턴의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;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;영속적인 도메인 이벤트는 애그리게이트 상태에서 발생한 모든 것에 대한 강력하게 일관된 감사 로그(audit log)를 나타낸다&lt;/li&gt;
&lt;li&gt;고급 낙관적 동시성 제어를 사용할 수 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 데이터가 기록되는 동안 다른 프로세스에 의해 덮어 쓰어지는 경우를 방지한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 소싱 도메인 모델 패턴의 단점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;러닝 커브가 높다&lt;/li&gt;
&lt;li&gt;이벤트 소싱 모델을 변화시키는 것이 어렵다&lt;/li&gt;
&lt;li&gt;아키텍처의 복잡도가 높아진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8장) 아키텍처 패턴&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 소개하는 아키텍처 패턴들은 시스템 전체의 구성 원칙이나 전체 바운디드 컨텍스트를 위한 고수준의 아키텍처 패턴이 아니다&lt;/li&gt;
&lt;li&gt;바운디드 컨텍스트 내의 여러 하위 도메인에서 필요에 따라 다양한 패턴들을 적용할 수 있다 (수직적 분할)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;계층형 아키텍처&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 프레젠테이션 계층, (서비스 계층), 비즈니스 로직 계층, 데이터 접근 계층으로 나눠짐&lt;/li&gt;
&lt;li&gt;탑-다운 구조의 의존 관계를 지님&lt;/li&gt;
&lt;li&gt;세부 구조 (위에서부터 고수준)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프레젠테이션 계층&lt;/li&gt;
&lt;li&gt;비즈니스 로직 계층&lt;/li&gt;
&lt;li&gt;데이터 접근 계층&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프레젠테이션 계층 (= 사용자 인터페이스 계층)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 그래픽 인터페이스를 나타낸다 (e.g. 웹 인터페이스, 데스크톱 인터페이스)&lt;/li&gt;
&lt;li&gt;프로그램의 동작을 촉발하는 모든 동기식 또는 비동기식 수단과 같은 좀 더 광범위한 범주를 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;서비스 계층 (= 애플리케이션 계층)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계층형 아키텐처 패턴을 확정해서 서비스 계층을 추가할 수 있다&lt;/li&gt;
&lt;li&gt;논리적 경계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직 계층으로의 관문 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비즈니스 로직 패턴에서 외부 조율을 해야 할 경우에만 서비스 계층이 필요하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 스크립트는 이미 퍼블릭 인터페이스를 통해서 비즈니스 로직이 구성되어 있다&lt;/li&gt;
&lt;li&gt;따라서 트랜잭션 스크립트 패턴에서는 서비스 계층 사용이 적절치 않고, 액티브 레코드 패턴에서는 적절하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;비즈니스 로직 계층 (= 도메인 계층 = 모델 계층)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 의사결정을 구현하는 계층&lt;/li&gt;
&lt;li&gt;액티브 레코드 또는 도메인 모델과 같은 비즈니스 로직 패턴을 이 계층에서 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;데이터 접근 계층 (= 인프라스트럭처 계층)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템의 데이터베이스&lt;/li&gt;
&lt;li&gt;영속성 메커니즘에 접근할 수 있는 계층&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 RDB 외에도 더 포괄적인 내용이 포함된다&lt;/li&gt;
&lt;li&gt;e.g. NoSQL, 메시지 버스, 외부 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;포트와 어댑터 아키텍처 (= 헥사고날 아키텍처, 클린 아키텍처)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계층형 아키텍처의 단점을 해결하고 좀 더 복잡한 비즈니스 로직을 구현하는 데 적합한 패턴&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;모든 기술적 관심사로부터 비즈니스 로직을 분리&lt;/b&gt;하는 것이 목적&lt;/li&gt;
&lt;li&gt;도메인 모델 패턴을 사용하여 구현한 비즈니스 로직에 매우 적합하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프레젠테이션 계층과 데이터 접근 계층을 '인프라스트럭처 계층'으로 통합했다&lt;/li&gt;
&lt;li&gt;세부 구조 (위에서부터 고수준)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직 계층&lt;/li&gt;
&lt;li&gt;애플리케이션 계층 (= 서비스 계층 = 유스케이스 계층)&lt;/li&gt;
&lt;li&gt;인프라스트럭처 계층 (= 도메인 계층 = 핵심 계층)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;의존성 역전 원칙&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 계층형 아키텍처는 의존성 역전 원칙(DIP)를 위반한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위 수준 모듈이 하위 수준 모듈을 의존한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;포트와 어댑터 아키텍처는 의존성 역전 원칙이 반영되었다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직 계층(상위 수준 모듈)이 인프라스트럭처 계층이 구현할 '포트'를 정의한다 (비즈니스 로직 계층 &amp;rarr; 인터페이스 정의)&lt;/li&gt;
&lt;li&gt;인프라스트럭처 계층은 '어댑터'를 구현한다 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;(인프라스트럭처 계층&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;인터페이스 구현&lt;/span&gt;&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;퍼블릭 인터페이스를 위한 관문으로 애플리케이션 계층이 중간에 추가된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tmg3V/btsBGhSt3gz/X4ZdPjD3Um5dGk6pyukk1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tmg3V/btsBGhSt3gz/X4ZdPjD3Um5dGk6pyukk1K/img.png&quot; data-alt=&quot;https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tmg3V/btsBGhSt3gz/X4ZdPjD3Um5dGk6pyukk1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftmg3V%2FbtsBGhSt3gz%2FX4ZdPjD3Um5dGk6pyukk1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1242&quot; height=&quot;876&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;876&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CQRS(command-query responsibility segregation)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포트와 어댑터와 동일하게 비즈니스 로직과 인프라스트럭처 관심사에 기반한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그러나 포트와 어댑터 아키텍처와 다르게 시스템의 데이터를 관리하는 방법이 다르다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CQRS는 시스템 모델의 책임을 분리시키는 패턴이다&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 모델 패턴과 이벤트 소싱 도메인 모델에 모두 적합하다&lt;/li&gt;
&lt;li&gt;또한, 인프라스트럭처 관점에선 다양한 종류의 데이터베이스의 장점을 활용할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;커맨드 실행 모델&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템의 상태를 수정하는 오퍼레이션(시스템 커맨드)을 전담으로 수행하는 단일 모델&lt;/li&gt;
&lt;li&gt;비즈니스 로직을 구현하고 규칙을 검사하며 불변성을 강화하는 데 사용됨&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;낙관적 동시성을 지원해야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;읽기 모델(프로젝션)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자에게 데이터를 보여주거나 다른 시스템에 정보를 제공하기 위해 필요한 만큼 정의하는 모델&lt;/li&gt;
&lt;li&gt;RDB의 머터리얼라이즈 뷰(materialize view)의 개념과 유사하다&lt;/li&gt;
&lt;li&gt;읽기 모델은 읽기 전용이므로 읽기 모델의 데이터를 직접 수정하는 것은 불가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로젝션의 생성 방식&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝션을 생성하는 데엔 동기식 프로젝션과 비동기식 프로젝션이라는 두 가지 방법이 존재한다&lt;/li&gt;
&lt;li&gt;동기식 프로젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝션 엔진이 마지막에 처리한 체크포인트 이후에 추가되거나 갱신된 레코드를 조회한다&lt;/li&gt;
&lt;li&gt;조회된 데이터를 이용해서 읽기 모델을 재생성/갱신한다&lt;/li&gt;
&lt;li&gt;다시 처리 체크포인트를 기록한다&lt;/li&gt;
&lt;li&gt;정리하자면 상태 변경 -&amp;gt; 읽기 모델 반영 -&amp;gt; 체크 포인트 기록이라는 흐름으로 프로젝션을 생성한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비동기식 프로젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커맨드 실행 모델이 모든 변경사항을 메시지 버스에 발행한다&lt;/li&gt;
&lt;li&gt;프로젝션 엔진은 이를 구독하고 읽기 모델에 반영한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가능하면 동기식 프로젝션 방식을 구현하고, 선택적으로 비동기식 프로젝션 방식을 추가하는 것을 권장한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기식 프로젝션 방식은 확장성과 성능적인 측면에서 유리하지만, 분산 컴퓨팅에서 문제가 발생하기 더 쉽다&lt;/li&gt;
&lt;li&gt;메시지의 순서가 꼬이거나 중복 처리가 일어나면 일관성 없는 데이터가 프로젝션 될 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;모델 분리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CQRS에서 데이터 조회는 읽기 모델을 통해서만 이뤄지는 것이 아니다&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커맨드가 데이터를 반환함으로써 동작에 대한 피드백을 받을 수 있다 (e.g. 실패/성공에 대한 사유)&lt;/li&gt;
&lt;li&gt;사용자 입장에서 반환값을 다음 워크플로우에 사용할 수 있다&lt;/li&gt;
&lt;li&gt;데이터가 궁극적으로 일관성을 갖는 프로젝션의 경우에는 데이터에 대한 즉각적인 갱신은 기대할 수 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>책/도메인 주도 설계 첫걸음</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/370</guid>
      <comments>https://cotak.tistory.com/370#entry370comment</comments>
      <pubDate>Sun, 3 Sep 2023 23:46:52 +0900</pubDate>
    </item>
    <item>
      <title>[도메인 주도 설계 첫걸음] Part 1. 비즈니스 도메인 분석하기</title>
      <link>https://cotak.tistory.com/369</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1장) 비즈니스 도메인 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비즈니스 도메인&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기업의 주요 활동 영역&lt;/li&gt;
&lt;li&gt;기업은 여러 비즈니스 도메인을 운영할 수 있음&lt;/li&gt;
&lt;li&gt;비즈니스 도메인은 자주 변경될 수 있음 (e.g. 노키아)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;하위 도메인&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 활동의 세분화된 영역&lt;/li&gt;
&lt;li&gt;핵심, 일반, 지원의 세 가지 유형으로 구분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 하위 도메인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회사가 경쟁업체와 다르게 수행하고 있는 것 (e.g. 구글의 검색 알고리즘)&lt;/li&gt;
&lt;li&gt;핵심 하위 도메인은 복잡성이 높음&lt;/li&gt;
&lt;li&gt;핵심 하위 도메인은 경쟁 우위의 원천이 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반 하위 도메인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 회사가 같은 방식으로 수행하는 비즈니스 활동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지원 하위 도메인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회사의 비즈니스를 지원하는 활동&lt;/li&gt;
&lt;li&gt;지원 하위 도메인은 어떠한 경쟁 우위도 제공하지 않음&lt;/li&gt;
&lt;/ul&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;width: 16.6667%;&quot;&gt;&lt;b&gt;하위 도메인 유형&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&lt;b&gt;경쟁 우위&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&lt;b&gt;복잡성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&lt;b&gt;변동성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&lt;b&gt;구현 방식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;핵심&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;예&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;사내 개발&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;흥미로움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;일반&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;아니오&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;구매/도입&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;해결됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;지원&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;아니오&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;사내 개발/하청&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;뻔함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;하위 도메인 경계 식별&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 하위 도메인은 분석을 통해 여러 핵심 하위 도메인, 지원 도메인, 일반 하위 도메인으로 나눠질 수 있다 (경계 식별)&lt;/li&gt;
&lt;li&gt;핵심 하위 도메인에는 경계 식별을 위한 노력이 많이 필요하다&lt;/li&gt;
&lt;li&gt;반면 지원 및 일반 하위 도메인의 경우에는 이러한 식별 작업이 비교적 적게 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도메인 전문가&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어의 비즈니스 도메인에 대한 권위자&lt;/li&gt;
&lt;li&gt;일반적인 도메인 전문가는 요구사항을 제시하는 사람 or 소프트웨어의 최종 사용자&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2장) 도메인 지식 찾아내기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비즈니스 문제&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 도메인 컨텍스트에서 &amp;lsquo;문제&amp;rsquo;의 의미는 광범위함&lt;/li&gt;
&lt;li&gt;비즈니스 문제는 비즈니스 도메인과 하위 도메인의 모든 수준에서 발생할 수 있음&lt;/li&gt;
&lt;li&gt;하위 도메인은 세분화된 문제 도메인(problem domain)으로 특정 비즈니스 기능에 대한 솔루션을 제공하는 것이 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도메인 지식 찾아내기&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 전문가를 이해하고 그들이 쓰는 동일한 비즈니스 용어를 사용하는 것이 중요하다&lt;/li&gt;
&lt;li&gt;즉, 도메인 전문가와 소프트웨어 엔지니어 간의 효과적인 커뮤니케이션이 있어야 효과적인 지식 공유가 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;유비쿼터스 언어&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 지식은 그 전달 과정에서 종종 왜곡된 상태로 전달되는 경우가 있다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비즈니스 도메인을 설명하기 위한 단일화된 언어 체계&lt;/b&gt;가 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;유비쿼터스 언어&lt;/b&gt;&lt;/span&gt;이다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 프로젝트 참가자의 공통된 이해는 오직 유비쿼터스 언어와 그 용어의 지속적인 사용을 통해서만 함양될 수 있다&lt;/li&gt;
&lt;li&gt;유비쿼터스 언어는 비즈니스 언어이므로 기술 용어는 제외하고 비즈니스 도메인에 관련된 용어로만 구성해야 한다&lt;/li&gt;
&lt;li&gt;유비쿼터스 언어는 도메인 전문가의 이해와 비즈니스 도메인에 대한 멘탈 모델을 쉽게 이해할 수 있는 관점으로 표현하는 것이 목표이다&lt;/li&gt;
&lt;li&gt;유비쿼터스 언어의 모든 용어는 일관성이 있어야 하며, 모호하지 않고 동의어가 없어야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유비쿼터스 언어는 비즈니스 도메인 모델링과 같다&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델이란? 문제를 해결하려는 의도가 있으며, 그 목적에 필요한 정보가 제공해야 한다&lt;/li&gt;
&lt;li&gt;도메인 전문가의 사고 프로세스인 멘탈 모델을 포착해야 한다&lt;/li&gt;
&lt;li&gt;필요한 만큼의 비즈니스 도메인 관점을 포함하면 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;용어집과 거킨 테스트 같은 도구는 유비쿼터스 언어를 문서화하고 유지보수하는 데 도움이 된다&lt;/li&gt;
&lt;li&gt;그러나&lt;span&gt; &lt;/span&gt;가장&lt;span&gt; &lt;/span&gt;중요한&lt;span&gt; &lt;/span&gt;것은&lt;span&gt; &lt;/span&gt;지속적으로&lt;span&gt; &lt;/span&gt;언어를&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;것이다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3장) 도메인 복잡성 관리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;바운디드 컨텍스트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;유비쿼터스 언어의 일관성이 유지되는 경계&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유비쿼터스 언어의 용어, 원칙, 비즈니스 규칙은 해당 바운디드 컨텍스트 내에서만 일관성이 있음&lt;/li&gt;
&lt;li&gt;유비쿼터스 언어는 명시적으로 적용 가능한 컨텍스트 없이 정의하거나 사용이 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;쉽게&lt;span&gt; &lt;/span&gt;말해&lt;span&gt; &lt;/span&gt;&lt;b&gt;모델의&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;경계&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;바운디드 컨텍스트 vs 하위 도메인&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바운디드 컨텍스트 vs 하위 도메인 = 소프트웨어 설계 vs 비즈니스 전략
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위 도메인은 발견하고, 바운디드 컨텍스트는 설계한다&amp;nbsp;&amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;도메인은 문제 영역이고, 바운디드 컨텍스트는 해결 영역이다!&lt;/b&gt;&lt;/span&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;span&gt; &lt;/span&gt;컨텍스트와&lt;span&gt; &lt;/span&gt;하위&lt;span&gt; &lt;/span&gt;도메인의&lt;span&gt; &lt;/span&gt;설계는&lt;span&gt; 1:1&lt;/span&gt;이&lt;span&gt; &lt;/span&gt;될&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있지만&lt;span&gt;, 1:1&lt;/span&gt;로&lt;span&gt; &lt;/span&gt;제한해버리면&lt;span&gt; &lt;/span&gt;유연성이&lt;span&gt; &lt;/span&gt;떨어진다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;경계&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;물리적 경계&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바운디드 컨텍스트는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;물리적 경계&lt;/b&gt; &lt;/span&gt;역할도 한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;각 바운디드 컨텍스트는 개별 서비스/프로젝트로 구현&lt;/b&gt;돼야 한다&lt;/li&gt;
&lt;li&gt;서로&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;생명주기를&lt;span&gt; &lt;/span&gt;지닌다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;바운디드 컨텍스트가 여러 하위 도메인을 포함할 경우, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;바운디드 컨텍스트는 물리적 경계&lt;/b&gt;&lt;/span&gt;가 되는 반면&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; 하위 도메인은 논리적 경계&lt;/b&gt;&lt;/span&gt;(e.g. 네임스페이스, 모듈, 패키지)가 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;소유권 경계&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바운디드 컨텍스트는 한 팀에서만 구현, 발전 유지 관리해야 한다&lt;/li&gt;
&lt;li&gt;단일 팀이 여러 개의 바운디드 컨텍스트를 소유할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4장) 바운디드 컨텍스트 연동&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 바운디드 컨텍스트 내의 언어는 특정 문제를 해결하는 비즈니스 도메인을 모델링한다&lt;/li&gt;
&lt;li&gt;다른 바운디드 컨텍스트의 모델은 서로 독립적으로 발전하고 구현될 수 있지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;바운디드 컨텍스트 자체가 독립적인 것은 아니다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;바운디드 컨텍스트는 항상 &lt;b&gt;접점&lt;/b&gt;이 있는데, 이것을 &lt;b&gt;컨트랙트(contract)&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; &lt;/span&gt;컨트랙트는&lt;span&gt; &lt;/span&gt;하나&lt;span&gt; &lt;/span&gt;이상의&lt;span&gt; &lt;/span&gt;당사자에&lt;span&gt; &lt;/span&gt;영향을&lt;span&gt; &lt;/span&gt;끼치므로&lt;span&gt; &lt;/span&gt;서로&lt;span&gt; &lt;/span&gt;조율해서&lt;span&gt; &lt;/span&gt;컨트랙트를&lt;span&gt; &lt;/span&gt;정의해야&lt;span&gt; &lt;/span&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; &lt;/span&gt;연동이&lt;span&gt; &lt;/span&gt;필요할&lt;span&gt; &lt;/span&gt;때&lt;span&gt; &lt;/span&gt;어떤&lt;span&gt; &lt;/span&gt;언어를&lt;span&gt;? &lt;/span&gt;연동에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;고민은&lt;span&gt; &lt;/span&gt;솔루션&lt;span&gt; &lt;/span&gt;설계에서&lt;span&gt; &lt;/span&gt;평가되고&lt;span&gt; &lt;/span&gt;다뤄져야&lt;span&gt; &lt;/span&gt;한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바운디드 컨텍스트 간의 관계와 연동을 정의하는 도메인 주도 설계 패턴&lt;/b&gt;은 &lt;b&gt;바운디드 컨텍스트에서 작업하는 팀 간의 협력적 특성&lt;/b&gt;에 의해 주도된다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한&lt;span&gt; &lt;/span&gt;패턴은&lt;span&gt; &lt;/span&gt;협력&lt;span&gt;, &lt;/span&gt;사용자&lt;span&gt;-&lt;/span&gt;제공자&lt;span&gt;, &lt;/span&gt;분리형&lt;span&gt; &lt;/span&gt;노선의&lt;span&gt; &lt;/span&gt;세&lt;span&gt; &lt;/span&gt;그룹으로&lt;span&gt; &lt;/span&gt;나뉜다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;협력형 패턴 그룹&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협력형 그룹의 패턴은 소통이 잘 되는 팀에서 구현된 바운디드 컨텍스트와 관련있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 팀에 의해 구현된 바운디드 컨텍스트&lt;/li&gt;
&lt;li&gt;한&lt;span&gt; &lt;/span&gt;팀의&lt;span&gt; &lt;/span&gt;성공이&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;팀의&lt;span&gt; &lt;/span&gt;성공에&lt;span&gt; &lt;/span&gt;달려있고&lt;span&gt;, &lt;/span&gt;그&lt;span&gt; &lt;/span&gt;반대도&lt;span&gt; &lt;/span&gt;마찬가지인&lt;span&gt; &lt;/span&gt;팀&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;커뮤니케이션과 협업의 매우 중요한 팀&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;파트너십 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바운디드 컨텍스트 간의 연동은 애드혹(ad-hoc) 방식으로 조정한다
&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;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;팀에게&lt;span&gt; API&lt;/span&gt;의&lt;span&gt; &lt;/span&gt;변경을&lt;span&gt; &lt;/span&gt;알리고&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;팀은&lt;span&gt; &lt;/span&gt;충돌&lt;span&gt; &lt;/span&gt;없이&lt;span&gt; &lt;/span&gt;이를&lt;span&gt; &lt;/span&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; &lt;/span&gt;팀&lt;span&gt; &lt;/span&gt;모두&lt;span&gt; &lt;/span&gt;협력할&lt;span&gt; &lt;/span&gt;뿐&lt;span&gt;, &lt;/span&gt;방해하지&lt;span&gt; &lt;/span&gt;않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동기화와 커뮤니케이션의 어려움 때문에 지리적으로 떨어져 있는 팀에게는 적합하지 않을 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공유 커널 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바운디드 컨텍스트가 모델의 경계지만, 여전히 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;하위 도메인의 동일 모델 또는 그 일부가 여러 다른 바운디드 컨텍스트에서 구현되는 경우&lt;/b&gt;&lt;/span&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 style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바운디드 컨텍스트 간에 강한 의존관계를 만드므로 &lt;b&gt;중복 비용이 조율 비용보다 클 경우에만 적용해야 한다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이 패턴은 단일 팀 원칙을 위반하는 것으므로 &lt;b&gt;신중하게 고려해야 하는 실용적인 예외&lt;/b&gt;라고 할 수 있다&lt;/li&gt;
&lt;li&gt;결국&lt;span&gt; &lt;/span&gt;앞서&lt;span&gt; &lt;/span&gt;말한&lt;span&gt; &lt;/span&gt;것처럼&lt;span&gt; &lt;/span&gt;공유되는&lt;span&gt; &lt;/span&gt;범위를&lt;span&gt; &lt;/span&gt;최소화하고&lt;span&gt;, &lt;/span&gt;매&lt;span&gt; &lt;/span&gt;변경&lt;span&gt; &lt;/span&gt;시마다&lt;span&gt; &lt;/span&gt;통합&lt;span&gt; &lt;/span&gt;테스트를&lt;span&gt; &lt;/span&gt;수행하는&lt;span&gt; &lt;/span&gt;것이&lt;span&gt; &lt;/span&gt;중요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자-제공자 패턴 그룹&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 제공자는 업스트림, 고객 또는 사용자는 다운스트림&lt;/li&gt;
&lt;li&gt;협력 그룹과 다르게 양 쪽은 서로 독립적으로 성공할 수 있다&lt;/li&gt;
&lt;li&gt;그러나 실제론 권력의 불균형이 존재하여 업스트림 또는 다운스트림의 팀이 연동 컨트랙트를 주도한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;순응주의자 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;힘의 균형이 서비스를 제공하는 업스트림 팀에 있는 경우&lt;/li&gt;
&lt;li&gt;다운스트림 팀이 업스트림 팀의 모델을 그대로 받아들이는 바운디드 컨텍스트의 관계&lt;/li&gt;
&lt;li&gt;제공자의 모델에 따라 정의된 연동 컨트랙트를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;충돌 방지 계층 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;힘의 균형이 업스트림 서비스에 있지만, 다운스트림 바운디드 컨텍스트가 이에 순응하지 않는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순응에 필요한 노력이 가치가 없는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다운스트림 바운디드 컨텍스트가 핵심 하위 도메인을 포함할 경우&lt;/li&gt;
&lt;li&gt;업스트림 모델이 사용자의 요건에 비효율적이거나 불편한 경우&lt;/li&gt;
&lt;li&gt;제공자가&lt;span&gt; &lt;/span&gt;컨트랙트를&lt;span&gt; &lt;/span&gt;자주&lt;span&gt; &lt;/span&gt;변경하는&lt;span&gt; &lt;/span&gt;경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;충돌 방지 계층(ACL: anticorruption layer)을 통해 업스트림 바운디드 컨텍스트의 모델을 스스로의 필요에 맞게 가공할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;오픈 호스트 서비스 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;힘이 사용자 측에 있을 경우&lt;/li&gt;
&lt;li&gt;업스트림 제공자는 퍼블릭 인터페이스와 구현 모델을 분리&lt;/li&gt;
&lt;li&gt;제공자의 퍼블릭 인터페이스는 자신(업스트림)의 유비쿼터스 언어를 따르는 대신, 연동 지향(integration-oriented language)를 통해 사용자에게 더 편리한 프로토콜을 노출하려 한다
&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;span&gt;프로토콜을&lt;/span&gt; &lt;span&gt;공표된&lt;/span&gt; &lt;span&gt;언어&lt;/span&gt;(published language)&lt;span&gt;라고&lt;/span&gt; &lt;span&gt;한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오픈 호스트 서비스(OHS: open-host service) 패턴은 충돌 방지 계층 패턴의 반대
&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;span&gt; &lt;/span&gt;제공자가&lt;span&gt; &lt;/span&gt;내부&lt;span&gt; &lt;/span&gt;모델&lt;span&gt; &lt;/span&gt;번역을&lt;span&gt; &lt;/span&gt;구현한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;분리형 노선&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전혀 협력하지 않는 것&lt;/li&gt;
&lt;li&gt;팀에 협업 의지가 없거나 협업할 수 없는 경우 등 다양한 케이스가 존재
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커뮤니케이션 이슈&lt;/li&gt;
&lt;li&gt;일반 하위 도메인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 하위 도메인이 일반 솔루션과 연동하는 것이 쉽다면 각 바운디드 컨텍스트 내에서 각자 연동하는 것이 더욱 비용 효과적 (e.g. 로깅 프레임워크)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로깅 프레임워크는 바운디드 컨텍스트 내의 모든 하위 도메인이 사용하지만, 공유 모델로 만드는 것보다 일반 솔루션을 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/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; &lt;/span&gt;너무&lt;span&gt; &lt;/span&gt;달라서&lt;span&gt; &lt;/span&gt;중복을&lt;span&gt; &lt;/span&gt;감안하고&lt;span&gt; &lt;/span&gt;가는&lt;span&gt; &lt;/span&gt;게&lt;span&gt; &lt;/span&gt;나은&lt;span&gt; &lt;/span&gt;경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;컨텍스트 맵&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨텍스트 맵은 시스템의 바운디드 컨텍스트와의 연동을 시각적으로 표현&lt;/li&gt;
&lt;li&gt;&lt;span&gt;장점&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거시적&lt;span&gt; &lt;/span&gt;설계&lt;span&gt; &lt;/span&gt;관점&lt;span&gt;, &lt;/span&gt;커뮤니케이션&lt;span&gt; &lt;/span&gt;패턴&lt;span&gt;, &lt;/span&gt;조직&lt;span&gt; &lt;/span&gt;문제에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;통찰력을&lt;span&gt; &lt;/span&gt;얻을&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&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;다 함께 유지보수하는 것이 Best&lt;/li&gt;
&lt;li&gt;각 팀은 자신이 담당하는 외부 바운디드 컨텍스트 연동에 대해 자신한다&lt;/li&gt;
&lt;li&gt;&lt;span&gt;컨텍스트&lt;/span&gt; &lt;span&gt;매퍼&lt;/span&gt;(Context Mapper) &lt;span&gt;같은&lt;/span&gt; &lt;span&gt;도구를&lt;/span&gt; &lt;span&gt;사용해서&lt;/span&gt; &lt;span&gt;코드로&lt;/span&gt; &lt;span&gt;관리할&lt;/span&gt; &lt;span&gt;수&lt;/span&gt; &lt;span&gt;있다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;한계&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작성하기 어려움
&lt;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;span&gt; &lt;/span&gt;하위&lt;span&gt; &lt;/span&gt;도메인이&lt;span&gt; &lt;/span&gt;각각&lt;span&gt; &lt;/span&gt;다르게&lt;span&gt; &lt;/span&gt;연동할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>책/도메인 주도 설계 첫걸음</category>
      <author>코택</author>
      <guid isPermaLink="true">https://cotak.tistory.com/369</guid>
      <comments>https://cotak.tistory.com/369#entry369comment</comments>
      <pubDate>Sun, 27 Aug 2023 20:55:04 +0900</pubDate>
    </item>
  </channel>
</rss>