Skip to content

[volume-4] 쿠폰 도메인 설계 및 동시성 제어 #159

Merged
plan11plan merged 172 commits intoLoopers-dev-lab:plan11planfrom
plan11plan:main
Mar 12, 2026
Merged

[volume-4] 쿠폰 도메인 설계 및 동시성 제어 #159
plan11plan merged 172 commits intoLoopers-dev-lab:plan11planfrom
plan11plan:main

Conversation

@plan11plan
Copy link

@plan11plan plan11plan commented Mar 6, 2026

📌 Summary

쿠폰 도메인 설계/구현 완료. 낙관락을 기본 전략으로 채택했으나,
주문 흐름처럼 트랜잭션이 긴 유스케이스에서의 재시도 비용 문제를 발견했고,
이 판단이 맞는 방향인지 멘토님 피드백을 구하고 싶습니다.

과제 요약

  • 쿠폰 CRUD, 발급, 사용, 복원 전 기능 구현
  • 동시성 테스트 작성 및 검증
  • 낙관락(@Version) 기본 전략 채택

리뷰 포인트 및 질문

1. 락 선택기준 - 낙관락을 기본 전략으로 선택한 이유

-충돌과 상관없이, 낙관락이 비관락보다 DB 커넥션 점유 시간과 자원 효율 면에서 유리하다고 판단했습니다.

2. 주문같이 긴 트랜잭션에서의 락 선택과 개선법 질문

현재 주문 생성 흐름은 아래와 같습니다.
image

저의 현재 판단

  • Product/User에도 비관락을 좁게 적용하고, Facade의 @retryable을 제거하는 방향이 맞지 않을까 생각합니다.(낙관락이 하나라도 있으면 재시도해야하니 낙관락 없게하고, 비관락으로 변경)
  • 다만 아직 확신이 없는 이유는, 충돌 빈도가 낮다면 긴 트랜잭션이라도 재시도 비용이 실제로는 크지 않을 수 있다는 반론이 스스로 해소되지 않아서입니다.

질문

고민하다 보니 더 혼란이 생겼습니다..

  • "긴 트랜잭션에서의 동시성 문제를 락 전략(낙관 → 비관 전환)으로 해결하려는 게 맞는 접근인가,
    아니면 트랜잭션을 분리하고 실패 시 보상하는 구조처럼 다른 차원에서 접근해야 하는 문제인가?"

  • Claude는 트랜잭션 분리 + 보상 이벤트를 제안했습니다.

image

저는 아래가 아직 정리되지 않았고, 이에 대한 멘토님의 조언 및 의견을 구하고, 학습 및 개선으로 이루고 싶습니다.

  • 트랜잭션을 분리해도 주문이라는 하나의 큰 트랜잭션에 참여하는 거 아닌가? 정말 트랜잭션 길이가 짧아지나?
  • 지금 구조의 문제가 실제로 문제가 되는 시점은 언제인가? (지금 당장인가?)
  • 지금 내 수준에서 시도해볼 수 있는 범위는 어디까지인가? (Saga? 보상트랜잭션? 이게뭐지)
  • 클로드 말대로 바꾼다면 현재 구조에서 이벤트 구조로 바꾼다는건 아키텍처가 바뀐다는건데 이번주에 이렇게 고쳐야하나? 아니면 앞으로의 round 발제 세션에 이에 대한 내용을 다루나?

최종적으로 멘토님께 얻고 싶은 것

  • 저는 신입 취준생이고, 모르는게 정말 많습니다.
  • 제가 한 판단의 방향성이 잘 나아가고 있는지 궁금합니다. 그리고 이런 트랜잭션 길이가 긴 주문처리에 있어서 학습 방향 및 키워드 및 조언을 구하고싶습니다.

round4 과제 진행 내용

동시성 이슈 발생 지점 리스트업 및 테스트 진행

프로젝트에서 동시성 이슈가 발생할 수 있는 지점을 모두 식별하고, 각각 어떻게 해결했는지 정리했다.

1. 쿠폰 동시 발급CouponModel

  • 위험: N명이 한정 수량 쿠폰을 동시 발급 → 초과 발급
  • 해결: 낙관적 락 (@Version + @Retryable(50회))
  • issuedQuantity++ 시 버전 충돌 감지, 충돌 시 50ms + random 후 재시도

