EntityFrameworkの分野駆動設計実践【拡張読解】:CQRSアーキテクチャモデル


原文住所:http://www.cnblogs.com/daxnet/archive/2010/08/02/1790299.html
 
CQRSアーキテクチャモード
本稿では,CQRS(Command Query Responsibility Segregation,コマンドクエリ職責分離)モードについて比較的包括的に紹介する.このように、CQRSは古典的な分野駆動設計の実践を破り、CQRSを応用する全過程で、あなたは別の異なる角度で問題を考え、解決策を求めることができます.例えば、CQRSはイベント駆動アーキテクチャであり、イベントはどのように生成され、どのように処理されますか?イベント駆動アーキテクチャは、どのようなアプリケーションシステムに適していますか?CQRSの中の倉庫は、経典DDDの中の倉庫と何が違いますか?これらの問題は、私たちに無限の思考空間を残した.
背景
CQRSについて話す前に、CQS(Command-Query Separation、コマンドクエリー)モードについて説明します.名前的には両者に差はないが,CQRSはDDDの実践にCQS理論を導入して現れたアーキテクチャモデルと言えるだろう.CQSモードは、有名なソフトウェアの巨匠Bertrand Meyer(Eiffel言語の父、オブジェクトの開閉原則OCPの提出者)が最初に提案した.彼は、オブジェクトの行為は2つしかないと考えている:コマンドとクエリー、3つ目の状況は存在しない.彼自身の言葉で言えば、「質問は永遠に答えを変えることができない」ということだ.CQSによれば、どの方法でもコマンドとクエリーの2つの部分に分割できます.たとえば、次のコードがあります.
行番号を隠す?コード#コード#
private int i = 0;
  • private int Add(int factor)
  • {
  •     i += factor;
  •     return i;
  • }

  • 上のコードでは、Domain Event:CustomerCreatedEventとChangeName Eventの2つが定義されています.Customer集約ルートのコンストラクション関数では、イベントのサブスクライバがCustomerオブジェクトを初期化するために、CustomerCreatedEventが直接トリガーされます.一方、Customer集約ルートのChangeNameメソッドでは、イベントの購読者がCustomerのfirst nameとlast nameを変更するために、ChangeNameEventが直接トリガーされます.CustomerのベースクラスSourcedAggregationRootは、レルムイベントがトリガーされたときにReflectionメカニズムによって内部のイベント処理関数を取得し、その関数を呼び出してイベント処理を完了する.上記の例では、DoChangeNameとDoCreateCustomerの2つの方法について説明します.ここで注意したいのは、DoChangeNameやDoCreateCustomerのようなイベント処理関数では、オブジェクトの状態に対する設定ロジックのみを含めることができます.他の操作を導入すると、これらの操作によってオブジェクトの状態が変化しないことを保証することは難しいからです.
    上記の設計を深く考えると、モデルオブジェクトが非常に膨大になったり、時間が経つにつれて領域イベントが多くなったりすると、Event Sourcingによって集約ルートを再構築するプロセスもますます時間がかかります.なぜなら、構築するたびに最初に発生したイベントから開始する必要があるからです.この問題を解決するためにEvent Sourcingは「スナップショット(Snapshots)」を導入した.
    スナップショット(Snapshots)
    Snapshotのデザインは実は簡単です.標準的なCQRS実装では,「N個のレルムイベントが発生するごとにオブジェクトに対してSnapshotを1回行う」という簡単なルールを採用している.設計者は実際に自分の実情に基づいてNの値を定義することができ、特定のSnapshotルールを選択してオブジェクトの再構築の効率を高めることもできます.倉庫を通じて集約ルートエンティティを取得する必要がある場合、倉庫はまずSnapshot Storeから最近のスナップショットを取得し、その後、このスナップショットによって復元された集約ルートエンティティにスナップショットを1つずつ適用した後に発生したレルムイベントを取得し、オブジェクトの再構築プロセスを大幅に高速化します.スナップショットは、通常、GoF Mementoモードで実装される.CQRSがスナップショットの概念を導入したのは、オブジェクトの再構築の効率的な問題を解決するためだけであり、レルムイベントが表す意味に代わるものではないことに注意してください.すなわち、スナップショットが導入されても、スナップショット以前のすべてのイベントをイベントストレージ(Event Store)から削除できるとは限らない.なぜなら、領域イベントを記録する目的は、SnapshotsではなくEvent Sourcingのためです.
    EntityFramework之领域驱动设计实践【扩展阅读】:CQRS体系结构模式_第1张图片
     
    イベントストア(Event Store)
    通常、イベントストレージは、レルムオブジェクトのステータスの変更を引き起こすすべてのレルムイベントを保存するリレーショナル・データベースです.上述したように,CQRS構造のシステム実装では,データベースはオブジェクトの現在の状態を直接保存するのではなく,オブジェクトの状態の変化を引き起こす領域イベントのみを保存する.そこで,データベースのデータ構造は非常に単一であり,単純な領域イベントデータである.イベントデータの書き込み、読み取りは非常に簡単で高速になり、ORMの介入を必要とせず、SQLまたはストレージプロセスを直接使用してイベントストレージを操作すれば、簡単で効率的です.ここまで読むと、システムはEvent Storeと呼ばれるメカニズムでレルムイベントを保存していますが、このEvent Storeはシステム全体のデータストレージの核心となっています.さらに考えてみると、Event Storeのイベントデータは、倉庫で「保存」操作を実行する際に、レルムモデルから収集して書き込まれたものであり、つまり、最新の、最も真実なデータがレルムモデルに存在し、DDDのレルム向けの考え方にぴったり合っていることを意味し、同時にもう一つの深い考えを引き出した:In Memory Domain!
     
    構造に戻る
    「オブジェクト状態」、「イベントトレース」、「スナップショット」および「イベントストレージ」についての議論が完了した後、CQRS全体の構造をさらに明らかにします.上記の【CQRSアーキテクチャモード】図では、ユーザ操作は、コマンド部(図中の上半分)とクエリー部(図中の下半分)に分けられる.
  • ユーザとレルム層とのインタラクションは、ユーザがCommand Serviceを介してレルムモデルにコマンドを送信するコマンドとして行われる.Command Serviceは、一般に、NET WCF Service.Command Busは、コマンドを受信した後、コマンドエフェクタにコマンドを割り当てて実行します(GoF Commandモードを参照してください.TBD:CQRS実装に適した他の方法を選択できます).コマンドアクチュエータは、コマンドを実行するときにレルムイベントでオブジェクトの状態を変更し、レルムオブジェクトを倉庫で保存します.一方、倉庫は、オブジェクトの状態を外部永続化メカニズムに直接保存するのではなく、レルムオブジェクトから生成された一連のレルムイベントを取得し、イベントストレージに保存するとともに、イベントバスEvent Bus
  • にイベントをパブリッシュする.
  • Event Handlerは、Event Bus内のイベントを購読し、イベントが発生したときに関連処理を行うことができる.上記でサービスについて説明した場合、インフラストラクチャ層サービスを利用してSMSメッセージを送信する例があります.CQRSのアーキテクチャでは、Warehouse Transferredイベントを完全に購読し、インフラストラクチャ層サービスを呼び出してSMSメッセージを送信することができます.Domain Modelは、自分の内部イベントがトリガーされた後、どのような状況が発生するか全く知らないが、Event Handlerはこれらの状況を処理する(Domain Modelはインフラストラクチャ層と完全にデカップリングされる)
  • Event Handlerには、SynchronizerまたはDenormalizerと呼ばれる特殊なEvent Handlerがあり、その役割は「Query Database」を同期させることである.Query Databaseはクエリーにデータソースを提供するストレージメカニズムであり、ユーザーがUIで見たクエリーデータはすべてこのデータベースに由来する.したがって、CQRSはユーザ操作を分離するだけでなく、データソースを分離する最大の利点は、設計者がUIのニーズに応じてQuery Databaseを構成および最適化することができ、例えば、Query Databaseを1枚のデータテーブルに対応するUI境界面に設計することができ、ユーザクエリが非常に柔軟で効率的になることである.ここではDDDのRepositoryとORMを組み合わせてデータの読み取りを実現することもできます.Domain LayerのRepositoryとは異なり、このRepositoryはDDDで述べた古典的な倉庫であり、契約モードを柔軟に使用することができます.もちろん、ユーザーのニーズやテクノロジーの選択に応じて、ORMを使用せずに直接SQL、またはNo SQLを使用することもできます.また、必要に応じてSynchronizerとDenormalizerの実装にキャッシュを採用することもできます.例えば、リアルタイムで更新する必要のないコンテンツについては、N個のイベントをキャプチャするごとにQueryデータベースを同期したり、クライアントqueryリクエストがある場合に同期したりすることができます.これも効率的な方法です.
  • ユーザUIは、Data Proxyを介してクエリー結果データを取得し、WCFは、クライアント
  • にDTOとして送信する.
    まとめ
    本文はCQRSモードの基本構造を紹介し、その中のいくつかの重要な概念について注釈を行い、私が実践と思考の中でまとめた内容でもある(PS:転載は出典を明記してください).DDDを学んでCQRSを始めたばかりの友人は、いくつかの資料を読むときに疑問に思うに違いない.本文がこれらの友人を助けることができることを望んでいる.例えば、最初に読んだとき、なぜオブジェクトの状態が変更されるのか、オブジェクトの状態が変更されるのではなく、領域イベントをトリガーしなければならないのか分からなかった.当時、私はDomain Modelでgetter/setterを便利に使用することを望んでいたので、Domain Modelを古典的なDDDとCQRSアーキテクチャに同時に適応させることを望んでいたからだ.何度も試みた結果、このやり方は不合理で望ましくないことが分かった.Udi Dahanが言ったように、CQRSはモデルであり、モデルである以上、特定の問題を解決するために使われている.
    やはり古い言葉です:需要によって決まります.CQRSのためにCQRSをしないでください.システムの性能を大幅に向上させることができ、auditingの能力を持つことができるが、Domain-Centralizedを実現することができ、データストレージをより簡単にすることができ、多くの技術選択の機会を提供しているが、CQRSにも多くの不足点があり、例えば構造実現が複雑で、データ同期の安定性が保証されにくい.イベントトレースエラーの場合、モデルオブジェクトの状態の復元など.やはりUdi Dahanの一言を引用します:簡単ですが、容易ではありません!