'基於DDD的微服務設計和開發實戰'

設計 軟件 數據庫 技術 物理 java架構coid 2019-08-24
"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

基於DDD的微服務設計和開發實戰

基礎層代碼結構

Config:主要存放配置相關代碼。

Util:主要存放平臺、開發框架、消息、數據庫、緩存、文件、總線、網關、第三方類庫、通用算法等基礎代碼,可為不同的資源類別建立不同的子目錄。

(六) 微服務總目錄結構

微服務總目錄結構如下:

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

基於DDD的微服務設計和開發實戰

基礎層代碼結構

Config:主要存放配置相關代碼。

Util:主要存放平臺、開發框架、消息、數據庫、緩存、文件、總線、網關、第三方類庫、通用算法等基礎代碼,可為不同的資源類別建立不同的子目錄。

(六) 微服務總目錄結構

微服務總目錄結構如下:

基於DDD的微服務設計和開發實戰

九、 微服務設計原則

微服務設計原則中如高內聚低耦合、複用、單一職責等原則在此就不贅述了,這裡主要強調以下幾條:

第一條:“要領域驅動設計,而不是數據驅動設計,也不是界面驅動設計”。

微服務設計首先應建立領域模型,確定邏輯和物理邊界後,然後才進行微服務邊界拆分,而不是一上來就定義數據庫表結構,也不是界面需要什麼,就去調整領域邏輯代碼。

領域模型和領域服務應具有高度通用性,通過接口層和應用層屏蔽外部變化對業務邏輯的影響,保證核心業務功能的穩定性。

第二條:“要邊界清晰的微服務,而不是泥球小單體”。

微服務完成開發後其功能和代碼也不是一成不變的。隨著需求或設計變化,微服務內的代碼也會分分合合。邏輯邊界清晰的微服務,可快速實現微服務代碼的拆分和組合。DDD思想中的邏輯邊界和分層設計也是為微服務各種可能的分分合合做準備的。

微服務內聚合與聚合之間的領域服務以及數據原則上禁止相互產生依賴。如有必要可通過上層的應用服務編排或者事件驅動機制實現聚合之間的解耦,以利於聚合之間的組合和拆分。

第三條:“要職能清晰的分層,而不是什麼都放的大籮筐”。

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層調用內層服務,內層服務通過封裝、組合或編排對外逐層暴露,服務粒度由細到粗。

應用層負責服務的編排和組合,領域層負責領域業務邏輯的實現,基礎層為各層提供資源服務。

第四條:“要做自己能hold住的微服務,而不是過度拆分的微服務”。

微服務的過度拆分必然會帶來軟件維護成本的上升,如:集成成本、運維成本以及監控和定位問題的成本。企業轉型過程中很難短時間內提升這些能力,如果項目團隊不具備這些能力,將很難hold住這些過細的微服務。而如果我們在微服務設計之初就已經定義好了微服務內的邏輯邊界,項目初期我們可以儘可能少的拆分出過細的微服務,隨著技術的積累和時間的推移,當我們具有這些能力後,由於微服務內有清晰的邏輯邊界,這時就可以隨時根據需要輕鬆的拆分或組合出新的微服務。

十、 不同場景的微服務設計

微服務的設計先從領域建模開始,領域模型是微服務設計的核心,微服務是領域建模的結果。在微服務設計之前,請先判斷你的業務是否聚焦在領域和領域邏輯。

實際在做系統設計時我們可能面臨各種不同的情形,如從傳統單體拆分為多個微服務,也可能是一個全新領域的微服務設計(如創業中的應用),抑或是將一個單體中面臨問題或性能瓶頸的模塊拆分為微服務而其餘功能仍為單體的情況。

下面分幾類不同場景說明如何進行微服務和領域模型設計。

(一) 新建系統的微服務設計

新建系統會遇到複雜和簡單領域兩種場景,兩者的領域建模過程也會有所差別。

1、 簡單領域的建模

對於簡單的業務領域,一個領域可能就是一個小的子域。領域建模過程相對簡單,根據事件風暴可以分解出事件、命令、實體、聚合和限界上下文等,根據領域模型和微服務拆分原則設計出微服務即可。

2、 複雜領域的建模

對於複雜的業務領域,領域可能還需要拆分為子域,甚至子域還會進一步拆分,如:保險領域可以拆分為承保、理賠、收付費和再保等子域,承保子域還可以再拆分為投保、保單管理等子子域。對於這種複雜的領域模型,是無法通過一個事件風暴完成領域建模的,即使能完成,其工程量也是非常浩大,效果也不一定好。

對於這種複雜的領域,我們可以分三階段來完成領域模型和微服務設計。

1) 拆分子域建立領域模型

根據業務特點考慮流程節點或功能模塊等邊界因素(微服務最終的拆分結果很多時候跟這些邊界因素有一定的相關性),按領域逐級分解為大小合適的子域,針對子域進行事件風暴,記錄領域對象、聚合和限界上下文,初步確定各級子域的領域模型。

2) 領域模型微調

梳理領域內所有子域的領域模型,對各子域模型進行微調,這個過程重點考慮不同限界上下文內聚合的重新組合,同步需要考慮子域、限界上下文以及聚合之間的邊界、服務以及事件之間的依賴關係,確定最終的領域模型。

3) 微服務設計和拆分

根據領域的限界上下文和微服務的拆分原則,完成微服務的拆分和設計。

(二) 單體遺留系統的微服務設計

如果一個單體遺留系統,只是將面臨問題或性能瓶頸的模塊拆分為微服務,而其餘功能仍為單體。我們只需要將這些特定功能領域理解為一個簡單的子領域,按照簡單領域建模方式進行領域模型的設計即可。但在新微服務設計中需要考慮新老系統之間的服務協議,必要時引入防腐層。

(三) 特別說明

雖然有些業務領域在事件風暴後發現無法建立領域模型,如數據處理或分析類場景,但本文所述的分層架構模型、服務之間規約和代碼目錄結構在微服務設計和開發中仍然是通用的。

十一、 基於DDD的微服務設計和開發實例

為了更好的理解DDD的設計思想和過程,我們用一個場景簡單但基本涵蓋DDD設計思想的項目來說明微服務設計和開發過程。

(一)項目基本信息

項目主要目標是實現在線請假和考勤管理。基本功能包括:請假、考勤以及人員管理等。

請假:請假人填寫請假單提交審批,根據請假人身份和請假天數進行校驗,根據審批規則逐級遞交審批,核批通過則完成審批。

考勤:根據考勤規則,剔除請假數據後,對員工考勤數據進行校驗,輸出考勤統計表。

人員管理:維護人員基本信息和上下級關係。

(二)設計和實施步驟

步驟一:事件風暴

由於項目目標基本明確,我們在事件風暴過程中裁剪了產品願景,直接從用戶旅程和場景分析開始。

1. 場景分析

場景分析是一個發散的過程。根據不同角色的旅程和場景分析,儘可能全面的梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係等信息(如下圖),如:請假人員會執行創建請假信息操作命令,審批人員會執行審批操作,請假審批通過後會產生領域事件,通知郵件系統反饋請假人員結果,並將請假數據發送到考勤以便核銷等。在記錄這些領域對象的同時,我們也會標記各對象在DDD中的層和對象類型等屬性,如:應用服務、領域服務、事件和命令等類型。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

基於DDD的微服務設計和開發實戰

基礎層代碼結構

Config:主要存放配置相關代碼。

Util:主要存放平臺、開發框架、消息、數據庫、緩存、文件、總線、網關、第三方類庫、通用算法等基礎代碼,可為不同的資源類別建立不同的子目錄。

(六) 微服務總目錄結構

微服務總目錄結構如下:

基於DDD的微服務設計和開發實戰

九、 微服務設計原則

微服務設計原則中如高內聚低耦合、複用、單一職責等原則在此就不贅述了,這裡主要強調以下幾條:

第一條:“要領域驅動設計,而不是數據驅動設計,也不是界面驅動設計”。

微服務設計首先應建立領域模型,確定邏輯和物理邊界後,然後才進行微服務邊界拆分,而不是一上來就定義數據庫表結構,也不是界面需要什麼,就去調整領域邏輯代碼。

領域模型和領域服務應具有高度通用性,通過接口層和應用層屏蔽外部變化對業務邏輯的影響,保證核心業務功能的穩定性。

第二條:“要邊界清晰的微服務,而不是泥球小單體”。

微服務完成開發後其功能和代碼也不是一成不變的。隨著需求或設計變化,微服務內的代碼也會分分合合。邏輯邊界清晰的微服務,可快速實現微服務代碼的拆分和組合。DDD思想中的邏輯邊界和分層設計也是為微服務各種可能的分分合合做準備的。

微服務內聚合與聚合之間的領域服務以及數據原則上禁止相互產生依賴。如有必要可通過上層的應用服務編排或者事件驅動機制實現聚合之間的解耦,以利於聚合之間的組合和拆分。

第三條:“要職能清晰的分層,而不是什麼都放的大籮筐”。

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層調用內層服務,內層服務通過封裝、組合或編排對外逐層暴露,服務粒度由細到粗。

應用層負責服務的編排和組合,領域層負責領域業務邏輯的實現,基礎層為各層提供資源服務。

第四條:“要做自己能hold住的微服務,而不是過度拆分的微服務”。

