MASA Framework - DDD 設計(2)

Clean Architecture

國內對於 Clean Architecture 的翻譯很多,乾淨 / 整潔 / 清晰。但無論哪一種都說明了它簡潔、清晰的特性。

早期它長這樣

看到這張圖的同學可能會對另外一張圖有印象

洋蔥架構 (Onion)

現在長這樣

看起來好像是親戚,它們的確也有着千絲萬縷的關係

分析 Clean Architecture

這部分主要是根據 explicit architecture 文章的理解整理的,有翻譯也有自己理解消化的。如有錯漏歡迎指正,謝謝

三大構建塊


控制流

  1. 用戶界面

  2. 應用核心

  3. 基礎設施

  4. 應用核心

  5. 用戶界面

工具

左右兩側形成鮮明對比,動機不同

鏈接工具和交付機制到應用核心

將工具連接到應用程序核心的代碼單元稱爲適配器(端口和適配器架構)。

告訴我們的應用程序做某事的適配器稱爲主適配器或驅動適配器,而我們的應用程序告訴我們做某事的適配器稱爲輔助適配器或驅動適配器。

端口

這些適配器爲了適應應用核心的一個非常特定的入口點,即端口。端口只不過是工具如何使用應用程序核心或應用核心如何使用它的規範。

你可以看作是接口和 DTO

主適配器或主動適配器

主適配器或主動適配器圍繞一個端口並使用它來告訴應用核心該做什麼。

我們的主動適配器是 Controller 或 Console Commands,它們在其構造函數中注入了一些對象,該對象的類實現了 Controller 或 Console Commands 所需的接口(端口)。

端口可以是控制器需要的服務接口或存儲庫接口,然後將 Service、Repository 或 Query 的具體實現注入並在 Controller 中使用。

或者,端口可以是 Command Bus 或 Query Bus 的接口。在這種情況下,將 Command Bus 或 Query Bus 的具體實現注入到 Controller 中,然後 Controller 構造 Command 或 Query 並將其傳遞給相關 Bus。

注:這裏其實提到了 CQRS

從適配器或被動適配器

與圍繞在端口周圍的主動適配器不同,從適配器實現一個端口、一個接口,然後在需要端口的任何地方注入應用核心。

可以理解爲是右側是符合應用核心的需要的接口或者對象,而左側則是包裝用例的傳達機制,如 HTTP/CLI 等

假設我們有一個需要持久化數據的需求:

  1. 我們創建一個持久化接口(在左側端口周圍),有一個保存數據的方法和一個通過 ID 刪除數據的方法

  2. 基礎設施中提供一個實現類,通過 IoC 注入這個接口和對應的實現類

  3. 在需要持久化數據的類的構造函數中注入一個持久化接口

  4. 如果有一天我們需要從 SQL Server 換到 MongoDb,只需要替換步驟 2 中的實現類和注入新的實現類即可

IoC

與上圖相比,僅僅是多了一個藍色的箭頭從外部直插入應用核心內部。在上面例子中有提到通過 IoC 的作用這裏就不再重複了。

至此,我們一致在講解應用核心的外圍,而應用核心是我們架構設計的重點。

應用核心組織

洋蔥架構採用 DDD 分層並將它們合併到端口和適配器架構中。這些層旨在爲業務邏輯帶來一些組織,即端口和適配器 “六邊形” 的內部,就像在端口和適配器中一樣,依賴方向是朝向中心的。

應用層

用例 (Use Case) 是我們的應用中的一個或多個用戶界面觸發的流程(業務邏輯)。用戶界面可以是終端用戶界面也可以是管理界面,或者控制檯界面和 API。

用例在應用層定義,由 DDD 和洋蔥架構提供,它可以包含端口,ORM 接口,搜索引擎接口,消息接口等,也可以是 CQRS 處理 Handlers 的地方,發送郵件,調用第三方 API 等。

應用服務 / Command Handler 包含用例的業務邏輯,作用是:

  1. 使用 Repository 查找一個或多個實體

  2. 告訴這些實體做一些領域邏輯

  3. 使用 Repository 持久化這些實體,保存數據更改

Command Handler 可以有兩種不同的使用方式:

  1. 包含執行用例的真實業務邏輯

  2. 作爲架構中的中間件,接收 Command 並觸發應用服務中的邏輯

領域層

領域內的對象除了有對象本身的屬性外,還可以操作該對象內部的屬性,這是特定於域本身的,並且獨立於觸發該邏輯的業務流程,它們是獨立的,完全不知道應用層。

領域服務

有時我們會遇到一些涉及不同實體的領域邏輯,無論是否相同,該領域邏輯不屬於實體本身,它沒有直接責任。

那我們可以使用領域服務來承載這部分邏輯,可能有人會覺得那可以放應用層,但領域邏輯在其他用例中就不能重用了。領域邏輯應該在領域內部,不要上升到應用層。

領域服務可以使用其他領域服務,或者其他領域對象

領域模型

在最中心,依賴於它之外的任何東西,是領域模型,它包含代表領域中某些事物的業務對象。至於如何定義領域模型可以參考第一篇。

組件

組件與應用核心內所有的層交叉,從外貫穿到內部。例如身份驗證、授權、計費、用戶、評論或賬戶,但它們依然與領域有關。

