1. ホーム
  2. domain-driven-design

CQRSのイベントソーシング。UserNameの一意性を検証する

2023-08-24 09:59:29

質問

簡単な「アカウント登録」を例に、流れを説明します。

  • ユーザーがウェブサイトを訪問
  • 登録ボタンをクリックし、フォームに記入後、保存ボタンをクリックします。
  • MVCコントローラです。ReadModelから読み込んだUserNameの一意性を検証する。
  • RegisterCommandです。UserNameの一意性を再度検証する(ここが問題点)

もちろん、パフォーマンスとユーザーエクスペリエンスを向上させるために、MVCコントローラでReadModelから読み込んでUserNameの一意性を検証することは可能です。しかし のように、RegisterCommandで再度一意性の検証を行う必要があります。 また、明らかにコマンドでReadModelにアクセスするべきではありません。

Event Sourcingを使用しない場合、ドメインモデルへのクエリーは可能なので、問題はありません。しかし、イベントソーシングを使用している場合、ドメインモデルをクエリすることはできません。 RegisterCommandでUserNameの一意性を検証するにはどうしたらよいでしょうか?

お知らせです。 UserクラスにはIdプロパティがあり、UserNameはUserクラスのkeyプロパティではありません。イベントソーシングを使用する場合、Idによってのみドメインオブジェクトを取得することができます。

ところで。 要件では、入力されたUserNameがすでに使われている場合、ウェブサイトは訪問者にエラーメッセージ "申し訳ありませんが、ユーザー名XXXは使用できません"を表示する必要があります。また、「アカウント作成中です、しばらくお待ちください、登録結果は後ほどメールでお送りします」というようなメッセージを表示するのは好ましくありません。

何かアイデアはありますか?ありがとうございます。

[UPDATE】です。]

より複雑な例です。

要件です。

注文するとき、システムはクライアントの注文履歴をチェックする必要があり、もし彼が貴重なクライアントであれば(もしクライアントが過去1年間に毎月少なくとも10の注文をしたならば、彼は貴重である)、我々は注文に10%の割引をする。

実装。

PlaceOrderCommandを作成し、そのコマンドの中で、クライアントが貴重であるかどうかを確認するために、注文履歴を照会する必要があります。しかし、どのようにそれを行うことができますか?コマンドの中でReadModelにアクセスするべきではありません! Mikaelのように が言うように のように、アカウント登録の例では補償コマンドを使用できますが、この注文の例でもそれを使用すると、コードが複雑になりすぎて、メンテナンスが大変になるかもしれません。

どのように解決するのですか?

コマンドを送信する前に読み取りモデルを使用してユーザー名を検証する場合、実際のレース コンディションが発生する可能性がある数百ミリ秒のレース コンディション ウィンドウについて話していることになりますが、私のシステムではこれは処理されません。私のシステムでは、レースコンディションは処理されません。

しかし、何らかの理由でそれを処理しなければならないと感じる場合、あるいは単にそのようなケースをマスターする方法を知りたいと感じる場合、ここに 1 つの方法があります。

イベントソーシングを利用する場合、コマンドハンドラやドメインから読み込みモデルにアクセスするべきではありません。しかし、あなたができることは、読み取りモデルに再度アクセスし、ユーザー名がまだ重複していないかどうかをチェックするUserRegisteredイベントをリッスンするドメインサービスを使用することです。もちろん、ここでもUserGuidを使用する必要があります。なぜなら、読み取りモデルは作成したばかりのユーザーで更新されているかもしれないからです。もし重複が見つかった場合は、ユーザー名を変更したり、ユーザー名が取得されたことをユーザーに通知するなどの補償コマンドを送信するチャンスがあります。

これは問題に対する1つのアプローチです。

おそらくお分かりのように、これを同期的なリクエスト-レスポンス方式で行うことはできません。それを解決するために、私たちは、クライアントにプッシュしたいものがあるときはいつでも (つまり、まだ接続されていれば)、UI を更新するために SignalR を使用しています。私たちが行っているのは、クライアントがすぐに見るのに役立つ情報を含むイベントを、Web クライアントに購読させるということです。

更新

より複雑なケースに対応。

コマンドを送信する前に、クライアントが貴重であるかどうかを調べるために読み取りモデルを使用することができるので、注文の配置はそれほど複雑ではないと言うことができます。実際、注文フォームをロードするときに、おそらくクライアントが注文する前に10%オフを得ることを示したいので、それをクエリすることができます。ただ、割引を PlaceOrderCommand に割引を追加し、おそらく割引の理由も追加して、利益を削減する理由を追跡できるようにします。

しかし、もし本当に何らかの理由で注文後に割引を計算する必要があるのなら、もう一度、ドメインサービスを使用して OrderPlacedEvent この場合の "compensating" コマンドは、おそらく DiscountOrderCommand または何かです。このコマンドは Order Aggregate ルートに影響を与え、その情報は読み取りモデルに伝搬される可能性があります。

ユーザー名が重複しているケースについて。

を送ることができます。 ChangeUsernameCommand をドメインサービスからの補償コマンドとして送ることができます。または、ユーザー名が変更された理由を記述する、より具体的なもので、Web クライアントが購読できるイベントの作成につながり、ユーザー名が重複していたことをユーザーに知らせることができます。

ドメインサービスのコンテキストでは、ユーザーがまだ接続しているかどうかを知ることができないので、有用である電子メールの送信など、ユーザーに通知するために他の手段を使用する可能性もあると言えるでしょう。おそらく、通知機能は、Web クライアントがサブスクライブしているのとまったく同じイベントによって開始される可能性があります。

SignalRに関して言えば、私はユーザーが特定のフォームをロードしたときに接続するSignalR Hubを使用しています。私は SignalR Group 機能を使用して、コマンドで送信する Guid の値を名前にしたグループを作成することができます。これは、あなたの場合はuserGuidかもしれません。そして、クライアントにとって有用なイベントを購読するEventhandlerを用意し、イベントが到着したら、SignalR Group内のすべてのクライアント(この場合は、重複するユーザー名を作成しているクライアントのみ)に対してjavascript関数を呼び出すことができる。複雑そうに見えますが、実はそうでもないんです。私は午後にはすべてセットアップしました。SignalRのGithubページには、素晴らしいドキュメントと例があります。