Entity(實體)

是什麼?

Entity 是 DDD 中具有**唯一身份識別(Identity)**的領域物件。兩個 Entity 即使所有屬性都相同,只要 ID 不同,就是不同的物件;反之,即使屬性全都改了,只要 ID 相同,就是同一個物件。

ℹ️DDD 戰術模式

Entity 是 DDD 戰術設計(Tactical Design)的核心建構塊之一,與 Value Object、Aggregate 並列為最基礎的三個概念。

核心觀念

常見誤區

⚠️常見誤區

誤區一:把 Entity 當成資料庫的 Row。Entity 是領域概念,不是 ORM 的產物。先思考業務需求,再考慮怎麼存。

誤區二:Entity 只有 getter/setter 沒有行為。這叫 Anemic Domain Model(貧血模型),失去了 DDD 的核心價值。Entity 必須封裝業務邏輯。

誤區三:用所有屬性來比較兩個 Entity 是否相同。Entity 的相等性只看 ID。

設計流程

1

識別領域概念

找出業務中需要追蹤身份的物件(如訂單、使用者、帳戶)

2

定義 Identity

決定用什麼作為唯一識別(GUID、流水號、業務編號)

3

封裝業務規則

把相關的業務邏輯放進 Entity,而非散落在 Service 裡

4

實作相等性

覆寫 Equals 和 GetHashCode,只比較 ID

5

管理生命週期

定義 Entity 的狀態轉換規則(如訂單:建立→付款→出貨→完成)

程式碼範例

C# 版本

csharp
// Entity 基底類別
public abstract class Entity<TId> where TId : notnull
{
    public TId Id { get; protected set; }
 
    protected Entity(TId id)
    {
        Id = id;
    }
 
    public override bool Equals(object? obj)
    {
        if (obj is not Entity<TId> other) return false;
        if (ReferenceEquals(this, other)) return true;
        return Id.Equals(other.Id);
    }
 
    public override int GetHashCode() => Id.GetHashCode();
 
    public static bool operator ==(Entity<TId>? left, Entity<TId>? right)
        => Equals(left, right);
    public static bool operator !=(Entity<TId>? left, Entity<TId>? right)
        => !Equals(left, right);
}
 
// 具體 Entity:訂單
public class Order : Entity<Guid>
{
    public Customer Customer { get; }
    public OrderStatus Status { get; private set; }
    private readonly List<OrderLineItem> _lineItems = new();
    public IReadOnlyList<OrderLineItem> LineItems => _lineItems.AsReadOnly();
 
    public Order(Guid id, Customer customer) : base(id)
    {
        Customer = customer;
        Status = OrderStatus.Created;
    }
 
    public void AddLineItem(Product product, int quantity)
    {
        if (Status != OrderStatus.Created)
            throw new InvalidOperationException("Cannot modify a placed order.");
        _lineItems.Add(new OrderLineItem(product, quantity));
    }
 
    public void Place()
    {
        if (!_lineItems.Any())
            throw new InvalidOperationException("Order must have at least one line item.");
        Status = OrderStatus.Placed;
    }
}
 
// 使用
var order1 = new Order(Guid.NewGuid(), customer);
var order2 = new Order(order1.Id, customer);
Console.WriteLine(order1 == order2); // true — 同一個 ID 就是同一張訂單

TypeScript 版本

typescript
// Entity 基底類別
abstract class Entity<TId> {
  constructor(public readonly id: TId) {}
 
  equals(other: Entity<TId>): boolean {
    if (this === other) return true;
    return this.id === other.id;
  }
}
 
// 具體 Entity:訂單
class Order extends Entity<string> {
  private lineItems: OrderLineItem[] = [];
  private status: OrderStatus = OrderStatus.Created;
 
  constructor(id: string, public readonly customer: Customer) {
    super(id);
  }
 
  addLineItem(product: Product, quantity: number): void {
    if (this.status !== OrderStatus.Created) {
      throw new Error("Cannot modify a placed order.");
    }
    this.lineItems.push(new OrderLineItem(product, quantity));
  }
 
  place(): void {
    if (this.lineItems.length === 0) {
      throw new Error("Order must have at least one line item.");
    }
    this.status = OrderStatus.Placed;
  }
}
 
