目次を表示する

Event Sourcing 深掘り ── 経緯・必須要素・デファクトツール・実践と落とし穴

デファクトツール/フレームワーク比較

デファクトツール/フレームワーク比較

「Event Sourcing をやろう」と決めた次の問いは、「何を使うか」 だ。前章で見た5つの責務(Event / Stream / Store / Projection / Snapshot)を、どこまで自前で書き、どこからは既製のものに任せるか ── ここで方針を間違えると、半年後に後悔する。

この章では、2026年5月時点で デファクトとして実務で選ばれる 6つの選択肢を比較する。

1. KurrentDB(旧 EventStoreDB)── Event Sourcing 専用 DB
2. Axon Framework             ── JVM 上の DDD/CQRS/ES オールインワン
3. Marten                     ── .NET + PostgreSQL の Document/Event Store
4. Akka Persistence / Pekko   ── Actor 上の永続化機構
5. AWS DynamoDB + EventBridge ── マネージドでの組み立て
6. 自作 PostgreSQL ベース      ── 言語問わずの最小構成

最後に、選定の判断軸を整理する。


全体マップ:3つの軸で見る

ツールを比較する前に、座標軸を引いておく。

quadrantChart
    title 抽象度 × プラットフォーム結合度
    x-axis "言語非依存(多言語対応)" --> "特定エコシステム結合"
    y-axis "インフラのみ提供(自前実装多い)" --> "フレームワーク含む(実装最小)"
    quadrant-1 "高抽象+特定言語"
    quadrant-2 "高抽象+多言語"
    quadrant-3 "低抽象+多言語"
    quadrant-4 "低抽象+特定言語"
    "Axon Framework": [0.85, 0.92]
    "Akka/Pekko Persistence": [0.85, 0.78]
    "Marten": [0.78, 0.7]
    "KurrentDB": [0.25, 0.6]
    "AWS DynamoDB+EB": [0.4, 0.4]
    "PostgreSQL自作": [0.2, 0.18]

3つ目の軸 ── 「ES だけ vs DDD/CQRS/ES 一式」 ── も合わせて見ておく。

ES だけ ────────────────────────── 一式
KurrentDB    自作    Marten    AWS    Axon  Akka

これらの軸の組み合わせで、ツールごとの性格が見えてくる。


1. KurrentDB(旧 EventStoreDB)── Event Sourcing 専用 DB

概要

Greg Young 自身が CTO として関与した、Event Sourcing 専用の DB。2012年に EventStoreDB として登場し、2024年11月に Kurrent へリブランド、製品名は KurrentDB(バージョン25.0以降)になった。

特徴

✅ Event Sourcing 専用設計
    Append-only / 楽観的並行性制御 / Stream / サブスクリプションが標準装備
    「自分で実装すべきこと」が最も少ない

✅ 言語非依存(gRPC ベース)
    .NET / Java / Node.js / Go / Python など主要言語のクライアントが揃う

✅ サブスクリプション機構が強力
    Catch-up / Persistent / Filtered の3種類を提供
    Projection を実装する基盤として完成度が高い

❌ 採用判断のハードル
    別途「専用 DB」を運用する負担(バックアップ・監視・アップグレード)
    既存スタックに PostgreSQL や DynamoDB がある場合、追加 DB の正当化が必要

❌ Read Model(CQRS の Q 側)は別途用意
    KurrentDB はあくまで Event Store であり、Read Model のストアではない

TypeScript での使い方

import { KurrentDBClient, jsonEvent } from '@kurrent/kurrentdb-client'

const client = KurrentDBClient.connectionString(
  'kurrentdb://localhost:2113?tls=false'
)

// 書き込み
await client.appendToStream(
  'screening-process-sp-001',
  [jsonEvent({
    type: 'FirstInterviewScheduled',
    data: { scheduledFor: '2026-05-20', interviewerId: 'iv-7' },
  })],
  { expectedRevision: 'no_stream' },
)

// 読み込み
const events = client.readStream('screening-process-sp-001')
for await (const { event } of events) {
  console.log(event?.type, event?.data)
}

向いているケース

- Event Sourcing を「主アーキテクチャ」として腰を据える
- 言語スタックが多様(マイクロサービス間で言語が異なる)
- サブスクリプション機構を自前実装したくない
- 専用 DB の運用負担を払える組織体制がある

公式ドキュメント:docs.kurrent.io


2. Axon Framework ── JVM のオールインワン

概要

