web-dev-qa-db-ja.com

CQRS:コマンドの戻り値

コマンドが戻り値を持つべきかどうかについて、無限の混乱があるようです。混乱が単に参加者が自分のコンテキストや状況を述べていないためかどうかを知りたいです。

混乱

ここに混乱の例があります...

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

  • Microsoft Press Storeの記事には「コマンドは応答を返さない」とありますが、その後は曖昧な注意を払っています。

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

さて、コマンドハンドラーは値を返しますか?

答え?

ジミー・ボガードの「 CQRS Myths 」からヒントを得て、この質問に対する答えは、あなたが話しているプログラム/コンテキスト「象限」に依存すると思います。

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

受け入れ(検証など)

コマンド「Acceptance」は主に検証を指します。おそらく、検証の結果は、コマンド「フルフィルメント」が同期であるかキューに入っているかに関係なく、呼び出し元に同期的に提供される必要があります。

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

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

フルフィルメント

コマンドの「フルフィルメント」に関して、コマンドを発行したクライアントは、「account overdrawn」など、新しく作成されたレコードまたはおそらく失敗情報のscope_identityを知る必要があります。

リアルタイム設定では、戻り値が最も意味があるようです。例外は、ビジネス関連の失敗の結果を伝えるために使用しないでください。ただし、「キューイング」コンテキストでは...戻り値は当然意味がありません。

これは、すべての混乱を要約できる場所です。

多くの(ほとんど?)CQRS実践者は、現在または将来、非同期フレームワークまたはプラットフォーム(バスまたはキュー)を組み込むと想定しているため、コマンドハンドラーには戻り値がないと宣言しています。ただし、一部の開業医は、そのようなイベント駆動型の構造を使用する意図がないため、値を(同期的に)返すコマンドハンドラを推奨します。

そのため、たとえば、 Jimmy Bogardがこのサンプルコマンドインターフェイスを提供した の場合、同期(要求/応答)コンテキストが想定されたと思います。

public interface ICommand<out TResult> { }

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

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

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

...すべてのコマンドに返される意味のある値があるわけではありませんが。

繰り返してまとめます

このトピックに混乱がある理由を正しく把握できていますか?私が見逃しているものはありますか?

55
Brent Arias

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

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

  • 実行結果:成功または失敗。
  • 失敗の場合のエラーメッセージまたは検証エラー。
  • 成功した場合の集合体の新しいバージョン番号。

次の理由により、この情報はシステムのユーザーエクスペリエンスを劇的に改善します。

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

Daniel Whittakerは、この情報を含むコマンドハンドラーから " common result "オブジェクトを返すことを推奨しています。

16
Ben Smith

さて、コマンドハンドラーは値を返しますか?