2. 쿠폰 동시 사용OwnedCouponModel

  • 위험: 같은 쿠폰으로 여러 주문이 동시 사용 → 중복 사용
  • 해결: CAS Update (UPDATE ... SET order_id = ? WHERE id = ? AND order_id IS NULL)
  • 단일 UPDATE문의 원자성 활용. affected rows = 0이면 이미 사용된 쿠폰

3. 재고 동시 차감ProductModel

  • 위험: N명이 같은 상품을 동시 주문 → 재고 음수
  • 해결: 낙관적 락 (@Version), OrderFacade의 @Retryable(10회)로 커버

4. 포인트 동시 차감UserModel

  • 위험: 같은 유저로 동시 주문 → 잔액 초과 차감
  • 해결: 낙관적 락 (@Version), OrderFacade의 @Retryable(10회)로 커버

5. 주문 동시 생성OrderModel

  • 위험: 재고 + 포인트가 동시에 변경되면서 데이터 불일치
  • 해결: 낙관적 락 (@Version + @Retryable(10회))
  • 주문 트랜잭션 전체를 재시도하여 3, 4번과 함께 정합성 보장

6. 주문 아이템 동시 취소OrderModel

  • 위험: 같은 주문의 아이템을 동시 취소 → 총액 계산 오류
  • 해결: 낙관적 락 (@Version + @Retryable(5회))
  • 주문 총액 재계산 시 버전 충돌 감지

7. 좋아요 동시 등록ProductLikeModel

  • 위험: 같은 유저가 같은 상품에 동시 좋아요 → 중복 등록
  • 해결: UK 제약 + 예외 변환 (DataIntegrityViolationException → CONFLICT)
  • @Version 없이 DB UniqueConstraint로 원천 차단

결과: 동시성 테스트 검증 완료

각 시나리오별 동시성 테스트를 작성하여 데이터 정합성을 검증했다.

테스트 클래스 시나리오 동시 요청 검증 항목 결과
CouponIssueConcurrencyTest 10개 한정 쿠폰에 10명 동시 발급 10 초과 발급 없음, issuedQuantity == 실제 발급 수
OwnedCouponUseConcurrencyTest 1개 쿠폰을 10명이 동시 사용 10 정확히 1건 성공, 9건 실패
StockDeductionConcurrencyTest 재고 10개 상품에 10명 동시 주문 10 정확히 10건 성공, 재고 0
PointDeductionConcurrencyTest 포인트 100,000에 10명 동시 주문 (개당 40,000) 10 최대 2건 성공, 포인트 음수 방지
OrderItemCancelConcurrencyTest 3개 아이템 동시 취소 3 전부 성공, 재고 원복, 주문 CANCELLED
ProductLikeConcurrencyTest 같은 유저가 동시 좋아요 10회 10 정확히 1건 성공, 9건 CONFLICT

모든 테스트는 @RepeatedTest(3)으로 반복 실행하여 비결정적 동시성 환경에서의 안정성을 검증했다.

🏗️ 쿠폰 Design Overview

주요 컴포넌트 책임

  • CouponModel: 쿠폰 템플릿. 발급 가능 여부 검증(validateIssuable), 수량 증가(issue). @Version으로 동시 발급 제어
  • OwnedCouponModel: 발급된 쿠폰 인스턴스. Coupon 스냅샷 보유. 사용/복원/할인 계산. 상태는 orderId+expiredAt에서 파생 (AVAILABLE/USED/EXPIRED)
  • CouponService: 도메인 서비스. 쿠폰 등록/수정/삭제/발급, CAS Update 기반 사용, 주문 취소 시 복원
  • CouponFacade: 트랜잭션 경계 + @Retryable 재시도. 낙관적 락 충돌 시 최대 50회 재시도
  • OrderFacade: 쿠폰 적용 주문 생성 + 취소 시 쿠폰 복원 조율. @Retryable 최대 10회 재시도

🔁 Flow Diagram

쿠폰 발급 (낙관적 락 + 재시도)

