<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Amor DevFati(아모르 개발파티)</title>
    <link>https://wlghsp.tistory.com/</link>
    <description>Amor DevFati는 
김연자-Amor Fati에 Development(개발)의 Dev 를 첨가하여 만든 이름 
</description>
    <language>ko</language>
    <pubDate>Tue, 25 Aug 2026 17:50:29 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>고구마와 감자</managingEditor>
    <image>
      <title>Amor DevFati(아모르 개발파티)</title>
      <url>https://tistory1.daumcdn.net/tistory/3196821/attach/f7b077d0f1d447bba4b9159ac005e4ed</url>
      <link>https://wlghsp.tistory.com</link>
    </image>
    <item>
      <title>쿠폰 중복 사용 버그, 비관적 락 한 줄로 해결하기</title>
      <link>https://wlghsp.tistory.com/272</link>
      <description>&lt;h1&gt;쿠폰 중복 사용 버그, 비관적 락 한 줄로 해결하기&lt;/h1&gt;
&lt;h2&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;주문 API를 만들고 테스트하던 중 이상한 현상을 발견했다.&lt;/p&gt;
&lt;p&gt;동시에 여러 요청이 들어오면 &lt;strong&gt;같은 쿠폰이 2번 사용되는 버그&lt;/strong&gt;가 발생했다.&lt;/p&gt;
&lt;p&gt;분명히 쿠폰 사용 여부를 체크하는 로직이 있는데, 왜 이런 일이?&lt;/p&gt;
&lt;h2&gt;원인 분석&lt;/h2&gt;
&lt;p&gt;문제는 이랬다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;시간 →

스레드 A: 쿠폰 조회 (사용 가능!) → 사용 처리
스레드 B:      쿠폰 조회 (사용 가능!) → 사용 처리&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;두 스레드가 거의 동시에 조회하면 둘 다 &amp;quot;사용 가능&amp;quot;으로 판단한다. 그리고 둘 다 사용 처리를 해버린다.&lt;/p&gt;
&lt;p&gt;이게 바로 &lt;strong&gt;Lost Update&lt;/strong&gt; 문제다.&lt;/p&gt;
&lt;h2&gt;해결: 비관적 락&lt;/h2&gt;
&lt;p&gt;해결은 간단했다. 조회할 때 락을 걸면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query(&amp;quot;&amp;quot;&amp;quot;
    SELECT mc FROM MemberCoupon mc
    JOIN FETCH mc.coupon c
    WHERE mc.memberId = :memberId
    AND mc.coupon.id = :couponId
    AND mc.deletedAt IS NULL
&amp;quot;&amp;quot;&amp;quot;)
fun findByMemberIdAndCouponIdWithLock(memberId: String, couponId: Long): MemberCoupon?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)&lt;/code&gt; 한 줄 추가했다.&lt;/p&gt;
&lt;p&gt;이제 흐름이 이렇게 바뀐다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;시간 →

