Skip to content

[volume-4] 쿠폰 도메인 구현 및 동시성 제어#160

Open
letter333 wants to merge 134 commits intoLoopers-dev-lab:letter333from
letter333:WEEK4
Open

[volume-4] 쿠폰 도메인 구현 및 동시성 제어#160
letter333 wants to merge 134 commits intoLoopers-dev-lab:letter333from
letter333:WEEK4

Conversation

@letter333
Copy link

@letter333 letter333 commented Mar 6, 2026

4주차에 대한 커밋만 보시려면 이거로 보시는게 더 좋을 것 같습니다.

📌 Summary

  • 배경: 커머스 API에 쿠폰 기능이 부재하고, 주문·재고·좋아요 등 핵심 도메인에 동시성 제어가 없어 데이터 정합성 리스크가 존재
  • 목표: 쿠폰 도메인 전체 레이어 구현 + 주요 도메인 동시성 제어 + 배송비 자동 계산
  • 결과: 쿠폰 Admin/User API 완성, 비관적/낙관적 락 기반 동시성 제어 적용

🧭 Context & Decision

문제 정의

  • 현재 동작/제약: 쿠폰 기능 미구현, 동시 요청 시 재고 초과 차감·쿠폰 중복 발급·좋아요 카운트 유실 가능
  • 문제(또는 리스크): 플래시 세일 등 동시 트래픽 시 데이터 정합성 보장 불가
  • 성공 기준: 모든 동시성 테스트(10~50 스레드) 통과, E2E 테스트 전량 통과

선택지와 결정

동시성 전략

  • 고려한 대안:
    • A: 낙관적 락(Optimistic Lock) — 충돌 시 재시도, DB 부하 낮음
    • B: 비관적 락(Pessimistic Lock) — SELECT FOR UPDATE로 직렬화, 충돌 방지
    • C: 원자적 네이티브 UPDATE — JPA 우회, DB 레벨 원자적 연산
  • 최종 결정: 도메인별 차등 적용
    • 재고(Product)·쿠폰 발급(Coupon)·주문 취소(Order) → 비관적 락 (높은 경합 + 카운터 불변식, 무거운 부수효과의 fail-fast)
    • 쿠폰 사용(MemberCoupon) → 낙관적 락 (@Version, 낮은 경합(단일 유저 소유) + 단순 상태 전이 + 부수효과 없음)
    • Product·Brand 좋아요 → 원자적 네이티브 UPDATE (like_count = like_count ± 1, JPA 영속성 컨텍스트 우회)
  • 트레이드오프: 비관적 락은 동시성 높을수록 대기 시간 증가, 그러나 정합성이 우선. 낙관적 락은 경합이 낮은 도메인에서 락 대기 없이 성능 이점 확보
  • 추후 개선 여지: Redis 분산 락 또는 Kafka 기반 비동기 처리로 확장 가능

MemberCoupon 낙관적 락 + Facade 재시도

  • 배경: MemberCoupon은 단일 유저만 소유하므로 경합이 거의 없고, 상태 전이(AVAILABLE→USED)만 수행하여 부수효과가 없음
  • 구현: @Version 필드로 낙관적 락 적용, OrderFacade.createOrder()에 @retryable로 ObjectOptimisticLockingFailureException 발생 시 최대 3회 재시도
  • AOP 순서: @retryable(order=MAX-1)이 @transactional(order=MAX)보다 우선 → 재시도마다 새 트랜잭션 생성, 이전 재고 차감도 롤백
  • 멱등성: 재시도 시 쿠폰이 이미 USED면 CoreException("이미 사용된 쿠폰") → 재시도 중단

쿠폰 중복 발급 방지

  • 고려한 대안:
    • A: 애플리케이션 레벨 중복 체크 (TOCTOU 취약)
    • B: DB UNIQUE 제약조건 + 비관적 락 이중 방어
  • 최종 결정: B — findByIdForUpdate로 수량 체크 후, (memberId, couponId) UNIQUE 제약으로 2차 방어

데드락 방지

  • 주문 생성/취소 시 다수 Product 락 획득 → productId 오름차순 정렬로 락 순서 고정

🏗️ Design Overview

변경 범위

  • 영향 받는 모듈/도메인: commerce-api (coupon, order, product, brand, like)
  • 신규 추가: 쿠폰 도메인 전체 (domain/infrastructure/application/interfaces 4계층), 배송비 계산, 동시성 테스트 9개
  • 제거/대체: OrderService.cancelOrder()OrderFacade로 일원화, dead code 제거

