
RabbitMQ를 사용해 Choreography 기반 Saga 패턴의 보상 트랜잭션 동작 방식을 학습하고 구현한 기록입니다.
@Transactional로 묶을 수 없습니다.
order서비스에서product-q로 상품 수량 차감을 요청하고,product서비스 성공 시payment-q로 결제를 진행합니다.

실패 시 역방향 보상 메시지(
product-err-q,order-err-q)를 발행해 트랜잭션을 취소합니다.

| 큐 이름 | 발행 (Producer) | 소비 (Consumer) | 역할 |
|---|---|---|---|
| product-q | order | product | 재고 차감 요청 |
| payment-q | product | payment | 결제 요청 |
| product-err-q | payment | product | 재고 복구 (보상) |
| order-err-q | product | order | 주문 취소 (보상) |
product 서비스는 재고 차감 성공 시 다음 단계(payment-q)로 이벤트를 넘기고, 예외 발생 시 보상 이벤트(order-err-q)를 발행합니다.
@Slf4j
@Component
@RequiredArgsConstructor
public class ProductConsumer {
private final ProductService productService;
private final RabbitTemplate rabbitTemplate;
@RabbitListener(queues = "product-q")
public void handleOrder(OrderMessage message) {
try {
// 로컬 트랜잭션: 재고 차감
productService.decreaseStock(message.getProductId(), message.getQuantity());
// 다음 단계 진행 (결제 요청)
rabbitTemplate.convertAndSend("saga-exchange", "payment", message);
} catch (Exception e) {
log.error("재고 차감 실패 - orderId: {}", message.getOrderId(), e);
// 보상 트랜잭션 발행 (주문 취소 요청)
rabbitTemplate.convertAndSend("saga-exchange", "order.err", message);
}
}
}
보상 트랜잭션조차 실패하는 상황에 대비하기 위해 다음과 같은 설계 전략을 적용합니다.
PENDING(처리 중)으로 유지합니다.catch하여 보상 메시지를 발행하고, 보상 흐름 자체의 실패는 예외를 던져 RabbitMQ의 Retry/DLQ로 넘기는 메커니즘을 명확히 구분하게 되었습니다.