juchanei

Sagas


https://microservices.io/patterns/data/saga.html 을 번역한 글입니다.

Context

당신은 Database per Service 패턴을 적용했다. 각각 의 서비스는 자신만의 데이터베이스를 가지고 있다. 그런데, 한 비즈니스 트랜잭션이 여러 서비스에 걸쳐 존재하고 당신은 이를 구현하기 위한 메커니즘이 필요하다. 예를 들어, 구매자에게 신용한도가 있는 e-커머스 스토어를 만든다고 해보자. Order 서비스와 Customer 서비스는 각각의 데이터베이스를 가지고 있어 로컬 ACID 트랜잭션을 쓸 수 없는 상태이다.

Problem

어떻게 하면 여러 서비스에 걸쳐 트랜잭션을 구현할 수 있을까?

Forces

  • 2PC (2 Phase Commit)은 사용하지 않는다.

Solution

여러 서비스에 걸친 비즈니스 트랜잭션은 saga로 구현한다. Saga는 연속된 로컬 트랜잭션이다. 각각의 로컬 트랜잭션은 데이터베이스를 업데이트하고 메시지나 이벤트를 발행해 saga 다음 트랜잭션을 트리거한다. 만약 비즈니스 규칙을 위반하여 로컬 트랜잭션이 실패하면 saga는 이전 트랜잭션으로 인한 변화를 되돌리기 위해 보상 트랜잭션들을 실행한다.

image

여기에 saga를 조정(coordination)하는 두 방식이 있다.

  • Choreography - 각각의 로컬 트랜잭션이 다른 서비스의 로컬 트랜잭션을 트리거 하는 도메인 이벤트를 발행한다.
  • Orchestration - 오케스트레이터가 다른 서비스들에게 로컬 트랜잭션을 실행하도록 지시한다.

Example: Choreography-based saga

image

e-커머스 어플리케이션이 choreography 기반의 saga를 사용하여 ‘주문’을 생성하려고 한다면, 아래 단계를 따른다.

  1. Order 서비스는 POST /orders 요청을 받고 OrderPENDING 상태로 생성한다.
  2. Order Created 이벤트를 발행한다.
  3. Customer 서비스의 이벤트 핸들러가 이를 받아 ‘신용’을 확보한다.
  4. 위 결과를 의미하는 이벤트를 발행한다.
  5. Order 서비스의 이벤트 핸들러가 Order를 승인하거나 거절한다.

Example: Orchestration-based saga

image

e-커머스 어플리케이션이 orchestration 기반의 saga를 사용하여 ‘주문’을 생성하려고 한다면, 아래 단계를 따른다.

  1. Order 서비스가 POST /orders 요청을 받고 Create Order saga 오케스트레이터를 생성한다.
  2. saga 오케스트레이터는 OrderPENDING 상태로 생성한다.
  3. 그리고 Reserve Credit 명령(command)를 Customer 서비스로 보낸다.
  4. Customer 서비스는 ‘신용’을 확보한다.
  5. Customer 서비스는 결과를 saga 오케스트레이터에게 보낸다.
  6. saga 오케스트레이터는 Order를 승인하거나 거절한다.

Resulting context

이 패턴은 다음과 같은 장점이 있다.

  • 분산 트랜잭션 없이 여러 서비스에 걸쳐 데이터 일관성을 유지할 수 있도록 한다.

이 패턴은 다음과 같은 단점이 있다.

  • 프로그래밍 모델이 복잡하다. 예를 들어, 개발자는 앞선 변화를 되돌릴 수 있도록 반드시 보상 트랜잭션을 설계해야 한다.

이 패턴은 또한 아래 대처해야 할 이슈가 있다.

  • 신뢰성을 위해 서비스는 반드시 원자적으로 데이터베이스를 업데이트 한 후 메시지/이벤트를 발행해야 한다. 이때 데이터베이스와 메시지 브로커에 걸친 분산 트랜잭션 메커니즘은 사용할 수 없다. 대신, 다음에 소개 될 아래 패턴 중 하나를 사용해야 한다.
  • 클라이언트는 동기적인 요청(e.g. HTTP POST /orders)을 통해 saga를 동작 시키고, 그 결과를 비동기적으로 얻어야 한다. 여기에는 트레이드오프가 있는 몇가지 옵션이 있다.
    • 서비스는 saga가 완료되면 그 결과를 응답한다. (e.g. OrderApproved 또는 OrderRejected 이벤트)
    • 서비스는 saga를 동작시킨 뒤 orderID를 포함하여 응답한다. 클라이언트는 주기적으로 GET /orders/{orderID}를 요청해 polling 하여 결과를 얻는다.
    • 서비스는 saga를 동작시킨 뒤 orderID를 포함하여 응답한다. 그리고 saga가 완료되면 웹 소캣 또는 웹 훅 등을 이용해 이벤트를 발행한다.

Learn more

Example code

아래 예제는 Customer, Order 예제를 각각 다른 방식으로 구현한다.