Slide 1

Slide 1 text

CQRS/ESのクラスとシステムフロー 鈴木まー  イベントソーシング・CQRS勉強会 #2 RailsでフルスクラッチでCQRSESを組んで みたことから得た学び

Slide 2

Slide 2 text

自己紹介 鈴木まー RailsでDDDを会社に導入定着させて 今はCQRS/ESの仕組みを フルスクラッチしながら勉強している ChatGPTでアイディアをはなしたり、まとめたりや Cursorが使って開発やアイティなどをするのが得意

Slide 3

Slide 3 text

なぜRailsを普段使っているプログラマー の自分がCQRS/ESに興味を持ったか

Slide 4

Slide 4 text

なぜRailsを普段使っているプログラマー の自分がCQRS/ESに興味を持ったか RailsでDDDとかモジュラーモノリスを実践してきたが CQRS/ESにであったことで CQRS/ESという思考モデルに触れた瞬間、次元の違う設 計を感じた DDDとかをRailsで導入してきた自分にとってあたらしい 挑戦になるのではないか

Slide 5

Slide 5 text

今の段階 一通りのCQRSES機能をRailsを使って実装している 実務プロジェクトでの経験はまだこれからだけど、仕組みや 設計の本質は自分の言葉で説明できるようになってきた RubyやPHPなどのCQRS/ESが盛んでもない環境でも、この 設計モデルは十分活かせる手応えを感じている

Slide 6

Slide 6 text

開発しているもの CQRS/ESのいかの機能を実装したもの 自動の一人用ポーカー カードを一枚ずつ交換していき役を作成する ゲームを開始する  カードを交換する ゲームを終了する このコマンドからイベントを作成して、リードモデルを更新 するところまで実装している 実際のゲームをするのはまだ未実装

Slide 7

Slide 7 text

CQRS/ESは処理の流れやクラスが多く 存在するので最初の理解は難しいです それの理解をする一歩目になれば幸いです 今日発表したいこと

Slide 8

Slide 8 text

CQRSESの最大の特徴 CQRSESの最大の特徴は、書き込み専用(ライト)DBと読 み取り専用(リード)DBを明確に分けること ライトDBとは: リードDBとは: 役割ごとにデータベースを分離することで、システム全体の 構造をシンプルかつ強力にできる

Slide 9

Slide 9 text

CQRSESの最大の特徴は、書き込み専用(ライト)DBと読 み取り専用(リード)DBを明確に分けること ライトDBとは:「データの記録」を担う書き込み専用デー タベース リードDBとは:「データの参照・検索」を担う読み取り専 用のデータベース 役割ごとにデータベースを分離することで、システム全体の 構造をシンプルかつ強力にできる CQRSESの最大の特徴

Slide 10

Slide 10 text

書き込み専用DBの特徴 操作の完全記録 ロールバック・リプレイが容易 拡張性

Slide 11

Slide 11 text

書き込み専用DBの特徴 操作の完全記録   全てのビジネス操作をイベントとして保存し、 後から全履歴を追跡可能 ロールバック・リプレイが容易   過去のイベントを再生することで、 任意時点の状態を再現できる 拡張性   新しいイベント種別の追加や、 イベントスキーマの拡張が容易

Slide 12

Slide 12 text

全ての操作をイベントとして保存し 履歴・監査・再現性を担保するための 基盤を提供する コマンドサイドの目的

Slide 13

Slide 13 text

システムへの変更リクエストを 受け取る仕組みを用意する ルールに従って内容をチェックし、 問題なければ記録する すべての変更をあとから振り 返られるように履歴を残す 詳細は後で説明 コマンドサイドの主な実装方法

Slide 14

Slide 14 text

読み込み専用DBの特徴 高速な読み込み 用途に応じた柔軟性がある

Slide 15

Slide 15 text

読み込み専用DBの特徴 高速な読み込み ユーザーの検索・集計・レポート要件ごとに 最適で高速な読み込む仕組み用意 用途に応じた柔軟性がある

Slide 16

Slide 16 text

読み込み専用DBの特徴 高速な読み込み ユーザーの検索・集計・レポート要件ごとに 最適で高速な読み込む仕組み用意 用途に応じた柔軟性がある 用途に応じて全文検索DBなどの 特化したDBも選択可能

