Slide 1

Slide 1 text

ドメインイベントの観点から 再考するソフトウェア設計 かとじゅん( ) 2021/11/24 イミュータブルでゆこう 現場から学ぶモデル駆動設計 @j5ik2o 1

Slide 2

Slide 2 text

自己紹介 Chatwork社 テックリード 副業では技術顧問として活動中 Rustで関数型プログラミングやってます(完全に理解した状態) HaskellやScalaのParserコンビネータから影響を受けた JSONのParserは90行ぐらいで宣言的に書ける(if,for,while 不要) @j5ik2o https://github.com/j5ik2o https://github.com/j5ik2o/oni-comb-rs 2

Slide 3

Slide 3 text

今日はドメインイベントの話です 3

Slide 4

Slide 4 text

ドメインイベントは「過去に起きた」 ドメイン上の「出来事」を意味します 4

Slide 5

Slide 5 text

「過去に起きた」なので 原則的に後から変更できません (変更すると歴史の改ざん?) つまり不変(イミュータブル)なモデルです 5

Slide 6

Slide 6 text

ドメインイベントは主に以下の書籍で紹介されています 実際に実装上でドメインイベントが利用される事例も 増えています(というか昔からある) Chatworkのメッセージングシステムでも利用されています。 6

Slide 7

Slide 7 text

有益性は認識されつつありますが 「うちはEVENT SOURCINGじゃないので イベントは関係ありません」 と視野が狭くなっている方もいます 7

Slide 8

Slide 8 text

たとえ実装で使えなくても ドメイン分析に基盤的な視点を 与えてくれるのがドメインイベントです 8

Slide 9

Slide 9 text

今回は 「そもそもドメインイベントは ソフトウェア設計にどのような影響を与えるのか」 を再考する場にしたいと思います 9

Slide 10

Slide 10 text

旧アジェンダ これはまた別の機会に…。 禅の心で絞り込みました 10

Slide 11

Slide 11 text

アジェンダ ドメインイベントのWhat,Why ドメインイベントのHow 11

Slide 12

Slide 12 text

ドメインイベントのWHAT,WHY 12

Slide 13

Slide 13 text

ドメインイベントとは何か 13

Slide 14

Slide 14 text

ドメインイベントとは何か ドメインイベントはドメイン上の「出来事」 14

Slide 15

Slide 15 text

「出来事」とは何か 「出来事」とは起こった「事態(事柄)」 つまり 成立した「事態(事柄)」のこと 2020年 コロナウィルスが流行した 論理的に可能であれば成立していなくても「事態(事柄)」になる 不成立でも言語にして意味が通れば 論理空間の扱い 2021年 関東が沈没する 15

Slide 16

Slide 16 text

ドメインイベントは「事実」のこと 成立している「事態(事柄)」を「事実」と呼ぶ。 ドメインイベントは「事実」のこと ドメインイベント 起こった出来事 成⽴している事態(事柄) 事実 16

Slide 17

Slide 17 text

世界は事実(コト)の総体である 「世界は、事実 の総体である。事物 の総体ではない。」 世界が事物の総体であり、事実の総体ではないとしたら、「ここ にリンゴがある」 という事実すらも世界には含まれなくなって しまう 17

Slide 18

Slide 18 text

「事実」とは何か 事実=事態+成立 成立・不成立に関わらず、論理的に可能であれば「事態」 「事実」は事態の成立を意味する 「事態は、対象(事柄、事物)が結合したものである」 事態とは事柄と事物が結合したもの 事態 事物 事柄 事物 事実(成立した事態)には事柄や事物が結合する 18

Slide 19

Slide 19 text

ソフトウェア設計において ドメインイベントをなぜ使うのか 19

Slide 20

Slide 20 text

ドメインイベントの有用性 20

Slide 21

Slide 21 text

現場で役立つシステム設計の原則 21

Slide 22

Slide 22 text

コトに注目すると 全体の関係を整理しやすい(1/3) コトはヒトとモノとの関係として出現する 入荷イベントには注文商品・注文者などが紐付く コトがモノ・ヒトと関連する 注⽂商品(モノ) ⼊荷(コト) 注⽂者(ヒト) 22

Slide 23

Slide 23 text

コトに注目すると 全体の関係を整理しやすい(2/3) コトは時間軸に沿って明確な前後関係を持つ 入荷イベントの前に注文イベントがある。逆転はない イベントの順序 ⼊荷 注⽂ 23

Slide 24

Slide 24 text

コトに注目すると 全体の関係を整理しやすい(3/3) ヒトやモノから分析すると発散しがち コトを手がかりにすると効率的 ⼊荷 出荷 ⼊荷(コト) 出荷(コト) 商品(モノ) 担当者(ヒト) 24

Slide 25

Slide 25 text

コトは業務ルールの宝庫 コトに関連する業務知識を理解することで、業務知識をドメイン オブジェクトに反映できる 販売活動のイベントには前後関係がある 受注イベントを一つをとってみても特徴がある 発生源が外部のヒトである 将来についての(出荷・請求・入金の)約束である 受注 出荷 請求 ⼊⾦ 25

Slide 26

Slide 26 text

ソフトウェア設計において ドメインイベントをどう使うか (HOW) 26

Slide 27

Slide 27 text

EVENT SOURCING(以下 ES) イベントからステートを作り出すことができる 空のステートにイベントを適用することで 最新状態を得ることができる Cart replayCart(CartEvents cartEvents) { var cart = new Cart(...); for (event : events) { cart = applyEvent(cart, event); } } 27

Slide 28

Slide 28 text

