57

Click here to load reader

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

Embed Size (px)

Citation preview

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

CQRS+EventSourcing を Akka Persistence を使って実装してみる。〜コツとハマりポイント〜

2016/03/16 Reactive Messaging Patterns プレ読書会 - CQRS 、 ES の基本を学ぶ -

Satoshi Matsushita

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

自己紹介

• Satoshi Matsushita @satoshi_m8a• Scala, Akka, DDD, フロントエンド , コンピュータビジョン , 機械学習• ゲヒルン株式会社

  Python, Go, Erlang, Scala, OCaml, TypeScript 「 Gehirn Infrastructure Services 」 セキュリティ診断

• EC のシステムを Akka Persistence を使って開発していた。

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

Akka + DDD 気運の高まり (1)

• Scala Matsuri 2016 二日目アンカンファレンスDDD+CQRS+EventSourcing 実装する会(Akka パフォーマンスチューニングについて話してみよう会 ) by かとじゅんさん (@j5ik2o)

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

Akka + DDD 気運の高まり (2)

• Vaughn Vernon 氏の書籍

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

Akka + DDD 気運の高まり (3)

• Lightbend の一貫したツールキット• DDD を意識したもの

Akka PersistenceAkka Persistence Query

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

Akka + DDD 気運の高まり (4)

• Lagom マイクロサービスを構築するためのフレームワーク  CQRS+ES がベースになっている

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

Akka + DDD 気運の高まり (5)

• マイクロサービス化の流れ• リアクティブという考え方の広まり

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

目次

• CQRS• イベントソーシング• コマンドサイド• クエリサイド• 参考

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

CQRSCommand Query Responsibility Segregation

コマンド・クエリ責務分離

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

よくある階層化パターン

プレゼンテーション層

アプリケーション層

ドメイン層

インフラストラクチャ層

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

CQRS 概念図

プレゼンテーション層

アプリケーション層

ドメイン層

インフラストラクチャ層

データアクセス層

コマンドサイド クエリサイド

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

ドメインモデル• 例: Twitter のフォロワー / フォロイー

ユーザー

フォロワーのリスト

フォロイーのリストブロックリスト

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

ドメインモデル• フォローするという振る舞いに着目すると

ユーザー フォローする (userId)ブロックされる (userId)

フォロイーのリストブロックされているユーザーのリスト

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

CQRS

ユーザー

フォロイーのリスト

ブロックされているユーザーのリスト

コマンドサイド クエリサイド

フォロワーのリストフォロイーのリスト

ブロックされているユーザーのリスト

ブロックしているユーザーのリスト

…ユーザーのリスト

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

複雑さに立ち向かう

• 複雑なドメインを、そのまま複雑なドメインモデルに落として満足しがち• まずは、コンテキスト分割を検討• ドメインをよく観察し、振る舞いにフォーカスする• CQRS や ES の検討はそのあと

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

Event Sourcing

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

Event Sourcing

カート ID : “ cart1”商品: “ A”->0, “B”->1

カート作成

商品 A を追加

商品 B を追加

商品 A を削除

カート ID : “ cart1”商品: “ A”->1, “B”->0

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

Snapshot1

2

Snapshot

101

100• 全てのイベントを初めから復元していては時間がかかる• スナップショットをとって途中から復元

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

CQRS+ES

コマンドサイド クエリサイド

Journal

Aggregate Root

Command Service

Projection DAO

Query Service

DB DB

Command

Domain Event

Domain Event

DTO

DTO

Polling

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

データベース選択のポイント

• コマンドサイド ・ Cassandra, DynamoDB, Riak ・書き込みをスケールできるもの、可用性の高いものが良い• クエリサイド ・各種 RDB, NoSQL( ドキュメント指向・グラフ指向 ) ・クエリに強いものが良い ・組み合わせ OK

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

Materialized View Pattern

• コマンドサイドの DB が正のデータを保持する、クエリサイドはそれの View• リードレプリカの構築(読み込みをスケール)

https://msdn.microsoft.com/ja-jp/library/dn589782.aspx から引用

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

サービス統合も容易

• 新しいサービスを追加したら、ドメインイベントを流し込む。• あたかも、その新しいサービスが最初から統合されてるかのように振る舞う。• 現在のイベントまで追いついたら、システムに馴染んでいる。

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

結果整合性コマンドサイド クエリサイド

Journal

Aggregate Root

Command Service

Projection DAO

Query Service

DB DB

Command

Domain Event

Domain Event

DTO

DTO

Polling

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

Over Kill

• 例: ID 、名前、パスワード、 E-Mail アドレスを持つ、会員 AR• パスワードや E-Mail アドレスの変更履歴を追うことで、ビジネスの価値を生むのか?• CQRS だけ、もしくは単純な CRUD ができるだけでよいのでは?

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

余談:純粋な REST API は DDD に向かない

• REST API で一旦少なくなった情報を復元するのは困難• 純粋な REST にこだわらない。

CQRS で作った折角のリッチなコマンドモデルが意味をなさなくなる。

業務で発生する操作情報量:大 REST API情報量:小 リッチなコマンドモデル情報量:大> <×

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

ES のメリット・デメリット

