반응형
Advanced Architecture Series 02
이론에서 실전으로!
Kafka 기반 Saga 패턴 구현 가이드 (2부)
안녕하세요, code-resting입니다. 어제는 Saga 패턴의 이론적 배경을 다뤘습니다. 오늘은 가장 널리 쓰이는 Choreography-based Saga(안무 방식)를 아파치 카프카(Apache Kafka)와 스프링 부트로 직접 구현해 보겠습니다. 시나리오는 간단합니다: [주문 생성] -> [결제 시도] -> [결제 실패 시 주문 취소 보상 트랜잭션 실행]입니다.
1. 비즈니스 프로세스 흐름도
중앙 제어자 없이 각 서비스가 이벤트를 발행하고 구독하며 자신의 책임을 다하는 흐름입니다.
2. 주문 서비스: 주문 생성 및 보상 로직
주문 서비스는 주문을 생성한 뒤 이벤트를 발행하고, 결제 서비스로부터 '실패 이벤트'를 받으면 상태를 CANCELLED로 변경합니다.
@Service
public class OrderSagaService {
private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
// 1. 주문 생성 및 이벤트 발행
public void createOrder(Order order) {
order.setStatus(OrderStatus.PENDING);
orderRepository.save(order);
kafkaTemplate.send("order-events", new OrderEvent(order.getId(), "ORDER_CREATED"));
}
// 2. 보상 트랜잭션: 결제 실패 이벤트 구독
@KafkaListener(topics = "payment-events")
public void handlePaymentEvent(PaymentEvent event) {
if ("PAYMENT_FAILED".equals(event.getType())) {
Order order = orderRepository.findById(event.getOrderId()).orElseThrow();
order.setStatus(OrderStatus.CANCELLED); // 보상 실행
orderRepository.save(order);
log.info("주문 ID {} 보상 트랜잭션 완료: 주문 취소", order.getId());
}
}
}
3. 결제 서비스: 결과 이벤트 발행
결제 서비스는 주문 이벤트를 구독하여 결제를 진행하고, 성공/실패 여부를 다시 Kafka로 알립니다.
@KafkaListener(topics = "order-events")
public void processPayment(OrderEvent event) {
try {
paymentService.pay(event.getOrderId()); // 결제 로직
kafkaTemplate.send("payment-events", new PaymentEvent(event.getOrderId(), "PAYMENT_SUCCESS"));
} catch (Exception e) {
// 실패 시 실패 이벤트 발행 -> 주문 서비스가 이를 받아 보상 실행
kafkaTemplate.send("payment-events", new PaymentEvent(event.getOrderId(), "PAYMENT_FAILED"));
}
}
⚠️ 실무 팁: Transactional Outbox 패턴
DB 업데이트는 성공했는데 Kafka 메시지 전송만 실패하면 어떻게 할까요? 이를 방지하기 위해 DB에 메시지를 먼저 저장하고 별도 프로세스가 Kafka로 보내는 Outbox 패턴을 함께 사용하는 것이 2026년 표준 아키텍처입니다.
💡 결론: 완벽한 정합성 대신 유연한 복구를
Saga 패턴은 구현이 복잡하고 테스트가 어렵지만, 서비스 간의 강한 결합을 끊어내고 확장성을 확보할 수 있는 유일한 대안입니다. 데이터가 찰나의 순간 불일치할 수 있음을 인정하고, 이를 어떻게 우아하게 '복구'할 것인지 고민하는 개발자가 진정한 시니어 백엔드 개발자입니다.
반응형
'아키텍처' 카테고리의 다른 글
| 서버 한 대로 안 될 때: 로드밸런서(L4 vs L7)와 Nginx 리버스 프록시 완벽 이해 (0) | 2026.03.12 |
|---|---|
| MSA에서 겹치지 않는 ID 만들기: Twitter Snowflake 알고리즘의 원리와 구현 (0) | 2026.03.06 |
| MSA의 거대한 장벽: 분산 트랜잭션, 왜 2PC 대신 Saga 패턴인가? (0) | 2026.03.02 |
댓글