導入
DDD(ドメイン駆動設計)の出発点は「業務の言葉とコードの言葉を一致させる」こと。その第一歩として、モデルの基本パーツエンティティ(IDで識別)と値オブジェクト(値そのものが本体)を区別できるようになりましょう。
図解
flowchart TB
Q{"属性が全部同じ2つを<br/>区別する必要がある?"}
Q -->|ある(同姓同名でも別人)| E["エンティティ<br/>IDを持つ・状態が変化<br/>例: Member / Order"]
Q -->|ない(1000円はどれも1000円)| V["値オブジェクト<br/>IDなし・値で等価・不変<br/>例: Money / Email"]
style E fill:#e1f5fe
style V fill:#e8f5e9
サンプル
// 値オブジェクト: 金額を int で持たず Money 型にして「不正な金額は存在できない」ようにする
var price = new Money(1000);
var shipping = new Money(500);
Console.WriteLine(price.Add(shipping)); // 1500円
// record なので「値が同じなら等しい」が自動で手に入る
Console.WriteLine(new Money(1000) == new Money(1000)); // True
// エンティティ: IDで同一性を判断(名前が変わっても同じ会員)
var m1 = new Member(1, "Taro");
var m2 = new Member(1, "Taro(改名後)");
Console.WriteLine(m1.IsSameAs(m2)); // True(IDが同じ)
record Money
{
public int Amount { get; }
public Money(int amount)
{
if (amount < 0) throw new ArgumentException("金額は0以上");
Amount = amount;
}
public Money Add(Money other) => new(Amount + other.Amount);
public override string ToString() => $"{Amount}円";
}
class Member
{
public int Id { get; }
public string Name { get; private set; }
public Member(int id, string name) { Id = id; Name = name; }
public bool IsSameAs(Member other) => Id == other.Id; // 同一性はID
}
- 値オブジェクト: 値が本体・不変・生成時にバリデーション。
recordが最適 - エンティティ: IDで同一性を判断。属性が変わっても同じ個体
- 金額を
intでなくMoney型にすると「不正な金額」を型で防げる
演習
var email = new Email("taro@example.com");
Console.WriteLine(email.Value);
// TODO: Email 値オブジェクトを record で定義してください
// ・Value(string) を持つ
// ・生成時 "@" を含まなければ ArgumentException を投げる
___
- 期待される出力:
taro@example.com
ヒント1を見る
record Email { public string Value { get; } public Email(string value) { if (!value.Contains("@")) throw new ArgumentException(); Value = value; } }
ヒント2を見る
Moneyの構造をまね、検証条件をContains("@")に変えます
まとめ
- 値オブジェクトは値が本体・不変・生成時バリデーション(record向き)
- エンティティはIDで同一性を判断する
- 業務の概念を型にすることで不正状態を防ぐ
次回: 業務ルールをどこに書くか——ドメインの振る舞いです。