주의 : 전체적인 MSA 학습을 위한 프로젝트이며 디테일한 기능구현에서 미흡함이 있습니다.
- 모놀리식 구조를 8개의 마이크로서비스로 분리한 E-Commerce 시스템 구현
- Apache Kafka 기반 Event-Driven Architecture로 서비스 간 비동기 통신
- Saga 패턴을 통한 분산 트랜잭션 처리 및 보상 트랜잭션 구현
- Redis 분산 락을 활용한 동시성 제어 및 재고 관리
- OpenFeign을 통한 마이크로서비스 간 동기 통신 구현
- Docker 컨테이너화 및 Docker Compose를 통한 로컬 개발 환경 구축
- Micrometer Tracing + Zipkin으로 분산 추적 환경 구축, 서비스 간 호출 흐름 시각화
┌─────────────────────────────────────────────────────────────────┐
│ API Gateway (8080) │
│ (Spring Cloud Gateway) │
└────────────────────────┬────────────────────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│User Service │ │Order Service │ │Product Svc │
│ (8081) │◄─│ (8082) │◄─│ (8087) │
└──────────────┘ └──────┬───────┘ └──────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│Inventory Svc │ │Payment Svc │ │Delivery Svc │
│ (8084) │ │ (8085) │ │ (8086) │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐
│Notification │
│Service (8088)│
└──────────────┘
┌────────────────────────────────┐
│ Apache Kafka (9092) │
│ - order-created │
│ - inventory-reserved │
│ - payment-completed │
│ - delivery-started │
└────────────────────────────────┘
┌────────────────────────────────┐
│ Redis (6379) │
│ - 분산 락 (재고 동시성 제어) │
└────────────────────────────────┘
- 동기 통신 (OpenFeign): Order → User, Order → Product (가격 검증)
- 비동기 통신 (Kafka): Saga 패턴 기반 이벤트 체인
- 분산 락 (Redis): 재고 차감 시 동시성 제어
- Circuit Breaker: User Service 장애 시 fallback 동작
- 분산 추적 (Zipkin): 서비스 간 호출 흐름 시각화
- Java 21, Spring Boot 3.1.5
- Spring Cloud 2022.0.4: Gateway, OpenFeign
- Apache Kafka: 비동기 메시징 (Event-Driven Architecture)
- Redis: 분산 락 (Redisson)
- Resilience4j: Circuit Breaker, Fallback
- Micrometer Tracing + Brave: 분산 추적
- Zipkin: 트레이싱 서버
- MySQL: RDBS
- Docker: 컨테이너화
- Docker Compose: 로컬 개발 환경
- Kubernetes: 프로덕션 배포 (예정)
- 사용자 정보 관리
- 사용자 검증 API 제공
- H2 in-memory database
- 주문 생성 및 관리
- Saga 오케스트레이터 역할
- User/Product Service 호출 (OpenFeign)
- Kafka 이벤트 발행: order-created
- 상품 정보 관리 (카탈로그)
- 가격 정보 제공 (서버 측 가격 검증)
- 카테고리별 상품 조회
- 주문 시점 스냅샷 데이터 제공
- 재고 관리 및 확보
- Redis 분산 락 기반 동시성 제어
- Saga 보상 트랜잭션 구현 (재고 복구)
- Kafka 이벤트 처리: order-created → inventory-reserved
- 결제 처리 (시뮬레이션)
- Kafka 이벤트 처리: inventory-reserved → payment-completed
- 배송 시작 처리
- Kafka 이벤트 처리: payment-completed → delivery-started
- 알림 발송 (이메일, SMS 시뮬레이션)
- Kafka 이벤트 구독: order-created, delivery-started, delivery-completed
- 단일 진입점 (Single Entry Point)
- 라우팅 및 로드 밸런싱
- 인증/인가 (예정)
E-Commerce의 주문 프로세스를 Choreography 기반 Saga로 구현
정상 플로우:
Order Created → Inventory Reserved → Payment Completed → Delivery Started → Completed
보상 트랜잭션 플로우 (결제 실패 시):
Order Created → Inventory Reserved → Payment Failed
↓ ↓
재고 복구 (Release) Order Cancelled
구현 상세:
- Kafka 이벤트 체인으로 각 단계 연결
PaymentFailedEvent에productId,quantity포함하여 재고 복구 가능- Inventory Service가
payment-failed토픽을 구독하여 자동 롤백
재고 차감 시 Race Condition 방지
문제 시나리오:
- 100명이 마지막 1개 재고 동시 주문 시 음수 재고 발생
해결 방법:
- Redisson 기반 분산 락 구현
@DistributedLock커스텀 어노테이션 + AOP- SpEL 표현식으로 상품별 동적 락 키 생성 (
inventory:lock:#productId) - Lock 타임아웃: 5초, Lease Time: 3초 (데드락 방지)
효과:
- 동시 요청 시 1개만 성공, 나머지는 "재고 부족" 응답
- 재고 음수 발생 방지
클라이언트가 가격을 조작할 수 없도록 서버에서 가격 계산
Before (취약점):
POST /orders
{
"userId": 1,
"productName": "MacBook Pro",
"price": 1, // 클라이언트가 1원으로 조작 가능!
"quantity": 1
}After (보안 강화):
POST /orders
{
"userId": 1,
"productId": 1, // 상품 ID만 전달
"quantity": 1
}
// 서버가 Product Service에서 실제 가격(3,500,000원)을 조회하여 계산- 8개 독립 서비스로 분리 (User, Order, Product, Inventory, Payment, Delivery, Notification, Gateway)
- Database per Service 패턴 (각 서비스 독립 DB)
- RESTful API 설계
- 단일 진입점을 통한 라우팅
- 서비스별 로드 밸런싱
- 인증/인가 처리 (예정)
- 동기 통신 (OpenFeign): 즉시 응답 필요 (사용자 검증, 가격 조회)
- 비동기 통신 (Kafka): 이벤트 기반 처리 (알림, Saga 플로우)
- Service Discovery: Kubernetes Service DNS (운영), URL 직접 지정 (로컬)
- Circuit Breaker: 장애 전파 차단
- Fallback: User Service 장애 시 기본값 반환
- 설정: 10번 중 50% 실패 시 Circuit Open
- Trace ID/Span ID: 서비스 간 요청 흐름 추적
- B3 Propagation: OpenFeign 호출 시 trace context 자동 전파
- Zipkin UI: 서비스 의존성 그래프 및 성능 시각화
주문 시점의 가격을 저장하여 이후 가격 변동에 영향받지 않도록 처리
Order Entity:
productId: Product Service 참조 (현재 정보 조회 가능)productName: 주문 시점 스냅샷 (상품명 변경 시에도 유지)unitPrice: 주문 시점 단가 (BigDecimal)totalPrice: 서버 계산 총액 (단가 × 수량)
- User/Order 서비스 분리
- Spring Cloud Gateway 구축
- Circuit Breaker 적용
- Micrometer + Zipkin 분산 추적
- Product Service 신규 구축: 서버 측 가격 검증 보안 강화
- Inventory Service 리팩토링: productName → productId 기반으로 전환
- Payment/Delivery Service 추가: 전체 주문 프로세스 구현
- Saga 패턴 구현: Choreography 방식의 분산 트랜잭션
- Redis 분산 락: 동시성 제어 및 재고 무결성 보장
- Notification Service: 비동기 알림 발송
- Kubernetes 배포
- Outbox Pattern (트랜잭셔널 메시징)
- 멱등성 처리 (Idempotency)
- API 인증/인가 (JWT, OAuth2)
- 모니터링 (Prometheus, Grafana)
- CI/CD 파이프라인
# 1. 전체 빌드
./gradlew clean build -x test
# 2. Docker Compose로 전체 시스템 시작
docker-compose up -d --build
# 3. 서비스 상태 확인
docker-compose ps
# 4. 특정 서비스 로그 확인
docker-compose logs -f order-service
docker-compose logs -f inventory-service
# 5. 종료
docker-compose down- API Gateway: http://localhost:8080
- User Service: http://localhost:8081
- Order Service: http://localhost:8082
- Inventory Service: http://localhost:8084
- Payment Service: http://localhost:8085
- Delivery Service: http://localhost:8086
- Product Service: http://localhost:8087
- Notification Service: http://localhost:8088
- Zipkin UI: http://localhost:9411
- Kafka UI: http://localhost:9000 (Kafdrop)
# 1. 상품 조회
curl http://localhost:8087/products | jq
# 2. 특정 상품 상세 정보
curl http://localhost:8087/products/1 | jq
# Response: {"id":1,"name":"MacBook Pro 16","price":3500000,...}
# 3. 주문 생성 (productId만 전달, 가격은 서버에서 계산!)
curl -X POST http://localhost:8082/orders \
-H "Content-Type: application/json" \
-d '{
"userId": 1,
"productId": 1,
"quantity": 1
}' | jq
# 4. 사용자별 주문 조회
curl "http://localhost:8082/orders?userId=1" | jq
# 5. 재고 확인
curl http://localhost:8084/inventory/1 | jq# Terminal 1: Order Service 로그
docker-compose logs -f order-service
# Terminal 2: Inventory Service 로그
docker-compose logs -f inventory-service
# Terminal 3: Payment Service 로그
docker-compose logs -f payment-service
# Terminal 4: Delivery Service 로그
docker-compose logs -f delivery-service
# Terminal 5: Notification Service 로그
docker-compose logs -f notification-service
# Terminal 6: 주문 생성
curl -X POST http://localhost:8082/orders \
-H "Content-Type: application/json" \
-d '{"userId": 1, "productId": 1, "quantity": 1}'
# 로그에서 Saga 이벤트 체인 확인:
# Order → order-created 발행
# Inventory → 재고 확보 → inventory-reserved 발행
# Payment → 결제 처리 → payment-completed 발행
# Delivery → 배송 시작 → delivery-started 발행
# Notification → 각 단계마다 알림 발송# 시나리오: 100명이 마지막 1개 재고 동시 주문
# 1. 재고 확인
curl http://localhost:8084/inventory/1 | jq
# {"productId":1,"quantity":1}
# 2. 100개 동시 요청 발생
for i in {1..100}; do
curl -X POST http://localhost:8082/orders \
-H "Content-Type: application/json" \
-d '{"userId":1,"productId":1,"quantity":1}' &
done
wait
# 3. 결과 확인
# - 1개 주문만 성공 (200 OK)
# - 99개 주문 실패 (재고 부족)
# - 재고: 0개 (음수 아님!)
# 4. Redis 락 상태 확인
docker exec -it redis redis-cli
> KEYS inventory:lock:*
> TTL inventory:lock:1 # 락이 자동으로 해제되었는지 확인# 시나리오: 재고 부족으로 주문 실패 시 정상 롤백
# 1. 재고 확인
curl http://localhost:8084/inventory/1 | jq
# {"productId":1,"quantity":5}
# 2. 재고보다 많은 수량 주문
curl -X POST http://localhost:8082/orders \
-H "Content-Type: application/json" \
-d '{"userId":1,"productId":1,"quantity":999}' | jq
# 3. 로그 확인
docker-compose logs inventory-service | grep "재고 부족"
# ⚠️ 재고 부족 - productId: 1, 요청: 999, 현재: 5
# 📤 inventory-failed 이벤트 발행
docker-compose logs order-service | grep "주문 취소"
# ❌ 주문 취소 처리 - orderId: X
# 4. 재고 재확인 (변동 없어야 함)
curl http://localhost:8084/inventory/1 | jq
# {"productId":1,"quantity":5} ← 원상태 유지 ✅# 1. 주문 생성
curl -X POST http://localhost:8082/orders \
-H "Content-Type: application/json" \
-d '{"userId":1,"productId":1,"quantity":1}'
# 2. Zipkin UI 접속
open http://localhost:9411
# 3. 확인 사항:
# - Service Name: order-service 선택
# - Find Traces 클릭
# - 서비스 간 호출 흐름: Order → User → Product → Inventory
# - 각 서비스의 응답 시간 확인
# - 동일 Trace ID로 연결된 Span 확인- 문제: 클라이언트가 가격을 조작하여 1원에 상품 구매 가능
- 해결: Product Service 신규 구축 + 서버 측 가격 검증
- 학습: 금액 관련 데이터는 절대 클라이언트 입력을 믿으면 안 됨
- 문제: 100명이 동시 주문 시 재고 음수 발생
- 해결: Redis 분산 락 (Redisson) + AOP 기반 커스텀 어노테이션
- 학습: 단일 서버 락(@Synchronized)은 MSA에서 무용지물, 분산 락 필수
- 문제: 결제 실패 시 이미 차감된 재고 복구 방법 부재
- 해결: Kafka 이벤트에 productId/quantity 포함 + Compensating Transaction
- 학습: 분산 트랜잭션은 2PC가 아닌 Saga 패턴으로 해결
- 문제: 문자열 기반 productName으로 오타 발생 (재고 조회 실패)
- 해결: productId 기반 아키텍처 전환 (Product Service와 1:1 매칭)
- 학습: 외래키 대신 ID 참조로 서비스 간 느슨한 결합 유지
- 문제: OpenFeign 호출 시 trace context가 자동 전파되지 않음
- 해결:
feign-micrometer의존성 추가 + B3 Propagation 설정 - 학습: Spring Boot 3.x는 Sleuth가 제거되어 Micrometer Tracing으로 전환
- 장점: 서비스 독립성, 기술 스택 자유도
- 단점: JOIN 불가, 데이터 중복 (스냅샷 패턴으로 해결)
- 학습: 주문 시점의 상품명/가격을 Order 테이블에 저장 (가격 변동 영향 없음)
MSA-SpringCloud-Kubernetes/
├── common/ # 공통 이벤트 클래스 (Kafka)
│ └── src/main/java/com/example/common/event/
│ ├── OrderCreatedEvent.java
│ ├── InventoryReservedEvent.java
│ ├── PaymentCompletedEvent.java
│ └── DeliveryStartedEvent.java
├── user-service/ # 사용자 관리 (8081)
├── order-service/ # 주문 관리 (8082)
│ ├── client/
│ │ ├── UserClient.java # OpenFeign
│ │ └── ProductClient.java # OpenFeign
│ └── kafka/
│ └── SagaEventConsumer.java
├── product-service/ # 상품 관리 (8087) ⭐ 신규
│ ├── entity/Product.java
│ ├── dto/ProductResponse.java
│ └── controller/ProductController.java
├── inventory-service/ # 재고 관리 (8084)
│ ├── annotation/
│ │ └── DistributedLock.java # 커스텀 어노테이션
│ ├── aop/
│ │ └── DistributedLockAop.java
│ ├── config/
│ │ └── RedisConfig.java
│ └── service/
│ └── InventoryService.java
├── payment-service/ # 결제 처리 (8085)
├── delivery-service/ # 배송 관리 (8086)
├── notification-service/ # 알림 발송 (8088)
├── gateway/ # API Gateway (8080)
├── docs/ # 프로젝트 문서
│ ├── E-Commerce-Production-Improvements.md
│ ├── resilience4j-patterns.md
│ ├── Circuit-Breaker-QNA.md
│ └── Zipkin-Distributed-Tracing.md
├── docker-compose.yml # 로컬 개발 환경
└── README.md
- 이슈 제기: GitHub Issues
- 개선 제안: Pull Request
- 질문: Discussions