Skip to content

hasune/msa-kafka-saga-pattern-orchestration

Repository files navigation

Saga 패턴 마이크로서비스 구현 프로젝트

1. 개요

이 프로젝트는 Spring Boot 기반 마이크로서비스 아키텍처에서 분산 트랜잭션을 관리하기 위한 Saga 패턴을 구현한 예제입니다. 주문 처리 과정을 여러 독립적인 서비스로 분리하고, Kafka를 통한 이벤트 기반 통신으로 트랜잭션 일관성을 유지합니다. 특히 트랜잭션 실패 시 보상 트랜잭션(Compensating Transaction)을 통한 데이터 일관성 복구에 중점을 두었습니다.

2. 시스템 구성

다음 4개의 마이크로서비스로 구성되어 있습니다:

  • Order Service (포트: 8082): 주문 생성 및 전체 Saga 오케스트레이션 관리
  • Product Service (포트: 8081): 상품 정보 관리 및 재고 예약
  • Payment Service (포트: 8083): 결제 처리 및 고객 잔액 관리
  • Inventory Service (포트: 8084): 재고 관리 및 물류 업데이트

각 서비스는 MySQL 데이터베이스를 사용하며, Kafka를 통해 이벤트 기반으로 통신합니다.

graph TD
    Client([Client]) --> OrderService
    
    subgraph "Microservices"
        OrderService[Order Service] <--> Kafka
        ProductService[Product Service] <--> Kafka
        PaymentService[Payment Service] <--> Kafka
        InventoryService[Inventory Service] <--> Kafka
    end
    
    OrderService --- MySQL[(MySQL)]
    ProductService --- MySQL
    PaymentService --- MySQL
    InventoryService --- MySQL
    
    classDef service fill:#afd,stroke:#6c6,stroke-width:2px;
    classDef database fill:#bbe,stroke:#99c,stroke-width:2px;
    classDef messagebroker fill:#fcb,stroke:#d91,stroke-width:2px;
    
    class OrderService,ProductService,PaymentService,InventoryService service;
    class MySQL database;
    class Kafka messagebroker;
Loading

3. Saga 패턴 구현 방식

3.1 오케스트레이션(Orchestration) vs 코레오그래피(Choreography)

본 프로젝트는 오케스트레이션(Orchestration) 방식으로 구현되었습니다.

오케스트레이션 방식

  • 중앙 조정자(Orchestrator)가 전체 트랜잭션 흐름을 제어 (OrderSagaManager가 이 역할)
  • 조정자가 각 단계를 조율하고 성공/실패에 따라 다음 단계나 보상 트랜잭션 결정
  • 전체 트랜잭션 상태를 명확하게 추적 가능
  • 복잡한 로직을 중앙에서 관리

코레오그래피 방식 (비교용)

  • 중앙 조정자 없이 각 서비스가 자율적으로 이벤트에 반응
  • 각 서비스는 자신의 로직만 처리하고 이벤트 발행
  • 더 느슨한 결합과 높은 자율성
  • 구현은 단순하지만 전체 흐름 파악이 어려울 수 있음

3.2 구현 난이도 비교

  • 코레오그래피 방식: 초기 구현이 더 간단하고 각 서비스의 독립성이 높음
  • 오케스트레이션 방식: 중앙 조정자 설계가 필요하여 구현이 복잡하나, 트랜잭션 관리와 모니터링이 용이함

본 프로젝트에서는 명확한 트랜잭션 추적과 체계적인 보상 로직 관리를 위해 오케스트레이션 방식을 선택했습니다.

4. 트랜잭션 흐름

4.1 주문 처리 흐름

sequenceDiagram
    participant Client as 클라이언트
    participant Order as 주문 서비스
    participant Product as 상품 서비스
    participant Payment as 결제 서비스
    participant Inventory as 재고 서비스
    
    Client->>Order: 주문 생성 요청
    Order->>Order: 주문 생성 및 ORDER_CREATED 이벤트 발행
    Order->>Product: ORDER_CREATED 이벤트 송신
    
    Product->>Product: 재고 예약 시도
    alt 재고 충분
        Product->>Order: PRODUCT_RESERVED 이벤트 송신
        Order->>Payment: PRODUCT_RESERVED 이벤트 전달
        
        Payment->>Payment: 결제 처리 시도
        alt 결제 성공
            Payment->>Order: PAYMENT_PROCESSED 이벤트 송신
            Order->>Inventory: PAYMENT_PROCESSED 이벤트 전달
            
            Inventory->>Inventory: 재고 업데이트
            Inventory->>Order: INVENTORY_UPDATED 이벤트 송신
            Order->>Order: 주문 상태 "COMPLETED"로 변경
            Order->>Client: 주문 완료 응답
            
        else 결제 실패 (잔액 부족)
            Payment->>Order: PAYMENT_FAILED 이벤트 송신
            Order->>Product: CANCEL_PRODUCT_RESERVATION 이벤트 송신
            Product->>Product: 상품 예약 취소 (보상 트랜잭션)
            Order->>Order: 주문 상태 "FAILED"로 변경
            Order->>Client: 주문 실패 응답
        end
        
    else 재고 부족
        Product->>Order: PRODUCT_RESERVATION_FAILED 이벤트 송신
        Order->>Order: 주문 상태 "FAILED"로 변경
        Order->>Client: 주문 실패 응답 (재고 부족)
    end