JVM 上で DDD / CQRS / Event Sourcing を統合的に実装するためのフレームワーク。AxonIQ 社が開発・サポートしている。Java / Kotlin で使われ、累計70M ダウンロードを超える。

特徴

✅ DDD/CQRS/ES の語彙が直接コードに現れる
    @Aggregate / @CommandHandler / @EventHandler / @EventSourcingHandler
    アノテーションでアーキテクチャの意図を表現できる

✅ Axon Server とのセット運用が強力
    Event Store + Command Bus + Query Bus + Saga 管理が一括で揃う
    分散環境でのメッセージルーティングが自動化される

✅ Saga(プロセスマネージャ)の標準サポート
    複数集約をまたぐ業務プロセスをコードで素直に書ける

❌ JVM ロックイン
    他言語からは(基本的に)使えない。マイクロサービスが多言語なら不向き

❌ 学習コストが高い
    DDD の概念を一通り知っていることが前提
    アノテーション魔術が多く、デバッグ時に内部理解が必要

❌ Axon Server の運用
    Standard Edition は無償だが、Enterprise 機能(クラスタ等)は商用ライセンス

Java での使い方

@Aggregate
public class ScreeningProcess {
    @AggregateIdentifier
    private String processId;
    private ScreeningStage currentStage;

    @CommandHandler
    public void handle(ScheduleFirstInterviewCommand cmd) {
        if (currentStage != ScreeningStage.DOCUMENT_PASSED) {
            throw new IllegalStateException("Must pass document screening first");
        }
        AggregateLifecycle.apply(new FirstInterviewScheduledEvent(
            processId, cmd.getScheduledFor(), cmd.getInterviewerId()
        ));
    }

    @EventSourcingHandler
    public void on(FirstInterviewScheduledEvent event) {
        this.currentStage = ScreeningStage.FIRST_INTERVIEW_SCHEDULED;
    }
}

向いているケース

- Java / Kotlin / Spring エコシステム中心の組織
- DDD/CQRS/ES の3点セットを「一気通貫」で導入したい
- Saga による複数集約調整が頻出する業務
- 商用サポートが必要なエンタープライズ

公式:axoniq.io/axon-framework


3. Marten ── .NET + PostgreSQL

概要

.NET 環境で、PostgreSQL を Document DB + Event Store として使うためのライブラリ。JasperFx Software が開発。MIT ライセンス。

特徴

✅ PostgreSQL があれば動く
    専用 DB が不要、既存の PostgreSQL クラスタに乗せられる
    バックアップ・監視・運用ノウハウを既存資産で賄える

✅ Document DB と Event Store のハイブリッド
    集約は Document として保存可、イベントストリームも保存可
    段階的に Event Sourcing を導入する戦略を取りやすい

✅ Projection の async daemon が標準装備
    非同期 Projection、リトライ、再構築を仕組みとして提供

✅ アクティブな開発
    2025年6月時点で「Quick append mode で2倍高速化」「PostgreSQL Partitioning 対応」など
    パフォーマンス改善が継続している

❌ .NET ロックイン
    Java/Node.js/Go では使えない

❌ PostgreSQL ロックイン
    他の RDB やマネージドサービスへは移植できない

C# での使い方

// イベントの追記
await session.Events.Append(processId,
    new FirstInterviewScheduled(processId, scheduledFor, interviewerId)
);
await session.SaveChangesAsync();

// 集約の復元(Marten が自動的に Stream を読んで Apply する)
var process = await session.Events.AggregateStreamAsync<ScreeningProcess>(processId);

// Projection の登録
storeOptions.Projections.Add<ScreeningDashboardProjection>(ProjectionLifecycle.Async);

向いているケース

- .NET 中心のプロジェクト
- PostgreSQL を既に運用している(または運用ノウハウがある)
- 「全集約を ES に倒す」ではなく「一部だけ ES、残りは Document」と段階導入したい
- 専用 DB の運用負担を増やしたくない

公式:martendb.io


4. Akka Persistence / Apache Pekko Persistence

概要

Actor モデル上で、Actor の状態を Event Sourcing で永続化する機構。元は Lightbend 社の Akka に組み込まれていたが、2022年9月の Akka のライセンス変更(Apache 2.0 → Business Source License)を受け、コミュニティが Akka 2.6.x を Apache Foundation 配下にフォークした Apache Pekko が誕生した。

