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

常見範例對照:

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

實戰補充

💡資深開發者筆記

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

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

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

理解測驗

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

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

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

重點整理

💡一句話記住

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

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

你可能也想看

Ubiquitous LanguageValue Object(值物件)

按 ← → 鍵切換課程