Skip to content

第九章:領域驅動設計 (DDD) 理論與實踐

9.1 引言:為什麼需要 DDD?

在複雜的企業軟體開發中,最大的挑戰往往不是技術,而是業務邏輯的複雜性。領域驅動設計 (DDD) 提供了一套方法論,幫助我們將複雜的業務需求轉化為清晰的軟體模型。

ABP Framework 是一個 Opinionated (有主見的) 框架,它將 DDD 的最佳實踐直接內建在架構中。本章將帶您理解這些核心概念。


9.2 核心概念解析

1. 實體 (Entity)

實體是具有 唯一識別碼 (Identity) 的物件。即使兩個實體的屬性完全相同,只要 ID 不同,它們就是不同的物件。

  • 例子User (使用者), Order (訂單)。
  • ABP 實作:繼承 Entity<TKey>

2. 值物件 (Value Object)

值物件沒有唯一識別碼,它是透過 屬性值 來定義的。如果兩個值物件的所有屬性都相同,它們就被視為相等。值物件通常是 不可變的 (Immutable)

  • 例子Address (地址 - 包含城市、街道), Money (金額 - 包含數值、幣別)。
  • ABP 實作:繼承 ValueObject

3. 聚合 (Aggregate) 與 聚合根 (Aggregate Root)

聚合是一組相關物件的集合,它們被視為一個修改的單元。

  • 聚合根:是聚合中唯一允許外部直接引用的實體。外部物件只能透過聚合根來存取聚合內部的其他實體。
  • 原則
    • 交易邊界:一個交易通常只修改一個聚合。
    • 級聯刪除:刪除聚合根時,聚合內的所有物件也應被刪除。
  • ABP 實作:繼承 AggregateRoot<TKey>

4. 領域服務 (Domain Service)

當某個業務邏輯不屬於任何單一實體或值物件時,我們將其放入領域服務中。

  • 例子:轉帳 (涉及兩個帳戶實體)。
  • ABP 實作:繼承 DomainService

9.3 聚合設計原則

設計良好的聚合是 DDD 成功的關鍵。

1. 定義值物件 (Address)

csharp
public class Address : ValueObject
{
    public string City { get; private set; }
    public string Street { get; private set; }

    private Address() { } // ORM 需要

    public Address(string city, string street)
    {
        City = city;
        Street = street;
    }

    protected override IEnumerable<object> GetAtomicValues()
    {
        yield return City;
        yield return Street;
    }
}

2. 定義聚合根 (Order)

csharp
public class Order : FullAuditedAggregateRoot<Guid>
{
    public Guid CustomerId { get; private set; } // 引用其他聚合
    public Address ShippingAddress { get; private set; } // 值物件
    public List<OrderItem> Items { get; private set; } // 聚合內部實體
    public decimal TotalPrice { get; private set; }

    private Order() { }

    public Order(Guid id, Guid customerId, Address address) : base(id)
    {
        CustomerId = customerId;
        ShippingAddress = address;
        Items = new List<OrderItem>();
    }

    // 業務方法:新增項目
    public void AddItem(Guid productId, decimal price, int quantity)
    {
        if (quantity <= 0) throw new BusinessException("Order:InvalidQuantity");

        var existingItem = Items.FirstOrDefault(i => i.ProductId == productId);
        if (existingItem != null)
        {
            existingItem.IncreaseQuantity(quantity);
        }
        else
        {
            Items.Add(new OrderItem(Id, productId, price, quantity));
        }

        RecalculateTotal();
    }

    private void RecalculateTotal()
    {
        TotalPrice = Items.Sum(x => x.Price * x.Quantity);
    }
}

3. 定義聚合內部實體 (OrderItem)

csharp
public class OrderItem : Entity<Guid>
{
    public Guid OrderId { get; private set; }
    public Guid ProductId { get; private set; }
    public decimal Price { get; private set; }
    public int Quantity { get; private set; }

    internal OrderItem(Guid orderId, Guid productId, decimal price, int quantity)
        : base(Guid.NewGuid())
    {
        OrderId = orderId;
        ProductId = productId;
        Price = price;
        Quantity = quantity;
    }

    internal void IncreaseQuantity(int quantity)
    {
        Quantity += quantity;
    }
}

注意 internal 建構子與方法,這強迫外部只能透過 Order 聚合根來操作 OrderItem


9.5 領域事件 (Domain Events)

當領域中發生了重要的事情時,我們發布領域事件。

1. 定義事件

csharp
public class OrderCreatedEvent
{
    public Guid OrderId { get; set; }
    public Guid CustomerId { get; set; }
}

2. 發布事件 (在聚合根中)

csharp
public class Order : AggregateRoot<Guid>
{
    public void Complete()
    {
        // ... 狀態變更邏輯
        AddDomainEvent(new OrderCreatedEvent { OrderId = Id, CustomerId = CustomerId });
    }
}

ABP 會在 SaveChangesAsync 被呼叫時自動發送這些事件。

3. 訂閱事件 (Handler)

csharp
public class OrderCreatedEventHandler : IDistributedEventHandler<OrderCreatedEvent>, ITransientDependency
{
    public async Task HandleEventAsync(OrderCreatedEvent eventData)
    {
        // 發送 Email 通知客戶
        await _emailSender.SendAsync(eventData.CustomerId, "訂單成立通知", "...");
    }
---

## 9.7 總結

本章介紹了 DDD 的核心戰術模式。

- **聚合** 是資料一致性的守門員。
- **值物件** 讓模型更具表達力。
- **領域事件** 實現了系統的解耦。

在下一章,我們將深入探討 **領域服務 (Domain Services)**  **規約模式 (Specification Pattern)**,進一步完善我們的領域層實作

---

**參考資源**:

- [ABP 領域驅動設計文件](https://docs.abp.io/en/abp/latest/Domain-Driven-Design)

Released under the MIT License.