2026年時点の状況:
  - Akka:BSL ライセンス(年間売上 25M USD 超は商用契約必要)
         ただし36ヶ月後に各リリースは Apache 2.0 へ自動転換
  - Pekko:Apache 2.0 のまま、コミュニティ駆動で開発継続

特徴

✅ Actor モデルとの統合
    1つの Actor が1つの集約に対応する設計が自然
    高並行・分散の文脈で、Akka Cluster と組み合わせると強力

✅ 多様なバックエンド
    Cassandra / JDBC / Couchbase など、Plugin で永続化先を選べる

✅ Snapshot / Projection の機構が組み込み

❌ Actor モデル自体の学習コスト
    DDD/ES だけでなく Actor モデルの理解も必要

❌ ライセンスの注意
    Akka を選ぶか Pekko を選ぶかの判断が必要

Scala での使い方(Pekko)

object ScreeningProcessActor {
  sealed trait Command
  case class ScheduleFirstInterview(scheduledFor: LocalDateTime, interviewerId: String) extends Command

  sealed trait Event
  case class FirstInterviewScheduled(scheduledFor: LocalDateTime, interviewerId: String) extends Event

  def apply(processId: String): Behavior[Command] =
    EventSourcedBehavior[Command, Event, State](
      persistenceId = PersistenceId.ofUniqueId(s"screening-$processId"),
      emptyState = State.empty,
      commandHandler = (state, cmd) => /* バリデーション → Event を返す */,
      eventHandler   = (state, event) => /* state を更新 */,
    )
}

向いているケース

- 既に Akka/Pekko を使っている JVM プロジェクト
- 高並行・分散処理が業務要件
- Actor モデルがチームの設計言語として機能している

公式:pekko.apache.org / akka.io


5. AWS DynamoDB + EventBridge ── マネージドの組み立て

概要

専用ツールではなく、AWS のマネージドサービスを組み合わせて Event Sourcing を実現する アプローチ。DynamoDB を Event Store として、DynamoDB Streams + EventBridge + Lambda で Projection を構築する。

構成例

graph LR
    Cmd["Lambda<br/>(Command)"] --> DDB[("DynamoDB<br/>━━━━━<br/>PK: streamId<br/>SK: version")]
    DDB -->|変更を検知| Streams["DynamoDB<br/>Streams"]
    Streams --> Pipes["EventBridge<br/>Pipes"]
    Pipes --> Proj["Projection<br/>Lambda"]
    Proj --> RDB[("Read DB")]

    style DDB fill:#fff3e0,stroke:#fb8c00
    style RDB fill:#e8f5e9,stroke:#43a047

特徴

✅ サーバレス
    専用 DB のプロビジョニング不要、トラフィックに応じて自動スケール

✅ AWS エコシステム統合
    EventBridge → Step Functions / SNS / SQS への連携が容易
    既に AWS を使っているなら追加コストが小さい

✅ 楽観的並行性制御は DynamoDB の condition expression で実現可能

❌ Event Store としての機能は自前で組み立てる
    Stream の読み取り、Snapshot、再生機構などはアプリ側で実装

❌ ベンダーロックイン
    設計が AWS 固有になり、他クラウドへの移植性は低い

❌ DynamoDB のサイズ・コスト特性を理解する必要
    単一 PK あたりのアイテム数制限、ホットパーティション問題への対処

TypeScript での使い方(簡略)

// 楽観的並行性制御つき書き込み
// テーブルは PK=streamId, SK=version の複合キーで構成
await dynamodb.send(new PutItemCommand({
  TableName: 'events',
  Item: {
    streamId: { S: 'screening-process-sp-001' },
    version:  { N: '5' },
    eventType: { S: 'FirstInterviewScheduled' },
    payload:  { S: JSON.stringify({ scheduledFor, interviewerId }) },
    occurredAt: { S: new Date().toISOString() },
  },
  // 同じ (streamId, version) のアイテムが存在しない場合のみ書き込む
  ConditionExpression: 'attribute_not_exists(streamId)',
}))
// version=5 が既に存在すれば ConditionalCheckFailedException
// (= 楽観的並行性制御。PK存在チェックがSKと組み合わさって複合キーで効く)

向いているケース

- すでに AWS でサーバレス構成を採用している
- 専用 DB の運用負担を避けたい
- スパイクの大きいトラフィックでオートスケール優先
- Event Sourcing を「主軸」ではなく「特定境界文脈で」採用する

参考:Event-driven Architecture on AWS


6. 自作 PostgreSQL ベース ── 言語問わずの最小構成