스레드 A: 쿠폰 조회 (락 획득) → 사용 처리 → 락 해제
스레드 B:      대기...              → 조회 (이미 사용됨!) → 실패&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;서비스 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun getMemberCoupon(memberId: String, couponId: Long): MemberCoupon? {
    couponRepository.findByIdOrThrow(couponId)

    // 비관적 락으로 조회
    return memberCouponRepository.findByMemberIdAndCouponIdWithLock(memberId, couponId)
        ?: throw CoreException(ErrorType.COUPON_NOT_FOUND, &amp;quot;쿠폰을 보유하고 있지 않습니다&amp;quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;락이 걸린 메서드를 호출하면, 트랜잭션이 끝날 때까지 다른 스레드는 해당 row에 접근하지 못한다.&lt;/p&gt;
&lt;h2&gt;왜 비관적 락?&lt;/h2&gt;
&lt;p&gt;낙관적 락도 고려했지만, 쿠폰은 비관적 락이 맞다고 판단했다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;비관적 락&lt;/th&gt;
&lt;th&gt;낙관적 락&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;방식&lt;/td&gt;
&lt;td&gt;조회 시 락&lt;/td&gt;
&lt;td&gt;수정 시 버전 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;충돌 시&lt;/td&gt;
&lt;td&gt;대기&lt;/td&gt;
&lt;td&gt;예외 → 재시도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적합한 경우&lt;/td&gt;
&lt;td&gt;충돌 많음, 실패 비용 큼&lt;/td&gt;
&lt;td&gt;충돌 적음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;쿠폰 사용은:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 번 실패하면 UX가 나빠짐 (결제 실패)&lt;/li&gt;
&lt;li&gt;동시 요청 가능성 있음 (여러 기기에서 동시 주문)&lt;/li&gt;
&lt;li&gt;재시도보다 대기가 나음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 비관적 락을 선택했다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;동시성 문제는 단일 스레드 테스트로는 발견 안 됨&lt;/li&gt;
&lt;li&gt;비관적 락은 &lt;code&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)&lt;/code&gt; 한 줄&lt;/li&gt;
&lt;li&gt;쿠폰처럼 실패 비용이 큰 경우 비관적 락이 적합&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Loopers 2기</category>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/272</guid>
      <comments>https://wlghsp.tistory.com/272#entry272comment</comments>
      <pubDate>Fri, 21 Nov 2025 17:49:24 +0900</pubDate>
    </item>
    <item>
      <title>재고 차감, 검증은 몇 번 해야 할까?</title>
      <link>https://wlghsp.tistory.com/271</link>
      <description>&lt;h1&gt;재고 차감, 검증은 몇 번 해야 할까?&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TL;DR&lt;/b&gt;: 주문 생성 시 재고 검증과 차감이 중복&amp;middot;분산돼 혼란스러웠다. 검증을 한 곳에 명확히 두고, 메서드 호출 방식을 통일하는 게 핵심임을 배웠다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 발견&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문 생성 로직을 구현하다 보니 재고 처리 방식이 어색했다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Order.create()
product.validateStock(quantity)   // 검증
OrderItem.of(product, quantity)

// OrderService.createOrder()
order.items.forEach {
    it.product.decreaseStock(it.quantity)  // 차감만
}
&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;Order 생성 시 &lt;b&gt;검증&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;OrderService에서 &lt;b&gt;차감&lt;/b&gt; &amp;rarr; &lt;b&gt;검증을 두 번? 한 번?&lt;/b&gt; 헷갈렸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Product에 메서드가 3개나&amp;hellip;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun validateStock(quantity: Quantity) { ... }           // 검증만
fun decreaseStock(quantity: Quantity) { ... }           // 차감만 (VO 내부 검증)
fun decreaseStockWithValidation(quantity: Quantity) { ... } // 검증+차감
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어느 걸 써야 정답이지?&quot; &amp;rarr; &lt;b&gt;일관성 없는 설계의 신호&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;decreaseStock()은 재사용성을 위해 검증 없이 설계&lt;/li&gt;
&lt;li&gt;Stock VO 내부에도 검증 존재 &amp;rarr; &lt;b&gt;검증이 두 곳에 중복&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;에러 메시지에 상품명을 넣고 싶어 Product에도 검증 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선택지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식 설명 장점 단점&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;1. 검증+차감 통합&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;decreaseStock()이 항상 검증&lt;/td&gt;
&lt;td&gt;호출 측 부담 &amp;darr;&lt;/td&gt;
&lt;td&gt;VO 검증 중복&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;2. VO 검증만 믿기&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;엔티티 검증 제거&lt;/td&gt;
&lt;td&gt;중복 없음&lt;/td&gt;
&lt;td&gt;에러 메시지 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내가 선택한 방식: &lt;b&gt;2번 + 타협&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// Product.kt
fun decreaseStock(quantity: Quantity) {
    stock = stock.decrease(quantity.value)  // VO가 검증
}

fun validateStock(quantity: Quantity) {     // 상품명 포함 에러용
    if (!hasEnoughStock(quantity)) {
        throw CoreException(ErrorType.INSUFFICIENT_STOCK,
            &quot;재고 부족. 상품: $name (요청: ${quantity.value})&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Order.create() &amp;rarr; validateStock() (구체적 에러)&lt;/li&gt;
&lt;li&gt;OrderService &amp;rarr; decreaseStock() (VO 검증 위임)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여전히 불편한 점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;validateStock()이 public이라 오용 가능성&lt;/li&gt;
&lt;li&gt;검증 후 차감 사이 &lt;b&gt;동시성 문제&lt;/b&gt; (트랜잭션/락 필요)&lt;/li&gt;
&lt;li&gt;메서드 2개 &amp;rarr; 여전히 많음&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배운 점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;검증은 한 곳에 명확히&lt;/b&gt; (VO or 엔티티)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메서드 많으면 설계 의심&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관성 &amp;gt; 완벽함&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음엔 이렇게&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun decreaseStock(quantity: Quantity) {
    if (!hasEnoughStock(quantity)) {
        throw CoreException(ErrorType.INSUFFICIENT_STOCK,
            &quot;재고 부족. 상품: $name (요청: ${quantity.value}, 재고: ${stock.quantity})&quot;)
    }
    stock = stock.decrease(quantity.value)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메서드 하나, 검증+차감+에러 메시지 통일&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재고 차감은 단순해 보이지만, &lt;b&gt;&quot;누가 검증할까? 몇 번 할까? 어떻게 일관되게 할까?&quot;&lt;/b&gt; 이 고민이 좋은 설계를 만든다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음엔 더 간결하게!&lt;/p&gt;</description>
      <category>Loopers 2기</category>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/271</guid>
      <comments>https://wlghsp.tistory.com/271#entry271comment</comments>
      <pubDate>Fri, 14 Nov 2025 17:41:33 +0900</pubDate>
    </item>
    <item>
      <title>Round 2 회고</title>
      <link>https://wlghsp.tistory.com/270</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 정리는 한적 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세하게 작성한적도 없고, 다이어그램은 그려본 적이 한번도 없어서 코드 구현보다 어려운 과제였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 좋아요 기능의 멱등성 보장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멱등성이 뭐죠?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #505860; text-align: start;&quot;&gt;멱등성이란 동일한 연산을 여러 번 수행해도 결과가 달라지지 않는 성질을 말합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #505860; text-align: start;&quot;&gt;그래서 좋아요 기능의 멱등성 보장은 어떻게 하죠?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멱등성이 보장된다는건 뭐지?&lt;/p&gt;
&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;- 같은 사용자가 같은 게시물에 좋아요를 100번 눌러도, 좋아요는 딱 1개만 저장되어야 한다는 거라고 합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;결론: 몇 번을 눌러도 좋아요는 1개만 저장되는 게 멱등성 보장&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;잘못된 경우 (멱등성 X)&lt;/h4&gt;
&lt;pre id=&quot;code_1762574667390&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;사용자 A가 게시물 1번에 좋아요 3번 클릭
&amp;rarr; DB에 좋아요 3개 저장됨
&amp;rarr; 좋아요 개수 3 증가
&amp;rarr; ❌ 문제!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;올바른 경우 (멱등성 O)&lt;/h4&gt;
&lt;pre id=&quot;code_1762574700623&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;사용자 A가 게시물 1번에 좋아요 3번 클릭
&amp;rarr; DB에 좋아요 1개만 저장됨
&amp;rarr; 좋아요 개수 1만 증가
&amp;rarr; ✅ 정상!&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 확실한 방법:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. DB에 유니크 제약&lt;/h4&gt;
&lt;pre id=&quot;code_1762574729794&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UNIQUE KEY (user_id, post_id)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이렇게 하면 같은 사용자 + 같은 게시물 조합은 절대 중복 저장 안됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 지연으로 똑같은 요청이 2번 와도, 첫 번째만 저장되고 두 번째는 무시됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 애플리케이션 레벨에서 제어&amp;nbsp;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 좋아요 데이터 존재여부 확인 후 분기 처리&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 재고 및 포인트 동시성 제어&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러&amp;nbsp;사용자가&amp;nbsp;동시에&amp;nbsp;주문할&amp;nbsp;때&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;문제:&lt;/p&gt;
&lt;pre id=&quot;code_1762506544790&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;상황: 상품 재고 10개, 사용자 A와 B가 동시에 각각 8개씩 주문

[동시성 제어 없이]
T1: A가 재고 조회 (10개) ✓
T2: B가 재고 조회 (10개) ✓  &amp;larr; 문제: 둘 다 10개로 보임
T3: A가 재고 차감 (10 - 8 = 2개)
T4: B가 재고 차감 (10 - 8 = 2개)  &amp;larr; 재앙: 실제로는 -6개여야 함!
결과: 총 16개 판매, 재고는 2개로 표시 &amp;rarr; 데이터 정합성 깨짐 ❌&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  처리 방안&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비관적 락 적용&lt;/p&gt;
&lt;pre id=&quot;code_1762506603318&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[비관적 락 적용 후]
T1: A가 재고 조회 + 락 획득 (10개)  
T2: B가 재고 조회 시도 &amp;rarr; ⏳ 대기 (A의 락이 풀릴 때까지)
T3: A가 재고 차감 (10 - 8 = 2개)
T4: A의 트랜잭션 커밋 + 락 해제  
T5: B가 재고 조회 + 락 획득 (2개)  
T6: B가 재고 부족 확인 (2 &amp;lt; 8)
T7: B에게 InsufficientStockException 발생
결과: A만 주문 성공, B는 재고 부족 에러 &amp;rarr; 정합성 보장 ✅&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 비관적 락인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택 이유:&lt;br /&gt;1. 주문/재고는 충돌 가능성이 높음 (인기 상품일수록)&lt;br /&gt;2. 재시도 로직보다 락으로 확실히 제어하는 것이 안전&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.&amp;nbsp;외부&amp;nbsp;시스템&amp;nbsp;연동&amp;nbsp;실패&amp;nbsp;시&amp;nbsp;트랜잭션&amp;nbsp;롤백&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;주문&amp;nbsp;생성&amp;nbsp;후&amp;nbsp;외부&amp;nbsp;시스템(배송/결제&amp;nbsp;등)&amp;nbsp;연동&amp;nbsp;필요&lt;br /&gt;-&amp;nbsp;외부&amp;nbsp;시스템&amp;nbsp;장애&amp;nbsp;시&amp;nbsp;이미&amp;nbsp;차감된&amp;nbsp;재고와&amp;nbsp;포인트&amp;nbsp;처리&amp;nbsp;방법?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  처리 방안&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부&amp;nbsp;시스템&amp;nbsp;연동을&amp;nbsp;트랜잭션&amp;nbsp;범위에&amp;nbsp;포함하여&amp;nbsp;연동 실패 시 재고 및 포인트 차감 자동 복원&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 글 작성도 조금 더 발전할 수 있을거 같은데 다음에는 좀 더 시간을 들여서 작성할 수 있도록 해야겠습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Loopers 2기</category>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/270</guid>
      <comments>https://wlghsp.tistory.com/270#entry270comment</comments>
      <pubDate>Fri, 7 Nov 2025 16:01:08 +0900</pubDate>
    </item>
    <item>
      <title>Round 1 회고</title>
      <link>https://wlghsp.tistory.com/269</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Loopers 2기 참여하여 첫주 과제진행 후 배운 점 및 느낌을 정리하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종합적으로 전반적으로 새로운 아키텍처 구조에서 과제를 구현하며 새로운 테스트 스타일, 예외 처리 등을 배울 수 있는 과제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 통합테스트, E2E 테스트 (Ent-to-End Test)&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;하지만 통합테스트, E2E 테스트라는 이름으로 테스트를 진행한 것은 처음이라 통합테스트, E2E 테스트 작성에 대해서 학습할 수 있었습니다.&lt;/li&gt;
&lt;li&gt;통합테스트는 Facade 에서 진행하였고, 기존에 하던 Service 테스트와 유사하였습니다.&lt;/li&gt;
&lt;li&gt;E2E 테스트는 기존의 컨트롤러 API 테스트와 비슷한데 API 마다 @Nested 어노테이션과 함께 inner class 로 테스트를 작성하는 방식을 배울 수 있었습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 하니 1개의 API 테스트에 대한 테스트들만을 작성하니 가독성이나 테스트 작성하는데에도 집중하기 좋았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합테스트란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합 테스트는 &lt;b&gt;애플리케이션의 여러 계층(Layer)이 함께 올바르게 작동하는지 검증&lt;/b&gt;하는 테스트입니다. 주로 Service Layer와 Repository Layer 간의 상호작용을 테스트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;E2E 테스트란?&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;E2E 테스트는 &lt;b&gt;사용자 관점에서 HTTP API를 직접 호출하여 전체 플로우를 검증&lt;/b&gt;하는 테스트입니다. 실제 사용자가 API를 호출하는 것과 동일한 환경에서 테스트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ 통합테스트 예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@DisplayName(&quot;회원 가입 시 Member 저장이 수행된다&quot;)
@Test
fun saveMemberOnJoin() {
    // Given
    val command = JoinMemberCommand(&quot;user1&quot;, &quot;test@email.com&quot;, &quot;1990-05-15&quot;, &quot;MALE&quot;)

    // When
    memberFacade.joinMember(command)

    // Then
    verify(memberFacade).joinMember(any())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;✅ E2E 테스트 예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@DisplayName(&quot;POST /api/v1/users/join - 회원 가입&quot;)
@Nested
inner class Join {
    @DisplayName(&quot;회원 가입이 성공할 경우, 생성된 유저 정보를 응답으로 반환한다&quot;)
    @Test
    fun successfulJoin() {
        // Given: 회원 가입 요청 데이터
        val request = JoinMemberRequest(
        &quot;testUser1&quot;,
        &quot;test@gmail.com&quot;,
        &quot;1990-05-15&quot;,
        &quot;MALE&quot;
        )
        // When: POST /api/v1/users/join 호출
        val responseType = object : ParameterizedTypeReference&amp;lt;ApiResponse&amp;lt;MemberV1Dto.MemberResponse&amp;gt;&amp;gt;() {}
        val response = testRestTemplate.exchange(
            &quot;/api/v1/users/join&quot;,
            HttpMethod.POST,
            HttpEntity(request),
            responseType
        )

        // Then: 응답 검증
        assertAll(
            { assertThat(response.statusCode).isEqualTo(HttpStatus.OK) },
            { assertThat(response.body?.data?.memberId).isEqualTo(&quot;testUser1&quot;) },
            { assertThat(response.body?.data?.email).isEqualTo(&quot;test@gmail.com&quot;) },
            { assertThat(response.body?.data?.gender).isEqualTo(&quot;MALE&quot;) },
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  고민한 부분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp; 제공된 예제 코드에는 Service를 통합테스트의 대상으로 삼고 있었으나, Facade에서 통합테스트 하는 것이 다수의 Service를 활용하고 트랜잭션 제어에도 용이, 그리고 전체 로직을 테스트하기 위해 Facade에서 테스트 하기로 결정하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp; 대체적으로E2E 테스트가 API 호출을 다루다보니 코드양이 많아 자칫 한 클래스에 몰려서 테스트 작성 및 실행이 불편할 거 같아&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MemberController 하나에 들어갈 API중에 Point 관련한 Api 들을 분리해서 PointController를 추가하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Spy 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mock과 Spy의 차이를 정확히 몰라 비슷한건가 보다 생각했는데 정확하게 이해할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;Spy란?&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Spy는 실제 객체를 감싸서(wrapping) 실제 동작은 그대로 수행하면서, 메서드 호출을 추적할 수 있는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;2. Mock vs Spy&lt;/p&gt;
&lt;pre id=&quot;code_1761898000665&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // Mock: 가짜 객체 (모든 동작이 stubbing 필요)
  @MockBean
  private lateinit var memberFacade: MemberFacade
  // memberFacade.joinMember()는 아무 동작도 하지 않음 (null 반환)

  // Spy: 실제 객체를 감싼 것 (실제 동작 수행 + 호출 추적)
  @MockitoSpyBean
  private lateinit var memberFacade: MemberFacade
  // memberFacade.joinMember()는 실제로 동작함 (DB 저장 등)&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;3. 코드 예시&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761897820520&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MockitoSpyBean
private lateinit var memberFacade: MemberFacade

@DisplayName(&quot;회원 가입 시 Member 저장이 수행된다&quot;)
@Test
fun saveMemberOnJoin() {
    val command = JoinMemberCommand(&quot;user1&quot;, &quot;test@email.com&quot;, &quot;1990-05-15&quot;, &quot;MALE&quot;)

    memberFacade.joinMember(command)

    verify(memberFacade).joinMember(command)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 예외 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKH2BI/dJMcaiuQgC8/NM97nlRGozXagwvbfkXvZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKH2BI/dJMcaiuQgC8/NM97nlRGozXagwvbfkXvZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKH2BI/dJMcaiuQgC8/NM97nlRGozXagwvbfkXvZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKH2BI%2FdJMcaiuQgC8%2FNM97nlRGozXagwvbfkXvZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;115&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2개의 과제 퀘스트를 처리하기 위해서는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 발생하는 예외를 확인해서 이를 처리하는 예외핸들러 메서드를 구현해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761913904654&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @ExceptionHandler
    fun handleMethodArgumentNotValid(e: MethodArgumentNotValidException): ResponseEntity&amp;lt;ApiResponse&amp;lt;*&amp;gt;&amp;gt; {
        val fieldErrors = e.bindingResult.fieldErrors
        val errorMessage = fieldErrors.joinToString(&quot;, &quot;) { error -&amp;gt;
            &quot;${error.field}: ${error.defaultMessage}&quot;
        }

        log.warn(&quot;Validation failed: {}&quot;, errorMessage)
        return failureResponse(
            errorType = ErrorType.BAD_REQUEST,
            errorMessage = errorMessage
        )
    }

    @ExceptionHandler
    fun handleMissingRequestHeader(e: MissingRequestHeaderException): ResponseEntity&amp;lt;ApiResponse&amp;lt;*&amp;gt;&amp;gt; {
        val errorMessage = e.message
        log.warn(&quot;MissingRequestHeaderException: {}&quot;, errorMessage)
        return failureResponse(
            errorType = ErrorType.BAD_REQUEST,
            errorMessage = errorMessage
        )
    }&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;성별 값이 없는 경우에는 MethodArgumentNotValidException 이 발생&lt;/li&gt;
&lt;li&gt;RequestHeader 어노테이션이 달린 값이 헤더값이 부재한 경우에는 MissingRequestHeaderException 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MethodArgumentNotValidException, MissingRequestHeaderException 를 처리하는 메서드 구현하면 이를 잡아 처리함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Loopers 2기</category>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/269</guid>
      <comments>https://wlghsp.tistory.com/269#entry269comment</comments>
      <pubDate>Fri, 31 Oct 2025 16:23:47 +0900</pubDate>
    </item>
    <item>
      <title>colima 에서  testcontainer 사용테스트 시 에러 처리</title>
      <link>https://wlghsp.tistory.com/268</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;colima 로 docker 사용 중 testcontainer 설정으로 테스트 시&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;1514&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TwBiL/btsQNe1hw3Y/hHEnKap4vesznrEKfSGUa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TwBiL/btsQNe1hw3Y/hHEnKap4vesznrEKfSGUa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TwBiL/btsQNe1hw3Y/hHEnKap4vesznrEKfSGUa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTwBiL%2FbtsQNe1hw3Y%2FhHEnKap4vesznrEKfSGUa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;144&quot; data-origin-width=&quot;1514&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  원인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 환경을 찾을 수 없다는 에러입니다. Colima와 테스트컨테이너를 함께 사용할 때 자주 발생하는 문제&lt;/p&gt;
&lt;p data-ke-size=&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;build.gradle 에 아래 4줄을 입력하고 Sync gradle changes 눌러주면 정상적으로 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5jF2L/btsQM4R99WS/xGvXQjwkiXYSEN7KIXgF71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5jF2L/btsQM4R99WS/xGvXQjwkiXYSEN7KIXgF71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5jF2L/btsQM4R99WS/xGvXQjwkiXYSEN7KIXgF71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5jF2L%2FbtsQM4R99WS%2FxGvXQjwkiXYSEN7KIXgF71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;799&quot; height=&quot;342&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758768985604&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  environment &quot;DOCKER_HOST&quot;, &quot;unix://${System.getProperty(&quot;user.home&quot;)}/.colima/default/docker.sock&quot;
  environment &quot;TESTCONTAINERS_RYUK_DISABLED&quot;, &quot;true&quot;
  environment &quot;TESTCONTAINERS_REUSE_ENABLE&quot;, &quot;true&quot;
  systemProperty &quot;testcontainers.docker.socket.override&quot;, &quot;/var/run/docker.sock&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/268</guid>
      <comments>https://wlghsp.tistory.com/268#entry268comment</comments>
      <pubDate>Thu, 25 Sep 2025 11:58:10 +0900</pubDate>
    </item>
    <item>
      <title>fork/exec /Users/jihochoi/.docker/cli-plugins/docker-buildx: no such file or directory</title>
      <link>https://wlghsp.tistory.com/267</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-desktop을 사용하지 못하는 상황에서&amp;nbsp;colima 로 docker를 사용하면, docker-compose 설치 하고 docker-compose 실행 중에 아래와 같은 명령어로 실행이 안되는 경우 강 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758706268345&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fork/exec /Users/jihochoi/.docker/cli-plugins/docker-buildx: no such file or directory&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 원인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker buildx 가 설치되어 있지 않거나, 해당 경로에 실행 파일이 없어서 발생하는 오류이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;M3 맥기준 해결책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 한 줄 씩 입력하자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#&amp;nbsp;1.&amp;nbsp;아키텍처&amp;nbsp;설정&amp;nbsp;(Intel&amp;nbsp;Mac:&amp;nbsp;amd64&amp;nbsp;/&amp;nbsp;Apple&amp;nbsp;Silicon&amp;nbsp;M1,&amp;nbsp;M2:&amp;nbsp;arm64)&lt;br /&gt;ARCH=arm64&amp;nbsp;&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;2.&amp;nbsp;buildx&amp;nbsp;버전&amp;nbsp;설정&amp;nbsp;(필요&amp;nbsp;시&amp;nbsp;최신&amp;nbsp;버전으로&amp;nbsp;변경&amp;nbsp;가능)&lt;br /&gt;VERSION=v0.10.5&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;3.&amp;nbsp;buildx&amp;nbsp;바이너리&amp;nbsp;다운로드&lt;br /&gt;curl&amp;nbsp;-LO&amp;nbsp;&lt;a href=&quot;https://github.com/docker/buildx/releases/download/$&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/docker/buildx/releases/download/$&lt;/a&gt;{VERSION}/buildx-${VERSION}.darwin-${ARCH}&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;4.&amp;nbsp;CLI&amp;nbsp;플러그인&amp;nbsp;디렉토리&amp;nbsp;생성&lt;br /&gt;mkdir&amp;nbsp;-p&amp;nbsp;~/.docker/cli-plugins&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;5.&amp;nbsp;바이너리&amp;nbsp;이동&amp;nbsp;및&amp;nbsp;실행&amp;nbsp;권한&amp;nbsp;부여&lt;br /&gt;mv&amp;nbsp;buildx-${VERSION}.darwin-${ARCH}&amp;nbsp;~/.docker/cli-plugins/docker-buildx&lt;br /&gt;chmod&amp;nbsp;+x&amp;nbsp;~/.docker/cli-plugins/docker-buildx&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;6.&amp;nbsp;설치&amp;nbsp;확인&lt;br /&gt;docker&amp;nbsp;buildx&amp;nbsp;version&lt;/p&gt;</description>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/267</guid>
      <comments>https://wlghsp.tistory.com/267#entry267comment</comments>
      <pubDate>Wed, 24 Sep 2025 18:33:00 +0900</pubDate>
    </item>
    <item>
      <title>25년 1월 ~ 2월 헤이밥 알고리즘 과외 후기</title>
      <link>https://wlghsp.tistory.com/265</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요약:&amp;nbsp;&lt;br /&gt;헤이밥 과외로 브론즈 문제만 많이 풀다가 16주 강의 듣고 골드 3 까지는 도전할만하게 됨&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;시작할&amp;nbsp;때&lt;br /&gt;&lt;br /&gt;스터디를&amp;nbsp;하며&amp;nbsp;브론즈~실버&amp;nbsp;문제를&amp;nbsp;많이&amp;nbsp;풀었지만&amp;nbsp;스스로&amp;nbsp;풀지&amp;nbsp;못하고&amp;nbsp;있었다&lt;br /&gt;그래서&amp;nbsp;실력이&amp;nbsp;늘지&amp;nbsp;못하고&amp;nbsp;골드&amp;nbsp;문제에&amp;nbsp;도전을&amp;nbsp;하지&amp;nbsp;못하고&amp;nbsp;있었다&lt;br /&gt;&lt;br /&gt;또&amp;nbsp;주변에&amp;nbsp;코딩테스트&amp;nbsp;준비하는&amp;nbsp;사람도&amp;nbsp;없다보니&lt;br /&gt;물어볼&amp;nbsp;사람도&amp;nbsp;없고,&amp;nbsp;어떻게&amp;nbsp;체계적으로&amp;nbsp;공부해야할&amp;nbsp;지를&amp;nbsp;몰랐었다&lt;br /&gt;&lt;br /&gt;개발&amp;nbsp;회사에&amp;nbsp;다녀서&amp;nbsp;개발은&amp;nbsp;하지만,&amp;nbsp;알고리즘에&amp;nbsp;대한&amp;nbsp;공부는&amp;nbsp;체계적으로&amp;nbsp;한&amp;nbsp;적이&amp;nbsp;없는&amp;nbsp;상황이었다&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;선택한&amp;nbsp;이유&lt;br /&gt;&lt;br /&gt;헤이밥&amp;nbsp;인프런&amp;nbsp;강의를&amp;nbsp;들어봤는데&amp;nbsp;문제&amp;nbsp;접근법이나&amp;nbsp;수학&amp;nbsp;내용&amp;nbsp;등을&amp;nbsp;세밀하게&amp;nbsp;알려주고&amp;nbsp;있었다&lt;br /&gt;특히&amp;nbsp;Big-O를&amp;nbsp;어떻게&amp;nbsp;계산하는지&amp;nbsp;내용을&amp;nbsp;보고&amp;nbsp;좋다고&amp;nbsp;생각했다&lt;br /&gt;&lt;br /&gt;로그&amp;nbsp;계산&amp;nbsp;내용을&amp;nbsp;알려주고&amp;nbsp;각&amp;nbsp;문제마다&amp;nbsp;계산&amp;nbsp;복잡도를&amp;nbsp;강의하는&amp;nbsp;것을&amp;nbsp;보고&amp;nbsp;과외를&amp;nbsp;받아도&amp;nbsp;좋겠다고&amp;nbsp;생각했다&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;좋았던&amp;nbsp;점&lt;br /&gt;&lt;br /&gt;생각하는&amp;nbsp;법을&amp;nbsp;알려주었다.&amp;nbsp;어떻게&amp;nbsp;문제에&amp;nbsp;접근하는지&amp;nbsp;나의&amp;nbsp;케이스에&amp;nbsp;맞게&amp;nbsp;맞춰&amp;nbsp;배울&amp;nbsp;수&amp;nbsp;있었다&lt;br /&gt;&lt;br /&gt;지식의&amp;nbsp;빈&amp;nbsp;공간을&amp;nbsp;채워주었다.&amp;nbsp;문제&amp;nbsp;읽는&amp;nbsp;법을&amp;nbsp;위한&amp;nbsp;글&amp;nbsp;읽기&amp;nbsp;과제도&amp;nbsp;있었고,&amp;nbsp;수학&amp;nbsp;과제와&amp;nbsp;수학&amp;nbsp;지식을&amp;nbsp;활용하는&amp;nbsp;법도&amp;nbsp;배울&amp;nbsp;수&amp;nbsp;있었고,&amp;nbsp;코딩테스트&amp;nbsp;문제&amp;nbsp;해결을&amp;nbsp;위한&amp;nbsp;소프트웨어&amp;nbsp;설계&amp;nbsp;또한&amp;nbsp;배울&amp;nbsp;수&amp;nbsp;있었다.&amp;nbsp;이런&amp;nbsp;내용은&amp;nbsp;코딩테스트&amp;nbsp;뿐만&amp;nbsp;아니라&amp;nbsp;회사에&amp;nbsp;돌아가&amp;nbsp;주중에&amp;nbsp;실무를&amp;nbsp;하는&amp;nbsp;데에도&amp;nbsp;쓸모가&amp;nbsp;있었다&lt;br /&gt;&lt;br /&gt;수학&amp;nbsp;과제를&amp;nbsp;내주었는데,&amp;nbsp;고등학교&amp;nbsp;과정에서&amp;nbsp;필요한&amp;nbsp;내용만&amp;nbsp;빼서&amp;nbsp;알려주었기에&amp;nbsp;최적화된&amp;nbsp;공부를&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있었다&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;결과&lt;br /&gt;&lt;br /&gt;쉽게 풀지도 못하고 며칠 고생하면 골드 3 까지는 문제를 풀 수 있게 되었다&lt;br /&gt;좀 더 노력해서 코딩테스트에 통과할 수 있는 실력을 갖춰야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝.&lt;/p&gt;</description>
      <category>알고리즘</category>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/265</guid>
      <comments>https://wlghsp.tistory.com/265#entry265comment</comments>
      <pubDate>Fri, 7 Mar 2025 09:08:11 +0900</pubDate>
    </item>
    <item>
      <title>Dto to Entity, Entity to Dto 변환</title>
      <link>https://wlghsp.tistory.com/264</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;# 1. Dto to Entity or Entity to Dto&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;서비스에서 변환처리&amp;nbsp;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2200&quot; data-origin-height=&quot;2816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSPuxr/btsAnAyiRg6/eRkged50aqwxAveD1JIg21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSPuxr/btsAnAyiRg6/eRkged50aqwxAveD1JIg21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSPuxr/btsAnAyiRg6/eRkged50aqwxAveD1JIg21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSPuxr%2FbtsAnAyiRg6%2FeRkged50aqwxAveD1JIg21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;681&quot; data-origin-width=&quot;2200&quot; data-origin-height=&quot;2816&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;&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;우선 Spring Data JPA를 사용하게 되면 Entity를 받아 오므로 싸비스에서 변환해주는게 좋다.&lt;/li&gt;
&lt;li&gt;비즈니스 처리, 로직 처리 등은 서비스에서 하는게 좋지 않을까 해서 서비스에서 함.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;# 2. 사용한 변환 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;b&gt;포켓 몬스터 너로 정했다 !!! : Builder 패턴&amp;nbsp;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bul2h8/btsAovKbS8R/ojkDDSk8FPtvIykw9RKcC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bul2h8/btsAovKbS8R/ojkDDSk8FPtvIykw9RKcC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bul2h8/btsAovKbS8R/ojkDDSk8FPtvIykw9RKcC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbul2h8%2FbtsAovKbS8R%2FojkDDSk8FPtvIykw9RKcC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;151&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;616&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;1332&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B0S4R/btsAjVi6icx/3KeyOK611qOJkhWg8xTuC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B0S4R/btsAjVi6icx/3KeyOK611qOJkhWg8xTuC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B0S4R/btsAjVi6icx/3KeyOK611qOJkhWg8xTuC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB0S4R%2FbtsAjVi6icx%2F3KeyOK611qOJkhWg8xTuC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;616&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;616&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;생성자나 대신에 Builder 패턴을 사용한 이유는 유연성, 가독성, 불변성&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;1. 생성자는 파라미터가 많을 경우 가독성이 좋지 않고, 순서를 정해줘야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 값 설정 순서가 상관이 없다. 생성자는 순서가 중요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 생성자는 그때 그때 파라미터 수에 맞게 생성자를 만들어줘야 함&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;Setter를 사용하지 않은 이유&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;객체의 일관성 유지가 어려움. public으로 어디서든 객체의 상태를 변경하게 되므로 변경지점 파악도 어려움&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Setter를 지양하고 변경의 이유가 있을 때는 객체에 변경에 관한 메서드를 만들어 사용하는 것이 객체지향적인 코드를 작성하기 용이함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌더패턴 관련해서는 아래 블로그 참고 바람&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev-youngjun.tistory.com/197&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev-youngjun.tistory.com/197&lt;/a&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스프링</category>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/264</guid>
      <comments>https://wlghsp.tistory.com/264#entry264comment</comments>
      <pubDate>Wed, 15 Nov 2023 09:33:43 +0900</pubDate>
    </item>
    <item>
      <title>@ResponseBody 와 JPA의 엔티티는 기본생성자가 필수</title>
      <link>https://wlghsp.tistory.com/263</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;# 1. Spring RequestBody와 JPA 엔티티에 기본 생성자가 필요한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;da1a&quot;&gt;Reflection을 사용하기 때문이다.&lt;/li&gt;
&lt;li&gt;Reflection은 구체적인 타입은 알지 못하더라도, 내부 변수와 메소드에 접근할 수 있는 방법이다.&lt;/li&gt;
&lt;li&gt;만약 생성자가 여러 개 있다면, Reflection은 이 중 어떤 생성자를 호출해야 할지 알 수가 없다. 따라서 Reflection은&lt;span style=&quot;color: #242424; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;기본 생성자를 통해 객체를 생성&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; letter-spacing: 0px;&quot;&gt;한 후 값을 넣어준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://medium.com/sjk5766/%EC%99%9C-responsebody%EB%8A%94-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90%EA%B0%80-%ED%95%84%EC%9A%94-%ED%96%88%EC%9D%84%EA%B9%8C-8104e6d02408&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/sjk5766/%EC%99%9C-responsebody%EB%8A%94-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90%EA%B0%80-%ED%95%84%EC%9A%94-%ED%96%88%EC%9D%84%EA%B9%8C-8104e6d02408&lt;/a&gt;&lt;/p&gt;</description>
      <category>스프링</category>
      <author>고구마와 감자</author>
      <guid isPermaLink="true">https://wlghsp.tistory.com/263</guid>
      <comments>https://wlghsp.tistory.com/263#entry263comment</comments>
      <pubDate>Mon, 13 Nov 2023 15:06:27 +0900</pubDate>
    </item>
    <item>
      <title>DDD 설계 vs SQL중심 설계</title>
      <link>https://wlghsp.tistory.com/262</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 1. DDD(Domain Driven Design) 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) DDD란?&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;이런 도메인을 중심으로 설계해 나가는 방법을 DDD 라고 함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 2. SQL 중심 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 구조와 데이터의 흐름을 중심으로 시스템을 개발하는 것&lt;/li&gt;
&lt;/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://wlghsp.tistory.com/262</guid>
      <comments>https://wlghsp.tistory.com/262#entry262comment</comments>
      <pubDate>Sat, 11 Nov 2023 13:45:36 +0900</pubDate>
    </item>
  </channel>
</rss>