
이번 주의 핵심 목표는 도메인 이벤트를 기점으로 Outbox + Kafka 기반의 비동기 전파 아키텍처를 직접 설계하고,
그 흐름을 통해 도메인 간의 협업을 결합도 낮은 구조로 구현해보는 것이었다.
이를 통해 다음과 같은 설계를 실험하고 검증했다:
@TransactionalEventListener(BEFORE_COMMIT or AFTER_COMMIT) 기반 이벤트 감지예를 들어, 쿠폰 발급 과정에서 수량 확인 → 발급 처리 → 메시지 전송까지 한 메서드에 몰려있었고,
메시지 발행 실패 시 전체 트랜잭션이 롤백되는 구조는 장애 복원력 측면에서도 한계가 있었다.
초기에는 컨트롤러나 서비스 단에서 Kafka 메시지를 직접 발행하려 했으나,
"도메인의 책임은 상태 전이",
도메인 객체는 상태 변화 시 registerEvent()를 통해 도메인 이벤트를 등록하고,
Application Layer에서 publishEvent()로 발행한다.
coupon.validateUsable(clock, userId); // → 내부적으로 도메인 이벤트 등록
coupon.getDomainEvents().forEach(eventPublisher::publishEvent);
coupon.clearEvents();
[Coupon 도메인]
- validateUsable() 내부에서 CouponIssueRequestedEvent 등록
[Application Layer]
- @TransactionalEventListener(BEFORE_COMMIT)
→ OutboxService.saveEvent() 호출 → OutboxMessage 저장
[Infrastructure Layer]
- ScheduledOutboxRelayRunner (1초마다 실행)
→ 저장된 메시지를 Kafka 로 전송 → 성공 시 lastProcessedId 갱신
[Kafka Consumer]
- coupon.issue.requested 수신 → 발급 처리 실행
[Payment 도메인]
- createSuccess() 시 OrderConfirmedEvent 등록
[OrderConfirmedEventHandler]
- AFTER_COMMIT + @Async로 수신 → 주문 상태 CONFIRMED 전이
[OrderAggregate]
- markConfirmed() → ProductSalesRankRecordedEvent, OrderExportRequestedEvent 등록
[OrderExportEventHandler]
- AFTER_COMMIT + @Async → Kafka 전송
[Kafka Consumer]
- order-export 수신 → 외부 연동 처리
| 테스트 시나리오 | 검증 내용 |
|---|---|
| 쿠폰 발급 요청 | 도메인 이벤트 등록 → Outbox 저장 → Kafka 전파 → 발급 처리 |
| 주문 생성 → 재고 감소 실패 | Kafka 메시지 전송 실패 시 OrderCanceled 이벤트 발행 |
| 결제 성공 | 주문 상태 전이 + 후속 이벤트 발행 및 Kafka 전송 |
| 주문 CONFIRMED | 상품 랭킹 등록 + 외부 전송 이벤트 발행 |
Awaitility를 활용하여 비동기 리스너가 동작 완료될 때까지 기다리며, 로그 기반으로 실제 흐름 검증 완료.
| 흐름 | 이벤트 | 리스너/처리 방식 | 목적 |
|---|---|---|---|
| 쿠폰 발급 요청 | CouponIssueRequestedEvent | BEFORE_COMMIT → Outbox 저장 | Kafka로 전파 후 발급 처리 |
| 결제 성공 | OrderConfirmedEvent | AFTER_COMMIT → 주문 CONFIRMED | 후속 이벤트 트리거 |
| 주문 CONFIRMED | ProductSalesRankRecordedEvent, OrderExportRequestedEvent | AFTER_COMMIT + @Async | 상품 랭킹, 외부 전송 |
| 메시지 전파 | OutboxMessage | Scheduled Relay → Kafka | 트랜잭션과 전파 분리 |