Skip to main content

「不変条件を持つか」をドメイン判定の基準にしてよいか(古典DDD vs 関数型ドメインモデリング)

TL;DR​

  • 「不変条件・守るべきルールがあるか」を domain の判定基準にするのは、関数型ドメインモデリングの潮流では標準的。
  • 古典DDD(Evans / レイヤード)は「ルールの出どころがビジネスか技術・UIか」で線を引くため、UI由来のルールは domain 外に置く立場。
  • FP側の「domain」は問題領域の型モデル全般を指し、モーダルの開閉状態のような UI 状態も illegal states の例として同列に並ぶ。
  • ただしFP側も「全部型で守れ」とは言っていない。失敗のコストが高いところに投資せよという選別は共通。
  • 結論: 不変条件基準で domains/ に入れるのは一貫している。残る実務的問題は「永続化境界が名前から読めない」ことだけで、これは命名かサブディレクトリで解ける。

このドキュメントの射程​

フロントエンドで domains/ フォルダの入居基準を「不変条件を持つか/守るべきルールがあるか」に置いてよいのか。UI由来の一時状態(編集セッションなど)をそこに含めてよいのか。

// これは domains/ に置いてよいか?
export type TextEdit = Readonly<{
draft: string;
bounds: CanvasBounds; // 編集中は不変
}>;

export const TextEdit = {
create(text: EditableText, bounds: CanvasBounds): TextEdit { /* ... */ },
withDraft(edit: TextEdit, draft: string): TextEdit { /* ... */ },
};

不変条件はある(初期値は現在の文言 / 編集中に矩形は動かない / 生の組み立てを禁止)。だが「保存されるデータ」ではない。

2つの立場​

古典DDD: ルールの出どころで分ける​

  • domain 層は「ドメイン知識を元にしたビジネスのルール・制約・プロセス」を担当する、という役割定義(レイヤードアーキテクチャの標準的整理)。
  • この基準だと、仕様変更の駆動要因がドメインエキスパート/要求側なら domain、実装都合や操作モデル側なら domain 外。
  • 「編集中に矩形が動かない」は後者寄り → domain には入れない、という判定になる。
  • 弱点: キャンバスエディタのように「エディタの操作それ自体がプロダクトのドメイン」な領域では境界が曖昧になる。

関数型ドメインモデリング: 型で表現される意図がすべて​

  • Make illegal states unrepresentable の定義は「データモデルはプログラマの意図を表現するもので、意味をなす値がすべて表現でき、意味をなさない値が一つも表現できないなら意図が捉えられている」。ビジネスかUIかという条件が定義に入っていない。
  • 実例として、cekrem のドメインモデリング記事は illegal states の例に「emailがないのに検証済みのユーザー」「発送済みかつキャンセル済みの注文」「合計がマイナス」と並べて**「モーダルダイアログが閉じているのにアクティブ」**を挙げている。注文とモーダルが同じリストにいる。
  • Elm 界隈も同様。Richard Feldman の Advanced Elm はタブの選択状態を題材に、「同じ情報の source of truth が複数あるとありえない状態に陥る」問題をデータモデリングの変更で解決している。完全にUI状態だが「モデリングの問題」として扱われる。
  • XState 界隈も「UIも有限状態機械として考えられ、この概念でUIが不正な状態を取ることを禁止できる」という位置づけ。

FP側も「全部やれ」とは言っていない​

  • Wlaschin の骨格はあくまでワークフロー中心。ワークフローをパイプラインとして実装し、状態機械はワークフローのモデリングに有用、という構成。
  • cekrem 自身も「エラー状態の結果が重大なとき(ユーザー・ビジネス・セキュリティのいずれにとっても)illegal state をコンパイル時に不可能にする価値がある」=テストと同じで落ちてほしくないところでやれ、と選別を明示。
  • XState 側も「単純な boolean トグル、基本的なフォーム入力、一時的なUI状態は、もっと単純な状態管理のほうが向いていることが多い」と明記。

→ 判定基準に「不変条件があるか」を採るのは妥当だが、それだけだとページネーションやモーダルスタックも全部 domain になる。第二の軸として「壊れたときのコスト」を足すと運用が締まる。

型不変条件壊れたときのコスト
EditableText空文字は保存させない保存データが不正になる
TextEdit編集中に矩形が動かないUIがちらつく

結論と実務的な落としどころ​

  • 不変条件基準で domains/ に入れるのは、関数型の語彙では一貫している。基準を変える必要はない。
  • 残る問題は「domain かどうか」ではなく、永続化境界(document スコープ / session スコープ)が名前から読めないこと。EditableText と TextEdit が同じ棚に並ぶと、後から読む人は「どっちが保存されるのか」が判断できない。
  • 解決策はフォルダを増やすより命名で:
    • TextEditSession / TextEditDraft のように、型名に揮発性を織り込む
    • あるいは domains/editor/ のようなサブディレクトリで分ける
  • チェック方法: domains/ の中身を見て「これ全部そのまま永続化していいか」と自問する。No が混ざる棚なら名前で区別しておく。

まとめ​

  • 「不変条件があるか」は FP 系ドメインモデリングでは正当な判定基準。古典DDDの「ルールの出どころ」基準と食い違うだけで、間違いではない。分けるべきは domain かどうかではなく、永続化されるかどうか。

参考​