メインコンテンツまでスキップ

実現パターン

ドメインイベントイベントソーシングCQRS の概念を理解したうえで、これらを実際のシステムでどう実現するかを学びます。


全体像

┌─────────────────────────────────────────────────────────────────┐
│ 実現すべき3つの要素 │
│ │
│ ① イベントの保存場所(イベントストア) │
│ 「どこに書くか」 │
│ │
│ ② イベントの伝搬方法 │
│ 「どうやって届けるか」 │
│ │
│ ③ 読み取りモデルの構築 │
│ 「どう集計・検索するか」 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ ① │ │ ② │ │ ③ │ │
│ │ イベント │ → │ 伝搬 │ → │ 読み取りモデル │ │
│ │ ストア │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

① イベントストアの選択

イベントをどこに保存するか。

RDB(PostgreSQL 等)のテーブル

CREATE TABLE events (
id UUID PRIMARY KEY,
aggregate_id UUID NOT NULL, -- 対象エンティティのID
type VARCHAR(255) NOT NULL, -- イベントタイプ
data JSONB NOT NULL, -- イベントデータ
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
メリットデメリット
既存インフラで使える大量データの集計は苦手
トランザクション保証パーティション管理が必要
SQL で自由にクエリイベントソーシング専用機能なし
チームの学習コストなし

ほとんどのケースではこれで十分。 専用のイベントストアが必要になるのは、イベント数が数十億を超えるか、完全なイベントソーシングを採用する場合。

Apache Kafka

Producer → [Topic: events] → Consumer
├── Partition 0: tenant-A のイベント
├── Partition 1: tenant-B のイベント
└── Partition 2: tenant-C のイベント
メリットデメリット
超高スループット(数百万msg/秒)運用が複雑
複数コンシューマーに同時配信クエリ機能なし
順序保証(パーティション内)保持期間に制限
マイクロサービス間の標準的な選択

マイクロサービスアーキテクチャで、複数のサービスがイベントを購読する場合に適切。

EventStoreDB(専用イベントストア)

Stream: user-123
Event 0: UserRegistered {name: "Alice"}
Event 1: LoginSucceeded {ip: "1.2.3.4"}
Event 2: PasswordChanged {}
...

Stream: order-456
Event 0: OrderPlaced {items: [...]}
Event 1: PaymentCompleted {amount: 1000}
メリットデメリット
イベントソーシング専用に設計専用インフラの追加
ストリーム、プロジェクション組み込みチームの学習コスト
楽観的並行制御が自然エコシステムが小さい

完全なイベントソーシングを採用する場合の専用ソリューション。

選択ガイド

┌──────────────────────────────────────────────────────┐
│ │
│ まず RDB で始める │
│ ├── 十分な性能 → そのまま │
│ ├── 複数サービス間でイベント共有 → Kafka 追加 │
│ └── 完全なイベントソーシング → EventStoreDB 検討 │
│ │
└──────────────────────────────────────────────────────┘

② イベント伝搬の方法

イベントストアから読み取りモデルへ、どうやってイベントを届けるか。

CDC(Change Data Capture)

┌──────────┐  WAL  ┌──────────┐       ┌────────────────┐
│ PostgreSQL│ ───→ │ PeerDB │ ───→ │ ClickHouse │
│ (events) │ │ Debezium │ │ Elasticsearch │
└──────────┘ └──────────┘ └────────────────┘
  • DB の WAL(Write-Ahead Log)を監視し、変更を外部に配信
  • アプリ変更不要 — DB に書くだけで自動的に伝搬
  • 遅延: 数秒

最も導入コストが低い。 アプリがイベントの伝搬を意識する必要がない。

メッセージキュー(Kafka, RabbitMQ 等)

┌──────┐  publish  ┌───────┐  subscribe  ┌────────────────┐
│ App │ ────────→ │ Kafka │ ──────────→ │ 統計サービス │
└──────┘ │ │ ──────────→ │ 通知サービス │
│ │ ──────────→ │ 検索サービス │
└───────┘ └────────────────┘
  • アプリが明示的にイベントを publish
  • 1つのイベントを複数の購読者が受信可能
  • 遅延: ミリ秒〜秒

複数サービスがイベントを購読する場合の標準的な選択。

アプリ内 Pub/Sub

┌──────────────────────────────────────┐
│ App │
│ │
│ EventPublisher.publish(event) │
│ → @EventListener │
│ ├→ StatisticsHandler │
│ ├→ NotificationHandler │
│ └→ AuditLogHandler │
└──────────────────────────────────────┘
  • Spring の @EventListener や Observer パターン
  • 同一プロセス内で完結
  • 遅延: なし

モノリスアーキテクチャで、外部インフラを増やしたくない場合。

比較

CDCメッセージキューアプリ内 Pub/Sub
アプリ変更不要Publisher 追加EventListener 追加
複数購読者△(ツール依存)○(同一プロセス)
遅延ミリ秒〜秒なし
信頼性高(WALベース)高(at-least-once)中(プロセス障害で消失)
インフラ追加CDCツールMQクラスタなし
適用場面CQRS の読み取りモデル構築マイクロサービス間連携モノリス内の疎結合化

③ 読み取りモデルの選択

イベントから構築する「読み取り専用のデータストア」。用途に応じて最適なものを選びます。

┌─────────────────────────────────────────────────────────────┐
│ 同じイベントから │
│ 複数の読み取りモデルを構築 │
│ │
│ events ──┬──→ ClickHouse → 統計ダッシュボード │
│ ├──→ Elasticsearch → 監査ログ全文検索 │
│ ├──→ Redis → リアルタイムカウンター │
│ └──→ PostgreSQL → マテリアライズドビュー │
│ │
│ 各モデルは独立して構築・再構築可能 │
└─────────────────────────────────────────────────────────────┘
読み取りモデル得意なこと用途例
ClickHouse大量データの集計(COUNT, SUM, GROUP BY)統計ダッシュボード、月次レポート
Elasticsearch全文検索、フィルタ監査ログ検索、イベント検索
Redis超低レイテンシの読み取りリアルタイムカウンター、セッション
PostgreSQL(マテビュー)追加インフラなし小規模な集計、事前計算

組み合わせの具体例

シンプル構成(RDB + CDC + ClickHouse)

┌──────┐    ┌──────────┐  CDC  ┌────────────┐
│ App │ → │PostgreSQL│ ───→ │ ClickHouse │ → ダッシュボード
└──────┘ │(events) │ └────────────┘
└──────────┘
  • インフラ: PostgreSQL + PeerDB + ClickHouse
  • アプリ変更: なし(PostgreSQL に INSERT するだけ)
  • 適用: 統計・分析を高速化したい

フル構成(RDB + Kafka + 複数読み取りモデル)

┌──────┐    ┌──────────┐    ┌───────┐    ┌────────────────┐
│ App │ → │PostgreSQL│ → │ Kafka │ → │ ClickHouse │ → 統計
└──────┘ └──────────┘ │ │ → │ Elasticsearch │ → 検索
│ │ → │ 通知サービス │ → メール
└───────┘ └────────────────┘
  • インフラ: PostgreSQL + Kafka + ClickHouse + Elasticsearch
  • アプリ変更: Kafka Publisher 追加
  • 適用: マイクロサービス、多様な読み取り要件

段階的な導入

┌─────────────────────────────────────────────────────────────┐
│ 段階的に進める │
│ │
│ Step 1: アプリ内 Pub/Sub │
│ ├── EventListener で統計更新・通知を疎結合化 │
│ └── インフラ追加なし │
│ │
│ Step 2: CDC + OLAP │
│ ├── PeerDB で ClickHouse にイベントを同期 │
│ ├── 統計APIの読み取り先を ClickHouse に │
│ └── アプリ変更なし │
│ │
│ Step 3: メッセージキュー │
│ ├── Kafka で複数サービスにイベント配信 │
│ ├── 通知・検索・分析を独立サービス化 │
│ └── マイクロサービス化の基盤 │
│ │
│ 全部一度にやる必要はない。 │
│ 問題が顕在化した部分から段階的に。 │
│ │
└─────────────────────────────────────────────────────────────┘

関連ドキュメント

読み取りモデルの構築

パフォーマンス