Save 37% off PRO during our Black Friday Sale! »

ドメインイベントの観点から再考するソフトウェア設計

 ドメインイベントの観点から再考するソフトウェア設計

ドメインイベントは過去に起きたドメイン上の出来事を意味します。「過去に起きた」なので後から変更できません。つまり不変(イミュータブル)なモデルです。

昨今、このドメインイベントはCQRS/Event Sourcingやマイクロサービスなどの書籍で取り上げられ、実際に実装上でドメインイベントが利用される事例も増えています。このように有益性は認識されつつありますが「うちはEvent Sourcingじゃないのでイベントは関係ありません」と視野が狭くなっている方もいます。

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

ともあれ、この資料は「そもそもドメインイベントはソフトウェア設計にどのような影響を与えるのか」を解説します。

933291444e456bfb511a66a2fa9c6929?s=128

かとじゅん

November 23, 2021
Tweet

Transcript

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

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

    https://github.com/j5ik2o/oni-comb-rs 2
  3. 今日はドメインイベントの話です 3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    事実(成立した事態)には事柄や事物が結合する 18
  19. ソフトウェア設計において ドメインイベントをなぜ使うのか 19

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

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

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

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

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

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

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

  27. EVENT SOURCING(以下 ES) イベントからステートを作り出すことができる 空のステートにイベントを適用することで 最新状態を得ることができる Cart replayCart(CartEvents cartEvents) {

    var cart = new Cart(...); for (event : events) { cart = applyEvent(cart, event); } } 27
  28. EVENT SOURCINGの利点と欠点 利点 イベントは追記のみで更新がないので、スケーラビリティが 確保しやすい イベントがあればステートを導出できる。過去のどの時点で も 他のマイクロサービスと非同期連携がしやすい 監査ログや行動履歴の分析に利用できる 欠点

    大量のイベントからステートをリプレイする際にレイテンシ が悪化する 原則的にすべてのイベントをストレージに保存する必要があ る 欠点はカバーするソリューションがある 28
  29. FYI: EVENT STORMINGでは コトからモノを探索する イベントがわかればコマンドを想起できる。「出荷した」イベン トは「出荷指示」のコマンドが受理されたときに生成される 29

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

    イベントの追記 リプレイ イベントのコンシューム リードモデル構築 30
  31. プログラミングモデルがどうなるか 31

  32. 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
  33. CRUD+OOPLの弱点 Cartの1アイテムを更新する場合でも 集約全体を手に入れる必要がある 集約全体を更新する必要がある(通常) できるだけ集約を小さくするべき…。 だが分割しすぎると守るべきルール(不変条件)すら 維持できなくなるので注意 // 集約全体を読み込む必要がある var

    cart = cartRepository.findById(cartId); // ドメインロジックを呼び出す var cartUpdated = cart.addItem(itemId, num); // 集約全体を書き込む必要がある cartRepository.store(cartUpdated); 33
  34. 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
  35. 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
  36. AKKA-ESモデル 集約アクターはリクエストから分離されたライフサイクルへ。 CRUD+OOPLのリプレイオーバーヘッド問題を軽減 そして集約アクター内の オブジェクトグラフが真のデータソー ス となる ワークロードがあるときにランタイム上に呼び戻す ワークロードがなくなればランタイムから消える class

    AddCartItemUseCase( cartAggregateActorRef: ActorRef[CartAggregateCommand] ) { def execute(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = { // ドメインロジックを呼び出す cartAggregateActorRef ! AddItem(cartId, itemId, num) } } 36
  37. 更新コマンドでも追記しかしない 集約はDBと完全に同期されるため 更新コマンドを処理するときでもイベントの追記のみ 集約アクター ドメインオブジェクト 状態は集約内部のドメインオブジェクトが保持する コマンド イベント 集約のバックアップログを保存する イベントの追記

    しかし同一IDの集約アクターが複数のノードで起動すると スプリットブレインが発生する… 37
  38. シャーディングされた集約アクタ Node1 Node2 Shard1 Shard2 Node3 Shard3 Database API ユースケース

    シャードリージョンプロキシ シャードリージョン1 集約アクター1 集約アクター2 集約アクター3 シャードリージョン2 シャードコーディネータ 集約アクター4 状態は集約内部のドメインオブジェクトが保持する ジャーナル 集約のバックアップログを保存する 集約IDからアクター参照を解決する アクターはシャーディングされるので、同一IDの集約アクターは 同一ノードでしか起動しない。集約へのコマンドメッセージはどの ノードが送信しても特定のノードに配送される。基本的にロック不 要。コマンドは到着順に処理される。 38
  39. まとめ ドメインイベントは、分析に使える強力なツール 実装面においても、スケーラブルな設計を実現できる(ツールの サポートが必要) まずは、分析のツールとして使うことをお勧めしたい 39

  40. 終わり 40