🪪
概念 #DDD #エンティティ #ドメインモデル #同一性 📚 実践ドメイン駆動設計

エンティティ(Entity)

同一性(ID)によって識別されるドメインオブジェクト。ライフサイクルを通じてIDが変わらない

解決する課題

「同じ人」「同じ注文」をシステムが追跡するとき、どの属性が変わっても**「それが同一のものである」と判断する基準**が必要。名前が変わっても同一人物、住所が変わっても同じ注文。

概念

エンティティは、同一性(Identity) によって識別されるドメインオブジェクト。

  • 属性(名前・状態)が変わっても、IDが同じなら同一のエンティティ
  • 時間とともに状態が変化する(ライフサイクルを持つ)
  • 2つのエンティティは ID が等しい場合のみ等しい
# 値オブジェクトとの比較
# エンティティ: IDで等価判定
user_a = User(id="123", name="Alice")
user_b = User(id="123", name="Bob")   # 名前が違っても
assert user_a == user_b               # IDが同じなら同一

# 値オブジェクト: 内容で等価判定
addr_a = Address("東京都", "渋谷区")
addr_b = Address("東京都", "渋谷区")
assert addr_a == addr_b               # 内容が同じなら等しい

IDの設計

Vernon はエンティティのIDの生成方式として4パターンを示す:

方式特徴使いどころ
ユーザー入力人が決める(従業員番号など)業務上の識別子がある場合
アプリケーション生成UUID等で事前生成永続化前にIDが必要な場合
永続化機構が生成DB のオートインクリメントシンプルだが、永続化前IDがない
別コンテキストが割り当て外部システムのIDを使う既存IDとの統合

推奨: UUID をアプリケーション側で生成。DBに依存せずテストしやすく、分散システムでも一意。

エンティティの責務

エンティティは自身の不変条件を守る責任を持つ。状態変更は必ずメソッドを通じて行い、invariant が壊れないようにする。

class Order:
    def __init__(self, order_id: OrderId, customer_id: CustomerId):
        self._id = order_id
        self._customer_id = customer_id
        self._items: list[OrderItem] = []
        self._status = OrderStatus.PENDING

    def add_item(self, product: Product, quantity: int) -> None:
        if self._status != OrderStatus.PENDING:
            raise DomainException("確定済みの注文には商品を追加できません")
        self._items.append(OrderItem(product, quantity))

    @property
    def id(self) -> OrderId:
        return self._id

値オブジェクトとの使い分け

エンティティ値オブジェクト
等価の基準ID値の内容
変更可能性可変(状態が変わる)不変(変更したら新しいオブジェクト)
注文、ユーザー、商品金額、住所、メールアドレス

迷ったら値オブジェクトを選ぶ。エンティティはライフサイクル管理のコストが高い。

関連概念

出典: 実践ドメイン駆動設計(Vaughn Vernon)第5章