共用方式為


設計 DDD 導向的微服務

小提示

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

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

領域驅動設計 (DDD) 提倡根據業務的實際,根據您的使用案例進行模型化。 在建置應用程式時,DDD 將問題視為不同的領域。 它會將獨立問題區域描述為系結內容(每個限定內容與微服務相互關聯),並強調共同語言來討論這些問題。 它也建議許多技術概念和模式,例如具有豐富模型的領域實體(無 非麥克風領域模型)、值對象、匯總和匯總根(或根實體)規則,以支持內部實作。 本節介紹這些內部模式的設計和實作。

有時候,這些 DDD 技術規則和模式被視為學習曲線陡峭的障礙,阻礙實施 DDD 方法。 但重要的部分不是模式本身,而是組織程序代碼,使其符合商務問題,並使用相同的商務術語(無處不在的語言)。 此外,只有在您實作具有重要商務規則的複雜微服務時,才應該套用 DDD 方法。 更簡單的責任,例如 CRUD 服務,可以使用更簡單的方法來管理。

在設計和定義微服務時,要繪製界限的位置是關鍵工作。 DDD 模式可協助您瞭解網域中的複雜性。 針對每個界限上下文的領域模型,您可以識別並定義實體、值對象及聚合,來建構您的領域模型。 您建置並改進一個在定義您背景的界限內的領域模型。 這種形式在微服務中顯得尤為明確。 這些界限內的元件最終會成為您的微服務,但在某些情況下,BC 或商務微服務可以由數個實體服務組成。 DDD 是關於界限,微服務也是如此。

保持微服務上下文邊界相對較小

判斷有界上下文之間劃定界限的位置,需要平衡兩個相互競爭的目標。 首先,您想要一開始建立最小可能的微服務,雖然這不應該成為主要推動力;您應該在需要凝聚力的事物之間建立界限。 其次,您想要避免微服務之間的閒聊通訊。 這些目標可能會相互矛盾。 您應該透過將系統分解成盡可能多的小型微服務來平衡它們,直到每次嘗試分離新的界限上下文時,通訊邊界迅速擴展。 凝聚力是單一界限上下文中的關鍵。

它類似於實作類別時 不適當的親密代碼氣味 。 如果兩個微服務需要彼此共同作業,它們可能應該是相同的微服務。

另一種看待這一方面的方式是自主性。 如果微服務必須依賴另一個服務來直接服務要求,則不會真正自主。

DDD 微服務中的圖層

大部分具有重要商務和技術複雜性的企業應用程式都是由多層定義。 層是邏輯成品,與服務的部署無關。 它們的存在可協助開發人員管理程序代碼中的複雜性。 不同的圖層(例如領域模型層與表示層等)可能有不同的類型,這些類型之間會強制轉譯。

例如,可以從資料庫載入實體。 然後,該資訊的一部分或包含來自其他實體的附加數據的資訊彙總,可以透過 REST Web API 傳送至客戶端用戶介面。 此處的重點是領域實體包含在定義域模型層內,不應傳播到不屬於的其他區域,例如表示層。

此外,您必須擁有由匯總根(根實體)控制的一律有效實體(請參閱定義 域模型層中的設計驗證 一節)。 因此,實體不應該系結至客戶端檢視,因為在UI層級,某些數據可能仍未驗證。 這是 ViewModel 的用途。 ViewModel 是專用於呈現層需求的數據模型。 網域實體不直接屬於 ViewModel。 相反地,您必須在 ViewModel 和網域實體之間轉譯,反之亦然。

在處理複雜度時,務必要有一個由聚合根控制的領域模型,以確保與該實體群組相關的所有不變式和規則,均透過聚合根這一單一進入點或閘口來執行。

圖 7-5 顯示如何在 eShopOnContainers 應用程式中實作分層設計。

此圖顯示網域驅動設計微服務中的圖層。

圖 7-5。 eShopOnContainers 中排序微服務中的 DDD 層

DDD 微服務中的三個層級,例如 Ordering。 每一層都是 VS 專案:應用層是 Ordering.API,網域層是 Ordering.Domain,而基礎結構層是 Ordering.Infrastructure。 您想要設計系統,讓每一層只與其他特定層通訊。 如果圖層實作為不同的類別庫,這種方法可能會更容易強制執行,因為您可以清楚識別連結庫之間的相依性。 例如,領域模型層不應相依於任何其他層(領域模型類別應該是純舊類別物件或 POCO 類別)。 如圖 7-6 所示, Ordering.Domain 層連結庫只相依於 .NET 連結庫或 NuGet 套件,但不適用於任何其他自定義連結庫,例如資料庫或持續性連結庫。

Ordering.Domain 相依性的螢幕快照。

圖 7-6。 實作為連結庫的圖層可讓您更妥善地控制各層之間的相依性

