Problem
Problem
주문, 결제, 재고, 정산이 서비스와 저장소로 분리되면 성공 흐름보다 중간 실패와 보상 흐름을 먼저 설계해야 했습니다.
Decision
Decision
2PC 대신 SAGA와 Outbox를 사용해 즉시 일관성보다 재시도, 보상, 검증 가능한 E2E 흐름을 우선했습니다.
Build
Implementation
- 재고 예약, 결제, 주문 저장을 SAGA 단계로 나누고 실패 지점별 보상 호출을 정의했습니다.
- 결제 성공 후 주문 저장 실패처럼 이벤트 유실이 치명적인 구간은 Outbox 테이블과 폴링 프로세서로 분리했습니다.
- Docker Compose, Helm, GitHub Actions E2E 스크립트를 같이 두어 로컬과 CI에서 같은 흐름을 검증할 수 있게 했습니다.
MSA는 서비스 개수를 늘리는 작업이 아니라 실패를 되돌리는 절차와 자동 검증 경로를 함께 만드는 작업이라는 점을 정리했습니다.
Proof
Verification
docs/IMPLEMENTED-SUMMARY.md와 e2e-all-scenarios.sh, Helm 차트 링크를 기준으로 6개 서비스 구성, Gateway 기준 E2E 스크립트, Helm 배포 템플릿을 확인했습니다.
Gateway를 포함한 주문·결제·재고 분산 시스템 구성
Gateway 기준 주문·정산·실패 케이스 검증 스크립트
Kubernetes 배포 구성을 템플릿으로 문서화
E2E scripts
분산 흐름 검증
- Scenario
- 인증, 주문, 장바구니, 실패 케이스, 정산 흐름
- Method
- GitHub Actions에서 서비스 기동 후 E2E 스크립트 실행
- Result
- Gateway 기준 통합 스크립트로 성공·실패 경로를 검증할 수 있게 구성했습니다.
Helm chart
배포 구성
- Scenario
- Kubernetes 배포 템플릿 관리
- Method
- 서비스별 Deployment, Service, ConfigMap, HPA를 Helm으로 분리
- Result
- 로컬 Compose와 Kubernetes 배포 템플릿을 함께 구성했습니다.
Boundaries
Trade-offs & Limitations
Trade-offs
- SAGA/Outbox는 분산 트랜잭션을 현실적으로 다루지만 즉시 일관성보다 보상과 재처리 흐름이 중요해집니다.
- 서비스를 6개로 나누면 책임은 명확해지지만 로컬 기동과 테스트 비용이 늘어납니다.
Limitations
- 실제 PG, 물류, 정산 외부 연동은 모의 도메인으로 대체했습니다.
- 운영 클러스터가 아닌 로컬·CI 기반 검증 결과입니다.
아키텍처
전체 아키텍처▼
architecture
전체 아키텍처
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
Client
사용자 진입과 실시간 상태 확인
Web / App
Application
API, 도메인 로직, 워커 처리
API Gateway (8080) routing / JWT / Rate Limit / Swagger
user-service (8081) user / login / JWT
Services
order-service (8083) order / cart / SAGA / Outbox
Services
payment-service (8084) payment / cancel
Services
settlement-service (8085) daily / monthly settlement
Services
Services
Data / Messaging
상태 저장, 캐시, 이벤트 전달
product-service (8082) product / stock / cache
Services
RabbitMQ payment.events
Services
userdb
Storage
productdb
Storage
Redis product cache
Storage
orderdb
Storage
paymentdb
Storage
settlementdb
Storage
Ops
관측성, 배포, 운영 보조
Prometheus
Monitoring
Grafana
Monitoring
Zipkin
Monitoring
핵심 연결 흐름
- 1. Web / App → API Gateway (8080) routing / JWT / Rate Limit / Swagger · HTTP Bearer JWT
- 2. API Gateway (8080) routing / JWT / Rate Limit / Swagger → user-service (8081) user / login / JWT · /users /auth
- 3. API Gateway (8080) routing / JWT / Rate Limit / Swagger → product-service (8082) product / stock / cache · /products
- 4. API Gateway (8080) routing / JWT / Rate Limit / Swagger → order-service (8083) order / cart / SAGA / Outbox · /orders /cart
- 5. API Gateway (8080) routing / JWT / Rate Limit / Swagger → settlement-service (8085) daily / monthly settlement · /settlements
- 6. order-service (8083) order / cart / SAGA / Outbox → payment-service (8084) payment / cancel · REST payment / cancel
- 7. order-service (8083) order / cart / SAGA / Outbox → product-service (8082) product / stock / cache · REST stock reserve / release
- 8. payment-service (8084) payment / cancel → RabbitMQ payment.events · payment.completed event
- 9. RabbitMQ payment.events → settlement-service (8085) daily / monthly settlement · subscription
- 10. user-service (8081) user / login / JWT → userdb
- 11. product-service (8082) product / stock / cache → productdb
- 12. product-service (8082) product / stock / cache → Redis product cache
- 13. order-service (8083) order / cart / SAGA / Outbox → orderdb
- 14. payment-service (8084) payment / cancel → paymentdb
- 15. settlement-service (8085) daily / monthly settlement → settlementdb
- 16. Services → Zipkin · traces
- 17. Services → Prometheus · metrics
- 18. Prometheus → Grafana
원본 Mermaid 보기
flowchart TB
Client["Web / App"]
Gateway["API Gateway (8080)<br/>routing / JWT / Rate Limit / Swagger"]
subgraph Monitoring["Monitoring"]
Prometheus["Prometheus"]
Grafana["Grafana"]
Zipkin["Zipkin"]
end
subgraph Services["Microservices"]
User["user-service (8081)<br/>user / login / JWT"]
Product["product-service (8082)<br/>product / stock / cache"]
Order["order-service (8083)<br/>order / cart / SAGA / Outbox"]
Payment["payment-service (8084)<br/>payment / cancel"]
Settlement["settlement-service (8085)<br/>daily / monthly settlement"]
Rabbit["RabbitMQ<br/>payment.events"]
end
subgraph Storage["Data Storage (independent DB per service)"]
UserDb[("userdb")]
ProductDb[("productdb")]
Redis[("Redis<br/>product cache")]
OrderDb[("orderdb")]
PaymentDb[("paymentdb")]
SettlementDb[("settlementdb")]
end
Client -->|HTTP Bearer JWT| Gateway
Gateway -->|/users /auth| User
Gateway -->|/products| Product
Gateway -->|/orders /cart| Order
Gateway -->|/settlements| Settlement
Order -->|REST payment / cancel| Payment
Order -->|REST stock reserve / release| Product
Payment -->|payment.completed event| Rabbit
Rabbit -->|subscription| Settlement
User --> UserDb
Product --> ProductDb
Product --> Redis
Order --> OrderDb
Payment --> PaymentDb
Settlement --> SettlementDb
Services -. traces .-> Zipkin
Services -. metrics .-> Prometheus
Prometheus --> GrafanaERD
전체 ERD▼
erd
전체 ERD
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
users
- bigint
- id
- PK
- string
- UK
- string
- password
- string
- name
products
- bigint
- id
- PK
- string
- name
- string
- category
- int
- price
- int
- stock_quantity
cart_items
- bigint
- id
- PK
- bigint
- user_id
- FK
- bigint
- product_id
- FK
- int
- quantity
orders
- bigint
- id
- PK
- bigint
- user_id
- FK
- bigint
- product_id
- FK
- int
- quantity
- int
- total_amount
- string
- status
+ 2개 필드
payments
- bigint
- id
- PK
- bigint
- user_id
- FK
- int
- amount
- string
- payment_method
- string
- status
- datetime
- created_at
outbox_events
- bigint
- id
- PK
- string
- event_type
- text
- payload
- string
- status
- datetime
- created_at
- datetime
- processed_at
daily_settlements
- bigint
- id
- PK
- date
- report_date
- UK
- bigint
- total_amount
- int
- payment_count
monthly_settlements
- bigint
- id
- PK
- string
- settlement_year_month
- UK
- bigint
- total_amount
- int
- payment_count
관계 요약
- #1 users ||--o{ cart_items · owns
- #2 users ||--o{ orders · places
- #3 users ||--o{ payments · makes
- #4 products ||--o{ cart_items · contains
- #5 products ||--o{ orders · contains
- #6 orders ||--|| payments · associated_with
원본 Mermaid 보기
erDiagram
users ||--o{ cart_items : "owns"
users ||--o{ orders : "places"
users ||--o{ payments : "makes"
products ||--o{ cart_items : "contains"
products ||--o{ orders : "contains"
orders ||--|| payments : "associated_with"
users {
bigint id PK
string email UK
string password
string name
}
products {
bigint id PK
string name
string category
int price
int stock_quantity
}
cart_items {
bigint id PK
bigint user_id FK
bigint product_id FK
int quantity
}
orders {
bigint id PK
bigint user_id FK
bigint product_id FK
int quantity
int total_amount
string status
bigint payment_id
datetime created_at
}
payments {
bigint id PK
bigint user_id FK
int amount
string payment_method
string status
datetime created_at
}
outbox_events {
bigint id PK
string event_type
text payload
string status
datetime created_at
datetime processed_at
}
daily_settlements {
bigint id PK
date report_date UK
bigint total_amount
int payment_count
}
monthly_settlements {
bigint id PK
string settlement_year_month UK
bigint total_amount
int payment_count
}시퀀스 다이어그램
시퀀스 다이어그램▼
sequence
시퀀스 다이어그램
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
Participants
- 1요청
Client → Gateway
POST /orders (productId, quantity, paymentMethod)
- 2요청
Gateway → OrderController
X-User-Id + Request
- 3요청
OrderController → OrderService
createOrder(userId, request)
- 4요청
OrderService → ProductService
GET /products/{id} (price lookup)
- 5저장
ProductService → ProductRepository
findById
- 6처리
ProductRepository → ProductService
Product
- 7처리
ProductService → OrderService
Product (price)
- 8요청
OrderService → ProductService
POST /internal/stocks/reserve
+ 13개 단계는 아래 상세 메시지에서 확인할 수 있습니다.
전체 메시지 상세
| Step | From → To | Message | Condition |
|---|---|---|---|
| 1 | Client → Gateway | POST /orders (productId, quantity, paymentMethod) | - |
| 2 | Gateway → OrderController | X-User-Id + Request | - |
| 3 | OrderController → OrderService | createOrder(userId, request) | - |
| 4 | OrderService → ProductService | GET /products/{id} (price lookup) | - |
| 5 | ProductService → ProductRepository | findById | - |
| 6 | ProductRepository → ProductService | Product | - |
| 7 | ProductService → OrderService | Product (price) | - |
| 8 | OrderService → ProductService | POST /internal/stocks/reserve | - |
| 9 | ProductService → ProductRepository | stock lookup, decrease | - |
| 10 | ProductRepository → ProductService | success | - |
| 11 | ProductService → OrderService | { success: true } | - |
| 12 | OrderService → PaymentService | POST /payments (userId, amount, method) | - |
| 13 | PaymentService → PaymentRepository | save | - |
| 14 | PaymentRepository → PaymentService | Payment | - |
| 15 | PaymentService → OrderService | { paymentId, success: true } | - |
| 16 | OrderService → OrderRepository | save(order) | - |
| 17 | OrderRepository → OrderService | Order | - |
| 18 | OrderService → OrderController | OrderResponse | - |
| 19 | OrderController → Gateway | 201 | - |
| 20 | Gateway → Client | OrderResponse | - |
| 21 | Note · OrderService | On failure: SAGA compensation / stock release / outbox recovery | - |
원본 Mermaid 보기
sequenceDiagram
actor Client
participant Gateway
participant OrderController
participant OrderService
participant ProductService
participant ProductRepository
participant PaymentService
participant PaymentRepository
participant OrderRepository
Client->>Gateway: POST /orders (productId, quantity, paymentMethod)
Gateway->>OrderController: X-User-Id + Request
OrderController->>OrderService: createOrder(userId, request)
OrderService->>ProductService: GET /products/{id} (price lookup)
ProductService->>ProductRepository: findById
ProductRepository-->>ProductService: Product
ProductService-->>OrderService: Product (price)
OrderService->>ProductService: POST /internal/stocks/reserve
ProductService->>ProductRepository: stock lookup, decrease
ProductRepository-->>ProductService: success
ProductService-->>OrderService: { success: true }
OrderService->>PaymentService: POST /payments (userId, amount, method)
PaymentService->>PaymentRepository: save
PaymentRepository-->>PaymentService: Payment
PaymentService-->>OrderService: { paymentId, success: true }
OrderService->>OrderRepository: save(order)
OrderRepository-->>OrderService: Order
OrderService-->>OrderController: OrderResponse
OrderController-->>Gateway: 201
Gateway-->>Client: OrderResponse
Note over OrderService,ProductService: On failure: SAGA compensation / stock release / outbox recovery