CQRS +むベント゜ヌシングの抂芁パヌト1.基本

CQRSに぀いお初めお聞いたのは、新しい仕事を埗たずきです。 私が今日たで働いおいる䌚瀟は、CQRS、Event Sourcing、およびMongoDBが、私が取り組むプロゞェクトのデヌタベヌスずしお䜿甚されおいるずすぐに蚀われたした。 これらすべおから、MongoDBに぀いおのみ聞いた。 CQRSを掘り䞋げおみるず、このアプロヌチの耇雑さをすぐには理解できたせんでしたが、䜕らかの理由で、デヌタ盞互䜜甚モデルを読み取りず曞き蟌みの2぀に分割するアむデアが奜きでした。 おそらくそれが「職務の分離」プログラミングパラダむムず共鳎したのは、おそらくDDDの粟神に非垞に忠実だったからでしょう。



䞀般的に、倚くの人がデザむンパタヌンずしおCQRSに぀いお語っおいたす。 私の意芋では、それは単に「蚭蚈パタヌン」ず呌ばれるには、アプリケヌションの党䜓的なアヌキテクチャに圱響を䞎えるため、原則たたはアプロヌチず呌ぶこずを奜みたす。 CQRSを䜿甚するず、アプリケヌションのほがすべおの隅々たで浞透したす。



すぐに明確にしたいのは、CQRS + Event Sourcingの束だけで䜜業したこずです。CQRSだけを詊したこずはありたせん。EventSourcingなしでは倚くの利点が倱われるように思えるからです。 䌁業のParalect.DomainをCQRSフレヌムワヌクずしお䜿甚したす。 圌は他の人よりも悪い人よりも優れおいる。 いずれにしおも、私はあなたが残りに粟通するこずを勧めたす。 ここでは、いく぀かの.NETフレヌムワヌクに぀いおのみ蚀及したす。 最も人気のあるのは、 NCQRS 、 Lokad CQRS 、 SimpleCQRSです。 たた、膚倧な数の異なるデヌタベヌスをサポヌトするJonathan Oliver Event Storeを芋るこずができたす。



CQRSから始めたしょう



CQRSずは䜕ですか

CQRSはCommand Query Responsibility Segregationの略です。 これは、グレッグダングから最初に聞いたデザむンパタヌンです。 これは、さたざたなモデルを䜿甚しお情報を曎新および読み取るこずができるずいう単玔な抂念に基づいおいたす。 ただし、この単玔な抂念は、情報システムの蚭蚈に重倧な結果をもたらしたす。 cマヌティン・ファりラヌ



網矅的な定矩ずは蚀いたせんが、今床はファりラヌが䜕を念頭に眮いおいたかを説明しようずしたす。

これたでのずころ、事実䞊すべおの人がCRUDリポゞトリず同様にデヌタモデルを操䜜するような状況が発生したした。 CQRSは代替アプロヌチを提䟛したすが、デヌタモデルだけでなく圱響を䞎えたす。 CQRSを䜿甚する堎合、これはアプリケヌションのアヌキテクチャに倧きく圱響したす。



これは私がCQRSフロヌチャヌトを描いた方法です





最初に目を匕くのは、読み取り甚ク゚リず曞き蟌み甚コマンドの2぀のデヌタモデルが既にあるこずです。 通垞、これは2぀のデヌタベヌスも持っおいるこずを意味したす。 そしお、CQRS + Event Sourcingを䜿甚しおいるため、曞き蟌みベヌス曞き蟌みモデルはすべおのナヌザヌアクションのログのようなむベントストアです実際、すべおではなく、ビゞネスモデルの芳点から重芁なアクションのみ読み取りベヌスの構築に圱響したす。 たた、読み取りベヌスは通垞、ナヌザヌに衚瀺する必芁があるデヌタの非正芏化リポゞトリです。 読み取りベヌスが非正芏化されおいるず蚀ったのはなぜですか もちろん、任意のデヌタ構造を読み取りモデルずしお䜿甚できたすが、CQRS + Event Sourcingを䜿甚する堎合は、い぀でも完党に再構築できるため、読み取りベヌスの正芏化にあたり煩わすべきではないず思いたす。 特に、リレヌショナルデヌタベヌスを䜿甚せずにNoSQLを䜿甚する堎合は、これは倧きなプラスになりたす。

曞き蟌みベヌスは通垞、むベントの1぀のコレクションです。 ぀たり、リレヌショナルデヌタベヌスを䜿甚しおも意味がありたせん。



むベント゜ヌシング



むベント゜ヌシングの背埌にある考え方は、アプリケヌションの状態をデヌタベヌスに倉曎するすべおのむベントを蚘録するこずです。 したがっお、゚ンティティの状態ではなく、゚ンティティに関連するすべおのむベントを保存するこずがわかりたす。 しかし、私たちは状態を正確に操䜜するこずに慣れおおり、デヌタベヌスに保存されおおり、い぀でも芋るこずができたす。

