共用方式為


設計微服務領域模型

小提示

此內容是適用於容器化 .NET 應用程式的電子書.NET 微服務架構摘錄,可在 .NET Docs 或免費下載的 PDF 中取得,可脫機讀取。

.NET 微服務架構的容器化 .NET 應用程式電子書封面縮圖。

為每個商務微服務或限定內容定義一個豐富的領域模型。

您的目標是為每個業務微服務或界限上下文(BC)建立單一統一的領域模型。 不過,請記住,BC 或商務微服務有時可能由數個共用單一網域模型的實體服務所組成。 領域模型必須擷取它所代表之單一限定內容或商務微服務的規則、行為、商務語言和條件約束。

網域實體模式

實體代表領域物件,主要由其身分識別、持續性和持續性所定義,而不只是由組成它們的屬性所定義。 正如 Eric Evans 所說,“主要由其身分識別定義的對象稱為實體。實體在領域模型中非常重要,因為它們是模型的基底。 因此,您應該仔細識別並設計它們。

實體的身分識別可以跨多個微服務或限定內容。

相同的身分識別(也就是相同的 Id 值,雖然可能不是相同的領域實體)可以跨多個限定內容或微服務進行模型化。 不過,這並不表示在多個限定內容中會以相同屬性和邏輯來實作相同的實體。 相反地,每個系結內容中的實體都會將其屬性和行為限制在該限定內容網域中所需的屬性和行為。

例如,購買者實體可能擁有個人資料或身份識別微服務中用戶實體中定義的大部分屬性,包括身份。 但訂購微服務中的購買者實體可能會有較少的屬性,因為只有特定購買者數據與訂單程序相關。 每個微服務或限定內容的內容會影響其領域模型。

網域實體除了實作數據屬性之外,還必須實作行為。

DDD 中的定義域實體必須實作與實體數據相關的領域邏輯或行為(記憶體中存取的物件)。 例如,作為訂單實體類別的一部分,您必須將商業規則和作業實作為工作的方法,例如新增訂單專案、數據驗證和總計算。 實體的方法會負責實體的不變和規則,而不是讓這些規則分散到應用層。

圖 7-8 顯示定義域實體,該實體不僅會實作數據屬性,而且實作具有相關領域邏輯的作業或方法。

顯示網域實體模式的圖表。

圖 7-8。 實作數據加上行為的網域實體設計範例

領域模型實體會透過方法實作行為,也就是說,它不是「貧血模型」。 當然,有時候您可以有實體,這些實體不會實作任何邏輯做為實體類別的一部分。 如果子實體沒有任何特殊邏輯,則匯總內的子實體可能會發生這種情況,因為大部分邏輯都是在匯總根目錄中定義。 如果您的複雜微服務在服務類別中實作邏輯,而非在領域實體實作,您可能會落入貧血型領域模型,下節將進一步說明。

豐富的領域模型與貧血的領域模型

AnemicDomainModel 的帖子中,Martin Fowler 這樣描述貧血的領域模型:

貧血型領域模型的基本徵兆是,它起初看起來像真實模型。 有對象,許多以定義域空間中的名詞命名,而且這些物件會與真實領域模型所擁有的豐富關聯性和結構連接。 問題在於當你觀察行為時,你會意識到這些對象上幾乎沒有任何行為,使它們僅僅是包含存取方法的包包。

當然,當您使用貧血性領域模型時,這些數據模型將會從一組服務物件(傳統上稱為 業務層)中使用,這組物件包含了所有的領域或商業邏輯。 商務層位於數據模型之上,並使用數據模型做為數據。

貧血型領域模型只是一種程序樣式設計。 Anemic 實體物件不是真正的對象,因為它們缺少行為(方法)。 它們只會保存資料屬性,因此它不是面向對象設計。 藉由將所有行為放入服務物件(商務層),您基本上會得到雜亂無章的代碼交易腳本,因此您會失去領域模型的優勢。

無論您的微服務或有界上下文非常簡單(例如 CRUD 服務),僅具數據屬性的實體物件形式的貧血領域模型可能已足夠,而可能不值得實施更複雜的 DDD 模式。 在此情況下,這只是一個持久化模型,因為您已特意創建一個只有 CRUD 數據的資料實體。

這就是為什麼微服務架構非常適合根據每個限定內容而採用多架構方法的原因。 例如,在 eShopOnContainers 中,訂購微服務會實作 DDD 模式,但目錄微服務是簡單的 CRUD 服務,則不會。