sequenceDiagram
    autonumber
    participant Client
    participant Controller
    participant CouponFacade
    participant CouponService
    participant DB

    Client->>Controller: POST /api/v1/coupons/{id}/issue
    Controller->>CouponFacade: issueCoupon(couponId, userId)
    Note over CouponFacade: @Retryable(maxAttempts=50)
    CouponFacade->>CouponService: issue(couponId, userId)
    CouponService->>DB: SELECT coupon (version=N)
    DB-->>CouponService: CouponModel
    Note over CouponService: validateIssuable()<br/>중복 발급 체크
    CouponService->>DB: UPDATE coupon SET version=N+1
    alt 버전 충돌
        DB-->>CouponService: OptimisticLockException
        Note over CouponFacade: 50ms + random 후 재시도
    else 성공
        CouponService->>DB: INSERT owned_coupon (스냅샷)
        DB-->>CouponService: OK
    end
    CouponFacade-->>Controller: 발급 결과
    Controller-->>Client: 201 Created
Loading

쿠폰 적용 주문 생성 (CAS Update)

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

    Client->>OrderFacade: 주문 요청 (items, couponId)
    OrderFacade->>ProductService: 재고 검증 + 차감
    OrderFacade->>OrderService: 주문 생성 (discountAmount=0)
    OrderService-->>OrderFacade: Order (orderId)

    alt couponId != null
        OrderFacade->>CouponService: useAndCalculateDiscount()
        CouponService->>DB: UPDATE owned_coupon<br/>SET order_id=? WHERE id=? AND order_id IS NULL
        alt affected rows = 0
            Note over CouponService: 이미 사용된 쿠폰 → 예외<br/>트랜잭션 롤백
        else affected rows = 1
            CouponService-->>OrderFacade: discountAmount
            Note over OrderFacade: order.applyDiscount()
        end
    end

    OrderFacade-->>Client: 주문 완료
Loading

📦 쿠폰 도메인 모델링

설계 핵심 결정

결정 이유
2 Aggregate Root (Coupon + OwnedCoupon) 생명주기 분리: Coupon soft delete 시에도 OwnedCoupon은 유효기간까지 유지
스냅샷 분리 발급 시점의 할인 조건이 "계약". Admin의 사후 수정이 기발급 쿠폰에 영향 없음
status 컬럼 제거 → 파생 상태 orderId/expiredAt으로 AVAILABLE/USED/EXPIRED 동적 판정. 상태 불일치 원천 차단
ID 참조 (FK 미사용) 도메인 간 결합도 최소화. 애플리케이션 레벨에서 무결성 검증
CAS Update (쿠폰 사용) WHERE order_id IS NULL 단일 UPDATE의 원자성 활용. 별도 락 없이 동시 사용 방지

ERD

