デコレータ、クロスカッティング機能、CQRS、および階層化アーキテクチャについて

SimpleInjectorの開発者は、特に次の形式のジェネリックとの組み合わせで、 「デコレータ」非常に好きです。
QueryHandler<TIn, TOut> , CommandHanler<TIn, TOut>

このアプローチにより、 登録やSMS インターセプト 、およびFodyPostSharpなどの特別なストリートマジックなしで、一般に横断的関心事と呼ばれるハンドラーを「ハング」させることができます。

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が正当化されるアプリケーションのいくつかの別個の境界付きコンテキストでのみ。

IHandlerApplication 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で実装されています 。 デコレータの代替として、ライブラリは動作メカニズムを提供します

IRequestHandlerIUseCaseHandler


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に掛けることができます。 同時に、たとえば独立したトランザクション管理のために、 ICommandHandlerIQueryHandlerデコレータをIQueryHandlerIQueryHandlerます。

アプリケーションサービスのデコレータ


明示的な実装を使用する場合は、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ません。

Source: https://habr.com/ru/post/J353258/


All Articles