微服務的過度拆分必然會帶來軟件維護成本的上升,如:集成成本、運維成本以及監控和定位問題的成本。企業轉型過程中很難短時間內提升這些能力,如果項目團隊不具備這些能力,將很難hold住這些過細的微服務。而如果我們在微服務設計之初就已經定義好了微服務內的邏輯邊界,項目初期我們可以儘可能少的拆分出過細的微服務,隨著技術的積累和時間的推移,當我們具有這些能力後,由於微服務內有清晰的邏輯邊界,這時就可以隨時根據需要輕鬆的拆分或組合出新的微服務。

十、 不同場景的微服務設計

微服務的設計先從領域建模開始,領域模型是微服務設計的核心,微服務是領域建模的結果。在微服務設計之前,請先判斷你的業務是否聚焦在領域和領域邏輯。

實際在做系統設計時我們可能面臨各種不同的情形,如從傳統單體拆分為多個微服務,也可能是一個全新領域的微服務設計(如創業中的應用),抑或是將一個單體中面臨問題或性能瓶頸的模塊拆分為微服務而其餘功能仍為單體的情況。

下面分幾類不同場景說明如何進行微服務和領域模型設計。

(一) 新建系統的微服務設計

新建系統會遇到複雜和簡單領域兩種場景,兩者的領域建模過程也會有所差別。

1、 簡單領域的建模

對於簡單的業務領域,一個領域可能就是一個小的子域。領域建模過程相對簡單,根據事件風暴可以分解出事件、命令、實體、聚合和限界上下文等,根據領域模型和微服務拆分原則設計出微服務即可。

2、 複雜領域的建模

對於複雜的業務領域,領域可能還需要拆分為子域,甚至子域還會進一步拆分,如:保險領域可以拆分為承保、理賠、收付費和再保等子域,承保子域還可以再拆分為投保、保單管理等子子域。對於這種複雜的領域模型,是無法通過一個事件風暴完成領域建模的,即使能完成,其工程量也是非常浩大,效果也不一定好。

對於這種複雜的領域,我們可以分三階段來完成領域模型和微服務設計。

1) 拆分子域建立領域模型

根據業務特點考慮流程節點或功能模塊等邊界因素(微服務最終的拆分結果很多時候跟這些邊界因素有一定的相關性),按領域逐級分解為大小合適的子域,針對子域進行事件風暴,記錄領域對象、聚合和限界上下文,初步確定各級子域的領域模型。

2) 領域模型微調

梳理領域內所有子域的領域模型,對各子域模型進行微調,這個過程重點考慮不同限界上下文內聚合的重新組合,同步需要考慮子域、限界上下文以及聚合之間的邊界、服務以及事件之間的依賴關係,確定最終的領域模型。

3) 微服務設計和拆分

根據領域的限界上下文和微服務的拆分原則,完成微服務的拆分和設計。

(二) 單體遺留系統的微服務設計

如果一個單體遺留系統,只是將面臨問題或性能瓶頸的模塊拆分為微服務,而其餘功能仍為單體。我們只需要將這些特定功能領域理解為一個簡單的子領域,按照簡單領域建模方式進行領域模型的設計即可。但在新微服務設計中需要考慮新老系統之間的服務協議,必要時引入防腐層。

(三) 特別說明

雖然有些業務領域在事件風暴後發現無法建立領域模型,如數據處理或分析類場景,但本文所述的分層架構模型、服務之間規約和代碼目錄結構在微服務設計和開發中仍然是通用的。

十一、 基於DDD的微服務設計和開發實例

為了更好的理解DDD的設計思想和過程,我們用一個場景簡單但基本涵蓋DDD設計思想的項目來說明微服務設計和開發過程。

(一)項目基本信息

項目主要目標是實現在線請假和考勤管理。基本功能包括:請假、考勤以及人員管理等。

請假:請假人填寫請假單提交審批,根據請假人身份和請假天數進行校驗,根據審批規則逐級遞交審批,核批通過則完成審批。

考勤:根據考勤規則,剔除請假數據後,對員工考勤數據進行校驗,輸出考勤統計表。

人員管理:維護人員基本信息和上下級關係。

(二)設計和實施步驟

步驟一:事件風暴

由於項目目標基本明確,我們在事件風暴過程中裁剪了產品願景,直接從用戶旅程和場景分析開始。

1. 場景分析

場景分析是一個發散的過程。根據不同角色的旅程和場景分析,儘可能全面的梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係等信息(如下圖),如:請假人員會執行創建請假信息操作命令,審批人員會執行審批操作,請假審批通過後會產生領域事件,通知郵件系統反饋請假人員結果,並將請假數據發送到考勤以便核銷等。在記錄這些領域對象的同時,我們也會標記各對象在DDD中的層和對象類型等屬性,如:應用服務、領域服務、事件和命令等類型。

基於DDD的微服務設計和開發實戰

事件風暴

2. 領域建模

領域建模是一個收斂的過程。這個收斂過程分三步:第一步根據場景分析中的操作集合定義領域實體;第二步根據領域實體業務關聯性,定義聚合;第三步根據業務及語義邊界等因素,定義限界上下文。

1) 定義領域實體

在場景分析過程中梳理完操作、命令、領域事件以及外部依賴關係等領域對象後。分析這些操作應由什麼實體發起或產生,從而定義領域實體對象,並將這些操作與實體進行關聯。

在請假場景中,經分析需要有請假單實體對象,請假單實體有創建請假信息以及修改請假信息等操作。

2) 定義聚合

將業務緊密相關的實體進行組合形成聚合,同時確定聚合中的聚合根、值對象和實體。經分析項目最終形成三個聚合:人員管理、請假和考勤。在請假聚合中有請假單、審批軌跡、審批規則等實體,其中請假單是聚合根,審批軌跡是請假單的值對象,審批規則是輔助實體。

聚合內須保證業務操作的事務性,高度內聚的實體對象可自包含完成本領域功能。聚合是可拆分為微服務的最小單元。在同一限界上下文內多個聚合可以組合為一個微服務。如有必要,也可以將某一個聚合獨立為微服務。

3) 定義限界上下文

根據領域及語義邊界等因素確定限界上下文,將同一個語義環境下的一個或者多個聚合放在一個限界上下文內。由於人員管理與請假聚合兩者業務關聯緊密,共同完成人員請假功能,兩者一起構成請假限界上下文,考勤聚合則單獨形成考勤限界上下文。

3. 微服務設計和拆分

理論上一個限界上下文可以設計為一個微服務,但還需要綜合考慮多種外部因素,如:職責單一性、性能差異、版本發佈頻率、團隊溝通效率和技術異構等要素。

由於本項目微服務設計受技術以及團隊等因素影響相對較小,主要考慮職責單一性,因此根據限界上下文直接拆分為請假和考勤兩個微服務。其中請假微服務包含人員和請假兩個聚合,考勤微服務只包含考勤聚合。

步驟二、領域對象及服務矩陣

將事件風暴中產出的領域對象按照各自所在的微服務進行分類,定義每個領域對象在微服務中的層、領域類型和依賴的領域對象等。

這個步驟最關鍵的工作是確定實體、方法、服務等領域對象在微服務分層架構中的位置以及各對象之間的依賴關係,形成服務矩陣(如下表)。這個過程也將在事件風暴數據的基礎上,進一步細化領域對象以及它們之間關係,並補充事件風暴中可能遺漏的細節。

確定完各領域對象的屬性後,按照代碼模型設計各個領域對象在代碼模型中的代碼對象(包括代碼對象所在的:包名、類名和方法名),建立領域對象與代碼對象的一一映射關係。根據這種映射關係,相關人員可快速定位到業務邏輯所在的代碼位置。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

基於DDD的微服務設計和開發實戰

基礎層代碼結構

Config:主要存放配置相關代碼。

Util:主要存放平臺、開發框架、消息、數據庫、緩存、文件、總線、網關、第三方類庫、通用算法等基礎代碼,可為不同的資源類別建立不同的子目錄。

(六) 微服務總目錄結構

微服務總目錄結構如下:

基於DDD的微服務設計和開發實戰

九、 微服務設計原則

微服務設計原則中如高內聚低耦合、複用、單一職責等原則在此就不贅述了,這裡主要強調以下幾條:

第一條:“要領域驅動設計,而不是數據驅動設計,也不是界面驅動設計”。

微服務設計首先應建立領域模型,確定邏輯和物理邊界後,然後才進行微服務邊界拆分,而不是一上來就定義數據庫表結構,也不是界面需要什麼,就去調整領域邏輯代碼。

領域模型和領域服務應具有高度通用性,通過接口層和應用層屏蔽外部變化對業務邏輯的影響,保證核心業務功能的穩定性。

第二條:“要邊界清晰的微服務,而不是泥球小單體”。

微服務完成開發後其功能和代碼也不是一成不變的。隨著需求或設計變化,微服務內的代碼也會分分合合。邏輯邊界清晰的微服務,可快速實現微服務代碼的拆分和組合。DDD思想中的邏輯邊界和分層設計也是為微服務各種可能的分分合合做準備的。

微服務內聚合與聚合之間的領域服務以及數據原則上禁止相互產生依賴。如有必要可通過上層的應用服務編排或者事件驅動機制實現聚合之間的解耦,以利於聚合之間的組合和拆分。

第三條:“要職能清晰的分層,而不是什麼都放的大籮筐”。

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層調用內層服務,內層服務通過封裝、組合或編排對外逐層暴露,服務粒度由細到粗。

應用層負責服務的編排和組合,領域層負責領域業務邏輯的實現,基礎層為各層提供資源服務。

第四條:“要做自己能hold住的微服務,而不是過度拆分的微服務”。

