はじめに
会計まわりの機能を Event Sourcing で実装してみたときの
- 技術選定がつらかった話(今回は Axon Framework を採用)
- 運用フェーズで大変だった話
- それでも「ビジネスロジックはかなり読みやすく・変更に強くなった」話
あたりを、振り返り的にまとめてみます。
※あくまで一プロジェクトの感想なので、「そういうケースもあるのね」くらいで読んでもらえると嬉しいです。
なぜ会計領域で Event Sourcing?
会計ドメインは
- 過去の履歴がすべて重要(「今いくら?」より「どう変遷してきたか?」)
- イベントの訂正や再計算、監査対応など「過去の状態を再現したい」ケースが多い
- 仕訳や残高などが「状態」というより「イベントの結果」として自然に考えられる
という特徴があり、設計検討の初期から
「Event Sourcing がハマるのでは?」
という話が出ていました。
RDB に「今の残高」を直接持つより、
- 入金イベント
- 出金イベント
- 突合イベント
などを全部残しておき、残高はイベントの集計結果として再生(リプレイ)する、という発想です。
この時点では「会計 × Event Sourcing = 気持ちよさそう」というテンションでスタートしています。
技術選定つらめ:Axon を採用するまで
Event Sourcing をやると決めてから、まず悩んだのが技術選定です。
- フルスクラッチで Event Store + CQRS まわりを実装する
- 既存のライブラリ/フレームワークを使う
の二択がありましたが、今回は
Axon Framework を使う
という選択をしました。
Axon を選んだ理由
- Java / Spring ベースで乗りやすい
- Aggregate / Command / Event の構造がフレームワークである程度ガイドされる
- Event Store や Saga、Query Model 更新まわりの仕組みが揃っている
「Event Sourcing を一から設計してフルスクラッチでやる元気はないが、ある程度の自由度はほしい」
という現実的な理由もありました。
とはいえ、技術選定はかなり悩んだ
つらかったポイントを挙げると:
- Axon 独自の概念やアノテーションが多く、学習コストが高い
- ドキュメントやサンプルは多いものの、会計ドメイン向けの事例は少ない
- パフォーマンス改善に手を出すと、結局フレームワークが隠してくれている、インフラ関連のコードを解析する必要がある
特に、
- どこまでを Aggregate に持たせるのか
- イベントの粒度をどこに切るのか
といった Event Sourcing × DDD 的なモデリングの話 と、
Axon の推奨パターン をすり合わせるのがなかなか大変でした。
実装構成ざっくり(Axon + 会計)
実際の構成はざっくりこんな感じです:
-
Aggregate
- 口座 / 勘定単位の Aggregate
- Command を受けて、ドメインルールをチェックし、Event を発行
-
Event Store
- Axon の Event Store を RDBに永続化
- 必要に応じてスナップショットを取得
-
Query Model
- 残高一覧、取引履歴一覧などを、別テーブルに投影してクエリ
-
統合
- 他サービスとの連携は Event Handler 経由で非同期に処理
イメージ的には:
良かった点:ビジネスロジックがかなり読みやすくなった
一番のメリットはここでした。
1. ビジネスロジックが Aggregate 内にきれいにまとまる
たとえば「入金」処理でやっていることは、
コマンド(入金リクエスト)を受ける
ドメインルールをチェックする
DepositRecorded みたいなイベントを発行する
という流れで、
「何が起きたのか」をイベント名としてそのまま表現できる ので、コードが読みやすいです。
@CommandHandler
public void handle(DepositCommand command) {
if (command.amount().isNegative()) {
throw new IllegalArgumentException("入金額は正である必要があります");
}
AggregateLifecycle.apply(new DepositRecordedEvent(
accountId,
command.amount(),
command.occurredAt()
));
}
@EventSourcingHandler
public void on(DepositRecordedEvent event) {
this.balance = this.balance.add(event.amount());
}
「コマンドでルールチェック」「イベントで状態変更」という分離がきれいにできるので、
仕様確認、差分レビュー、影響範囲の把握がやりやすかったです。
2. ビジネスロジック変更に強い
会計領域なので、
- 消費税の扱い
- 手数料計算
- 丸めのルール
などが後からじわじわ変わっていくのですが、Event Sourcing のおかげで
- 新ルールを適用した再計算
- ある時点を境にルール変更
のような対応がやりやすくなりました。
新しいルールを導入するときも、
イベント構造を変える or 追加する
再生時に「発生日時」や「バージョン」を見て条件分岐
みたいな戦略が取りやすく、「イベント履歴さえ残っていれば、あとからどうとでも再生し直せる」という安心感があります。
運用フェーズで大変な点
イベントスキーマ変更のコスト
会計領域は後から要件が追加されがちで、イベントにフィールドを追加したい、解釈を変えたい(例えば税区分の意味が変わる)といったケースがそこそこ発生します。
Event Sourcingでは「既に保存済みのイベント」と「新しく発生するイベント」の両方を考慮しないといけないので、イベントスキーマ変更のコストが高めです。古いイベントをどう扱うか(マイグレーションするか、そのまま解釈を変えるか)、バージョニングをどうするか、Projection側の再構築をどうやって安全にやるか、などの話が機能改修のたびに出てきます。
チーム全体にEvent Sourcingの前提知識が必要
もし新しくメンバーがjoinした場合に「なぜDBに残高が直接入っていないのか」「なぜイベントを経由して状態が変わるのか」「なぜクエリ側とコマンド側でモデルが違うのか」を毎回説明するとすればかなり大変です。
Event Sourcing / CQRSを知らない前提で、オンボーディング用のドキュメントや図を用意しておかないと、「とっつきづらいサービス」と感じられてしまいます。
会計 × Event Sourcingをやってみての総評
良かった点とつらかった点をざっくりまとめると:
👍 良かった
- ドメインイベントを中心に、ビジネスロジックがきれいに整理される
- イベント履歴が残るので、監査・トレース・再計算に強い
- ルール変更や再計算系の要件に対応しやすい
👎 つらかった
- Axonを含む技術スタックの習得コストが高い
- イベントスキーマ変更 / Projection再構築のコストが重い
- チーム全体にEvent Sourcing / CQRSの理解が必要
個人的な結論
会計のように「履歴」と「再計算」が本質的に重要なドメインでは、Event Sourcingはかなり相性が良いと感じました。
ただし、システム規模、チームの経験値、運用で許容できる複雑さを踏まえて、「全部Event Sourcing」ではなく、「ここだけEvent Sourcing」くらいの慎重さは必要だと思います。
以上、会計領域の開発をEvent Sourcing(+ Axon)でやってみた感想をざっと書いてみました。
「会計 × Event Sourcingに興味ある」「Axonを採用するか悩んでいる」「Event Sourcing、実際どうなの?」という方の判断材料のひとつになれば幸いです。