微服務設計為什么要選擇DDD?其中最重要的原因,就是采用DDD方法建立的領域模型,可以清晰的劃分微服務的邏輯邊界、物理邊界和代碼邊界。在DDD實踐中,好的領域模型直接關乎到微服務的設計水平,因此,我認為DDD的戰略設計比戰術設計更為重要。
1. 戰略設計
戰略設計是根據業務分析,找出領域對象和聚合根,對實體和值對象進行聚類組成聚合,劃分限界上下文,建立領域模型的過程。
戰略設計采用的方法是事件風暴,包括:產品愿景、場景分析、領域建模和微服務拆分等幾個主要過程。
2. 戰術設計
戰術設計是根據領域模型進行微服務設計的過程。這個階段主要梳理微服務內的領域對象,梳理領域對象之間的關系,確定它們在代碼模型和分層架構中的位置,建立領域模型與微服務模型的映射關系,以及服務之間的依賴關系。
戰術設計包括以下兩個階段:分析微服務領域對象和設計微服務代碼結構。
3. 邏輯邊界、物理邊界和代碼邊界
- 邏輯邊界: 主要定義同一業務領域或應用內緊密關聯的對象所組成的不同聚類的組合之間的邊界。事件風暴對不同實體對象進行關聯和聚類分析后,會產生多個聚合和限界上下文,它們一起組成這個領域的領域模型。微服務內聚合之間的邊界就是邏輯邊界。一般來說微服務會有一個以上的聚合,在開發過程中不同聚合的代碼隔離在不同的聚合代碼目錄中。
- 物理邊界:主要從部署和運行的視角來定義微服務之間的邊界。不同微服務部署位置和運行環境是相互物理隔離的,分別運行在不同的進程中。這種邊界就是微服務之間的物理邊界。
- 代碼邊界:主要用于微服務內的不同職能代碼之間的隔離。微服務開發過程中會根據代碼模型建立相應的代碼目錄,實現不同功能代碼的隔離。由于領域模型與代碼模型的映射關系,代碼邊界直接體現出業務邊界。代碼邊界可以控制代碼重組的影響范圍,避免業務和服務之間的相互影響。微服務如果需要進行功能重組,只需要以聚合代碼為單位進行重組就可以了。
通過以上邊界,我們可以讓業務能力高內聚、代碼松耦合,且清晰的邊界,可以快速實現微服務代碼的拆分和組合,輕松實現微服務架構演進。
在微服務拆分與設計時,我們不能簡單地將領域模型作為拆分微服務的唯一標準,它只能作為微服務拆分的一個重要依據。微服務設計時還需要考慮服務的粒度、分層、邊界劃分、依賴關系和集成關系。除了考慮業務職責單一外,還需要考慮將敏態與穩態業務的分離、非功能需求和效率等。
只有建立了標準的微服務代碼模型和代碼規范后,我們才可以將領域對象所對應的代碼對象放在合適的軟件包的目錄結構中。標準的代碼模型可以讓項目團隊成員更好地理解代碼,根據代碼規范實現團隊協作;還可以讓微服務各層的邏輯互不干擾、分工協作、各據其位、各司其職,避免不必要的代碼混淆。另外,標準的代碼模型還可以讓你在微服務架構演進時,輕松完成代碼重構。
在代碼模型中,各代碼對象各居其位、各司其職、共同協作完成微服務的業務邏輯。 關于代碼模型需要強調兩點:
- 第一點:聚合之間的代碼邊界一定要清晰。
- 第二點:一定要有代碼分層的概念。寫代碼時一定要清楚代碼的職責,將它放在職責對應的目錄內。 應用層代碼主要完成服務組合和編排,以及聚合之間的協作,它是很薄的一層,不應該有核心領域邏輯代碼。 領域層是業務的核心,領域模型的核心邏輯代碼一定要在領域層實現。
4. 設計微服務代碼結構
根據 DDD 的代碼模型和各領域對象所在的包、類和方法,我們可以定義出微服務的代碼結構,設計代碼對象。
4.1 應用層代碼結構
應用層包括:應用服務、DTO 以及事件發布相關代碼。在 LeaveApplicationService 類內實現與聚合相關的應用服務,在 LoginApplicationService 封裝外部微服務認證和權限的應用服務。
如果應用服務邏輯復雜的話,一個應用服務就可以構建一個類,這樣可以避免一個類的代碼過于龐大,不利于維護。
4.2 領域層代碼結構
領域層包括一個或多個聚合的實體類、事件實體類、領域服務以及工廠、倉儲相關代碼。一個聚合對應一個聚合代碼目錄,聚合之間在代碼上完全隔離,聚合之間通過應用層協調。
微服務領域層包含請假和人員兩個聚合。人員和請假代碼都放在各自的聚合所在目錄結構的代碼包中。如果隨著業務發展,人員相關功能需要從請假微服務中拆分出來,我們只需將人員聚合代碼包稍加改造,獨立部署,即可快速發布為人員微服務。到這里,微服務內的領域對象,分層以及依賴關系就梳理清晰了。微服務的總體架構和代碼模型也基本搭建完成了。
5. 如何實現服務協作
5.1 服務的類型
按照分層架構設計出來的微服務,其內部有 Facade 服務、應用服務、領域服務和基礎服務。各層服務的主要功能和職責如下。
- Facade 服務:位于用戶接口層,包括接口和實現兩部分。用于處理用戶發送的 Restful 請求和解析用戶輸入的配置文件等,并將數據傳遞給應用層。或者在獲取到應用層數據后,將 DO 組裝成 DTO,將數據傳輸到前端應用。
- 應用服務:位于應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果拼裝,對外提供粗粒度的服務。
- 領域服務:位于領域層。領域服務封裝核心的業務邏輯,實現需要多個實體協作的核心領域邏輯。它對多個實體或方法的業務邏輯進行組合或編排,或者在嚴格分層架構中對實體方法進行封裝,以領域服務的方式供應用層調用。
- 基礎服務:位于基礎層。提供基礎資源服務(比如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務應用邏輯的影響。基礎服務主要為倉儲服務,通過依賴倒置提供基礎資源服務。領域服務和應用服務都可以調用倉儲服務接口,通過倉儲服務實現數據持久化。
5.2 服務的調用
微服務的服務調用包括三類主要場景:微服務內跨層服務調用,微服務之間服務調用和領域事件驅動。
5.2.1 微服務內跨層服務調用
微服務架構下往往采用前后端分離的設計模式,前端應用獨立部署。前端應用調用發布在 API 網關上的 Facade 服務,Facade 定向到應用服務。應用服務作為服務組織和編排者,它的服務調用有這樣兩種路徑:
- 第一種是應用服務調用并組裝領域服務。此時領域服務會組裝實體和實體方法,實現核心領域邏輯。領域服務通過倉儲服務獲取持久化數據對象,完成實體數據初始化。
- 第二種是應用服務直接調用倉儲服務。這種方式主要針對像緩存、文件等類型的基礎層數據訪問。這類數據主要是查詢操作,沒有太多的領域邏輯,不經過領域層,不涉及數據庫持久化對象。
5.2.2 微服務之間的服務調用
微服務之間的應用服務可以直接訪問,也可以通過 API 網關訪問。由于跨微服務操作,在進行數據新增和修改操作時,你需關注分布式事務,保證數據的一致性。
5.2.3 領域事件驅動
領域事件驅動包括微服務內之間的事件。 微服務內通過事件總線完成聚合之間的異步處理。 微服務之間通過消息中間件完成。 異步化的領域事件驅動機制是一種間接的服務訪問方式。
5.3 服務的封裝與組合
微服務的服務是從領域層逐級向上封裝、組合和暴露的。
5.3.1 基礎層
基礎層的服務形態主要是倉儲服務。倉儲服務包括接口和實現兩部分。倉儲接口服務供應用層或者領域層服務調用,倉儲實現服務,完成領域對象的持久化或數據初始化。
5.3.2 領域層
領域層實現核心業務邏輯,負責表達領域模型業務概念、業務狀態和業務規則。主要的服務形態有實體方法和領域服務。
5.3.3 應用層
應用層用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝,負責不同聚合之間的服務和數據協調,負責微服務之間的事件發布和訂閱。通過應用服務對外暴露微服務的內部功能,這樣就可以隱藏領域層核心業務邏輯的復雜性以及內部實現機制。應用層的主要服務形態有:應用服務、事件發布和訂閱服務。
5.3.4 用戶接口層
用戶接口層是前端應用和微服務之間服務訪問和數據交換的橋梁。它處理前端發送的 Restful 請求和解析用戶輸入的配置文件等,將數據傳遞給應用層。或獲取應用服務的數據后,進行數據組裝,向前端提供數據服務。主要服務形態是 Facade 服務。
Facade 服務分為接口和實現兩個部分。完成服務定向,DO 與 DTO 數據的轉換和組裝,實現前端與應用層數據的轉換和交換。
5.4 兩種分層架構的服務依賴關系
DDD 分層架構,分層架構有一個重要的原則就是:每層只能與位于其下方的層發生耦合。
那根據耦合的緊密程度,分層架構可以分為兩種:嚴格分層架構和松散分層架構。在嚴格分層架構中,任何層只能與位于其直接下方的層發生依賴。在松散分層架構中,任何層可以與其任意下方的層發生依賴。
5.4.1 松散分層架構的服務依賴
在松散分層架構中,領域層的實體方法和領域服務可以直接暴露給應用層和用戶接口層。松散分層架構的服務依賴關系,無需逐級封裝,可以快速暴露給上層。
但它存在一些問題,第一個是容易暴露領域層核心業務的實現邏輯;第二個是當實體方法或領域服務發生服務變更時,由于服務同時被多層服務調用和組合,不容易找出哪些上層服務調用和組合了它,不方便通知到所有的服務調用方。
我們再來看一張圖,在松散分層架構中,實體 A 的方法在應用層組合后,暴露給用戶接口層 aFacade。abDomainService 領域服務直接越過應用層,暴露給用戶接口層 abFacade 服務。松散分層架構中任意下層服務都可以暴露給上層服務。
5.4.2 嚴格分層架構的服務依賴
在嚴格分層架構中,每一層服務只能向緊鄰的上一層提供服務。雖然實體、實體方法和領域服務都在領域層,但實體和實體方法只能暴露給領域服務,領域服務只能暴露給應用服務。在嚴格分層架構中,服務如果需要跨層調用,下層服務需要在上層封裝后,才可以提供跨層服務。比如實體方法需要向應用服務提供服務,它需要封裝成領域服務。
這是因為通過封裝你可以避免將核心業務邏輯的實現暴露給外部,將實體和方法封裝成領域服務,也可以避免在應用層沉淀過多的本該屬于領域層的核心業務邏輯,避免應用層變得臃腫。還有就是當服務發生變更時,由于服務只被緊鄰上層的服務調用和組合,你只需要逐級告知緊鄰上層就可以了,服務可管理性比松散分層架構要好是一定的。
我們還是看圖,A 實體方法需封裝成領域服務 aDomainService 才能暴露給應用服務 aAppService。abDomainService 領域服務組合和封裝 A 和 B 實體的方法后,暴露給應用服務 abAppService。