O0oO0Oo / netty-reservation-service

트랜잭션, 동시성을 공부하기 위한 토이 프로젝트입니다.
0 stars 0 forks source link

feat: impl orchestration saga pattern #19

Closed O0oO0Oo closed 1 month ago

O0oO0Oo commented 1 month ago

PR 설명

오케스트레이셔 사가 패턴의 구현.

변경 사항

배경

오케스트레이션 사가를 구현하면서 다음과 같은 요구사항이 있었습니다.

  1. 모든 작업은 비동기적으로 진행
  2. 각 작업은 이벤트로 온 응답값으로 완료된다.
  3. 여기서 각 작업은 이 응답값을 바탕으로 이벤트를 발행하여 다른 서비스로 요청하는 것이다.
  4. 각 작업은 이전의 작업들이 완료되어야 진행될 수 있다. (이전의 작업은 하나 또는 그 이상)
  5. 보상 트랜잭션 이벤트 발행을 위한, 이전 결과값을 재활용 해야한다.

이러한 요구사항을 위해 다음과 같은 정보를 저장해야 했습니다.

  1. 이벤트로 온 응답값
  2. 각 작업의 성공 상태
  3. 현재 작업이 실행되기 위해 완료되어야 하는 작업 목록
  4. 이전의 작업들이 완료되었는지

유저의 요청마다 SagaCoordinator 가 동작하게 되고 유저의 요청마다 위의 값들은 고유해야 하기 때문에, 요청에 대한 SagaState 가 생성되어야 했습니다.

Collection, Wrapper 로 위의 정보와 로직을 구현

단순히 이를 Collection 과 Wrapper 클래스로 만들어 SagaState 에서 관리하게 된다면, 각 응답을 받아 작업의 상태를 단순히 업데이트하는 SagaState 클래스에서는 위의 정보를 바탕으로 이전 작업의 의존성 관리와 성공 여부 추적 등 추가적인 로직이 포함되어 단일 책임 원칙 등 객체지향 원칙을 위배할 수 있는 구조가 될 것이라 생각하였습니다.

Netty 의 Promise 로 구현

Netty Promise 를 사용하게 된다면 addListener 로 다음 작업을 등록하여 실행 여부 판단과 성공으로 인한 다음 작업 실행등 로직을 분리할 수 있으며, 또한 PromiseAggregator 를 사용하면 이전에 의존하는 작업이 여러개인 경우에도 쉽게 처리할 수 있습니다.

Orchestration Saga 의 설정 예시는 다음과 같습니다.

SagaDefinition<Integer> sagaDefinition = sagaDefinitionFactory
        .addStep("step1", "{desination - 토픽 이름}", "{operation - Function(id, previous result, return)}", messageProducer, StepType.EXECUTE)
        .addStep("step2", "none", operation, messageProducer, StepType.EXECUTE)
        .addStep("step3", "none", operation, messageProducer, StepType.EXECUTE)
        .addStep("step4", "none", aggregateOperation, messageProducer, StepType.EXECUTE, "step2", "step3")
        .addStep("step5", "none", operation, messageProducer, StepType.EXECUTE, "step4")
        .addStep("step5", "none", operation, messageProducer, StepType.COMPENSATE)
        .addStep("step6", "none", aggregateOperation, messageProducer, StepType.EXECUTE, "step1", "step2", "step5")
        .addStep("step7", "none", operation, messageProducer, StepType.EXECUTE, "step6")
        .getSagaDefinition();

위의 설정은 아래와 같은 실행 체인을 구성하도록 하여, 이전 작업이 끝나면 자동적으로 다음 작업이 시작되도록 구현하였습니다.


 1, 2, 5 번 SagaPromise 가 완료되면 6번이 execute 된다.
 2, 3 번 SagaPromise 가 완료되면 4번이 execute 된다.

           +-> [1] --+
           |         | ------------> [6] --------> [7]
           |    +----+                ^
           |    |                     |
  [0] ---> +-> [2]                    |
           |    |                     |
           |    +----+                |
           |         | ---> [4] ---> [5 (Compensable)]
           +-> [3] --+

 [2, 3] done
  |
  v
 [4] --------->+ request -------+
               |    Service A   |
 [4] <---------+ response ------+
  |
  v
 [5] ----------> request Service B...

관련 이슈

11

추가 정보