Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
AWS データベースブログの記事 「Amazon DynamoDBによる CQRSイベントスト...
Search
かとじゅん
November 28, 2022
Programming
2
2.6k
AWS データベースブログの記事 「Amazon DynamoDBによる CQRSイベントストアの構築」 を勝手に読み解く
AWS データベースブログの記事「Amazon DynamoDBによるCQRSイベントストアの構築」を勝手に読み解くスライドです。
かとじゅん
November 28, 2022
Tweet
Share
More Decks by かとじゅん
See All by かとじゅん
メッセージとイベントを中核に置いたシステム設計の有用性について
j5ik2o
10
2.8k
私のキャリアの旅路: 技術をきっかけに変化を楽しむ
j5ik2o
3
770
いかに開発効率と品質を高めるか: ドメイン駆動設計と組織パターンの視点から考える
j5ik2o
3
2.3k
社内のメンバーに「関数型プログラミングの学習・教育」についていろいろ聞いてみた
j5ik2o
2
1.7k
EIPとAkkaについて
j5ik2o
3
2.5k
モデルを中心にデザイン(設計)すること
j5ik2o
2
2.6k
ドメインイベントの観点から再考するソフトウェア設計
j5ik2o
17
10k
セキュリティのためのソフトウェア設計について
j5ik2o
4
1.9k
AWS Dev Day 2021 - AWSでスケーラビリティとレジリエンスを実現するアーキテクチャを考える
j5ik2o
2
1.7k
Other Decks in Programming
See All in Programming
subpath importsで始めるモック生活
10tera
0
300
GitHub Actionsのキャッシュと手を挙げることの大切さとそれに必要なこと
satoshi256kbyte
5
430
ピラミッド、アイスクリームコーン、SMURF: 自動テストの最適バランスを求めて / Pyramid Ice-Cream-Cone and SMURF
twada
PRO
10
1.3k
카카오페이는 어떻게 수천만 결제를 처리할까? 우아한 결제 분산락 노하우
kakao
PRO
0
110
OnlineTestConf: Test Automation Friend or Foe
maaretp
0
110
WebフロントエンドにおけるGraphQL(あるいはバックエンドのAPI)との向き合い方 / #241106_plk_frontend
izumin5210
4
1.4k
聞き手から登壇者へ: RubyKaigi2024 LTでの初挑戦が 教えてくれた、可能性の星
mikik0
1
130
アジャイルを支えるテストアーキテクチャ設計/Test Architecting for Agile
goyoki
9
3.3k
Kaigi on Rails 2024 〜運営の裏側〜
krpk1900
1
190
Laravel や Symfony で手っ取り早く OpenAPI のドキュメントを作成する
azuki
1
110
Click-free releases & the making of a CLI app
oheyadam
2
110
Quine, Polyglot, 良いコード
qnighy
4
640
Featured
See All Featured
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
28
2k
Navigating Team Friction
lara
183
14k
Raft: Consensus for Rubyists
vanstee
136
6.6k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
329
21k
Thoughts on Productivity
jonyablonski
67
4.3k
Speed Design
sergeychernyshev
24
610
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
860
The Language of Interfaces
destraynor
154
24k
Ruby is Unlike a Banana
tanoku
97
11k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
364
24k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
4
370
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5k
Transcript
© Chatwork CQRS Meetup 【Chatwork × ZOZO】 2022年11月25日 Chatwork株式会社
AWS データベースブログの記事 「Amazon DynamoDBによる CQRSイベントストアの構築」 を勝手に読み解く
会社概要 会社名 Chatwork株式会社 代表取締役CEO 山本 正喜 従業員数 284名(2022年6月末日時点) 所在地 東京、大阪、ベトナム、台湾
設立 2004年11月11日 2
コーポレートミッション 働くを もっと楽しく、 創造的に 人生の大半を過ごすことになる 「働く」という時間において、 ただ生活の糧を得るためだけではなく、 1人でも多くの人がより楽しく、 自由な創造性を存分に発揮できる社会を実現する 3
Chatwork は日本最大級のビジネスチャットサービス 3月 リリース 30万社 突破! 20万社 突破! 導入社数34.3万社を突破! (2021年12月末日時点)
10万社 突破! 4 ※Nielsen NetView 及びNielsen Mobile NetView 2020年6月度調べ月次利用者 (MAU:Monthly Active User)調査 ※調査対象 44サービスはChatwork株式会社にて選定
2016年末 CQRS/ESシステムをリリース 5 • Akka+Kafka+HBaseで実現 • Chatworkではダブルコミットはしない 事例紹介されている本
自己紹介 6 • Chatworkのテックリード。10歳で初めてプログラミン グに触れる。SIとしてさまざまな現場での業務を経験し た後、2011年より某D社、2013年より大手ソーシャル ゲーム企業で、それぞれScalaやドメイン駆動設計を採 用したシステム開発に従事。2014年7月よりChatwork に参画。現在はChatwork次期アーキテクチャのプラン ニングや設計、開発に携わる。
@j5ik2o
AGENDA 以下のAWS Blogを解説します。 「Amazon DynamoDBによるCQRSイベントストアの構築」 アジェンダ
イベントソーシングとCQRSの概要 1
CQRSのアーキテクチャ 9 • WRITE(コマンド)モデルはコマンドを処理する集約で構成される • READ(クエリ)モデルはクエリを受け取り、そのクエリをマテリアライズド ビューに対して適用するもので、読み取り要求をサポートする
コマンドと集約とイベント 10 • ドキュメントでは、Widgetのリネームが例示されている ChangeWidgetName (コマンド) WidgetNameChanged (イベント) Widget (集約)
DynamoDBによるイベントストアの設計 2
DynamoDB 12 • RDBなどを使ってもイベントストアは構築できるが、DynamoDBを使うと以下の主 な利点がある ◦ サーバレスで、独立性が高くスキーマレスな性質を持つイベントをスケーラブ ルに書き込みができる ◦ 変更データキャプチャ(CDC)はDynamoDB
Streams, KDS for DynamoDB
DynamoDBのイベントストアで使うエンティティタイプ 13 • Aggregate ◦ ドメインオブジェクトをカプセル化する責務 • Event ◦ 何かが起こったことを示すイベント。原則的
に不変 • Snapshot ◦ 特定の時系列ポイントまでに起こったイベン トから導出された状態を示すスナップショッ ト。スナップショットを使うとランタイムで 全てのイベントの履歴の保持やロードが不要 になる。
Eventテーブル 14 • PartitionKey(PK)はWidget集約の識別子(ID) • Sort Key(SK)はイベントの時系列番号=シーケンス番号(集約単位で一意である必要がある) • WidgetCreated →
WidgetNameChanged → WidgetDescriptionChanged • イベントのペイロードは JSON にシリアライズされた状態で保存。ProtocolBufferなどを利用する
Aggregateテーブル 15 • 集約の現在の状態を示すテーブル • PKは集約のID • last_eventsは未処理のイベント集合 • 楽観的ロックのためにversionを使う
Snapshotテーブル 16 • 過去の集約状態を示すテーブル • PKは集約のID • SKはシーケンス番号 • event_numberはスナップショット作成時処理されたイベント数
◦ イベント数なの? ◦ シーケンス番号のほうが正しいと思われる
集約読み込みのアルゴリズム 3
• 集約ルートの項目を取得 • 現在の集約の状態を準備 • 最新のスナップショット を読み込む • スナップショットに取り 込んでいない残りのイベ
ントを読み込む 集約読み込み時(リプレイ)のアルゴリズム 18
• GetItemを使い、集約 IDをもとに、 Aggregateテーブルか ら関連する項目を取得 する • 見つからない場合はク ライアントにエラーを 返す
①集約ルート項目を取得する 19
• 未処理イベント (last_events)があれば PutItemする ②現在の集約の状態の準備 20
• ScanIndexForward: false + Limit 1で最新 のSnaptshotテーブル を読み込む ③最新のスナップショットを読み込む 21
• PK = 123 and SK > event_number で Eventテーブルをクエリ
する ④差分のイベントを読み込む 22
集約の保存アルゴリズム 4
• 保存されるのは最新の ステートではなく未処 理のイベント (last_events) • versionで楽観的ロック ①集約ルート項目を保存する 24
• 集約の現在の状態、具 体的な項目を保存する • Snapshotテーブル自体 への保存はオプション ②スナップショット項目の保存(任意) 25
集約の読み込みと保存の例 5
• CreateWidgetのコマンドを受理・処理したら、Aggregateテーブル にWidgetCreatedを追加する CreateWidgetコマンド 27
ChangeWidgetNameコマンド 28 Aggregateテーブル Eventテーブル • Aggregateテーブルのlast_events(WidgetCreated)をEventテーブルを移す • WidgetNameChangedをlast_eventsに追加する
• Aggregateテーブルのlast_events(WidgetNameChanged)をEventテーブルを移す • WidgetDescriptionChangedをlast_eventsに追加する ChangeWidgetDescriptionコマンド 29 Aggregateテーブル Eventテーブル
DynamoDB Streams によるイベントストアの変更 データキャプチャ 6
DynamoDB Streams によるイベントストアの変更データキャプチャ 31 • Eventテーブルの変更をDynamoDB Streamsを通じて変更を取得できる ◦ 変更をキャプチャできるのは1度だけ ◦
キャプチャ前のデータは24時間後に消える DynamoDB Application (DynamoDB Streams Client) Event をストリーム経由で読み込む
私なりのレビュー 7
• スケーラビリティを犠牲にする トランザクションをいかに避け るかという観点が盛り込まれて いる ◦ 集約が複数のイベントを発 生させた場合でも単一の書 き込みにできる ◦
集約状態+上限数を設けた 未処理イベントの組み合わ せでWCUをキャップできる 前提) 書き込みのスケーラビリティを犠牲にしない設計戦略 33
懸念) 集約を更新しても、集約を読まないとイベントがCDCできない? 34 • 集約を更新しても、イベントは すぐにイベントテーブルに保存 されない。CDCできない? • 集約を読み出さないと、イベン トが流れない?
集約が読まれるタイミングで EventのPutItemが起きる… イベントはAggregateテーブルに一時的に保存される イベントはAggregateテーブルから CDCする
懸念事項)リプレイ中のPutItemがコンフリクトするのでは? 35 • PutItemする複数スレッ ドで更新が重なりそう。 複数回、同一イベントが 発生しないのか? • 読み込み動作中に書き込 み動作を行うことはCQS
違反ではある EventをCDCしないなら、Eventの書き込みは後勝ち?になるだけ
懸念事項) リプレイ中のPutItemがコンフリクトするのでは?(図解) 36 サーバー1 getAggreateState(id=1) サーバー2 getAggreateState(id=1) Aggregate Event Event
A { PK: 123, SK: 1, created: 1, name: WidgetCreated, payload: { name: WidgetCreated } Event A’ { PK: 123, SK: 1, created: 2, name: WidgetCreated, payload: { name: WidgetCreated } Event A’ Event A 後勝ち?イベントをCDCすると問題があ る? イベントテーブルから CDCしないのでこ の問題は無害
• あなたの要件次第。このドキュ メントもその前提で読む • 1度に格納するイベント数を100 件までにすれば、集約+イベン トという非正規化テーブルも考 えられる すべてに合理的な設計戦略はない 37
FYI) トランザクションを使う方法もシンプル 38 • Aggregateテーブルのlast_eventsを廃止する • Aggregate + Events でTransactWriteItems(PutItem)
+ version で楽観的ロック。ただしスケーラビリティが犠牲になる サーバー1 updateAggreateState(id=1) サーバー2 updateAggreateState(id=1) Aggregate Event TransactWriteItems(PutItem) + version optimistic lock
FYI) 1コマンドによってNイベントが発生するか? 39 • ないとはいえないが、懐疑的です • カート追加コマンドで値引きイベントが生じるかどうか。責務の観点からはそ ういうことは起きない可能性が高い。意図しない副作用ではないか AddCartItem (コマンド)
CartItemAdded (イベント) Cart (集約) CartItemDiscounted (イベント) コマンドにあっているイベントなのか 意図しない副作用ではないか?
FYI) 1コマンドによってNイベントが発生するか? 40 • 値引きコマンドの意図を明確したインターフェイスに変更するにはポリシーを使う • 非イベント駆動ならユースケース内で順番にコマンドを実行するフローを制御する AddCartItem (コマンド) CartItemAdded
(イベント) Cart (集約) CartItemDiscounted (イベント) DiscountCartItem (コマンド) DiscountPolicy (ポリシー)
結論 8
• 特定の言語/フレームワークに依存しないCQRS/ESの実装モデルが公 開されたことは、CQRS/ESの認知・普及に一定効果があると思われる • トランザクション使用の有無で設計戦略が大きく変わりそう ◦ スケーラビリティを重視するなら今回の設計パターンは有用 ◦ そうでないならトランザクションのパターンも検討する まとめ
42
Chatwork 株式会社では、 日本全体を DX する ことの実現に向けて、 全方位でエンジニアを募集しています! We are Hiring
!!! 43 働くを もっと楽しく、 創造的に
44 チャットサービスづくり ≒ ハイトラフィックとの戦い "落とさないこと" を意識してインフラを構成 DAU 100 万人 が
1 日平均 8 時間利用! 470万 ユーザー 突破! 1 Web アプリという枠組みではもはやなく、 仕事を支える 一種の社会基盤 というフェーズへ (サービスダウンすると災害情報に載ることも …)
45 社会基盤を見据えたアプリケーション設計 45 state Shard Shard ShardR egion RoomAggregateActor Journal
DB (DynamoDB) SnapshotStore (S3) Message Bus RMU Read DB Read API Read API Read API Controller UseCase Write API Server Write API Server Write API Server akka-clsuter 他の MS へ logic Client ID = 1 サーバーサイド・チーム クライアントサイド・チーム リアクティブシステム と CQRS / ES を反映したアーキテクチャを採用 (クレジット決済基盤 / 銀行送金基盤 / 証券取引基盤などで実績あり) • 非同期・ノンブロッキング • スーパービジョン • 位置透過性 • ステートフル • DBとの完全な同期によって読み込み不要 • ワークロードのパーティショニング • 正規化されたデータ構造を扱う • ネットワーク分断時は一貫性を重視 コマンドサイドではドメインロジックを実行して ドメイン状態を変える機能のみを提供する クエリサイドはドメイン イベントをもとにクライアントに とって都合のよいリードモデルを 構築する • 非同期・ノンブロッキング • ステートレス • 非正規型データを扱う • ネットワーク分断時は可能性を重視 • ラムダアーキテクチャでも十分可能 MessagePosted MessageDTO PostMessage MessageDTO ID = 2 ID = 3
参考:代表的な技術スタック 46
None