微服務的過度拆分必然會帶來軟件維護成本的上升,如:集成成本、運維成本以及監控和定位問題的成本。企業轉型過程中很難短時間內提升這些能力,如果項目團隊不具備這些能力,將很難hold住這些過細的微服務。而如果我們在微服務設計之初就已經定義好了微服務內的邏輯邊界,項目初期我們可以儘可能少的拆分出過細的微服務,隨著技術的積累和時間的推移,當我們具有這些能力後,由於微服務內有清晰的邏輯邊界,這時就可以隨時根據需要輕鬆的拆分或組合出新的微服務。

十、 不同場景的微服務設計

微服務的設計先從領域建模開始,領域模型是微服務設計的核心,微服務是領域建模的結果。在微服務設計之前,請先判斷你的業務是否聚焦在領域和領域邏輯。

實際在做系統設計時我們可能面臨各種不同的情形,如從傳統單體拆分為多個微服務,也可能是一個全新領域的微服務設計(如創業中的應用),抑或是將一個單體中面臨問題或性能瓶頸的模塊拆分為微服務而其餘功能仍為單體的情況。

下面分幾類不同場景說明如何進行微服務和領域模型設計。

(一) 新建系統的微服務設計

新建系統會遇到複雜和簡單領域兩種場景,兩者的領域建模過程也會有所差別。

1、 簡單領域的建模

對於簡單的業務領域,一個領域可能就是一個小的子域。領域建模過程相對簡單,根據事件風暴可以分解出事件、命令、實體、聚合和限界上下文等,根據領域模型和微服務拆分原則設計出微服務即可。

2、 複雜領域的建模

對於複雜的業務領域,領域可能還需要拆分為子域,甚至子域還會進一步拆分,如:保險領域可以拆分為承保、理賠、收付費和再保等子域,承保子域還可以再拆分為投保、保單管理等子子域。對於這種複雜的領域模型,是無法通過一個事件風暴完成領域建模的,即使能完成,其工程量也是非常浩大,效果也不一定好。

對於這種複雜的領域,我們可以分三階段來完成領域模型和微服務設計。

1) 拆分子域建立領域模型

根據業務特點考慮流程節點或功能模塊等邊界因素(微服務最終的拆分結果很多時候跟這些邊界因素有一定的相關性),按領域逐級分解為大小合適的子域,針對子域進行事件風暴,記錄領域對象、聚合和限界上下文,初步確定各級子域的領域模型。

2) 領域模型微調

梳理領域內所有子域的領域模型,對各子域模型進行微調,這個過程重點考慮不同限界上下文內聚合的重新組合,同步需要考慮子域、限界上下文以及聚合之間的邊界、服務以及事件之間的依賴關係,確定最終的領域模型。

3) 微服務設計和拆分

根據領域的限界上下文和微服務的拆分原則,完成微服務的拆分和設計。

(二) 單體遺留系統的微服務設計

如果一個單體遺留系統,只是將面臨問題或性能瓶頸的模塊拆分為微服務,而其餘功能仍為單體。我們只需要將這些特定功能領域理解為一個簡單的子領域,按照簡單領域建模方式進行領域模型的設計即可。但在新微服務設計中需要考慮新老系統之間的服務協議,必要時引入防腐層。

(三) 特別說明

雖然有些業務領域在事件風暴後發現無法建立領域模型,如數據處理或分析類場景,但本文所述的分層架構模型、服務之間規約和代碼目錄結構在微服務設計和開發中仍然是通用的。

十一、 基於DDD的微服務設計和開發實例

為了更好的理解DDD的設計思想和過程,我們用一個場景簡單但基本涵蓋DDD設計思想的項目來說明微服務設計和開發過程。

(一)項目基本信息

項目主要目標是實現在線請假和考勤管理。基本功能包括:請假、考勤以及人員管理等。

請假:請假人填寫請假單提交審批,根據請假人身份和請假天數進行校驗,根據審批規則逐級遞交審批,核批通過則完成審批。

考勤:根據考勤規則,剔除請假數據後,對員工考勤數據進行校驗,輸出考勤統計表。

人員管理:維護人員基本信息和上下級關係。

(二)設計和實施步驟

步驟一:事件風暴

由於項目目標基本明確,我們在事件風暴過程中裁剪了產品願景,直接從用戶旅程和場景分析開始。

1. 場景分析

場景分析是一個發散的過程。根據不同角色的旅程和場景分析,儘可能全面的梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係等信息(如下圖),如:請假人員會執行創建請假信息操作命令,審批人員會執行審批操作,請假審批通過後會產生領域事件,通知郵件系統反饋請假人員結果,並將請假數據發送到考勤以便核銷等。在記錄這些領域對象的同時,我們也會標記各對象在DDD中的層和對象類型等屬性,如:應用服務、領域服務、事件和命令等類型。

基於DDD的微服務設計和開發實戰

事件風暴

2. 領域建模

領域建模是一個收斂的過程。這個收斂過程分三步:第一步根據場景分析中的操作集合定義領域實體;第二步根據領域實體業務關聯性,定義聚合;第三步根據業務及語義邊界等因素,定義限界上下文。

1) 定義領域實體

在場景分析過程中梳理完操作、命令、領域事件以及外部依賴關係等領域對象後。分析這些操作應由什麼實體發起或產生,從而定義領域實體對象,並將這些操作與實體進行關聯。

在請假場景中,經分析需要有請假單實體對象,請假單實體有創建請假信息以及修改請假信息等操作。

2) 定義聚合

將業務緊密相關的實體進行組合形成聚合,同時確定聚合中的聚合根、值對象和實體。經分析項目最終形成三個聚合:人員管理、請假和考勤。在請假聚合中有請假單、審批軌跡、審批規則等實體,其中請假單是聚合根,審批軌跡是請假單的值對象,審批規則是輔助實體。

聚合內須保證業務操作的事務性,高度內聚的實體對象可自包含完成本領域功能。聚合是可拆分為微服務的最小單元。在同一限界上下文內多個聚合可以組合為一個微服務。如有必要,也可以將某一個聚合獨立為微服務。

3) 定義限界上下文

根據領域及語義邊界等因素確定限界上下文,將同一個語義環境下的一個或者多個聚合放在一個限界上下文內。由於人員管理與請假聚合兩者業務關聯緊密,共同完成人員請假功能,兩者一起構成請假限界上下文,考勤聚合則單獨形成考勤限界上下文。

3. 微服務設計和拆分

理論上一個限界上下文可以設計為一個微服務,但還需要綜合考慮多種外部因素,如:職責單一性、性能差異、版本發佈頻率、團隊溝通效率和技術異構等要素。

由於本項目微服務設計受技術以及團隊等因素影響相對較小,主要考慮職責單一性,因此根據限界上下文直接拆分為請假和考勤兩個微服務。其中請假微服務包含人員和請假兩個聚合,考勤微服務只包含考勤聚合。

步驟二、領域對象及服務矩陣

將事件風暴中產出的領域對象按照各自所在的微服務進行分類,定義每個領域對象在微服務中的層、領域類型和依賴的領域對象等。

這個步驟最關鍵的工作是確定實體、方法、服務等領域對象在微服務分層架構中的位置以及各對象之間的依賴關係,形成服務矩陣(如下表)。這個過程也將在事件風暴數據的基礎上,進一步細化領域對象以及它們之間關係,並補充事件風暴中可能遺漏的細節。

確定完各領域對象的屬性後,按照代碼模型設計各個領域對象在代碼模型中的代碼對象(包括代碼對象所在的:包名、類名和方法名),建立領域對象與代碼對象的一一映射關係。根據這種映射關係,相關人員可快速定位到業務邏輯所在的代碼位置。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣

步驟三:領域模型及服務架構

根據領域模型中領域對象屬性以及服務矩陣,畫出領域對象及服務架構視圖(如下圖)。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

基於DDD的微服務設計和開發實戰

基礎層代碼結構

Config:主要存放配置相關代碼。

Util:主要存放平臺、開發框架、消息、數據庫、緩存、文件、總線、網關、第三方類庫、通用算法等基礎代碼,可為不同的資源類別建立不同的子目錄。

(六) 微服務總目錄結構

微服務總目錄結構如下:

基於DDD的微服務設計和開發實戰

九、 微服務設計原則

微服務設計原則中如高內聚低耦合、複用、單一職責等原則在此就不贅述了,這裡主要強調以下幾條:

第一條:“要領域驅動設計,而不是數據驅動設計,也不是界面驅動設計”。

微服務設計首先應建立領域模型,確定邏輯和物理邊界後,然後才進行微服務邊界拆分,而不是一上來就定義數據庫表結構,也不是界面需要什麼,就去調整領域邏輯代碼。

領域模型和領域服務應具有高度通用性,通過接口層和應用層屏蔽外部變化對業務邏輯的影響,保證核心業務功能的穩定性。

第二條:“要邊界清晰的微服務,而不是泥球小單體”。

微服務完成開發後其功能和代碼也不是一成不變的。隨著需求或設計變化,微服務內的代碼也會分分合合。邏輯邊界清晰的微服務,可快速實現微服務代碼的拆分和組合。DDD思想中的邏輯邊界和分層設計也是為微服務各種可能的分分合合做準備的。

微服務內聚合與聚合之間的領域服務以及數據原則上禁止相互產生依賴。如有必要可通過上層的應用服務編排或者事件驅動機制實現聚合之間的解耦,以利於聚合之間的組合和拆分。

