Skip to content

yunhobb/spring-saga-pattern-example

Repository files navigation

Spring Saga Pattern Example — Orchestration + Layered Architecture

e-커머스 주문 처리 시나리오를 saga를 Orchestration 으로 구현한 예제입니다.

주문 → 재고 예약 → 결제 흐름을 order-app(오케스트레이터)이 Kafka command/reply로 순서대로 호출하고, 재고 부족 또는 결제 실패 시 이미 차감한 재고를 되돌리고 주문을 CANCELLED로 바꿉니다.

스택

  • Java 21, Spring Boot 3.5.13, Gradle (멀티모듈)
  • Spring Kafka (command/reply 비동기 통신)
  • Spring Data JPA + H2 (서비스별 인메모리 DB)
  • 테스트: JUnit5, Mockito, spring-kafka-test(EmbeddedKafka), Awaitility

모듈 / 레이어 구조

3개 독립 마이크로서비스. 각 서비스 내부는 레이어드 아키텍처를 따릅니다.

controller        # presentation (REST)
service           # business (OrderSagaOrchestrator 포함)
repository        # persistence (Spring Data JPA)
domain            # JPA @Entity = 도메인 (분리 매핑 없음)
dto               # web DTO + Kafka command/reply 메시지 (서비스별 중복 정의)
infrastructure
  ├── messaging   # Kafka producer / consumer
  └── config      # KafkaConfig (토픽 선언)
서비스 포트 역할
order-app 8080 오케스트레이터 + 주문/saga 상태 영속화
stock-app 8081 재고 예약(차감)/복원
payment-app 8082 결제 처리(결정론적 성공/실패)

Saga 흐름 (Orchestration)

POST /orders → Order(PENDING) + OrderSaga(STARTED) 저장
  └─ stock-command(RESERVE) ─▶ stock-app
        ├─ RESERVED ─────────▶ saga=STOCK_RESERVED
        │     └─ payment-command(PAY) ─▶ payment-app
        │           ├─ COMPLETED ─▶ saga=COMPLETED,  Order=CONFIRMED   (정상 흐름)
        │           └─ FAILED ────▶ saga=COMPENSATING
        │                 └─ stock-command(RELEASE) ─▶ stock-app
        │                       └─ RELEASED ─▶ saga=COMPENSATED, Order=CANCELLED  (보상)
        └─ RESERVE_FAILED ───▶ saga=FAILED, Order=CANCELLED            (재고 부족, 보상 없음)

Kafka 토픽

토픽 producer consumer 메시지 타입
stock-command order stock RESERVE, RELEASE
stock-reply stock order RESERVED, RESERVE_FAILED, RELEASED
payment-command order payment PAY
payment-reply payment order COMPLETED, FAILED

메시지는 JSON 문자열로 직렬화하며, 응답이 도착하면 sagaId로 어떤 주문의 saga인지 찾습니다.

결정론적 실패 (시연용)

  • 재고 부족: 요청 quantity > 보유 재고RESERVE_FAILED
  • 결제 실패: 요청 amount >= 100000(saga.payment.fail-threshold) → FAILED

초기 재고(stock-app/src/main/resources/data.sql): productId 1=10, 2=5, 3=0.

테스트

./gradlew test
  • 단위: StockService(재고 부족 분기), PaymentService(실패 분기), OrderSagaOrchestrator(상태 전이/보상)
  • 통합(@SpringBootTest + @EmbeddedKafka): 정상 흐름 / 재고 부족 / 결제 실패 보상 3 시나리오. stock·payment는 OrderSagaIntegrationTestSagaPartnerStub이 대신합니다.

수동 E2E 실행

docker-compose up -d                 # Kafka 기동 (localhost:9092)
./gradlew :stock-app:bootRun &       # 8081
./gradlew :payment-app:bootRun &     # 8082
./gradlew :order-app:bootRun &       # 8080

# ① 정상 흐름 → CONFIRMED
curl -X POST localhost:8080/orders -H 'Content-Type: application/json' \
  -d '{"productId":1,"quantity":1,"amount":1000}'

# ② 재고 부족 → CANCELLED (saga FAILED)
curl -X POST localhost:8080/orders -H 'Content-Type: application/json' \
  -d '{"productId":1,"quantity":99999,"amount":1000}'

# ③ 결제 실패 → CANCELLED (saga COMPENSATED, 재고 원복)
curl -X POST localhost:8080/orders -H 'Content-Type: application/json' \
  -d '{"productId":1,"quantity":1,"amount":100000}'

# 상태 조회
curl localhost:8080/orders/1

주문 생성은 비동기 처리이므로 202 Accepted(PENDING)로 즉시 응답하며, 최종 상태는 GET /orders/{id}로 확인합니다. 각 단계 전이는 로그(SLF4J)와 H2 콘솔 (/h2-console)에서 추적할 수 있습니다.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages