[BE-03] LimitedDropDomainService 구매 오케스트레이션
작업 내용 (설계 의도)
변경 사항
한정판 구매 도메인 흐름을 조립한다(근거 TDD: ../TDD.md “Sequence Diagram”, ADR-001·ADR-003). 기존 GoodsDomainService.createPendingOrder(같은 도메인)를 무변경 재사용한다(FR-3). 롤백: 피처 플래그 OFF로 즉시 비활성(기존 goods 흐름 무영향).
LimitedDropDomainService.purchase(command):findOpenByProductId→ 없으면 NotFound.drop.validatePurchasable()(시각 게이트 FR-2, DB·Redis 미접근 거부).reservationStore.reserve(...)결과 when 분기: Admitted/AlreadyReserved 진행, SoldOut→예외(409), Throttled→예외(429), PerUserLimitExceeded→예외(403).- try
goodsDomainService.createPendingOrder(userId, items, key)→confirmSuccess(). catch →cancel()후 재전파(슬롯 반환, 언더셀만 허용).
openDrop(dropId): 상태 OPEN 전이 +seedIfAbsent로 Redis 카운터 시드(BE-08에서 호출).- Redis 장애(
DataAccessException/RedisLockException) 시 fail-open to DB: 게이트 우회하고 createPendingOrder 진행(오버셀은 Stock.@Version이 봉쇄),redis-degraded신호. - 메서드 15줄 초과 시
validateGate·admit·persistOrRelease헬퍼로 분해. - 오버셀 감지 지점(리컨실리에이션 입력)에서
LimitedDropOversoldEvent발행(DomainEventPublisher).
의존
- BE-01 (LimitedDrop·Repository·예외·이벤트), BE-02 (DropReservationStore·ReservationResult)
다이어그램
처리 흐름
sequenceDiagram participant D as LimitedDropDomainService participant R as DropReservationStore participant G as GoodsDomainService D->>D: validatePurchasable() D->>R: reserve(...) R-->>D: Admitted D->>G: createPendingOrder(재사용) G-->>D: GoodsOrder D->>R: confirmSuccess()
클래스 의존
flowchart LR LimitedDropDomainService --> LimitedDropRepository LimitedDropDomainService --> DropReservationStore LimitedDropDomainService --> GoodsDomainService LimitedDropDomainService --> DomainEventPublisher
테스트 케이스
- Admitted면 createPendingOrder를 호출하고 confirmSuccess로 확정한다
- SoldOut이면 createPendingOrder를 호출하지 않고 SoldOut 예외를 던진다
- Throttled/PerUserLimitExceeded는 각각 429/403 매핑 예외를 던지고 DB에 도달하지 않는다
- reserve는 Admitted였으나 createPendingOrder가 예외를 던지면 cancel로 슬롯을 복원한 뒤 재전파한다
- 동일 idempotencyKey 재요청 시 AlreadyReserved로 재-DECR 없이 기존 주문을 반환한다
- now<openAt이면 reserve를 호출하지 않고 TooEarly 예외를 던진다
- Redis가 DataAccessException을 던지면 fail-open으로 createPendingOrder를 진행한다