第三條:“要職能清晰的分層,而不是什麼都放的大籮筐”。

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層調用內層服務,內層服務通過封裝、組合或編排對外逐層暴露,服務粒度由細到粗。

應用層負責服務的編排和組合,領域層負責領域業務邏輯的實現,基礎層為各層提供資源服務。

第四條:“要做自己能hold住的微服務,而不是過度拆分的微服務”。

微服務的過度拆分必然會帶來軟件維護成本的上升,如:集成成本、運維成本以及監控和定位問題的成本。企業轉型過程中很難短時間內提升這些能力,如果項目團隊不具備這些能力,將很難hold住這些過細的微服務。而如果我們在微服務設計之初就已經定義好了微服務內的邏輯邊界,項目初期我們可以儘可能少的拆分出過細的微服務,隨著技術的積累和時間的推移,當我們具有這些能力後,由於微服務內有清晰的邏輯邊界,這時就可以隨時根據需要輕鬆的拆分或組合出新的微服務。

十、 不同場景的微服務設計

微服務的設計先從領域建模開始,領域模型是微服務設計的核心,微服務是領域建模的結果。在微服務設計之前,請先判斷你的業務是否聚焦在領域和領域邏輯。

實際在做系統設計時我們可能面臨各種不同的情形,如從傳統單體拆分為多個微服務,也可能是一個全新領域的微服務設計(如創業中的應用),抑或是將一個單體中面臨問題或性能瓶頸的模塊拆分為微服務而其餘功能仍為單體的情況。

下面分幾類不同場景說明如何進行微服務和領域模型設計。

(一) 新建系統的微服務設計

新建系統會遇到複雜和簡單領域兩種場景,兩者的領域建模過程也會有所差別。

1、 簡單領域的建模

對於簡單的業務領域,一個領域可能就是一個小的子域。領域建模過程相對簡單,根據事件風暴可以分解出事件、命令、實體、聚合和限界上下文等,根據領域模型和微服務拆分原則設計出微服務即可。

2、 複雜領域的建模

對於複雜的業務領域,領域可能還需要拆分為子域,甚至子域還會進一步拆分,如:保險領域可以拆分為承保、理賠、收付費和再保等子域,承保子域還可以再拆分為投保、保單管理等子子域。對於這種複雜的領域模型,是無法通過一個事件風暴完成領域建模的,即使能完成,其工程量也是非常浩大,效果也不一定好。

對於這種複雜的領域,我們可以分三階段來完成領域模型和微服務設計。

1) 拆分子域建立領域模型

根據業務特點考慮流程節點或功能模塊等邊界因素(微服務最終的拆分結果很多時候跟這些邊界因素有一定的相關性),按領域逐級分解為大小合適的子域,針對子域進行事件風暴,記錄領域對象、聚合和限界上下文,初步確定各級子域的領域模型。

2) 領域模型微調

梳理領域內所有子域的領域模型,對各子域模型進行微調,這個過程重點考慮不同限界上下文內聚合的重新組合,同步需要考慮子域、限界上下文以及聚合之間的邊界、服務以及事件之間的依賴關係,確定最終的領域模型。

3) 微服務設計和拆分

根據領域的限界上下文和微服務的拆分原則,完成微服務的拆分和設計。

(二) 單體遺留系統的微服務設計

如果一個單體遺留系統,只是將面臨問題或性能瓶頸的模塊拆分為微服務,而其餘功能仍為單體。我們只需要將這些特定功能領域理解為一個簡單的子領域,按照簡單領域建模方式進行領域模型的設計即可。但在新微服務設計中需要考慮新老系統之間的服務協議,必要時引入防腐層。

(三) 特別說明

雖然有些業務領域在事件風暴後發現無法建立領域模型,如數據處理或分析類場景,但本文所述的分層架構模型、服務之間規約和代碼目錄結構在微服務設計和開發中仍然是通用的。

十一、 基於DDD的微服務設計和開發實例

為了更好的理解DDD的設計思想和過程,我們用一個場景簡單但基本涵蓋DDD設計思想的項目來說明微服務設計和開發過程。

(一)項目基本信息

項目主要目標是實現在線請假和考勤管理。基本功能包括:請假、考勤以及人員管理等。

請假:請假人填寫請假單提交審批,根據請假人身份和請假天數進行校驗,根據審批規則逐級遞交審批,核批通過則完成審批。

考勤:根據考勤規則,剔除請假數據後,對員工考勤數據進行校驗,輸出考勤統計表。

人員管理:維護人員基本信息和上下級關係。

(二)設計和實施步驟

步驟一:事件風暴

由於項目目標基本明確,我們在事件風暴過程中裁剪了產品願景,直接從用戶旅程和場景分析開始。

1. 場景分析

場景分析是一個發散的過程。根據不同角色的旅程和場景分析,儘可能全面的梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係等信息(如下圖),如:請假人員會執行創建請假信息操作命令,審批人員會執行審批操作,請假審批通過後會產生領域事件,通知郵件系統反饋請假人員結果,並將請假數據發送到考勤以便核銷等。在記錄這些領域對象的同時,我們也會標記各對象在DDD中的層和對象類型等屬性,如:應用服務、領域服務、事件和命令等類型。

基於DDD的微服務設計和開發實戰

事件風暴

2. 領域建模

領域建模是一個收斂的過程。這個收斂過程分三步:第一步根據場景分析中的操作集合定義領域實體;第二步根據領域實體業務關聯性,定義聚合;第三步根據業務及語義邊界等因素,定義限界上下文。

1) 定義領域實體

在場景分析過程中梳理完操作、命令、領域事件以及外部依賴關係等領域對象後。分析這些操作應由什麼實體發起或產生,從而定義領域實體對象,並將這些操作與實體進行關聯。

在請假場景中,經分析需要有請假單實體對象,請假單實體有創建請假信息以及修改請假信息等操作。

2) 定義聚合

將業務緊密相關的實體進行組合形成聚合,同時確定聚合中的聚合根、值對象和實體。經分析項目最終形成三個聚合:人員管理、請假和考勤。在請假聚合中有請假單、審批軌跡、審批規則等實體,其中請假單是聚合根,審批軌跡是請假單的值對象,審批規則是輔助實體。

聚合內須保證業務操作的事務性,高度內聚的實體對象可自包含完成本領域功能。聚合是可拆分為微服務的最小單元。在同一限界上下文內多個聚合可以組合為一個微服務。如有必要,也可以將某一個聚合獨立為微服務。

3) 定義限界上下文

根據領域及語義邊界等因素確定限界上下文,將同一個語義環境下的一個或者多個聚合放在一個限界上下文內。由於人員管理與請假聚合兩者業務關聯緊密,共同完成人員請假功能,兩者一起構成請假限界上下文,考勤聚合則單獨形成考勤限界上下文。

3. 微服務設計和拆分

理論上一個限界上下文可以設計為一個微服務,但還需要綜合考慮多種外部因素,如:職責單一性、性能差異、版本發佈頻率、團隊溝通效率和技術異構等要素。

由於本項目微服務設計受技術以及團隊等因素影響相對較小,主要考慮職責單一性,因此根據限界上下文直接拆分為請假和考勤兩個微服務。其中請假微服務包含人員和請假兩個聚合,考勤微服務只包含考勤聚合。

步驟二、領域對象及服務矩陣

將事件風暴中產出的領域對象按照各自所在的微服務進行分類,定義每個領域對象在微服務中的層、領域類型和依賴的領域對象等。

這個步驟最關鍵的工作是確定實體、方法、服務等領域對象在微服務分層架構中的位置以及各對象之間的依賴關係,形成服務矩陣(如下表)。這個過程也將在事件風暴數據的基礎上,進一步細化領域對象以及它們之間關係,並補充事件風暴中可能遺漏的細節。

確定完各領域對象的屬性後,按照代碼模型設計各個領域對象在代碼模型中的代碼對象(包括代碼對象所在的:包名、類名和方法名),建立領域對象與代碼對象的一一映射關係。根據這種映射關係,相關人員可快速定位到業務邏輯所在的代碼位置。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣

步驟三:領域模型及服務架構

根據領域模型中領域對象屬性以及服務矩陣,畫出領域對象及服務架構視圖(如下圖)。

基於DDD的微服務設計和開發實戰

領域對象及服務架構視圖

這個視圖可以作為標準的DDD分層領域服務架構視圖模型,應用在不同的領域模型中。這個模型可以清晰的體現微服務內實體、聚合之間的關係,各層服務之間的依賴關係以及應用層服務組合和編排的關係,微服務之間的服務調用以及事件驅動的前後處理邏輯關係。

在這個階段,前端的設計也可以同步進行,在這裡我們用到了微前端的設計理念,為請假和考勤微服務分別設計了請假和考勤微前端,基於微前端和微服務,形成從前端到後端的業務邏輯自包含組件。兩個微前端之上有一個集成主頁面,可根據頁面流動態加載請假和考勤的微前端頁面。

步驟四:代碼模型設計

根據DDD的代碼結構模型和各領域對象在所在的包、類和方法,定義出請假微服務的代碼結構模型。

應用層代碼結構包括:應用服務以及事件發佈相關代碼(如下圖)。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

基於DDD的微服務設計和開發實戰

基礎層代碼結構

Config:主要存放配置相關代碼。

Util:主要存放平臺、開發框架、消息、數據庫、緩存、文件、總線、網關、第三方類庫、通用算法等基礎代碼,可為不同的資源類別建立不同的子目錄。

(六) 微服務總目錄結構

微服務總目錄結構如下:

基於DDD的微服務設計和開發實戰

九、 微服務設計原則

微服務設計原則中如高內聚低耦合、複用、單一職責等原則在此就不贅述了,這裡主要強調以下幾條:

第一條:“要領域驅動設計,而不是數據驅動設計,也不是界面驅動設計”。

微服務設計首先應建立領域模型,確定邏輯和物理邊界後,然後才進行微服務邊界拆分,而不是一上來就定義數據庫表結構,也不是界面需要什麼,就去調整領域邏輯代碼。

領域模型和領域服務應具有高度通用性,通過接口層和應用層屏蔽外部變化對業務邏輯的影響,保證核心業務功能的穩定性。

第二條:“要邊界清晰的微服務,而不是泥球小單體”。

微服務完成開發後其功能和代碼也不是一成不變的。隨著需求或設計變化,微服務內的代碼也會分分合合。邏輯邊界清晰的微服務,可快速實現微服務代碼的拆分和組合。DDD思想中的邏輯邊界和分層設計也是為微服務各種可能的分分合合做準備的。

微服務內聚合與聚合之間的領域服務以及數據原則上禁止相互產生依賴。如有必要可通過上層的應用服務編排或者事件驅動機制實現聚合之間的解耦,以利於聚合之間的組合和拆分。

第三條:“要職能清晰的分層,而不是什麼都放的大籮筐”。

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層調用內層服務,內層服務通過封裝、組合或編排對外逐層暴露,服務粒度由細到粗。

應用層負責服務的編排和組合,領域層負責領域業務邏輯的實現,基礎層為各層提供資源服務。

第四條:“要做自己能hold住的微服務,而不是過度拆分的微服務”。

微服務的過度拆分必然會帶來軟件維護成本的上升,如:集成成本、運維成本以及監控和定位問題的成本。企業轉型過程中很難短時間內提升這些能力,如果項目團隊不具備這些能力,將很難hold住這些過細的微服務。而如果我們在微服務設計之初就已經定義好了微服務內的邏輯邊界,項目初期我們可以儘可能少的拆分出過細的微服務,隨著技術的積累和時間的推移,當我們具有這些能力後,由於微服務內有清晰的邏輯邊界,這時就可以隨時根據需要輕鬆的拆分或組合出新的微服務。

十、 不同場景的微服務設計

微服務的設計先從領域建模開始,領域模型是微服務設計的核心,微服務是領域建模的結果。在微服務設計之前,請先判斷你的業務是否聚焦在領域和領域邏輯。

實際在做系統設計時我們可能面臨各種不同的情形,如從傳統單體拆分為多個微服務,也可能是一個全新領域的微服務設計(如創業中的應用),抑或是將一個單體中面臨問題或性能瓶頸的模塊拆分為微服務而其餘功能仍為單體的情況。

下面分幾類不同場景說明如何進行微服務和領域模型設計。

(一) 新建系統的微服務設計

新建系統會遇到複雜和簡單領域兩種場景,兩者的領域建模過程也會有所差別。

1、 簡單領域的建模

對於簡單的業務領域,一個領域可能就是一個小的子域。領域建模過程相對簡單,根據事件風暴可以分解出事件、命令、實體、聚合和限界上下文等,根據領域模型和微服務拆分原則設計出微服務即可。

2、 複雜領域的建模

對於複雜的業務領域,領域可能還需要拆分為子域,甚至子域還會進一步拆分,如:保險領域可以拆分為承保、理賠、收付費和再保等子域,承保子域還可以再拆分為投保、保單管理等子子域。對於這種複雜的領域模型,是無法通過一個事件風暴完成領域建模的,即使能完成,其工程量也是非常浩大,效果也不一定好。

對於這種複雜的領域,我們可以分三階段來完成領域模型和微服務設計。

1) 拆分子域建立領域模型

根據業務特點考慮流程節點或功能模塊等邊界因素(微服務最終的拆分結果很多時候跟這些邊界因素有一定的相關性),按領域逐級分解為大小合適的子域,針對子域進行事件風暴,記錄領域對象、聚合和限界上下文,初步確定各級子域的領域模型。

2) 領域模型微調

梳理領域內所有子域的領域模型,對各子域模型進行微調,這個過程重點考慮不同限界上下文內聚合的重新組合,同步需要考慮子域、限界上下文以及聚合之間的邊界、服務以及事件之間的依賴關係,確定最終的領域模型。

3) 微服務設計和拆分

根據領域的限界上下文和微服務的拆分原則,完成微服務的拆分和設計。

(二) 單體遺留系統的微服務設計

如果一個單體遺留系統,只是將面臨問題或性能瓶頸的模塊拆分為微服務,而其餘功能仍為單體。我們只需要將這些特定功能領域理解為一個簡單的子領域,按照簡單領域建模方式進行領域模型的設計即可。但在新微服務設計中需要考慮新老系統之間的服務協議,必要時引入防腐層。

(三) 特別說明

雖然有些業務領域在事件風暴後發現無法建立領域模型,如數據處理或分析類場景,但本文所述的分層架構模型、服務之間規約和代碼目錄結構在微服務設計和開發中仍然是通用的。

十一、 基於DDD的微服務設計和開發實例

為了更好的理解DDD的設計思想和過程,我們用一個場景簡單但基本涵蓋DDD設計思想的項目來說明微服務設計和開發過程。

(一)項目基本信息

項目主要目標是實現在線請假和考勤管理。基本功能包括:請假、考勤以及人員管理等。

請假:請假人填寫請假單提交審批,根據請假人身份和請假天數進行校驗,根據審批規則逐級遞交審批,核批通過則完成審批。

考勤:根據考勤規則,剔除請假數據後,對員工考勤數據進行校驗,輸出考勤統計表。

人員管理:維護人員基本信息和上下級關係。

(二)設計和實施步驟

步驟一:事件風暴

由於項目目標基本明確,我們在事件風暴過程中裁剪了產品願景,直接從用戶旅程和場景分析開始。

1. 場景分析

場景分析是一個發散的過程。根據不同角色的旅程和場景分析,儘可能全面的梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係等信息(如下圖),如:請假人員會執行創建請假信息操作命令,審批人員會執行審批操作,請假審批通過後會產生領域事件,通知郵件系統反饋請假人員結果,並將請假數據發送到考勤以便核銷等。在記錄這些領域對象的同時,我們也會標記各對象在DDD中的層和對象類型等屬性,如:應用服務、領域服務、事件和命令等類型。

基於DDD的微服務設計和開發實戰

事件風暴

2. 領域建模

領域建模是一個收斂的過程。這個收斂過程分三步:第一步根據場景分析中的操作集合定義領域實體;第二步根據領域實體業務關聯性,定義聚合;第三步根據業務及語義邊界等因素,定義限界上下文。

1) 定義領域實體

在場景分析過程中梳理完操作、命令、領域事件以及外部依賴關係等領域對象後。分析這些操作應由什麼實體發起或產生,從而定義領域實體對象,並將這些操作與實體進行關聯。

在請假場景中,經分析需要有請假單實體對象,請假單實體有創建請假信息以及修改請假信息等操作。

2) 定義聚合

將業務緊密相關的實體進行組合形成聚合,同時確定聚合中的聚合根、值對象和實體。經分析項目最終形成三個聚合:人員管理、請假和考勤。在請假聚合中有請假單、審批軌跡、審批規則等實體,其中請假單是聚合根,審批軌跡是請假單的值對象,審批規則是輔助實體。

聚合內須保證業務操作的事務性,高度內聚的實體對象可自包含完成本領域功能。聚合是可拆分為微服務的最小單元。在同一限界上下文內多個聚合可以組合為一個微服務。如有必要,也可以將某一個聚合獨立為微服務。

3) 定義限界上下文

根據領域及語義邊界等因素確定限界上下文,將同一個語義環境下的一個或者多個聚合放在一個限界上下文內。由於人員管理與請假聚合兩者業務關聯緊密,共同完成人員請假功能,兩者一起構成請假限界上下文,考勤聚合則單獨形成考勤限界上下文。

3. 微服務設計和拆分

理論上一個限界上下文可以設計為一個微服務,但還需要綜合考慮多種外部因素,如:職責單一性、性能差異、版本發佈頻率、團隊溝通效率和技術異構等要素。

由於本項目微服務設計受技術以及團隊等因素影響相對較小,主要考慮職責單一性,因此根據限界上下文直接拆分為請假和考勤兩個微服務。其中請假微服務包含人員和請假兩個聚合,考勤微服務只包含考勤聚合。

步驟二、領域對象及服務矩陣

將事件風暴中產出的領域對象按照各自所在的微服務進行分類,定義每個領域對象在微服務中的層、領域類型和依賴的領域對象等。

這個步驟最關鍵的工作是確定實體、方法、服務等領域對象在微服務分層架構中的位置以及各對象之間的依賴關係,形成服務矩陣(如下表)。這個過程也將在事件風暴數據的基礎上,進一步細化領域對象以及它們之間關係,並補充事件風暴中可能遺漏的細節。

確定完各領域對象的屬性後,按照代碼模型設計各個領域對象在代碼模型中的代碼對象(包括代碼對象所在的:包名、類名和方法名),建立領域對象與代碼對象的一一映射關係。根據這種映射關係,相關人員可快速定位到業務邏輯所在的代碼位置。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣

步驟三:領域模型及服務架構