Business Dataを返さず、メタデータのみ(コマンドの実行の成功または失敗に関して)を返す必要があります。 CQRS[〜#〜] cqs [〜#〜] の上位レベルになります。たとえあなたが純粋主義者の規則を破って何かを返したとしても、あなたは何を返しますか? CQRSでは、コマンドハンドラーはaggregateをロードするapplication serviceのメソッドであり、aggregateでメソッドを呼び出し、aggregateを永続化します。コマンドハンドラの目的は、aggregateを変更することです。呼び出し元とは無関係に何を返すかはわかりません。すべてのコマンドハンドラの呼び出し元/クライアントは、新しい状態について他の何かを知りたいと思うでしょう。

コマンドの実行がブロック(同期)している場合は、コマンドが正常に実行されたかどうかを知る必要があります。次に、上位層で、ニーズに最適なクエリモデルを使用して、新しいアプリケーションの状態について知る必要がある正確なものをクエリします。

そうでなければ、コマンドハンドラーから何かを返す場合、2つの責任を与えます。1。集計状態を変更し、2読み取りモデルを照会します。

コマンド検証については、少なくとも2種類のコマンド検証があります。

  1. コマンドの健全性チェック。コマンドに正しいデータが含まれていることを確認します(つまり、電子メールアドレスが有効です)。これは、コマンドがコマンドハンドラー(アプリケーションサービス)またはコマンドコンストラクターで集約に到達する前に行われます。
  2. コマンドが集合体に到達した後(集合体でメソッドが呼び出された後)、集合体の内部で実行されるドメイン不変条件チェック、および集合体が新しい状態に変化できることをチェックします。

ただし、Presentation layer(つまり、RESTエンドポイント)のApplication layerのクライアントで、レベルを上げると、何でも返すことができ、ルールを破ることはできません。エンドポイントはユースケース後に設計されているため、すべてのユースケースで、コマンドの実行後に何を返すかを正確に把握できます。

5

@Constantin Galbenuへの返信、制限に直面しました。

@Misanthropeそして、あなたはそれらのイベントで正確に何をしますか?

@Constantin Galbenu、ほとんどの場合、もちろんコマンドの結果としてそれらは必要ありません。場合によっては、このAPIリクエストに応じてクライアントに通知する必要があります。

次の場合に非常に便利です。

  1. 例外ではなくイベントを介してエラーについて通知する必要があります。通常は、エラーが発生した場合でも、モデルを保存する必要があるときに発生します(たとえば、間違ったコード/パスワードでの試行回数をカウントします)。また、一部の人はビジネスエラーの例外をまったく使用しません-イベントのみ( http://andrzejonsoftware.blogspot.com/2014/06/custom-exceptions-or-domain-events.html =)コマンドハンドラからビジネス例外をスローすることは問題ないと考える特別な理由はありませんが、ドメインイベントを返すことはできません
  2. 集約ルート内の特定の状況でのみイベントが発生する場合。

そして、2番目のケースの例を提供できます。 LikeStrangerコマンドがあるTinderのようなサービスを作成するとします。以前に私たちを好きだった人が好きな場合、このコマンドはStrangersWereMatchedになります。一致したかどうかに応じて、モバイルクライアントに通知する必要があります。コマンドの後にmatchQueryServiceを確認するだけで一致する場合がありますが、SOMETIMES Tinderが既に一致した見知らぬ人を表示するため、一致が発生したという保証はありません2番目のデバイスなど)。

StrangersWereMatchedが今本当に起こったかどうかの応答を確認するのはとても簡単です:

$events = $this->commandBus->handle(new LikeStranger(...));

if ($events->contains(StrangersWereMatched::class)) {
  return LikeApiResponse::matched();
} else {
  return LikeApiResponse::unknown();
}

はい。たとえば、コマンドIDを導入し、Matchにモデルを読み取らせて保持することができます。

// ...

$commandId = CommandId::generate();

$events = $this->commandBus->handle(
  $commandId,
  new LikeStranger($strangerWhoLikesId, $strangerId)
);

$match = $this->matchQueryService->find($strangerWhoLikesId, $strangerId);

if ($match->isResultOfCommand($commandId)) {
  return LikeApiResponse::matched();
} else {
  return LikeApiResponse::unknown();
}

...しかし、それについて考えてみてください:単純なロジックを使用した最初の例が悪いと思うのはなぜですか?とにかくCQRSに違反することはなく、暗黙的に明示的にしました。ステートレスで不変のアプローチです。バグに遭遇する可能性が低くなります(たとえば、matchQueryServiceがキャッシュ/遅延された場合(瞬時に一貫性がない場合)、問題が発生します)。

はい、一致する事実が十分ではなく、応答用のデータを取得する必要がある場合、クエリサービスを使用する必要があります。しかし、コマンドハンドラーからイベントを受け取ることを妨げるものはありません。

1
Misanthrope

CQRSとCQSはマイクロサービスとクラス分解のようなものです。主な考え方は同じです(「小さな凝集モジュールになりやすい」)が、セマンティックレベルは異なります。

CQRSのポイントは、書き込み/読み取りモデルを分離することです。特定のメソッドからの戻り値など、このような低レベルの詳細は完全に無関係です。

次の点に注意してください ファウラーの引用

CQRSが導入する変更は、その概念モデルを更新および表示のために個別のモデルに分割することです。これは、それぞれCommandQuerySeparationの語彙に続くCommandおよびQueryと呼ばれます。

それはmodelsであり、methodsではありません。

コマンドハンドラーは、読み取りモデル以外のすべてを返します:ステータス(成功/失敗)、生成されたイベント(コマンドハンドラーの主な目標、btw:指定されたコマンドのイベントを生成する)、エラー。コマンドハンドラーは未チェックの例外を頻繁にスローします。これは、コマンドハンドラーからの出力信号の例です。

さらに、用語の作成者であるグレッグヤングは、コマンドは常に同期する(そうでなければイベントになる)と述べています。 https://groups.google.com/forum/#!topic/dddcqrs/xhJHVxDx2pM

グレッグ・ヤング

実際、非同期コマンドは存在しないと言っていました:)実際には別のイベントです。

1
Misanthrope