
이커머스에서 주문은 단순 데이터가 아니다.
주문은 다음 모든 흐름의 출발점이다.
주문 → 결제 → 참여 수량 집계 → 목표 달성 → 정산 → 판매자 수익
즉 주문은 비즈니스의 기준 데이터다.
초기 구조에서는 주문 생성 과정에서 결제 모듈을 동기 호출(openfeign)했다.
주문 생성 → 포인트 차감 → 성공 시 주문 저장
문제는 여기서 발생했다.
포인트 모듈 장애 발생 → 트랜잭션 롤백 → 주문 자체가 저장되지 않음
이 구조에서는 다음 문제가 생긴다.
팀 논의를 통해 주문 도메인의 핵심 요구사항을 다음과 같이 정의했다.
"결제가 실패하더라도 주문 의도는 반드시 기록되어야 한다."
이 요구사항을 만족시키는 구조가 필요했다.
아키텍처 논의 과정에서 3가지 구조를 비교했다.
프론트 → 주문 → 결제
장점
치명적 단점
→ 장애 전파 구조
1. 프론트 → 주문 생성
2. 프론트 → 결제 호출
장점
문제
→ 프론트가 오케스트레이터가 되는 구조
1. 프론트 → 주문 생성 → 저장(PENDING)
2. 프론트 → 결제 수행 → 이벤트 발행(카프카)
3. 카프카 → 주문 상태 업데이트
이 구조는 다음 특성을 가진다:
초기에는 단순 동기 구조도 후보였다.
하지만 장애 시나리오를 기준으로 검토하면서 문제가 명확해졌다.
결제 서버 다운
→ 주문 생성 실패
→ 주문 기록 없음
결제 서버 다운
→ 주문 생성 성공
→ 상태만 PENDING 유지
즉 차이는 이것이다.
| 구조 | 장애 시 주문 |
|---|---|
| 동기 | 사라짐 |
| 이벤트 | 남음 |
주문을 "트랜잭션 결과"가 아니라 "고객 의도 기록"으로 정의하면 정답은 명확했다.
Saga도 검토했지만 선택하지 않았다.
이유는 도메인 특성 때문이다.
주문은 되돌릴 대상이 아니라 남겨야 할 기록이다.
Saga는 선형 트랜잭션 흐름에 적합하지만
이 구조는 분기 흐름이 많았다.
그래서 Saga 대신 이벤트 기반 상태 전이 모델을 선택했다.
주문 생성 (PENDING)
↓
결제 요청
↓
[PAYMENT_CHANGED 이벤트]
↓
주문 상태 업데이트 (COMPLETED / FAILED)
sequenceDiagram
participant F as Client
participant O as Order
participant P as Payment
participant K as Kafka
F->>O: 주문 생성
O->>F: orderId 반환 (PENDING)
F->>P: 결제 요청
P->>K: PAYMENT_CHANGED
K->>O: 이벤트 전달
O->>O: 상태 업데이트
loop 폴링 (주기적 상태 확인)
F->>O: GET /orders/{id}
O->>F: status 응답
end

PENDING → PAYMENT_PROCESSING → COMPLETED
↘ FAILED
이번 설계를 통해 깨달은 가장 중요한 사실은 이것이다.
좋은 아키텍처는 기술이 아니라 도메인에서 나온다.
이벤트 기반 구조를 선택한 이유도
기술 트렌드 때문이 아니라
"주문은 기록이어야 한다" 는 도메인 정의 때문이었다.
최종적으로 선택한 구조는
이벤트 기반 주문 상태 전이 아키텍처
이 구조는 다음 요구사항을 만족한다.
즉 이 설계는 단순 기능 구현이 아니라
장애 상황에서도 비즈니스 데이터 정합성을 유지하는 구조 설계 경험이었다.
ORDER_CREATED: 주문 생성 이벤트PAYMENT_CHANGED: 결제 상태 변경 이벤트ORDER_CONFIRMED: 주문 확정 이벤트ORDER_CANCELED: 주문 취소 이벤트/commerce/src/main/java/store/_0982/commerce/domain/order/Order.javaapplication/order/OrderEventListener.java/common/src/main/java/store/_0982/common/kafka/event/