Eric Evans 《領域驅動設計》出版十週年,其核心在於「以領域為中心」建模,而非以資料庫為中心。Jimmy Nilsson 推薦此書「如詩般美妙」,作者在研讀並參加 Evans 工作坊後,嘗試將 DDD 技術模式套用於 Entity Framework 專案,分享心得供同好參考。
設計實體類別時,易陷入「EF 如何對應資料表」的思維。除非你的業務就是資料儲存(如 Dropbox),持久化只是輔助性關注點。擁有成熟 ORM 技巧(Fluent API、AutoMapper)者,應先專注領域行為與不變條件,事後再處理對應;極端情況下可建立獨立持久化模型對應舊資料庫。
屬性 setter 設為 private(或 protected),透過具體業務方法修改狀態,而非暴露 SetLastName 等貧血操作。
public class Customer : Contact {
public Customer(string firstName, string lastName, string email) { ... }
internal Customer() { } // EF 所需無參建構子
public void CopyBillingAddressToShippingAddress() { ... }
public void CreateNewShippingAddress(string street, string city, string zip) { ... }
public void CreateBillingInformation(string street, string city, string zip,
string creditCardNumber, string bankName) { ... }
public void SetCustomerContactDetails(string email, string phone, string companyName) { ... }
public string SalesPersonId { get; private set; }
public CustomerStatus Status { get; private set; }
public Address ShippingAddress { get; private set; }
public Address BillingAddress { get; private set; }
public CustomerCreditCard CreditCard { get; private set; }
}
public class Contact : Identity {
public string CompanyName { get; protected set; }
public string EmailAddress { get; protected set; }
public string Phone { get; protected set; }
}
public void SetCustomerContactDetails(string email, string phone, string companyName) {
EmailAddress = email; Phone = phone; CompanyName = companyName;
}
Customer 作為聚合根,負責維護聚合內不變條件(如地址、信用卡的一致性)。公開的是行為,而非屬性。
並非所有子系統都需要完整 DDD。簡單的建立/讀取/更新/刪除(CRUD)場景(如聯絡人記錄),使用一般 EF Code First 類別即可。系統通常混合「核心領域(DDD)」與「支撐子系統(CRUD)」,需明確劃分界定上下文邊界。
傳統追求「重用」導致跨系統共用實體類別與資料表,耦合度極高。DDD 建議:
下一篇將討論「貧血領域模型」vs「豐富領域模型」、單向關聯設計、EF 持久化對應考量等進階主題。
練習建議:檢視現有類別,嘗試將 setter 隱藏、補上語意業務方法(禁用 SetLastName)。