Skip to content

StudyForBetterLife/saga-example

Repository files navigation

Saga Pattern Example with Temporal & Eureka

이 프로젝트는 Temporal을 Saga Orchestrator로, Spring Cloud Netflix Eureka를 서비스 디스커버리로 사용하여 마이크로서비스 간 분산 트랜잭션을 관리하는 예제입니다.

📋 목차


🏗 아키텍처

┌─────────────────────────────────────────────────────────────────────┐
│                           클라이언트                                 │
└─────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────┐
│                    order-service (8080)                              │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  POST /api/orders                                            │   │
│  │  1. 재고 확인 (Feign → inventory-service) [동기]            │   │
│  │  2. 주문 저장 (PENDING)                                      │   │
│  │  3. Workflow 시작 (Temporal)                                 │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘
        │                               │
        │ Feign (동기)                   │ Workflow 시작
        ▼                               ▼
┌───────────────────┐     ┌─────────────────────────────────────────┐
│ inventory-service │     │              Temporal Server             │
│     (8083)        │     │                (7233)                   │
└───────────────────┘     └─────────────────────────────────────────┘
                                        │
                                        │ Task Queue 폴링
                                        ▼
┌─────────────────────────────────────────────────────────────────────┐
│               saga-orchestrator (8082) ⭐ Orchestrator               │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │  Temporal Worker                                             │   │
│  │  ├─ OrderWorkflow (Saga 로직)                                │   │
│  │  │   ├─ Step 1: reserveStock (inventory-service)            │   │
│  │  │   ├─ Step 2: processPayment (payment-service)            │   │
│  │  │   └─ Step 3: completeOrder (order-service)               │   │
│  │  └─ 보상 트랜잭션                                            │   │
│  │      ├─ releaseStock (재고 해제)                             │   │
│  │      └─ refundPayment (결제 환불)                            │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘
        │                       │                       │
        ▼                       ▼                       ▼
┌───────────────┐     ┌───────────────┐     ┌───────────────┐
│ inventory     │     │ payment       │     │ order         │
│ -service      │     │ -service      │     │ -service      │
│ (Activity)    │     │ (Activity)    │     │ (Activity)    │
└───────────────┘     └───────────────┘     └───────────────┘

                    ┌─────────────────────┐
                    │   Eureka Server     │
                    │      (8761)         │
                    │  서비스 디스커버리    │
                    └─────────────────────┘
                              ▲
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
   order-service      inventory-service     payment-service
   saga-orchestrator

🛠 기술 스택

구분 기술
Language Java 21
Framework Spring Boot 3.3.x
Build Tool Gradle 8.x
Service Discovery Spring Cloud Netflix Eureka
동기 호출 OpenFeign
Saga Orchestration Temporal
Database H2 (in-memory), PostgreSQL (Temporal)
Container Docker, Docker Compose

📁 프로젝트 구조

saga-example/
├── eureka-server/                    # 🆕 Eureka Server (서비스 디스커버리)
│   └── src/main/java/com/example/eureka/
│       └── EurekaServerApplication.java
│
├── saga-common/                      # 공유 모듈 (인터페이스, 상수)
│   └── src/main/java/com/example/saga/common/
│       ├── workflow/
│       │   ├── OrderWorkflow.java
│       │   ├── OrderServiceActivities.java
│       │   ├── PaymentServiceActivities.java
│       │   └── InventoryServiceActivities.java  # 🆕
│       └── constants/
│           └── SagaConstants.java
│
├── inventory-service/                # 🆕 재고 서비스 (도메인)
│   └── src/main/java/com/example/inventory/
│       ├── InventoryServiceApplication.java
│       ├── domain/
│       │   ├── Inventory.java
│       │   └── StockReservation.java
│       ├── repository/
│       ├── service/
│       │   ├── InventoryService.java
│       │   └── InventoryServiceActivitiesImpl.java
│       ├── controller/
│       │   └── InventoryController.java
│       └── config/
│
├── order-service/                    # 주문 서비스 (+ Feign Client)
│   └── src/main/java/com/example/order/
│       ├── OrderServiceApplication.java
│       ├── client/                   # 🆕
│       │   ├── InventoryClient.java  # Feign Client
│       │   └── InventoryClientFallback.java
│       ├── domain/
│       ├── service/
│       │   ├── OrderService.java     # 재고 확인 로직 추가
│       │   └── OrderServiceActivitiesImpl.java
│       └── ...
│
├── payment-service/                  # 결제 서비스
│   └── src/main/java/com/example/payment/
│       └── ...
│
├── saga-orchestrator/                # Saga Orchestrator
│   └── src/main/java/com/example/orchestrator/
│       └── workflow/
│           └── OrderWorkflowImpl.java  # 재고 Saga 추가
│
├── docker-compose.yml
├── build.gradle
├── settings.gradle
└── README.md

