Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Ladder of cqrs+es

4,240 views

Published on

scala kansai 2017

Published in: Software
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Ladder of cqrs+es

  1. 1. copyright Fringe81 Co.,Ltd. Ladder of CQRS+ES @mtoyoshi 2017.9.9
  2. 2. copyright Fringe81 Co.,Ltd. 自己紹介 @mtoyoshi Fringe81から来ました Scala歴3年と少しになりました iPad + ApplePencilが最近のお気に入り
  3. 3. copyright Fringe81 Co.,Ltd. [PR]
  4. 4. copyright Fringe81 Co.,Ltd. 今日の話のテーマ: 思想・定義の話より現場感(リアル)を どういう課題感から導入に至ったのか 自分が感じていた疑問や失敗の話などを中心に 小さく始めるとどういうところから? 小さくはじめた場合その先には どういう事を考慮したらいい世界が待っている?
  5. 5. copyright Fringe81 Co.,Ltd. なお、登壇に際して色々読みましたが 「CQRS Journey」が一番参考になりました https://msdn.microsoft.com/ja-jp/library/jj554200.aspx
  6. 6. copyright Fringe81 Co.,Ltd. 長年の悩み
  7. 7. copyright Fringe81 Co.,Ltd. プロジェクト途中から モデルの凝集度が下がりだす 個人的経験では レポート機能きっかけでなりやすい DB側も正規化崩しなど TABLEの制約・定義も汚染されてくる
  8. 8. copyright Fringe81 Co.,Ltd. 自分の技術力が まだまだ足りないからだと思っていた (理想vs現実の落とし所がヘタ?)
  9. 9. copyright Fringe81 Co.,Ltd. It is not possible to create an optimal solution for searching,reporting,and processing transactions utilizing a single model. -CQRS documents by Greg Young-
  10. 10. copyright Fringe81 Co.,Ltd.
  11. 11. copyright Fringe81 Co.,Ltd. Command Query Responsibility Segregation 〜コマンド・クエリ責務分離〜
  12. 12. copyright Fringe81 Co.,Ltd. Command(Write) Query(Read) アクセス量の 占める割合 少 多 スケール性 △ ◎ 複雑さ ビジネスロジック 多データと応答速度 DB傾向 正規化 非正規化
  13. 13. copyright Fringe81 Co.,Ltd. 新しいサービス立ち上げの チャンスが巡ってきた CQRSアーキテクチャを導入 ※当初はEventSourcingはやるつもりなし
  14. 14. copyright Fringe81 Co.,Ltd. ■構成: C側はCleanArchitecture + DDD Q側はController + DAO ※実行環境はGCP ※DBはcloud datastore  (クセ強め)
  15. 15. copyright Fringe81 Co.,Ltd. さっそくですが CQRSアーキテクチャを導入してみて どうだったの?
  16. 16. copyright Fringe81 Co.,Ltd. モデルの凝集度が下がる問題は 今のところ問題なし 導入のねらいは成功 (DDDに素直に取り組める)
  17. 17. copyright Fringe81 Co.,Ltd. 〜嬉しい発見〜 生産性の観点では レビューにメリハリ C側(のdomain層)に集中
  18. 18. copyright Fringe81 Co.,Ltd. ところで CとQでDB分けたの?
  19. 19. copyright Fringe81 Co.,Ltd. 自分たちは 基本は同一テーブルを使う※が 最適化したいところだけQ用を用意 ※スケール性は? cloud datastoreは分散DBで R/W負荷に応じてオートスケールする
  20. 20. copyright Fringe81 Co.,Ltd. ・分けずに同一DB参照する
  21. 21. copyright Fringe81 Co.,Ltd. ・master/slave構成でCはmasterをQはslaveを ・分けずに同一DB参照する
  22. 22. copyright Fringe81 Co.,Ltd. ・C専用DBとQ専用DBを分ける ・master/slave構成でCはmasterをQはslaveを ・分けずに同一DB参照する ※DBまで含めた最適化が出来る
  23. 23. copyright Fringe81 Co.,Ltd. ・C専用DBとQ専用DBを分ける ・master/slave構成でCはmasterをQはslaveを ・分けずに同一DB参照する 【一貫性】 一般的に 複数台に分散すると 強い一貫性が失われ 結果整合性(eventual concistency) を考慮する必要がある
  24. 24. copyright Fringe81 Co.,Ltd. ・C専用DBとQ専用DBを分ける ・master/slave構成でCはmasterをQはslaveを ・分けずに同一DB参照する どうやってC側からQ側を作るの?
  25. 25. copyright Fringe81 Co.,Ltd. C Q
  26. 26. copyright Fringe81 Co.,Ltd. C Q 別サービス 別クエリ用
  27. 27. copyright Fringe81 Co.,Ltd. C Q 別サービス INSERT や UPDATE 集約用 レコード Event query1用 レコード query2用 レコード publish EventBus subscriber
  28. 28. copyright Fringe81 Co.,Ltd. C Q 別サービス INSERT & UPDATE 集約用 レコード Event query1用 レコード query2用 レコード publish EventBus subscriber Event: 発生した事象をあらわす 命名は過去形
  29. 29. copyright Fringe81 Co.,Ltd. C Q 別サービス INSERT & UPDATE 集約用 レコード Event query1用 レコード query2用 レコード publish EventBus subscriber ※ちなみにですが これはEventSourcingでは ありません(後ほど)
  30. 30. copyright Fringe81 Co.,Ltd. val (updatedAggregate, event) = aggregate.executeXxx(arg1, arg2) repository.store(updatedAggregate) eventBusPublisher.run(event)
  31. 31. copyright Fringe81 Co.,Ltd. さらにLadderをのぼる
  32. 32. copyright Fringe81 Co.,Ltd. ・集約の永続化 ・Eventのpublish 一方が失敗した場合、非一貫状態になる
  33. 33. copyright Fringe81 Co.,Ltd. それはそうだけど publishに失敗するなんて・・? そこまで考慮する?
  34. 34. copyright Fringe81 Co.,Ltd. 「Eventのpublishに失敗」 経験しました
  35. 35. copyright Fringe81 Co.,Ltd. クラウドサービスへの過信 障害復旧後にログから 手動リカバリする辛さ・・ レジリエンス考慮していれば!
  36. 36. copyright Fringe81 Co.,Ltd. ではどうやって? 参考:CQRS jornery
  37. 37. copyright Fringe81 Co.,Ltd. ・集約の永続化 ・Eventのpublish 一方が失敗した場合、非一貫状態になる この2つは別々実行ではなく 同時に行われるようにしたい
  38. 38. copyright Fringe81 Co.,Ltd. val (updatedAggregate, event) = aggregate.executeXxx(arg1, arg2) repository.store(updatedAggregate) eventBusPublisher.run(event) val updatedAggregate = aggregate.executeXxx(arg1, arg2) repository.store(updatedAggregate) リポジトリ内からイベントパブリッシュ (集約からイベントを取得)
  39. 39. copyright Fringe81 Co.,Ltd. val (updatedAggregate, event) = aggregate.executeXxx(arg1, arg2) repository.store(updatedAggregate) eventBusPublisher.run(event) val updatedAggregate = aggregate.executeXxx(arg1, arg2) repository.store(updatedAggregate) アトミックにしたいがDB保存とイベントpublishのTx を同一にするのは難しい・・
  40. 40. copyright Fringe81 Co.,Ltd. ・集約の永続化 ・Eventのpublish ・集約の永続化 ・publish待ちレコードとして永続化 ※2件保存するということ ※定期的にpublish待ちレコード群を読み込んでEventのpublish ※成功したらこれらレコード群を削除 ※サービス落ちても復帰時にレコードを参照して途中のものの publishを継続
  41. 41. copyright Fringe81 Co.,Ltd. publish成功後にレコード削除に失敗したら 再送してしまう問題
  42. 42. copyright Fringe81 Co.,Ltd. publish成功後にレコード削除に失敗したら 再送してしまう問題 案1:at-least-onceだし誤って再送するもやむなし
  43. 43. copyright Fringe81 Co.,Ltd. publish成功後にレコード削除に失敗したら 再送してしまう問題 案1:at-least-onceだし誤って再送するもやむなし 案2:利用しているメッセージサービス側に重複を検知 する仕組みがないか調べる  → GCPの場合、Cloud PubSubだが、調べてみると無かった ただし、Cloud PubSubとCloud Dataflowを組み合わせることで重複 を検知できる 参考:https://cloud.google.com/dataflow/model/pubsub-io
  44. 44. copyright Fringe81 Co.,Ltd. ちなみにakkaの場合、この問題はどう取り扱うのだろう? akkaというよりは応用的な話だからLagom※が参考になるかな ※akka,playを基盤としたリアクティブなmicroservice構築のためのフレームワーク 参考:https://www.lagomframework.com/documentation/latest/java/PubSub.html
  45. 45. copyright Fringe81 Co.,Ltd. CQRSまとめ ・レジリエンスの考慮 ・集約保存 & イベントpublish ・DBをCとQに分離 ・プログラムの世界をCとQに分離
  46. 46. copyright Fringe81 Co.,Ltd. さらにLadderをのぼる
  47. 47. copyright Fringe81 Co.,Ltd. C Q 別サービス INSERT や UPDATE 集約用 レコード Event query1用 レコード query2用 レコード publish EventBus subscriber
  48. 48. copyright Fringe81 Co.,Ltd. C Q 別サービス INSERT & UPDATE 集約用 レコード Event query1用 レコード query2用 レコード publish EventBus subscriber もしここの部分が サービスの最初からあるわけではなくて 途中から必要になったとしたら・・・?
  49. 49. copyright Fringe81 Co.,Ltd. どうやったら 作れるだろう?
  50. 50. copyright Fringe81 Co.,Ltd. 最新状態 C Q C側DBから作れ・・・ないだろう updated_atは最終更新日を表すだけ 現在に至るまで何回更新されたのか?など情報は不足 INSERT & UPDATE
  51. 51. copyright Fringe81 Co.,Ltd. publishしたEvent群と 同じものが取得できれば
  52. 52. copyright Fringe81 Co.,Ltd. Event1 Event2 Event3 Event4 Event5 つまりEventが 永続化されていて いつでも取り出せたら Q
  53. 53. copyright Fringe81 Co.,Ltd. value: 8 Event1 Event2 Event3 Event4 Event5 value:8 ※DBから復元したScalaオブジェクト
  54. 54. copyright Fringe81 Co.,Ltd. Event1 Event2 Event3 Event4 Event5 value:8 Added: 3 Added: 6 Deleted: 4 Deleted: 1 Added: 4
  55. 55. copyright Fringe81 Co.,Ltd. Event1 Event2 Event3 Event4 Event5 value:8 Added: 3 Added: 6 Deleted: 4 Deleted: 1 Added: 4 Eventは append-only
  56. 56. copyright Fringe81 Co.,Ltd. Event1 Event2 Event3 Event4 Event5 Event Sourcing
  57. 57. copyright Fringe81 Co.,Ltd. C Q 別サービス INSERT や UPDATE 集約用 レコード Event query1用 レコード query2用 レコード publish EventBus subscriber 集約の永続化 集約変更Eventのpublish 集約変更Eventの永続化 集約変更Eventのpublish
  58. 58. copyright Fringe81 Co.,Ltd. EventSourcingのメリット と言われているもの
  59. 59. copyright Fringe81 Co.,Ltd. メリット 要約 Performance ESにおいては永続化はイベントの append-only。 UPDATE処理のパフォーマンス改善が望める。 Simplification factとしてのイベントを保存するだけ。 O-R間のインピーダンスミスマッチが起きない。 Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし て使える Integration with other subsystems 疎結合を促す。 また、他のサブシステムへ publishした全てのイベントは eventStoreに保存 されている。 Deriving additional business value from the event history 将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を 持っていることで対処可能性が高まる。 例:ある時点での状態がどうであったかにこたえられるなど Production troubleshooting 本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に できるためトラブルシューティングに役立つ Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ ラーを修正するだけで DB値を手動メンテせずにすむ場合がある Testing テスト容易性が高まる Flexibility イベント群があればいかなる構造のデータにも変換可能 参考:CQRS jornery(Introducing Event Sourcing)より
  60. 60. copyright Fringe81 Co.,Ltd. メリット 要約 Performance ESにおいては永続化はイベントの append-only。 UPDATE処理のパフォーマンス改善が望める。 Simplification factとしてのイベントを保存するだけ。 O-R間のインピーダンスミスマッチが起きない。 Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし て使える Integration with other subsystems 疎結合を促す。 また、他のサブシステムへ publishした全てのイベントは eventStoreに保存 されている。 Deriving additional business value from the event history 将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を 持っていることで対処可能性が高まる。 例:ある時点での状態がどうであったかにこたえられるなど Production troubleshooting 本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に できるためトラブルシューティングに役立つ Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ ラーを修正するだけで DB値を手動メンテせずにすむ場合がある Testing テスト容易性が高まる Flexibility イベント群があればいかなる構造のデータにも変換可能 参考:CQRS jornery(Introducing Event Sourcing)より イベント群があれば いかなる構造のデータにも 変換可能
  61. 61. copyright Fringe81 Co.,Ltd. メリット 要約 Performance ESにおいては永続化はイベントの append-only。 UPDATE処理のパフォーマンス改善が望める。 Simplification factとしてのイベントを保存するだけ。 O-R間のインピーダンスミスマッチが起きない。 Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし て使える Integration with other subsystems 疎結合を促す。 また、他のサブシステムへ publishした全てのイベントは eventStoreに保存 されている。 Deriving additional business value from the event history 将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を 持っていることで対処可能性が高まる。 例:ある時点での状態がどうであったかにこたえられるなど Production troubleshooting 本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に できるためトラブルシューティングに役立つ Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ ラーを修正するだけで DB値を手動メンテせずにすむ場合がある Testing テスト容易性が高まる Flexibility イベント群があればいかなる構造のデータにも変換可能 参考:CQRS jornery(Introducing Event Sourcing)より 将来起こるビジネス要件を 予想することは困難だが 全てのイベント履歴を持っていることで 対処可能性が高まる
  62. 62. copyright Fringe81 Co.,Ltd. メリット 要約 Performance ESにおいては永続化はイベントの append-only。 UPDATE処理のパフォーマンス改善が望める。 Simplification factとしてのイベントを保存するだけ。 O-R間のインピーダンスミスマッチが起きない。 Audit trail システムで何が起きてその状態なのかの追跡が可能となり、監査ログとし て使える Integration with other subsystems 疎結合を促す。 また、他のサブシステムへ publishした全てのイベントは eventStoreに保存 されている。 Deriving additional business value from the event history 将来起こるビジネス要件を予想することは困難だが全てのイベント履歴を 持っていることで対処可能性が高まる。 例:ある時点での状態がどうであったかにこたえられるなど Production troubleshooting 本番環境のデータをテスト環境にコピーするなどして状態の再現を簡単に できるためトラブルシューティングに役立つ Fixing Errors ESの場合factとしてイベントが保存されているだけなので、コーディングエ ラーを修正するだけで DB値を手動メンテせずにすむ場合がある Testing テスト容易性が高まる Flexibility イベント群があればいかなる構造のデータにも変換可能 参考:CQRS jornery(Introducing Event Sourcing)より 本番環境のデータを テスト環境にコピーするなどして 状態の再現を簡単にできるため トラブルシューティングに役立つ
  63. 63. copyright Fringe81 Co.,Ltd. ところで
  64. 64. copyright Fringe81 Co.,Ltd. 新しいサービス立ち上げの チャンスが巡ってきた CQRSアーキテクチャを導入 ※当初はEventSourcingはやるつもりなし
  65. 65. copyright Fringe81 Co.,Ltd. CQRSは比較的受け入れやすい印象 Eventを伝搬させる方法についても 昨今のmicroserviceの文脈からも 受け入れやすい印象 Event Sourcingはどうでしょう?
  66. 66. copyright Fringe81 Co.,Ltd. メリットは理解できるものの 少し抵抗を感じていました それはなぜだろう? (自分のケースで考えてみた)
  67. 67. copyright Fringe81 Co.,Ltd. 1. 慣れ親しんだやり方を変える難しさ 状態を1レコードで表現し UPDATEを繰り返すスタイル イベントを積み上げて 状態を表現するスタイル 新しい概念なので チームメンバーの巻き込み(説得?) パフォーマンスへの不安
  68. 68. copyright Fringe81 Co.,Ltd. 新しい概念と思っていたのですが 実は意外と古くからありました
  69. 69. copyright Fringe81 Co.,Ltd. 2.技術的チャレンジが多い? Scala文脈だとESやるにはAkkaを連想するが ESもakkaも初チャレンジという場合は やや技術的チャレンジが多い印象?  ・Actor  ・Persistence  ・Query(含: Akka Stream)  ・Cluster ※使えるなら使ったほうがいいと思います
  70. 70. copyright Fringe81 Co.,Ltd. 3. 部分採用が難しい? ESは通常アーキテクチャパターン として語られる やるかやらないか
  71. 71. copyright Fringe81 Co.,Ltd. (自分たちの場合) ESでも出来るというよりは ESの方が好ましい/じゃなきゃできない というものが出てきた 小さくはじめるアプローチ ※akkaは今のところなし
  72. 72. copyright Fringe81 Co.,Ltd. 例えば次のような要件部分に EventSourcingを採用 ※少し一般化して
  73. 73. copyright Fringe81 Co.,Ltd. ケース1: ECサイトなどのポイント ・ポイントの獲得 ・ポイントの消費 ・ポイントのキャンセル ・獲得したポイントには有効期限がある 最新の保持ポイント総数を持つだけでは 厳しい
  74. 74. copyright Fringe81 Co.,Ltd. ケース2: 操作履歴の表示 ADNWなどの広告配信システムにおいては、 何をしたから効果が良くなったのか もしくは悪くなったのかを把握したい 「配信リーチ対象を変えた」 「クリエイティブを差し替えた」 「1日上限予算を変更した」 などの操作履歴が追えると ユーザーのPDCAを促すことができそう
  75. 75. copyright Fringe81 Co.,Ltd. ケース3: 技術的な制約を乗り越えるために(cloud datastore) 同一レコードにupdateが集中するケースに弱い 解決策の1つとしてappend-onlyなES
  76. 76. copyright Fringe81 Co.,Ltd. EventSourcingの課題 ・検索に弱い ・パフォーマンスの課題
  77. 77. copyright Fringe81 Co.,Ltd. イベント積み上げでは検索に弱い: ex) 「保有ポイントが350pt以上の人を検索」を簡単 に行えるようにしたい CQRSと組み合わせていることで Qに最適化した形にして対応できる
  78. 78. copyright Fringe81 Co.,Ltd. イベント数増による再生時間増対応: snapshot機能 なんらかのタイミングでレコードをまとめあげたポイ ントを作り、再生する回数を削減する
  79. 79. copyright Fringe81 Co.,Ltd. バッチプログラムが ドメイン的に意味のある周期で (weeklyやmonthlyなど) データを退避させ まとめあげたレコードを作る バッチ バッチ バッチ
  80. 80. copyright Fringe81 Co.,Ltd. もしくは
  81. 81. copyright Fringe81 Co.,Ltd. 参考:  Akka Persistence ※DBはPostgresqlにして 保存状態を見やすくしてみる with akka-persistence-jdbc
  82. 82. copyright Fringe81 Co.,Ltd. class ExamplePersistentActor(id: String) extends PersistentActor { override def persistenceId = id var state = ExampleState() def updateState(event: Evt): Unit = { state = state.updated(event) } override val receiveRecover: Receive = { case evt: Evt => updateState(evt) case SnapshotOffer(_, snapshot: ExampleState) => state = snapshot } override val receiveCommand: Receive = { case Cmd(data) => persist(Evt(data)) { event => updateState(event) if (lastSequenceNr % 10 == 0 && lastSequenceNr != 0) { saveSnapshot(state.compress) } } }
  83. 83. copyright Fringe81 Co.,Ltd. class ExamplePersistentActor(id: String) extends PersistentActor { override def persistenceId = id var state = ExampleState() def updateState(event: Evt): Unit = { state = state.updated(event) } override val receiveRecover: Receive = { case evt: Evt => updateState(evt) case SnapshotOffer(_, snapshot: ExampleState) => state = snapshot } override val receiveCommand: Receive = { case Cmd(data) => persist(Evt(data)) { event => updateState(event) if (lastSequenceNr % 10 == 0 && lastSequenceNr != 0) { saveSnapshot(state.compress) } } } 10回毎にsnapshot
  84. 84. copyright Fringe81 Co.,Ltd.
  85. 85. copyright Fringe81 Co.,Ltd. journal snapshot
  86. 86. copyright Fringe81 Co.,Ltd. さらにLadderをのぼる
  87. 87. copyright Fringe81 Co.,Ltd. 再生頻度自体の削減 リクエストのたびにEvent群を 毎回再生して状態を復元するのでは なくてメモリ上に常駐しておくスタイル ex. akka-cluster sharding
  88. 88. copyright Fringe81 Co.,Ltd. EventSourcingまとめ ・Eventの永続化 ・検索に強く with CQRS ・再生時間の短縮(snapshot) ・再生頻度の削減(onMemory)
  89. 89. copyright Fringe81 Co.,Ltd. さいごに CQRS+ESの実装をするための Fun.CQRSというフレームワーク 実装参考としてして紹介します http://www.funcqrs.io/
  90. 90. copyright Fringe81 Co.,Ltd. Akka実装とInMemory実装がある (もちろん追加可能) (State[Aggregate], Command) => F[Events] (State[Aggregate], Event) => State[Aggregate] WhatとHowの分離
  91. 91. copyright Fringe81 Co.,Ltd. 例) Lottery(くじ引き) ※このサンプルコードでは 参加者のうち 勝者は1人だけとなる (当たりは1つ)
  92. 92. copyright Fringe81 Co.,Ltd. EmptyLottery NonEmptyLottery FinishedLottery CreateLottery AddParticipant AddParticipant RemoveParticipant Run
  93. 93. copyright Fringe81 Co.,Ltd. LotteryCreated ParticipantAdded ParticipantRemoved WinnerSelected EmptyLottery NonEmptyLottery FinishedLottery
  94. 94. copyright Fringe81 Co.,Ltd. sealed trait Lottery { def id: LotteryId } case class EmptyLottery(id: LotteryId) extends Lottery case class NonEmptyLottery( participants: List[String], id: LotteryId) extends Lottery case class FinishedLottery( winner: String, id: LotteryId) extends Lottery <C側のモデル>
  95. 95. copyright Fringe81 Co.,Ltd. case class LotteryView( participants: List[String] = List(), winner: Option[String] = None, runDate: Option[OffsetDateTime] = None, id: LotteryId ) <Q側のモデル>
  96. 96. copyright Fringe81 Co.,Ltd. sealed trait LotteryCommand case object CreateLottery extends LotteryCommand case class AddParticipant(name:String) extends LotteryCommand case class RemoveParticipant(name:String) extends LotteryCommand case object Run extends LotteryCommand sealed trait LotteryEvent { def lotteryId: LotteryId } case class LotteryCreated(lotteryId: LotteryId) extends LotteryEvent sealed trait LotteryUpdateEvent extends LotteryEvent case class ParticipantAdded(name: String, raffleId: LotteryId) extends LotteryUpdateEvent case class ParticipantRemoved(name: String, raffleId: LotteryId) extends LotteryUpdateEvent case class WinnerSelected(winner:String,date:OffsetDateTime,lotteryId:LotteryId) extends LotteryUpdateEvent <CommandとEvent>
  97. 97. copyright Fringe81 Co.,Ltd. (State[Lottery], LotteryCommand) => LotteryEvents (State[Lottery], LotteryEvent) => State[Lottery] つまり
  98. 98. copyright Fringe81 Co.,Ltd. C側
  99. 99. copyright Fringe81 Co.,Ltd. case class NonEmptyLottery(...) extends Lottery { def acceptParticipants = ... def removeParticipants = … def runTheLottery = Lottery.actions .commandHandler { OneEvent { case Run => val winner = participants(Random.nextInt(participants.size)) WinnerSelected(winner, OffsetDateTime.now, id) } } .eventHandler { case evt: WinnerSelected => FinishedLottery(evt.winner, id) } Command => Event
  100. 100. copyright Fringe81 Co.,Ltd. case class NonEmptyLottery(...) extends Lottery { def acceptParticipants = ... def removeParticipants = … def runTheLottery = Lottery.actions .commandHandler { OneEvent { case Run => val winner = participants(Random.nextInt(participants.size)) WinnerSelected(winner, OffsetDateTime.now, id) } } .eventHandler { case evt: WinnerSelected => FinishedLottery(evt.winner, id) } Event => Aggregate
  101. 101. copyright Fringe81 Co.,Ltd. object Lottery extends Types[Lottery] { type Id = LotteryId type Command = LotteryCommand type Event = LotteryEvent def create(lotteryId: LotteryId) = ... def behavior(lotteryId: LotteryId): Behavior[Lottery, LotteryCommand, LotteryEvent] = Behavior .first { create(lotteryId) } .andThen { case r: EmptyLottery => r.acceptParticipants case r: NonEmptyLottery => r.acceptParticipants ++ r.removeParticipants ++ r.runTheLottery case r: FinishedLottery => r.rejectAllCommands }
  102. 102. copyright Fringe81 Co.,Ltd. Q側 EventをもとにQへ写す (Projectionする)
  103. 103. copyright Fringe81 Co.,Ltd. class LotteryViewProjection(dao: LotteryDAO) extends Projection[LotteryEvent] { def handleEvent = attempt.HandleEvent { case evt: LotteryCreated => dao.save(LotteryView(id = evt.raffleId)) case evt: LotteryUpdateEvent => dao.updateById(evt.lotteryId) { r => evt match { case e: ParticipantAdded => r.copy(participants = r.participants :+ newParticipant(e)) case e: ParticipantRemoved => r.copy(participants = r.participants.filter(_.name != e.name)) case e: WinnerSelected => r.copy(winner = Some(e.winner), runDate = Some(e.date)) } } } Eventをもとに Q側DBを更新
  104. 104. copyright Fringe81 Co.,Ltd. 最後に定義
  105. 105. copyright Fringe81 Co.,Ltd. object AppContext { val backend = { val inMemoryBackend = new InMemoryBackend inMemoryBackend .configure { // “C” in CQRS aggregate(Lottery.behavior) } .configure { // “Q” in CQRS projection( projection = new LotteryViewProjection(new LotteryDAO()), publisherFactory = inMemoryBackend.inMemoryPublisherFactory[LotteryEvent] ) } } }
  106. 106. copyright Fringe81 Co.,Ltd. object AppContext { val backend = { val inMemoryBackend = new InMemoryBackend inMemoryBackend .configure { // “C” in CQRS aggregate(Lottery.behavior) } .configure { // “Q” in CQRS projection( projection = new LotteryViewProjection(new LotteryDAO()), publisherFactory = inMemoryBackend.inMemoryPublisherFactory[LotteryEvent] ) } } }
  107. 107. copyright Fringe81 Co.,Ltd. 実行
  108. 108. copyright Fringe81 Co.,Ltd. val lotteryRef = AppContext.backend.aggregateRef[Lottery].forId(lotteryId) lotteryRef ! CreateLottery lotteryRef ! AddParticipant("Ichiro") lotteryRef ! AddParticipant("Jiro") lotteryRef ! AddParticipant("Saburo") lotteryRef ! Run dao.find(lotteryId) match { case Success(r) => println(r.winner) case Failure(t) => println(t.getMessage) } Command を送る
  109. 109. copyright Fringe81 Co.,Ltd. val lotteryRef = AppContext.backend.aggregateRef[Lottery].forId(lotteryId) lotteryRef ! CreateLottery lotteryRef ! AddParticipant("Ichiro") lotteryRef ! AddParticipant("Jiro") lotteryRef ! AddParticipant("Saburo") lotteryRef ! Run dao.find(lotteryId) match { case Success(r) => println(r.winner) case Failure(t) => println(t.getMessage) } Q側を 確認
  110. 110. copyright Fringe81 Co.,Ltd. CQRS EventSourcing ありがとうございました

×