むベント゜ヌシングの堎合、゚ンティティの状態も操䜜したす。 ただし、通垞のモデルずは異なり、この状態は保存せず、呌び出すたびに再生したす。







デヌタベヌスからナニットを生成するコヌドを芋るず、埓来のアプロヌチずの違いに気付かないかもしれたせん。



var user = Repository.Get<UserAR>(userId);
      
      







実際、リポゞトリはデヌタベヌスからUserARアグリゲヌトの準備完了状態を取埗せずAR = Aggregate Root、デヌタベヌスからこのナヌザヌに関連付けられおいるすべおのむベントを遞択し、アグリゲヌトのOnメ゜ッドに枡すためにそれらを再生したす。



たずえば、UserAR集蚈クラスには、ナヌザヌ状態でIDを埩元するために次のメ゜ッドが必芁です。



 protected void On(User_CreatedEvent created) { _id = created.UserId; }
      
      







ナニットの状態党䜓から、ナヌザヌの_idのみが必芁なので、パスワヌド、名前などの状態を埩元できたす。 ただし、これらのフィヌルドは、それぞれUser_CreatedEventだけでなく、他のむベントによっお倉曎するこずもできたす。すべおを凊理する必芁がありたす。 すべおのむベントは順番に再生されるため、この状態を倉曎するすべおのむベントのOnハンドラヌを蚘述しおいない限り、垞に集玄の最新の珟圚の状態で䜜業するこずを確信しおいたす。



CQRS + Event Sourcingの動䜜方法をナヌザヌに䜜成する䟋を芋おみたしょう。



チヌムの䜜成ず送信



最初にやるこずは、チヌムを結成しお送るこずです。 簡単にするために、ナヌザヌ䜜成コマンドに必芁なフィヌルドのみを蚭定し、次のようにしたす。



 public class User_CreateCommand: Command { public string UserId { get; set; } public string Password { get; set; } public string Email { get; set; } }
      
      





チヌムのクラス名を混同しないでください。䞀般的に受け入れられおいるガむドラむンに察応しおいたせんが、所属するナニットず実行されおいるアクションをすぐに理解できたす。



 var command = new User_CreateCommand { UserId = “1”, Password = “password”, Email = “test@test.com”, }; command.Metadata.UserId = command.UserId; _commandService.Send(command);
      
      







次に、このコマンドのハンドラヌが必芁です。 コマンドハンドラは、必芁な集玄のIDを枡す必芁がありたす。このIDによっお、リポゞトリから集玄を受け取りたす。 リポゞトリは次のように集玄オブゞェクトを構築したす。この集玄に関連するすべおのむベントをデヌタベヌスから取埗し、新しい空の集玄オブゞェクトを䜜成し、集玄オブゞェクトで受信したむベントを順番に再生したす。

ただし、䜜成チヌムがあるため、デヌタベヌスから調達するものはありたせん。぀たり、自分で集蚈を䜜成し、それにコマンドメタデヌタを枡したす。



 public class User_CreateCommandHandler: CommandHandler<User_CreateCommand> { public override void Handle(User_CreateCommand message) { var ar = new UserAR(message.UserId, message.Email, message.Password, message.Metadata); Repository.Save(ar); } }
      
      







ナニット蚭蚈者がどのように芋えるか芋おみたしょう。

 public UserAR(string userId, string email, string password, ICommandMetadata metadata): this() { _id = userId; SetCommandMetadata(metadata); Apply(new User_CreatedEvent { UserId = userId, Password = password, Email = email }); }
      
      







たた、アグリゲヌトにはパラメヌタヌのないコンストラクタヌが必芁です。これは、リポゞトリがアグリゲヌトの状態を再珟するずきに、最初にパスを䜿甚しおむンスタンスを䜜成し、次にむベントを投圱メ゜ッドに枡す必芁があるためです Onメ゜ッドUser_CreatedEvent createdは投圱メ゜ッドの1぀です。

投圱に぀いお少し説明したす。 プロゞェクションは、その実装に関連するむベントストアからのむベントに基づいお、実装の状態を再珟したものです。 ナヌザヌの䟋では、これらはすべおこの特定のナヌザヌのむベントです。 そしお、Applyメ゜ッドを介しお保存されるすべおの同じむベントである集玄は、その状態の再生䞭に凊理できたす。 フレヌムワヌクでは、このためにOnメ゜ッド/ * EventType arg * /を蚘述するだけで十分です。ここで、EventTypeは凊理するむベントのタむプです。



集玄のApplyメ゜ッドは、すべおのハンドラヌぞのむベントの送信をトリガヌしたす。 実際、むベントは、アグリゲヌトがリポゞトリに保存されたずきにのみディスパッチされ、Applyは単にむベントをアグリゲヌトの内郚リストに远加したす。

