Skip to content

モードレス自動保存とデータ整合性

問題: NOT NULL 制約と自動保存の矛盾

TASHIKA はモードレス自動保存(GL-007)を採用しており、画面遷移時に自動的にデータを保存する。 一方、エンティティ設計の原則として カラムは原則 NOT NULL とし、不完全なデータを NULL で表現しない。

この2つの方針は、複数画面にまたがる入力で矛盾する:

例: 配偶者情報の入力が3画面に分かれている場合

画面1: 氏名・生年月日を入力 → 自動保存
画面2: 所得見積額を入力     → 自動保存
画面3: 障害者区分を入力     → 自動保存

画面1の時点で正規化テーブルに保存しようとすると、
画面2・3のカラムが NOT NULL 制約に違反する。

解決策: JSONB Draft + 正規化テーブル Submitted

Draft 状態の自動保存は 申告スナップショット(JSONB)に対して行い、 正規化テーブルへの展開は Draft → Submitted のライフサイクル遷移時に行う。

Draft 状態:
  ┌─────────────────────────────────────┐
  │ DeclarationSnapshot (JSONB)         │
  │  → スキーマレスな構造               │
  │  → 部分的・不完全なデータを許容     │
  │  → 画面遷移ごとに自動保存           │
  │  → バリデーションは UI レベルのみ   │
  └─────────────────────────────────────┘

Draft → Submitted 遷移時:
  ┌─────────────────────────────────────┐
  │ JSONB → 正規化テーブルへの展開      │
  │  → FluentValidation で全項目を検証  │
  │  → NOT NULL 制約を完全に満たす      │
  │  → 検証失敗 → Submitted 不可        │
  └─────────────────────────────────────┘

Submitted 以降:
  ┌─────────────────────────────────────┐
  │ 正規化テーブル                       │
  │  → FamilyMember, InsuranceDeduction │
  │     等の各テーブルにデータが存在     │
  │  → 全カラム NOT NULL(原則)         │
  │  → RLS、FK 等の制約が有効           │
  └─────────────────────────────────────┘

この設計の利点

  1. NOT NULL 原則との両立

    • 正規化テーブルのカラムは原則 NOT NULL を維持できる
    • 不完全なデータは JSONB の中にのみ存在し、正規化テーブルに漏れない
  2. 入力順序の自由度

    • 従業員は任意の順序で画面を進められる(人的控除→保険料控除でも、逆でも可)
    • 途中で中断し、後日再開しても問題ない
    • JSONB はスキーマレスなので、部分的な更新が自然にできる
  3. データモデルが UI に引きずられない

    • 「画面1用テーブル」「画面2用テーブル」のような分割は不要
    • 正規化テーブルの設計はドメインモデルに基づく(UI の画面分割とは独立)
    • 画面構成が変わっても、正規化テーブルの構造は変わらない
  4. 既存の CQRS スナップショット方式との親和性

    • 申告スナップショット(DM-406)は既に JSONB での状態保存として設計されている
    • Draft 中の自動保存も同じ 申告スナップショット の仕組みに乗せられる
    • スナップショットの蓄積→確定後凍結のライフサイクルと整合
  5. バリデーションの二段階化

    • Draft 中: UI レベルの軽いバリデーション(形式チェック、即座のフィードバック)
    • Submitted 遷移時: FluentValidation による網羅的バリデーション(業務ルール、整合性チェック)
    • 「入力途中でエラーを出さない」UX と「提出データの完全性保証」を両立

制約と注意点

  • Draft 中の 税額計算エンジン 実行: Draft 中もリアルタイムで控除額を仮計算し UI に表示するが、 入力は JSONB から読み取る。計算結果は参考値であり、確定的な TaxCalculationResult は Submitted 以降で生成する
  • JSONB のスキーマ進化: JSONB はスキーマレスだが、アプリケーション側で読み取る際のデシリアライズ互換性は管理が必要。 → バージョンフィールドを JSONB 内に持たせ、マイグレーション時の互換性を確保する
  • 検索・集計: Draft 状態のデータは JSONB 内にあるため、SQL での検索・集計が困難。 管理者向けの進捗ダッシュボード等では JSONB 内の特定フィールドに GIN インデックスを設定するか、 Read Model に投影する設計が必要