[4주차] 쿠폰 신규 도메인 개발, 동시성 제어 및트랜잭션 관리 - 양권모#163
Merged
Praesentia-YKM merged 79 commits intoLoopers-dev-lab:Praesentia-YKMfrom Mar 12, 2026
Merged
[4주차] 쿠폰 신규 도메인 개발, 동시성 제어 및트랜잭션 관리 - 양권모#163Praesentia-YKM merged 79 commits intoLoopers-dev-lab:Praesentia-YKMfrom
Praesentia-YKM merged 79 commits intoLoopers-dev-lab:Praesentia-YKMfrom
Conversation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- spring-security-crypto 의존성 추가 (BCryptPasswordEncoder) - PasswordEncoderConfig 빈 등록 - ErrorType에 UNAUTHORIZED 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 4개 VO 구현 (LoginId, Password, MemberName, Email) - MemberModel 엔티티 (@Embedded VO, matchesPassword 행위 메서드) - MemberRepository 인터페이스 및 JPA 구현 - ErrorType 도메인 에러 코드 추가 (10개) - 단위 테스트: VO 검증 + MemberModel + Repository 통합테스트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberSignupService (중복 체크, 비밀번호 암호화, 저장) - 단위 테스트: Mock을 활용한 동작 검증 - 통합 테스트: 실제 DB 연동 검증 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberAuthService (loginId/password 검증, 회원 조회) - 단위 테스트: Mock을 활용한 동작 검증 - 통합 테스트: 실제 DB 연동 검증 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberPasswordService (현재 비밀번호 검증, 새 비밀번호 암호화 저장) - 단위 테스트: Mock을 활용한 동작 검증 - 통합 테스트: 실제 DB 연동 검증 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MemberFacade (signup, getMyInfo, changePassword) - MemberInfo 응답 DTO (이름 마스킹 포함) - MemberV1Controller (POST /members, GET /me, PATCH /me/password) - MemberV1Dto (SignupRequest, MemberResponse, ChangePasswordRequest) - E2E 테스트: MemberV1ApiE2ETest - MemberFacadeTest 단위 테스트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Example/Core 테스트 DisplayName 자연스럽게 개선 - TEST-README.md 테스트 체크리스트 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fix : 예제 테스트 코드 오류 해결을 위한 testcontainers 버전 업
- 기능요구정의서 - 유비쿼터스 언어 정의서 - 시퀀스 다이어그램 - 클래스 다이어그램 - erd
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
auth-annotation(인증인가+DDD리팩토링) + order(주문CRUD) 병합 - 충돌 해결: BrandFacade, ProductService, ApiControllerAdvice - OrderFacadeIntegrationTest: productFacade.register() 사용으로 변경 - OrderV1ApiE2ETest: admin 헤더 추가, /api/v1/users 경로 반영 - ProductServiceTest: 제거된 getBrandNamesByIds 테스트 삭제 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…h Facade 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Repository에 의존하는 LikeService를 domain 패키지에서 application 패키지로 이동하여 레이어 책임을 명확히 분리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
테스트 파일들의 import를 domain.stock.StockService에서 application.stock.StockService로 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…o가 BrandInfo 경유 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ller는 Facade만 의존 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 엔티티 이관, Controller/Dto가 OrderInfo 경유 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ade에 authenticate 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…류 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- §2-5 레이어 구조에 Service 추가, Controller 의존 규칙 명시 - §2-6 도메인/애플리케이션 서비스 판별 기준을 '비즈니스 의사결정' 기준으로 변경 - 플로우차트에 Service vs Facade 분기 추가 - 프로젝트 배치 테이블에 전체 14개 컴포넌트 반영 - 클래스 다이어그램 의존 화살표를 Facade→Service→Model 구조로 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- §8 판별 기준을 "규칙 vs 절차" → "비즈니스 의사결정을 내리는가?"로 변경 - Service가 domain이 아닌 application 레이어임을 명시 (의사결정은 엔티티/VO가 담당) - 최종 설계를 "도메인(의사결정)" + "애플리케이션(Service+Facade)" 구조로 재분류 - §10 레이어 의존 규칙에 Controller→application만 의존 원칙 추가 - 회고에 "규칙을 담는다 ≠ 의사결정을 내린다" 깨달음 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- LikeToggleService (domain): Like+Product 두 엔티티 상태를 종합하여 좋아요 반응 결정 - 순수 비즈니스 의사결정만 담당 (인프라 의존 없음, @component 등록) - 신규 생성 / 복구 / 멱등 무시 3가지 분기 판단 - LikeFacade (application): 데이터 조회/저장만 담당, 판단은 도메인 서비스에 위임 - LikeToggleServiceTest: DB 없는 순수 단위 테스트 4개 추가 - 블로그/설계 문서에 도메인 서비스 내용 반영 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CouponModel(템플릿): 정액/정률 할인, 최소주문금액, 만료일 관리 - CouponIssueModel(발급): 상태 파생(usedAt/expiredAt 기반), 사용 처리 - CouponType enum 다형성으로 할인 계산 (FIXED/RATE) - Money VO에 divide, subtract, min 메서드 추가 - Repository 3계층 패턴 (domain interface → infra impl → JPA) - Admin API (CRUD) + User API (발급/조회) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OrderModel에 discountAmount, finalAmount, couponIssueId 필드 추가 - OrderFacade.placeOrder에 쿠폰 검증/할인/사용 처리 로직 통합 - OrderV1Dto, OrderInfo, OrderV1Controller 할인 필드 반영 - CouponModel에 validateUsable() 메서드 추가 - CouponIssueModel에 setOrderId() 메서드 추가 - CouponModelTest: 생성/할인계산/사용가능검증 단위 테스트 - CouponIssueModelTest: 생성/사용/소유자검증/상태판별 단위 테스트 - 기존 Order 테스트 시그니처 3-parameter로 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
재고 동시성 (비관적 락): - StockRepository/JpaRepository에 findByProductIdForUpdate 추가 - OrderFacade에서 productId 오름차순 정렬 후 FOR UPDATE로 재고 차감 (데드락 방지) 쿠폰 동시성 (비관적 락): - CouponIssueRepository/JpaRepository에 findByIdForUpdate 추가 - OrderFacade에서 쿠폰 사용 시 FOR UPDATE로 이중 사용 방지 좋아요 동시성 (낙관적 락): - ProductModel에 @Version 필드 추가 - LikeTransactionService 분리로 self-invocation 문제 해결 - LikeFacade에서 OptimisticLockException 발생 시 최대 3회 재시도 OrderFacade 트랜잭션 최적화: - 락 없이 할 수 있는 작업(상품 조회, 금액 계산) 먼저 수행 - 비관적 락 구간을 트랜잭션 후반으로 최소화 동시성 테스트: - 재고: 10명 동시 주문 시 정확한 차감, 재고 부족 시 일부만 성공 - 쿠폰: 동일 쿠폰 동시 사용 시 1회만 성공 - 좋아요: 10명 동시 좋아요 시 정확한 카운트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OrderFacade: coupon issue를 FOR UPDATE 한 번만 조회하도록 restructure (기존: 락 없이 먼저 읽어 1차 캐시에 stale 엔티티 적재 → FOR UPDATE 시 갱신 안 됨) - LikeFacade: MAX_RETRY 3→10 (10 스레드 동시 경합 시 재시도 부족) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /api-admin/v1/coupons?page=0&size=20 — Pageable 파라미터 추가
- GET /api-admin/v1/coupons/{couponId}/issues?page=0&size=20 — Pageable 파라미터 추가
- Repository/Service/Controller 전 레이어 Page<T> 반환으로 변경
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ConcurrencyIntegrationTest: successCount뿐 아니라 product.likeCount()도 검증 - CouponIssueJpaRepository merge 충돌 해결 (LockModeType + Pageable import 병합) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OrderV1Controller: @RequestHeader 수동 인증 → @LoginMember 전환 - CouponV1Controller: @RequestHeader 수동 인증 → @LoginMember 전환 - OrderAdminV1Controller: 인증 없음 → @adminuser 적용 - CouponAdminV1Controller: 인증 없음 → @adminuser 적용 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CouponIssueModel: (user_id, coupon_id) 유니크 키 추가 - CouponIssueService: DataIntegrityViolationException 처리로 중복 발급 시 CONFLICT 응답 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Version이 엔티티 레벨이라 상품 수정과 좋아요가 False Conflict를 일으키는 문제를 해결하기 위해, likeCount를 @Modifying @query 원자적 업데이트로 전환. - ProductModel: @Version 제거, incrementLikeCount/decrementLikeCount 제거 - ProductJpaRepository: 원자적 UPDATE 쿼리 추가 - LikeToggleService: ProductModel 의존 제거, LikeResult 반환 - LikeFacade: retryOnOptimisticLock 재시도 로직 제거 - OrderV1ApiE2ETest: 관리자 API 테스트 adminHeaders 누락 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge branch 'Loopers-dev-lab:main' into volume-1
|
Important Review skippedToo many files! This PR contains 192 files, which is 42 over the limit of 150. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (13)
📒 Files selected for processing (192)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
ab5f265
into
Loopers-dev-lab:Praesentia-YKM
1 check passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌Summary
배경: 커머스 도메인에서 재고 차감, 쿠폰 사용, 좋아요 카운트 등 동시 요청 시 Lost Update, 초과 차감, 중복 사용 등의 정합성 문제가 발생할 수 있습니다.
목표: 각 도메인의 동시성 리스크를 분석하고, 리스크 수준과 경합 특성에 맞는 동시성 제어 전략을 적용하여 데이터 정합성을 보장합니다.
결과: 3개 도메인에 차별화된 동시성 전략을 적용하고,
CountDownLatch + ExecutorService기반 동시성 통합 테스트 4종으로 검증을 완료했습니다. 쿠폰 도메인 신규 구현 및 주문-쿠폰 연동도 포함됩니다.🧭Context & Decision
문제 정의
현재 동작/제약: 기존 코드에는 동시성 제어가 없어, 동시 요청 시 데이터 정합성이 보장되지 않습니다.
문제 (리스크):
성공 기준: 모든 동시성 시나리오에서 데이터 정합성이 유지되며,
CountDownLatch + ExecutorService기반 통합 테스트 4종으로 검증 가능해야 합니다.동시성 제어 전략 선택 기준
위 문제들은 대부분 Read-Modify-Write (R-M-W) 패턴에서 발생합니다.
UPDATE SET quantity = 4)Read와 Write 사이의 시간 간격(gap) 동안 다른 스레드가 같은 값을 읽으면, 두 스레드 모두
quantity = 4를 쓰게 되어 하나의 차감이 사라집니다 (Lost Update).이 gap을 해결하는 전략은 크게 3가지이며, 도메인별 경합 특성에 따라 다른 전략을 선택했습니다.
SELECT ... FOR UPDATE로 행을 잠금@Version으로 Write 시 충돌 감지SET col = col + 1로 DB가 직접 처리선택지와 결정
1. 재고 차감
고려한 대안
@Version)SET quantity = quantity - 1)SELECT ... FOR UPDATE)최종 결정: 비관적 락 (PESSIMISTIC_WRITE)
근거
quantity - 1이 아니라 재고 부족 검증 → 금액 계산 → 주문 생성이 하나의 트랜잭션에서 이루어져야 합니다. Read 단계에서 가져온 값으로 비즈니스 판단을 해야 하므로, 원자적 업데이트로는 부족하고 Read-Write gap을 근본적으로 차단하는 비관적 락이 적합합니다.productId오름차순 정렬로 락 획득 순서를 통일하여 데드락 방지트레이드오프: 동시 처리량(throughput)이 직렬화로 제한되지만, 재고 정합성이 더 중요
2. 쿠폰 사용
고려한 대안
@Version)FOR UPDATE)최종 결정: 비관적 락 + UniqueConstraint
근거
@UniqueConstraint(user_id, coupon_id)+DataIntegrityViolationException이중 방어트레이드오프: 없음 — 쿠폰별 경합이 낮아 성능 영향 미미
3. 좋아요 — 낙관적 락에서 원자적 업데이트로 전환
고려한 대안
@Version)@Version스코프가 엔티티 전체 → 상품 수정과 좋아요가 False Conflict@Version간섭 해결, 하지만 테이블 추가 + JOIN 필요SET like_count = like_count + 1)@Version트리거 안 됨, 재시도 불필요최종 결정: 원자적 업데이트
근거
likeCount증감은 순수한+1/-1연산 — R-M-W가 아니라 단순 증감이므로 원자적 업데이트가 자연스럽게 들어맞음@Modifying @Query로 DB가 직접 처리 → 애플리케이션 레벨 gap 없음@Version이 트리거되지 않음 → 상품 수정과의 False Conflict 근본적 해결retryOnOptimisticLock), 별도 트랜잭션 경계(LikeTransactionService) 등 복잡도 대폭 감소트레이드오프:
@Modifying @Query는 JPA 1차 캐시와 동기화되지 않으므로, 같은 트랜잭션 내에서likeCount를 다시 읽으려면entityManager.refresh()필요. 현재 구조에서는 좋아요 후 즉시 likeCount를 재조회하지 않으므로 문제 없음.리스크 / 주의사항
productId오름차순 정렬 후 락 획득. 순서가 어긋나면 교착 상태 발생@UniqueConstraint+DataIntegrityViolationException처리가 반드시 함께 있어야 함@Modifying @Query는 JPA 1차 캐시를 갱신하지 않음. 같은 트랜잭션에서likeCount재조회 시 캐시된 구값을 반환할 수 있으므로, 필요 시entityManager.refresh()호출 필요@Transactional범위 안에서 외부 API 호출, 이메일 발송 등 느린 작업을 수행하면 락 유지 시간이 늘어나 병목 심화. 락이 필요한 구간은 최대한 짧게 유지왜 원자적 업데이트를 재고/쿠폰에는 쓰지 않았나?
+1/-1락 선택 판단 기준 요약
🏗️Design Overview
변경 범위
영향 받는 도메인: Brand, Product, Stock, Order, Like, Coupon(신규), CouponIssue(신규)
신규 추가:
@LoginMember,@AdminUser인증 어노테이션 + ArgumentResolver주요 변경:
StockJpaRepository:findByProductIdForUpdate()— 비관적 락 조회CouponIssueJpaRepository:findByIdForUpdate()— 비관적 락 조회ProductJpaRepository:incrementLikeCount()/decrementLikeCount()— 원자적 업데이트CouponIssueModel:@UniqueConstraint(user_id, coupon_id)— 중복 발급 방지주요 컴포넌트 책임
OrderFacadeLikeFacadeLikeTransactionService에 위임LikeTransactionServiceLikeToggleServiceLikeResult반환CouponIssueServiceStockServicegetByProductIdForUpdate()— 비관적 락으로 재고 조회🔁Flow Diagrams
1. 주문 처리 흐름 (재고 + 쿠폰 동시성 제어)
flowchart TD Client -->|POST /api/v1/orders| Controller[OrderV1Controller] Controller --> Facade[OrderFacade.placeOrder] subgraph TX["@Transactional"] S1["① 상품 조회 + 금액 계산 (락 없음)\nproductId 오름차순 정렬"] S2["② 재고 차감 (PESSIMISTIC_WRITE)\nFOR UPDATE → StockModel 잠금\nstock.decrease(quantity)"] S3["③ 쿠폰 검증+사용 (PESSIMISTIC_WRITE)\nFOR UPDATE → CouponIssueModel 잠금\nvalidateOwner / validateUsable / use()"] S4["④ 주문 생성 (INSERT)\nOrderModel / OrderItemModel"] S5["⑤ 쿠폰에 orderId 연결"] COMMIT(["커밋 — 모든 락 해제"]) ROLLBACK1(["전체 롤백"]) ROLLBACK2(["전체 롤백"]) S1 --> S2 S2 -->|재고 부족| ROLLBACK1 S2 -->|정상| S3 S3 -->|검증 실패| ROLLBACK2 S3 -->|정상| S4 --> S5 --> COMMIT end Facade --> TX2. 재고 동시성 시나리오 (비관적 락)
sequenceDiagram participant T1 as Thread 1 participant T2 as Thread 2~5 participant T3 as Thread 6~10 participant DB as DB (재고: 5) T1->>DB: FOR UPDATE (잠금 획득) T2-->>DB: 대기 T3-->>DB: 대기 DB-->>T1: 잠금 OK T1->>DB: decrease(1) → 커밋 (재고: 4) Note over T2,DB: Thread 2~5 순서대로 동일하게 처리 T2->>DB: FOR UPDATE 획득 → decrease(1) → 커밋 (재고: 3→2→1→0) T3->>DB: FOR UPDATE 획득 → 재고 부족 예외 Note over T3: 실패 — 롤백3. 쿠폰 동시 사용 시나리오 (비관적 락)
sequenceDiagram participant T1 as Thread 1 participant T2 as Thread 2~5 participant DB as DB (쿠폰: AVAILABLE) T1->>DB: FOR UPDATE (잠금 획득) T2-->>DB: 대기 DB-->>T1: 잠금 OK T1->>DB: isAvailable() = true → use() → 커밋 Note over DB: 쿠폰 상태: USED T2->>DB: FOR UPDATE 획득 → isAvailable() = false → 예외 Note over T2: 실패 — 롤백4. 좋아요 동시성 시나리오 (원자적 업데이트)
flowchart LR subgraph Threads["Thread 1~10 (동시 요청)"] T1[Thread 1] T2[Thread 2] T3[Thread ...] T10[Thread 10] end subgraph DB["DB"] Q["UPDATE SET like_count = like_count + 1\n(DB가 원자적으로 직렬 처리)"] end T1 -->|INSERT like| Q T2 -->|INSERT like| Q T3 -->|INSERT like| Q T10 -->|INSERT like| Q Q --> Result["like_count = 10\n10명 전원 성공 / 재시도 없음"]5. 데드락 방지 (productId 정렬)
flowchart TD subgraph Bad["❌ 정렬 없이 — Deadlock 발생"] A1[User A] -->|상품2 락 획득| LA2[상품2 잠금] A1 -->|상품1 락 요청| W1([대기 중...]) B1[User B] -->|상품1 락 획득| LB1[상품1 잠금] B1 -->|상품2 락 요청| W2([대기 중...]) W1 <-.->|Deadlock| W2 end subgraph Good["✅ productId 오름차순 정렬 — 교착 없음"] A2[User A] -->|상품1 락| G1[상품1 잠금] G1 -->|상품2 락| G2[상품2 잠금 → 커밋] B2[User B] -->|상품1 락 요청| GW([User A 완료 대기]) GW --> G3[상품1 락 → 상품2 락 → 커밋] end6. @Version 스코프 문제와 원자적 업데이트 전환 결정 흐름
flowchart TD A["[초기 설계]\n좋아요 → ProductModel.incrementLikeCount()\n→ @Version 충돌 감지 → 재시도"] B["문제: @Version은 엔티티 레벨 (필드 레벨 아님)"] C["좋아요 → version++"] D["상품 수정 → version++"] E["같은 version 경합\nFalse Conflict 발생"] F{"좋아요는 순수 증감 연산인가?"} G["incrementLikeCount()\nthis.likeCount++ → YES"] H["decrementLikeCount()\nif(>0) likeCount-- → YES"] I["[최종 설계]\n@Modifying @Query\nSET like_count = like_count + 1"] J["JPA 변경 감지 우회\n→ @Version 미트리거"] K["재시도 로직 제거\nretryOnOptimisticLock 삭제"] L["@Version 제거\n→ 상품 수정과의 간섭 근본 해결"] A --> B B --> C & D --> E --> F F --> G & H G & H --> I I --> J & K & L동시성 테스트
stockDecreasedCorrectlyUnderConcurrencyonlyAvailableStockSucceedscouponUsedOnlyOncelikeCountAccurateUnderConcurrencyChecklist
Coupon 도메인
validateOwner)CouponType.FIXED/RATE)use()-> USED 상태)@UniqueConstraint+DataIntegrityViolationException)주문
@Transactional로 원자성 보장 (재고+쿠폰+주문 단일 트랜잭션)rollsBackOnPartialFailure테스트 검증)동시성
인증
@LoginMember— 사용자 API 인증 (Order, Coupon, Like, Member)@AdminUser— 어드민 API 인증 (Brand, Product, Order, Coupon Admin)주요 파일
동시성 제어 핵심 파일
StockJpaRepository@Lock(PESSIMISTIC_WRITE)— 재고 비관적 락CouponIssueJpaRepository@Lock(PESSIMISTIC_WRITE)— 쿠폰 비관적 락ProductJpaRepository@Modifying @Query— 좋아요 원자적 업데이트OrderFacadeLikeFacadeLikeTransactionServiceLikeToggleServiceLikeResultCouponIssueModel@UniqueConstraint+use()상태 전이CouponIssueServiceDataIntegrityViolationException처리ConcurrencyIntegrationTest