쿠폰(Coupon)
3그룹 순차 적용 + DFS 최적 조합 + 전략 패턴 할인 엔진
배경 및 제약조건
- •3개 그룹: GROUP_1(브랜드/상품 지정), GROUP_2(전체 주문), GROUP_3(회원 등급/특별)
- •그룹 내 1개만 선택, 그룹 간 중복 적용 가능 (순차 적용 — 이전 할인 후 남은 금액 기준)
- •할인 방식: 정액(Fixed) / 정률(Percent) — 최대 할인 금액 제한
- •적용 대상: 전체 상품 / 특정 브랜드 / 특정 상품 / 주문 금액 기준
- •자동 적용(최적 쿠폰 자동 선택) + 수동 적용(사용자 선택) + '쿠폰 선택 안함' 지원
설계 구조
Coupon Aggregate (Template Method + Strategy Pattern) ├── DiscountStrategy → 정액/정률 할인 계산 (최대 금액 제한) ├── TargetStrategy → 적용 대상 필터 (전체/브랜드/상품/금액) ├── ApplyStrategy → 분배 방법 (상품별 비례/단일/전체) └── EligibilityStrategy → 사용 자격 (최소금액/특정상품/복합) 구체 구현: ├── Group1: DesignatedCoupon (브랜드/상품 지정) ├── Group2: SitewideCoupon (전체 주문) └── Group3: MemberRankCoupon (회원 등급) OptimalCouponSelector (DFS 기반 최적 조합): ├── 수동 적용 쿠폰 우선 선택 ├── GROUP_1: 전체 후보 중 최대 할인 1개 └── GROUP_2/3: DFS로 모든 조합 탐색 → 전역 최적해 CouponDiscountService (순차 할인 계산): └── GROUP_1 할인 후 남은 금액 → GROUP_2 적용 → GROUP_3 적용
Problem & Solution
Problem
그룹 간 순차 적용의 상호작용 — GROUP_2 할인이 크면 GROUP_3 적용 금액이 줄어 오히려 총 할인 감소. 단순 max가 전역 최적이 아님
Solution
DFS로 모든 유효한 조합을 실제 할인 계산(순차 적용 시뮬레이션)하여 전역 최적해 보장. 각 조합마다 CouponDiscountService로 실제 할인 금액을 비교
Problem
할인 금액의 상품별 분배가 부정확 → 환불/부분 취소 시 금액 불일치
Solution
대상 상품의 (판매가 × 수량) 비율로 비례 분배. 정수 반올림 오차는 마지막 아이템에서 보정. 음수 방어 로직으로 할인이 상품 가격을 초과하지 않도록 보장
Problem
쿠폰 조회 시 각 쿠폰의 사용 여부 확인을 위해 CouponTempLog를 쿠폰 수만큼 개별 조회 → N+1 발생
Solution
CouponWalletRepository에서 temp log를 한번에 배치 조회. odId 기반 필터링으로 조회 쿼리 1회로 통합
Problem
타임딜/더블딜/사은품 등 프로모션 상품에는 쿠폰 적용 제외해야 하지만 레거시에서 규칙이 일관되지 않음
Solution
CartItemFlags.isCouponExcluded()로 쿠폰 제외 상품 명시적 마킹. TargetStrategy에서 프로모션 플래그와 couponExcluded 플래그를 모두 확인하여 일관된 제외 규칙
기술적 챌린지
레거시 테이블 구조에서 도메인 모델 설계
단일 테이블에 다양한 쿠폰 타입이 혼재. 그룹 개념, 최적 할인, 순차 적용 규칙을 추출하여 전략 패턴 기반 도메인 모델로 재설계
비즈니스 복잡도
3그룹 순차 적용 + 그룹 내 1개 제한 + DFS 최적 조합 + 상품별 비례 분배 + 프로모션 상품 쿠폰 제외 — 규칙 간 상호작용이 많아 설계 자체가 어려움
성과
- ✓평균 체류 시간 33.7% 증가에 기여 (3:10 → 4:14) — 최적 할인 자동 적용으로 구매 탐색 경험 개선
- ✓DFS 기반 전역 최적 쿠폰 조합 선택 — 순차 적용 상호작용 고려
- ✓정확한 상품별 할인 분배 — 환불/부분 취소 시에도 정확
- ✓전략 패턴으로 새 할인 방식 추가 시 Strategy만 구현 (OCP)
- ✓N+1 해소 — temp log 배치 조회로 성능 개선
- ✓레거시 호환 — cp_no 그룹 매핑, 사용 횟수, coupon_use/temp_log 구조 유지