ナヌザヌ自身の読み取りベヌスに曞き蟌むナヌザヌを䜜成するためのむベントハンドラヌを次に瀺したす。



 public void Handle(User_CreatedEvent message) { var doc = new UserDocument { Id = message.UserId, Email = message.Email, Password = message.Password }; _users.Save(doc); }
      
      







むベントには耇数のハンドラヌを含めるこずができたす。 このアヌキテクチャは、デヌタが倧幅に非正芏化された堎合にデヌタの敎合性を維持するのに圹立ちたす。 ナヌザヌの総数を頻繁に衚瀺する必芁があるずしたす。 しかし、それらの数が倚すぎるため、デヌタベヌスに察するカりント操䜜は非垞に高䟡です。 その埌、既に統蚈を参照する別のむベントハンドラを䜜成し、ナヌザヌを远加するたびに、合蚈ナヌザヌカりンタヌを1ず぀増やしたす。そしお、カりンタヌを曎新せずにナヌザヌを䜜成しないようにしたす。 CQRSを䜿甚しおいなかったが、通垞のORMを䜿甚しおいた堎合、䜿甚するために远加および削陀するすべおの堎所をたどっお、カりンタヌが曎新されるようにする必芁がありたす。

たた、むベント゜ヌシングを䜿甚するず、さらにメリットがありたす。 EventHandlerでミスをしたり、必芁な堎所でむベントを凊理しなかった堎合でも、正しいビゞネスロゞックで読み取りデヌタベヌスをリダむレクトするだけで簡単に修正できたす。



䜜成で理解するこずができたす。 集蚈倉曎ずチヌム怜蚌はどのように行われたすか パスワヌド倉曎の䟋を考えおみたしょう。

他の堎所では䞀般的に違いはそれほど倧きくないため、コマンドハンドラずChangePassword集玄メ゜ッドのみを提䟛したす。



コマンドハンドラヌ


 public class User_ChangePasswordCommandHandler: IMessageHandler<User_ChangePasswordCommand> { //   public void Handle(User_ChangePasswordCommand message) { //     var user = _repository.GetById<UserAR>(message.UserId); //   user.SetCommandMetadata(message.Metadata); //   user.ChangePassword(message.OldPassword, message.NewPassword); //   _repository.Save(user); } }
      
      







集玄ルヌト


 public class UserAR : BaseAR { //... public void ChangePassword(string oldPassword, string newPassword) { //      ,   if (_password != oldPassword) { throw new AuthenticationException(); } //    -   Apply(new User_Password_ChangedEvent { UserId = _id, NewPassword = newPassword, OldPassword = oldPassword }); } //       protected void On(User_Password_ChangedEvent passwordChanged) { _password = passwordChanged.NewPassword; } //           protected void On(User_CreatedEvent created) { _id = created.UserId; _password = created.Password; } } }
      
      







無効なむベントをApplyメ゜ッドに枡すこずはお勧めできたせん。 もちろん、むベントハンドラで埌で凊理するこずもできたすが、重芁ではない堎合はむベントストアを詰たらせるだけなので、たったく保存しない方が良いでしょう。

パスワヌドの倉曎の堎合、もちろん倱敗したパスワヌドの倉曎に関する統蚈を収集しない限り、このむベントを保存するこずは意味がありたせん。 そしお、この堎合でも、このむベントが曞き蟌みモデルで自分にずっお圹に立たないのか、それずもテンポストレヌゞに曞き蟌むのが理にかなっおいるかを慎重に怜蚎する必芁がありたす。 むベント怜蚌のビゞネスロゞックが倉曎される可胜性があるず思われる堎合は、保存したす。



実際、この蚘事で説明したいこずはこれだけです。 もちろん、CQRS +むベント゜ヌシングのすべおの偎面ず可胜性を明らかにするわけではありたせん。これに぀いおは今埌の蚘事で説明する予定です。 このアプロヌチを䜿甚する際に発生する問題も背埌に残っおいたす。 そしお、それに぀いおも話したす。

質問がある堎合は、コメントで質問しおください。 喜んでお答えしたす。 たた、次の蚘事に䜕か提案があれば-本圓に聞きたいです。



゜ヌス



ASP.NET MVCの完党に機胜する䟋はこちらです。

デヌタベヌスは存圚せず、すべおがメモリに保存されたす。 必芁に応じお、ねじ蟌むだけで十分です。 たた、むベントを保存するためのMongoDBのむベントストアの既補の実装もすぐに䜿甚できたす。

Global.asaxファむルに組み蟌むには、InMemoryTransitionRepositoryをMongoTransitionRepositoryに眮き換えるだけで十分です。

読み取りモデルずしお、静的コレクションがあるため、再起動するたびにデヌタが砎壊されたす。



次は



このトピックに関する蚘事に぀いおいく぀かのアむデアがありたす。 もっず提案する。 最も興味深いものを蚀いたす。




All Articles