Slide 1

Slide 1 text

アクターシステムに頼らず Event Sourcing する 方法について かとじゅん(@j5ik2o) Press Space for next page

Slide 2

Slide 2 text

自己紹介 Kubell 社( 旧Chatwork 社) テックリード。 ウェブ業界に転身後、Scala やDDD を採用した開発に従事。現在は次 期アーキテクチャの設計に携わる → 次あります 執筆 プロダクトデザインやソフトウェア設計の本を書いてるけどいつ 出るか… レビュー 改訂版 良いコード/悪いコードで学ぶ設計入門 WEB+DB PRESS Vol.132 - 特集1 オブジェクト指向神話からの脱 却 ドメイン駆動設計入門 Akka 実践バイブル エリックエヴァンスのドメイン駆動設計 twitter: j5ik2o github: j5ik2o

Slide 3

Slide 3 text

CQRS+Event Sourcing は難しい? それなりに難しいとは思いますが まず注目するところはコマンド側。 モジュール化の技法の一つとしてみると理解しやすい

Slide 4

Slide 4 text

今日の話題 データベースもわける必要あるの? C とQ をつなげるストリーム処理をどうやって実現する? コマンド側にアクターモデルが必要なんでしょ? ← 今日はここの話

Slide 5

Slide 5 text

FYI: アクターモデルといえば アクターモデルは、メッセージをやりとりするための軽量プロセスモデルで、以下の実装がよく知られている Akka/Pekko Proto.Actor 特に Proto.Actor はとても参考になる。Akka は内部実装が複雑になりすぎている… 。今からやる人は Proto.Actor の実装を学ぶとよいです。Proto.Actor の影響を受けた実装は以下。 PHP での実装: https://github.com/ytake/phluxor Rust での実装: https://github.com/j5ik2o/nexus-actor-rs

Slide 6

Slide 6 text

CQRS+Event Sourcing が想定するシステム構成

Slide 7

Slide 7 text

想定のシステム構成 集約はドメインモデルの集合体 コマンドを受け付け、状態遷移する(状態機 械) 更新のための状態は保持できるが閲覧はできな い コマンドに成功するとイベントを送出する 集約そのものではなくイベントが永続化される 集約という単位でオブジェクトグラフを手にで きる DB とは直接関係ないのでインピーダンスミス マッチも解決しない( 関係があるのはイベント の方) 集約をアクターという軽量プロセスで実現するのが一般的。今日はその集約をクラスベースで実現する方法に ついて考える。

Slide 8

Slide 8 text

アクターモデルを使わなくても実現は可能 ただし、イベントストアをどのように実現するかという課題がある。 ヒントは以下。AWS さんのドキュメント。 Amazon DynamoDB を使った CQRS イベントストアの構築 AWS データベースブログの記事 「Amazon DynamoDB による CQRS イベントストアの構築」 を勝手に読み解 く なぜDynamoDB なのだろうか?→ CDC が比較的容易にできるNoSQL だから

Slide 9

Slide 9 text

AWS さんの提案方式

Slide 10

Slide 10 text

【AWS 方式】 テーブル構成 Aggregate (集約) :ビジネスロジックの実装をカプセル化するCQRS パターン Event :何かが起こったことを示すイベント Snapshot :特定の時系列ポイントまでに起こったイベントから導出された状態を示すスナップショット。 スナップショットを使うとランタイムで全てのイベントの履歴を保持したりロードしたりする必要がなくな ります。

Slide 11

Slide 11 text

【AWS 方式】 Event テーブル 最初からイベントをこのテーブルに保存しない このテーブルからはCDC (変更データキャプチャ)できない。Aggregate からやることになる…

Slide 12

Slide 12 text

【AWS 方式】 Aggregate テーブル version は楽観的ロックのために使う last_events は未処理イベントの列。

Slide 13

Slide 13 text

【AWS 方式】 Snapshot テーブル

Slide 14

Slide 14 text

どのように動作するのか

Slide 15

Slide 15 text

