MASA Framework - DDD設計(2)
目錄
MASA Framework - 整體設計思路
MASA Framework - EventBus設計
MASA Framework - MASA Framework - DDD設計(1)
MASA Framework - MASA Framework - DDD設計(2)
Clean Architecture
國內對于Clean Architecture的翻譯很多,干凈/整潔/清晰。但無論哪一種都說明了它簡潔、清晰的特性。
早期它長這樣

看到這張圖的同學可能會對另外一張圖有印象
洋蔥架構(Onion)

現在長這樣

看起來好像是親戚,它們的確也有著千絲萬縷的關系
分析Clean Architecture
這部分主要是根據explicit architecture文章的理解整理的,有翻譯也有自己理解消化的。如有錯漏歡迎指正,謝謝
三大構建塊
- 用戶界面
- 基礎設施
- 應用核心

控制流
- 用戶界面
- 應用核心
- 基礎設施
- 應用核心
- 用戶界面

工具
左右兩側形成鮮明對比,動機不同
- HTTP/CLI:告訴應用要做什么
- SMS/Mailing Server/Search Engine...:應用告訴它們要做什么

鏈接工具和交付機制到應用核心
將工具連接到應用程序核心的代碼單元稱為適配器(端口和適配器架構)。
告訴我們的應用程序做某事的適配器稱為主適配器或主動適配器,而我們的應用程序告訴我們做某事的適配器稱為從適配器或被動適配器。
端口
這些適配器為了適應應用核心的一個非常特定的入口點,即端口。端口只不過是工具如何使用應用程序核心或應用核心如何使用它的規范。
你可以看作是接口和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等

假設我們有一個需要持久化數據的需求:
- 我們創建一個持久化接口(在應用核心中領域層的倉儲內),有一個保存數據的方法和一個通過ID刪除數據的方法
- 基礎設施中提供一個實現類,通過IoC注入這個接口和對應的實現類
- 在需要持久化數據的類的構造函數中注入一個持久化接口
- 如果有一天我們需要從SQL Server換到MongoDb,只需要替換步驟2中的實現類和注入新的實現類即可
IoC
與上圖相比,僅僅是多了一個藍色的箭頭從外部直插入應用核心內部。在上面例子中有提到通過IoC的作用這里就不再重復了。
至此,我們一致在講解應用核心的外圍,而應用核心是我們架構設計的重點。

應用核心組織
洋蔥架構采用 DDD 分層并將它們合并到端口和適配器架構中。這些層旨在為業務邏輯帶來一些組織,即端口和適配器“六邊形”的內部,就像在端口和適配器中一樣,依賴方向是朝向中心的。
應用層
用例(Use Case)是我們的應用中的一個或多個用戶界面觸發的流程(業務邏輯)。用戶界面可以是終端用戶界面也可以是管理界面,或者控制臺界面和API。
用例在應用層定義,由DDD和洋蔥架構提供,它可以包含端口,ORM接口,搜索引擎接口,消息接口等,也可以是CQRS處理Handlers的地方,發送郵件,調用第三方API等。
應用服務/Command Handler包含用例的業務邏輯,作用是:
- 使用Repository查找一個或多個實體
- 告訴這些實體做一些領域邏輯
- 使用Repository持久化這些實體,保存數據更改
Command Handler可以有兩種不同的使用方式:
- 包含執行用例的真實業務邏輯
- 作為架構中的中間件,接收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進行處理。
工作單元:
默認事件是在應用服務中首次開啟,所以UoW也會在應用層被激活(實際上底層會根據倉儲的操作,只有首次增刪改才會自動激活,這個功能可以關閉,改為手動控制)
中間件:
對于使用Event Bus開發來說,應用層還可以作為統一的AOP出入口。
例如統一的事件參數驗證:
然后為對應的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,它的功能包括:
- 自動協調進程內和跨進程的事件傳遞
- 支持被調用方是CQRS
- 默認支持事件壓棧,在UoW Commit后統一觸發。也支持實時發送事件(如后續業務可被降級,但跨進程的事件為主業務邏輯不可被降級)
- 跨進程事件支持最終一致性和Saga

倉儲:
領域中定義的倉儲為接口,代表在當前領域內關心的業務。比如用戶,在用戶管理和名片兩種業務中,對于IRepository

基礎設施層
給接口提供實現,如倉儲接口的實現、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的角度,而接下來將是站在開發者角度。
-
User Interface Layer:它負責提供用戶接口,完整前端邏輯。用戶也可以是計算機系統,不特指是人。所以這里既可以是API,也可以是Blazor、MVC等。
-
Application Layer:它可以很薄也可以很厚(在當前分層下推薦薄)。負責協調User Interface和Domain,包括服務的編排和轉發,AOP,發送事件等。
如果你有Domain Layer可以把Command做的很薄調用Domaiin。如果你要精簡CQRS,也可以不用Domain,在這一層直接做應用服務。當然Query也一樣,但Query即便使用Domain也推薦把查詢放在應用服務里,這樣可以把Query和Command分離來獲得CQRS的優勢。
-
Domain Layer:業務核心,包括了領域對象和領域服務以及適配器的接口。建議采用充血模型將行為留在領域內,跨領域且需要被復用的可以使用領域服務。倉儲接口則限定領域內的倉儲行為,與物理倉儲不同的是更聚焦業務本身,而不是實體的完整倉儲能力。
-
Infrastructure Layer:給接口提供實現,如倉儲接口的實現、Event Bus中MQ或中介者的實現(MASA Framework已實現,所以我們的示例中目前只有倉儲接口的實現)等。
總結
至此,我們不僅實現了對單體架構的支持,還通過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,歡迎聯系我們

自動簽名

浙公網安備 33010602011771號