SAGA는 두 가지 전략으로 동작합니다.
예시 주문 트랜잭션
Order Service에서 주문 생성 이벤트 발행 (OrderCreated)
Payment Service가 이를 받아 결제 승인 (PaymentApproved)
Inventory Service가 재고 차감 (InventoryUpdated)
실패하면 보상 이벤트(OrderCancelled)를 발행하여 롤백
✅ 장점
서비스 간 느슨한 결합 (각 서비스가 독립적으로 동작)
확장성(새로운 서비스 추가가 쉬움)
❌ 단점
서비스 간 이벤트 흐름이 복잡해질 수 있음
디버깅과 트랜잭션 추적이 어려움
예시 주문 트랜잭션
SAGA Coordinator가 Order Service에 주문 생성 요청
Order Service가 성공하면 Payment Service에 결제 요청
Payment Service가 성공하면 Inventory Service에 재고 차감 요청
어느 한 단계라도 실패하면, 이전 서비스들에 보상 트랜잭션 수행
예: 결제 성공 후 재고 부족 → Payment Service에 결제 취소 요청
✅ 장점
트랜잭션 흐름이 명확하고 관리가 쉬움
디버깅 및 모니터링이 용이
❌ 단점
중앙 SAGA Coordinator에 의존 → 단일 장애점(Single Point of Failure, SPOF) 위험
서비스 간 결합도가 증가할 가능성이 있음
(1) @Transactional을 활용한 트랜잭션 처리 (Repository 간 트랜잭션)
JPA 기반 Repository는 @Transactional을 활용하여 자동 롤백이 가능합니다.
@Service
public class OrderService {
@Autowired private OrderRepository orderRepository;
@Autowired private PaymentRepository paymentRepository;
@Transactional
public void processOrder(Order order) {
orderRepository.save(order); // 주문 저장
paymentRepository.save(order.getPayment()); // 결제 저장
// 만약 여기서 예외 발생 시, 두 작업 모두 롤백됨
}
}
(2) 외부 API와 함께 사용할 때는 보상 트랜잭션 적용
ex) 주문 저장 + 결제 API 호출
@Service
public class OrderService {
@Autowired private OrderRepository orderRepository;
@Autowired private PaymentService paymentService;
public void processOrder(Order order) {
try {
orderRepository.save(order); // 1. 주문 저장
paymentService.processPayment(order); // 2. 외부 결제 API 호출
} catch (Exception e) {
compensateOrder(order); // 실패 시 보상 트랜잭션 실행
}
}
private void compensateOrder(Order order) {
orderRepository.delete(order); // 주문 취소 (보상 트랜잭션)
}
}
(3) 이벤트 기반 SAGA 방식 적용 (Kafka, RabbitMQ 활용)
만약 하나의 서비스 내에서 여러 개의 API와 DB 트랜잭션을 관리해야 한다면, 이벤트 기반으로 관리 가능
Kafka 같은 메시지 큐를 사용하여 비동기 방식으로 트랜잭션을 보상 처리
ex)
@Service
public class OrderService {
@Autowired private KafkaTemplate<String, String> kafkaTemplate;
@Autowired private OrderRepository orderRepository;
public void processOrder(Order order) {
orderRepository.save(order);
kafkaTemplate.send("order_created", order.getId().toString()); // 이벤트 발행
}
}
@Component
@KafkaListener(topics = "order_created")
public class PaymentService {
@Autowired private PaymentRepository paymentRepository;
public void processPayment(String orderId) {
try {
paymentRepository.save(new Payment(orderId));
} catch (Exception e) {
kafkaTemplate.send("order_failed", orderId); // 실패 시 보상 이벤트 발행
}
}
}
@Component
@KafkaListener(topics = "order_failed")
public class CompensationService {
@Autowired private OrderRepository orderRepository;
public void compensateOrder(String orderId) {
orderRepository.deleteById(orderId); // 보상 트랜잭션 실행
}
}
@Transactional DB의 여러 Repository를 한 트랜잭션으로 처리할 때
이벤트 기반 처리 (Kafka, RabbitMQ) API & DB 작업이 많고 비동기 처리가 필요한 경우
💡결론
단일 서비스 내에서 여러 개의 Repository를 다룰 때는 @Transactional을 사용하자.
외부 API가 포함될 경우 보상 트랜잭션(Compensating Transaction)을 구현하자.
비동기 이벤트 기반 처리가 필요하면 Kafka를 활용하여 SAGA 패턴과 유사한 흐름을 만들자.