概要

普通の PostgreSQL に events テーブルを1つ作って、自前で Event Sourcing を実装する アプローチ。実は多くのプロジェクトが、最初はこれで始める。

最小スキーマ

CREATE TABLE events (
  global_position BIGSERIAL PRIMARY KEY,         -- 全Stream横断の位置
  stream_id       TEXT NOT NULL,                 -- "screening-process-sp-001"
  stream_version  INTEGER NOT NULL,              -- Stream内のバージョン
  event_type      TEXT NOT NULL,
  payload         JSONB NOT NULL,
  metadata        JSONB NOT NULL DEFAULT '{}'::jsonb,
  occurred_at     TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),

  UNIQUE (stream_id, stream_version)             -- 楽観的並行性制御の核
);

CREATE INDEX idx_events_stream ON events(stream_id, stream_version);
CREATE INDEX idx_events_type   ON events(event_type, global_position);

特徴

✅ 既存 PostgreSQL に乗せられる
    追加 DB の運用なし、バックアップやレプリケーションは既存運用に統合

✅ 言語非依存
    Node.js でも Python でも Go でも、SQL を書ける言語ならどれでも

✅ 仕組みが透明
    「Event Sourcing がどう動いているか」が SQL レベルで見える
    チームの学習教材としても優れる

✅ 段階導入しやすい
    既存システムに events テーブルを追加するだけで導入できる

❌ サブスクリプション機構を自前実装
    LISTEN/NOTIFY、Logical Replication、ポーリングなどから選んで実装
    ここで品質差が出る

❌ Snapshot 戦略を自前実装

❌ 大規模化への備え
    数億イベントになるとパーティショニング戦略が必要になる

Node.js/TypeScript エコシステムの選択肢

「自作」と言っても、TypeScript ではいくつか有力なライブラリがある。

- Emmett (event-driven-io)
  KurrentDB と PostgreSQL の両方をバックエンドにできる ES ライブラリ。
  軽量で関数的アプローチ、TypeScript ファースト。

- @ricofritzsche/eventstore
  PostgreSQL + TypeScript で楽観的ロック・サブスクリプションをサポート。
  DCB(タグベースのクエリ)の実装にも使われている。

- evtstore (Seikho)
  Node.js / TypeScript の Event Sourcing & CQRS ライブラリ。

向いているケース

- 既に PostgreSQL を運用しており、追加 DB を増やしたくない
- 言語スタックが TypeScript / Python / Go など、JVM や .NET 以外
- Event Sourcing を「特定の境界文脈だけ」に部分導入したい
- チームに Event Sourcing の仕組みを浸透させる教材としても使いたい

比較表:6つの選択肢

ツール言語/PFバックエンドDDD/CQRS統合サブスク自前実装の量ライセンス
KurrentDBgRPC(多言語)専用△(ES特化)商用+OSS
AxonJVMAxon Server / RDB◎(一気通貫)最少OSS+商用
Marten.NETPostgreSQL△(ES特化)MIT
Akka/PekkoJVM/ScalaCassandra/JDBC等○(Actor)Akka:BSL / Pekko:Apache2
DynamoDB+EB多言語DynamoDB×中〜多AWS従量
PostgreSQL自作多言語PostgreSQL×△(自前)最多OSS

◎ = 強力 ○ = 一般的 △ = 限定的 × = なし


Event Sourcing と Event Streaming の違い(混同注意)

ここで一度、Kafka を Event Sourcing に使えるか という頻出の問いに触れておく。

Event Sourcing:
  目的:「業務状態の真実をイベント列で表現する」
  要件:永続的・任意時点読み取り・楽観的並行性制御・集約単位の原子追記

Event Streaming(Kafka, Pulsar など):
  目的:「イベントを高速に流す」
  要件:高スループット・順序保証(パーティション内)・パブサブ

Kafka を Event Sourcing の Event Store として使うのは、設計上ミスマッチが多い

Kafka を Event Store にする際の問題:
  1. Stream(特定集約のイベント)を直接読むのに最適化されていない
     - 全パーティションをスキャンするか、1集約 = 1パーティションが必要
     - 集約数が多いとパーティション数が破綻する
  2. 楽観的並行性制御が標準では存在しない
  3. 古いイベントの保持期間が無限ではない(compaction か永続保持の設定が必要)

正しい使い方は、Event Store にイベントが追記された後、Kafka で他のサービスに配信する という二段構成だ。Event Store と Event Streaming は「役割の異なる別物」と理解する。

