導入
「データだけのクラス+処理は全部サービス」という構造はドメイン貧血症というアンチパターンです。DDDでは業務ルールを、そのデータを持つ本人(エンティティ・値オブジェクト)に書くのが流儀です。
図解
flowchart TB
A["❌ 貧血症<br/>呼び出し側が Point>=500 を判断<br/>(ルールが外に漏れる)"]
B["✅ 本人に聞く<br/>member.CanExchangeGift()<br/>(ルールは本人の中に1か所)"]
A -->|Tell, Don't Ask| B
style B fill:#e8f5e9
サンプル
var member = new Member(1, "Taro", point: 480);
// ✅ ルールは本人に聞く(Tell, Don't Ask)
Console.WriteLine(member.CanExchangeGift()); // False
member.AddPoint(50);
Console.WriteLine(member.CanExchangeGift()); // True
class Member
{
public int Id { get; }
public string Name { get; }
public int Point { get; private set; }
public Member(int id, string name, int point) { Id = id; Name = name; Point = point; }
public void AddPoint(int amount)
{
if (amount <= 0) throw new ArgumentException("ポイントは1以上");
Point += amount;
}
// 「500ポイントで景品交換」という業務ルールが、コードのこの1か所にだけ存在する
public bool CanExchangeGift() => Point >= 500;
}
- 業務ルールは、そのデータを持つ本人(エンティティ・値オブジェクト)に書く
- 呼び出し側に判断ロジックを漏らさない(貧血症を避ける)
- 複数エンティティにまたがり本人に書けないルールだけドメインサービスに置く(乱用注意)
演習
var ticket = new Ticket(usedCount: 9, limit: 10);
Console.WriteLine(ticket.CanUse()); // True
ticket.Use();
Console.WriteLine(ticket.CanUse()); // False
// TODO: Ticket を定義してください
// ・CanUse() → 利用回数が上限未満なら true
// ・Use() → 使えないのに呼ばれたら InvalidOperationException、使えるなら UsedCount を+1
class Ticket
{
public int UsedCount { get; private set; }
public int Limit { get; }
public Ticket(int usedCount, int limit) { UsedCount = usedCount; Limit = limit; }
___
}
- 期待される出力:
TrueFalse(2行)
ヒント1を見る
public bool CanUse() => UsedCount < Limit;
ヒント2を見る
public void Use() { if (!CanUse()) throw new InvalidOperationException(); UsedCount++; }
まとめ
- 業務ルールはデータを持つ本人に書く(貧血症を避ける)
- Tell, Don’t Ask(本人に判断させる)
- 本人に置けないルールだけドメインサービスへ
次回: モデルを守る構造——レイヤードアーキテクチャです。