像授權和身份驗證這樣的限界上下文應該被視爲隱藏在某種端口後面的外部工具。

解耦組件

具有完全解耦的組件意味着一個組件不直接瞭解任何另一個組件。換句話說,它可能沒有接口,所以我們需要一些新的架構結構。

比如事件、最終一致性、服務發現等。當你往這條路上走的時候,你就開始脫離單體了。

這裏 Dapr 或許是個不錯的選擇,它包含了這些功能,對 Dapr 感興趣的可以看之前的手把手教你學 Dapr 系列

MASA Framework 解決方案

結合 DDD 和 Clean Architecture 以及 MASA Framework 的特性,我們將在 MASA.BuildingBlocks 中以接口的形式定義規範,在 MASA.Contrib 中對接口進行實現。

這意味着你可以只關心 BuildingBlocks 中的接口定義來編寫你的代碼,也可以基於接口重新實現在 DDD 落地中你自己的業務特性來調整或擴展我們提供的默認行爲。比如,你有自己的 UoW、倉儲層等都可以隨意換掉。

應用層

應用服務:

實現應用程序的用例,銜接表示層(接口層)與領域層

除此之外,基於 MASA EShop 的示例中的 MASA.EShop.Services.Catalog 的 CQRS 架構演示,應用層也可以承載 CQRS 的 Command Hanlder。除了可以繼續使用領域層來解決 Command 業務外,你也可以選擇在此中止,在 Command Handler 裏簡化架構直接對 Command 進行處理。

示例代碼:https://github.com/masalabs/MASA.EShop/tree/develop/src/Services/MASA.EShop.Services.Catalog/Application/CatalogBrands

工作單元:

默認事件是在應用服務中首次開啓,所以 UoW 也會在應用層被激活(實際上底層會根據倉儲的操作,只有首次增刪改纔會自動激活,這個功能可以關閉,改爲手動控制)

中間件:

對於使用 Event Bus 開發來說,應用層還可以作爲統一的 AOP 出入口。

例如統一的事件參數驗證:

首先需要啓用統一驗證中間件 https://github.com/masalabs/MASA.EShop/blob/develop/src/Services/MASA.EShop.Services.Catalog/Application/Middleware/ValidatorMiddleware.cs

然後爲對應的 Event/Command 編寫驗證邏輯 https://github.com/masalabs/MASA.EShop/blob/develop/src/Services/MASA.EShop.Services.Catalog/Application/Catalogs/Commands/DeleteProductCommandValidator.cs

領域層

對於實體、聚合、值對象等概念就不再介紹了,可以參考上一章的內容。

貧血模型 VS 充血模型

領域中需要限定領域內的業務邏輯,加上 EF Core 對充血模型的支持,充血模型更適合用作領域模型的開發。

將數據與行爲封裝,表現出現實業務對象完整行爲,每個領域具備明確的職責劃分,將邏輯分散到領域對象中。這也是應用層與領域層的一個比較明顯的區別。

對於實體相關對象,我們提供了對應的類,當然也包括審計和值對象可能需要用到的枚舉類。

枚舉類:我們提供了 Enumeration,參考自:https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types

領域服務:

領域服務中可以調用其他的領域服務(包括進程內或跨進程),所以我們提供了 IDomainService,它的功能包括:

倉儲:

領域中定義的倉儲爲接口,代表在當前領域內關心的業務。比如用戶,在用戶管理和名片兩種業務中,對於 IRepository 的定義是不同的。但在基礎設施中 BaseRepository 可以是同一個,因爲 BaseRepository 可以是最完整的實現,但領域內倉儲服務只認其中一部分

基礎設施層

給接口提供實現,如倉儲接口的實現、Event Bus 中 MQ 或中介者的實現(MASA Framework 已實現,所以我們的示例中目前只有倉儲接口的實現)等。

MASA Framework 模板架構

在 MASA Framework 模板中提供了自由組合的方式,你可以根據你的需求隨意調整如是否包含 Blazor、Dapr、DDD、CQRS 等。

我們的 MASA.EShop 推薦採用了 4 種架構方式,從簡到繁,本篇介紹最複雜的一個

Minimal APIS + CQRS + Dapr actor

MASA.EShop 中的 Ordering 服務就是採用這種架構分層,其實分層解釋上面也有,只是之前解釋的是站在 MASA BuildingBlocks 的角度,而接下來將是站在開發者角度。

總結

至此,我們不僅實現了對單體架構的支持,還通過 Event Bus 對微服務架構提供了支持。

如果你對 DDD 或者 MASA Framework 感興趣,不妨把 MASA.EShop 跑起來看一下,它提供了 4 種架構方式參考,可以滿足大部分業務場景對架構的要求。

學以致用,學無止境。

參考:

DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together:https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/

開源地址

MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks

MASA.Contrib:https://github.com/masastack/MASA.Contrib

MASA.Utils:https://github.com/masastack/MASA.Utils

MASA.EShop:https://github.com/masalabs/MASA.EShop

MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

如果你對我們的 MASA Framework 感興趣,無論是代碼貢獻、使用、提 Issue,歡迎聯繫我們

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BH9UA83--HJxQdcUCxcizQ