web-dev-qa-db-ja.com

Command自体でメソッドを処理するのではなく、クラスCommandHandlerをHandle()で分離する理由

私は S#arp Architecture を使用して実装されたCQRSパターンの一部を次のようにしています:

public class MyCommand
{
    public CustomerId { get; set; }

    // some other fields
}

public class MyCommandHandler<MyCommand> : ICommandHandler<MyCommand, CommandResult>
{
    Handle(MyCommand command)
    {
        // some code for saving Customer entity

        return CommandResult.Success;
    }
}

クラスCommandにデータの両方の処理メソッドが含まれているのはなぜですか?コマンドのプロパティとは別にコマンド処理ロジックをテストする必要がある、テストのしやすさのようなものですか?または、1つのコマンドをICommandHandler<MyCommand, CommandResult>の異なる実装で処理する必要があるという、ビジネス要件が頻繁に発生しますか?

13
rgripper

おかしなことに、この質問は、私が取り組んでいる通信ライブラリについて、エンジニアの1人とまったく同じ会話を思い出しました。

コマンドの代わりに、Requestクラスがあり、次にRequestHandlerがありました。デザインはあなたが説明しているものと非常によく似ていました。あなたが持っている混乱の一部は、あなたが英語の単語「コマンド」を見て、即座に「動詞、行動...など」を考えることだと思います。

しかし、この設計では、コマンド(または要求)を手紙と考えてください。または、郵便サービスが何であるかを知らない人のために、電子メールを考えてください。それは単にコンテンツであり、そのコンテンツがどのように処理されるべきかから切り離されています。

なぜこれを行うのですか?コマンドパターンの最も単純なケースでは、理由はなく、このクラスに直接作業を実行させることができます。ただし、アクション/コマンド/リクエストがある程度の距離を移動する必要がある場合は、設計のようにデカップリングを行うことは理にかなっています。たとえば、ソケット、パイプ、ドメインとインフラストラクチャの間などです。または、アーキテクチャでコマンドを永続化する必要がある場合があります(たとえば、コマンドハンドラは一度に1つのコマンドを実行できますが、いくつかのシステムイベントにより、200のコマ​​ンドが到着し、最初の40のプロセスがシャットダウンした後)。その場合、単純なメッセージのみのクラスがあるため、メッセージパーツだけをJSON/XML/binary/whateverにシリアル化し、コマンドハンドラーが処理できるようになるまでパイプラインに渡すことが非常に簡単になります。

CommandHandlerからCommandを分離するもう1つの利点は、並列継承階層のオプションが利用できるようになったことです。たとえば、すべてのコマンドは、シリアル化をサポートする基本コマンドクラスから派生できます。そして、20のコマンドハンドラーのうち4つが類似性の高いものである可能性があります。これで、付属のハンドラーの基本クラスからそれらを派生させることができます。 1つのクラスでデータとコマンドの処理を行う場合、このタイプの関係はすぐに制御不能になります。

デカップリングのもう1つの例は、コマンドに必要な入力が非常に少ない(2つの整数と文字列など)場合でも、中間のメンバー変数にデータを格納する場合の処理​​ロジックが十分に複雑な場合です。 50個のコマンドをキューに入れる場合、そのすべての中間ストレージにメモリを割り当てたくないので、CommandHandlerからCommandを分離します。これで、50個の軽量データ構造をキューに入れ、より複雑なデータストレージは、コマンドを処理しているCommandHandlerによって1回だけ(またはNハンドラーがある場合はN回)割り当てられます。

14
DXM

通常の コマンドパターン は、データと動作を単一のクラスに含めることです。この種の「コマンド/ハンドラパターン」は少し異なります。通常のパターンと比較した場合の唯一の利点は、コマンドをフレームワークに依存させないという利点があります。たとえば、コマンドはDBアクセスを必要とする場合があるため、何らかのDBコンテキストまたはセッションが必要です。つまり、コマンドはフレームワークに依存しています。しかし、このコマンドはドメインの一部である可能性があるため、 依存関係の逆転の原則 に従ってフレームワークに依存しないようにします。入力パラメータと出力パラメータを動作から分離し、それらを接続するディスパッチャを用意すると、これを修正できます。

一方、コマンドの継承と構成の両方の利点は失われます。それは本当の力だと思います。

また、マイナーなnitpick。名前にコマンドがあるからといって、CQRSの一部にはなりません。それは、もっと根本的な何かについてです。この種の構造は、コマンドとしてもクエリとしても機能します。

2
Euphoric