根據領域模型中領域對象屬性以及服務矩陣,畫出領域對象及服務架構視圖(如下圖)。

基於DDD的微服務設計和開發實戰

領域對象及服務架構視圖

這個視圖可以作為標準的DDD分層領域服務架構視圖模型,應用在不同的領域模型中。這個模型可以清晰的體現微服務內實體、聚合之間的關係,各層服務之間的依賴關係以及應用層服務組合和編排的關係,微服務之間的服務調用以及事件驅動的前後處理邏輯關係。

在這個階段,前端的設計也可以同步進行,在這裡我們用到了微前端的設計理念,為請假和考勤微服務分別設計了請假和考勤微前端,基於微前端和微服務,形成從前端到後端的業務邏輯自包含組件。兩個微前端之上有一個集成主頁面,可根據頁面流動態加載請假和考勤的微前端頁面。

步驟四:代碼模型設計

根據DDD的代碼結構模型和各領域對象在所在的包、類和方法,定義出請假微服務的代碼結構模型。

應用層代碼結構包括:應用服務以及事件發佈相關代碼(如下圖)。

基於DDD的微服務設計和開發實戰

應用層代碼結構

領域層代碼結構包括一個或多個聚合的實體類以及領域服務相關代碼(如下圖)。在本項目中請假微服務領域層包含了請假和人員兩個聚合。

"

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與代碼模型的一致性?或許本文能幫你找到答案。

本文是基於DDD的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務項目團隊進行設計和開發本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務視圖、數據視圖和領域事件發佈和訂閱等;第二部分講述微服務設計方法、過程、模板、代碼目錄、設計原則等內容;最後部分以一個項目為例講述基於DDD的微服務設計過程。

一、 目標

本文采用DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域對象及服務矩陣和服務架構圖,定義符合DDD分層架構思想的代碼結構模型,保證業務模型與代碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照DDD設計思想完成微服務設計和開發。

通過領域模型和DDD的分層思想,屏蔽外部變化對領域邏輯的影響,確保交付的軟件產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定運行。

二、 適用範圍

本文適用於按照DDD設計方法進行微服務設計和開發的項目及相關人員。

以下情況不適用:

1、“我們的目標是按期蓋出一棟大樓來,不要跟我提什麼方法,有這囉嗦的時間,還不如抓緊點時間搬磚,把樓給我快點蓋好!”。

2、“我的工作就是讓軟件運行起來,能工作一切就OK!我不需要那麼多約束,什麼設計方法、擴展性、業務變化、領域模型、響應能力與我無關。別耽誤工期啦!先上線再說!”。

3、“好的軟件是自己演進出來的,我們不需要設計!”。

哈哈,開個玩笑啦!其實設計不會花太多時間的!

不耽誤大家時間了,言歸正傳。

三、 DDD分層架構視圖

DDD分層架構包括:展現層、應用層、領域層和基礎層。

基於DDD的微服務設計和開發實戰

DDD分層架構模型

DDD分層架構各層職能如下:

1、 展現層

展現層負責向用戶顯示信息和解釋用戶指令。

2、 應用層

應用層是很薄的一層,主要面向用戶用例操作,協調和指揮領域對象來完成業務邏輯。應用層也是與其他系統的應用層進行交互的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域對象協調任務,使它們互相協作。應用層還可進行安全認證、權限校驗、分佈式和持久化事務控制或向外部應用發送基於事件的消息等。

3、 領域層

領域層是軟件的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域對象(實體、值對象)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。

4、 基礎層

基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供API管理,為領域層提供數據庫持久化機制。它還能通過技術框架來支持各層之間的交互。

四、 服務視圖

(一)微服務內的服務視圖

微服務內有Facade接口、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

基於DDD的微服務設計和開發實戰

服務視圖

1、 接口服務

接口服務位於用戶接口層,用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給應用層。

2、 應用服務

應用服務位於應用層。用來表述應用和用戶行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。

應用層的服務包括應用服務和領域事件相關服務。

應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如文件、緩存等數據直接操作形成應用服務,對外提供粗粒度的服務。

領域事件服務包括兩類:領域事件的發佈和訂閱。通過事件總線和消息隊列實現異步數據傳輸,實現微服務之間的解耦。

3、 領域服務

領域服務位於領域層,為完成領域中跨實體或值對象的操作轉換而封裝的服務,領域服務以與實體和值對象相同的方式參與實施過程。

領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。

為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。

為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務調用和跨聚合的數據相互關聯。

4、 基礎服務

基礎服務位於基礎層。為各層提供資源服務(如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。

基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務調用倉儲服務接口,利用倉儲實現持久化數據對象或直接訪問基礎資源。

(二)微服務外的服務視圖

1. 前端應用與微服務

微服務中的應用服務通過用戶接口層組裝和數據轉換後,發佈在API網關,為前端應用提供數據展示服務。

2. 微服務與外部應用

跨微服務數據處理時,對實時性要求高的場景,可選擇直接調用應用服務的方式(新增和修改類型操作需關注事務一致性)。對實時性要求不高的場景,可選擇異步化的領域事件驅動機制(最終數據一致性)。

五、 數據視圖

DDD分層架構中數據對象轉換的過程如下圖。

基於DDD的微服務設計和開發實戰

數據視圖

應用服務通過數據傳輸對象(DTO)完成外部數據交換。領域層通過領域對象(DO)作為領域實體和值對象的數據和行為載體。基礎層利用持久化對象(PO)完成數據庫的交換。

DTO與VO通過Restful協議實現JSON格式和對象轉換。

前端應用與應用層之間DTO與DO的轉換髮生在用戶接口層。如微服務內應用服務需調用外部微服務的應用服務,則DTO的組裝和DTO與DO的轉換髮生在應用層。

領域層DO與PO的轉換髮生在基礎層。

六、 領域事件和事件總線

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

基於DDD的微服務設計和開發實戰

領域事件和事件總線

(一)微服務內的領域事件

微服務內的領域事件可以通過事件總線或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的集成發生在同一個線程內,不一定需要引入消息中間件。但一個事件如果同時更新多個聚合數據,按照DDD“一個事務只更新一個聚合根”的原則,可以考慮引入消息中間件,通過異步化的方式,對微服務內不同的聚合根採用不同的事務。

(二)微服務之間的領域事件

微服務之間的數據交互方式通常有兩種:應用服務調用和領域事件驅動機制。

領域事件驅動機制更多的用於不同微服務之間的集成,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的數據對賬,在應用、網絡等出現問題後,可以實現源和目的端的數據比對,在數據暫時不一致的情況下仍可根據這些數據完成後續業務處理流程,保證微服務之間數據的最終一致性。

應用服務調用方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的數據修改,將會增加分佈式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

(三)事件總線

事件總線位於基礎層,為應用層和領域層服務提供事件消息接收和分發等服務。其大致流程如下:

1. 服務觸發併發布事件。

2. 事件總線事件分發。

1) 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。

2) 如果是微服務外的訂閱者,則事件消息先保存到事件庫(表)並異步發送到消息中間件。

3) 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件消息保存到事件庫(表)並異步發送到消息中間件。

為了保證事務的一致性,事件表可以共享業務數據庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件發佈操作跨數據庫時,須保證業務操作和事件發佈操作數據的強一致性。

(四)事件數據持久化

事件數據的持久化存儲可以有兩種方案,在項目實施過程中根據具體場景選擇最佳方案。

1、事件數據保存到微服務所在業務數據庫的事件表中,利用本地事務保證業務操作和事件發佈操作的強一致性。

2、事件數據保存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件發佈操作會跨數據庫操作,須保證事務的強一致性(如分佈式事務機制)。

事件數據的持久化可以保證數據的完整性,基於這些數據可以完成跨微服務數據的對賬操作。

七、 微服務設計方法

(一)事件風暴

本階段主要完成領域模型設計。

基於DDD的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域對象,指導微服務設計和開發。

事件風暴通常包括產品願景、場景分析、領域建模和領域對象和服務矩陣等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

建議參與角色:業務需求方、產品經理和開發組長。

2、場景分析

場景分析是從用戶視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的對象設計。

建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和集成關係進行梳理,完成微服務拆分和設計。

微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等因素。

建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

(二)領域對象及服務矩陣和代碼模型設計

本階段完成領域對象及服務矩陣文檔以及微服務代碼模型設計。

1、領域對象及服務矩陣

在微服務拆分完成後,根據事件風暴過程領域對象和關係,對微服務內聚合、實體、值對象、倉儲、事件、應用服務、領域服務等領域對象以及各對象之間的依賴關係進行梳理,確定各對象在分層架構中的位置和依賴關係,建立領域對象分層架構視圖,為每個領域對象建立與代碼模型對象的一一映射。

建議參與角色:架構師和開發組長。

2、微服務代碼模型

根據領域對象在DDD分層架構中所在的層、領域類型、與代碼對象的映射關係,定義領域對象在微服務代碼模型中的包、類和方法名稱等,設計微服務工程的代碼層級和代碼結構,明確各層間的調用關係。

建議參與角色:架構師和開發組長。

(三)領域對象及服務矩陣

領域對象及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域對象屬性,如:各領域對象在DDD分層架構中的位置、屬性、依賴關係以及與代碼對象的映射關係等。通過建立領域對象與代碼對象的映射關係,可指導軟件開發人員準確無誤的按照設計文檔完成微服務開發。

以下為領域對象及服務矩陣樣例(部分數據,僅供參考)。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣樣例

各欄說明如下:

層:定義領域對象位於DDD分層架構中的哪一層。如:接口層、應用層、領域層以及基礎層等。