Slide 17

Slide 17 text

大量の参照リクエストにも 高速に応えるための専用設計 用途ごとに最適化したデータ表示で、 柔軟な情報提供を実現 クエリーサイドの目的

Slide 18

Slide 18 text

イベントをコマンドサイドからうけとり、 各クラスに通知する 通知を受けたクラスは、ドメインロジックをするために 必要な外部のAPIを実行したり、データを読み込めるように 表示用のデータを更新する ユーザーからの読み込みがあった場合は、 更新した表示用のデータをユーザーにわたす 詳細は後で説明 クエリーサイドの主な実装方法

Slide 19

Slide 19 text

その前にCQRS/ES の最大の特徴

Slide 20

Slide 20 text

Event SourcingはCQRSESの最大の特徴 CQRSESを理解するにあたり一番最初に理解すること 従来のCRUD型の設計では「今の状態」だけを保存する

Slide 21

Slide 21 text

Event SourcingはCQRSESの最大の特徴 CQRSESを理解するにあたり一番最初に理解すること 従来のCRUD型の設計では「今の状態」だけを保存する Event Sourcingでは「どんな操作があったか(イベント)」 をすべて記録します。 これにより、過去の経緯や履歴をあとからたどることができます

Slide 22

Slide 22 text

CQRS/ESを理解するにはどのような処理の フローがあることを理解することが重要 このクラス群に従うだけで、開発時は考える ことが少なくなる(はず

Slide 23

Slide 23 text

コマンドサイド クエリーサイド

Slide 24

Slide 24 text

コマンドサイド すべての操作をイベントとして記録することで、 「誰が・いつ・何をしたか」を後から正確に追跡でき、 過去の状態も再現できるようになる。 クエリーサイド

Slide 25

Slide 25 text

コマンドサイド すべての操作をイベントとして記録することで、 「誰が・いつ・何をしたか」を後から正確に追跡でき、 過去の状態も再現できるようになる。 クエリーサイド 用途ごとに最適な形でデータを用意することで、 検索や集計が速くなり、 さまざまな情報を柔軟に取り出せるようになる。

Slide 26

Slide 26 text

全体フロー

Slide 27

Slide 27 text

クラスが多すぎるのでこのまま 理解をすると挫折する これから今の自分の知識で少しでも わかったと思って もらえるように説明をします 経験が少ないので間違っていてもご了承ください

Slide 28

Slide 28 text

簡略版

Slide 29

Slide 29 text

次からは補足をしながら 処理の流れ(フロー)の理解 フローの理解と各クラスの役割を理解す ることがCQRS/ESの最初の大きな難関

Slide 30

Slide 30 text

コマンドサイド

Slide 31

Slide 31 text

簡略版 クエリーサイド まずはコマンドサイ トのざっくり理解 詳しい説明は つぎから

Slide 32

Slide 32 text

コマンドを受け付けて 処理を開始できるまで コマンドを受け付けて処理を開始する

Slide 33

Slide 33 text

登場するクラス Command CommandHandler

Slide 34

Slide 34 text

登場するクラス Command ユーザーやシステムが「この操作をしてほしい」 と依頼するための命令オブジェクト。 たとえば「注文を作成する」「プロフィールを更新する」 など、具体的なアクションを表現する。 CommandHandler

Slide 35

Slide 35 text

登場するクラス Command ユーザーやシステムが「この操作をしてほしい」 と依頼するための命令オブジェクト。 たとえば「注文を作成する」「プロフィールを更新する」 など、具体的なアクションを表現する。 CommandHandler コマンドを受け取ったあと、内容を見て適切な 処理を実行する役割。 これにより、処理の分担が明確になり、 システム全体がシンプルになる。

Slide 36

Slide 36 text

コマンドを受け付けて処理を開始できるまで 作成されたCommandはCommandBusに送られ、 CommandBusは「これは商品購入の処理だな」と判断 して、商品購入用のコマンドハンドラーにそのコマン ドを渡す

Slide 37

Slide 37 text

コマンドを受け付けて処理を開始できるまで ユーザーが「商品を購入したい」と操作すると、 その内容を表す「商品購入コマンド」が作成する コマンドサイドでは、このコマンドを もとに処理をしてイベントを作成する

Slide 38

Slide 38 text

フロー

Slide 39

Slide 39 text

コマンドの内容に 基づきビジネスロジックを実行 しイベントを生成する コマンドを受け付けて処理を開始する コマンドの内容に基づきビジネスロジックを実行しイベントを生成する

Slide 40

Slide 40 text

登場するクラス CommandHandler Agreegate

Slide 41

Slide 41 text

登場するクラス CommandHandler コマンドハンドラーは、受け取ったコマンドの内容に基づいて、 該当するアグリゲート(例:注文Aggregate)を呼び出します。 問題がなければ、アグリゲート内部で「商品が購入された」「注文が作成され た」といったイベントを新たに生成します。 Agreegate

Slide 42

Slide 42 text

登場するクラス CommandHandler コマンドハンドラーは、受け取ったコマンドの内容に基づいて、 該当するアグリゲート(例:注文Aggregate)を呼び出します。 問題がなければ、アグリゲート内部で「商品が購入された」「注文が作成され た」といったイベントを新たに生成します。 Agreegate アグリゲートは、複数のエンティティや値オブジェクトをまとめて管理し、 外部からの操作やシステムの変更は必ずアグリゲート経由で行うことで、 ビジネスルールやデータの一貫性を保証する役割を持つ。

Slide 43

Slide 43 text

登場するクラス Event 
 EventStore

Slide 44

Slide 44 text

登場するクラス Event ドメインで発生した重要な出来事を表現するオブジェクトです。 システムの状態がどのように変化したかを記録し、 後から履歴をたどったり、他の処理に通知したりするために使われます。

 EventStore

Slide 45

Slide 45 text

登場するクラス Event ドメインで発生した重要な出来事を表現するオブジェクトです。 システムの状態がどのように変化したかを記録し、 後から履歴をたどったり、他の処理に通知したりするために使われます。

 EventStore システム内で発生したすべてのイベント (例:商品が購入された、ユーザー情報が変更されたなど) を時系列で記録・保存するための専用データベースです。

Slide 46

Slide 46 text

コマンドの内容に基づきビジネスロジック を実行しイベントを生成するまで CommandBusから商品購入CommandHandlerはコマンド を受け取ると、入力内容や権限などを確認する CommandHandlerが実行可能な状態 であることをチェックします 問題がなければ、CommandHandlerが 注文Aggregateに 「購入処理を実行してほしい」と依頼します

Slide 47

Slide 47 text

コマンドの内容に基づきビジネスロジック を実行しイベントを生成するまで 注文Aggregateが在庫確認や 金額計算などのビジネスロジックを実行します 注文が確定できた場合、その結果をもとに CommandHandlerが"商品が購入された"イベントを発行します そのEventをEventStoreに追記します。

Slide 48

Slide 48 text

フロー

Slide 49

Slide 49 text

クエリーサイド

Slide 50

Slide 50 text

作成されたイベントを クエリーサイドに通達する コマンドを受け付けて処理を開始する コマンドの内容に基づきビジネスロジックを実行しイベントを生成する 作成されたイベントをクエリーサイドに通達する

Slide 51

Slide 51 text

登場するクラス EventBus EventPublisher

Slide 52

Slide 52 text

登場するクラス EventBus コマンドサイドで発行して 記録したイベントを、コマンドサイドから クエリーサイドに流すための橋渡し役です。 EventPublisher

Slide 53

Slide 53 text

登場するクラス EventBus コマンドサイドで発行して 記録したイベントを、コマンドサイドから クエリーサイドに流すための橋渡し役です。 EventPublisher EventBusから受け取ったイベントを受け取り、 それをもとに登録されている 各種処理(読み取りDBの更新や外部サービスへの通知など) を担当するクラスに通知する仕組みです。

Slide 54

Slide 54 text

イベントをクエリーサイドに通達するまで クエリサイドで読み取り用のDBを更新したり、外部システムの やりとりをするためにEventStoreに記録されたイベントが EventBusに渡されます。 渡たされたイベントはEventPublisherに「商品購入イベントが 発生した」ということを伝えます。 その後、EventPublisherが登録されているProjectionや EventListenerに通知します。

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

クエリーサイドの処理をして データを取得できるようにする コマンドを受け付けて処理を開始する コマンドの内容に基づきビジネスロジックを実行しイベントを生成する 作成されたイベントをクエリーサイドに通達する クエリーサイドの処理をしてデータを取得できるようにする

Slide 57

Slide 57 text

登場するクラス Projection EventListener

Slide 58

Slide 58 text

登場するクラス Projection Projectionは、イベントの内容に応じて ReadModelを更新するクラスや機能。 クエリーサイドのデータが保たれる。 EventListener

Slide 59

Slide 59 text

登場するクラス Projection Projectionは、イベントの内容に応じて ReadModelを更新するクラスや機能。 クエリーサイドのデータが保たれる EventListener システム内で発生したイベントを受け取って 反応する役割を持つ。 通知の送信や外部サービスとの連携など、 さまざまな追加処理を担当する

Slide 60

Slide 60 text

登場するクラス ReadModel QueryService

Slide 61

Slide 61 text

登場するクラス ReadModel ユーザーの検索や参照リクエストに 最適化された読み取り専用のオブジェクト。 Projectionに更新され、 クエリーサービスによって取得される QueryService

Slide 62

Slide 62 text

登場するクラス ReadModel ユーザーの検索や参照リクエストに 最適化された読み取り専用のオブジェクト。 Projectionに更新され、 クエリーサービスによって取得される QueryService ユーザーからの検索や参照リクエストを 受け付ける役割を持つ。 ReadModelを参照して、 最新かつ最適な情報を素早く返す

Slide 63

Slide 63 text

表示用のデータを取得できるまで EventPublisherから通知されたのを受け取った、 Projection"商品が購入されたイベント"の 内容に基づいてReadModelを更新します EventPublisherから通知された EventListenerは商品が購入された イベントに必要な決済APIをしようする

Slide 64

Slide 64 text

表示用のデータを取得できるまで ユーザーからのデータ取得には QueryServiceをつかって ReadModelを取得する

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

コマンドサイドで おこなうことの振り返り

Slide 67

Slide 67 text

Command CommandBus CommandHandler コマンドサイドで登場するクラス

Slide 68

Slide 68 text

Command ユーザーやシステムが「この操作をしてほしい」 と依頼するための命令オブジェクト  CommandBus CommandHandler コマンドサイドで登場するクラス

Slide 69

Slide 69 text

Command ユーザーやシステムが「この操作をしてほしい」 と依頼するための命令オブジェクト  CommandBus コマンドを受け取り、適切なCommandHandler に振り分けて処理を依頼する役割を持つ。 CommandHandler コマンドサイドで登場するクラス

Slide 70

Slide 70 text

Command ユーザーやシステムが「この操作をしてほしい」 と依頼するための命令オブジェクト  CommandBus コマンドを受け取り、適切なCommandHandler に振り分けて処理を依頼する役割を持つ。 CommandHandler コマンドハンドラーは、受け取ったコマンドの内容に基づいて、 該当するアグリゲートを呼び出します。 コマンドサイドで登場するクラス

Slide 71

Slide 71 text

Agreegate Event EventStore

Slide 72

Slide 72 text

Agreegate ユーザーの操作やシステムの変更は 必ずアグリゲート経由で行うことで、 ビジネスルールやデータの一貫性を保証する役割を持つ Event EventStore

Slide 73

Slide 73 text

Agreegate ユーザーの操作やシステムの変更は 必ずアグリゲート経由で行うことで、 ビジネスルールやデータの一貫性を保証する役割を持つ Event ドメインで発生した重要な出来事を表現するオブジェクト EventStore

Slide 74

Slide 74 text

Agreegate ユーザーの操作やシステムの変更は 必ずアグリゲート経由で行うことで、 ビジネスルールやデータの一貫性を保証する役割を持つ Event ドメインで発生した重要な出来事を表現するオブジェクト EventStore EventStoreは、システム内で発生したすべてのイベント(例:商品 が購入された、ユーザー情報が変更されたなど)を時系列で記録・ 保存するための専用データベース

Slide 75

Slide 75 text

CommandHanderにCommandを依頼する
 EventStoreに追記してイベントをクエリーサイドに渡す コマンドサイドでおこなうこと

Slide 76

Slide 76 text

CommandHanderにCommandを依頼する
 ユーザーの操作でCommandは作成され、 作成されたのはCommandBusに送られ、 CommandBusは適したコマンドハンドラーにそのコマンドを渡す EventStoreに追記するまで コマンドサイドでおこなうこと

Slide 77

Slide 77 text

CommandHanderにCommandを依頼する
 ユーザーの操作でCommandは作成され、作成されたのはCommandBusに送られ、 CommandBusは適したコマンドハンドラーにそのコマンドを渡す EventStoreに追記するまで CommandHandlerはコマンドを受け取ると、 バリデーションをおこない問題がなければ、Aggregateがビジネス ロジックを実行し、それをもとにCommandHanlderが Eventを発行し、EventStoreに追記する コマンドサイドでおこなうこと

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

クエリーサイドで おこなうことの振り返り

Slide 80

Slide 80 text

EventBus EventPublisher Projection クエリーサイドで登場するクラス

Slide 81

Slide 81 text

EventBus コマンドサイドで発行して記録したイベントを、 コマンドサイドからクエリーサイドに流すための橋渡し役 EventPublisher Projection クエリーサイドで登場するクラス

Slide 82

Slide 82 text

EventBus コマンドサイドで発行して記録したイベントを、 コマンドサイドからクエリーサイドに流すための橋渡し役 EventPublisher EventBusから受け取ったイベントを受け取り、それをもとに登録さ れている各種処理(読み取りDBの更新や外部サービスへの通知な ど)を担当するクラスに通知する仕組み Projection

Slide 83

Slide 83 text

EventBus コマンドサイドで発行して記録したイベントを、 コマンドサイドからクエリーサイドに流すための橋渡し役 EventPublisher EventBusから受け取ったイベントを受け取り、それをもとに登録さ れている各種処理(読み取りDBの更新や外部サービスへの通知な ど)を担当するクラスに通知する仕組み Projection Eventの内容に応じてReadModelを更新するクラスや機能

Slide 84

Slide 84 text

EventListener ReadModel QueryService

Slide 85

Slide 85 text

EventListener ReadModel QueryService システム内で発生したイベントを受け取って 処理をする役割を持つ。

Slide 86

Slide 86 text

EventListener システム内で発生したイベントを受け取って 処理をする役割を持つ。 ReadModel 参照リクエストに最適化された読み取り専用のオブジェクト。 Projectionがイベントをもとに最新の状態に更新する QueryService

Slide 87

Slide 87 text

EventListener システム内で発生したイベントを受け取って 処理をする役割を持つ。 ReadModel 参照リクエストに最適化された読み取り専用のオブジェクト。 Projectionがイベントをもとに最新の状態に更新する QueryService QueryServiceは、ユーザーからの検索や 参照リクエストを受け付ける役割を持つ。 ReadModelを参照して、最新かつ最適な情報を素早く返す。

Slide 88

Slide 88 text

クエリーサイドの実行をするクラスに通知する 通知された各クラスが処理をする ユーザーに表示用のデータ渡す クエリーサイドでおこなうこと

Slide 89

Slide 89 text

クエリーサイドの実行をするクラスに通知する EventBus経由でEventをEventPublisherにわたり、 EventPubliserに登録されている、 EvnetListnerやProjectionに通知をする 通知された各クラスが処理をする ユーザーに表示用のデータ渡す クエリーサイドでおこなうこと

Slide 90

Slide 90 text

通知された各クラスが処理をする EvnetListnerが外部のサービスと 連携をすることでコマンドの実行に必要なことをする Projetionがイベントの内容を ReadModelに反映することで、表示用のデータを作成する ユーザーに表示用のデータ渡す

Slide 91

Slide 91 text

ユーザーに表示用のデータ渡す

Slide 92

Slide 92 text

ユーザーに表示用のデータ渡す ユーザーがデータを取得するときは QueryServiceからReadModelに問い合わせる

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

全体詳細

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

全体概要図

Slide 97

Slide 97 text

No content

Slide 98

Slide 98 text

今回の発表でCQRS/ESをするときに 必要なクラスや各クラスのフローを 理解するための一歩になれば幸いです

Slide 99

Slide 99 text

ご清聴ありがとう ございました