領域模型層

Eric Evans 的優秀書籍 領域驅動設計 提到以下內容,關於領域模型層和應用層。

領域模型層:負責代表商務的概念、商務情況的相關信息,以及商務規則。 反映商務狀況的狀態會在這裡受到控制及使用,即使儲存它的技術詳細數據會委派給基礎結構。 這一層是商務軟體的核心。

領域模型層是表示業務的位置。 當您在 .NET 中實作微服務領域模型層時,該層會編碼為類別庫,其中包含擷取數據加行為的領域實體(具有邏輯的方法)。

遵循 持續性無知基礎結構無知 準則,此層必須完全忽略數據持續性詳細數據。 這些持續性工作應該由基礎結構層執行。 因此,此層不應直接相依於基礎結構,這表示重要的規則是您的領域模型實體類別應該是POCO。

領域實體不應該直接依賴於任何數據存取基礎架構框架,例如衍生自基類,像是 Entity Framework 或 NHibernate。 在理想情況下,您的網域實體不應該衍生自或實作任何基礎結構架構中定義的類型。

大部分的新式 ORM 架構,例如 Entity Framework Core 都允許這種方法,因此您的領域模型類別不會與基礎結構結合。 不過,使用某些 NoSQL 資料庫和架構時,不一定能夠擁有 POCO 實體,例如 Azure Service Fabric 中的 Actor 模型和可靠集合。

即使網域模型中重要的是遵循持久性無知原則,您也不應忽視持久性相關因素。 請務必瞭解物理數據模型及其對應至實體物件模型的方式。 否則,您可以建立不可能的設計。

此外,這個層面並不表示您可以採用專為關係資料庫設計的模型,並將模型直接移至 NoSQL 或文件導向資料庫。 在某些實體模型中,模型可能適合,但通常不適用。 仍然存在根據儲存技術和 ORM 技術,您的實體模型必須遵守的限制。

應用層

繼續到應用層,我們可以再次引用 Eric Evans 的網域 驅動設計書:

應用層: 定義軟體應該執行的作業,並指示表達領域物件找出問題。 此層負責的工作對於商務或與其他系統應用層互動所需的工作有意義。 此層會保持薄。 它不包含商務規則或知識,但只會協調工作,並將工作委派給下一層中的領域物件共同作業。 它沒有反映商務情況的狀態,但它可以有狀態來反映使用者或程式的工作進度。

.NET 中的微服務應用層通常會編碼為 ASP.NET Core Web API 專案。 專案會實作微服務的互動、遠端訪問,以及 UI 或用戶端應用程式所使用的外部 Web API。 如果使用 CQRS 方法、微服務所接受的命令,甚至是微服務之間的事件驅動通訊(整合事件),它就會包含查詢。 代表應用層的 ASP.NET Core Web API 不得包含商務規則或領域知識(特別是交易或更新的網域規則):這些應該由領域模型類別庫所擁有。 應用層只能協調工作,不得保留或定義任何領域狀態(領域模型)。 它會將商務規則的執行委派給領域模型類別本身(匯總根和領域實體),最終會更新這些領域實體中的數據。

基本上,應用程式邏輯是您實作相依於指定前端的所有使用案例。 例如,與 Web API 服務相關的實作。

目標是領域模型層中的領域邏輯、其不變性、數據模型和相關商務規則必須與表示層和應用層完全無關。 最重要的是,領域模型層不得直接相依於任何基礎結構架構。

基礎結構層

基礎結構層是最初保留在網域實體中的數據(記憶體中)如何保存在資料庫或其他永續性存放區中。 例如,使用 Entity Framework Core 程式代碼來實作使用 DBContext 將數據保存在關係資料庫中的存放庫模式類別。

根據先前提到的 持續性無知基礎結構無 知原則,基礎結構層不得「污染」領域模型層。 您必須將領域模型實體類別與用來保存數據的基礎結構無關(EF 或任何其他架構),而不需對架構採取硬式相依性。 您的領域模型層類別庫應該只有您的網域程序代碼,只有 POCO 實體類別會實作您軟體的核心,且完全與基礎結構技術分離。

因此,您的圖層、類別函式庫和專案最終應該依賴於您的領域模型層(函式庫),而不是讓模型層依賴它們,如圖 7-7 所示。

此圖顯示存在於 DDD 服務層級之間的相依性。

圖 7-7。 DDD 中圖層之間的依賴關係

DDD 服務中的相依性、應用層相依於網域和基礎結構,而基礎結構則相依於網域,但網域不相依於任何層級。 此層設計應該是獨立於每個微服務。 如先前所述,您可以實作遵循 DDD 模式的最複雜微服務,同時以更簡單的方式實作更簡單的數據驅動微服務(單一層中的簡單 CRUD)。

其他資源