주요 컴포넌트 책임

쿠폰 도메인

  • Coupon: 쿠폰 생성/수정/삭제, 할인 계산(FIXED/RATE), 발급 수량 관리
  • MemberCoupon: 발급된 쿠폰 상태 관리(AVAILABLE→USED→AVAILABLE), 사용/취소
  • CouponValidator: 쿠폰 생성/수정 시 비즈니스 규칙 검증 (유틸리티 클래스)
  • CouponFacade: Admin CRUD, 사용자 발급/조회 오케스트레이션
  • CouponAdminV1Controller / CouponV1Controller: REST API 엔드포인트

동시성 제어

  • ProductService.decreaseStock/increaseStock: ID-only 비관적 락 → 재고 변경
  • MemberCouponService.issueCoupon: 비관적 락 + UNIQUE 제약 이중 방어
  • MemberCouponService.useCoupon: @Version 낙관적 락으로 이중 사용 방지 (비관적 락에서 전환)
  • OrderFacade.createOrder: @retryable로 낙관적 락 실패 시 전체 재시도 (재고 차감 포함 롤백 후 새 TX)
  • OrderFacade.cancelOrder/changeOrderStatusForAdmin: 주문 행 비관적 락 → 재고 복구 + 쿠폰 취소
  • Product·Brand 좋아요: 원자적 네이티브 UPDATE (like_count = like_count ± 1)

주문 개선

  • Order: 상태 머신(canCancel/canTransitionTo), 배송비 자동 계산(5만원 이상 무료)
  • OrderFacade.restoreStockAndCancelCoupon(): 취소 시 재고+쿠폰 복구 공통 메서드

🔁 Flow Diagram

쿠폰 발급 Flow (동시성 제어 포함)

sequenceDiagram
  autonumber
  participant Client
  participant CouponFacade
  participant MemberCouponService
  participant CouponRepository
  participant MemberCouponRepository
  participant DB

  Client->>CouponFacade: POST /api/v1/coupons/{id}/issue
  CouponFacade->>MemberCouponService: issueCoupon(couponId, memberId)
  MemberCouponService->>CouponRepository: findByIdForUpdate(couponId)
  CouponRepository->>DB: SELECT id FOR UPDATE
  DB-->>CouponRepository: locked row
  MemberCouponService->>MemberCouponService: coupon.issue() (수량 체크 + 증가)
  MemberCouponService->>CouponRepository: save(coupon)
  MemberCouponService->>MemberCouponRepository: save(memberCoupon)
  Note over MemberCouponRepository,DB: UNIQUE(memberId, couponId) 중복 방어
  MemberCouponRepository-->>MemberCouponService: saved
  MemberCouponService-->>CouponFacade: MemberCoupon
  CouponFacade-->>Client: 발급 완료
Loading

주문 생성 Flow (낙관적 락 + @retryable 재시도)

sequenceDiagram
  autonumber
  participant Client
  participant OrderFacade
  participant ProductService
  participant MemberCouponService
  participant OrderService
  participant DB

  Client->>OrderFacade: POST /api/v1/orders
  Note over OrderFacade: @Retryable (max 3회, ObjectOptimisticLockingFailureException)
  Note over OrderFacade: @Transactional (재시도마다 새 TX)

  loop 상품별 (productId 오름차순)
    OrderFacade->>ProductService: decreaseStock(productId, optionId, qty)
    ProductService->>DB: SELECT id FOR UPDATE → 재고 차감
  end

  alt 쿠폰 사용 요청 있음
    OrderFacade->>MemberCouponService: useCoupon(memberCouponId)
    MemberCouponService->>DB: SELECT (낙관적 락, @Version 체크)
    alt 버전 충돌
      DB-->>MemberCouponService: ObjectOptimisticLockingFailureException
      MemberCouponService-->>OrderFacade: 예외 전파
      Note over OrderFacade: TX 롤백 (재고 차감 포함) → 재시도
    else 쿠폰 이미 USED
      MemberCouponService-->>OrderFacade: CoreException("이미 사용된 쿠폰")
      Note over OrderFacade: 재시도 중단 (비즈니스 예외)
    else 성공
      MemberCouponService->>DB: UPDATE status=USED, version+1
    end
  end

  OrderFacade->>OrderService: createOrder(order)
  OrderService->>DB: INSERT order
  OrderFacade-->>Client: 주문 완료
Loading

주문 취소 Flow