聚合:在事件風暴過程中將關聯緊密的實體和值對象等組合形成聚合。本欄說明聚合名稱。

領域對象名稱:領域模型中領域對象的具體名稱。如:“請假審批已通過”是類型為“事件”的領域對象;“請假單”是領域類型為“實體”的領域對象。

領域類型:在領域模型中根據DDD知識域定義的領域對象的類型,如:限界上下文、聚合、聚合根(實體)、實體、值對象、事件、命令、應用服務、領域服務和倉儲服務等。

依賴對象名稱:根據業務對象依賴或分層調用依賴關係建立的領域對象的依賴關係(如服務調用依賴、關聯對象聚合等)。本欄說明領域對象需依賴的其他領域對象,如上層服務在組合和編排過程中對下層服務的調用依賴、實體之間或者實體與值對象在聚合內的依賴等。

包名:代碼模型中的包名,本欄說明領域對象所在的軟件包。

類名:代碼模型中的類名,本欄說明領域對象的類名。

方法名:代碼模型中的方法名,本欄說明領域對象實現或操作的方法名。

八、 微服務代碼結構模型

微服務代碼模型最終結果來源於領域對象及服務矩陣。在代碼模型設計時須建立領域對象和代碼對象的一一映射,保證業務模型與代碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉代碼的業務人員也可以很快定位到代碼位置。

(一)微服務代碼總目錄

基於DDD的代碼模型包括interfaces、application、domain和infrastructure四個目錄。

基於DDD的微服務設計和開發實戰

微服務目錄結構

Interfaces(用戶接口層):本目錄主要存放用戶接口層代碼。前端應用通過本層嚮應用服務獲取展現所需的數據。本層主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層。主要代碼形態是數據組裝以及Facade接口等。

Application(應用層):本目錄主要存放應用層代碼。應用服務代碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為用戶接口層提供各種應用數據展現支持。主要代碼形態是應用服務和領域事件等。

Domain(領域層):本目錄主要存放領域層代碼。本層代碼主要實現核心領域邏輯,其主要代碼形態是實體類方法和領域服務等。

Infrastructure(基礎層):本目錄存放基礎層代碼,為其它各層提供通用技術能力、三方軟件包、配置和基礎資源服務等。

(二) 用戶接口層代碼模型

用戶接口層代碼模型目錄包括:assembler、dto和facade。

基於DDD的微服務設計和開發實戰

用戶接口層代碼結構

Assembler:實現DTO與領域對象之間的相互轉換和數據交換。理論上Assembler總是與DTO一同被使用。

Dto:數據傳輸的載體,內部不存在任何業務邏輯,通過DTO把內部的領域對象與外界隔離。

Facade:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理。

(三) 應用層代碼模型

應用層代碼模型目錄包括:event和service。

基於DDD的微服務設計和開發實戰

應用層代碼結構

Event(事件):事件目錄包括兩個子目錄:publish和subscribe。publish目錄主要存放微服務內領域事件發佈相關代碼。subscribe目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關代碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的發佈和訂閱處理都統一放在應用層。

Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

(四) 領域層代碼模型

微服務領域層包括一個或多個聚合代碼包。標準的聚合代碼模型包括:entity、repository和service三個子目錄。

基於DDD的微服務設計和開發實戰

領域層代碼結構

Aggregate(聚合):聚合代碼包的根目錄,實際項目中以實際業務屬性的名稱來命名。聚合定義了領域對象之間的關係和邊界,實現領域模型的內聚。

Entity(實體):存放實體(含聚合根、實體和值對象)相關代碼。同一實體所有相關的代碼(含對同一實體類多個對象操作的方法,如對多個對象的count等)都放在一個實體類中。

Service(領域服務):存放對多個不同實體對象操作的領域服務代碼。這部分代碼以領域服務的形式存在,在設計時一個領域服務對應一個類。

Repository(倉儲):存放聚合對應的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。

特別說明:按照DDD分層原則,倉儲實現本應屬於基礎層代碼,但為了微服務代碼拆分和重組的便利性,我們把聚合的倉儲實現代碼放到了領域層對應的聚合代碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合代碼包為單位,輕鬆實現微服務聚合的拆分和組合。

(五) 基礎層代碼模型

基礎層代碼模型包括:config和util兩個子目錄

基於DDD的微服務設計和開發實戰

基礎層代碼結構

Config:主要存放配置相關代碼。

Util:主要存放平臺、開發框架、消息、數據庫、緩存、文件、總線、網關、第三方類庫、通用算法等基礎代碼,可為不同的資源類別建立不同的子目錄。

(六) 微服務總目錄結構

微服務總目錄結構如下:

基於DDD的微服務設計和開發實戰

九、 微服務設計原則

微服務設計原則中如高內聚低耦合、複用、單一職責等原則在此就不贅述了,這裡主要強調以下幾條:

第一條:“要領域驅動設計,而不是數據驅動設計,也不是界面驅動設計”。

微服務設計首先應建立領域模型,確定邏輯和物理邊界後,然後才進行微服務邊界拆分,而不是一上來就定義數據庫表結構,也不是界面需要什麼,就去調整領域邏輯代碼。

領域模型和領域服務應具有高度通用性,通過接口層和應用層屏蔽外部變化對業務邏輯的影響,保證核心業務功能的穩定性。

第二條:“要邊界清晰的微服務,而不是泥球小單體”。

微服務完成開發後其功能和代碼也不是一成不變的。隨著需求或設計變化,微服務內的代碼也會分分合合。邏輯邊界清晰的微服務,可快速實現微服務代碼的拆分和組合。DDD思想中的邏輯邊界和分層設計也是為微服務各種可能的分分合合做準備的。

微服務內聚合與聚合之間的領域服務以及數據原則上禁止相互產生依賴。如有必要可通過上層的應用服務編排或者事件驅動機制實現聚合之間的解耦,以利於聚合之間的組合和拆分。

第三條:“要職能清晰的分層,而不是什麼都放的大籮筐”。

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層調用內層服務,內層服務通過封裝、組合或編排對外逐層暴露,服務粒度由細到粗。

應用層負責服務的編排和組合,領域層負責領域業務邏輯的實現,基礎層為各層提供資源服務。

第四條:“要做自己能hold住的微服務,而不是過度拆分的微服務”。

微服務的過度拆分必然會帶來軟件維護成本的上升,如:集成成本、運維成本以及監控和定位問題的成本。企業轉型過程中很難短時間內提升這些能力,如果項目團隊不具備這些能力,將很難hold住這些過細的微服務。而如果我們在微服務設計之初就已經定義好了微服務內的邏輯邊界,項目初期我們可以儘可能少的拆分出過細的微服務,隨著技術的積累和時間的推移,當我們具有這些能力後,由於微服務內有清晰的邏輯邊界,這時就可以隨時根據需要輕鬆的拆分或組合出新的微服務。

十、 不同場景的微服務設計

微服務的設計先從領域建模開始,領域模型是微服務設計的核心,微服務是領域建模的結果。在微服務設計之前,請先判斷你的業務是否聚焦在領域和領域邏輯。

實際在做系統設計時我們可能面臨各種不同的情形,如從傳統單體拆分為多個微服務,也可能是一個全新領域的微服務設計(如創業中的應用),抑或是將一個單體中面臨問題或性能瓶頸的模塊拆分為微服務而其餘功能仍為單體的情況。

下面分幾類不同場景說明如何進行微服務和領域模型設計。

(一) 新建系統的微服務設計

新建系統會遇到複雜和簡單領域兩種場景,兩者的領域建模過程也會有所差別。

1、 簡單領域的建模

對於簡單的業務領域,一個領域可能就是一個小的子域。領域建模過程相對簡單,根據事件風暴可以分解出事件、命令、實體、聚合和限界上下文等,根據領域模型和微服務拆分原則設計出微服務即可。

2、 複雜領域的建模

對於複雜的業務領域,領域可能還需要拆分為子域,甚至子域還會進一步拆分,如:保險領域可以拆分為承保、理賠、收付費和再保等子域,承保子域還可以再拆分為投保、保單管理等子子域。對於這種複雜的領域模型,是無法通過一個事件風暴完成領域建模的,即使能完成,其工程量也是非常浩大,效果也不一定好。

對於這種複雜的領域,我們可以分三階段來完成領域模型和微服務設計。

1) 拆分子域建立領域模型

根據業務特點考慮流程節點或功能模塊等邊界因素(微服務最終的拆分結果很多時候跟這些邊界因素有一定的相關性),按領域逐級分解為大小合適的子域,針對子域進行事件風暴,記錄領域對象、聚合和限界上下文,初步確定各級子域的領域模型。

2) 領域模型微調

梳理領域內所有子域的領域模型,對各子域模型進行微調,這個過程重點考慮不同限界上下文內聚合的重新組合,同步需要考慮子域、限界上下文以及聚合之間的邊界、服務以及事件之間的依賴關係,確定最終的領域模型。

3) 微服務設計和拆分

根據領域的限界上下文和微服務的拆分原則,完成微服務的拆分和設計。

(二) 單體遺留系統的微服務設計

如果一個單體遺留系統,只是將面臨問題或性能瓶頸的模塊拆分為微服務,而其餘功能仍為單體。我們只需要將這些特定功能領域理解為一個簡單的子領域,按照簡單領域建模方式進行領域模型的設計即可。但在新微服務設計中需要考慮新老系統之間的服務協議,必要時引入防腐層。

