ADR-003: スナップショット方式の申告履歴管理
- ステータス: Accepted
- 日付: 2025-12-16
- 決定者: アーキテクトチーム
コンテキスト
TASHIKA の申告データ(Declaration)は、Draft → Submitted → Approved → Finalized のライフサイクルを持つ(→ DM-401 Declaration)。このライフサイクルにおいて、以下の要件を満たす履歴管理方式を選択する必要がある:
- モードレス自動保存: ビュー遷移時に自動保存し、明示的な保存ボタンを持たない
- 履歴復元: 過去の保存時点に戻れる
- Finalized 後の不変性: 確定後のデータは凍結し、修正は再年調として新バージョンを作成する
- スキーマの柔軟性: Draft 状態のフォームは開発中にフィールドの追加・変更が頻繁に発生する
- 7年間の保持: 法定保存期間中、過去の申告状態を参照可能にする(→ CR-005 データの不変性と再現性)
決定
申告データの履歴管理にスナップショット方式を採用する。画面遷移(自動保存)ごとに申告データ全体の状態を JSONB スナップショットとして保存する(→ DM-406 DeclarationSnapshot)。
理由
- モードレス自動保存との親和性: 画面遷移のたびにスナップショットを生成するだけでよく、差分計算やイベント生成の複雑さがない。自動保存のレスポンスタイム要件(≤ 300ms)を満たしやすい
- JSONB によるスキーマ柔軟性: Draft 状態のスナップショットを JSONB で保存することで、フォームのフィールド追加・変更に対してマイグレーション不要で追従できる。UI の進化速度に合わせた開発が可能
- Fact/Judgment 分離との整合: 事実データ(家族属性、保険契約等)はスナップショットとして記録し、判定結果(税法カテゴリ該当有無、控除額)は Tax Calculation Core で常に再計算する。スナップショットは「事実の記録」であり、「判定の記録」ではない
- Finalized 後の不変性の自然な表現: スナップショットは本質的に不変(immutable)であり、Finalized 状態との相性がよい。確定後に新しいスナップショットを追加しないことで凍結を表現できる
- 復元の単純さ: 特定のスナップショットを選択するだけで過去の状態を参照・復元できる。イベントのリプレイや部分適用は不要
却下した代替案
| 代替案 | 却下理由 |
|---|---|
| イベントソーシング(全操作をイベントとして記録) | 税額計算は確定時点のスナップショットに対する純粋関数であり、イベントリプレイは不要(→ ADR-001)。イベントスキーマの進化管理コストが高い |
| 差分保存(前回からの変更差分のみ記録) | 復元時に差分の逐次適用が必要で処理が複雑。破損した差分があると以降の全履歴が復元不可になるリスクがある |
| リレーショナルテーブルの履歴テーブル(Temporal Table) | Draft 状態ではスキーマが流動的であり、カラム追加のたびに履歴テーブルもマイグレーションが必要。JSONB の柔軟性が失われる |
トレードオフ・リスク
- ストレージ消費: スナップショット方式は差分保存より多くのストレージを消費する。ただし、申告データ1件あたりのスナップショットサイズは約 50KB であり、法定保持期間(7年)を考慮しても実用的な範囲内(→ PE-003 容量計画)
- フィールドレベルの変更追跡の困難さ: 「どのフィールドがいつ変更されたか」をスナップショット間の比較で導出する必要がある。ただし、監査ログ(→ DM-901 AuditLog)で操作レベルの追跡を補完する
- JSONB と正規化テーブルの二重管理: Draft 状態では JSONB スナップショット、Submitted 以降は正規化リレーショナルテーブル(FamilyMember、InsuranceDeduction 等)にも展開する。この二重管理は Submitted 遷移時のバリデーション・正規化処理で整合性を担保する
- スナップショット間のマージ不可: 2つのスナップショットの「いいとこ取り」はできない。楽観的排他制御(→ AV-002 データ整合性)でコンフリクトを検出し、ユーザーに選択を促す
結果
DeclarationSnapshotエンティティ(JSONB)を定義し、画面遷移ごとに申告データ全体の状態を保存- スナップショットには操作者(従業員/管理者)、日時、スナップショットバージョンを記録
- Submitted 遷移時に FluentValidation で全ビジネスルールを検証し、正規化テーブルに展開
- Finalized 後は新規スナップショットの生成を禁止(再年調は新バージョンとして別 Declaration を作成)
docs/design/backend/cqrs.mdにスナップショット方式の CQRS 構成図を記載