[x] 상태 머신 그리기
순차적으로 실행되는 트랜잭션
병렬적으로 실행되는 트랜잭션 - A, B 는 서로 독립적이고, C 작업이 A, B 가 완료되어야 할 수 있다면 A, B 는 병렬적으로 실행될 수 있다.
구현
[x] 카프카 간단한 설정
[x] 사가 패턴 구현
[x] SagaCoordinator r : 사가 실행 관리, 각 서비스의 Response 메시지 구독
[x] SagaState : 실행중인 사가의 상태
[x] SagaStateManager : 사가의 상태 정보를 업데이트, 검색
[x] SagaDefinition : 정의된 로직에 따라 실행에 필요한 SagaPromise 실행 체인을 생성한다.
[x] SagaDefinitionFactory : SagaDefinition 을 생성
[x] SagaStep : 이벤트를 전처리하여 MessageProducer 로 발행 요청을 한다.
[x] SagaPromise: netty 의 promise 의 wrapper 객체로 위 그림들의 실행 체인을 형성하여, 이전 단계에 의존하는 Promise 들이 성공시 현재 단계를 실행하게 된다.
[x] CompensableSagaPromise : 기존의 Promise 는 최초 한번 성공/실패 시 addListener 가 동작한다, Compensating Transaction 은 성공한 트랜잭션에 대해 롤백하는 요청을 보내야하므로, CompensableSagaPromise 는 성공 이후에도 실패를 설정할 수 있고 그에 따른 보상 트랜잭션 동작 요청이 등록되어 있다.
[x] DefaultSagaPromise : 보상 트랜잭션 요청이 없는 기본적인 Promise
[x] SagaPromiseCreationStrategy : ServiceLoader 를 사용하여 Step 의 타입에 따라(Compensate, Execute 등 추가 가능) 동적으로 맞는 SagaPromise 를 생성해준다.
[x] MessageProducer : 이벤트 발행
[x] MessageConsumer : 이벤트 구독
[x] Protobuf : protobuf 의 oneof 를 사용한 유연한 envelop 구현
[x] 롤백의 동작
SagaPromise 를 모두 failure 로 설정
[x] 테스트 코드 작성
[x] SagaDefinition
[x] SagaPromise
[x] SagaCoordinator
[x] SagaStateManager
예상 동작
예약 요청이 들어오면 이벤트를 생산하고 각 서비스에서 소비해간다.
예외가 발생하면 이전에 있던 Compensatable 트랜잭션들이 보상 트랜잭션을 사용하여 롤백된다.
Pivot 트랜잭션까지 진행된다면 이후의 트랜잭션들은 어떠한 예외가 발생하여도 재시도를 한다.
실제 동작
SagaCoordinator 에 시작을 요청
등록한 SagaDefinition 을 통해 Netty 의 Promise 체인을 생성, 배열로 리턴 (아래의 예시)
1, 2 번 SagaPromise 가 완료되면 4번이 execute 된다.
2, 3 번 SagaPromise 가 완료되면 5, 6번이 execute 된다.
+---> [1] ---+
| |---> [4 (depends on 1,2)] ---+
[init promise] ---> +---> [2] ---+ |---+
| |---> [5 (depends on 2,3)] ---+ |
+---> [3] ---+ +---> [7]
| |
+---> [6 (depends on 3)] ---------+
SagaDefinition 에서 받은 정보를 SagaCoordinator 가 SagaStateManger 를 통해 상태를 저장
응답이 오면 위의 그림처럼 각 단계를 success 한 상태로 설정하게 되고, 각 단계는 이전단계가 성공하면, 결과 값들을 가지고 다음 이벤트를 발행.
추가 정보
결과가 비동기적으로 생성되는데 이를 어떻게 할 것인지. - Netty Promise?
트랜잭션을 순차적으로 보다 병렬적으로 처리할 수 있도록 구현하자, A, B 를 읽은 데이터를 바탕으로 C 작업을 해야한다면 A, B 는 병렬적으로 실행될 수 있다.
Kafka Schema Registry : 각 스텝마다 이벤트에 대한 응답, 요청 클래스들을 정의하다보니 서비스간에 강한 결합이 생기고, Builder 를 통해 객체를 생성하는 과정도 복잡해져서, 스키마 레지스트리를 사용하기로 하였다. protobuf 를 사용해 중앙에서 관리해자.
작업의 동작 순서를 정의할 때 List, Map 등의 자료구조와 Netty 의 Promise 둘 중 어느것을 사용할지 고민해보자, - 메모리 사용량, 객체 지향적 측면
Compensate 는 이미 성공한 트랜잭션을 되돌리는 것이다. promise 로 사가의 진행상태를 체인으로 삼아 구현했지만 promise 의 경우 성공/실패 최초 설정시에만 addlistener 가 동작하기에 재실행 가능하도록 구현해야 한다.
모든 promise 가 성공 했을때, 서비스에서 이를 알아야 하는데, 서비스와 통합을 하면서 구현
이슈 개요
사가 패턴을 구현하기 위해 필요 구성요소, 구조 공부, 구현
재현 단계
문서
구현
예상 동작
실제 동작
추가 정보