이벤트 소싱, 하나의 이벤트, 두 집계의 상태가 변경됨


10

DDD 및 관련 과목의 방법을 배우려고합니다. 나는 "은행 (bank)"을 구현하기위한 단순한 제한적 맥락에 대한 아이디어를 생각 해냈다. 계좌가 있고, 자금이 입금, 인출 및 송금 될 수있다. 변경 기록을 유지하는 것도 중요합니다.

계정 엔티티를 식별 했으며 이벤트 소싱을 통해 변경 사항을 추적하는 것이 좋습니다. 다른 엔터티 또는 값 개체는 문제와 관련이 없으므로 언급하지 않습니다.

예금과 인출을 고려할 때, 하나의 집계가 수정되기 때문에 비교적 간단합니다.

전송할 때마다 다릅니다-두 개의 집계는 하나의 MoneyTransferred 이벤트 로 수정해야합니다 . DDD는 한 트랜잭션에서 여러 집계 수정을 더 이상 사용하지 않습니다. 반면 이벤트 소싱의 규칙은 이벤트를 엔티티에 적용하고이를 기반으로 상태를 수정하는 것입니다. 이벤트를 데이터베이스에 간단히 저장할 수 있으면 아무런 문제가 없습니다. 그러나 이벤트 소스 엔티티의 동시 수정을 방지하려면 트랜잭션 범위를 유지하기 위해 각 집계의 이벤트 스트림을 버전 화하는 것을 구현해야합니다. 버전 관리와 함께 또 다른 문제가 발생합니다. 간단한 구조를 사용하여 이벤트를 저장하고 다시 읽어 집계에 적용 할 수 없습니다.

내 질문은- "하나의 집계 하나의 트랜잭션", "이벤트-> 집계의 변경"및 "동시 수정 방지"라는 세 가지 원칙을 어떻게 통합 할 수 있습니까?

답변:


7

전송할 때마다 다릅니다-두 개의 집계는 하나의 MoneyTransferred 이벤트로 수정해야합니다.

송금은 원장 갱신과는 별도의 행위입니다.

MoneyTransferred
AccountCredited
AccountDebited

마침내 나를 위해이 문제를 깨뜨린 운동 AccountOverdrawn은 이벤트 라는 것을 깨달았 습니다.이 거래소의 다른 참가자와 관계없이 계정의 상태를 설명하므로 계정을 생성하는 계정에 대한 명령 실행이 있어야합니다.

AccountOverdrawn아직 모든 이벤트를 보았는지 알 수 없기 때문에 읽기 모델에서 와 같이 합리적으로 상태를 도출 할 수는 없습니다. 집계 자체 만 특정 시점에서 히스토리를 전체적으로 볼 수 있기 때문입니다.

물론 대답은 어디에서나 유비쿼터스 언어로되어 있습니다. 계좌는 고객에 대한 은행의 의무를 반영하기 위해 입금되거나 차감됩니다.

그래도 예금과 인출에도 AccountCredited 및 AccountDebited 이벤트를 사용해야하므로 변경의 원인 만 아니라 다른 조치로 인한 변경을 등록해야합니다. 모든 이벤트가 등록되지 않았기 때문에 조치를 취소하려면 할 수 없었습니다.

나는 당신이 (이와 같은 경우) 거래 ID 자체 인 자연 상관 식별자를 가지고 있기 때문에 다음을 완전히 확신하지는 못합니다.

두 번째는-사가와 같은 것을 사용해야한다는 의미입니다.

약간 다른 철자 : 올바른 명령을 전달 하는 인간 과 같은 것이 필요 합니다 .

최소한 두 가지 방법이 있습니다. 하나는 가입자가를 듣고 MoneyTransferred두 명령을 원장에게 발송하도록하는 것입니다.

또 다른 대안은 트랜잭션 처리를 별도의 집계로 추적하는 것입니다. 트랜잭션이 발생한 후 수행해야하는 모든 사항에 대한 체크리스트로 생각하십시오. 따라서 MoneyTransferred이벤트 처리기는 ProcessTransaction을 전달하여 수행 할 작업을 예약하고 완료된 작업을 확인합니다.


그래도 예금과 인출에도 AccountCredited 및 AccountDebited 이벤트를 사용해야하므로 변경의 원인 만 아니라 다른 조치로 인한 변경을 등록해야합니다. 모든 이벤트가 등록되지 않았기 때문에 조치를 취소하려면 할 수 없었습니다. 어떻게하면 되나요 (사건의 원인)? 두 번째는-사가와 같은 것을 사용해야한다는 의미입니다. 그렇다면 어떻게 전송을 모델링해야합니까? 계정에 전송 방법 이있는 순간 . 호출되면 MoneyTransferred 이벤트를 게시 합니다. saga와 같은 것을 시작 해야할지 모르겠습니다.
cocsackie

가요-> AccountCreditedAccoundDebited 그리고 MoneyTransferred 입니까? 첫 번째 솔루션은 하나의 트랜잭션에서 두 집계를 업데이트 합니까 ( 어떤 종류의 일관성 보장도 없음 )? MoneyTransferred- > 상관 없음을 게시 할 수있는 집계도 없습니다 . 두 번째 솔루션은 더 나은 것 같다 - ProcessTransaction을 게시 할 수 있습니다 MoneyTransferred을 내가에서 이벤트 게시 할 수 있습니다 하나의 트랜잭션 내에서 여러 집계 수정 방지하기 위해 계정 거래를 저지르고 있습니다. 화려해서 죄송합니다. 초보자에게는 이해하기 어렵습니다. 다른 패턴없이 하나의 패턴 만 사용할 수는 없습니다.
cocsackie