// 使用
const order1 = new Order("ORD-001", customer);
const order2 = new Order("ORD-001", customer);
console.log(order1.equals(order2)); // true

Python 版本

python
from abc import ABC
from dataclasses import dataclass, field
from typing import Generic, TypeVar, List
from uuid import UUID, uuid4
from enum import Enum
 
TId = TypeVar("TId")
 
class Entity(ABC, Generic[TId]):
    def __init__(self, id: TId):
        self._id = id
 
    @property
    def id(self) -> TId:
        return self._id
 
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Entity):
            return False
        return self._id == other._id
 
    def __hash__(self) -> int:
        return hash(self._id)
 
class OrderStatus(Enum):
    CREATED = "created"
    PLACED = "placed"
    SHIPPED = "shipped"
 
class Order(Entity[UUID]):
    def __init__(self, id: UUID, customer: "Customer"):
        super().__init__(id)
        self.customer = customer
        self._status = OrderStatus.CREATED
        self._line_items: List["OrderLineItem"] = []
 
    def add_line_item(self, product: "Product", quantity: int) -> None:
        if self._status != OrderStatus.CREATED:
            raise ValueError("Cannot modify a placed order.")
        self._line_items.append(OrderLineItem(product, quantity))
 
    def place(self) -> None:
        if not self._line_items:
            raise ValueError("Order must have at least one line item.")
        self._status = OrderStatus.PLACED
 
# 使用
order1 = Order(uuid4(), customer)
order2 = Order(order1.id, customer)
print(order1 == order2)  # True

概念圖

Entity(實體)
必須具備→
Identity(唯一識別)
→
生命週期
→
業務行為
→
Value Object(值物件)
→
Aggregate(聚合)

此圖展示 Entity 的三大特徵(Identity、生命週期、業務行為)和它與其他 DDD 建構塊的關係。Entity 的屬性通常是 Value Object(如 Money、Email),而 Entity 本身又是組成 Aggregate 的核心元素。

Entity vs Value Object 決策樹

問自己以下問題來判斷一個概念是 Entity 還是 Value Object:

  1. 這個東西需要被「追蹤」嗎? 你需要說「就是那一個」而不是「任何一個都可以」嗎?
    • 是 → 往 Entity 方向
    • 否 → 往 Value Object 方向
  2. 兩個屬性完全相同的實例,是「同一個」嗎?
    • 不是(如兩個同名同齡的人是不同的人) → Entity
    • 是(如兩張一百元是等值的) → Value Object
  3. 它的屬性會隨時間改變嗎?
    • 會(如訂單狀態從「建立」變「已付款」) → Entity
    • 不會,要改就建新的 → Value Object

常見範例對照:

EntityValue Object
User(使用者帳號)Email(電子郵件地址)
Order(訂單)Money(金額)
Product(商品)Address(地址)
BankAccount(銀行帳戶)DateRange(日期區間)

實戰補充

💡資深開發者筆記

在 C# 專案中,建議建立一個 Entity<TId> 基底類別放在 Domain 層的 SeedWork 或 Common 資料夾。所有 Entity 繼承它,省去重複實作 Equals 和 GetHashCode 的麻煩。

ID 的型別選擇:GUID 適合分散式系統(無需中央分配);自增整數適合單體應用(查詢效率高);業務編號(如 ORD-20260407-001)適合需要人類可讀的場景。三者可以混搭使用。

最重要的一點:Entity 不是 DTO。Entity 要有行為,DTO 只有資料。如果你的 Entity 只有 getter/setter,回頭檢查業務邏輯是不是被放到 Service 裡了。

理解測驗

🤔 兩個 Entity 什麼情況下被視為「相同」?

🤔 以下哪個是 Entity 而不是 Value Object?

🤔 什麼是 Anemic Domain Model(貧血模型)?

重點整理

💡一句話記住

口訣:「Entity 有身分證、有生命、會做事」—— 三個特徵缺一就不是 Entity。

概念說明
IdentityEntity 的唯一識別,決定相等性
生命週期Entity 會被建立、修改、刪除
業務行為Entity 封裝業務邏輯,不只是資料容器
基底類別統一處理 ID 和 Equals/GetHashCode
vs Value ObjectEntity 看 ID,Value Object 看值

你可能也想看

← Ubiquitous LanguageValue Object(值物件) →

按 ← → 鍵切換課程