1. ホーム
  2. design-patterns

[解決済み] CQRS コマンドの戻り値 [終了しました]

2023-02-06 05:44:03

質問

コマンドに戻り値を持たせるべきか否かについて、終わりのない混乱があるようです。 私は、その混乱が、単に参加者が自分の文脈や状況を述べていないためかどうかを知りたいのです。

混乱

以下は混乱の例です...

  • Udi Dahan は、コマンドは "クライアントにエラーを返さない、" と言っていますが は同じ記事の中で では、コマンドが実際にクライアントにエラーを返している図を示しています。

  • Microsoft Press Store の記事では、"コマンドは...応答を返しません" と述べていますが、その後、曖昧な注意を与えています。

CQRS に関する戦場での経験が増えるにつれて、いくつかのプラクティスが統合され、ベストプラクティスになる傾向があります。先ほど述べたことに一部反していますが...コマンド ハンドラとアプリケーションの両方が、トランザクション操作がどのように行われたかを知る必要があると考えるのが、今日の一般的な見解となっています。結果は知られていなければなりません...

  • ジミー・ボガードが語る " コマンドは常に結果を伴う と言っていますが、その後、コマンドがどのようにvoidを返すかを示すために特別な努力をしています。

さて、コマンドハンドラは値を返すのでしょうか、それとも返さないのでしょうか?

答えは?

ジミー・ボガードの「"」をヒントに。 CQRSの神話 この質問に対する答えは、どのようなプログラム/コンテキストで「象限」(quot;quadrant")を話しているかによると思います。

+-------------+-------------------------+-----------------+
|             | Real-time, Synchronous  |  Queued, Async  |
+-------------+-------------------------+-----------------+
| Acceptance  | Exception/return-value* | <see below>     |
| Fulfillment | return-value            | n/a             |
+-------------+-------------------------+-----------------+

受入(バリデーションなど)

コマンド "Acceptance"は主にバリデーションを指します。 おそらく、コマンド "fulfillment" が同期またはキューであるかどうかにかかわらず、検証結果は呼び出し元に同期的に提供されなければなりません。

しかし、多くの実務家は、コマンド ハンドラ内から検証を開始しないようです。 私が見たところ、(1) アプリケーション層で検証を処理する素晴らしい方法をすでに見つけている (つまり、データ注釈を介して有効な状態をチェックする ASP.NET MVC コントローラー)、または (2) コマンドが (プロセス外の) バスまたはキューに提出されると想定するアーキテクチャが存在する、のいずれかであることがわかりました。 これらの後者の非同期の形態は、一般に、同期検証のセマンティクスまたはインターフェイスを提供しません。

要するに、多くの設計者は、コマンド ハンドラーが (同期の) 返り値として検証結果を提供することを望むかもしれませんが、使用している非同期ツールの制限に従わなければなりません。

充足感

コマンドの履行に関して、コマンドを発行したクライアントは、新しく作成されたレコードの scope_identity や、account overdrawn のような障害情報を知る必要があるかもしれません。

リアルタイムの設定では、戻り値が最も理にかなっているように思われます。例外は、ビジネス関連の失敗の結果を伝えるために使用すべきではありません。 しかし、quot;queuing" のコンテキストでは...戻り値は当然ながら意味を持ちません。

これはおそらくすべての混乱を要約することができる場所です。

多くの (ほとんどの?) CQRS 実践者は、現在または将来、非同期フレームワークまたはプラットフォーム (バスまたはキュー) を組み込むことを想定しており、したがってコマンドハンドラが戻り値を持たないことを宣言しています。 しかし、そのようなイベント駆動型の構造を使うつもりのない実務者もいるので、彼らは(同期的に)値を返すコマンド・ハンドラを支持する。

つまり、例えば、以下のような場合、同期(リクエスト・レスポンス)コンテキストが想定されていたと思います。 Jimmy Bogard はこのサンプル コマンド インターフェイスを提供しました。 :

public interface ICommand<out TResult> { }

public interface ICommandHandler<in TCommand, out TResult>
    where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}

彼の製品であるMediatrは、結局のところ、インメモリーツールなのです。 このようなことを考えると、ジミーが が慎重に時間をかけて、コマンドから無効なリターンを生成したのは は、コマンド ハンドラが戻り値を持つべきでないからではなく、単に Mediator クラスに一貫したインターフェイスを持たせたかったからだと思います。

public interface IMediator
{
    TResponse Request<TResponse>(IQuery<TResponse> query);
    TResult Send<TResult>(ICommand<TResult> query);  //This is the signature in question.
}

...すべてのコマンドが意味のある値を返すわけではないにもかかわらず。

繰り返しとまとめ

私は、なぜこのトピックに混乱があるのかを正しく理解しているでしょうか。 何か見落としていることはないでしょうか?

更新情報 (6/2020)

ご回答をいただいて、混乱が解けたような気がします。 簡単に言うと、もし CQRS コマンドが成功/失敗を表す 完了 ステータスを返すことができるのであれば、戻り値は理にかなっています。 これには、新しいDB行のID、またはドメインモデル(ビジネス)コンテンツを読み取ったり返したりしない任意の結果を返すことが含まれます。

私は、quot;CQRSコマンドの混乱が生じるのは、quot;非同期の定義と役割に起因すると考えています。 タスクベースの非同期 IO と非同期アーキテクチャ (例: キューベースのミドルウェア) には大きな違いがあります。 前者では、非同期の"task"は非同期コマンドの完了結果を提供することができ、また提供します。 しかし、RabbitMQに送信されたコマンドは、同様にリクエスト/レスポンスの完了通知を受け取ることはありません。 非同期コマンドというものは存在しない」「コマンドは値を返さない」と言う人がいるのは、この後者の非同期アーキテクチャの文脈に起因しています。

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

のアドバイスにしたがって CQRSの複雑さへの取り組み Vladik Khononovによるアドバイスに従うと、コマンド処理はその結果に関連する情報を返すことができることを示唆しています。

CQRS]の原則に違反することなく、コマンドは安全に以下のデータを返すことができます。

  • 実行結果: 成功または失敗。
  • 失敗の場合、エラーメッセージまたはバリデーションエラー。
  • 成功の場合、アグリゲートの新しいバージョン番号。

この情報は、あなたのシステムのユーザーエクスペリエンスを劇的に向上させます、なぜなら。

  • コマンドの実行結果について外部ソースをポーリングする必要がなく、すぐに入手できます。コマンドを検証したり、エラーメッセージを返したりすることが簡単になります。
  • 表示されているデータを更新したい場合、アグリゲートの新しいバージョンを使って、ビューモデルが実行されたコマンドを反映しているかどうかを判断することができます。もう古くなったデータを表示する必要はありません。

Daniel Whittakerは、"を返すことを提唱しています。 共通の結果 この情報を含むコマンド ハンドラからオブジェクトを返すことを提唱しています。