1

거래 기반 계정을 이해하는 데 중요한 세부 사항 :의 balance속성 account은 실제로 비정규 화의 인스턴스입니다. 편의를 위해 존재합니다. 실제로 계정 잔액은 거래의 합계이므로 계정 자체가 잔액을 가질 필요 는 없습니다 .

이를 염두에두고 돈을 이체하는 행위는 업데이트가 account아니라에 삽입 되어야합니다 transaction.

상기 존재가 있다는 또 다른 중요한 규칙이있다 : 를 추가하는 단계는 transaction제 (비정규 밸런스 필드)에 대한 업데이트와 원자이어야한다 account.

이제 DDD 집계 개념을 이해하면 다음과 관련이 있습니다.

집계는 주어진 컨텍스트의 비즈니스 트랜잭션에서 변경 될 수있는 사물의 논리적 경계입니다. 집계는 단일 클래스 또는 여러 클래스로 표시 될 수 있습니다. 둘 이상의 클래스가 집계로 구성되는 경우 그 중 하나가 소위 루트 클래스 또는 엔터티입니다. 외부에서 집계에 대한 모든 액세스는 루트 클래스를 통해 이루어져야합니다.

따라서 DDD 디자인 측면에서 다음과 같이 제안합니다.

  1. 이전을 나타내는 하나의 집계가 있습니다.

  2. 집합체는 다음 객체로 구성됩니다. 전송 (루트 객체); 루트 개체는 두 개의 트랜잭션 목록에 연결됩니다 (각 계정마다 하나씩). 각 거래 목록은 하나의 계정에 연결되어 있습니다.

  3. 전송에 대한 모든 액세스는 루트 객체 ( transfer)에 의해 명상되어야합니다 .

비동기 전송 지원을 구현하려는 경우 기본 코드는 "보류 중"상태 인 전송 작성에 대해 걱정해야합니다. 실제로 거래 내역을 삽입하고 잔액을 업데이트하여 돈을 이동시키고 이전을 "전기 됨"으로 설정하는 다른 스레드 또는 작업이있을 수 있습니다.

전송 트랜잭션을 차단하는 실시간을 구현하려는 경우 비즈니스 로직이 작성해야 transfer하며 해당 오브젝트가 다른 활동을 실시간으로 조정해야합니다.

동시성 문제를 방지하기 위해 첫 번째 비즈니스 순서는 차변 거래를 소스 계정의 거래 목록에 삽입하는 것입니다 (물론 잔액 업데이트). 데이터베이스 수준에서 (저장 프로 시저를 통해) 원자 적으로 수행해야합니다. 차변이 발생한 후에는 대상 계정에 대한 신용을 막는 비즈니스 규칙이 없어야하기 때문에 동시성 문제와 상관없이 나머지 양도에 성공할 수 있습니다.

(실제로 은행 계좌에는 게으른 2 단계 커밋 개념을 지원 하는 메모 포스트 개념이 있습니다. 메모 포스트 작성은 가볍고 쉬우 며 문제없이 롤백 할 수 있습니다. 하드 포스트에 대한 메모 포스트는 돈이 실제로 움직일 때-롤백 할 수 없으며 2 단계 커밋의 두 번째 단계를 나타내며 모든 유효성 검사 규칙을 확인한 후에 만 ​​발생합니다).


0

나는 또한 현재 학습 단계에 있습니다. 구현 관점에서 볼 때 이것이이 작업을 수행 할 것입니다.

다음 이벤트를 발생시키는 Dispatch TransferMoneyCommand [MoneyTransferEvent, AccountDebitedEvent]

이러한 이벤트가 발생하기 전에 피상적 명령 유효성 검사 및 도메인 논리 유효성 검사를 수행해야합니다. 즉, 계정의 잔액이 충분합니까?

일관성 문제가 없도록 이벤트를 버전 관리와 함께 유지하십시오. 이 명령 이전에 이벤트를 성공으로 저장하고 저장하는 다른 동시 명령 (예 : 모든 자금 인출)이있을 수 있으므로 집계의 현재 상태가 오래되어 이벤트가 이전 상태에서 발생하여 올바르지 않습니다. 이벤트 저장에 실패하면 처음부터 명령을 재 시도해야합니다.

이벤트가 데이터베이스에 성공적으로 저장되면 발생한 두 개의 이벤트를 공개 할 수 있습니다.

AccountDebitedEvent는 지불 자의 계정에서 돈을 제거합니다 (집계 상태 및 관련보기 / 투영 모델을 업데이트 함)

MoneyTransferEvent가 Saga / Process Manager를 시작합니다.

사가 / 프로세스 관리자의 임무는 수취인의 계좌에 신용을 부여하는 것이며, 실패한 경우 잔액을 지불 자에게 다시 신용해야합니다.

Saga / 프로세스 관리자는 수취인의 계정에 적용된 CreditAccountCommand를 게시하고 AccountCreditedEvent보다 성공한 경우 발생합니다.

이벤트 소싱 관점에서이 조치를 취소하려는 경우이 트랜잭션의 모든 이벤트는 원래의 TransferMoneyCommand로 상관 관계 / 원인 ID를 가지므로 실행 취소 / 리버스 조작을위한 이벤트를 발생시키는 데 사용할 수 있습니다.

위의 문제 또는 잠재적 개선 사항을 제안하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.