•  メリットインピーダンスミスマッチがない。履歴管理が不要、データ解析やデバッグにも使える。イベントは追記のみなのでパフォーマンスが良い。機能追加も容易。•  デメリットイベントの修正が煩雑 ( 後述 )データサイズの問題

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

CQRS+ES のメリット・デメリット

•  メリットドメインの振る舞いが明確になるView を柔軟につくれるスケールも柔軟に

•  デメリット結果整合性

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

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

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

コマンドサイド

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

Akka Persistence

• Actor の内部状態を永続化することができる• Akka の CQRS とイベントソーシングに使われる• メッセージの再送の仕組みも提供( At least once

delivery )

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

例:カウントする Actor

• CounUp コマンドを受け取り、内部のカウントを増加させていく。

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

PersistentActor

Persistent

Actor

Journal

persistenceId = “c100”count = 0

CountUp

CountIncreased Ack(永続化完了 )

• コマンドを受け付け、ドメインイベントを発行する。①

② ③

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

PersistentActor

Persistent

Actor

Journal

persistenceId = “c100”count = 1

Ack

Ack⑤

• Journal からの Ack を待ち、内部状態を更新する

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

ポイント

• 内部状態 (count) の更新はドメインイベントの永続化完了を待ってから行う• 永続化されていないイベントは起こっていないイベントと同義

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

PersistentActor の復元

• クラッシュ、タイムアウト時の停止、シャードの移動など様々な理由で Actor は再起動する。• 再起動した Actor を元の状態に戻し、コマンドを受け付けたい。

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

PersistentActor の復元

Persistent

Actor

Journal

persistenceId = “c100”count = 3

CountIncreased

① ②

CountIncreased

CountIncreased

Select Events where persistenceId = “c100”

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

Akka Persistenceclass 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) }}

<- ここで永続化<- 永続化が終わった後に状態を更新

<- 復元したイベントを元に状態を更新

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

ポイント

• Recovery が完了するまで、コマンドを処理しないようになっている。• Recovery 時は内部状態の更新だけを行う、外部へコマンドやメッセージを発行してはならない。

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

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, リカバリを隠蔽

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

ドメインイベントの設計

• ドメインイベントは起こった事実を表す。イベント名は過去形 (Increased, Decresed, Created)

• 「住所を変更しました」 vs 「引っ越しました」• 「旧システムからデータを移行しました」イベント• きっかけとなったコマンドをイベントのメタデータとして保持することも• 粒度は細かすぎても良くない。

e.g. 「郵便番号を変更しました」

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

ドメインイベントのシリアライズ

• ドメインイベントはシリアライズされて、コマンドサイドの DB に保存される。• デフォルトでは Java のシリアライザが使われる• Java のシリアライザは速度面でも、拡張面でも問題がある• 実運用するのであれば、 Google Protocol Buffers が無難

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

ドメインイベントのスキーマ変更

• フィールドを追加したり、一つのイベントを分割など• EventAdapter を使ったり、一応の解決方法はあるが煩雑• Stamina  https://github.com/scalapenos/stamina

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

Persistence Plugin

• Cassandra, JDBC, DynamoDB, Riak 向けのPlugin

• テスト用の InMemory Plugin や LevelDB Plugin• ReadJournal API( 後述 ) の実装しやすい DB がおすすめ• Cassandra Plugin は Akka公式

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

クエリサイド

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

Akka Persistence Query

• CQRS のクエリサイドの実装に使われる• クエリサイド全体ではなく、

Journal からクエリ側の DBへの投影に使われる• experimental (Akka 2.4.2)

Plugin も出揃っていない

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

Journal Projection DAO

DB

Domain Event DTO

Polling

クエリサイドDTO

• Read Journal API を実装した Persistence Plugin を使う• Journal を Polling して、ドメインイベントを待ち受ける

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

ReadJournal API

• EventsByTagQuery タグを元にイベントを取得• EventsByPersistenceIdQuery  PersistenceId を元にイベントを取得 • AllPersistenceIdsQuery すべての PersistenceId を取得• CurrentPersistenceIdsQuery 現在存在する全ての PersistenceId を取得(ポーリングなし)• すべての Journal Plugin がこれら実装しているわけではない 実装が困難なものもあるので、 Journal 用の DB選びは慎重に

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

イベントにタグを付与する

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 }}

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

Projection

• Read Model Projection / Read Model Updater ともいう• ドメインイベントを元に、 View を構築する

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

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)

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

クエリ

• Slick3 などを使ってクエリする。

Page 52: CQRS+ESをAkka Persistenceを使って実装してみる。
Page 53: CQRS+ESをAkka Persistenceを使って実装してみる。

その他

• Process Manager 複数の Aggregate Root にまたがった処理を順序良く実行する  PersistentFSM を使う。

• Cluster Sharding  Aggregate Root を分散させる。  Cluster Singleton

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

まとめ

• CQRS+ES のコマンドサイドとクエリサイドをAkka Persistence と Akka Persistence Query で実装した

• Lagom

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

参考• CQRS Journey

https://msdn.microsoft.com/ja-jp/library/jj554200.aspx• .NET のエンタープライズアプリケーションアーキテクチャ• 実践ドメイン駆動設計

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

Reactive Messaging Patterns with the Actor Model 読書会

興味のある方はお声がけください。

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

ありがとうございました