あなたも出来るakka-clusterとPersistentActorで実現するEvent Sourcing+CQRS

はじめに

Nyle
Nyle Engineering Blog
43 min readOct 18, 2016

--

開発室のはちわれです。ナイルに転職して約3ヶ月、Scalaの経験も転職してから本格的にコードを書く様になったのでまだ3ヶ月です。普段はScala、Akkaを使って開発を行っています。今回は、Scala試用期間中の私でも出来たakka-clusterとakka-persistentを使った「Event Sourcing+CQRS」を実現する方法について書かせて頂きたいと思います。

今回やること

最初に書かせて頂きましたが、今回はakka-clusterとakka-persistentを組み合わせて
「Event Sourcing+CQRS」を実現する一例を紹介させて頂きます。
本題に移る前に、Event SourcingとCQRSについて軽く触れさせて頂きます。

Event Sourcing

ステート(状態)ではなく、イベントを中心に考えられたアークテクチャ
ステートではなく、全てのイベントを保存、再生することによってステートを表します。

CQRS(コマンドクエリ責任分離)

Command Query Responsibility Segregationの略です。
簡単に説明するとCommand(更新)とQuery(参照)を明確に分離しようという考え方です。
弊社でも取り入れているDDD(ドメイン駆動設計)と一緒に語られることが多いです。
一緒に語られるのが多いのはQuery側はドメインの影響を受けることが少なく、逆にCommand側はドメインの影響を受けることが多い為ドメインの分離により、CommandとQueryの分離が自然に行えることが多い為かと思います。
どの程度CommandとQueryの分離を行うかは、人によって認識や意見が別れることもあるかと思います。今回の記事を執筆する上で調査した限りでも、アプリケーションのレイヤーでドメイン層からQueryを出すべきという方もいればドメイン層の中で分離を行うという意見もあり、この辺は携わっているシステムのドメインによる所かと思います。

この実装によって得られるもの

今回の実装によって、Event sourcingとCQRSを実現することができます。
また、それと伴にakka-clusterとakka-persistentを組み合わせることにより、以下の効果も同時に得ることができます。

  • 耐障害性(Resilient)

ここで言う耐障害性とは、障害が起きないという意味での耐障害性ではありません。
障害が発生した際の高い回復性を指し、リアクティブ宣言で言われるResilientにあたります。この高い回復性は以下のメカニズムによって実現されています。

  • akka-clusterのfailure detector
  • akka-persistentのリカバリー機能
  • ドメインの分離による高い独立性と保守性
    CQRSの概念に基づいてCommandとQueryの分離(ドメインの分離)をすることにより、それぞれを独立したコンポーネントして扱うことができます。
    これにより、CommandとQueryが疎結合となりどちらかに修正を行った際に、もう片方にも修正を行わなければならないという事態を避けることができ
    修正の影響を気にすることなく、作業を行うことができます。
    ドメインの変更があった場合も、Command側だけに影響する内容であればCommand側だけを修正すれば良く工数の短縮と高い保守性を得ることができます。

実装例

それでは、少し前置きが長くなってしまいましたが、これからコードを交えながら
今回の実装例について説明をさせていただきます。まず構成のイメージは下記の図をご覧ください。

構成イメージ

implements_image

簡単に今回の構成について説明します。
クラスターのノードはCommad(更新)を行うノードとQuery(参照)を行うノードの2種類があります。Commandノードは更新のイベント、Queryノードは参照のイベントだけをそれぞれClusterClientから受け取ります。Commandノードはクラスターの中でシングルトンのノードして存在し、このノードが持つデータをマスターのデータとします。Commandノードにて行われたイベントをQueryノードにPublishして、Query側はそのイベントを実行することによりCommandとQueryのノードの状態が同期されます。

※先に断っておきますと、コードの中のsleepやprintln、logなどはデモの為のコードとなります。見やすくする為に、コードしては必要ない空行なども入れていますので予めご了承ください

環境情報

今回の実装例のコードは以下のバージョンを対象としています。
設定は後述しているbuild.sbtを参照して下さい。

  • Scala:2.11.8
  • akka-persistence:2.4.8
  • akka-cluster:2.4.8
  • akka-cluster-tools:2.4.8
  • akka-actor:2.4.8

Write側ノードの実装

  • SaveSnapshotEventをレシーブ。
  • context.becomeで処理の実行をreceiveCommandSavingに移譲。
  • これによりスナップショットの保存が終わるまでのレシーブはreceiveCommandSavingが行う。
  • case other -> 更新のイベントが送られてきた場合、送られてきたイベントをstash。
  • case SaveSnapshotEvent -> 既にスナップショットが保存中である事をログ出力。
  • case SaveSnapshotSuccess (スナップショット保存成功)
  • 新たにスナップショットが保存されたので、それ以前のjournalはリカバリーの際に不要の為削除。
  • unstashAll()でstashしていたイベントを実行。
  • context.unbecome()で処理の実行をreceiveComanndに戻す。
  • case SaveSnapshotFailure(スナップショット保存失敗)
  • エラー情報をログに出力。
  • unstashAll()でstashしていたイベントを実行。
  • context.unbecome()で処理の実行をreceiveComanndに戻す。
  1. journalの採番をそのまま連番で続ける為。
  2. 起動、リカバリー時にjournalを読み込める様にする為。

Read側ノードの実装

  • PersistentQueryの場合
  • Command側が更新のイベントがあったことを伝えるメッセージを送信。
  • Query側がメッセージを受け取る。
  • Query側がjournalからイベントを取得。
  • 取得したイベントをリプレイ。
  • PubSubMediatorの場合
  • Commad側が自身で行われたイベント自体を送信。
  • Query側がイベントを受け取る。
  • 取得したイベントをリプレイ。
  • QueryのノードとなるActorの生成。
  • 生成したActorをClusterClientReceptionistに登録。
  • QueryノードのActorを子ActorとしてClusterClientReceptionistのリスナーのActorを生成。
  • Commadのノードをシングルトンのノードして生成。
  • シングルトンのノードのProxyとなるActorを生成。
  • ProxyのActorをClusterClientReceptionistに登録。
  • ProxyのActorを子ActorとしてClusterClientReceptionistのリスナーのActorを生成。
  • JOINING -> クラスターに入ろうとしている
  • Up -> クラスターに入ってアクセス可能になった状態
  • Leaving -> クラスターから正常ぬに抜けようとしている状態
  • Down −> ノードがおち落ちてしまった状態
  • removed ->クラスターから削除された状態
  • Commandのノードが起動してクラスタに入り、Upの状態となる。
  • CommandにClusterClientからイベントを送る。
  • Commandがイベントをレシーブ。
  • Commandが自身の状態を更新。
  • Query側にイベントPublish
  • Query側がイベントをSubscribe
  • Query側の2つのノードの状態が更新される
  • Query側に状態を取得するイベントを送る。
  • 更新された状態が取得される。

まとめ

  • akka-clusterとakka-persistentを組み合わせることによってEvent SourcingとCQRSを実現するシステムを構築する事が出来る。
  • akka-persistentのリカバリー機能等によって耐障害性の高いシステムを構築できる。
  • クラスターのノードの実装や実際の更新処理を明確に分離することによって、ドメインの分離とCQRSを実現でき保守性も高くなる。
  • クラスターのノードを起動の仕方によって様々な構成のクラスター構成を実現することが出来る。

--

--