(三) 特別說明

雖然有些業務領域在事件風暴後發現無法建立領域模型,如數據處理或分析類場景,但本文所述的分層架構模型、服務之間規約和代碼目錄結構在微服務設計和開發中仍然是通用的。

十一、 基於DDD的微服務設計和開發實例

為了更好的理解DDD的設計思想和過程,我們用一個場景簡單但基本涵蓋DDD設計思想的項目來說明微服務設計和開發過程。

(一)項目基本信息

項目主要目標是實現在線請假和考勤管理。基本功能包括:請假、考勤以及人員管理等。

請假:請假人填寫請假單提交審批,根據請假人身份和請假天數進行校驗,根據審批規則逐級遞交審批,核批通過則完成審批。

考勤:根據考勤規則,剔除請假數據後,對員工考勤數據進行校驗,輸出考勤統計表。

人員管理:維護人員基本信息和上下級關係。

(二)設計和實施步驟

步驟一:事件風暴

由於項目目標基本明確,我們在事件風暴過程中裁剪了產品願景,直接從用戶旅程和場景分析開始。

1. 場景分析

場景分析是一個發散的過程。根據不同角色的旅程和場景分析,儘可能全面的梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係等信息(如下圖),如:請假人員會執行創建請假信息操作命令,審批人員會執行審批操作,請假審批通過後會產生領域事件,通知郵件系統反饋請假人員結果,並將請假數據發送到考勤以便核銷等。在記錄這些領域對象的同時,我們也會標記各對象在DDD中的層和對象類型等屬性,如:應用服務、領域服務、事件和命令等類型。

基於DDD的微服務設計和開發實戰

事件風暴

2. 領域建模

領域建模是一個收斂的過程。這個收斂過程分三步:第一步根據場景分析中的操作集合定義領域實體;第二步根據領域實體業務關聯性,定義聚合;第三步根據業務及語義邊界等因素,定義限界上下文。

1) 定義領域實體

在場景分析過程中梳理完操作、命令、領域事件以及外部依賴關係等領域對象後。分析這些操作應由什麼實體發起或產生,從而定義領域實體對象,並將這些操作與實體進行關聯。

在請假場景中,經分析需要有請假單實體對象,請假單實體有創建請假信息以及修改請假信息等操作。

2) 定義聚合

將業務緊密相關的實體進行組合形成聚合,同時確定聚合中的聚合根、值對象和實體。經分析項目最終形成三個聚合:人員管理、請假和考勤。在請假聚合中有請假單、審批軌跡、審批規則等實體,其中請假單是聚合根,審批軌跡是請假單的值對象,審批規則是輔助實體。

聚合內須保證業務操作的事務性,高度內聚的實體對象可自包含完成本領域功能。聚合是可拆分為微服務的最小單元。在同一限界上下文內多個聚合可以組合為一個微服務。如有必要,也可以將某一個聚合獨立為微服務。

3) 定義限界上下文

根據領域及語義邊界等因素確定限界上下文,將同一個語義環境下的一個或者多個聚合放在一個限界上下文內。由於人員管理與請假聚合兩者業務關聯緊密,共同完成人員請假功能,兩者一起構成請假限界上下文,考勤聚合則單獨形成考勤限界上下文。

3. 微服務設計和拆分

理論上一個限界上下文可以設計為一個微服務,但還需要綜合考慮多種外部因素,如:職責單一性、性能差異、版本發佈頻率、團隊溝通效率和技術異構等要素。

由於本項目微服務設計受技術以及團隊等因素影響相對較小,主要考慮職責單一性,因此根據限界上下文直接拆分為請假和考勤兩個微服務。其中請假微服務包含人員和請假兩個聚合,考勤微服務只包含考勤聚合。

步驟二、領域對象及服務矩陣

將事件風暴中產出的領域對象按照各自所在的微服務進行分類,定義每個領域對象在微服務中的層、領域類型和依賴的領域對象等。

這個步驟最關鍵的工作是確定實體、方法、服務等領域對象在微服務分層架構中的位置以及各對象之間的依賴關係,形成服務矩陣(如下表)。這個過程也將在事件風暴數據的基礎上,進一步細化領域對象以及它們之間關係,並補充事件風暴中可能遺漏的細節。

確定完各領域對象的屬性後,按照代碼模型設計各個領域對象在代碼模型中的代碼對象(包括代碼對象所在的:包名、類名和方法名),建立領域對象與代碼對象的一一映射關係。根據這種映射關係,相關人員可快速定位到業務邏輯所在的代碼位置。

基於DDD的微服務設計和開發實戰

領域對象及服務矩陣

步驟三:領域模型及服務架構

根據領域模型中領域對象屬性以及服務矩陣,畫出領域對象及服務架構視圖(如下圖)。

基於DDD的微服務設計和開發實戰

領域對象及服務架構視圖

這個視圖可以作為標準的DDD分層領域服務架構視圖模型,應用在不同的領域模型中。這個模型可以清晰的體現微服務內實體、聚合之間的關係,各層服務之間的依賴關係以及應用層服務組合和編排的關係,微服務之間的服務調用以及事件驅動的前後處理邏輯關係。

在這個階段,前端的設計也可以同步進行,在這裡我們用到了微前端的設計理念,為請假和考勤微服務分別設計了請假和考勤微前端,基於微前端和微服務,形成從前端到後端的業務邏輯自包含組件。兩個微前端之上有一個集成主頁面,可根據頁面流動態加載請假和考勤的微前端頁面。

步驟四:代碼模型設計

根據DDD的代碼結構模型和各領域對象在所在的包、類和方法,定義出請假微服務的代碼結構模型。

應用層代碼結構包括:應用服務以及事件發佈相關代碼(如下圖)。

基於DDD的微服務設計和開發實戰

應用層代碼結構

領域層代碼結構包括一個或多個聚合的實體類以及領域服務相關代碼(如下圖)。在本項目中請假微服務領域層包含了請假和人員兩個聚合。

基於DDD的微服務設計和開發實戰

領域層代碼結構

領域模型中的一個聚合對應一個聚合代碼包,如:人員和請假領域邏輯代碼都放在各自的聚合代碼包中,如隨著業務發展,人員管理功能需要從請假微服務中拆分出來,我們只需要將人員聚合代碼包稍加改造並獨立部署即可快速發佈為微服務。

步驟五:詳細設計

在完成領域模型和代碼模型設計後,就可以開始詳細設計了,詳細設計主要結合具體的業務功能來開展,主要工作包括:系統界面、數據庫表以及字段、服務參數規約及功能等。

步驟六:代碼開發

軟件開發人員只需要按照設計文檔和功能要求,找到業務功能對應的代碼位置,完成代碼開發和服務編排即可。

步驟七:測試和發佈

完成代碼開發後,由開發人員編寫單元測試用例,基於擋板模擬依賴對象完成跨服務的測試。單元測試完成後,在團隊內可進一步完成微服務與相應微前端的集成和測試,形成請假和考勤兩個業務組件。

前端主頁面完成請假和考勤微前端頁面集成和頁面流及組件基礎數據配置,主頁面可以按照頁面流程動態加載請假和考勤微前端頁面。

最終部署的軟件包包括:請假和考勤兩個微服務,請假和考勤兩個微前端,主頁面一個共計五個。這五個部署包獨立開發、獨立運行和獨立部署。

(三)技術組件說明

主頁面和微前端採用:Vue(前端框架),ElementUI(UI框架-PC),VUX(UI框架-移動端)、MPVUE(UI框架-小程序)。

微服務開發採用:Spring Cloud、Kafka等。

數據庫採用:PostgreSQL。

附錄一:DDD名詞和術語

Event Storming(事件風暴):事件風暴是一項團隊活動,旨在通過領域事件識別出聚合根,進而劃分微服務的限界上下文。在活動中,團隊先通過頭腦風暴的形式羅列出領域中所有的領域事件,整合之後形成最終的領域事件集合,然後對於每一個事件,標註出導致該事件的命令(Command),再然後為每個事件標註出命令發起方的角色,命令可以是用戶發起,也可以是第三方系統調用或者是定時器觸發等。最後對事件進行分類整理出聚合根以及限界上下文。

Entity(實體):每個實體是唯一的,並且可以相當長的一段時間內持續地變化。我們可以對實體做多次修改,故一個實體對象可能和它先前的狀態大不相同。但是,由於它們擁有相同的身份標識,他們依然是同一個實體。例如一件商品在電商商品上下文中是一個實體,通過商品中臺唯一的商品id來標示這個實體。

ValueObject(值對象):值對象用於度量和描述事物,當你只關心某個對象的屬性時,該對象便可作為一個值對象。實體與值對象的區別在於唯一的身份標識和可變性。當一個對象用於描述一個事物,但是又沒有唯一標示,那麼它就是一個值對象。例如商品中的商品類別,類別就沒有一個唯一標識,通過圖書、服裝等這些值就能明確表示這個商品類別。

Aggregate(聚合):聚合是實體的升級,是由一組與生俱來就密切相關實體和值對象組合而成的,整個組合的最上層實體就是聚合。

Bounded Context(限界上下文):用來封裝通用語言和領域對象,為領域提供上下文語境,保證在領域之內的一些術語、業務相關對象等(通用語言)有一個確切的含義,沒有二義性。使團隊所有成員能夠明確地知道什麼必須保持一致,什麼必須獨立開發。

原文鏈接: https://www.jianshu.com/p/b5abfb3cc0ce

"

相關推薦

推薦中...