Slide 1

Slide 1 text

CQRSはEvent Sourcingなしで実現できるのか? かとじゅん(@j5ik2o) BPStudy#151〜オブジェクト指向、モデリング、設計 LT大会[リモート開催] 2020/03/30

Slide 2

Slide 2 text

© Chatwork あなたは誰? ● 加藤潤一(@j5ik2o) ● Chatwork社のテックリード ● 直近の活動 ○ Kafka, S3のEvent Sourcingする ためのプラグインを開発 ○ Fargateを使った負荷試験ツール の開発

Slide 3

Slide 3 text

© Chatwork FYI: ChatworkのEvent Sourcing事例 ● 2016年にNTTデータさんと共同開発したプロジェクトでCQRS+ESを 採用した(詳細は以下スライド参照) ● Apache Kafkaの以下の書籍に事例が紹介されています

Slide 4

Slide 4 text

© Chatwork CQRSはEvent Sourcingなしで実現できるのか? ● 今日は「モノとの繋がりをトレースできるイベント= コトはドメイン分析に使える」という話題ではなく、 イベントを使ったアーキテクチャに関する話題 ● CQRSにはEvent Sourcingは必須?大袈裟では? ● State Sourcing(CRUD)のままで、CQRSをすること ができるかどうか考えてみたい

Slide 5

Slide 5 text

© Chatwork CQRS ● Command and Query Responsibility Segregation(CQRS責務分離) ○ 2010年 Greg Young氏 ● CQS(Command-Query Separation) をアーキテクチャレベルに適用 ● CommandとQueryで要件が違うから わける ○ 一貫性(強い or 弱い) ○ データ形式(正規化 or 非正規化) ○ スケーラビリティ(C:Q=2:8)

Slide 6

Slide 6 text

© Chatwork FYI: C/Qを分離しない場合の弊害 ● リポジトリのクエリメソッドが複雑になる ○ employeeRepository.findByDeptIdsWithEmpNamesWith…(…, …, …) ● N+1クエリが発生しやすい reservationRepository.findByIds(ids).map { reservation => val a = hotelRepository.findById(reservation.hotelId) val b = customerRepository.findById(reservation.customerId) readModelDto(a, b) } ● オブジェクト→DTO変換が非効率 ○ DTOに変換するときに、UIに合わせて捨てられる項目がある

Slide 7

Slide 7 text

© Chatwork FYI: リードモデルの形式変換をしない場合の問題 ● 例えば、入社日がEpocTime(Long値)として保存される場合、そのまま ではクエリのレスポンスに含めることができない。しかしクエリ側から 値オブジェクトに依存ができない。 ● これを回避するためにRMUで事前に形式変換する必要がある Attribute Type Value EmployeeId String “123456789” JoinedDate Long 1585039714572 ... ... .. JoinedDate#asFormattedString 2020年03月24日 08:48 内部データ表現を意味のある値表現にするにはドメ インオブジェクトを使う必要がある エンコードされたドメインオブジェクト リードしたい値表現

Slide 8

Slide 8 text

© Chatwork C/Qのモデル間の変換・同期の問題はイベントで解決 “The model that is best suited is the introduction of events, events are a well known integration pattern and offer the best mechanism for model synchronization.” by Greg Young 最も適したモデルはイベントの導入であり、イベントは よく知られた統合パターンであり、モデルの同期化に最 適なメカニズムを提供します。

Slide 9

Slide 9 text

© Chatwork Event Sourcing ● 過去に起きた出来事=イベント ● イベントは不変で追記のみ ● イベントを再生(リプレイ)すると最新状態が手に入る

Slide 10

Slide 10 text

© Chatwork CQRS+Event Sourcing ● 真のデータソースは永続されたイベ ント ● そのイベントを使ってリードモデル を構築する Event Sourcingは大袈裟で大変と いう意見を聞くので、他の方法がな いか考えてみた

Slide 11

Slide 11 text

© Chatwork 真のデータソースを最新状態にできないか

Slide 12

Slide 12 text

© Chatwork アプリケーションからのイベント伝搬方式 ● Write DBへ最新状態をロック付きで書き込み、成功したらキューにイベ ントを書き込む方法。これは非常にまずい方法。

Slide 13

Slide 13 text

© Chatwork アプリケーションからのイベント伝搬方式の問題 ● イベントは全順序に並んでいないとうまくリードモデルを構築できない ● ステートレスなアプリケーションでは、2層コミット問題とイベント追い 越し問題が起きる (参考文献) スターバックスは2フェーズコミットを使わない https://code.google.com/archive/p/gregors-ramblings-ja/wikis/18 _starbucks.wiki 最新状態を保存する DBとキューのトランザク ションを同一化しないと防ぐのが難しい。もしくは DB書き込み成功後にエンキューするプロセスを シャーディングする必要がある

Slide 14

Slide 14 text

© Chatwork CDC(Change Data Capture)+Outbox方式 ● CDCはWriteDBでの変更を捉える方式。WriteModelと同一TxのOutbox 内にドメインイベントを保存(一時保存でもよい)し下流に伝搬させる ● DebeziumにはMySQLのbinlogを読む機能がある。DBのトランザクショ ンログはイベントと同等。結局Event Sourcingになっている

Slide 15

Slide 15 text

© Chatwork FYI: AkkaのES方式 ● トランザクションをActorが管理しているため、イベント列への書き込 みの整合性が保たれる。スナップショットがあれば、それより古いイベ ントは削除可能。 ● ステートフルウェブアプリケーションになる

Slide 16

Slide 16 text

© Chatwork まとめ ● 単純にイベントを伝搬させるだけでは問題が生じる ● ステートレスなウェブアプリケーションでは、 CDC+Outbox方式を採用したほうが無難 ● CDC+Outbox方式は、トランザクションログが真の データソースでありESの一種であるという理解が必要 ● 外形的にはCQRS+SSはできるように見えるが、真の データソースは最新データにはならない。結局はESに 帰結する

Slide 17

Slide 17 text

© Chatwork ありがとうございました!

Slide 18

Slide 18 text

No content