ADR-001: CQRS without Event Sourcing
- ステータス: Accepted
- 日付: 2025-12-16
- 決定者: アーキテクトチーム
コンテキスト
TASHIKA は年末調整(年始調整)の申告データを管理するプラットフォームであり、以下の要件を持つ:
- 履歴管理: 申告データの変更履歴を保持し、任意時点の状態を参照可能にする
- 監査証跡: 「誰が」「いつ」「何を」変更したかを完全に追跡する(→ CR-003 監査・統制)
- データ分離: 従業員入力データと管理者入力データを明確に分離する
- 読み取りパフォーマンス: 表示用データを最適化し、高速なクエリを実現する(→ PE-001 パフォーマンス)
これらの要件を満たすパターンとして、CQRS(Command Query Responsibility Segregation)の採用は確定しているが、書き込み側の永続化方式として「イベントソーシング」と「スナップショット方式」のどちらを採用するかを判断する必要がある。
決定
CQRS パターンを採用し、書き込み側の永続化方式としてスナップショット方式を採用する。イベントソーシングは採用しない。
理由
- ドメインとの適合性: 税額計算は確定時点のスナップショット状態に対する決定論的関数(純粋関数パイプライン)であり、イベントのリプレイで状態を再構築する必要がない。入力(事実データ)が同一なら結果は常に同一であるため、「最新のスナップショット + 計算エンジン」で十分な再現性を持つ
- Finalized 後の不変性: 申告データは Finalized 後に凍結され、修正は再年調として新バージョンを作成する(→ DM-501 TaxCalculationResult)。イベントソーシングの「イベント修正・補償イベント」モデルはこのライフサイクルと合致しない
- Fact/Judgment 分離との整合: TASHIKA の設計思想は「事実(Fact)を記録し、判定(Judgment)は常に計算で導出する」である。事実データはスナップショットとしてバージョン管理し、判定結果は計算エンジンで再生成する。イベントストアからの再構築は不要
- 実装の複雑さの回避: イベントスキーマの進化管理(Upcaster)、イベントバージョニング、プロジェクション構築といった運用負荷を回避できる
- ストレージ効率: スナップショットのみの保持で十分であり、全イベントの無期限保持が不要
却下した代替案
| 代替案 | 却下理由 |
|---|---|
| CQRS + Event Sourcing | 税額計算は確定時点のスナップショットに対する純粋関数であり、イベントリプレイで状態を再構築するユースケースがない。イベントスキーマの進化管理が運用負荷となる |
| 従来型 CRUD(CQRS なし) | 書き込みと読み取りの最適化を独立に行えない。監査証跡の自然な記録が困難。自動保存のスナップショット履歴管理が煩雑になる |
トレードオフ・リスク
- イベント粒度の情報喪失: スナップショット間の個別フィールド変更の追跡はできない。ただし、監査ログ(→ DM-901 AuditLog)で操作レベルの追跡を補完する
- 将来のイベントソーシング移行: 現時点では不要だが、将来的な要件変化(例: リアルタイムの変更ストリーム配信)に備え、Command ハンドラーのインターフェースはイベント発行に拡張可能な設計とする
- Read Model の即時整合性: 結果整合性(Eventual Consistency)ではなく即時整合性を採用するため、書き込みと読み取りが同一トランザクション内で完結する。大規模化時にスケーラビリティの制約となる可能性がある
結果
docs/design/backend/cqrs.mdに CQRS アーキテクチャ構成図(スナップショット方式)を記載- Command 実行時に
DeclarationSnapshot(JSONB)を生成し、申告データ全体の状態を保存 - Read Model は Command 実行後に同期的に更新(即時整合性)
- MediatR を使用して Command/Query ハンドラーを分離