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

リアクティブシステムとCQRS/ESで実現する Chatwork新アーキテクチャについて

リアクティブシステムとCQRS/ESで実現する Chatwork新アーキテクチャについて

ChatworkではリアクティブシステムとCQRS/ESを反映した次期アーキテクチャを構想しています。まだ構想段階ではありますが、なぜそれらを採用するのかメリット・デメリットも含めてご説明します。

かとじゅん

March 05, 2021
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

  1. 自己紹介 • 加藤潤一(かとじゅん) ◦ @j5ik2o • Chatwork テックリード ◦ 仕事ではサグラダ・ファミリア的なプロダクト作ってます

    • 最近やっていること ◦ FP in ScalaをRustで写経(Propsは実装できた) ◦ 匠真堂 ▪ 月一ラジオ番組やってます • お知らせ ◦ Chatworkが10周年迎えました ◦ Chatwork Dev Day やります 2
  2. アーキテクチャ刷新プロジェクト • Falconプロジェクト(2014〜2016年) ◦ メッセージ機能をCQRS+ESシステム(Akka, Kafka, HBase)としてリプレース • Gaudiプロジェクト(2019年〜) ◦

    メッセージ機能以外も刷新することが目的 ◦ 技術検証と全体設計が完了 ◦ 2021年から開発を開始し社内で検証を開始する 7
  3. アーキテクチャ刷新の目的と手段 • 目的 ◦ 2025年の事業計画に合わせて生産性を維持・向上できるプロダクトと組織を 構築する • 手段 ◦ プロダクトの健全性を維持・向上させること

    ◦ チームが独立して改善活動をおこなえること ◦ 大規模なアジャイル開発が実践できること ◦ いつでもレスポンスを返せる状態になっていること ▪ リアクティブシステムを反映したアーキテクチャを実現する ▪ CQRS/Event Sourcingシステムを実現する 11 本日話すところ
  4. 支える原理 手段 届けたい価値 リアクティブシステムとは • 即応性(Responsive) ◦ システムは可能な限りすみやかに応答します • 耐障害性(Resilient)

    ◦ システムは障害に直⾯しても即応性を保ち続 けます • 弾力性(Elastic) ◦ システムはワークロードが変動しても即応性 を保ち続けます • メッセージ駆動(Message-Driven) ◦ リアクティブシステムは⾮同期・ノンブロッ キングなメッセージ・パッシングによってコ ンポーネント間の境界を確⽴します。 メッ セージ駆動は上記を実現するために利用され る手段 • 詳しくはこちらのブログ記事を参考にしてみてくだ さい 16 即応性(Responsive) メッセージ駆動(Message-driven) 伸縮性(Elastic) 耐障害性(Resilient)
  5. 【FYI】リアクティブシステム ≠ リアクティブプログラミング • 最近、リアクティブシステムとリアクティブプログラミングと いう用語に頻繁に遭遇します。よく誤解されていますが、これ らは等価ではありません。 • リアクティブシステムは、アーキテクチャレベルでリアクティ ブ原則を適用しています。リアクティブシステムを実現する手

    段としてFuture/Promise, Reactive Streams, アクターモデル などのリアクティブプログラミングが利用されます。だからと いって、自動的にリアクティブシステムになりません。 • 例えば、アプリケーションを1ノードだけにデプロイした場 合、そのノードが故障したら全システムを失います。これでは リアクティブ宣言の耐障害性(回復力)がないので、リアクティ ブプログラミングを使っていても、リアクティブシステムでは ありません。 17 Node 1 1台のみで運用。この 1台 が故障すると全システム を失う
  6. なぜリアクティブシステムか • リアクティブシステムは、どんな状況でも即応性があるシステムのこと ◦ 簡単にいえば、「いつでも使えるシステム」を実現するための考え方のこと • リアクティブ原則(The Reactive Principles) ◦

    応答性を維持する/Stay Responsive ◦ 不確実性を受入る/Accept Uncertainty ◦ 失敗を受け入れる/Embrace Failure ◦ 自律性を表明する/Assert Autonomy ◦ 一貫性を調整する/Tailor Consistency ◦ 時間を分離する/Decouple Time ◦ 空間を分離する/Decouple Space ◦ ダイナミクスを処理する/Handle Dynamics 18
  7. 失敗を受け入れる/Embrace Failure • expect things to go wrong and build

    for resilience ◦ 物事がうまくいかないことを期待し、回復力のために構築する • キーとなる考え方はBulkheading ◦ Bulkheadingは船舶由来の用語です。大型貨物船の船倉は隔壁によって多くの区画に分割されています。船底 が何らかの原因で破損した場合でも、影響を受けた区画だけが浸水し、他の区画は適切に密閉された状態を維 持できるため浮力を維持できます。 ◦ 以下の図はReactive Design Patternsで紹介されています。 19
  8. CQRSとは • Command and Query Responsibility Segregation = コマンド・クエリ責務分離 のこと。分離というより隔離という解釈が

    正しい • コマンド(書き込み)とクエリ(読み込み)を スタックごとにそれぞれに隔離することを 意味する。単にドメインモデルをコマンド 用・クエリ用に分割することではない(理 由はこちら参照) • CQRSはDDDを前提としています(ドメイン から本質的ではないクエリ責務を排除する ための設計パターンです)。詳しくは CQRS Documents by Greg Young を参照 のこと 22
  9. なぜCQRSか • コマンド側とクエリ側で以下のように要件がことなるため、コマンドとクエリをそ れぞれを隔離する 24 コマンド クエリ 一貫性/可用性 トランザクション整合性を使い 一貫性を重視する

    結果整合を使い可用性を重 視する データ構造 トランザクション処理をおこな い正規化されたデータを保存 することが好まれる(集約単 位など) 非正規化したデータ形式を取 得することが好まれる (クライ アント都合のレスポンスなど ) スケーラビリティ 全体のリクエスト比率とごく少 数のトランザクション処理しか しない。必ずしもスケーラビリ ティは重要ではない 全体のかなりのリクエスト比 率を占める処理をおこなうた め、クエリ側はスケーラビリ ティが重要
  10. CQRSではない場合の問題(1/2) • クエリ要件を満たすことでリポジトリが複雑になる ◦ 「クエリするだけでドメインロジックを呼び出さない」はドメインの責務らし くない。他にもページングやソートも扱うケースがある…。 • レスポンス用DTOをリポジトリで組み立てるため、N+1クエリが発生する ◦ 本来RDBに任せるようなことをアプリケーション空間でやっている。リポジト

    リはそもそもこのような用途には向かない 26 val employees = employeeRepository.findByDeptIdsWithEmpNamePatterns(deptIds, empNamePatterns) // このあとに、ドメインロジックはない。 DTOに詰め直してクライアントに返すだけ。 val reservationDtos = reservationRepository.findByIds(ids) // SQL発行 .map{ reservation => val hotel = hotelRepository.findById(reservation.hotelId) // SQL発行 val customer = cusotmerRepository.findById(reservation.customerId) // SQL発 new ReservationDto(reservation, hotel.name, customer.name) // アプリケーション空間で結合及びデータを捨てる }
  11. CQRSではない場合の問題(2/2) • コマンドを意識しないデータ指向では、エンドポイント、アプリケーションサービ ス、ドメインがCRUDの用語に汚染されてしまう、という仮説がある ◦ 商品の注文=createPurchaseItem ◦ 注文のキャンセル=updatePurchaseItem • ドメインの動詞を重視するコマンド指向では、orderItem,

    cancelOrderなどユビキ タス言語にフォーカスできるようになる。コマンドの表現によって意図が明白なイ ンターフェイスを作ることができる ◦ ただこの考え方は、CRUDであっても頑張れば可能…。 ◦ 実装というより分析の段階でコマンドを使うことのメリットが強い 27
  12. CQRSの利点と欠点 • 利点 ◦ コマンドとクエリが分離しているため、耐障害性が高くなる(耐障害性に寄与 する)。別々にデプロイできる ◦ コマンドとクエリを必要に応じて個別に最適化できる(弾力性に寄与する)。 別々にスケールさせることもできる •

    欠点 ◦ 非CQRSと比べてコストが掛かる。目的ごとにサブシステムを分けるので構成 要素が多くなる ◦ CQRSではC/Qごとにモデルが分離するため、単一モデルとしてシンプルだが ネットワーク全体としては複雑になる 28
  13. 【FYI】ドメインイベントとは • イベントは過去に起きた出来事を意味する • ドメインイベントは、ドメイン上のイベントを意味する • 一般的には過去形の動詞として表現される ◦ CargoShipped ◦

    CustomerRelocated • イベントからコマンドが想起可能 ◦ ShipCargo ◦ RelocateCustomer • イベントとコマンドは似ているが別概念 ◦ コマンドは拒否されることがある ◦ イベントはすでに起こったことを示す 31
  14. なぜEvent Sourcingか • CQRSはコマンド側からクエリ側に変更を伝える必要がある ◦ コマンド側のドメインイベントをクエリ側に伝える ◦ 場合によっては、コマンド側のDBに存在しない、ドメインの計算結果をクエリ側に伝える 必要がある •

    上記の現実的な実現手段として以下がある ◦ Event Sourcing ◦ CDC + Outbox ▪ アプリケーションではState Sourcingだが、ミドルウェアレベルではEvent Sourcingになっている ▪ マイクロサービスのための分散データ 〜 イベントソーシング vs チェンジデータ キャプチャ • 結局はEvent Sourcing以外に現実的な選択肢はない ◦ 詳しくは CQRSはなぜEvent Sourcingになってしまうのか を参照 32
  15. Event Sourcingの利点と欠点 • 利点 ◦ イベントは更新されず追記のみなので、スケーラビリティが確保しやすい ◦ 特定の時点のリードモデルをイベントから導出することができる ◦ ドメインイベントがあれば、リードモデルの設計をいつでもやり直せる

    ▪ 実装コスト・データマイグレーションコストではゼロではないが ◦ 監査ログや行動履歴の分析に利用することができる ▪ 技術的に可能だが、GDPRなどの同意があるかは別問題 • 欠点 ◦ 大量のイベントから状態をリプレイする際に時間がかかる ▪ スナップショットを使うとリプレイ時間を短縮できる ◦ 原則的にすべてのイベントをストレージに保存する必要がある ▪ スナップショット保存時に、古いイベントを消すことも可能 33