모듈 의존성

           saga-common (공유 라이브러리)
                    ↑
    ┌───────────────┼───────────────┬───────────────┐
    │               │               │               │
order-service  inventory-service  payment-service  saga-orchestrator
(Workflow 시작)   (Activity 구현)  (Activity 구현)  (Workflow 구현)
    │
    └──→ InventoryClient (Feign - 동기 호출)
              │
              ▼
         inventory-service

                    ↑ Eureka 등록 ↑
              eureka-server (8761)

🔄 Saga 흐름

성공 케이스 (amount ≤ 100 & 재고 충분)

sequenceDiagram
    participant Client
    participant OrderService
    participant InventoryService
    participant Temporal
    participant Orchestrator
    participant PaymentService

    Client->>OrderService: POST /api/orders<br/>{productId, quantity, amount: 50}
    OrderService->>InventoryService: [Feign] 재고 확인
    InventoryService-->>OrderService: {available: true}
    OrderService->>OrderService: 주문 저장 (PENDING)
    OrderService->>Temporal: Start OrderWorkflow
    OrderService-->>Client: 201 Created<br/>{id, status: PENDING}

    Temporal->>Orchestrator: Task Queue 폴링
    
    Note over Orchestrator: [Step 1] 재고 예약
    Orchestrator->>InventoryService: reserveStock()
    InventoryService-->>Orchestrator: true (예약 성공)
    
    Note over Orchestrator: [Step 2] 결제 처리
    Orchestrator->>PaymentService: processPayment()
    PaymentService-->>Orchestrator: true (결제 성공)
    
    Note over Orchestrator: [Step 3] 주문 완료
    Orchestrator->>OrderService: completeOrder()
    Orchestrator->>InventoryService: confirmStock()
    
    Note over Orchestrator: Saga 완료 (성공)
Loading

실패 케이스 (결제 실패 → 재고 복구)

sequenceDiagram
    participant Client
    participant OrderService
    participant InventoryService
    participant Temporal
    participant Orchestrator
    participant PaymentService

    Client->>OrderService: POST /api/orders<br/>{productId, quantity, amount: 200}
    OrderService->>InventoryService: [Feign] 재고 확인
    InventoryService-->>OrderService: {available: true}
    OrderService->>OrderService: 주문 저장 (PENDING)
    OrderService->>Temporal: Start OrderWorkflow
    OrderService-->>Client: 201 Created

    Temporal->>Orchestrator: Task Queue 폴링
    
    Note over Orchestrator: [Step 1] 재고 예약
    Orchestrator->>InventoryService: reserveStock()
    InventoryService-->>Orchestrator: true
    
    Note over Orchestrator: [Step 2] 결제 처리
    Orchestrator->>PaymentService: processPayment()
    PaymentService-->>Orchestrator: false (잔액 부족)
    
    Note over Orchestrator: 보상 트랜잭션 실행
    Orchestrator->>InventoryService: releaseStock() (보상)
    Orchestrator->>OrderService: cancelOrder()
    
    Note over Orchestrator: Saga 완료 (결제 실패 → 취소)