EVENT SOURCINGの利点と欠点 利点 イベントは追記のみで更新がないので、スケーラビリティが 確保しやすい イベントがあればステートを導出できる。過去のどの時点で も 他のマイクロサービスと非同期連携がしやすい 監査ログや行動履歴の分析に利用できる 欠点 大量のイベントからステートをリプレイする際にレイテンシ が悪化する 原則的にすべてのイベントをストレージに保存する必要があ る 欠点はカバーするソリューションがある 28

Slide 29

Slide 29 text

FYI: EVENT STORMINGでは コトからモノを探索する イベントがわかればコマンドを想起できる。「出荷した」イベン トは「出荷指示」のコマンドが受理されたときに生成される 29

Slide 30

Slide 30 text

ESの典型的なシステム構成 クライアント コマンドプロセッサ 集約アクター ジャーナル リードモデルアップデータ リードDB クエリプロセッサ クエリAPI コマンドAPI イベントの追記 リプレイ イベントのコンシューム リードモデル構築 30

Slide 31

Slide 31 text

プログラミングモデルがどうなるか 31

Slide 32

Slide 32 text

CRUDモデル I/Oするのは最新のステートのみ 集約はイミュータブルにできる class AddCartItemUseCase { void execute(CartId cartId, ItemId itemId, ItemNum num) { // ランタイムに集約を呼び戻す var cart = cartRepository.findById(cartId); // ドメインロジックを呼び出す var cartUpdated = cart.addItem(itemId, num); // 集約を永続化する cartRepository.store(cartUpdated); } } 32

Slide 33

Slide 33 text

CRUD+OOPLの弱点 Cartの1アイテムを更新する場合でも 集約全体を手に入れる必要がある 集約全体を更新する必要がある(通常) できるだけ集約を小さくするべき…。 だが分割しすぎると守るべきルール(不変条件)すら 維持できなくなるので注意 // 集約全体を読み込む必要がある var cart = cartRepository.findById(cartId); // ドメインロジックを呼び出す var cartUpdated = cart.addItem(itemId, num); // 集約全体を書き込む必要がある cartRepository.store(cartUpdated); 33

Slide 34

Slide 34 text

ESモデル(1) I/Oするのはイベントのみ ステートはイベントから導出する 普通にやるとCRUDモデルより問題がでる イベントからのリプレイ イベントが長大だとレイテンシが悪化する データ競合を防ぐ仕組みが必要 class AddCartItemUseCase { void execute(CartId cartId, ItemId itemId, ItemNum num) { // すべてのイベントを読み込む var allEvents = cartEventRepository.findAllEventsById(cartId); // すべてのイベントを空の状態にアプライ var cart = Cart.formEvents(allEvents); // ドメインロジックを呼び出す var itemAdd = cart.addItem(itemId, num); // ステートではなく発生したイベントだけを追記 cartEventRepository.store(itemAdd); } } 34

Slide 35

Slide 35 text

ESモデル(2) スナップショットでイベントをショートカットする ロックのために集約本体と追記イベントを同一Txで保存する リクエスト毎に余分なI/Oオーバーヘッドがかかる… class AddCartItemUseCase { void execute(CartId cartId, ItemId itemId, ItemNum num) { // 最新のスナップショットを読み込む var snapshot = cartEventRepository.findLastestSnapshotById(cartId); // 差分イベントを読み込む var events = cartEventRepository.findAllEventsById( cartId, snapshot.sequenceNumber ); var cart = Cart.formEvents(snapshot, events); // リプレイ // ドメインロジックを呼び出す var newCartWithItemAdd = cart.addItem(itemId, num); // スナップショット保存&イベント追記 cartEventRepository.storeSnapshotWithEvent( newCartWithItemAdd.cart, newCartWithItemAdd.event ); } } 35

Slide 36

Slide 36 text

AKKA-ESモデル 集約アクターはリクエストから分離されたライフサイクルへ。 CRUD+OOPLのリプレイオーバーヘッド問題を軽減 そして集約アクター内の オブジェクトグラフが真のデータソー ス となる ワークロードがあるときにランタイム上に呼び戻す ワークロードがなくなればランタイムから消える class AddCartItemUseCase( cartAggregateActorRef: ActorRef[CartAggregateCommand] ) { def execute(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = { // ドメインロジックを呼び出す cartAggregateActorRef ! AddItem(cartId, itemId, num) } } 36

Slide 37

Slide 37 text

更新コマンドでも追記しかしない 集約はDBと完全に同期されるため 更新コマンドを処理するときでもイベントの追記のみ 集約アクター ドメインオブジェクト 状態は集約内部のドメインオブジェクトが保持する コマンド イベント 集約のバックアップログを保存する イベントの追記 しかし同一IDの集約アクターが複数のノードで起動すると スプリットブレインが発生する… 37

Slide 38

Slide 38 text

シャーディングされた集約アクタ Node1 Node2 Shard1 Shard2 Node3 Shard3 Database API ユースケース シャードリージョンプロキシ シャードリージョン1 集約アクター1 集約アクター2 集約アクター3 シャードリージョン2 シャードコーディネータ 集約アクター4 状態は集約内部のドメインオブジェクトが保持する ジャーナル 集約のバックアップログを保存する 集約IDからアクター参照を解決する アクターはシャーディングされるので、同一IDの集約アクターは 同一ノードでしか起動しない。集約へのコマンドメッセージはどの ノードが送信しても特定のノードに配送される。基本的にロック不 要。コマンドは到着順に処理される。 38

Slide 39

Slide 39 text

まとめ ドメインイベントは、分析に使える強力なツール 実装面においても、スケーラブルな設計を実現できる(ツールの サポートが必要) まずは、分析のツールとして使うことをお勧めしたい 39

Slide 40

Slide 40 text

終わり 40