sequenceDiagram
  autonumber
  participant Client
  participant OrderFacade
  participant OrderService
  participant ProductService
  participant MemberCouponService
  participant DB

  Client->>OrderFacade: DELETE /api/v1/orders/{id}
  OrderFacade->>OrderService: getOrderForUpdate(orderId)
  OrderService->>DB: SELECT ... FOR UPDATE (주문 락)
  OrderFacade->>OrderFacade: order.cancel()
  loop 상품별 (productId 오름차순)
    OrderFacade->>ProductService: increaseStock(productId, optionId, qty)
    ProductService->>DB: SELECT id FOR UPDATE → 재고 복구
  end
  OrderFacade->>MemberCouponService: cancelCouponUsage(orderId)
  OrderFacade->>OrderService: saveOrder(order)
  OrderFacade-->>Client: 취소 완료
Loading

letter333 and others added 30 commits February 1, 2026 20:43
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- member-erd.md: 회원 테이블 ERD 설계
- member-signup-design.md: 시퀀스/클래스 다이어그램, 패키지 구조
- CLAUDE.md: 개발 규칙 및 문서 작성 가이드 반영

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Member 도메인 객체 구현 (순수 Java, JPA 어노테이션 없음)
- 필드 검증: loginId, password, name, birthday, email
- 비밀번호 규칙: 8~16자, 영문+숫자+특수문자, 생년월일 포함 불가
- encryptPassword()로 암호화된 비밀번호 교체 지원

test: add MemberTest with 14 unit test cases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… entity

Member 도메인의 Infrastructure 레이어 구현:
- MemberEntity (JPA 영속성 엔티티, Domain↔Entity 변환)
- MemberRepository 인터페이스 (도메인 레이어)
- MemberJpaRepository (Spring Data JPA)
- MemberRepositoryImpl (Repository 구현체)
- MemberEntityTest, MemberRepositoryImplIntegrationTest
- spring-security-crypto 의존성 추가
- docker-java.properties (Docker Engine 29 TestContainers 호환)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberService.signUp(): 중복 검사 → 도메인 생성 → 비밀번호 암호화 → 저장
- PasswordEncoderConfig: BCryptPasswordEncoder Bean 등록
- MemberServiceTest: 정상 가입, loginId 중복, email 중복 통합 테스트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberInfo: Domain → 응답 변환 record (password, birthday 제외)
- MemberFacade: MemberService 위임 및 MemberInfo 변환
- MemberInfoTest, MemberFacadeTest 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- POST /api/v1/members → 201 Created
- MemberV1Dto: SignUpRequest/SignUpResponse record
- MemberV1ApiSpec: Swagger 스펙 인터페이스
- MemberV1Controller: Facade 위임 및 응답 변환
- MemberV1ApiE2ETest: 정상 가입(201), 검증 실패(400), 중복(409) E2E 테스트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Controller에서 birthday null/빈 문자열/잘못된 형식 시 400 BAD_REQUEST 반환
- birthday 관련 E2E 테스트 2건 추가
- 회원가입 API .http 파일 생성
- CLAUDE.md 프로젝트 규칙 보강

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 내 정보 조회 기능 시퀀스/클래스 다이어그램 작성
- 회원가입 시퀀스 다이어그램 Entity 반환 화살표 누락 수정

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberRepository 인터페이스에 findByLoginId 추가
- MemberJpaRepository, MemberRepositoryImpl 구현
- 통합 테스트 2건 추가 (존재/미존재 케이스)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberService.authenticate() 메서드 추가 (loginId 조회 + 비밀번호 검증)
- ErrorType에 UNAUTHORIZED(401) 추가
- 통합 테스트 3건 추가 (성공, 회원 미존재, 비밀번호 불일치)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- GET /api/v1/members/me 엔드포인트 추가 (X-Loopers-LoginId, X-Loopers-LoginPw 헤더 인증)
- MemberInfo에 birthday 필드 추가, MyInfoResponse DTO 추가
- MemberFacade.getMyInfo(), MemberV1ApiSpec, MemberV1Controller 구현
- ApiControllerAdvice에 MissingRequestHeaderException 핸들러 추가
- E2E 테스트 4건 추가 (200, 401×2, 400)
- 통합 테스트 생성자 주입으로 리팩터링 (필드 주입 → 생성자 주입)
- member-v1.http에 내 정보 조회 요청 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- password MIN(8자), MAX(16자) 성공 테스트 추가
- name MIN(한글 2자), MAX(한글 20자) 성공 테스트 추가
- birthday 오늘 날짜(경계값) 성공 테스트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Member.changePassword() 메서드를 통해 현재 비밀번호 검증, 동일 비밀번호 방지,
새 비밀번호 룰 검증(길이/패턴/생년월일), 암호화까지 도메인 엔티티에서 캡슐화

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MemberService.updatePassword() 추가, MemberRepository에 updatePassword 메서드 정의,
MemberRepositoryImpl에서 JPA dirty checking 기반 UPDATE 구현, 통합 테스트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PATCH /api/v1/members/me/password 엔드포인트 추가,
헤더 PW와 Body currentPassword 일치 검증, E2E 테스트 8건 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
API 응답 규칙, 의존성 방향, 인증 헤더 규칙, TDD 단계별 진행 규칙,
테스트 경계값 케이스 가이드 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
도메인 계층의 PasswordEncoder 의존성을 제거하고,
유즈케이스 검증(현재 비밀번호 확인, 동일 비밀번호 확인)을
MemberService로 이동하여 의존성 방향 규칙 준수

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
refactor: 비밀번호 변경 검증 책임을 서비스 레이어로 분리
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
feat: 회원 도메인 기능 구현 (가입, 조회, 비밀번호 변경)
MemberInfo에 withMaskedName() 메서드 추가하여
이름의 마지막 글자를 *로 마스킹하는 기능 구현

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
feat: 내 정보 조회 시 이름 마스킹 기능 추가
LOGIN_ID_PATTERN을 추가하여 영문 대소문자와 숫자만 허용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
letter333 and others added 25 commits March 3, 2026 22:14
- 기본 배송비 3,000원, 50,000원 이상 주문 시 무료배송
- totalAmount 기준으로 배송비 판단 (쿠폰 할인 적용 전)
- 배송비 테스트 6개 케이스 추가 (경계값 포함)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
feat: 배송비 자동 계산 기능 구현
동일한 시그니처의 메서드가 중복 정의되어 컴파일 에러가 발생하는 문제 수정

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberCoupon 생성자에 Coupon 파라미터 추가
- setCoupon() 메서드 제거로 도메인 객체 불변성 강화
- CouponFacade.issueCoupon()에서 중복 Coupon 조회 제거 (DB 쿼리 1회 감소)
- MemberCouponEntity.toDomainWithCoupon()에서 생성자로 Coupon 전달

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- OrderFacade에서 CouponFacade 의존을 MemberCouponService 직접 의존으로 교체하여
  Facade→Facade 의존 제거 및 MemberCoupon 중복 DB 조회(2회→1회) 해소