Loading

4.2 보상 트랜잭션 (Compensating Transaction)

트랜잭션 중 실패가 발생하면 이전에 성공한 단계들을 역순으로 취소합니다:

  1. 결제 실패 시:

    • 주문 서비스가 CANCEL_PRODUCT_RESERVATION 이벤트 발행
    • 상품 서비스가 이전에 예약한 재고를 원복
  2. 재고 업데이트 실패 시:

    • 주문 서비스가 CANCEL_PAYMENT 이벤트 발행
    • 결제 서비스가 처리된 결제를 취소하고 고객 잔액 복원
    • 이후 상품 서비스의 재고 예약도 취소

5. 데이터베이스 엔티티 구조

각 서비스는 자체 데이터베이스 테이블을 가지고 있습니다:

5.1 Order Service

orders 테이블

+------------+--------------+------+-----+-------------------+
| Field      | Type         | Null | Key | Default           |
+------------+--------------+------+-----+-------------------+
| id         | varchar(255) | NO   | PRI | NULL              |
| customer_id| varchar(255) | NO   |     | NULL              |
| product_id | varchar(255) | NO   |     | NULL              |
| quantity   | int          | NO   |     | NULL              |
| amount     | decimal(19,2)| NO   |     | NULL              |
| status     | varchar(50)  | NO   |     | NULL              |
| created_at | datetime     | NO   |     | CURRENT_TIMESTAMP |
| updated_at | datetime     | NO   |     | CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+

주문 상태(status):

  • CREATED: 주문 생성 초기 상태
  • PRODUCT_RESERVED: 상품 예약 완료
  • PAYMENT_COMPLETED: 결제 완료
  • COMPLETED: 주문 프로세스 완료
  • FAILED: 주문 처리 실패

5.2 Product Service

products 테이블

+----------------+--------------+------+-----+---------+
| Field          | Type         | Null | Key | Default |
+----------------+--------------+------+-----+---------+
| id             | varchar(255) | NO   | PRI | NULL    |
| name           | varchar(255) | NO   |     | NULL    |
| description    | text         | YES  |     | NULL    |
| price          | decimal(19,2)| NO   |     | NULL    |
| stock_quantity | int          | NO   |     | NULL    |
+----------------+--------------+------+-----+---------+

5.3 Payment Service

customer_balance 테이블

+-----------------+--------------+------+-----+---------+
| Field           | Type         | Null | Key | Default |
+-----------------+--------------+------+-----+---------+
| customer_id     | varchar(255) | NO   | PRI | NULL    |
| available_amount| decimal(19,2)| NO   |     | NULL    |
+-----------------+--------------+------+-----+---------+

payments 테이블

+-------------+--------------+------+-----+-------------------+
| Field       | Type         | Null | Key | Default           |
+-------------+--------------+------+-----+-------------------+
| id          | varchar(255) | NO   | PRI | NULL              |
| order_id    | varchar(255) | NO   |     | NULL              |
| customer_id | varchar(255) | NO   |     | NULL              |
| amount      | decimal(19,2)| NO   |     | NULL              |
| status      | varchar(50)  | NO   |     | NULL              |
| processed_at| datetime     | NO   |     | CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+

결제 상태(status):

  • COMPLETED: 결제 성공
  • FAILED: 결제 실패 (잔액 부족)
  • CANCELLED: 보상 트랜잭션으로 취소됨

5.4 Inventory Service

inventory_records 테이블

+------------+--------------+------+-----+-------------------+
| Field      | Type         | Null | Key | Default           |
+------------+--------------+------+-----+-------------------+
| id         | varchar(255) | NO   | PRI | NULL              |
| order_id   | varchar(255) | NO   |     | NULL              |
| product_id | varchar(255) | NO   |     | NULL              |
| quantity   | int          | NO   |     | NULL              |
| status     | varchar(50)  | NO   |     | NULL              |
| created_at | datetime     | NO   |     | CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+

인벤토리 상태(status):

  • UPDATED: 재고 업데이트 완료
  • REVERTED: 보상 트랜잭션으로 취소됨

6. Kafka 토픽 및 이벤트 흐름

시스템은 다음 Kafka 토픽을 사용하여 이벤트를 교환합니다:

6.1 주요 토픽 목록 및 역할

