이 프로젝트는 Spring Boot 기반 마이크로서비스 아키텍처에서 분산 트랜잭션을 관리하기 위한 Saga 패턴을 구현한 예제입니다. 주문 처리 과정을 여러 독립적인 서비스로 분리하고, Kafka를 통한 이벤트 기반 통신으로 트랜잭션 일관성을 유지합니다. 특히 트랜잭션 실패 시 보상 트랜잭션(Compensating Transaction)을 통한 데이터 일관성 복구에 중점을 두었습니다.
다음 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;
본 프로젝트는 오케스트레이션(Orchestration) 방식으로 구현되었습니다.
- 중앙 조정자(Orchestrator)가 전체 트랜잭션 흐름을 제어 (OrderSagaManager가 이 역할)
- 조정자가 각 단계를 조율하고 성공/실패에 따라 다음 단계나 보상 트랜잭션 결정
- 전체 트랜잭션 상태를 명확하게 추적 가능
- 복잡한 로직을 중앙에서 관리
- 중앙 조정자 없이 각 서비스가 자율적으로 이벤트에 반응
- 각 서비스는 자신의 로직만 처리하고 이벤트 발행
- 더 느슨한 결합과 높은 자율성
- 구현은 단순하지만 전체 흐름 파악이 어려울 수 있음
- 코레오그래피 방식: 초기 구현이 더 간단하고 각 서비스의 독립성이 높음
- 오케스트레이션 방식: 중앙 조정자 설계가 필요하여 구현이 복잡하나, 트랜잭션 관리와 모니터링이 용이함
본 프로젝트에서는 명확한 트랜잭션 추적과 체계적인 보상 로직 관리를 위해 오케스트레이션 방식을 선택했습니다.
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
트랜잭션 중 실패가 발생하면 이전에 성공한 단계들을 역순으로 취소합니다:
-
결제 실패 시:
- 주문 서비스가
CANCEL_PRODUCT_RESERVATION이벤트 발행 - 상품 서비스가 이전에 예약한 재고를 원복
- 주문 서비스가
-
재고 업데이트 실패 시:
- 주문 서비스가
CANCEL_PAYMENT이벤트 발행 - 결제 서비스가 처리된 결제를 취소하고 고객 잔액 복원
- 이후 상품 서비스의 재고 예약도 취소
- 주문 서비스가
각 서비스는 자체 데이터베이스 테이블을 가지고 있습니다:
+------------+--------------+------+-----+-------------------+
| 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: 주문 처리 실패
+----------------+--------------+------+-----+---------+
| 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 |
+----------------+--------------+------+-----+---------+
+-----------------+--------------+------+-----+---------+
| Field | Type | Null | Key | Default |
+-----------------+--------------+------+-----+---------+
| customer_id | varchar(255) | NO | PRI | NULL |
| available_amount| decimal(19,2)| NO | | NULL |
+-----------------+--------------+------+-----+---------+
+-------------+--------------+------+-----+-------------------+
| 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: 보상 트랜잭션으로 취소됨
+------------+--------------+------+-----+-------------------+
| 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: 보상 트랜잭션으로 취소됨
시스템은 다음 Kafka 토픽을 사용하여 이벤트를 교환합니다:
| 토픽명 | 이벤트 생성 서비스 | 이벤트 소비 서비스 | 역할 및 설명 |
|---|---|---|---|
| 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 | - | 주문 취소 이벤트 (보상 트랜잭션 완료) |
order-created → product-reserved → payment-processed → inventory-updated → order-completed
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 → (주문 실패)
주문 생성 및 조회 API 제공
주문 엔티티 관리 및 주문 생성 시 이벤트 발행
사가 오케스트레이션 담당, 각 이벤트에 따른 흐름 제어 및 보상 트랜잭션 관리
Kafka 토픽에서 이벤트 수신 및 처리
상품 CRUD API 제공
상품 재고 예약 및 취소 로직, 이벤트 발행
주문 생성 및 취소 이벤트 수신 처리
결제 처리 및 취소 로직, 고객 잔액 관리
고객 잔액 CRUD 및 잔액 차감/환불 기능
결제 관련 이벤트 수신 및 처리
재고 업데이트 및 복원 기능
재고 관련 이벤트 수신 및 처리
- JDK 17 이상
- Docker 및 Docker Compose
- Gradle
./gradlew clean builddocker-compose up -d- Order Service: http://localhost:8082
- Product Service: http://localhost:8081
- Payment Service: http://localhost:8083
- Inventory Service: http://localhost:8084
- Kafka UI: http://localhost:8989
- 상품 등록:
curl --location 'http://localhost:8081/api/products' \
--header 'Content-Type: application/json' \
--data '{
"id": "prod-001",
"name": "테스트 상품",
"description": "테스트용 상품입니다.",
"price": 1000,
"stockQuantity": 10
}'- 주문 생성 (성공 케이스):
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)
curl --location 'http://localhost:8082/api/orders' \
--header 'Content-Type: application/json' \
--data '{
"customerId": "customer1",
"productId": "prod-001",
"quantity": 20,
"amount": 20000
}'결과:
- 주문 상태:
FAILED - 상품 재고: 변동 없음
- 고객 잔액: 변동 없음
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)
이 프로젝트는 오케스트레이션 방식의 사가 패턴을 구현하여 마이크로서비스 환경에서 분산 트랜잭션의 일관성을 유지하는 방법을 보여줍니다. 향후 개선 가능한 영역:
- 실시간 모니터링: 사가 진행 상황을 실시간으로 모니터링하는 대시보드
- 타임아웃 처리: 장기 실행 트랜잭션에 대한 타임아웃 메커니즘
- 멱등성 보장: 중복 이벤트 처리에 대한 안전장치
- 코레오그래피 방식 구현: 비교를 위한 코레오그래피 방식 구현
MIT