graph LR
    Cmd[書き込み] --> ES[("Event Store<br/>KurrentDB 等<br/>━━━━━<br/>業務状態の真実")]
    ES --> Out[("Outbox")]
    Out --> Kafka[("Kafka<br/>━━━━━<br/>イベント配信")]
    Kafka --> S1["サービスA"]
    Kafka --> S2["サービスB"]
    Kafka --> S3["分析パイプライン"]

    ES -.責務:永続化と整合性.- ES
    Kafka -.責務:配信とスループット.- Kafka

    style ES fill:#fff3e0,stroke:#e65100
    style Kafka fill:#e8f5e9,stroke:#2e7d32
    style Out fill:#f3e5f5

Event Store は「業務状態の真実」、Kafka は「イベントを高速に流すパイプライン」── この役割分担が崩れると、Kafka を Event Store にしようとして破綻する。


選定の判断軸

最後に、現場で実際に使える判断軸を3つの問いとしてまとめる。

flowchart TD
    Start[Event Sourcing ツール選定] --> Q1{言語スタックは?}
    Q1 -->|JVM| Q1a{DDD/CQRS一気通貫?}
    Q1 -->|.NET| Marten[Marten]
    Q1 -->|Node.js/<br/>多言語| Q2{専用DB追加運用<br/>を許容できる?}
    Q1 -->|Scala/Actor志向| Akka[Akka or Pekko<br/>Persistence]

    Q1a -->|Yes| Axon[Axon Framework]
    Q1a -->|No / Actor志向| Akka

    Q2 -->|Yes| Kurrent[KurrentDB]
    Q2 -->|No| Q3{既存DB資産は?}

    Q3 -->|PostgreSQL あり| Self[PostgreSQL自作<br/>+Emmett等ライブラリ]
    Q3 -->|AWS中心| AWS[DynamoDB+EventBridge]

    style Axon fill:#e3f2fd
    style Marten fill:#e3f2fd
    style Akka fill:#e3f2fd
    style Kurrent fill:#e8f5e9
    style Self fill:#fff3e0
    style AWS fill:#fff3e0

問い1:言語スタックは何か?

JVM 中心 → Axon Framework か Akka/Pekko Persistence
.NET 中心 → Marten が第一候補
Node.js / TypeScript 中心 → KurrentDB(gRPC)か PostgreSQL 自作(Emmett等)
多言語マイクロサービス → KurrentDB(gRPC)か AWS DynamoDB+EB

問い2:「専用 DB を追加運用する」コストを払えるか?

払える → KurrentDB(最も完成度が高い専用 DB)
払えない → 既存 DB を活用
  - PostgreSQL あり → Marten / 自作 PostgreSQL
  - DynamoDB あり  → AWS の組み合わせ

問い3:DDD/CQRS とのセット導入か、ES だけ部分導入か?

DDD/CQRS/ES 一気通貫で入れたい → Axon Framework
ES だけ部分導入から始めたい → Marten / 自作 PostgreSQL / KurrentDB

採用管理システムへの適用

題材としているシステムが TypeScript / Node.js で、既に PostgreSQL を運用している前提なら、現実的な選択肢は次の2つに絞られる。

選択肢A:自作 PostgreSQL(Emmett 等のライブラリ活用)
  - 既存スタックに統合できる
  - サブスクリプションは LISTEN/NOTIFY か Logical Replication で実装
  - 中規模までは十分動く

選択肢B:KurrentDB(gRPC で接続)
  - Event Sourcing の機能完成度が最高
  - 専用 DB の運用負担を払う覚悟が要る
  - 大規模・高スループット・複雑なサブスクリプションがあるなら有利

「まずは小さく始めて、必要になったら KurrentDB に移行する」が現実的な戦略だ。Event の表現自体は移行可能なので、最初の選択は不可逆ではない。


まとめ

Event Sourcing のツール選定は、3つの軸の掛け合わせで決まる:
  1. 言語/プラットフォーム
  2. 既存 DB 資産の活用 vs 専用 DB 採用
  3. ES 単体 vs DDD/CQRS/ES 一気通貫

ツールが決まったら、次は 「どう設計し、どう運用するか」 だ。次章では、現場で効くベストプラクティスを集めて見ていく。イベントの命名・粒度から、スキーマ進化・PII 対応・テスト戦略まで、Event Sourcing を「動かす」だけでなく「育てる」ための実践知を整理する。