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.
CQRS+EventSourcingをAkka Persistence
を使って実装してみる。〜コツとハマりポイン
ト〜
2016/03/16
Reactive Messaging Patternsプレ読書会 - CQRS、ESの基本を学ぶ
-...
自己紹介
• Satoshi Matsushita @satoshi_m8a
• Scala, Akka, DDD, フロントエンド, コンピュータビジョン, 機械学
習
• ゲヒルン株式会社
Python, Go, Erlang, Scala...
Akka + DDD 気運の高まり(1)
• Scala Matsuri 2016 二日目アンカンファレンス
DDD+CQRS+EventSourcing実装する会
(Akkaパフォーマンスチューニングについて話してみよう会)
by かとじゅん...
Akka + DDD 気運の高まり(2)
• Vaughn Vernon 氏の書籍
Akka + DDD 気運の高まり(3)
• Lightbendの一貫したツールキット
• DDDを意識したもの
Akka Persistence
Akka Persistence Query
Akka + DDD 気運の高まり(4)
• Lagom
マイクロサービスを構築するためのフレームワーク
CQRS+ESがベースになっている
Akka + DDD 気運の高まり(5)
• マイクロサービス化の流れ
• リアクティブという考え方の広まり
目次
• CQRS
• イベントソーシング
• コマンドサイド
• クエリサイド
• 参考
CQRS
Command Query Responsibility Segregation
コマンド・クエリ責務分離
よくある階層化パターン
プレゼンテーション層
アプリケーション層
ドメイン層
インフラストラクチャ層
CQRS概念図
プレゼンテーション層
アプリケーション層
ドメイン層
インフラストラクチャ層
データアクセス層
コマンドサイド クエリサイド
ドメインモデル
• 例:Twitterのフォロワー / フォロイー
ユーザー
フォロワーのリスト
フォロイーのリスト
ブロックリスト
ドメインモデル
• フォローするという振る舞いに着目すると
ユーザー
フォローする(userId)
ブロックされる(userId)
フォロイーのリスト
ブロックされてい
るユーザーのリス
ト
CQRS
ユーザー
フォロイーのリスト
ブロックされている
ユーザーのリスト
コマンドサイド クエリサイド
フォロワーのリスト
フォロイーのリスト
ブロックされている
ユーザーのリスト
ブロックしている
ユーザーのリスト
…
ユーザーのリスト
複雑さに立ち向かう
• 複雑なドメインを、そのまま複雑なドメインモデル
に落として満足しがち
• まずは、コンテキスト分割を検討
• ドメインをよく観察し、振る舞いにフォーカスする
• CQRSやESの検討はそのあと
Event Sourcing
Event Sourcing
カートID : “cart1”
商品: “A”->0, “B”->1
カート作成
商品Aを追加
商品Bを追加
商品Aを削除
カートID : “cart1”
商品: “A”->1, “B”->0
Snapshot
1
2
Snapshot
101
100
• 全てのイベントを初めから復元してい
ては時間がかかる
• スナップショットをとって途中から復
元
CQRS+ES
コマンドサイド クエリサイド
Journal
Aggregate Root
Command Service
Projection DAO
Query Service
DB DB
Command
Domain Event
Doma...
データベース選択のポイント
• コマンドサイド
・Cassandra, DynamoDB, Riak
・書き込みをスケールできるもの、可用性の高いものが良
い
• クエリサイド
・各種RDB, NoSQL(ドキュメント指向・グラフ指向)
・クエ...
Materialized View Pattern
• コマンドサイドのDBが正のデータを保持する、
クエリサイドはそれのView
• リードレプリカの構築(読み込みをスケール)
https://msdn.microsoft.com/ja-jp...
サービス統合も容易
• 新しいサービスを追加したら、ドメインイベントを流
し込む。
• あたかも、その新しいサービスが最初から統合されて
るかのように振る舞う。
• 現在のイベントまで追いついたら、システムに馴染ん
でいる。
結果整合性
コマンドサイド クエリサイド
Journal
Aggregate Root
Command Service
Projection DAO
Query Service
DB DB
Command
Domain Event
Domain...
Over Kill
• 例:ID、名前、パスワード、E-Mailアドレスを持つ、
会員AR
• パスワードやE-Mailアドレスの変更履歴を追うことで
、ビジネスの価値を生むのか?
• CQRSだけ、もしくは単純なCRUDができるだけでよ
いの...
余談:純粋なREST APIはDDDに向かない
• REST APIで一旦少なくなった情報を復元するのは困難
• 純粋なRESTにこだわらない。
CQRSで作った折角のリッチなコマンドモデルが意味をなさなくなる
。
業務で発生する操作
情報量:...
ESのメリット・デメリット
• メリット
インピーダンスミスマッチがない。
履歴管理が不要、データ解析やデバッグにも使える。
イベントは追記のみなのでパフォーマンスが良い。
機能追加も容易。
• デメリット
イベントの修正が煩雑(後述)
データ...
CQRS+ESのメリット・デメリット
• メリット
ドメインの振る舞いが明確になる
Viewを柔軟につくれる
スケールも柔軟に
• デメリット
結果整合性
Akkaで作る CQRS+ES
コマンドサイド クエリサイド
Journal
Aggregate Root
Command Service
Projection DAO
Query Service
DB DB
Command
Domain Ev...
コマンドサイド
Akka Persistence
• Actorの内部状態を永続化することができる
• AkkaのCQRSとイベントソーシングに使われる
• メッセージの再送の仕組みも提供(At least once delivery)
例:カウントするActor
• CounUpコマンドを受け取り、内部のカウントを増
加させていく。
PersistentActor
Persistent
Actor
Journal
persistenceId = “c100”
count = 0
CountUp
CountIncreased
Ack(永続化完了)
• コマンドを受け付け、ドメ...
PersistentActor
Persistent
Actor
Journal
persistenceId = “c100”
count = 1
Ack
④
Ack
⑤
• JournalからのAckを待ち、内部状態を更新する
ポイント
• 内部状態(count)の更新はドメインイベントの永続化
完了を待ってから行う
• 永続化されていないイベントは起こっていないイベ
ントと同義
PersistentActorの復元
• クラッシュ、タイムアウト時の停止、シャードの移
動など様々な理由でActorは再起動する。
• 再起動したActorを元の状態に戻し、コマンドを受
け付けたい。
PersistentActorの復元
Persistent
Actor
Journal
persistenceId = “c100”
count = 3
CountIncreased
① ②
CountIncreased
CountIncrea...
Akka Persistence
class CountUpActor extends PersistentActor {
override def persistenceId: String = self.path.name
context....
ポイント
• Recoveryが完了するまで、コマンドを処理しないよ
うになっている。
• Recovery時は内部状態の更新だけを行う、
外部へコマンドやメッセージを発行してはならない
。
Aggregate Root
• 実際はPersistentActorを継承して、AggregateRoot
アクターを作ると良い。(c.f. akka-ddd)
https://github.com/pawelkaczor/akka-ddd/...
ドメインイベントの設計
• ドメインイベントは起こった事実を表す。
イベント名は過去形 (Increased, Decresed, Created)
• 「住所を変更しました」 vs 「引っ越しました」
• 「旧システムからデータを移行しました...
ドメインイベントのシリアライズ
• ドメインイベントはシリアライズされて、
コマンドサイドのDBに保存される。
• デフォルトではJavaのシリアライザが使われる
• Javaのシリアライザは速度面でも、拡張面でも問題がある
• 実運用するので...
ドメインイベントのスキーマ変更
• フィールドを追加したり、一つのイベントを分割など
• EventAdapterを使ったり、一応の解決方法はあるが煩雑
• Stamina
https://github.com/scalapenos/stami...
Persistence Plugin
• Cassandra, JDBC, DynamoDB, Riak 向けのPlugin
• テスト用のInMemory Pluginや LevelDB Plugin
• ReadJournal API(後述...
クエリサイド
Akka Persistence Query
• CQRSのクエリサイドの実装に使われる
• クエリサイド全体ではなく、
Journalからクエリ側のDBへの投影に使われる
• experimental (Akka 2.4.2)
Pluginも...
Journal Projection DAO
DB
Domain Event DTO
Polling
クエリサイド
DTO
• Read Journal APIを実装したPersistence Plugin を使う
• JournalをPoll...
ReadJournal API
• EventsByTagQuery
タグを元にイベントを取得
• EventsByPersistenceIdQuery
PersistenceIdを元にイベントを取得
• AllPersistenceIdsQu...
イベントにタグを付与する
class ThreadEventAdapter extends WriteEventAdapter {
override def manifest(event: Any): String = ""
val tags ...
Projection
• Read Model Projection / Read Model Updaterともいう
• ドメインイベントを元に、Viewを構築する
Projection
val readJournal = PersistenceQuery(system)
.readJournalFor[LeveldbReadJournal](LeveldbReadJournal.Identifier)
i...
クエリ
• Slick3などを使ってクエリする。
その他
• Process Manager
複数のAggregate Rootにまたがった処理を順序
良く実行する
PersistentFSMを使う。
• Cluster Sharding
Aggregate Rootを分散させる。
Clust...
まとめ
• CQRS+ESのコマンドサイドとクエリサイドを
Akka Persistenceと Akka Persistence Queryで実装
した
• Lagom
参考
• CQRS Journey
https://msdn.microsoft.com/ja-jp/library/jj554200.aspx
• .NETのエンタープライズアプリケーションアーキテクチャ
• 実践ドメイン駆動設計
Reactive Messaging Patterns
with the Actor Model 読書会
興味のある方はお声がけください。
ありがとうございました
CQRS+ESをAkka Persistenceを使って実装してみる。
Upcoming SlideShare
Loading in …5
×

CQRS+ESをAkka Persistenceを使って実装してみる。

6,728 views

Published on

CQRS+ESをAkka Persistenceを使って実装してみる。

Published in: Software
  • Be the first to comment

CQRS+ESをAkka Persistenceを使って実装してみる。

  1. 1. CQRS+EventSourcingをAkka Persistence を使って実装してみる。〜コツとハマりポイン ト〜 2016/03/16 Reactive Messaging Patternsプレ読書会 - CQRS、ESの基本を学ぶ - Satoshi Matsushita
  2. 2. 自己紹介 • Satoshi Matsushita @satoshi_m8a • Scala, Akka, DDD, フロントエンド, コンピュータビジョン, 機械学 習 • ゲヒルン株式会社 Python, Go, Erlang, Scala, OCaml, TypeScript 「Gehirn Infrastructure Services」 セキュリティ診断 • ECのシステムをAkka Persistenceを使って開発していた。
  3. 3. Akka + DDD 気運の高まり(1) • Scala Matsuri 2016 二日目アンカンファレンス DDD+CQRS+EventSourcing実装する会 (Akkaパフォーマンスチューニングについて話してみよう会) by かとじゅんさん(@j5ik2o)
  4. 4. Akka + DDD 気運の高まり(2) • Vaughn Vernon 氏の書籍
  5. 5. Akka + DDD 気運の高まり(3) • Lightbendの一貫したツールキット • DDDを意識したもの Akka Persistence Akka Persistence Query
  6. 6. Akka + DDD 気運の高まり(4) • Lagom マイクロサービスを構築するためのフレームワーク CQRS+ESがベースになっている
  7. 7. Akka + DDD 気運の高まり(5) • マイクロサービス化の流れ • リアクティブという考え方の広まり
  8. 8. 目次 • CQRS • イベントソーシング • コマンドサイド • クエリサイド • 参考
  9. 9. CQRS Command Query Responsibility Segregation コマンド・クエリ責務分離
  10. 10. よくある階層化パターン プレゼンテーション層 アプリケーション層 ドメイン層 インフラストラクチャ層
  11. 11. CQRS概念図 プレゼンテーション層 アプリケーション層 ドメイン層 インフラストラクチャ層 データアクセス層 コマンドサイド クエリサイド
  12. 12. ドメインモデル • 例:Twitterのフォロワー / フォロイー ユーザー フォロワーのリスト フォロイーのリスト ブロックリスト
  13. 13. ドメインモデル • フォローするという振る舞いに着目すると ユーザー フォローする(userId) ブロックされる(userId) フォロイーのリスト ブロックされてい るユーザーのリス ト
  14. 14. CQRS ユーザー フォロイーのリスト ブロックされている ユーザーのリスト コマンドサイド クエリサイド フォロワーのリスト フォロイーのリスト ブロックされている ユーザーのリスト ブロックしている ユーザーのリスト … ユーザーのリスト
  15. 15. 複雑さに立ち向かう • 複雑なドメインを、そのまま複雑なドメインモデル に落として満足しがち • まずは、コンテキスト分割を検討 • ドメインをよく観察し、振る舞いにフォーカスする • CQRSやESの検討はそのあと
  16. 16. Event Sourcing
  17. 17. Event Sourcing カートID : “cart1” 商品: “A”->0, “B”->1 カート作成 商品Aを追加 商品Bを追加 商品Aを削除 カートID : “cart1” 商品: “A”->1, “B”->0
  18. 18. Snapshot 1 2 Snapshot 101 100 • 全てのイベントを初めから復元してい ては時間がかかる • スナップショットをとって途中から復 元
  19. 19. CQRS+ES コマンドサイド クエリサイド Journal Aggregate Root Command Service Projection DAO Query Service DB DB Command Domain Event Domain Event DTO DTO Polling
  20. 20. データベース選択のポイント • コマンドサイド ・Cassandra, DynamoDB, Riak ・書き込みをスケールできるもの、可用性の高いものが良 い • クエリサイド ・各種RDB, NoSQL(ドキュメント指向・グラフ指向) ・クエリに強いものが良い ・組み合わせOK
  21. 21. Materialized View Pattern • コマンドサイドのDBが正のデータを保持する、 クエリサイドはそれのView • リードレプリカの構築(読み込みをスケール) https://msdn.microsoft.com/ja-jp/library/dn589782.aspx から引用
  22. 22. サービス統合も容易 • 新しいサービスを追加したら、ドメインイベントを流 し込む。 • あたかも、その新しいサービスが最初から統合されて るかのように振る舞う。 • 現在のイベントまで追いついたら、システムに馴染ん でいる。
  23. 23. 結果整合性 コマンドサイド クエリサイド Journal Aggregate Root Command Service Projection DAO Query Service DB DB Command Domain Event Domain Event DTO DTO Polling
  24. 24. Over Kill • 例:ID、名前、パスワード、E-Mailアドレスを持つ、 会員AR • パスワードやE-Mailアドレスの変更履歴を追うことで 、ビジネスの価値を生むのか? • CQRSだけ、もしくは単純なCRUDができるだけでよ いのでは?
  25. 25. 余談:純粋なREST APIはDDDに向かない • REST APIで一旦少なくなった情報を復元するのは困難 • 純粋なRESTにこだわらない。 CQRSで作った折角のリッチなコマンドモデルが意味をなさなくなる 。 業務で発生する操作 情報量:大 REST API 情報量:小 リッチなコマンドモデル 情報量:大 > < ×
  26. 26. ESのメリット・デメリット • メリット インピーダンスミスマッチがない。 履歴管理が不要、データ解析やデバッグにも使える。 イベントは追記のみなのでパフォーマンスが良い。 機能追加も容易。 • デメリット イベントの修正が煩雑(後述) データサイズの問題
  27. 27. CQRS+ESのメリット・デメリット • メリット ドメインの振る舞いが明確になる Viewを柔軟につくれる スケールも柔軟に • デメリット 結果整合性
  28. 28. Akkaで作る CQRS+ES コマンドサイド クエリサイド Journal Aggregate Root Command Service Projection DAO Query Service DB DB Command Domain Event Domain Event DTO DTO Polling Akka Persistence Akka Persistence Plugin Akka Persistence Query Slick3 Akka Cluster Sharding
  29. 29. コマンドサイド
  30. 30. Akka Persistence • Actorの内部状態を永続化することができる • AkkaのCQRSとイベントソーシングに使われる • メッセージの再送の仕組みも提供(At least once delivery)
  31. 31. 例:カウントするActor • CounUpコマンドを受け取り、内部のカウントを増 加させていく。
  32. 32. PersistentActor Persistent Actor Journal persistenceId = “c100” count = 0 CountUp CountIncreased Ack(永続化完了) • コマンドを受け付け、ドメインイベントを発行する。 ① ② ③
  33. 33. PersistentActor Persistent Actor Journal persistenceId = “c100” count = 1 Ack ④ Ack ⑤ • JournalからのAckを待ち、内部状態を更新する
  34. 34. ポイント • 内部状態(count)の更新はドメインイベントの永続化 完了を待ってから行う • 永続化されていないイベントは起こっていないイベ ントと同義
  35. 35. PersistentActorの復元 • クラッシュ、タイムアウト時の停止、シャードの移 動など様々な理由でActorは再起動する。 • 再起動したActorを元の状態に戻し、コマンドを受 け付けたい。
  36. 36. PersistentActorの復元 Persistent Actor Journal persistenceId = “c100” count = 3 CountIncreased ① ② CountIncreased CountIncreased Select Events where persistenceId = “c100” ③
  37. 37. Akka Persistence class CountUpActor extends PersistentActor { override def persistenceId: String = self.path.name context.setReceiveTimeout(120.seconds) var count: Int = 0 def updateState(event: Increased) = { this.count = this.count + event.amount } override def receiveRecover: Receive = { case e: Increased => updateState(e) } override def receiveCommand: Receive = { case c: CountUp => persist(Increased(c.amount)) { event => updateState(event) sender() ! event } case ReceiveTimeout => context.parent ! Passivate(stopMessage = Stop) case Stop => context.stop(self) } } <- ここで永続化 <- 永続化が終わった後に状態を更新 <- 復元したイベントを元に状態を更新
  38. 38. ポイント • Recoveryが完了するまで、コマンドを処理しないよ うになっている。 • Recovery時は内部状態の更新だけを行う、 外部へコマンドやメッセージを発行してはならない 。
  39. 39. Aggregate Root • 実際はPersistentActorを継承して、AggregateRoot アクターを作ると良い。(c.f. akka-ddd) https://github.com/pawelkaczor/akka-ddd/blob/master/akka-ddd- core/src/main/scala/pl/newicom/dddd/aggregate/AggregateRoot.scala • スナップショット操作, GracefulPassivation, リカバリを隠蔽
  40. 40. ドメインイベントの設計 • ドメインイベントは起こった事実を表す。 イベント名は過去形 (Increased, Decresed, Created) • 「住所を変更しました」 vs 「引っ越しました」 • 「旧システムからデータを移行しました」イベント • きっかけとなったコマンドをイベントのメタデータとして保持することも • 粒度は細かすぎても良くない。 e.g.「郵便番号を変更しました」
  41. 41. ドメインイベントのシリアライズ • ドメインイベントはシリアライズされて、 コマンドサイドのDBに保存される。 • デフォルトではJavaのシリアライザが使われる • Javaのシリアライザは速度面でも、拡張面でも問題がある • 実運用するのであれば、 Google Protocol Buffers が無難
  42. 42. ドメインイベントのスキーマ変更 • フィールドを追加したり、一つのイベントを分割など • EventAdapterを使ったり、一応の解決方法はあるが煩雑 • Stamina https://github.com/scalapenos/stamina
  43. 43. Persistence Plugin • Cassandra, JDBC, DynamoDB, Riak 向けのPlugin • テスト用のInMemory Pluginや LevelDB Plugin • ReadJournal API(後述)の実装しやすいDBがおすすめ • Cassandra PluginはAkka公式
  44. 44. クエリサイド
  45. 45. Akka Persistence Query • CQRSのクエリサイドの実装に使われる • クエリサイド全体ではなく、 Journalからクエリ側のDBへの投影に使われる • experimental (Akka 2.4.2) Pluginも出揃っていない
  46. 46. Journal Projection DAO DB Domain Event DTO Polling クエリサイド DTO • Read Journal APIを実装したPersistence Plugin を使う • JournalをPollingして、ドメインイベントを待ち受ける
  47. 47. ReadJournal API • EventsByTagQuery タグを元にイベントを取得 • EventsByPersistenceIdQuery PersistenceIdを元にイベントを取得 • AllPersistenceIdsQuery すべてのPersistenceIdを取得 • CurrentPersistenceIdsQuery 現在存在する全てのPersistenceIdを取得(ポーリングなし) • すべてのJournal Pluginがこれら実装しているわけではない 実装が困難なものもあるので、Journal用のDB選びは慎重に
  48. 48. イベントにタグを付与する class ThreadEventAdapter extends WriteEventAdapter { override def manifest(event: Any): String = "" val tags = Set("Thread") override def toJournal(event: Any): Any = event match { case e: ThreadEvent => Tagged(event, tags) case _ => event } }
  49. 49. Projection • Read Model Projection / Read Model Updaterともいう • ドメインイベントを元に、Viewを構築する
  50. 50. Projection val readJournal = PersistenceQuery(system) .readJournalFor[LeveldbReadJournal](LeveldbReadJournal.Identifier) implicit val mat = ActorMaterializer()(system) val dao = new ThreadsDao(dbConfig) val projection = new ThreadProjection(dao) readJournal .eventsByTag("Thread", projection.lastOffset) .mapAsync(1) { envelope => projection.update(envelope.event).map(_ => envelope.offset) } .mapAsync(1) { offset => projection.saveProgress(offset) } .runWith(Sink.ignore)
  51. 51. クエリ • Slick3などを使ってクエリする。
  52. 52. その他 • Process Manager 複数のAggregate Rootにまたがった処理を順序 良く実行する PersistentFSMを使う。 • Cluster Sharding Aggregate Rootを分散させる。 Cluster Singleton
  53. 53. まとめ • CQRS+ESのコマンドサイドとクエリサイドを Akka Persistenceと Akka Persistence Queryで実装 した • Lagom
  54. 54. 参考 • CQRS Journey https://msdn.microsoft.com/ja-jp/library/jj554200.aspx • .NETのエンタープライズアプリケーションアーキテクチャ • 実践ドメイン駆動設計
  55. 55. Reactive Messaging Patterns with the Actor Model 読書会 興味のある方はお声がけください。
  56. 56. ありがとうございました

×