有些人說,anemic 領域模型是一種反模式。 這真的取決於您正在實作的內容。 如果您要建立的微服務足夠簡單(例如 CRUD 服務),使用貧血領域模型並不是一種反模式。 不過,如果您需要處理具有許多不斷變更的商業規則的微服務網域複雜性,那麼貧乏領域模型可能是該微服務或 Bounded Context 的反模式。 在此情況下,將它設計為包含數據加行為的實體以及實作其他 DDD 模式(匯總、值物件等)的豐富模型,對於這類微服務的長期成功可能會有很大的好處。

其他資源

值物件模式

正如埃裡克·埃文斯所指出的,“許多對象沒有概念性識別。 這些物件描述事物的某些特性。

實體需要身分識別,但系統中有許多物件不需要,例如值物件模式。 值物件是一個沒有概念身份,但描述領域方面的物件。 這些是您具現化來代表只關心您暫時的設計元素的物件。 你關心他們 是什麼 ,而不是他們 是誰 。 範例包括數位和字串,但也可以是較高層級的概念,例如屬性群組。

微服務中的實體可能不是另一個微服務中的實體,因為在第二個案例中,限定內容可能有不同的意義。 例如,電子商務應用程式中的位址可能根本沒有身分識別,因為它可能只代表個人或公司客戶配置檔的屬性群組。 在此情況下,位址應該分類為值物件。 不過,在電力公用事業公司的應用程式中,客戶位址對於商務領域可能很重要。 因此,地址必須具有身分識別,因此計費系統可以直接連結至位址。 在此情況下,位址應分類為網域實體。

具有名稱和姓氏的人通常是實體,因為某個人具有身分識別,即使名稱和姓氏與另一組值相吻合,例如,如果這些名稱也參考不同的人。

值物件很難在關係資料庫和 ORM 中管理,例如 Entity Framework (EF),而在文件導向的資料庫中,它們更容易實作和使用。

EF Core 2.0 和更新版本包含 「擁有的實體 」功能,可讓您更輕鬆地處理值物件,如下所述。

其他資源

匯總模式

領域模型包含不同數據實體和程式的叢集,這些叢集可以控制重要的功能領域,例如訂單履行或清查。 更精細的 DDD 單位是匯總,其描述可視為一致單位的叢集或實體和行為群組。

您通常會根據所需的交易來定義匯總。 經典的例子是一個包含訂單項目清單的訂單。 訂單項目通常是實體。 但它將會是訂單聚合中的子實體,其中也包含訂單實體作為其根實體,通常稱為聚合根。

識別合計有時候會很困難。 匯總是一組必須一致的物件,但您不只能挑選一組物件,併為其加上匯總標籤。 您必須從定義域概念開始,並思考與該概念相關的最常見交易中使用的實體。 需要交易一致的實體就是構成匯總的實體。 考慮交易操作可能是識別聚合的最佳方式。

匯總根或根實體模式

匯總是由至少一個實體所組成:匯總根目錄,也稱為根實體或主要實體。 此外,它可以有多個子實體和值物件,且所有實體和物件一起運作以實作必要的行為和交易。

匯總根目錄的目的是確保匯總的一致性;它應該是透過匯總根類別中的方法或作業,更新匯總的唯一進入點。 您應該只透過匯總根目錄對匯總內的實體進行變更。 這是匯總的一致性守護者,考慮您可能需要在匯總中遵守的所有不變和一致性規則。 如果您獨立變更子實體或值對象,匯總根目錄無法確定匯總處於有效狀態。 這就像一張桌腳不穩的桌子。 維護一致性是匯總根目錄的主要用途。

在圖 7-9 中,您可以看到範例匯總,例如購買者匯總,其中包含單一實體(匯總根購買者)。 順序匯總包含多個實體和值物件。

比較買家匯總和訂單匯總的圖表。

圖 7-9。 具有多個或單一實體的匯總範例

DDD 領域模型是由匯總所組成,匯總只能有一個實體或多個實體,也可以包含值物件。 請注意,購買者聚合可能會有其他子實體,取決於您的領域,如同 eShopOnContainers 參考應用程式中的訂購微服務一樣。 圖 7-9 僅是說明買家有單一實體的案例,作為只包含根聚合的聚合範例。

為了維持匯總的分隔,並保留它們之間的清楚界限,DDD 定義域模型最好不允許在匯總之間直接巡覽,而且只有外鍵 (FK) 字段,如 eShopOnContainers 中的 排序微服務領域模型 所實作。 Order 實體只有買家的外鍵欄位,但沒有 EF Core 導覽屬性,如下列程式代碼所示:

public class Order : Entity, IAggregateRoot
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    private int? _buyerId; // FK pointing to a different aggregate root
    public OrderStatus OrderStatus { get; private set; }
    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
    // ... Additional code
}

識別和處理群集需要研究和經驗。 如需詳細資訊,請參閱下列其他資源清單。

其他資源