- getMyCoupons API에 Page 기반 페이지네이션 추가 및 count 쿼리 분리
- Order.removeCouponDiscount() 미사용 데드 코드 제거
- CouponFacade에서 주문 전용 메서드 3개 제거 (calculateCouponDiscount, applyCoupon, cancelCouponUsage)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Domain Service 4개: MemberService, BrandService, CategoryService, ProductService → @service
- Infrastructure Repository 4개: MemberRepositoryImpl, BrandRepositoryImpl, CategoryRepositoryImpl, ProductRepositoryImpl → @repository

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BrandInfo에 likeCount 필드가 존재하지만 BrandResponse DTO에 매핑되지 않아
API 응답에서 좋아요 수가 누락되던 문제 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CategoryRepository, CategoryJpaRepository, CategoryRepositoryImpl에서
호출처 없는 findAllActiveByParentId() 메서드 삭제

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ID 포함 생성자(DB 복원용)에서 validateBirthday() 호출 누락 수정
- 인코딩된 비밀번호에 대한 validatePasswordNotContainsBirthday()는
  복원 경로에서 의미 없으므로 제외

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DB에서 복원되는 데이터는 저장 시 이미 검증된 값이므로 매 조회마다 LocalDate.now() 호출하는 오버헤드 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- @component → @Service/@repository 어노테이션 일관성 통일 (coupon 도메인)
- getMyCoupons() 3개 COUNT 쿼리를 SUM+CASE 1개 통합 쿼리로 최적화
- getIssuableCoupons() 전체 ID 메모리 로딩을 DB LEFT JOIN 쿼리로 대체
- OrderFacade 쿠폰 검증/할인 계산 로직을 MemberCouponService로 추출
- 미사용 import 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 통합 쿼리(getStatusCounts)로 대체된 개별 COUNT 메서드 3건 제거
- LEFT JOIN 쿼리로 대체된 getIssuedCouponIds, findAllIssuable 체인 제거
- MemberCouponListInfo 미사용 import 제거
- validateAndCalculateDiscount → validateAndGetCoupon으로 변경하여
  OrderFacade에서 MemberCoupon 재조회 없이 쿠폰 사용 처리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
