ドメイン層
ドメインモデルを表現するもの(ドメインオブジェクト)
- 値オブジェクト
- エンティティ
- ドメインイベント
値オブジェクト
システム内で扱う値のルールをクラスとして表現したものです。
名前や年齢、ユーザー番号などシステムで扱われる値にはそれぞれ守るべきルールがありそれに違反する値はシステム内に存在させないようにする必要があります。
そのルールをクラス内に閉じ込め、再利用しやすくしたものが値オブジェクトです。
値オブジェクトの特徴
完全コンストラクタにより安全性を保つ
インスタンスを生成した時点で完全な状態となるような初期化ロジックを組み、それ以降で値が書き変わらないようにします。
この考え方を完全コンストラクタといい、これによりオブジェクトが不正な状態で存在することがなくなります。
下の例だと、年齢オブジェクトはインスタンスが生成されたタイミングでバリデーションが実施され不正な値を弾き、さらにset関数がないため値の再設定によってさらなる不正値の設定を防ぎます。
export class Age {
private readonly value: number;
// コンストラクタでバリデーションを行ってから初期化
private constructor(value: number) {
if (value < 0 || value > 150) {
throw new Error("年齢は 0 から 150 である必要があります。");
}
this.value = value;
}
// ファクトリーメソッドでインスタンスを作成
public static create(value: number): Age {
return new Age(value);
}
// 年齢を取得するゲッター
public getValue(): number {
return this.value;
}
// 等価性の判定
public equals(other: Age): boolean {
return this.value === other.value;
}
}
型を使ってコードを分かりやすくする
プリミティブ型のみだけで書いたコードは思わぬバグを生むことがあります。
例えば下記のように金額と数量を扱う場合に引数として渡す値が反対になってしまっていた場合、致命的なバグを発生させてしまいます。
amount(quantity:number, unitPrice:number){
if(quantity > discountCreteria){
return discountAmount(quantity, unitPrice)
}
return quantity * unitPrice
}
こういうことを防ぐために用途を限定した独自の「型」を使ってコードの意図を分かりやすくすべきです。
amount(quantity:Quantity, unitPrice:Money){
if(quantity.isDiscountable()){
return discountAmount(quantity, unitPrice)
}
return unitPrice.multiply(quantity.value())
}
number型の代わりにMoney型とQuantity型を使うことでコードの意図が具体的になり、引数の渡し間違いを防ぐ安全なプログラムになります。
ロジックの散在を防ぐことができる
値オブジェクトにする基準
上記の通り、値をプリミティブ型で扱わず値オブジェクトにすることでシステムにメリットがあることがわかりました。
ですが、値を片っ端から値オブジェクトにするのは骨が折れる作業ですし、本領を発揮しない場合もあります。
下記は値オブジェクトにすべき値の特徴です。
ビジネスルールがあるか
オブジェクトが独自のバリデーションルールやドメイン特有のビジネスロジック (メールアドレスの形式、電話番号の形式など) を必要とする場合、それらのデータの正確性が保たれます。
例えばめ
再利用
システム内で頻出する値であれば