erDiagram
    coupons {
        bigint id PK
        varchar name
        varchar discount_type "FIXED / RATE"
        bigint discount_value
        bigint min_order_amount "nullable"
        int total_quantity
        int issued_quantity
        bigint version "낙관적 락"
        timestamp expired_at
        timestamp created_at
        timestamp updated_at
        timestamp deleted_at "Soft Delete"
    }

    owned_coupons {
        bigint id PK
        bigint coupon_id "ID 참조 (FK 없음)"
        varchar coupon_name "스냅샷"
        varchar discount_type "스냅샷"
        bigint discount_value "스냅샷"
        bigint min_order_amount "스냅샷"
        timestamp expired_at "스냅샷"
        bigint user_id
        bigint order_id "nullable — CAS Update 대상"
        timestamp used_at "nullable"
        timestamp created_at
        timestamp updated_at
        timestamp deleted_at
    }

    coupons ||..o{ owned_coupons : "couponId"
    users ||--o{ owned_coupons : "userId"
    owned_coupons |o--o| orders : "orderId (nullable)"
Loading

인덱스

인덱스 컬럼 용도
uk_owned_coupon_user (coupon_id, user_id) UNIQUE 유저당 동일 쿠폰 중복 발급 방지
idx_owned_coupon_user (user_id) 내 쿠폰 목록 조회
idx_owned_coupon_coupon (coupon_id) Admin 발급 내역 조회

API 목록

기능 액터 Method URI
쿠폰 발급 회원 POST /api/v1/coupons/{couponId}/issue
내 쿠폰 목록 회원 GET /api/v1/users/me/coupons
쿠폰 등록 Admin POST /api-admin/v1/coupons
쿠폰 수정 Admin PUT /api-admin/v1/coupons/{couponId}
쿠폰 삭제 Admin DELETE /api-admin/v1/coupons/{couponId}
쿠폰 목록 조회 Admin GET /api-admin/v1/coupons
쿠폰 상세 조회 Admin GET /api-admin/v1/coupons/{couponId}
발급 내역 조회 Admin GET /api-admin/v1/coupons/{couponId}/issues

✅ Checklist

AI로 체크리스트 누수 점검을 진행했습니다.

🗞️ Coupon 도메인

  • 쿠폰은 사용자가 소유하고 있으며, 이미 사용된 쿠폰은 사용할 수 없어야 한다.
  • 쿠폰 종류는 정액 / 정률로 구분되며, 각 적용 로직을 구현하였다.
  • 각 발급된 쿠폰은 최대 한번만 사용될 수 있다.

🧾 주문

  • 주문 전체 흐름에 대해 원자성이 보장되어야 한다.
  • 사용 불가능하거나 존재하지 않는 쿠폰일 경우 주문은 실패해야 한다.
  • 재고가 존재하지 않거나 부족할 경우 주문은 실패해야 한다.
  • 쿠폰, 재고, 포인트 처리 등 하나라도 작업이 실패하면 모두 롤백처리되어야 한다.
  • 주문 성공 시, 모든 처리는 정상 반영되어야 한다.

🧪 동시성 테스트

  • 동일한 상품에 대해 여러명이 좋아요/싫어요를 요청해도, 상품의 좋아요 수가 정상 반영되어야 한다.
  • 동일한 쿠폰으로 여러 기기에서 동시에 주문해도, 쿠폰은 단 한번만 사용되어야 한다.
  • 동일한 상품에 대해 여러 주문이 동시에 요청되어도, 재고가 정상적으로 차감되어야 한다.

plan11plan and others added 30 commits February 9, 2026 18:11
- Password VO를 EncryptedPassword로 변경하여 암호화된 비밀번호임을 명확히 표현
- 서비스 계층에 SignupCommand, ChangePasswordCommand, UserInfo DTO 도입
- 컨트롤러에서 엔티티(UserModel) 직접 노출 제거
- Example 관련 코드 삭제

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- EncryptedPassword.of() 오버로드 제거 → 하나만 유지 (형식 검증 + 암호화)
- UserModel 생성자에서 rawPassword + encoder를 받아 birthDate 교차 검증 수행
- 생성/변경 두 경로 모두 UserModel이 검증 → 일관성 확보
- 패스워드 설계 결정 문서 추가

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>
plan11plan and others added 25 commits March 4, 2026 19:14
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>
…FLICT 예외로 변환

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

coderabbitai bot commented Mar 6, 2026

Important

Review skipped

Too many files!

This PR contains 205 files, which is 55 over the limit of 150.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7ca8b41a-c3c3-45c5-a725-31e451bde411

📥 Commits

Reviewing files that changed from the base of the PR and between 2a442e0 and efaeb2f.

⛔ Files ignored due to path filters (50)
  • .claude/CLAUDE.md is excluded by !**/*.md and included by **
  • .claude/SKILL-WRITING-GUIDE.md is excluded by !**/*.md and included by **
  • .claude/agents/convention-review/AGENT.md is excluded by !**/*.md and included by **
  • .claude/agents/interview-partner/SKILL.md is excluded by !**/*.md and included by **
  • .claude/agents/interview-partner/agents/code-scanner.md is excluded by !**/*.md and included by **
  • .claude/agents/interview-partner/agents/notion-publisher.md is excluded by !**/*.md and included by **
  • .claude/agents/interview-partner/agents/refactor-executor.md is excluded by !**/*.md and included by **
  • .claude/agents/interview-partner/referencs/notion-schema.md is excluded by !**/*.md and included by **
  • .claude/agents/interview-partner/referencs/summary-format.md is excluded by !**/*.md and included by **
  • .claude/agents/interview-partner/scripts/session-state.md is excluded by !**/*.md and included by **
  • .claude/report/analyzing-concurrency/ANALYSIS-RESULT.md is excluded by !**/*.md and included by **
  • .claude/report/analyzing-query/ANALYSIS-RESULT.md is excluded by !**/*.md and included by **
  • .claude/skills/analyzing-concurrency/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/analyzing-concurrency/references/analysis-checklist.md is excluded by !**/*.md and included by **
  • .claude/skills/analyzing-fk-deadlock/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/analyzing-lock-index/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/analyzing-query/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/analyzing-tx-rollback /SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/commit-convention/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/deep-dive/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/deep-dive/references/dfs-templates.md is excluded by !**/*.md and included by **
  • .claude/skills/domain-modeling/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/domain-modeling/references/boundary-decision-guide.md is excluded by !**/*.md and included by **
  • .claude/skills/domain-modeling/references/question-patterns.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/application/service-layer-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/common/dto-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/common/exception-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/common/inline-variable-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/common/package-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/common/test-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/domain/entity-vo-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/infrastructure/infrastructure-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/interfaces/api-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/project-convention/references/interfaces/swagger-convention.md is excluded by !**/*.md and included by **
  • .claude/skills/requirements-analysis/SKILL.md is excluded by !**/*.md and included by **
  • .claude/skills/tdd/SKILL.md is excluded by !**/*.md and included by **
  • README.md is excluded by !**/*.md and included by **
  • docs/design/_shared/CONVENTIONS.md is excluded by !**/*.md and included by **
  • docs/design/_shared/OVERVIEW.md is excluded by !**/*.md and included by **
  • docs/design/brand/DESIGN.md is excluded by !**/*.md and included by **
  • docs/design/cart/DESIGN.md is excluded by !**/*.md and included by **
  • docs/design/coupon/API-SPEC.md is excluded by !**/*.md and included by **
  • docs/design/coupon/DESIGN.md is excluded by !**/*.md and included by **
  • docs/design/coupon/TODOLIST.md is excluded by !**/*.md and included by **
  • docs/design/coupon/domain-modeling.md is excluded by !**/*.md and included by **
  • docs/design/like/DESIGN.md is excluded by !**/*.md and included by **
  • docs/design/order/DESIGN.md is excluded by !**/*.md and included by **
  • docs/design/product/DESIGN.md is excluded by !**/*.md and included by **
  • docs/design/도메인_관계_설계_의사결정_기록_v3.md is excluded by !**/*.md and included by **
📒 Files selected for processing (205)
  • .claude/hooks/convention-check.sh
  • .claude/settings.json
  • .gitignore
  • .sdkmanrc
  • apps/commerce-api/build.gradle.kts
  • apps/commerce-api/src/main/java/com/loopers/CommerceApiApplication.java
  • apps/commerce-api/src/main/java/com/loopers/application/brand/BrandFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/brand/dto/BrandCriteria.java
  • apps/commerce-api/src/main/java/com/loopers/application/brand/dto/BrandResult.java
  • apps/commerce-api/src/main/java/com/loopers/application/coupon/CouponFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/coupon/dto/CouponCriteria.java
  • apps/commerce-api/src/main/java/com/loopers/application/coupon/dto/CouponResult.java
  • apps/commerce-api/src/main/java/com/loopers/application/example/ExampleFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/example/ExampleInfo.java
  • apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/order/dto/OrderCriteria.java
  • apps/commerce-api/src/main/java/com/loopers/application/order/dto/OrderResult.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/ProductLikeFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/dto/ProductCriteria.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/dto/ProductLikeResult.java
  • apps/commerce-api/src/main/java/com/loopers/application/product/dto/ProductResult.java
  • apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
  • apps/commerce-api/src/main/java/com/loopers/application/user/dto/UserCriteria.java
  • apps/commerce-api/src/main/java/com/loopers/application/user/dto/UserResult.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/BrandErrorCode.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/BrandModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/BrandRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/BrandService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/brand/dto/BrandCommand.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponDiscountType.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponErrorCode.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/OwnedCouponModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/OwnedCouponRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/coupon/dto/CouponCommand.java
  • apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderErrorCode.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItemStatus.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/OrderStatus.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/ProductSnapshot.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/dto/OrderCommand.java
  • apps/commerce-api/src/main/java/com/loopers/domain/order/dto/OrderInfo.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductErrorCode.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductLikeModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductLikeRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductLikeService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/dto/ProductCommand.java
  • apps/commerce-api/src/main/java/com/loopers/domain/product/dto/ProductInfo.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/AuthenticationService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/PasswordEncoder.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
  • apps/commerce-api/src/main/java/com/loopers/domain/user/dto/UserCommand.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/brand/BrandJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/brand/BrandRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/CouponJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/CouponRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/OwnedCouponJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/coupon/OwnedCouponRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderItemJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderItemRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductLikeJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductLikeRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/user/BCryptPasswordEncoderImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiResponse.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/auth/AdminAuthFilter.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/auth/AuthFilter.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/auth/Login.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/auth/LoginUser.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/auth/LoginUserArgumentResolver.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/brand/AdminBrandV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/brand/AdminBrandV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/brand/BrandV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/brand/BrandV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/brand/dto/AdminBrandV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/brand/dto/BrandV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/coupon/AdminCouponV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/coupon/AdminCouponV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/coupon/CouponV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/coupon/CouponV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/coupon/dto/AdminCouponV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/coupon/dto/CouponV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/order/AdminOrderV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/order/AdminOrderV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/order/OrderV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/order/OrderV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/order/dto/OrderRequest.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/order/dto/OrderResponse.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/AdminProductV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/AdminProductV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/ProductLikeV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/ProductLikeV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/ProductV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/ProductV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/dto/AdminProductV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/dto/ProductLikeV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/product/dto/ProductV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/user/UserV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/user/UserV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/user/dto/UserV1Dto.java
  • apps/commerce-api/src/main/java/com/loopers/support/config/WebMvcConfig.java
  • apps/commerce-api/src/main/java/com/loopers/support/error/CoreException.java
  • apps/commerce-api/src/main/java/com/loopers/support/error/ErrorCode.java
  • apps/commerce-api/src/main/java/com/loopers/support/error/ErrorType.java
  • apps/commerce-api/src/main/java/com/loopers/support/util/PaginationUtils.java
  • apps/commerce-api/src/test/java/com/loopers/application/brand/BrandFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/coupon/CouponFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/order/OrderIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/product/ProductFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/application/product/ProductLikeFacadeTest.java
  • apps/commerce-api/src/test/java/com/loopers/architecture/DomainPurityTest.java
  • apps/commerce-api/src/test/java/com/loopers/architecture/LayeredArchitectureTest.java
  • apps/commerce-api/src/test/java/com/loopers/architecture/NamingConventionTest.java
  • apps/commerce-api/src/test/java/com/loopers/architecture/ServiceLayerTest.java
  • apps/commerce-api/src/test/java/com/loopers/concurrency/CouponIssueConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/concurrency/OrderItemCancelConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/concurrency/OwnedCouponUseConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/concurrency/PointDeductionConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/concurrency/ProductLikeConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/concurrency/StockDeductionConcurrencyTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/brand/FakeBrandRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/CouponModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/CouponServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/FakeCouponRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/FakeOwnedCouponRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/coupon/OwnedCouponModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleServiceIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/FakeOrderItemRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/FakeOrderRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderItemModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/FakeProductLikeRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/FakeProductRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductLikeModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductLikeServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/product/ProductServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/AuthenticationServiceTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/FakePasswordEncoder.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/FakeUserRepository.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceFakeTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceMockTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/api/ExampleV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/auth/AuthFilterTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/auth/LoginUserArgumentResolverTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/brand/AdminBrandV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/brand/BrandV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/brand/BrandV1ApiScenarioTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/coupon/AdminCouponV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/coupon/AdminCouponV1ControllerTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/coupon/CouponV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/coupon/CouponV1ControllerTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/order/AdminOrderV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/order/AdminOrderV1ControllerTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/order/OrderCouponV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/order/OrderV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/order/OrderV1ControllerTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/product/AdminProductV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/product/ProductLikeV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/product/ProductLikeV1ControllerTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/product/ProductV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/user/UserV1ApiE2ETest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/user/UserV1ApiScenarioTest.java
  • build.gradle.kts
  • gradle.properties
  • http/commerce-api/admin-brand-v1.http
  • http/commerce-api/admin-product-v1.http
  • http/commerce-api/brand-v1.http
  • http/commerce-api/like-v1.http
  • http/commerce-api/product-v1.http
  • http/commerce-api/user-v1.http
  • modules/jpa/src/main/resources/jpa.yml

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

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.

@plan11plan plan11plan changed the base branch from main to plan11plan March 12, 2026 06:12
@plan11plan plan11plan merged commit 5086f39 into Loopers-dev-lab:plan11plan Mar 12, 2026
1 check passed
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.

1 participant