"주문 서비스, 결제 서비스, 배송 서비스가 모두 다른 DB를 씁니다. 결제는 성공했는데 배송 DB 저장에 실패하면 어떡하죠? 결제도 취소해야 하는데..."
과거의 모놀리식 시스템에서는 DB가 하나라 Transaction.rollback() 한 방이면 끝났습니다. 하지만 MSA에서는 불가능합니다. 그래서 **"실패하면 되돌리는 로직(보상 트랜잭션)을 순차적으로 실행하자"**는 것이 사가 패턴입니다.
이 순서를 누가 관리하느냐에 따라 구현 방식이 완전히 달라집니다.
1. 방식 1: 코레오그래피 (Choreography) - "자율적인 댄서들"
각 서비스가 **이벤트(Event)**를 주고받으며 스스로 다음에 할 일을 결정하는 방식입니다. 중앙 관리자가 없고, 무용수들이 서로 눈빛을 교환하며 춤을 추듯 동작합니다.
특징
- 이벤트 기반 (Pub/Sub): "나 이거 했어!"라고 이벤트를 던지면, 관심 있는 다른 서비스가 받아서 처리합니다.
- 분산된 로직: 트랜잭션의 흐름이 코드 여기저기에 흩어져 있습니다.
코드 예시 (주문 -> 결제 -> 배송)
Java
// 1. 주문 서비스 (Order Service)
public void createOrder(Order order) {
orderRepository.save(order);
// "주문 생성됨" 이벤트 발행
kafkaProducer.send("OrderCreatedEvent", order.getId());
}
// 2. 결제 서비스 (Payment Service)
@KafkaListener(topics = "OrderCreatedEvent")
public void handleOrderCreated(Long orderId) {
try {
paymentService.pay(orderId);
// "결제 완료됨" 이벤트 발행
kafkaProducer.send("PaymentCompletedEvent", orderId);
} catch (Exception e) {
// 실패 시 "결제 실패" 이벤트 발행 -> 주문 서비스가 받아서 주문 취소(보상 트랜잭션) 수행
kafkaProducer.send("PaymentFailedEvent", orderId);
}
}
// 3. 배송 서비스 (Delivery Service)
@KafkaListener(topics = "PaymentCompletedEvent")
public void handlePaymentCompleted(Long orderId) {
deliveryService.ship(orderId);
}
장단점
- 장점: 중앙 병목지점이 없어 구조가 간단하고 서비스 간 결합도가 낮습니다.
- 단점:
- 추적의 어려움: 서비스가 많아지면 "도대체 이 이벤트는 어디서 와서 어디로 가는 거야?"라며 흐름을 파악하기 힘들어집니다.
- 순환 의존성: A -> B -> C -> A 처럼 이벤트가 꼬일 위험이 있습니다.
2. 방식 2: 오케스트레이션 (Orchestration) - "지휘자와 연주자"
중앙에 **오케스트레이터(Saga Manager)**를 두고, 이 매니저가 각 서비스에게 **명령(Command)**을 내리는 방식입니다. 지휘자가 "바이올린 켜!", "첼로 멈춰!"라고 지시하는 것과 같습니다.
특징
- 중앙 집중형: 오케스트레이터가 트랜잭션의 상태와 흐름을 모두 알고 관리합니다.
- 명령 기반 (Req/Res): 이벤트를 구독하는 게 아니라, 매니저가 API를 직접 호출하거나 명령 메시지를 보냅니다.
코드 예시
Java
// 1. 주문 사가 매니저 (Orchestrator)
@Service
public class OrderSagaManager {
public void processOrder(Long orderId) {
try {
// 1단계: 결제 서비스 호출 (명령)
paymentClient.pay(orderId);
// 2단계: 배송 서비스 호출 (명령)
deliveryClient.ship(orderId);
// 성공 처리
confirmOrder(orderId);
} catch (Exception e) {
// 실패 시 중앙에서 보상 트랜잭션(Rollback) 지시
cancelDelivery(orderId); // 배송 취소
cancelPayment(orderId); // 결제 취소
rejectOrder(orderId); // 주문 취소
}
}
}
장단점
- 장점:
- 가시성: 매니저 코드만 보면 전체 업무 흐름이 한눈에 들어옵니다.
- 관리 용이: 서비스 간의 복잡한 관계를 중앙에서 통제하므로 순환 참조를 막기 쉽습니다.
- 단점: 오케스트레이터에 로직이 집중되어 "똑똑한 오케스트레이터와 멍청한 서비스들" 구조가 될 수 있으며, 오케스트레이터가 죽으면 전체가 멈추는 단일 실패 지점(SPOF)이 될 수 있습니다.
3. 실무 비교: 언제 무엇을 쓰는가?
| 구분 | Choreography (이벤트 기반) | Orchestration (매니저 기반) |
| 통신 방식 | 비동기 이벤트 (Event) | 동기/비동기 명령 (Command) |
| 참여자 역할 | 스스로 판단하고 행동 (Smart) | 시키는 대로 행동 (Dumb) |
| 의존성 | 서비스끼리 서로 알고 있음 (Pub/Sub) | 매니저가 모든 서비스를 알고 있음 |
| 복잡도 | 서비스가 적을 때 유리 | 서비스가 많고 흐름이 복잡할 때 유리 |
| 추천 상황 | 간단한 프로세스 (2~4개 서비스) | 복잡한 비즈니스, 장기 실행 트랜잭션 |
결론
서비스가 2~3개 정도로 적고 로직이 단순하다면 **코레오그래피(Choreography)**가 가볍고 좋습니다.
하지만 서비스가 5개 이상 넘어가고, "결제 실패 시 A는 취소하고 B는 알림을 보내고 C는 대기한다" 처럼 보상 로직이 복잡하다면 반드시 **오케스트레이션(Orchestration)**을 도입해야 유지보수 지옥에서 벗어날 수 있습니다. (실무에서는 주로 Netflix Conductor나 Camunda 같은 워크플로우 엔진을 쓰기도 합니다.)
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 서킷 브레이커 패턴(Circuit Breaker Pattern) 완벽 정리 (1) | 2025.12.07 |
|---|---|
| ORM 패턴(ORM Pattern) 완벽 정리 (0) | 2025.12.07 |
| 페이지네이션 패턴(Pagination Pattern) 완벽 정리 (0) | 2025.12.07 |
| 콜백 패턴(Callback Pattern) 총정리: 동기(Blocking) vs 비동기(Non-blocking) (0) | 2025.12.07 |
| 동시성 제어 패턴: 비관적 락(Pessimistic) vs 낙관적 락(Optimistic) (1) | 2025.12.07 |