Aecor による純粋関数型イベントソーシング

Aecor という純粋関数型 Event Sourcing ライブラリをざっくり紹介したい。

## 概要
- [Aecor](https://github.com/notxcain/aecor) は Denis Mikhaylov というロシアの技術者が開発した、Scala の**純粋関数型イベントソーシングライブラリ**。作者が勤務するオンライン予約会社の Evotor 社では、Arcorベースの数十のサービスが実運用されているという。
- ラテン語で海の意味で、あえて五十音で近似すると、ラテン語読みだとアエコル、英語読みだと[エイカー](https://www.youtube.com/watch?v=zHD5ap-Fab4)みたいな感じ。
- **純粋関数型DDD** に親和性が高い。OOP とは逆に、FP ではデータと振る舞いを分離することが基本となるが[^for_the_dysfunctional]、この辺りは関数型 DDDでも同様。分離の仕方にはいくつかあるが[^dmmf] [^frdm]、Aecor 流では **Tagless Final** で実現する。
- [Typelevel エコシステム](https://typelevel.org/projects/)のライブラリ、[Cats](https://github.com/typelevel/cats), [Cats Effect](https://typelevel.org/cats-effect/), [FS2](https://github.com/functional-streams-for-scala/fs2) などを多用。**MTL** や **Tagless Final** など中〜上級の関数型プログラミング技法も使われてるので、それなりの素養は必要になる。
- Akka というか **Actor が嫌いな人でも大丈夫**。裏では [Akka Cluster Sharding](https://doc.akka.io/docs/akka/current/cluster-sharding.html) が動いているが、FP的に残念な部分は隔離されているので、タイプフルで純粋な関数型コードが書ける。

以降、[Aecor の Github のコード例](https://github.com/notxcain/aecor) と、Mikhaylov 氏の同僚の Vladimir Pavkin 氏による[連載チュートリアル](https://pavkin.ru/aecor-intro/)を参考にしつつ、ややつまみ食い的に解説する。

[^for_the_dysfunctional]: 例えばこの[ブログ記事](https://medium.com/statuscode/functional-programming-for-the-dysfunctional-756531f5b9b4)でも言われているように。

## 構成要素
Aecor の構成要素としては以下のようなものがある。

1. エンティティの振る舞いに着目して高い抽象レベルでドメインモデルを記述する **Behavior**
2. 分散コンピューティング環境で Behavior を実行する **Runtime**
3. **CQRS** のリードサイド
4. ビジネスプロセスを Process Manager パターンで処理する **Distributed Processing**

この記事では、ES+CQRS に関連する 1 〜 3についてざっくり説明する。

## Behavior
ドメイン層では、アイデンティティとライフサイクルをもつ**エンティティ**（特に適切に境界付けられた**アグリゲート**のルートエンティティ）が主役になるが、Aecor ではエンティティの振る舞い＝**Behavior** に着目してモデリングし[^behavior_land]、プログラムの「記述と実行」をきっちり分離した上で、記述部分のみをドメイン層に置く。CQRS で言えば **ライトサイド**の振る舞いになる。[^dont_eventsource_anything]

以下のような構成要素がある

#### 振る舞い代数 / Behavior Algebra
エンティティの動詞を、Tagless final のスタイルで、**振る舞い代数** (behavior algebra)として定義したもの。データは持たない。入門レベルの Tagless Final の記事などでは、`F[_]`に当たるものとして、せいぜい`Id`、`Future`、`Try`くらいで説明されることが多いが、Aecor では MTL と組み合わせて、もっと豊かな型表現として活用されている([サンプル](https://github.com/vpavkin/ticket-booking-aecor/blob/master/booking/src/main/scala/ru/pavkin/booking/booking/entity/EventsourcedBooking.scala))。

#### 状態 / State
関数型プログラミングでは、データはデータとして、振る舞いと癒着させずにシンプルに表現することになるが、Aecor のドキュメントでは、これを**状態**（State）と呼んでいる。クラシカルな DDD と同様にデータベース都合のモデリングは厳禁で、Aecor でも [Persistence Ignorant](https://www.google.com/search?q=Persistence+Ignorance+DDD) なスタイルが尊重される。ちなみに、生成／更新日時といったメタデータやエンティティIDなどもドメイン層に属するものとは見なさず、状態には含めない（[後述](https://qiita.com/yasuabe2613/items/9cc09fe1d7ff172976fa#%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%83%86%E3%82%A3id-%E3%81%A8%E3%83%A1%E3%82%BF%E3%83%87%E3%83%BC%E3%82%BF)）。

初期状態を作る関数や、Event を受けて状態遷移させる関数も状態/State のモジュールに書く（[サンプル](https://github.com/vpavkin/ticket-booking-aecor/blob/master/booking/src/main/scala/ru/pavkin/booking/booking/entity/BookingState.scala)）。これらはランタイムに渡す`EventsourcedBehavior` インスタンスを生成するときに、後述のアクションと共に必要になる。

#### イベント / Domain Event
イベントソーシングだけに当然イベントは重視される。Aecor 仕様ではないが、[Event Storming](https://en.wikipedia.org/wiki/Event_storming) セッションなどで、関係者一同で合意できる**ユビキタス言語**として導き出すのが望ましい。

イベントは分散処理のノード間、またノードとジャーナルとの間でもやり取りされるので、後述のインフラ層の作業として**シリアライゼーション**が実装されることになる。

#### 却下 / Rejection
例えば、すでに拒否されている予約申し込みへのキャンセルのように、状態とイベントの組み合わせによってはビジネスロジックとして受け付けられないものがある。これを**却下**= rejection として列挙し理由を表現する([サンプル](https://github.com/vpavkin/ticket-booking-aecor/blob/master/booking/src/main/scala/ru/pavkin/booking/booking/entity/BookingCommandRejection.scala))。

#### アクション / Eventsourced Behavior
Tagless Final の入門記事では、代数を定義したら、具体的なエフェクト`F[_]`を決めてインタープリターを書く流れになることが多いが、Aecor では `F[_]`をパラメータとしたまま、暗黙に与えられる `MonadAction` インスタンスへの操作を使って、**アクション**を記述する（[サンプル](https://github.com/vpavkin/ticket-booking-aecor/blob/master/booking/src/main/scala/ru/pavkin/booking/booking/entity/EventsourcedBooking.scala)）。具体的な永続化層へのアクセスはランタイム層のプラグインなどに任せることになる。

`MonadAction` とその派生クラスは以下のような構成になっていて、ここまでに説明した状態(S)、イベント(E)、却下(R)と、効果`F[_]`、`I[_]`が、ここで型として合成される。

```scala
// 基本形
trait MonadAction[F[_], S, E] extends Monad[F] {
  def read: F[S]                        // 型Sの状態を読む
  def append(es: E, other: E*): F[Unit] // 型Eのイベントを追加する
  def reset: F[Unit]                    // リセットする
}
// 状態遷移の却下を加味したもの
trait MonadActionReject[F[_], S, E, R] extends MonadAction[F, S, E] {
  def reject[A](r: R): F[A]             // 却下理由Rで却下する
}
// 例えば Cats Effect の Clock に与えるような、Task や IO などの単純なF[_]と、
// モナドトランスフォーマーの合成結果となる I[_]を分けて扱いたい場合はこれを使う
trait MonadActionLift[I[_], F[_], S, E] extends MonadAction[I, S, E] {
  def liftF[A](fa: F[A]): I[A]          // F[_] から I[_] にリフトする（モナドトランスフォーマー的な意味で）
}
// 上の Monad*** をすべて併せたもの
trait MonadActionLiftReject[I[_], F[_], S, E, R]
    extends MonadActionLift[I, F, S, E]
    with MonadActionReject[I, S, E, R]
```
ここまでで、アクション、初期状態、状態遷移関数が出そろったので、`EventsourcedBehavior` のインスタンスを生成して、ランタイムにデプロイすることが出来るようになった。

※ 詳細は [Aecor Github のサンプル](https://github.com/notxcain/aecor#entity-behavior-definition)、チュートリアルの [Part 1](https://pavkin.ru/aecor-part-1/), [Part 2](https://pavkin.ru/aecor-part-2/) など参照。またチュートリアル[Part 2](https://pavkin.ru/aecor-part-2/)では、EventsourcedBehavior の中で動いている ActionT、EitherK、その他の MTL 操作などについても解説されている。

[^behavior_land]: [Pavkin 氏のチュートリアル](https://pavkin.ru/aecor-part-1/)では、behavior land とも表現されている。
[^dont_eventsource_anything]: ちなみに、このライブラリに限定した話ではないが、なんでもかんでもイベントソーシング対象にするのは間違い。

## Runtime
上述のイベントソーストな振る舞いを具体的に実行する層。以下のような要素がある。

#### ランタイム
ドメイン層で定義した振る舞いのデプロイ先を**ランタイム**=Runtimeという。

２タイプのランタイム、**Akka Persistence Runtime** と **Generic Akka Runtime** が提供されている。前者は [README](https://github.com/notxcain/aecor)のサンプルコードでは使われているもののやや古く、後者の Akka Cluster のみを使った Akka Persistence に依存しないランタイムの方がより新しく柔軟な実装で、チュートリアルではこちらが推奨されている。

#### ジャーナル
シリアライズされたイベントが、アペンドオンリーで永続化されるデータストアが**ジャーナル**で event log とも呼ばれる。postgres + doobie の[プラグイン](https://github.com/evotor/aecor-postgres-journal) が提供されている。

#### ノード
スケーラブルな分散 ES/CQRS のために、裏で Akka Cluster Sharding を使っている（ただし前述の通り、Aecor のユーザは Actor に触れることはなく、純粋関数型な ES+CQRS、関数型DDDが可能）。Single Writer Principle [^single_writer_principle]が成立しているため、個別のエンティティごとに見ると順序は保証されていて、これが後述の CQRS リードサイドでも重要になる。

上で触れた Eventsourced Behavior のインスタンスは、Aecor によってノードにデプロイされることになる。

#### ワイアリング と シリアライズ
用語的には、ジャーナル-ノードの間でイベントの読み書きに使われるのが**シリアライズ**で、ノード間の通信でのエンコード／デコードを**ワイヤプロトコル**という。

- シリアライズには Protocol Bufferなどが使える
- ワイヤプロトコルには、[BooPickle](https://github.com/suzaku-io/boopickle) 用の WireProtocol を生成するマクロアノテーションが Aecor で提供されている。永続化されるものでもなく、従って人が読める形式である必要がないため、スピードとフットプリントを優先しての BooPickle 採用らしい。

### エンティティID とメタデータ
エンティティID とメタデータは、実行環境プロダクトに依存するわけではないが、どちらかというと Behavior よりは Runtime に近い位置付けになる。

#### メタデータ
イベント生成日時など、ジャーナルには保存されるがドメイン層では扱わない[^update_time]情報は、**メタデータ**として別途定義し、`Enrich`という封筒状のコンテナにイベントと一緒に入れて、インフラ層で扱う。

[^update_time]: クラシカルなDDDでも生成日時、生成ユーザ、更新日、更新ユーザ、削除フラグなどDB都合の要素をドメインモデルに入れない方が良いのと同じこと。

#### エンティティID
エンティティIDそれ自体は、他のサブドメインからは参照されうる点で、ドメイン層に属すると言えなくもないが、自分自身のドメインではデータとしても振る舞いとしても使われることがない。

つまりドメイン層の Behavior がアクセスされる時点では、すでに「どのエンティティか？」は定まっているので ID は本来不要なはずであり、逆にエンティティが自分のIDを知る必要があるようならば、**関心事が混在** している可能性があるので、アグリゲートの境界を見直す余地がある。

Aecor ではこのポリシーを *separate identity and behavior*[^entity_identity] と呼び、これに則してエンティティIDを異なる関心事としてドメイン層の振る舞いから分離し、もっぱらジャーナルやワイアリングなどランタイム周りの層で取り扱うことになる。

[^entity_identity]: あるいは "entity should not need identity information to handle commands ([part 1](https://pavkin.ru/aecor-part-1/))"。または "entity behavior should not know about its identity, it's behavior should be defined solely by its state. ([README](https://github.com/notxcain/aecor#entity-behavior-definition))"



## CQRS （のリードサイド）
以下のような概念と構成要素がある。

#### 射影
ジャーナルをイベントの無限ストリームと捉えて、これに任意の**畳込み**演算 ＝ fold を施したものを projection = **射影**とよび、これを *secondary facts* として *the source of truth* としてのジャーナルに対置する。このうちクエリーに最適化されたものを**ビュー**と呼び、これが CQRS のリードサイドになる。

ジャーナルからイベントのストリームを得る機能は、ジャーナルのプラグインが提供する。 arcor-postgres-journal プラグインでは fs2 の `Stream` として提供される。

#### タギング
**タギング** という機能で、各イベントにタグがつけられる。**Paritioned Tagging** を使うと、Kafka のパーティションに相当する形で、イベントストリームを分割して分散処理できるようになる。

#### オフセット
Projection の畳み込みがどこまで済んだかを示す位置が**オフセット**で、それを記録するためのストレージを**オフセットストア**と呼ぶ。これも arcor-postgres-journal で提供されている。

イベント・ストリームの各要素は `Committable` にラップされていて、これをコミットすることでオフセットも永続化されることになる。

#### ビューとビュー・リポジトリ
前述の通りクエリー用途の射影が**ビュー**で、それを永続化するストレージが**ビュー・リポジトリ**。

ビューの更新処理は、現状のビューの上に未処理のイベントを畳み込んで、処理したところまでオフセットストアに書き込んでいくのが基本になる。RDB に限ったものではなく、NoSQL でも分析エンジンでも何でもいい。

リポジトリの実装面では、Tagless Final を使うとライトサイドの Behavior の実装との
整合感が出るが、その場合、欲しいビューに応じた自前のインタープリターを書くことになる。またオフセットのコミットが非同期になるため、若干ややこしい重複除去処理などが生じるが、この辺りはある程度 Aecor でサポートされていて、[サンプルコード](https://github.com/vpavkin/ticket-booking-aecor/blob/master/booking/src/main/scala/ru/pavkin/booking/booking/view/BookingViewProjection.scala)でもよく分かるようになっている。

ちなみにレイヤーで言えば、CQRSリードサイドのクエリーはほぼクライアント都合で決まる面が多く、ドメイン層というよりアプリケーション層またはユースケース層に属することになるが、個人的にはこの辺はライトサイドほど丁寧には関心事やレイヤーを分離しなくても、さほど問題ない気がしている。

★ 実務上は、ジャーナルをリプレイしてビューを再構築できる仕組みが、やはりとても重要だという。

## 所感
関数型 DDD、関数型ES+CQRSの一つのあり方、あるいは Tagless final の応用としてとても参考になる。

## 関連資料など
- [Aecor github](https://github.com/notxcain/aecor)
- [Vladimir Pavkin](https://pavkin.ru/aecor-intro/)氏の連載チュートリアル（全８回）。実はこの記事を書いたのも、この連載が面白かったからだったりする。
 - [Introduction](https://pavkin.ru/aecor-intro/) では以降に続く連載のロードマップを紹介している。
 - [Part 1](https://pavkin.ru/aecor-part-1/), [Part 2](https://pavkin.ru/aecor-part-2/)では Behavior について
 - [Part 3](https://pavkin.ru/aecor-part-3/)では、Runtime について解説される
 - [Part 4a](https://pavkin.ru/aecor-part-4a/)、[Part 4b](https://pavkin.ru/aecor-part-4b/)では、CQRS read-side について。
 - [PART 5](https://pavkin.ru/aecor-part-5/) では、ES+CQRS の応用として、[EIP](https://www.enterpriseintegrationpatterns.com/) (Enterprise Integration Patterns) の [Process Manager](https://www.enterpriseintegrationpatterns.com/patterns/messaging/ProcessManager.html) パターンの実装にも言及されている。[Saga パターン](https://qiita.com/yasuabe2613/items/b0c92ab8c45d80318420)とも関連するので、いずれ調べてみたい。
 - [Part 6](https://pavkin.ru/aecor-part-6/)では、中の仕組みについて若干深堀される。特に Tagless final については、「ちょっとマシな Java のインターフェイスに過ぎない」的な誤解を解くことも念頭においているという。
 - [Github 上のサンプルプロジェクト](https://github.com/vpavkin/ticket-booking-aecor)
- [Vladimir Pavkin 氏の Aecor プレゼン](https://www.youtube.com/watch?v=zHD5ap-Fab4)
  - [そのプレゼン資料PDF](https://www.dropbox.com/s/2ddch8egckkjcpz/Aecor.pdf)
- [最小の Aecor サンプル](https://github.com/yasuabe/simplest_aecor_trial)を書いた

[^oop_vs_fp]: 元ネタは Clojure の開発者である Rich Hickey 氏の"[Simple Made Easy](https://www.infoq.com/presentations/Simple-Made-Easy)"というプレゼン。超おもしろいやつ。

[^dmmf]: たとえば [この記事で](https://qiita.com/yasuabe2613/items/5ab33e103e4105630e4c)解説したように 『Domain Modeling Made Functional』では強く型付けられた**ワークフロー**として振る舞いを表現している。
[^frdm]: 『Functional Reactive Domain Modeling』では Free Monad を活用して、データと振る舞いを分離していた。

[^single_writer_principle]: In simple words it means, that at any point in time in the whole cluster there is maximum one instance of each specific entity, that can process commands and write events to the log. There should be no concurrent command processing and no concurrent writes to the log for any single entity instance. ([part 3](https://pavkin.ru/aecor-part-3/))