Open9

DDDの値オブジェクトを説明するのに年齢を例にすると分かりやすいかも

前田みお前田みお

プリミティブな値で実装する問題

  • 不正な値を代入できてしまう
    • マイナスの値
    • 少数
  • 不正な値を持たないようにする処理が散在してしまう
  • 不要な振る舞いができてしまう
    • 四則演算など
  • ドメインロジックが散在してしまう
    • 例:成年かどうかのロジックはどこに書く?
前田みお前田みお

不正な値を存在させない

コンストラクター内で不正な値を生成しようとする場合にエラーを投げるようにする

これで生成された Age オブジェクトは必ず不正な値ではないことが確立できる

class Age {
  public constructor(value: number) {
    if (!Number.isInteger(value)) throw new Error();
    if (value < Age.MIN_VALUE) throw new Error();

    this.value = value;
  }

  private readonly value: number;
  private static readonly MIN_VALUE: number = 0;
}
前田みお前田みお

ドメインオブジェクト自体にドメインオブジェクトの処理を持たせられるので、バリデーションが散在しない

前田みお前田みお

業務領域によっては変化する可能性がある

例えば子ども用の保険の場合、生まれる前に加入できるため、マイナスの値を持つ場合がある

生まれる2年前から契約可能な場合は MIN_VALUE の値が -2 といった具合になる

前田みお前田みお

また小児向け医療関係の場合、月齢を保持したい可能性もある

この場合はプリミティブの値の年齢と月齢の2つをラップして値オブジェクトを実装する

前田みお前田みお

不要な振る舞いを定義しない

プリミティブな値をクラスでラップしているので、年齢オブジェクトとしては不要な値の操作を禁じることが可能
不要な操作が起きないので、安全に値を扱いやすくなる

const age = new Age(20);

age + 1; // Error
前田みお前田みお

必要なドメインロジックを持たせられる

class Age {
  public isAdult(): boolean {
    return this.value > 18;
  }
}
前田みお前田みお

永続化

ドメインオブジェクトをそのまま永続化する必要はない例として

年齢は生年月日からある特定の日時(大体は現在日時)から計算される

年齢オブジェクトにファクトリーメソッドを生やして、生年月日オブジェクトから計算して生成させる

class Age {
  public static valueOf(birthday: Birthday): Age {
    // 計算して返す
  }
}

永続化する際は生年月日オブジェクトを永続化して、取得する際に再生成させる

前田みお前田みお

バリデーションはどこに書く?

結論としては必要なレイヤーで必要な分だけ

  • ドメイン層
    • ドメインオブジェクトとして必要な検証
    • 前述のコンストラクター内で不正な値を生成させないようにする
    • 上限値については(業務領域によって変化しそうだが)、寿命はさておき基本的にはないことがおおい
  • アプリケーション層
    • アプリケーションとして扱うことができる値かどうか
    • 言語使用によって取りえる整数の最大値が上限
  • UI 層
    • 入力補助としてのバリデーション
    • 上限値をもってもいいかもというところ
      • 寿命的に200歳以上は入力ミスだろうとしてもいいところ