QueryHandler<TIn, TOut> , CommandHanler<TIn, TOut>
。
このアプローチにより、
CQRSはトップレベルのアーキテクチャではないため、従来のApplication Serviceにも同じデコレータが必要です。 カットの下で、その方法を説明します。
横断的関心事とは何ですか?
横断的関心事は、AOPからの用語です。 モジュールの「補助」機能はクロスカットに関連しており、実行中のタスクに直接関連しているわけではありませんが、たとえば次のように必要です。
- 同期する
- エラー処理
- 検証
- トランザクション管理
- キャッシング
- ロギング
- 監視
通常、このロジックをコアから分離するのは困難です。 以下の2つの例をご覧ください。
横断的な懸念のないコード
public Book GetBook(int bookId) => dbContext.Books.FirstorDefault(x => x.Id == bookId);
横断的関心事コード
public Book GetBook(int bookId) { if (!SecurityContext.GetUser().HasRight("GetBook")) throw new AuthException("Permission Denied"); Log.debug("Call method GetBook with id " + bookId); Book book = null; String cacheKey = "getBook:" + bookId; try { if (cache.contains(cacheKey)) { book = cache.Get<Book>(cacheKey); } else { book = dbContext.Books.FirstorDefault(x => x.Id == bookId); cache.Put(cacheKey, book); } } catch(SqlException e) { throw new ServiceException(e); } Log.Debug("Book info is: " + book.toString()); return book; } }
1行ではなく、20行以上になりました。 そして最も重要なのは、このコードを何度も繰り返す必要があることです。 デコレータが助けになります。
デコレータ(Eng。デコレータ)-追加の動作をオブジェクトに動的に接続するために設計された構造デザインテンプレート Decoratorパターンは、機能を拡張するためのサブクラス化の実践に対する柔軟な代替手段を提供します。
CQRSのデコレータ
たとえば、グローバル検証を有効にします。 そのようなデコレータを宣言するだけで十分です。
public class ValidationCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> { private readonly IValidator validator; private readonly ICommandHandler<TCommand> decoratee; public ValidationCommandHandlerDecorator(IValidator validator, ICommandHandler<TCommand> decoratee) { this.validator = validator; this.decoratee = decoratee; } void ICommandHandler<TCommand>.Handle(TCommand command) { // validate the supplied command (throws when invalid). this.validator.ValidateObject(command); // forward the (valid) command to the real command handler. this.decoratee.Handle(command); } }
すべてのコマンドハンドラーに登録します。
container.RegisterDecorator( typeof(ICommandHandler<>), typeof(ValidationCommandHandlerDecorator<>));
これで、
ICommandHandler
インターフェイスのすべての実装で、デコレーターで検証が行われ、ハンドラーコードは単純なままになります。
public interface ICommandHandler<in TInput, out TOutput> { TOutput Handle(TInput command); } public class AddBookCommandHandler: ICommandHandler<BookDto, int> { public bool Handle(BookDto dto) { var entity = Mapper.Map<Book>(dto); dbContext.Books.Add(entity); dbContext.SaveChanges(); return entity.Id; } }
ただし、
ICommandHandler
および
IQueryHandler
デコレーターのセットを記述する必要があり
IQueryHandler
。 もちろん、デリゲートの助けを借りてこの問題を回避できます。 しかし、それはあまり美しくなく、CQRS、つまり CQRSが正当化されるアプリケーションのいくつかの別個の境界付きコンテキストでのみ。
IHandler
とApplication Service
違い
サービス層でデコレータをグローバルに使用する場合の主な問題は、サービスインターフェイスが汎用ハンドラーよりも複雑であることです。 すべてのハンドラーがこの汎用インターフェイスを実装する場合:
public interface ICommandHandler<in TInput, out TOutput> { TOutput Handle(TInput command); }
そのサービスは通常、ユースケースごとに1つのメソッドを実装します
public interface IAppService { ResponseType UseCase1(RequestType1 request); ResponseType UseCase2(RequestType2 request); ResponseType UseCase3(RequestType3 request); //... ResponseType UseCaseN(RequestTypeN request); }
検証に抽象デコレータを使用することはもうできません。各サービスにデコレータを作成する必要があります。これにより、検証コードを1回記述するというアイデアが失われ、忘れてしまいます。 さらに、メソッドを修飾するよりもメソッド内に検証コードを記述する方が簡単です。
Mediatr
CQRSの場合、
IRequestHandler
インターフェースを入力し、それを
Command
and
Query
使用することにより、重複したデコレーターの問題を解決できます。 この場合の読み取りサブシステムと書き込みサブシステムの区分は、命名規則にあります。
SomeCommandRequestHandler: IRequestHandler
は明らかにコマンドハンドラーであり、
SomeQueryRequestHandler: IRequestHandler
はリクエストです。 このアプローチはMediatRで実装されています 。 デコレータの代替として、ライブラリは動作メカニズムを提供します 。
IRequestHandler
→ IUseCaseHandler
IRequestHandler
インターフェイスの名前を
IRequestHandler
変更しないのはなぜ
IUseCaseHandler
。 要求ハンドラーとコマンドは全体的な抽象化であるため、それぞれがユースケース全体を処理します。 次に、CQRSアーキテクチャを次のように書き換えます。
public interface IUseCaseHandler<in TInput, out TOutput> { TOutput Handle(TInput command); } public interface IQueryHandler<in TInput, out TOutput> : IUseCaseHandler<in TInput, out TOutput> where TInput: IQuery<TOutput> { } public interface ICommandHandler<in TInput, out TOutput> : IUseCaseHandler<in TInput, out TOutput> where TInput: ICommand<TOutput> { }
現在、「一般的な」デコレータを
IUseCaseHandler
に掛けることができます。 同時に、たとえば独立したトランザクション管理のために、
ICommandHandler
と
IQueryHandler
デコレータを
IQueryHandler
に
IQueryHandler
ます。
アプリケーションサービスのデコレータ
明示的な実装を使用する場合は、Application Servicesで
IUseCaseHandler
インターフェイスを使用することもできます。
public class AppService : IAppService : IUseCaseHandler<RequestType1 , ResponseType1> : IUseCaseHandler<RequestType2 , ResponseType2> : IUseCaseHandler<RequestType3, ResponseType3> //... : IUseCaseHandler<RequestTypeN, RequestTypeN> { public ResponseType1 UseCase1(RequestType1 request) { //... } IUseCaseHandler<RequestType1 , ResponseType1>.Handle(RequestType1 request) => UseCase1(request); //... ResponseTypeN UseCaseN(RequestTypeN request) { //... } IUseCaseHandler<RequestTypeN , ResponseTypeN>.Handle(RequestTypeN request) => UseCaseN(request); //... }
デコレータは汎用インターフェイスにのみ適用されるため、アプリケーションコードでは、
IUseCaseHandler
ではなく、
IAppService
インターフェイスを使用する必要があります。
エラー処理
検証の例に戻りましょう。 以下のコードのバリデーターは、無効なコマンドを受け取ったときに例外をスローします。 例外を使用してユーザー入力を処理することは議論の余地がある問題です。
void ICommandHandler<TCommand>.Handle(TCommand command) { // validate the supplied command (throws when invalid). this.validator.ValidateObject(command); // forward the (valid) command to the real command handler. this.decoratee.Handle(command); }
メソッドの署名で実行が失敗する可能性があることを明示的に示す場合は、上記の例を次のように書き換えることができます。
Result ICommandHandler<TCommand>.Handle(TCommand command) { return this.validator.ValidateObject(command) && this.decoratee.Handle(command); }
したがって、デコレータを戻り値のタイプでさらに分離することが可能になります。 たとえば、
Result
を返すロギングメソッドは、追跡されていない値を返すメソッドと同じで
Result
ません。