SAGA 패턴의 동작 방식

hye_·2025년 3월 27일
0

Pattern

목록 보기
1/1
post-thumbnail

SAGA는 두 가지 전략으로 동작합니다.

1. Choreography (코레오그래피) 방식

  • 이벤트 기반으로 서비스들이 서로 통신하면서 트랜잭션을 관리하는 방식
  • 중앙 컨트롤러 없이 각 서비스가 이벤트를 보고 반응

예시 주문 트랜잭션

Order Service에서 주문 생성 이벤트 발행 (OrderCreated)

Payment Service가 이를 받아 결제 승인 (PaymentApproved)

Inventory Service가 재고 차감 (InventoryUpdated)

실패하면 보상 이벤트(OrderCancelled)를 발행하여 롤백

✅ 장점

서비스 간 느슨한 결합 (각 서비스가 독립적으로 동작)

확장성(새로운 서비스 추가가 쉬움)

❌ 단점

서비스 간 이벤트 흐름이 복잡해질 수 있음

디버깅과 트랜잭션 추적이 어려움

2. Orchestration (오케스트레이션) 방식

  • 중앙에서 하나의 SAGA Coordinator(조정자)가 전체 트랜잭션을 관리
  • 중앙 컨트롤러가 서비스 호출을 순차적으로 수행하고, 실패 시 롤백을 실행

예시 주문 트랜잭션

SAGA Coordinator가 Order Service에 주문 생성 요청

Order Service가 성공하면 Payment Service에 결제 요청

Payment Service가 성공하면 Inventory Service에 재고 차감 요청

어느 한 단계라도 실패하면, 이전 서비스들에 보상 트랜잭션 수행

예: 결제 성공 후 재고 부족 → Payment Service에 결제 취소 요청

✅ 장점

트랜잭션 흐름이 명확하고 관리가 쉬움

디버깅 및 모니터링이 용이

❌ 단점

중앙 SAGA Coordinator에 의존 → 단일 장애점(Single Point of Failure, SPOF) 위험

서비스 간 결합도가 증가할 가능성이 있음

단일 서비스에서 SAGA 패턴을 유사하게 적용하는 방법

(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()); // 결제 저장
        // 만약 여기서 예외 발생 시, 두 작업 모두 롤백됨
    }
}
  • 이 방식은 같은 DB 내의 여러 Repository를 조작하는 경우 적합
  • 예외 발생 시 전체 트랜잭션이 롤백됨
    ❌ 외부 API 호출이 포함될 경우에는 적용되지 않음

(2) 외부 API와 함께 사용할 때는 보상 트랜잭션 적용

  • @Transactional은 DB 내 트랜잭션만 관리 가능하므로 외부 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); // 주문 취소 (보상 트랜잭션)
    }
}
  • 주문이 저장된 후 결제 API 호출 중 실패하면 주문을 취소하는 "보상 트랜잭션" 실행
    ❌ 하지만, 이 방식은 여러 서비스 간의 비동기 이벤트 흐름을 관리하는 것이 아님

(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); // 보상 트랜잭션 실행
    }
}
  • Kafka를 활용하여 SAGA 패턴처럼 보상 트랜잭션을 수행
  • 비동기 이벤트 기반으로 처리 가능
    ❌ 구현이 복잡하고 메시지 큐를 관리해야 함
  1. 언제 어떤 방법을 쓰는게 좋을지

@Transactional DB의 여러 Repository를 한 트랜잭션으로 처리할 때

  • 간단하고 안정적 외부 API 포함 시 트랜잭션 관리 불가
  • 보상 트랜잭션 (Compensating Transaction) 외부 API가 포함된 트랜잭션 처리 시 예외 발생 시 롤백 가능 수동으로 롤백 로직을 구현해야 함

이벤트 기반 처리 (Kafka, RabbitMQ) API & DB 작업이 많고 비동기 처리가 필요한 경우

  • 비동기 트랜잭션 관리 가능, MSA에서 확장 가능 구현이 복잡하고 메시지 큐 필요

💡결론
단일 서비스 내에서 여러 개의 Repository를 다룰 때는 @Transactional을 사용하자.

외부 API가 포함될 경우 보상 트랜잭션(Compensating Transaction)을 구현하자.

비동기 이벤트 기반 처리가 필요하면 Kafka를 활용하여 SAGA 패턴과 유사한 흐름을 만들자.

즉, 단일 서비스에서는 전통적인 트랜잭션 관리 방식(@Transactional + 보상 트랜잭션)을 활용하고, 필요에 따라 이벤트 기반 방식도 고려하는 것이 좋다!

0개의 댓글