refactor: 코드 품질 개선 및 데드 코드 정리
SELECT ... FOR UPDATE 네이티브 쿼리로 상품 row를 잠근 후 재고를 변경하여
동시 주문 시 Lost Update로 인한 과매도(overselling)를 방지한다.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Product: 기존 재고용 비관적 락(findByIdForUpdate) 재사용하여 likeCount 증감 시 Lost Update 방지
- Brand: @Version 낙관적 락 + TransactionTemplate 재시도(최대 3회)로 동시성 문제 해결
- BrandEntity에 version 필드 추가, BrandRepository에 likeCount 전용 메서드 추가
- ApiControllerAdvice에 ObjectOptimisticLockingFailureException 핸들러 추가
- Product/Brand 각각 동시성 테스트(10 스레드) 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
useCoupon 호출 시 SELECT FOR UPDATE로 row-level lock을 획득하여
동시 사용 요청에서 정확히 1건만 성공하도록 보장한다.
원자적 UPDATE 대신 비관적 락을 선택한 이유:
- OrderFacade 구조상 할인 계산을 위한 SELECT 생략 불가
- 도메인 객체(MemberCoupon.use())에서 에러 세분화 유지
- 프로젝트 전체 동시성 패턴과 일관성 확보

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
주문 취소 시 재고 복구·쿠폰 반환 등 부수 효과의 이중 실행을 방지하기 위해
Order에 비관적 락(SELECT FOR UPDATE)을 적용하고, 단일 locking read 패턴으로
MySQL REPEATABLE READ 환경에서의 stale read 문제를 해결한다.

- OrderJpaRepository: @lock(PESSIMISTIC_WRITE) 적용 JPQL 추가
- OrderService: getOrderForUpdate, saveOrder 메서드 추가
- OrderFacade: cancelOrder, changeOrderStatusForAdmin 비관적 락 기반으로 변경
- 단위 테스트 수정 및 동시성 통합 테스트(OrderCancelConcurrencyTest) 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
createOrder, cancelOrder, changeOrderStatusForAdmin에서 Product 락을
클라이언트 요청 순서대로 획득하던 것을 productId 오름차순으로 정렬하여
순환 대기(Circular Wait) 조건을 원천 차단한다.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 락 없이 취소하는 위험 경로(OrderService.cancelOrder) 제거
- 취소는 OrderFacade에서 비관적 락 + 재고복원 + 쿠폰취소를 포함한 완전한 흐름으로만 실행
- 주문 상태 변경 동시성 테스트 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: 동시성 문제 해결 — 비관적 락 및 데드락 방지 적용
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- MemberCouponRepository에서 미사용 existsByMemberIdAndCouponId 제거
- LikeFacadeTest의 transactionTemplate 모킹을 mockTransactionTemplate()으로 추출

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af7ff3b2-a208-4c3e-bf47-0f8f9d1da69a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@letter333 letter333 changed the title feat: WEEK4 — 쿠폰 도메인 구현 및 동시성 제어 [volume-4] — 쿠폰 도메인 구현 및 동시성 제어 Mar 6, 2026
@letter333 letter333 changed the title [volume-4] — 쿠폰 도메인 구현 및 동시성 제어 [volume-4] 쿠폰 도메인 구현 및 동시성 제어 Mar 6, 2026
letter333 and others added 2 commits March 6, 2026 18:49
- Product/Brand의 like_count 증감을 낙관적 락 + 엔티티 수정 방식에서 네이티브 SQL(like_count + 1) 원자적 UPDATE로 변경
- LikeFacade의 브랜드 좋아요에서 TransactionTemplate 재시도 로직 제거
- LikeFacadeConcurrencyTest 신규 추가: 10명 동시 좋아요/취소 시 like_count 정합성 검증
- ProductLikeCountPersistenceTest 신규 추가: DB 반영 검증

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- MemberCouponEntity에 @Version 필드 추가로 낙관적 락 적용
- 비관적 락(lockById, findByIdForUpdate) 제거 → findByIdWithCoupon 사용
- OrderFacade.createOrder()에 @retryable 추가 (OptimisticLock 실패 시 최대 3회 재시도)
- spring-retry 의존성 및 @EnableRetry 설정 추가
- 동시성 테스트에서 ObjectOptimisticLockingFailureException 처리 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants