イベントソース、1つのイベント、2つのアグリゲートの状態が変更されました


10

私はDDDの方法と関連する主題を学ぼうとしています。私は、「銀行」を実装するための単純な境界付きコンテキストのアイデアを思いつきました。変更の履歴を保持することも重要です。

アカウントエンティティを特定しました。そのイベントソーシングは、その変更を追跡するのに適しています。他のエンティティまたは値オブジェクトは問題とは無関係であるため、それらについては触れません。

預金と引き出しを検討する場合-変更される集計は1つしかないため、比較的簡単です。

転送する場合は異なります-2つの集計は1つのMoneyTransferredイベントで変更する必要があります。DDDは、1つのトランザクションでの複数の集約の変更を非推奨にします。一方、イベントソースのルールは、エンティティにイベントを適用し、それらに基づいて状態を変更することです。イベントを単純にデータベースに保存できれば問題ありません。ただし、イベントソースエンティティの同時変更を防ぐには、各トランザクションのイベントストリームをバージョン管理するものを実装する必要があります(トランザクションの境界を維持するため)。バージョン管理には別の問題があります-単純な構造を使用してイベントを格納し、それらを読み取って集計に適用することはできません。

私の質問は、「1つの集約1つのトランザクション」、「イベント->集約の変更」、および「同時変更防止」の3つの原則をまとめるにはどうすればよいですか。

回答:


7

転送する場合は異なります-2つの集計は1つのMoneyTransferredイベントで変更する必要があります。

送金は元帳の更新とは別の行為です。

MoneyTransferred
AccountCredited
AccountDebited

最終的にこれを緩めたエクササイズAccountOverdrawnは、それがイベントであることを認識していました。それは、この交換の他の参加者に関係なくアカウントの状態を記述しているため、それを生成するアカウントに対してコマンドを実行する必要があります。

AccountOverdrawn読み取りモデルからのように状態を合理的に導出することはできません。これは、すべてのイベントをまだ表示したかどうかを知ることができないためです。特定の瞬間に、集計自体だけが履歴の完全なビューを持っています。

もちろん、答えはユビキタス言語であり、顧客に対する銀行の義務を反映するために口座に貸方記入または借方記入されます。

申し分ありませんが、入金と引き出しにもAccountCreditedイベントとAccountDebitedイベントを使用する必要があることを意味しているため、変更の原因ではなく、他のアクションによって引き起こされた変更のみを登録します。すべてのイベントが登録されているわけではないので、できなかったアクションを元に戻したい場合。

トランザクションID自体である自然相関識別子(このような場合)があるので、以下のことは完全にはわかりません。

第二に-それは私が佐賀のようなものを使用する必要があることを意味します。

スペルが少し異なります適切なコマンドをディスパッチする人間のようなものが必要です。

それを行うには、少なくとも2つの方法があります。1つは、サブスクライバーがをリッスンしMoneyTransferred、2つのコマンドを元帳にディスパッチすることです。

別の代替案は、トランザクションの処理を個別の集計として追跡することです。これは、トランザクションが発生してから実行する必要があるすべてのことのチェックリストと考えてください。そのため、MoneyTransferredイベントハンドラーはProcessTransactionをディスパッチし、実行する作業をスケジュールし、完了した作業をチェックします。


申し分ありませんが、入金と出金にもAccountCreditedイベントとAccountDebitedイベントを使用する必要があることを意味しているため、変更の原因ではなく、他のアクションによって引き起こされた変更のみを登録します。すべてのイベントが登録されているわけではないので、できなかったアクションを元に戻したい場合。これを行うにはどうすればよいですか(イベントの因果関係)?第二に-それは私が佐賀のようなものを使用する必要があることを意味します。次に、転送をどのようにモデル化する必要がありますか?口座振込方法がある瞬間。呼び出されると、イベントMoneyTransferredを発行します。佐賀のようなものを何から始めればいいのか分かりません。
cocsackie 2017

それではありませんか-> AccountCredited and AccoundDebited then MoneyTransferred?最初のソリューションは、1つのトランザクション(の無い一貫性保証の両方で集計を更新あらゆる種類の)?MoneyTransferredを発行できる集計もありません->相関関係はありません。第2の解決策は、より良いように思わ- ProcessTransactionを公開することができMoneyTransferredを、私はからイベント公開することができます1つのトランザクションで複数の集計の変更を避けるために、アカウントのトランザクションをコミットした後。気弱でごめんなさい。初心者には理解しにくいです。他のパターンがない1つのパターンだけを使用することはできません。
コクサッキー