Loading

🚀 실행 방법

1. 사전 요구사항

  • Java 21
  • Docker & Docker Compose
  • Gradle 8.x (또는 Gradle Wrapper 사용)

2. 프로젝트 빌드

./gradlew clean build -x test

3. 인프라 실행 (Temporal)

docker-compose up -d

4. 서비스 실행 (순서대로)

# 터미널 1: Eureka Server (먼저 실행!)
./gradlew :eureka-server:bootRun

# 터미널 2: Inventory Service
./gradlew :inventory-service:bootRun

# 터미널 3: Payment Service
./gradlew :payment-service:bootRun

# 터미널 4: Order Service
./gradlew :order-service:bootRun

# 터미널 5: Saga Orchestrator
./gradlew :saga-orchestrator:bootRun

5. 서비스 접속 정보

서비스 포트 설명
eureka-server 8761 서비스 디스커버리 Dashboard
order-service 8080 주문 API
payment-service 8081 결제 API
saga-orchestrator 8082 Saga Orchestrator (Temporal Worker)
inventory-service 8083 재고 API
Temporal Web UI 8088 Workflow 모니터링
Temporal Server 7233 Temporal gRPC

6. 종료

docker-compose down -v

🧪 API 테스트

재고 조회

curl http://localhost:8083/api/inventory

주문 생성 (성공 케이스: amount ≤ 100)

curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user-1",
    "productId": "PROD-001",
    "quantity": 2,
    "amount": 50
  }'

응답:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "userId": "user-1",
  "productId": "PROD-001",
  "quantity": 2,
  "amount": 50,
  "status": "PENDING"
}

주문 조회 (잠시 후 COMPLETED로 변경됨)

curl http://localhost:8080/api/orders/{orderId}

주문 생성 (실패 케이스: amount > 100)

curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user-2",
    "productId": "PROD-001",
    "quantity": 1,
    "amount": 200
  }'

잠시 후 조회하면 CANCELED 상태:

{
  "id": "...",
  "status": "CANCELED"
}

🖥 Eureka Dashboard

http://localhost:8761 에서 Eureka Dashboard에 접속하여:

  1. 등록된 서비스 목록 확인
  2. 각 서비스의 인스턴스 상태 확인
  3. 서비스 헬스 체크

등록되는 서비스 목록

  • ORDER-SERVICE
  • PAYMENT-SERVICE
  • INVENTORY-SERVICE
  • SAGA-ORCHESTRATOR

🖥 Temporal Web UI

http://localhost:8088 에서 Temporal Web UI에 접속하여:

  1. Workflow 목록 확인: 실행 중인/완료된 Workflow 목록
  2. Workflow 상세 보기: 각 Activity 실행 이력, 입력/출력 값
  3. Timeline 보기: Saga 각 단계의 시간 흐름

Workflow ID 형식

  • order-{orderId} 형태로 생성됨

🎯 주요 특징

Eureka 기반 서비스 디스커버리

  • 서비스 자동 등록/해제
  • 로드 밸런싱 지원
  • 서비스 헬스 체크

OpenFeign 동기 호출

  • 주문 생성 전 재고 확인 (동기)
  • 선언적 REST 클라이언트
  • Fallback 처리 지원

Temporal Saga 패턴

  • 3단계 Saga: 재고 예약 → 결제 → 주문 완료
  • 자동 보상 트랜잭션
  • 상태 영속성 및 재시도 처리

📝 프로덕션 고려사항

  1. 멱등성: 재고 예약, 결제 요청의 중복 처리 방지
  2. 타임아웃: Activity별 적절한 타임아웃 설정
  3. 재시도 정책: 서비스별 재시도 전략
  4. 모니터링: Prometheus, Grafana 연동
  5. 보안: mTLS, 인증/인가
  6. 로깅: 분산 추적 (Jaeger, Zipkin)

📚 참고 자료

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages