$30 off During Our Annual Pro Sale. View Details »

CQRSはEvent Sourcingなしで実現できるのか?

CQRSはEvent Sourcingなしで実現できるのか?

かとじゅん
PRO

March 24, 2020
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

  1. CQRSはEvent Sourcingなしで実現できるのか?
    かとじゅん(@j5ik2o)
    BPStudy#151〜オブジェクト指向、モデリング、設計 LT大会[リモート開催]
    2020/03/30

    View Slide

  2. © Chatwork
    あなたは誰?
    ● 加藤潤一(@j5ik2o)
    ● Chatwork社のテックリード
    ● 直近の活動
    ○ Kafka, S3のEvent Sourcingする
    ためのプラグインを開発
    ○ Fargateを使った負荷試験ツール
    の開発

    View Slide

  3. © Chatwork
    FYI: ChatworkのEvent Sourcing事例
    ● 2016年にNTTデータさんと共同開発したプロジェクトでCQRS+ESを
    採用した(詳細は以下スライド参照)
    ● Apache Kafkaの以下の書籍に事例が紹介されています

    View Slide

  4. © Chatwork
    CQRSはEvent Sourcingなしで実現できるのか?
    ● 今日は「モノとの繋がりをトレースできるイベント=
    コトはドメイン分析に使える」という話題ではなく、
    イベントを使ったアーキテクチャに関する話題
    ● CQRSにはEvent Sourcingは必須?大袈裟では?
    ● State Sourcing(CRUD)のままで、CQRSをすること
    ができるかどうか考えてみたい

    View Slide

  5. © Chatwork
    CQRS
    ● Command and Query Responsibility
    Segregation(CQRS責務分離)
    ○ 2010年 Greg Young氏
    ● CQS(Command-Query Separation)
    をアーキテクチャレベルに適用
    ● CommandとQueryで要件が違うから
    わける
    ○ 一貫性(強い or 弱い)
    ○ データ形式(正規化 or 非正規化)
    ○ スケーラビリティ(C:Q=2:8)

    View Slide

  6. © Chatwork
    FYI: C/Qを分離しない場合の弊害
    ● リポジトリのクエリメソッドが複雑になる
    ○ employeeRepository.findByDeptIdsWithEmpNamesWith…(…, …, …)
    ● N+1クエリが発生しやすい
    reservationRepository.findByIds(ids).map { reservation =>
    val a = hotelRepository.findById(reservation.hotelId)
    val b = customerRepository.findById(reservation.customerId)
    readModelDto(a, b)
    }
    ● オブジェクト→DTO変換が非効率
    ○ DTOに変換するときに、UIに合わせて捨てられる項目がある

    View Slide

  7. © Chatwork
    FYI: リードモデルの形式変換をしない場合の問題
    ● 例えば、入社日がEpocTime(Long値)として保存される場合、そのまま
    ではクエリのレスポンスに含めることができない。しかしクエリ側から
    値オブジェクトに依存ができない。
    ● これを回避するためにRMUで事前に形式変換する必要がある
    Attribute Type Value
    EmployeeId String “123456789”
    JoinedDate Long 1585039714572
    ... ... ..
    JoinedDate#asFormattedString
    2020年03月24日 08:48
    内部データ表現を意味のある値表現にするにはドメ
    インオブジェクトを使う必要がある
    エンコードされたドメインオブジェクト
    リードしたい値表現

    View Slide

  8. © Chatwork
    C/Qのモデル間の変換・同期の問題はイベントで解決
    “The model that is best suited is the introduction
    of events, events are a well known integration
    pattern and offer the best mechanism for model
    synchronization.” by Greg Young
    最も適したモデルはイベントの導入であり、イベントは
    よく知られた統合パターンであり、モデルの同期化に最
    適なメカニズムを提供します。

    View Slide

  9. © Chatwork
    Event Sourcing
    ● 過去に起きた出来事=イベント
    ● イベントは不変で追記のみ
    ● イベントを再生(リプレイ)すると最新状態が手に入る

    View Slide

  10. © Chatwork
    CQRS+Event Sourcing
    ● 真のデータソースは永続されたイベ
    ント
    ● そのイベントを使ってリードモデル
    を構築する
    Event Sourcingは大袈裟で大変と
    いう意見を聞くので、他の方法がな
    いか考えてみた

    View Slide

  11. © Chatwork
    真のデータソースを最新状態にできないか

    View Slide

  12. © Chatwork
    アプリケーションからのイベント伝搬方式
    ● Write DBへ最新状態をロック付きで書き込み、成功したらキューにイベ
    ントを書き込む方法。これは非常にまずい方法。

    View Slide

  13. © Chatwork
    アプリケーションからのイベント伝搬方式の問題
    ● イベントは全順序に並んでいないとうまくリードモデルを構築できない
    ● ステートレスなアプリケーションでは、2層コミット問題とイベント追い
    越し問題が起きる
    (参考文献) スターバックスは2フェーズコミットを使わない
    https://code.google.com/archive/p/gregors-ramblings-ja/wikis/18
    _starbucks.wiki
    最新状態を保存する DBとキューのトランザク
    ションを同一化しないと防ぐのが難しい。もしくは
    DB書き込み成功後にエンキューするプロセスを
    シャーディングする必要がある

    View Slide

  14. © Chatwork
    CDC(Change Data Capture)+Outbox方式
    ● CDCはWriteDBでの変更を捉える方式。WriteModelと同一TxのOutbox
    内にドメインイベントを保存(一時保存でもよい)し下流に伝搬させる
    ● DebeziumにはMySQLのbinlogを読む機能がある。DBのトランザクショ
    ンログはイベントと同等。結局Event Sourcingになっている

    View Slide

  15. © Chatwork
    FYI: AkkaのES方式
    ● トランザクションをActorが管理しているため、イベント列への書き込
    みの整合性が保たれる。スナップショットがあれば、それより古いイベ
    ントは削除可能。
    ● ステートフルウェブアプリケーションになる

    View Slide

  16. © Chatwork
    まとめ
    ● 単純にイベントを伝搬させるだけでは問題が生じる
    ● ステートレスなウェブアプリケーションでは、
    CDC+Outbox方式を採用したほうが無難
    ● CDC+Outbox方式は、トランザクションログが真の
    データソースでありESの一種であるという理解が必要
    ● 外形的にはCQRS+SSはできるように見えるが、真の
    データソースは最新データにはならない。結局はESに
    帰結する

    View Slide

  17. © Chatwork
    ありがとうございました!

    View Slide

  18. View Slide