1

トランザクションベースのアカウントを理解する上での重要な詳細:のbalance属性accountは、実際には非正規化のインスタンスです。便宜上そこにあります。実際には、口座の残高は取引の合計であり、口座自体が残高を持つ必要はありません。

これを念頭に置いて、送金の行為は更新ではaccountなく、への挿入transactionです。

そうは言っても、別の重要なルールがあります。aを追加する動作はtransaction、(非正規化バランスフィールド)を更新することでアトミックである必要がありますaccount

ここで、集約のDDDの概念を理解すると、次のようになります。

集約は、特定のコンテキストのビジネストランザクションで変更される可能性のあるものの論理的な境界です。集約は、単一のクラスまたは複数のクラスで表すことができます。複数のクラスが集合体を構成する場合、それらの1つはいわゆるルートクラスまたはエンティティです。外部からの集約へのすべてのアクセスは、ルートクラスを介して行われる必要があります。

したがって、DDD設計に関しては、次のことをお勧めします。

  1. 転送を表す1つの集計があります

  2. 集合体は、次のオブジェクトで構成されています。転送(ルートオブジェクト)。ルートオブジェクトは2つのトランザクションリスト(アカウントごとに1つ)にリンクされています。また、各トランザクションリストは1つのアカウントにリンクされています。

  3. 転送へのすべてのアクセスは、ルートオブジェクト(transfer)によって瞑想される必要があります。

非同期転送サポートを実装しようとしている場合、メインコードは、「保留」ステータスでの転送の作成について心配するだけです。実際にお金を移動し(トランザクション履歴に挿入し、したがって残高を更新する)、転送を「転記済み」に設定する別のスレッドまたはジョブがある場合があります。

リアルタイムのブロッキング転送トランザクションを実装する場合は、ビジネスロジックがを作成しtransfer、そのオブジェクトが他のアクティビティをリアルタイムで調整する必要があります。

同時実行性の問題を回避するという観点から、最初の業務は、借方トランザクションをソースアカウントのトランザクションリストに挿入することです(もちろん、残高を更新します)。これは、データベースレベルで(ストアドプロシージャを介して)アトミックに実行する必要があります。引き落としが発生した後は、同時実行の問題に関係なく、残りの転送は成功するはずです。これは、ターゲットアカウントへのクレジットを妨げるビジネスルールがないためです。

(現実の世界では、銀行口座には、遅延2フェーズコミットの概念をサポートするメモ投稿の概念があります。メモ投稿の作成は軽量で簡単で、問題なくロールバックすることもできます。変換ハードポストへのメモ投稿は、お金が実際に移動したとき(これはロールバックできません)であり、2フェーズコミットの2番目のフェーズを表し、すべての検証ルールがチェックされた後にのみ発生します)。


0

私も現在学習段階にあります。実装の観点から見ると、これはあなたがこのアクションを実行すると感じる方法です。

次のイベントを発生させるTransferMoneyCommandをディスパッチします[MoneyTransferEvent、AccountDebitedEvent]

これらのイベントを発生させる前に、表面的なコマンド検証とドメインロジック検証を実行する必要があることに注意してください。つまり、アカウントに十分なバランスがありますか?

一貫性の問題がないことを確認するために、イベントを(バージョン管理を使用して)永続化します。この前にイベントを成功させ保存する別の同時コマンド(withdraw all moneyなど)が存在する可能性があることに注意してください。そのため、集計の現在の状態が古くなっている可能性があるため、イベントは古い状態で発生し、正しくありません。イベントの保存に失敗した場合は、最初からコマンドを再試行する必要があります。

イベントがデータベースに正常に保存されたら、発生した2つのイベントを公開できます。

AccountDebitedEventは支払者の口座からお金を削除します(集計状態と関連するビュー/投影モデルを更新します)

MoneyTransferEventがSaga / Process Managerを起動します。

佐賀/プロセスマネージャーの仕事は、受取人の口座に入金することです。失敗した場合は、残高を支払人に返金する必要があります。

Saga / Processマネージャーは、受取人のアカウントに適用されるCreditAccountCommandを発行し、成功した場合はAccountCreditedEventが発生します。

イベントソースの観点から見ると、このアクションを取り消す場合、このトランザクションのすべてのイベントには、元に戻す/取り消す操作のイベントを発生させるために使用できる元のTransferMoneyCommandとして、相関/原因IDがあります。

上記の問題や改善の可能性がある場合は、遠慮なく提案してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.