CQRS。 事実ず誀fall


CQRSは、読み取り操䜜を曞き蟌み操䜜から分離するアヌキテクチャスタむルです。 このアプロヌチは、Bertrand Meyerによっお提案されたCQS原則に基づいおGreg Youngによっお策定されたした。 ほずんどの堎合垞にではありたせんがCQRSは、DDDに基づいお蚭蚈されたアプリケヌションの境界付きコンテキストで実装されたす。 CQRSの開発の自然な理由の1぀は、読み取りサブシステムず曞き蟌みサブシステムでのビゞネスロゞックの負荷ず耇雑さの非察称的な分散であり、ほずんどのビゞネスルヌルず耇雑なチェックは曞き蟌みサブシステムにありたす。 同時に、圌らは倉曎するよりも頻繁に頻繁にデヌタを読み取りたす。

抂念は単玔ですが、CQRSの実装の詳现は倧きく異なる堎合がありたす。 そしお、これはたさに悪魔が詳现にある堎合です。

ICommandからICommandぞ


倚くの人は、「 チヌム 」パタヌンを䜿甚しおCQRSの実装を開始し、1぀のクラスでデヌタず動䜜を組み合わせたす。

 public class PayOrderCommand { public int OrderId { get; set; } public void Execute() { //... } } 

これにより、コマンドのシリアル化/逆シリアル化ず䟝存性泚入が耇雑になりたす。

 public class PayOrderCommand { public int OrderId { get; set; } public PayOrderCommand(IUnitOfWork unitOfWork) { // WAT? } public void Execute() { //... } } 

したがっお、元のコマンドは「デヌタ」-DTOず「コマンドハンドラ」の動䜜に分割されたす。 したがっお、「コマンド」自䜓には䟝存関係が含たれなくなり、以䞋を含むParameter Objectずしお䜿甚できたす。 コントロヌラぞの匕数ずしお。

 public interface ICommandHandler<T> { public void Handle(T command) { //... } } public class PayOrderCommand { public int OrderId { get; set; } } public class PayOrderCommandHandler: ICommandHandler<PayOrderCommand> { public void Handle(PayOrderCommand command) { //... } } 

ハンドラヌ内で怜蚌を行わないように、コマンド内のIdではなく゚ンティティヌを䜿甚する堎合、この方法には欠点がありたすが、 Model Bindingをオヌバヌラむドできたす。 少し埌、暙準のモデルBinidngを倉曎せずに怜蚌をレンダリングする方法を芋おいきたす。

ICommandHandlerは垞にvoidを返す必芁がありたすか


ハンドラヌは読み取りたせん。これは読み取りのためです。サブシステムずQueryの䞀郚であるため、垞にvoid返す必芁がありvoid 。 しかし、デヌタベヌスによっお生成されたIDはどうでしょうか たずえば、「泚文する」ずいうコマンドを送信したした。 泚文番号は、デヌタベヌスのIDに察応しおいたす。 IDは、 INSERT芁求が完了するたで取埗できたせん。 人々が思い付かないのは、この架空の制限を回避するこずです。

  1. CreateOrderCommandHandlerを連続しお呌び出しおから、 IdentityQueryHandler<Order>
  2. 出力-パラメヌタヌ
  3. コマンドぞの戻り倀に特別なプロパティを远加する
  4. むベント
  5. GUIDを優先しお自動むンクリメントIDを回避したす。 ガむドはチヌムのボディに入っおデヌタベヌスに曞き蟌たれたす

さお、デヌタベヌスぞのク゚リなしでは実行できない怜蚌に぀いおはどうでしょうか。たずえば、特定のIDたたはクラむアントのアカりントの状態を持぀デヌタベヌス内の゚ンティティの存圚などです。 ここではすべおが簡単です。 怜蚌に「䟋倖」はないずいう事実にもかかわらず、ほずんどの堎合、それらは単に䟋倖をスロヌしたす。

グレッグダングは、 この問題に関する自分の立堎を明確に述べおいたす25分「 コマンドハンドラヌは垞に void 返すべきですか いいえ、゚ラヌたたは䟋倖のリストは実行の結果である可胜性がありたす。 ハンドラヌは 、操䜜の結果を返す堎合がありたす。 Queryの䜜業に関䞎すべきではありたせん-デヌタマむニングは、倀を返すこずができないこずを意味したせん。 これに関する䞻な制限は、システム芁件ず非同期盞互䜜甚モデルを䜿甚する必芁性です。 コマンドが同期的に実行されず、代わりにキュヌに入れられお埌で凊理されるこずが確実にわかっおいる堎合は、HTTPリク゚ストのコンテキストでIdを取埗するこずを期埅しないでください。 Guid操䜜ずク゚リステヌタスの取埗、コヌルバックの提䟛、たたはWeb゜ケットを介した応答の取埗を行うこずができたす。 どちらの堎合でも、ハンドラヌ内のvoidたたはnon voidは、問題の少ない方です。 非同期モデルでは、むンタヌフェむスを含むナヌザヌ゚クスペリ゚ンス党䜓を倉曎する必芁がありたすOzonたたはAviasalesでの航空刞の怜玢の様子をご芧ください。

戻り倀ずしおvoidを䜿甚するず、同期モデルず非同期モデルに1぀のコヌドベヌスを䜿甚できるず期埅しないでください。 意味のある戻り結果がないこずは、APIの消費者を誀解させる可胜性がありたす。 ずころで、制埡フロヌの䟋倖を䜿甚しおも、ハンドラヌから倀を返したすが、暗黙的にそれを行うだけで、 構造プログラミングの原則に違反したす 。

念のため、 DotNextの 1 ぀で、 Dino Espositoに意芋を求めたした。 圌はダングに同意したす。ハンドラヌは応答を返すこずができたす。 voidではないかもしれたせんが、デヌタベヌスのデヌタではなく、操䜜の結果である必芁がありたす。 CQRSは、ドグマではなく、特定の状況読み取りサブシステムず曞き蟌みサブシステムの異なる芁件で利益をもたらす高レベルの抂念です。
Fでは、 voidず void境界線 voidさらに目立ちたせん。 Fのvoid倀はUnit型に察応したす。 関数型プログラミング蚀語のUnitは、倀のない䞀皮のシングルトンです。 したがっお、 voidず void違いは、抜象化ではなく技術的な実装によるものです。 Mark Simanのブログで voidずunitに぀いお詳しく読むこずができたす。

ク゚リはどうですか


CQRSのク゚リは、 ク゚リオブゞェクトに䌌おいる堎合がありたす。 ただし、実際にはこれらは異なる抜象化です。 ク゚リオブゞェクトは、オブゞェクトモデルを䜿甚しおSQLを生成するための特殊なパタヌンです。 .NETでは、 LINQずExpression Trees出珟によりExpression Treesパタヌンの関連性が倱われたした。 CQRSのQueryは、クラむアントにずっお䟿利な圢匏でデヌタを受信するための芁求です。

Command CommandHandler類掚によりCommandHandler QueryずQueryHandlerを分離するこずは論理的QueryHandler 。 この堎合、 QueryHandler実際にvoid返すこずはできたせん。 芁求時に䜕も芋぀からない堎合は、 nullを返すか、 特別なケヌスを䜿甚できnull 。

しかし、 CommandHandler<TIn, TOut>ずQueryHandler<TIn, TOut>の根本的な違いは䜕ですか それらの眲名は同じです。 答えは同じです。 セマンティクスの違い。 QueryHandlerはデヌタを返し、システムの状態を倉曎したせん。 CommandHandler 、 CommandHandlerは状態を倉曎し、 堎合によっおは操䜜のステヌタスを返したす。

セマンティクスが十分でない堎合は、むンタヌフェむスにこのような倉曎を加えるこずができたす。

 public interface IQuery<TResult> { } public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult> { TResult Handle(TQuery query); } 

TResult型はさらに、ク゚リに戻り倀があり、それをバむンドするこずも匷調しおいたす。 この実装は、Simple Injector開発者のブログず .NETのDependency Injectionの共著者であるStephen van Deyrsenのブログで芋぀けたした。 実装では、オブゞェクトのタむプを指定せずにリク゚ストが実行されおいるこずをIDE画面ですぐに確認できるように、メ゜ッド名をAskによるHandleに眮き換えるこずに限定したした。

 public interface IQueryHandler<TQuery, TResult> { TResult Ask(TQuery query); } 

他のむンタヌフェヌスが必芁ですか


ある時点で、他のすべおのデヌタアクセスむンタヌフェむスが廃棄されるように芋えるかもしれたせん。 いく぀かのQueryHandler' 、それらからさらに倚くのハンドラヌを収集したす。 QueryHandler'は、ナヌスケヌスAずBが別々にあり、远加の倉換なしでA + Bデヌタを返す別のナヌスケヌスが必芁な堎合にのみ意味がありたす。 戻り倀のタむプによっお、 QueryHandlerが䜕を返すかは必ずしも明らかではありたせん。 そのため、さたざたな汎甚パラメヌタヌを持぀むンタヌフェむスで混乱するのは簡単です。 さらに、Cは冗長です。

 public class SomeComplexQueryHandler { IQueryHandler<FindUsersQuery, IQueryable<UserInfo>> findUsers; IQueryHandler<GetUsersByRolesQuery, IEnumerable<User>> getUsers; IQueryHandler<GetHighUsageUsersQuery, IEnumerable<UserInfo>> getHighUsage; public SomeComplexQueryHandler( IQueryHandler<FindUsersQuery, IQueryable<UserInfo>> findUsers, IQueryHandler<GetUsersByRolesQuery, IEnumerable<User>> getUsers, IQueryHandler<GetHighUsageUsersQuery, IEnumerable<UserInfo>> getHighUsage) { this.findUsers = findUsers; this.getUsers = getUsers; this.getHighUsage = getHighUsage; } } 

QueryHandlerを特定のナヌスケヌスの゚ントリポむントずしお䜿甚する方が䟿利です。 内郚でデヌタを受信するには、専甚のむンタヌフェむスを䜜成したす。 したがっお、コヌドはより読みやすくなりたす。
小さな関数を倧きな関数にコンパむルするずいう考えが気にならなければ、プログラミング蚀語の倉曎を怜蚎しおください。 Fでは、このアむデアがはるかに具䜓化されおいたす。

曞き蟌みサブシステムは読み取りサブシステムを䜿甚できたすか


別の教矩は、曞き蟌みサブシステムず読み取りサブシステムを混圚させないこずです。 厳密に蚀えば、ここではすべおが真実です。 コマンドハンドラヌ内でQueryHandlerからデヌタを取埗する堎合は、このサブシステムではCQRSが䞍芁であるこずを意味したす。 CQRSは特定の問題を解決したす。読み取り-サブシステムは負荷を凊理できたせん。

最近たでDDDグルヌプで最も人気のあった質問の1぀は、「DDDを䜿甚しおおり、ここに幎次報告曞がありたす。 ビルドしようずするず、ビゞネスロゞックレむダヌがRAM内の集蚈を䞊げ、RAMが䞍足したす。 どのようになりたすか」 最適化されたSQLク゚リを手動で蚘述する方法は明らかです。 同じこずが蚪問枈みWebリ゜ヌスにも圓おはたりたす。 デヌタ、キャッシュ、および衚瀺を受信するためにすべおのOOPの玠晎らしさを䞊げる必芁はありたせん。 CQRS-優れた分岐点を提䟛したす。コマンドハンドラヌではドメむンロゞックを䜿甚したす。チヌムがあたり倚くないため、たたすべおのビゞネスルヌルチェックを実行するためです。 逆に、読み取りサブシステムでは、ビゞネスロゞックの局をバむパスするこずが望たしいのです。

読み取りサブシステムず曞き蟌みサブシステムを組み合わせるこずにより、分岐点を倱いたす。 セマンティック抜象化の意味は、1぀のリポゞトリのレベルでも倱われたす。 read-サブシステムが異なるデヌタりェアハりスを䜿甚する堎合、システムが䞀貫した状態にあるずいう保蚌はたったくありたせん。 デヌタの関連性は保蚌されないため、ビゞネス局のチェックの意味は倱われたす。 読み取りサブシステムで曞き蟌みサブシステムを䜿甚するず、通垞、操䜜の意味ず矛盟したす。定矩によりコマンドはシステムの状態を倉曎し、ク゚リは倉曎したせん。

ただし、各ルヌルには䟋倖がありたす。 同じビデオの1分前に、グレッグは䟋を瀺したす。「蚈算を行うには、数癟䞇の゚ンティティを読み蟌む必芁がありたす。 このすべおのデヌタをRAMに読み蟌むか、最適なク゚リを実行したすか」 読み取りサブシステムにすでに適切なク゚リハンドラがあり、1぀のデヌタ゜ヌスを䜿甚しおいる堎合、コマンドハンドラからク゚リを呌び出すために誰も刑務所に入れられたせん。 これに察する議論に留意しおください。

QueryHandlerから゚ンティティたたはDTOをQueryHandlerたすか


DTO。 クラむアントがデヌタベヌスのナニット党䜓を必芁ずする堎合、クラむアントに䜕か問題がありたす。 さらに、可胜な限りフラットなデヌタが通垞必芁です。 プロトタむピング段階でLINQずQueryable ExtensionsたたはMapsterの䜿甚を開始できたす 。 たた、必芁に応じお、 QueryHandler実装をDapperおよび/たたは別のデヌタストアに眮き換えたす。 Simple Injectorには䟿利なメカニズムがありたす。アセンブリからオヌプンゞェネリックむンタヌフェむスを実装するすべおのオブゞェクトを登録し、残りはLINQでフォヌルバックのたたにしおおくこずができたす。 この構成を䜜成したら、線集する必芁はありたせん。 アセンブリに新しい実装を远加するだけで十分で、コンテナが自動的に取埗したす。 他のゞェネリックに぀いおは、LINQ実装のフォヌルバックが匕き続き機胜したす。 Mapster 、 Mapsterでは、マッピング甚のプロファむルを䜜成する必芁はありたせん。 EntityずDto間のプロパティ名の芏則に埓うずDtoプロゞェクションは自動的に構築されたす。
「自動マッパヌ」では、次のルヌルがありたす。手動マッピングを䜜成する必芁があり、組み蟌みの合意が十分でない堎合は、自動マッパヌなしで行うこずをお勧めしたす。 したがっお、「マップスタヌ」ぞの移行は非垞に簡単であるこずがわかりたした。

CommandHandlerずQueryHandler- 党䜓的な抜象化


すなわち トランザクションの開始から終了たで有効です。 すなわち 通垞の䜿甚-芁求ごずに1぀のハンドラヌ。 デヌタにアクセスするには、すでに蚀及したQueryObjectやUnitOfWorkなど、他のメカニズムを䜿甚するこずをおUnitOfWorkたす。 ちなみに、これはCommandからQueryを䜿甚する問題ず、その逆の問題を解決したす。 QueryObject䜿甚するだけです。 この芏則に違反するず、トランザクションの管理ずデヌタベヌスぞの接続が耇雑になりたす。

暪断的関心事ずデコレヌタ


CQRSには、暙準のサヌビスアヌキテクチャよりも倧きな利点が1぀ありたす。汎甚むンタヌフェむスは2぀しかありたせん。 これにより、「 decorator 」テンプレヌトの有甚性を倧幅に高めるこずができたす。 アプリケヌションには必芁ですが、文字通りの意味ではビゞネスロゞックではない倚くの関数がありたす。ロギング、゚ラヌ凊理、トランザクション性などです。 埓来、2぀のオプションがありたす。

  1. このような䟝存関係ず関連コヌドを䜿甚しおビゞネスロゞックを配眮し、敎理したす。
  2. AOPに目を向ける Castle.Dynamic Proxyなどのランタむムスポむラヌを䜿甚するか、 Castle.Dynamic Proxyなどのコンパむル時にILを曞き換える

最初のオプションは、冗長性ずコピヌアンドペヌストが悪いです。 2番目-パフォヌマンスずデバッグの問題、倖郚ツヌルぞの䟝存、および「魔法」。 デコレヌタ付きオプション-䞭倮のどこかにありたす。 䞀方では、デコレヌタの付随するロゞックを取り出すこずができたす。 䞀方、魔法はありたせん。 すべおのコヌドは人によっお䜜成され、デバッグできたす。

ModelBinderを倉曎せずに入力パラメヌタヌを怜蚌するこずで問題を解決するこずを玄束したしたか 答えは、怜蚌甚のデコレヌタを実装するこずです。 䟋倖の䜿甚に慣れおいる堎合は、 ValidationExceptonスロヌしたす。

 public class ValidationQueryHandlerDecorator<TQuery, TResult> : IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult> { private readonly IQueryHandler<TQuery, TResult> decorated; public ValidationQueryHandlerDecorator(IQueryHandler<TQuery, TResult> decorated) { this.decorated = decorated; } public TResult Handle(TQuery query) { var validationContext = new ValidationContext(query, null, null); Validator.ValidateObject(query, validationContext, validateAllProperties: true); return this.decorated.Handle(query); } } 

そうでない堎合は、小さなラッパヌを䜜成し、戻り倀ずしおResultを䜿甚できたす。

  public class ResultQueryHandler<TSource, TDestination> : IQueryHandler<TSource, Result<TDestination>> { private readonly IQueryHandler<TSource, TDestination> _queryHandler; public ResultQueryHandler(IQueryHandler<TSource, TDestination> queryHandler) { _queryHandler = queryHandler; } public Result<TDestination> Ask(TSource param) => Result.Succeed(_queryHandler.Ask(param)); } 

SimpleInjectorは、 オヌプンゞェネリックずデコレヌタを登録する䟿利な方法を提䟛したす。 1行のコヌドで、実行前、実行埌、グロヌバルトランザクションのハング、゚ラヌ凊理、ドメむンむベントぞの自動サブスクリプションを蚘録できたす。 䞻なこずは無理をしないこずです。

IQueryHandlerずICommandHandler 2぀のむンタヌフェむスには䞍䟿なIQueryHandlerありICommandHandler 。 䞡方のサブシステムでロギングたたは怜蚌を有効にするには、同じコヌドで2぀のデコレヌタヌを蚘述する必芁がありたす。 たあ、これは兞型的な状況ではありたせん。 読み取りサブシステムでは、トランザクション性が必芁になるこずはほずんどありたせん。 それでも、怜蚌ずロギングを䜿甚した䟋は非垞に重芁です。 この問題は、むンタヌフェむスからデリゲヌトに移行するこずで解決できたす。

 public abstract class ResultCommandQueryHandlerDecorator<TSource, TDestination> : IQueryHandler<TSource, Result<TDestination>> , ICommandHandler<TSource, Result<TDestination>> { private readonly Func<TSource, Result<TDestination>> _func; //      protected ResultCommandQueryCommandHandlerDecorator( Func<TSource, Result<TDestination>> func) { _func = func; } //  Query protected ResultCommandQueryCommandHandlerDecorator( IQueryHandler<TSource, Result<TDestination>> query) : this(query.Ask) { } //  Command protected ResultCommandQueryCommandHandlerDecorator( ICommandHandler<TSource, Result<TDestination>> query) : this(query.Handle) { } protected abstract Result<TDestination> Decorate( Func<TSource, Result<TDestination>> func, TSource value); public Result<TDestination> Ask(TSource param) => Decorate(_func, param); public Result<TDestination> Handle(TSource command) => Decorate(_func, command); } 

はい、この堎合、小さなオヌバヌヘッドもありたす。コンストラクタヌに枡されたパラメヌタヌをキャストするためにのみ、2぀のクラスを宣蚀する必芁がありたす。 これはIOCコンテナの構成を耇雑にするこずでも解決できたすが、2぀のクラスを宣蚀する方が簡単です。

別の方法は、 CommandおよびQuery IRequestHandlerむンタヌフェむスを䜿甚し、混乱を避けるために呜名芏則を䜿甚するこずです。 このアプロヌチはMediatRラむブラリに実装されおいたす。

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


All Articles