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

リアクティブアーキテクチャ基礎講座

 リアクティブアーキテクチャ基礎講座

かとじゅん
PRO

July 16, 2020
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

  1. リアクティブ・アーキテクチャ
    基礎講座
    かとじゅん( )
    2020/07/16
    @j5ik2o
    1

    View Slide

  2. 誰?
    Chatwork社でテックリード
    最近の活動
    2020-04-15
    2020-05-26
    以下のコースの認定を取得しました
    @j5ik2o
    Chatworkテックリードが“今”の⾃分に集中してきた理由。
    Scala×DDDに出会い、サービス改善に⽣かすまで - Findy Engineer Lab
    ドメイン駆動設計をわかりやすく - ドメインのモデル設計を⼿を動
    かしながら学ぼう - エンジニアHub
    Lightbend Academy
    2

    View Slide

  3. アジェンダ
    のコースで解説されている、リアクティブ・アーキテクチャの
    概要を解説します
    具体的なイメージがつきやすいように、リアクティブアーキテクチャのために設計さ
    れたツールキットであるAkkaも⼀緒に紹介します
    Lightbend Academy
    3

    View Slide

  4. なぜリアクティブか
    第⼀⽬標は常に使えるソフトウェアを提供すること
    4

    View Slide

  5. こんな状況になったら…
    仕事で使う重要なサービスで
    数時間停⽌したら…
    レスポンスタイムが、30秒以上掛かったら…
    応答性のないソフトウェアは、ユーザーの不満につながる
    5

    View Slide

  6. 代替サービスへの乗換リスク
    遅いソフトウェアや利⽤できないソフトウェアはユーザの効率を下げている
    提供側がミッションクリティカルでないと判断しても、ユーザには関係ない
    ユーザはすぐによりよい他の選択肢を⾒つける。選択肢は無数にある時代、すぐに乗
    り換えられてしまう
    6

    View Slide

  7. ⾮機能要求は変化している
    ノード数は数百・数千に
    レスポンスタイムはミリ秒オーダーに
    ダウンタイムは限りなく0時間に
    データ規模はペタバイトに
    技術的理由ではなく、ユーザ要求の変化に対応することが⽬的
    7

    View Slide

  8. リアクティブ原則
    2014年に公開された原則。16200名が署名
    Scala/Akkaで有名な、Lightbend社のCTO Jonas Bonér⽒がFirst Author

    ⼿段

    即応性
    拡張可能 拡張可能
    耐障害性
    メッセージ駆動
    弾⼒性
    http://www.reactivemanifesto.org/
    8

    View Slide

  9. リアクティブ原則
    即応性(Responsive)
    システムは可能な限り速やかに応答する
    耐障害性(Resilient)
    システムは障害に直⾯しても即応性を保ち続ける
    弾⼒性(Elastic)
    システムはワークロードが変動しても即応性を保ち続ける
    メッセージ駆動(Message-Driven)
    コンポーネント間を⾮同期にメッセージを送受信する通信スタイル(メッセージパ
    ッシング)
    9

    View Slide

  10. 即応性(Responsive)
    システムは可能な限りすみやかに応答する。
    同時に複数ユーザから重なり合うリアルタイムなリクエストがあっても、障害に直⾯し
    ても、応答性は維持される。
    10

    View Slide

  11. 耐障害性(Resilient)
    システムは障害に直⾯しても即応性を保ち続ける。
    障害はそれぞれのコンポーネントに封じ込められ、コンポーネントは互いに隔離される
    ので、システムが部分的に故障してもシステム全体を危険に晒すことなしに回復するこ
    とが保証される
    11

    View Slide

  12. 弾⼒性(Elastic)
    システムはワークロードが変動しても即応性を保ち続ける。
    ワークロードに連動してリソースを増減させ、シャーディングやレプリケーションされ
    たコンポーネント間でワークロードを分散する
    12

    View Slide

  13. メッセージ駆動(Message-Driven)
    リアクティブシステムは⾮同期なメッセージパッシングに依ってコンポーネント間の境
    界を確⽴する。
    13

    View Slide

  14. ⾮同期・ノンブロッキングなメッセージパッシング
    受信側はアクティブ時のみリソースを消費できるのでシステムのオーバヘッドを抑制
    できる
    Actor Actor
    ①メッセージを送信
    ②Dispathcer
    がプッシュする
    ③タスクを実⾏する
    Mailbox
    メソッド駆動の場合は呼出先が完了するまで呼出元は解放されず、他のタスクを実⾏
    できない。メッセージパッシングの場合はコマンドを送信したらすぐ返答を待たずに
    別のタスクを実⾏(Fire And Forget)し、呼出先から返答がきたときに続きを⾏うた
    め、スケーラビリティが向上する
    Actor Actor
    ①コマンドを送信
    ③コマンドを送信
    ②タスクを実⾏
    ②リプライを待た
    ずに次のタスクへ
    ④タスクを実⾏
    Method Method
    ①メソッドをコール
    ③メソッドコールのリターン
    ②タスクを実⾏
    ④次のタスクへ
    14

    View Slide

  15. 位置透過性と透過的リモーティング
    位置透過性は、アクターの位置情報がローカルであってもリモートのように⾒せるこ
    と。メッセージ送信側は、送信先のすべてがリモートに⾒える
    透過的リモーティングは、リモートからの呼出をローカルからの呼出に⾒せること。
    メッセージ受信側は、送信元のすべてがローカルに⾒える
    マルチスレッディングとRPCを、メッセージ駆動という⼀つのプログラミングモデル
    で扱えるようになる
    // ┝戚jパ もハかZぇおてwそおjノテ ガげぬdくヅェを わぶ想情wは
    groupChatRef1 ! AddMessage("Hello World!")
    object HelloWorld {
    final case class Greet(whom: String, replyTo: ActorRef[Greeted])
    final case class Greeted(whom: String, from: ActorRef[Greet])
    // パ もハiね肩し輯sばぃぬdく訣yは
    def apply(): Behavior[Greet] = Behaviors.receive { (context, message) =>
    context.log.info("Hello {}!", message.whom)
    message.replyTo ! Greeted(message.whom, context.self)
    Behaviors.same
    }
    }
    15

    View Slide

  16. Supervision
    監督者を仲介することで故障の隔離が可能となる。呼出元のアクターは⼦アクターが
    故障したことも知らない
    Actor Supervisor
    ①メッセージを送信
    ③タスクを実⾏する
    故障した⼦Actor
    再⽣成された⼦Actor
    ②作り直される
    -
    ⼦アクターの障害管理
    と存在を隠蔽
    -
    メッセージブローカー
    としての役割を持つ
    通知
    16

    View Slide

  17. リアクティブシステム vs
    リアクティブプログラミング
    17

    View Slide

  18. リアクティブシステム vs
    リアクティブプログラミング
    リアクティブシステム
    アーキテクチャレベルで、リアクティブ原則を適⽤する
    リアクティブプログラミング
    リアクティブシステムを効率よく実装するための⼿段。しかし、リアクティブプ
    ログラミングを使ったからと⾔ってリアクティブシステムになるわけではない
    例えば⾮同期・ノンブロッキングなプログラミングで実装したコンポーネント
    を、1つのノード上で稼働させた場合、耐障害性や弾⼒性がないのでリアクティ
    ブシステムではない
    18

    View Slide

  19. リアクティブ・マイクロサービス
    19

    View Slide

  20. モノリス vs マイクロサービス の スペクトラム
    ほとんどのシステムが中間に位置している
    Application
    A B
    C D
    A B
    C D
    Application
    A B
    C D
    A B
    C D
    A B
    C D
    20

    View Slide

  21. モノリス
    21

    View Slide

  22. FYI: モノリスとは
    アプリケーションに明確な分離がないもの
    最悪なシナリオは泥団⼦。アプリケーションの全てが他の全てに依存している
    理解することも修正することも難しい
    22

    View Slide

  23. FYI: モノリスの特徴
    単⼀ユニットとしてデプロイされる
    単⼀の共有されたデータベース
    同期化されたメソッド呼び出しに依存
    コンポーネントはデータベース結合
    ビックバン・リリース
    改善のサイクルタイムが⻑くなる
    複製のスケーリング
    23

    View Slide

  24. FYI: モノリスのメリット
    リファクタリングが簡単
    クロスモジュールのリファクタリングが可能
    ⼀貫性の維持が簡単
    データベースが⼀貫性の境界になる
    単⼀のデプロイプロセス
    1⼀つのものをデプロイすればプロセスが完了
    モニタリング対象が⼀つ
    モノリスだけを監視すればよい
    ⽐較的シンプルなスケーラビリティモデル
    プロセスのコピーをデプロイするだけでよい
    24

    View Slide

  25. モノリスのデメリット
    25

    View Slide

  26. パフォーマンスの問題
    パフォーマンスがマシンの上限に依存する
    モノリスのサイズに⽐例してリソースも消費する
    スケールアップしかできないので、サーバコストが割⾼になる傾向
    26

    View Slide

  27. スケーラビリティの問題
    データベースが許す限りのスケーラビリティしかない
    DB(Writer)はシングルインスタンスで拡張ができない
    不要なコンポーネントも⼀緒にスケールするためリソース効率が悪い
    27

    View Slide

  28. 耐障害性の問題
    カスケード障害が起きやすい
    1つのコンポーネントが障害を起こすと、他の部分は負荷を分配できないため、
    モノリス全体がダウンすることがある
    クラウド思考にありがちな「障害に直⾯したら再起動する」は代償が伴うことも
    ある(実⾏環境によってJITプロファイル結果を捨ててしまうこともあるなど)。ま
    た、Auto Scalingはフォローアップであることを忘れてはならない。再起動は最
    終⼿段、それまでにアプリケーションレベルでできることがある。
    28

    View Slide

  29. FYI: カスケード障害の例
    クラスタB
    が消失
    通常の負荷分散
    クラスタA
    が過負荷状態に
    出典:「オライリー社 SRE サイトリライアビリティエンジニアリング」より
    29

    View Slide

  30. モノリスへの処⽅箋
    ドメインの境界に沿ってサービスを分割すること。
    ECサイト
    予約
    注⽂
    出荷
    マイクロサービスへ近づいていくが、レガシーなモノリスからの移⾏は⾮常に難し
    い…。
    30

    View Slide

  31. マイクロサービス
    31

    View Slide

  32. FYI: マイクロサービスの特徴
    ⾃律性
    特殊化
    出典: マイクロサービスの概要 | AWS
    32

    View Slide

  33. FYI: マイクロサービスのメリット
    俊敏性
    柔軟性のあるスケーリング
    容易なデプロイ
    技術的な⾃由
    再利⽤可能なコード
    耐障害性
    出典: マイクロサービスの概要 | AWS
    33

    View Slide

  34. リアクティブ・マイクロサービスにおける
    隔離の原則(Principles of Isolation)
    34

    View Slide

  35. 隔離の原則(Principles of Isolation)
    システムから
    「状態(State)」
    「空間(Space)」
    「時間(Time)」
    「失敗(Failure)」
    を隔離する原則
    35

    View Slide

  36. 状態の隔離
    マイクロサービスの状態へのアクセスはAPIを経由しなければならない
    データベースへの直接アクセスは禁⽌されている
    APIを維持すれば、内部の改善は独⽴して進めることができる
    36

    View Slide

  37. 空間の隔離
    マイクロサービスは、他のマイクロサービスがどこにデプロイされているか気にすべ
    きではない。
    どこのマシンでもデータセンターであってもよい
    需要に応じてサービスをスケールアップしたりスケールダウンしたりできる
    37

    View Slide

  38. 時間の隔離
    マイクロサービスはお互いに待つべきではない。リクエストは⾮同期・ノンブロッキ
    ングであることが理想
    リクエストを送信後に応答を待つ間にスレッドをブロックせずにその場を⽴ち去る
    (Fire and Forget)
    スレッドを占拠せず、スレッドが⾃由になり資源をより効率的に使うことができるよ
    うになる
    時間が経てば最終的に⼀貫性が成り⽴ち、スケーラビリティが向上する
    38

    View Slide

  39. 故障の隔離
    あるサービスで故障が発⽣しても、他のサービスは影響を受けずに稼働し続けること
    ができる
    依存関係: 注⽂サービス→顧客サービス では、顧客サービスが故障すると、注⽂サー
    ビスも失敗してしまう。それに依存しないようにする
    39

    View Slide

  40. 隔離の技術 (1/2)
    Bulkheading
    ソフトウェアに区画を設けて全体障害に発展しないようにする
    アクターモデルはError Kernel(Supervisionによって故障した箇所を切り離し正
    常な部分を保護する), let-it-crash(故障したときは最初からすべてをリセットして
    やりやおす)パターンを利⽤して実現
    サービスレベルでは境界づけられたコンテキスト単位で区切る
    Circuit Breaker
    リトライ先のサービスが過負荷にならないようにするための仕組み
    40

    View Slide

  41. 隔離の技術 (2/2)
    メッセージ駆動アーキテクチャ
    ⾮同期・ノンブロッキングは時間と故障を分離することができる
    ⾃律性
    各サービスは⾃分⾃⾝の動作しか保証できない。サービス同⼠が互いに依存しあ
    って動作している場合、全体として脆弱になってしまう
    ゲートウェイサービス
    マイクロサービスのデメリットの1つとして、クライアントアプリケーションに
    複雑さをもたらすことがある
    API Gateway, BFFなどをつかってこのギャップを解消する
    41

    View Slide

  42. 分散システムにおけるスケーラビリティ
    42

    View Slide

  43. スケーラビリティと
    パフォーマンス
    パフォーマンス改善
    レイテンシを最適化すること
    0時間に近づくことはできるが、けっして0時間にはできない
    ユーザビリティの観点から⼗分であればよい
    スケーラビリティ改善
    スループットを最適化すること
    理論的には限界がないが、通常はソフトウェアの設計やコストなどの問題によっ
    て制限される
    リアクティブアーキテクチャでは、理論的限界のないスケーラビリティを追求する
    43

    View Slide

  44. 分散システムでの⼀貫性
    結果整合性(Eventual Consistency)
    強い⼀貫性(Strong Consistency)
    44

    View Slide

  45. 結果整合性
    Eventual Consistency
    データの更新がなければ、あるデータへの全てのアクセスが最終的には最新の値を返
    すようになること
    データの更新がある間は、整合性は保証されない。つまり、データの整合性を保証す
    るには、⼀定の期間 データの更新を⽌めなければならない
    45

    View Slide

  46. 結果整合性のサブセット
    因果⼀貫性 / Causal Consistency
    因果関係のある項⽬が、共通した順序で処理されること
    逐次⼀貫性 / Sequencial Consistency
    因果関係に関係なく、すべての項⽬が共通した順序で処理されること
    並列処理できないのでスケーラビリティが犠牲になる
    これらの⼀貫性にはそれぞれトレードオフがあることを認識して選択する。
    46

    View Slide

  47. 強い⼀貫性(Strong Consistency)
    強い⼀貫性とはデータが⾒えるようになる前に、すべてのコンポーネントからの同意
    を得ること
    分散システムにおいては情報を転送するにはタイムラグがあるため、強い⼀貫性をそ
    のまま実現できないのでエミュレートする
    データを⼀箇所でロック(部分的な⾮分散システム)すれば⼀貫性を持たせること
    ができる
    ロックにはトレードオフがあり、パフォーマンス悪化や柔軟性やスケーラビリテ
    ィが犠牲になる
    47

    View Slide

  48. 競合の影響
    同じリソースを2つ以上のコンポーネントから利⽤すると競合が発⽣し、⼀⽅がリソ
    ースを開放するまで他⽅は待つことになる
    レイテンシが悪化し待ち⾏列が⻑くなるとスケーラビリティに⼤きな制限を与える
    48

    View Slide

  49. アムダールの法則
    Amdahl's Law
    システムにおいて、並列化できる部分とできない部分がある。並列化によって改善す
    ることができるのはそれが可能な部分だけであり、並列化できない部分は改善できな

    競合は並列化を制限し、改善できる部分を減少させる
    並列処理A
    並列処理B
    並列処理C
    並列処理F
    並列処理G
    並列処理H
    直列処理D
    直列処理E
    49

    View Slide

  50. コヒーレンシ遅延
    複数のノード間で互いに状態を伝え合い、最終的にすべてのノードの状態が⼀致する
    ようになるまでの時間が遅延すること
    合意を取らなければならないコンポーネントの数が多くなると、合意が指数関数的に
    困難になる
    ヒト ヒト
    ヒト ヒト
    6
    ヒト ヒト
    ヒト
    3
    ヒト ヒト
    1
    コンポーネント数に対して通信チャネルの数は指数関数的に増える
    50

    View Slide

  51. ガンザーの法則
    Gunther's Universal Scalability Law
    システムをスケールさせるためコンポーネント数を増やしていくと、コンポーネント間
    の状態調整にかかるコストがスケールアウトによる改善を上回ってしまい、むしろ逆効
    果になること。
    スケールアウトの規模
    (
    コンポーネントの数)
    コンポーネント間の調整コスト
    51

    View Slide

  52. コヒーレンシ遅延の対策
    コーディネーターを配置して、全員が他の全員と合意を取らないようにする
    この⽅法のリスク
    集約されている情報が最新とは限らない
    コーディネーターが単⼀障害点やボトルネックになりやすい
    ヒト ヒト
    ヒト ヒト
    6
    ヒト ヒト
    ヒト ヒト
    3
    調整役を導⼊すればコヒーレン
    シ遅延を軽減できるが、調整役
    はSPoF
    になってしまう…

    52

    View Slide

  53. リアクティブ・アーキテクチャのアプローチ
    状態はシェアードナッシング
    できるだけロックを排除
    ロックするならできるだけ⼩さく
    ブロッキングしない
    結果整合性を有効活⽤
    コンポーネントの⾃律性を⾼める
    53

    View Slide

  54. シャーディングとは
    ⼀貫性とスケーラビリティのバランスを取るために競合を分離すること。シャーディ
    ングは競合を分離するテクニック
    データベースのI/Oシャーディングのことではない
    アプリケーションが扱う整合性を確保する単位(通常はドメインの集約単位)ごとに分
    割すること
    アプリケーションレベルで実装するので、データベースの種類に関係なく実現できる
    アプリケーションとデータベース間の通信を減らすことができ、パフォーマンスが向
    上する
    54

    View Slide

  55. Akka Cluster Sharding とは
    軽量プロセスであるActorを分散システム上でシャーディングするためのモジュール
    メッセージはShardRegion → Shard → Entityのようにルーティングされる
    ShadはEntity(集約)のグループ。Entityのスーパーバイザ。Entityを作成する
    ShardRegionは各ノードで開始される。ShardCoorinatorにShardの場所を要求す
    る。Shardを⼦アクターとして作成する
    ShardCoordinatorは調整役。どのShardRegionがどのShardを所有するかを決定
    し、ShardRegionに指⽰する。リバランス時はメッセージが移動先のShardRegion
    にバッファリングされ移動後に移動先のShardに配信される
    55

    View Slide

  56. 実装例
    Counter Entity(Aggregate)
    ShardRegion
    object Counter {
    sealed trait Command
    case object Increment extends Command
    final case class GetValue(replyTo: ActorRef[Int]) extends Command
    def apply(entityId: String): Behavior[Command] = {
    def updated(value: Int): Behavior[Command] = {
    Behaviors.receiveMessage[Command] {
    case Increment =>
    updated(value + 1)
    case GetValue(replyTo) =>
    replyTo ! value
    Behaviors.same
    }
    }
    updated(0)
    }
    }
    val TypeKey = EntityTypeKey[Counter.Command]("Counter")
    // ShardRegionげ粛巌押
    val shardRegion: ActorRef[ShardingEnvelope[Counter.Command]] =
    sharding.init(Entity(TypeKey)(createBehavior = entityContext => Counter(entityContext.entityId)))
    // ShardRegionくヅェを わぶ想は
    shardRegion ! ShardingEnvelope("counter-1", Counter.Increment)
    56

    View Slide

  57. ⼀貫性とスケーラビリティのバランス
    あるEntity(集約)IDは物理的に1つの場所にしか存在しない。これによって、分散シ
    ステム上の複数のノードで状態の整合性を保つ必要がなくなる
    Shardを経由するメッセージは順序が保証される。Entity(集約)のキーには集約のID
    が適している。Shard間で偏りが⽣じないようにID値は分散される必要がある
    ShardCoordinatorは競合になり得るが、⾮常に⼩さなタスクしかしない。リバラン
    スしない限りルート情報はキャッシュされる。多くのノードにShardを分散させるこ
    とでスケーラビリティを確保できる
    57

    View Slide

  58. シャーディングされたアクターはSSOTとなる
    Entityでデータベースに書き込んでからキャッシュすることでデータベースと同期し
    ていることを保証できる。キャッシュが失われていなければデータベースから読み出
    す必要がなくなる。データベースは常に読み書きが発⽣する場所でなく、キャッシュ
    が失われた場合に復元するためのバックアップになる。
    真のデータソースはデータベースではなく、Entity(集約)の中の状態となる。
    Entity(集約)は唯⼀信頼できる情報源(SSOT = Single Source Of Truth)。これまでの
    ステートレスなウェブアプリケーションとは全く違うプログラミングモデルとなる
    副作⽤を起こすコマンド
    副作⽤を起こさないコマンド
    副作⽤を伴うビジネスロジック
    キャッシュのバックアップ
    エンティティ(
    集約)
    アクター
    58

    View Slide

  59. ネットワーク分断時に⼀貫性か可⽤性を選ぶか
    CAP定理はそのまま解釈するとミスリードの原因に。完全に排他的選択ではなくバラ
    ンスが必要。ネットワーク分断時に、⼀貫性を選ぶのか、可能性を選ぶのかという選
    択になる
    シャーディングは⼀貫性を重視しているので、ネットワーク分断が発⽣した場合は可
    ⽤性が犠牲になるが、SPoFがないので全体障害にはならない。
    Sprit Brain Resolver(Akka2.6.6から標準搭載)はノードが失われたことを検知し
    て短時間に復旧させることができる。
    ネットワーク分断のときに可⽤性を選択したい場合はCRDT(Conflict-free
    Replicated Data Type)を使うことができる。実装としてはAkka Distributed
    Data
    ⼀貫性を取るか可⽤性を取るかの選択は、技術的な判断ではなくビジネス上の判断で
    ある。⼀貫性か可⽤性化は0か1かではなく、システムの部分部分で求められるビジ
    ネス要件に応じて選択する。ビジネスにとって、⼀貫性が保証されないことのインパ
    クトのほうが⼤きいのか、利⽤できない時間が発⽣することのほうが問題なのかを、
    ビジネス側と議論するべき
    59

    View Slide

  60. CQRS & Event Sourcing
    60

    View Slide

  61. Command and Query Responsibility
    Segregation
    コマンド・クエリ責務分離(分離という
    より隔離に近いイメージ)
    2010年 Greg Young⽒が考案したパタ
    ーン。
    1997年にBertrand Meyer⽒が考案したコ
    マンドクエリ分離原則(Command-Query
    Separation:CQS)をアーキテクチャに適⽤
    したものがCQRS。
    「あらゆるメソッドは、アクションを実
    ⾏するコマンドか、呼び出し元にデータ
    を返すクエリかのいずれかであって、両
    ⽅を⾏ってはならない。これは、質問を
    することで回答を変化させてはならない
    ということだ。」
    CQRSとは
    61

    View Slide

  62. なぜCQRSか
    そもそもコマンドとクエリの要件に⾮対称性がある
    Command Query
    ⼀貫性 結果整合性よりトランザクショ
    ン整合性を扱うこと多い
    ほとんどの場合結果整合を使う
    データ形式 トランザクション処理を⾏い正
    規化されたデータを保存するこ
    とが好まれる(集約単位など)
    ⾮正規化したデータ形式を取得
    することが好まれる(クライント
    都合のレスポンスなど)
    スケーラビリ
    ティ
    全体のリクエスト⽐率とごく少
    数のトランザクション処理しか
    しない。必ずしもスケーラビリ
    ティは重要ではない
    全体のかなりのリクエスト⽐率
    を占める処理を⾏うため、クエ
    リ側はスケーラビリティが重要
    検索・レポートやトランザクション処理を両⽴する単⼀モデルの実現は困難
    62

    View Slide

  63. 過去にに発⽣した出来事のこと=イベント
    ドメイン上のアクティビティを⽰す
    ⼀般的には過去形の動詞で表現される
    CustomerRelocated
    CargoShipped
    イベントからコマンドを想起できる
    RelocateCustomer
    ShipCargo
    イベントとコマンドは似ているが、⼈間が
    扱う⾔語としては別モノ
    コマンドは拒否されることがある
    イベントは既に起こったことを⽰す
    FYI: ドメインイベントとは
    63

    View Slide

  64. イベントソーシング
    イベントソーシングでは、状態ではなくドメインイベントを永続化する。ドメインイ
    ベントを履歴として扱われる。
    最新の状態は、ドメインイベントをリプレイすることによって得ることができる。
    CQRS と ES はそれぞれ個別に利⽤できる⼿法だが⼀般的に組み合わせて使われること
    が多く、組み合わせることでより弾⼒性や回復⼒のあるシステムを構築できるようにな

    64

    View Slide

  65. 永続化アクター(Akka Persistence)
    副作⽤を起こすコマンド
    副作⽤を起こさないコマンド
    ①コマンドハンドラ
    ドメインイベントが永続化される
    ジャーナルDB
    エンティティ(
    集約)
    アクター
    ②ドメインイベントの永続化
    ③イベントハンドラ
    スナップショットの永続化
    ドメインイベントN
    件ごとに
    スナップショットを永続化
    アクターがリプレ
    イ時にSnapshot
    以降のイベントが
    読み込まれる
    アクターがリプレイ時に最新の
    Snapshot
    があれば読み込まれる
    副作⽤が起きると
    きだけDB
    にロック
    なしでイベントが
    追記される
    スナップショットDB
    副作⽤を起こすコマンドが受理されるとドメインイベントへの追記保存とメモリ上の
    状態遷移が起きる。副作⽤が起きないコマンドはメモリ状態からリプライを返す。コ
    マンドを処理するために、DBからの読み込みは⼀切不要(パフォーマンスに有利)
    不要になった永続化アクターはいつでも破棄できる。再び必要ならば永続化されてい
    るドメインイベントを基にリプレイすることができる。リプレイするドメインイベン
    トが⼤量な場合はスナップショットと組み合わせることでリプレイ時間を短縮するこ
    とができる。
    65

    View Slide

  66. 実装例
    persistenceIdは集約IDを指定する
    Stateには、ドメインオブジェクトを格納する
    コマンドの処理はコマンドハンドラでイベントの処理はイベントハンドラで⾏う
    sealed trait Command
    final case class Add(data: String) extends Command
    case object Clear extends Command
    sealed trait Event
    final case class Added(data: String) extends Event
    case object Cleared extends Event
    final case class State(history: List[String] = Nil)
    def apply(id: String): Behavior[Command] =
    EventSourcedBehavior[Command, Event, State](
    persistenceId = PersistenceId.ofUniqueId(id),
    // 綬耗ID
    emptyState = State(Nil), // 綬耗げ訟尊
    commandHandler, // りヂプキコプキネ
    eventHandler) // ほソプガコプキネ
    private val commandHandler: (State, Command) =>
    Effect[Event, State] = { (state, command) =>
    command match {
    case Add(data) => Effect.persist(Added(data))
    case Clear => Effect.persist(Cleared)
    }
    }
    private val eventHandler: (State, Event) => State = {
    (state, event) =>
    event match {
    case Added(data) => state.copy((data ::
    state.history).take(5))
    case Cleared => State(Nil)
    }
    }
    66

    View Slide

  67. CQRS & Event Sourcing の Pros/Cons
    コマンドとクエリに分割することで、コンポーネントが増え、構築や運⽤にかかるコ
    ストが増⼤するが、それぞれの⽬的に合わせて最適化できる
    コマンドのドメインモデルとクエリのリードモデルに分割することで、⼀つ⼀つはモ
    デルがシンプルになる
    コマンド側とクエリ側は別々にスケールさせることができる
    データベースは増えるが、個々の要件に応じたデータベースを選択できるようになる
    クエリ要件に基づいたデータ設計を後付けでできる
    Writeモデルはシャーディングと組み合わせて⼀貫性を保証するが、ネットワーク分
    断時に可⽤性を犠牲にしなければならないことがある(ただし、SPoFを回避してリカ
    バリ可能)
    結果整合性のReadモデルは可⽤性を確保しやすい
    Event Sourcing
    監査ログの要件に対応できる
    ある時点の状態に戻すことができる。問題の調査に使える
    不正な状態を修正できる。git revertのように
    イベントの追記しか発⽣しないため、データベースの処理効率が良い
    67

    View Slide

  68. FYI: CQRS & ES with Akka Cluster on AWS
    akka-cluster
    ReadModel
    Updater
    Event
    Router
    DynamoDB
    Streams
    DynamoDB
    Read DB
    ing
    pod
    pod
    pod
    svc
    svc
    pod
    pod
    pod
    pod
    pod
    pod
    pod
    pod
    pod
    Kafka
    transfer domain events
    Read API
    Write API
    可⽤性重視
    ⼀貫性重視
    機能的には問題なし。パフォーマンスとスケーラビリティは知⾒が溜まり次第共有予定
    68

    View Slide

  69. まとめ
    ⽇常の仕事を⽀えるようなサービスでは「常に使えるソフトウェアを実現する」が
    MUSTになりがち。提供側がミッションクリティカルでないと判断しても顧客側には
    関係ない。競合のサービスに簡単に乗り換えられるリスクがある
    「常に使えるソフトウェアを実現する」の実現⼿段として、「リアクティブ・アーキ
    テクチャ」の考え⽅は⾮常に有益。⼀⽅で、それなりに仕組みや⼿間が必要。実際の
    現場では、それだけのコストを掛けるに値するシステムであるかよく考える必要があ

    エンジニア個⼈として選択肢を増やす意味で、リアクティブ・アーキテクチャの考え
    ⽅と経験を⾝につけて、バランスが取れるようになるとよいかもしれない
    (7⽉末まで無償) お勧めです
    Lightbend Academy
    69

    View Slide

  70. おしまい
    70

    View Slide