Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

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

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

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

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

かとじゅん
PRO

November 23, 2021
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. ドメインイベントは主に以下の書籍で紹介されています



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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    ドメインイベント
    起こった出来事
    成⽴している事態(事柄)
    事実
    16

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    コトがモノ・ヒトと関連する
    注⽂商品(モノ) ⼊荷(コト) 注⽂者(ヒト)
    22

    View Slide

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

    イベントの順序
    ⼊荷
    注⽂
    23

    View Slide

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

    ⼊荷
    出荷
    ⼊荷(コト)
    出荷(コト)
    商品(モノ)
    担当者(ヒト)
    24

    View Slide

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

    View Slide

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

    View Slide

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

    var cart = new Cart(...);

    for (event : events) {

    cart = applyEvent(cart, event);

    }

    }
    27

    View Slide

  28. EVENT SOURCINGの利点と欠点
    利点
    イベントは追記のみで更新がないので、スケーラビリティが
    確保しやすい
    イベントがあればステートを導出できる。過去のどの時点で

    他のマイクロサービスと非同期連携がしやすい
    監査ログや行動履歴の分析に利用できる
    欠点
    大量のイベントからステートをリプレイする際にレイテンシ
    が悪化する
    原則的にすべてのイベントをストレージに保存する必要があ

    欠点はカバーするソリューションがある
    28

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    var cart = cartRepository.findById(cartId);

    // ドメインロジックを呼び出す

    var cartUpdated = cart.addItem(itemId, num);

    // 集約全体を書き込む必要がある

    cartRepository.store(cartUpdated);
    33

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  40. 終わり
    40

    View Slide