【AWS 方式】 集約を読み込むロジック Aggregate テーブルを読む last_events があればEvent テーブルに書き込む。CQS 違反?? その後Snapshot テーブルを読み、Event を読む last_events はEvent テーブルに追記するだけではなくAggregate からlast_events を消さないといけない。Event へのPutItem と同時に行わないと安全とは言えないのでは??結局トランザクションが必要では??

Slide 16

Slide 16 text

【AWS 方式】 集約を書き込むロジック 興味があればご自分で調べてみてください。本題ではないので割愛します

Slide 17

Slide 17 text

というわけでシンプルな方法【KJ 方式】を提案します

Slide 18

Slide 18 text

【KJ 】j5ik2o/event-store-adaptor-* DynamoDB をイベントストア化するためのライブラリを作った。 AWS さんとは違って最初からトランザクシ ョンを使い、CQS の原則に従う https://github.com/j5ik2o/event-store-adapter Go,Rust,Java,Scala,Kotlin サンプル実装 https://github.com/j5ik2o/cqrs-es-example 実績 Black Cat Carnival

Slide 19

Slide 19 text

【KJ 】DynamoDB テーブル仕様

Slide 20

Slide 20 text

【KJ 】Journal テーブルはSnapshot,Journal の二つ pkey パーティションキー ${ 集約種別名}-${hash( 集約ID) % 論理シャードサイズ} skey ソートキー ${ 集約種別名}-${ 集約ID の値部分}-${ シーケンス番号} aid: 集約ID seq_nr: シーケンス番号 payload: イベント本体 occurred_at: 発生日時 ` ` ` `

Slide 21

Slide 21 text

【KJ 】Snapshot pkey パーティションキー ${ 集約種別名}-${hash( 集約ID) % 論理シャードサイズ} skey ソートキー ${ 集約種別名}-${ 集約ID の値部分}-${ シーケンス番号} 。最新のスナップショットはシーケンス番号=0 aid: 集約ID seq_nr: シーケンス番号 ttl: 削除のためのTTL version: 楽観的ロック用のバージョン ` ` ` `

Slide 22

Slide 22 text

【KJ 】イベントストアの設計を考える 必要なインターフェイスは4 つだけ

Slide 23

Slide 23 text

【KJ 】DynamoDB の細かい話をすると伝わらないと 思うので、ここではインメモリ操作で説明する

Slide 24

Slide 24 text

【KJ 】最初のイベントとスナップショットの書き込み Event はSnapshot と同じトランザクションで書き込まれる 更新にはSnapshot#version を使って楽観的ロック(条件付き書き込み)を行う 更新に成功したらversion をインクリメントする

Slide 25

Slide 25 text

【KJ 】イベントのみの書き込み Event はSnapshot と同じトランザクション。ただしSnapshot のversion 以外のカラムは更新しない 更新にはSnapshot#version を使って楽観的ロック(条件付き書き込み)を行う 更新に成功したらversion をインクリメントする

Slide 26

Slide 26 text

【KJ 】最新スナップショットの取得 集約ID からスナップショットデータを取得する

Slide 27

Slide 27 text

【KJ 】差分イベント集合の取得 集約ID とシーケンス番号から差分イベント集合を取得する。イベント集合は大きさの上限を決めることがで きる

Slide 28

Slide 28 text

【KJ 】リポジトリの実装例 findById 内部で集約をリプレイする。

Slide 29

Slide 29 text

【KJ 】ユーザアカウントのリプレイ

Slide 30

Slide 30 text

【KJ 】GroupChat#postMessage ドメインモデルは閲覧要件をカバーしない。あくまで更新動作に注力 インピーダンスミスマッチを解消しない。オブジェクトモデルだけで考える。 データモデルはイベントとリードモデルに任せる

Slide 31

Slide 31 text

まとめ コマンド側のドメインモデルは オブジェクトモデルだけで考えることができる 閲覧のことを考えないのでモデルが小さくなる クエリはクエリの事情に合わせて考える コマンドとクエリの間はどうすんの??? → また今度お会いするときにお話しましょう!

Slide 32

Slide 32 text

おしまい