토픽명 이벤트 생성 서비스 이벤트 소비 서비스 역할 및 설명
order-created Order Service Product Service 주문 생성 이벤트, 상품 예약 프로세스 시작
product-reserved Product Service Payment Service, Order Service 상품 예약 성공 이벤트, 결제 프로세스 시작
product-reservation-failed Product Service Order Service 상품 예약 실패 이벤트 (재고 부족 등)
payment-processed Payment Service Inventory Service, Order Service 결제 성공 이벤트, 재고 업데이트 프로세스 시작
payment-failed Payment Service Order Service 결제 실패 이벤트 (잔액 부족 등), 보상 트랜잭션 시작
inventory-updated Inventory Service Order Service 재고 업데이트 성공 이벤트, 주문 완료 처리
inventory-update-failed Inventory Service Order Service 재고 업데이트 실패 이벤트, 보상 트랜잭션 시작
cancel-product-reservation Order Service Product Service 상품 예약 취소 이벤트 (보상 트랜잭션)
cancel-payment Order Service Payment Service 결제 취소 이벤트 (보상 트랜잭션)
order-completed Order Service - 주문 완료 이벤트 (모든 단계 성공)
order-cancelled Order Service - 주문 취소 이벤트 (보상 트랜잭션 완료)

6.2 성공 케이스 이벤트 흐름

order-created → product-reserved → payment-processed → inventory-updated → order-completed

6.3 실패 케이스 및 보상 트랜잭션 이벤트 흐름

재고 부족 실패:

order-created → product-reservation-failed → (주문 실패)

결제 실패 및 보상:

order-created → product-reserved → payment-failed → cancel-product-reservation → (주문 실패)

재고 업데이트 실패 및 보상:

order-created → product-reserved → payment-processed → inventory-update-failed → cancel-payment → cancel-product-reservation → (주문 실패)

7. 코드 구조 및 주요 컴포넌트

7.1 Order Service

OrderController

주문 생성 및 조회 API 제공

OrderService

주문 엔티티 관리 및 주문 생성 시 이벤트 발행

OrderSagaManager

사가 오케스트레이션 담당, 각 이벤트에 따른 흐름 제어 및 보상 트랜잭션 관리

OrderEventConsumer

Kafka 토픽에서 이벤트 수신 및 처리

7.2 Product Service

ProductController

상품 CRUD API 제공

ProductService

상품 재고 예약 및 취소 로직, 이벤트 발행

ProductEventConsumer

주문 생성 및 취소 이벤트 수신 처리

7.3 Payment Service

PaymentService

결제 처리 및 취소 로직, 고객 잔액 관리

CustomerBalanceRepository

고객 잔액 CRUD 및 잔액 차감/환불 기능

PaymentEventConsumer

결제 관련 이벤트 수신 및 처리

7.4 Inventory Service

InventoryService

재고 업데이트 및 복원 기능

InventoryEventConsumer

재고 관련 이벤트 수신 및 처리

8. 설치 및 실행 방법

8.1 전제 조건

  • JDK 17 이상
  • Docker 및 Docker Compose
  • Gradle

8.2 프로젝트 빌드

./gradlew clean build

8.3 Docker Compose 실행

docker-compose up -d

8.4 서비스 접속 정보

9. 테스트 시나리오

9.1 성공 케이스

  1. 상품 등록:
curl --location 'http://localhost:8081/api/products' \
--header 'Content-Type: application/json' \
--data '{
    "id": "prod-001",
    "name": "테스트 상품",
    "description": "테스트용 상품입니다.",
    "price": 1000,
    "stockQuantity": 10
}'
  1. 주문 생성 (성공 케이스):
curl --location 'http://localhost:8082/api/orders' \
--header 'Content-Type: application/json' \
--data '{
    "customerId": "customer1",
    "productId": "prod-001",
    "quantity": 2,
    "amount": 2000
}'

결과:

  • 주문 상태: COMPLETED
  • 상품 재고: 8개로 감소
  • 고객 잔액: 8000 (10000 - 2000)

9.2 재고 부족 실패 케이스

curl --location 'http://localhost:8082/api/orders' \
--header 'Content-Type: application/json' \
--data '{
    "customerId": "customer1",
    "productId": "prod-001",
    "quantity": 20,
    "amount": 20000
}'

결과:

  • 주문 상태: FAILED
  • 상품 재고: 변동 없음
  • 고객 잔액: 변동 없음

9.3 잔액 부족 실패 케이스

curl --location 'http://localhost:8082/api/orders' \
--header 'Content-Type: application/json' \
--data '{
    "customerId": "customer3",
    "productId": "prod-001",
    "quantity": 5,
    "amount": 5000
}'

결과:

  • 주문 상태: FAILED
  • 상품 재고: 원래대로 복원됨 (보상 트랜잭션)
  • 고객 잔액: 변동 없음 (customer3의 잔액 3000 < 주문 금액 5000)

10. 결론 및 개선 방향

이 프로젝트는 오케스트레이션 방식의 사가 패턴을 구현하여 마이크로서비스 환경에서 분산 트랜잭션의 일관성을 유지하는 방법을 보여줍니다. 향후 개선 가능한 영역:

  • 실시간 모니터링: 사가 진행 상황을 실시간으로 모니터링하는 대시보드
  • 타임아웃 처리: 장기 실행 트랜잭션에 대한 타임아웃 메커니즘
  • 멱등성 보장: 중복 이벤트 처리에 대한 안전장치
  • 코레오그래피 방식 구현: 비교를 위한 코레오그래피 방식 구현

라이센스

MIT

About

MSA Kafka 스택에서 사가패턴 구현 예제(오케스트레이션 방식)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors