第19章 Domain入門②:Entity(同一性)👤🪪
この章は「同じものを、ずっと追いかけられる」って感覚をつかむ回だよ〜!😊 ヘキサゴナルの**Core(中心)**で、ちゃんと「同一人物(同一注文)」を扱えるようになると、設計が一気に安定するよ🔷🛡️
1) Entityってなに?ひとことで言うと…📝✨

Entity(エンティティ)= “IDで同じものだと判断する” ドメインの登場人物 だよ👤🪪
たとえばカフェ注文アプリなら☕🧾
- 注文(Order)
- 注文は「時間が経っても同じ注文」で、状態や中身が変わることがある(例:支払い済み、キャンセル、品目追加)
ここがポイント👇 **Entityは「中身が変わっても同じ」**って扱うのが得意😊🔁
2) ValueObjectとの違い(次章の予告も兼ねて)💎👀
ざっくり比較するとこう👇
- Entity:IDで同一性を判断する(同じ注文IDなら同じ注文)👤🪪
- ValueObject:値そのものが同一性(同じ金額なら同じ)💎🔒
例でいうと…
- Order(注文)= Entityっぽい🍩
- Money(金額)= ValueObjectっぽい💴✨(次章でやるよ!)
3) 「同一性」ってどういうこと?🧠🔍
✅ “同じ注文” って、どう決める?
こういう2つのOrderがあったとして…
- 注文A:ラテ1つ、作成直後
- 注文A:ラテ1つ+クッキー追加、支払い済み
中身が変わってても、注文IDが同じなら同じ注文だよね?☕🍪 これが Entityの同一性(Identity) だよ😊🪪✨
4) Entity設計の超基本ルール3つ🧱✨
ルール①:Entityには「ID」がいる🪪
IDがないと「同じもの」として追えないからね😊
ルール②:Entityは「変化する」前提でOK🔁
状態(Status)や明細(Items)が変わっても、同じIDなら同じ存在👤
ルール③:勝手に壊れないように、操作はメソッドで守る🛡️
「プロパティ全部 public set」だと、どこからでも壊せちゃう😭 だから、変更はメソッド経由にしてルールを守るのが基本だよ✅
5) カフェ題材で:Order Entityを作ってみよう☕🧾✨
まずはID型を用意(強い!)💪🪪
「Guidをそのまま使う」でもいいんだけど、ドメインの言葉として OrderId を作ると読みやすさが爆上がりするよ😊📈
public readonly record struct OrderId(Guid Value);
6) Order Entity(最小の形)👤🧾
public enum OrderStatus
{
Created,
Paid,
Cancelled
}
public sealed class Order
{
public OrderId Id { get; }
public OrderStatus Status { get; private set; } = OrderStatus.Created;
private readonly List<OrderItem> _items = new();
public IReadOnlyList<OrderItem> Items => _items;
private Order(OrderId id)
{
Id = id;
}
public static Order CreateNew(OrderId id)
=> new(id);
public void AddItem(MenuItemId menuItemId, int quantity)
{
if (Status != OrderStatus.Created)
throw new InvalidOperationException("作成中の注文にしか追加できません🥲");
if (quantity <= 0)
throw new ArgumentOutOfRangeException(nameof(quantity), "数量は1以上だよ🙂");
_items.Add(new OrderItem(menuItemId, quantity));
}
public void MarkPaid()
{
if (_items.Count == 0)
throw new InvalidOperationException("空の注文は支払えないよ🥲");
if (Status != OrderStatus.Created)
throw new InvalidOperationException("Created状態じゃないと支払えないよ🙂");
Status = OrderStatus.Paid;
}
public void Cancel()
{
if (Status == OrderStatus.Paid)
throw new InvalidOperationException("支払い後はキャンセル不可だよ🥲");
Status = OrderStatus.Cancelled;
}
}
public readonly record struct MenuItemId(Guid Value);
public readonly record struct OrderItem(MenuItemId MenuItemId, int Quantity);
ここでの学びポイント🎯✨
- Idがあるから “同じ注文” を追える🪪
- 状態は変わる(Created→Paid など)🔁
- 変更はメソッドで守る(勝手にPaidにできない)🛡️
7) IDはどう作る?(2026の最新寄りTips)🆔✨
いまの最新 .NET / C#(.NET 10 / C# 14)では、**UUID v7(タイムスタンプを含む形式)**の Guid.CreateVersion7() が使えるよ🕒✨
これは RFC 9562 に沿った v7 を作るメソッドとして案内されてるよ📘 (Microsoft Learn)
(C# 14 が .NET 10 でサポートされる、という公式ページもあるよ📌 (Microsoft Learn))
例:OrderId を v7 で作る🪪🕒
var id = new OrderId(Guid.CreateVersion7());
var order = Order.CreateNew(id);
v7は「時間っぽく並びやすい」性質があるので、DBのインデックス都合で嬉しいことがあるよ😊🗃️ ただし最初は Guid.NewGuid()でも全然OK!焦らなくて大丈夫👌✨
8) 「IDはどこで生成するの?」問題🏭🧠
初心者さん向けの“まずはこれ”の答えは👇😊
- アプリの手順(UseCase)側で作って、Entityに渡す → 分かりやすいし、テストもしやすいよ🧪✨
ヘキサの感覚で言うと🔷
- Coreは「注文を作る」という意思決定をする
- 外部の都合(DBが採番するとか)は後でPort/Adapterに逃がせる🔌🔁
9) Entityの “あるある事故” 🧯😭
😭 事故①:Entity=DBテーブルだと思っちゃう
Entityは「業務の存在」だよ👤 DBの列都合(null許可とか、外部キーとか)をそのまま持ち込むと、Coreが汚れやすい🧼💦
😭 事故②:public set だらけでルールが崩壊
どこからでも Status = Paid とかできると、業務ルールが守れない🥲
メソッドで守るのがコツ🛡️✨
😭 事故③:DTOとEntityがごっちゃになる
DTOは「運ぶ箱」📦 Entityは「ドメインの登場人物」👤 役割が違うよ〜!
10) ミニ演習(手を動かすと一気に腹落ちする!)🧪💪✨
演習A:Orderにルール追加してみよ😊
- Paidのとき
AddItem()できない(もう入れてあるけど確認!) - Cancelledのとき
MarkPaid()できない - Itemsが空なら
MarkPaid()できない
演習B:テストを書いてみよ🧪
- 「空注文は支払えない」を1本テストで保証✅
- 「支払い後キャンセル不可」を1本テストで保証✅
演習C:AIに下書き作ってもらう🤖✨
Copilot/Codexにこう聞くと早いよ👇
- 「Orderエンティティの状態遷移ルール(Created→Paid/Cancelled)をテストで保証したい。xUnitでテスト例を作って」
- 「public set を減らして、メソッドで不変条件を守る形にリファクタして」
ただし最後は人間がチェック!✅ “境界を守れてるか”(DTO/DB都合がCoreに入ってないか)を見てね🚦😊
まとめ🎁✨
- EntityはIDで“同じもの”を追う存在👤🪪
- 中身は変わってOK(状態遷移・明細追加など)🔁
- ルールはEntityのメソッドで守る(壊れにくい!)🛡️
- ID生成はまずシンプルでOK、最新だと
Guid.CreateVersion7()も選べるよ🕒✨ (Microsoft Learn)
次章は ValueObject(値・不変)💎🔒! 「Moneyを型にしてバグを減らす」みたいな、超気持ちいい世界に入るよ〜😊✨