DDD學習筆記 - 實戰篇(Ⅱ)

 

14 | 代碼模型(下):如何保證領域模型與代碼模型的一致性?

課程鏈接:https://time.geekbang.org/column/article/166147

 

DDD 強調先構建領域模型然後設計微服務,以保證領域模型和微服務的一體性,因此不能脫離領域模型來談微服務的設計和落地。但在構建領域模型時,往往是站在業務視角的,並且有些領域對象還帶着業務語言。還需要將領域模型作爲微服務設計的輸入,對領域對象進行設計和轉換,讓領域對象與代碼對象建立映射關係。

 

領域對象的整理

完成微服務拆分後,領域模型的邊界和領域對象就基本確定了。第一個重要的工作就是,整理事件風暴過程中產生的各個領域對象,比如:聚合、實體、命令和領域事件等內容,將這些領域對象和業務行爲記錄到下面的表格中。這張表格裏包含了:領域模型、聚合、領域對象和領域類型四個維度。一個領域模型會包含多個聚合,一個聚合包含多個領域對象,每個領域對象都有自己的領域類型。領域類型主要標識領域對象的屬性,比如:聚合根、實體、命令和領域事件等類型。

 

從領域模型到微服務的設計

從領域模型到微服務落地,還需要做進一步的設計和分析。事件風暴中提取的領域對象,還需要經過用戶故事或領域故事分析,以及微服務設計,才能用於微服務系統開發。這個過程會比事件風暴來的更深入和細緻。主要關注內容如下:

  • 分析微服務內有哪些服務?
  • 服務所在的分層?
  • 應用服務由哪些服務組合和編排完成?
  • 領域服務包括哪些實體的業務邏輯?
  • 採用充血模型的實體有哪些屬性和方法?
  • 有哪些值對象?
  • 哪個實體是聚合根等?
  • 最後梳理出所有的領域對象和它們之間的依賴關係,給每個領域對象設計對應的代碼對象,定義它們所在的軟件包和代碼目錄。

這個設計過程建議參與的角色有:DDD 專家、架構師、設計人員和開發經理。

 

領域層的領域對象

事件風暴結束時,領域模型聚合內一般會有:聚合、實體、命令和領域事件等領域對象。在完成故事分析和微服務設計後,微服務的聚合內一般會有:聚合、聚合根、實體、值對象、領域事件、領域服務和倉儲等領域對象。這些領域對象是怎麼得來的?

1. 設計實體

大多數情況下,領域模型的業務實體與微服務的數據庫實體是一一對應的。但某些領域模型的實體在微服務設計時,可能會被設計爲多個數據實體,或者實體的某些屬性被設計爲值對象。

分析個人客戶時,還需要有地址、電話和銀行賬號等實體,它們被聚合根引用,不容易在領域建模時發現,需要在微服務設計過程中識別和設計出來。

在分層架構裏,實體採用充血模型,在實體類內實現實體的全部業務邏輯。這些不同的實體都有自己的方法和業務行爲,比如地址實體有新增和修改地址的方法,銀行賬號實體有新增和修改銀行賬號的方法。

實體類放在領域層的 Entity 目錄結構下。

2. 找出聚合根

聚合根來源於領域模型,在個人客戶聚合裏,個人客戶這個實體是聚合根,它負責管理地址、電話以及銀行賬號的生命週期。個人客戶聚合根通過工廠和倉儲模式,實現聚合內地址、銀行賬號等實體和值對象數據的初始化和持久化。

聚合根是一種特殊的實體,它有自己的屬性和方法。聚合根可以實現聚合之間的對象引用,還可以引用聚合內的所有實體。聚合根類放在代碼模型的 Entity 目錄結構下。聚合根有自己的實現方法,比如生成客戶編碼,新增和修改客戶信息等方法。

3. 設計值對象

根據需要將某些實體的某些屬性或屬性集設計爲值對象。值對象類放在代碼模型的 Entity 目錄結構下。在個人客戶聚合中,客戶擁有客戶證件類型,它是以枚舉值的形式存在,所以將它設計爲值對象。

有些領域對象可以設計爲值對象,也可以設計爲實體,需要根據具體情況來分析。如果這個領域對象在其它聚合內維護生命週期,且在它依附的實體對象中只允許整體替換,就可以將它設計爲值對象。如果這個對象是多條且需要基於它做查詢統計,建議將它設計爲實體。

4. 設計領域事件

如果領域模型中領域事件會觸發下一步的業務操作,就需要設計領域事件。首先確定領域事件發生在微服務內還是微服務之間。然後設計事件實體對象,事件的發佈和訂閱機制,以及事件的處理機制。判斷是否需要引入事件總線或消息中間件。

在個人客戶聚合中有客戶已創建的領域事件,因此它有客戶創建事件這個實體。

領域事件實體和處理類放在領域層的 Event 目錄結構下。領域事件的發佈和訂閱類建議放在應用層的 Event 目錄結構下。

5. 設計領域服務

如果一個業務動作或行爲跨多個實體,就需要設計領域服務。領域服務通過對多個實體和實體方法進行組合,完成核心業務邏輯。可以認爲領域服務是位於實體方法之上和應用服務之下的一層業務邏輯。

按照嚴格分層架構層的依賴關係,如果實體的方法需要暴露給應用層,它需要封裝成領域服務後纔可以被應用服務調用。所以如果有的實體方法需要被前端應用調用,會將它封裝成領域服務,然後再封裝爲應用服務。

個人客戶聚合根這個實體創建個人客戶信息的方法,被封裝爲創建個人客戶信息領域服務。然後再被封裝爲創建個人客戶信息應用服務,向前端應用暴露。

領域服務類放在領域層的 Service 目錄結構下。

6. 設計倉儲

每一個聚合都有一個倉儲,倉儲主要用來完成數據查詢和持久化操作。倉儲包括倉儲的接口和倉儲實現,通過依賴倒置實現應用業務邏輯與數據庫資源邏輯的解耦。

倉儲代碼放在領域層的 Repository 目錄結構下。

 

應用層的領域對象

應用層的主要領域對象是應用服務和事件的發佈以及訂閱。

在事件風暴或領域故事分析時,往往會根據用戶或系統發起的命令,來設計服務或實體方法。爲了響應這個命令,需要分析和記錄:

  • 在應用層和領域層分別會發生哪些業務行爲;
  • 各層分別需要設計哪些服務或者方法;
  • 這些方法和服務的分層以及領域類型(比如實體方法、領域服務和應用服務等),它們之間的調用和組合的依賴關係。

在嚴格分層架構模式下,不允許服務的跨層調用,每個服務只能調用它的下一層服務。服務從下到上依次爲:實體方法、領域服務和應用服務。如果需要實現服務的跨層調用,應該怎麼辦?建議採用服務逐層封裝的方式。

服務的封裝和調用主要有以下幾種方式。

1. 實體方法的封裝

實體方法是最底層的原子業務邏輯。如果單一實體的方法需要被跨層調用,可以將它封裝成領域服務,這樣封裝的領域服務就可以被應用服務調用和編排了。如果它還需要被用戶接口層調用,還需要將這個領域服務封裝成應用服務。經過逐層服務封裝,實體方法就可以暴露給上面不同的層,實現跨層調用。

封裝時服務前面的名字可以保持一致,可以用 *DomainService 或 *AppService 後綴來區分領域服務或應用服務。

2. 領域服務的組合和封裝

領域服務會對多個實體和實體方法進行組合和編排,供應用服務調用。如果它需要暴露給用戶接口層,領域服務就需要封裝成應用服務。

3. 應用服務的組合和編排

應用服務會對多個領域服務進行組合和編排,暴露給用戶接口層,供前端應用調用。

在應用服務組合和編排時,需要關注一個現象:多個應用服務可能會對多個同樣的領域服務重複進行同樣業務邏輯的組合和編排。當出現這種情況時,就需要分析是不是領域服務可以整合了。可以將這幾個不斷重複組合的領域服務,合併到一個領域服務中實現。這樣既省去了應用服務的反覆編排,也實現了服務的演進。這樣領域模型將會越來越精煉,更能適應業務的要求。

應用服務類放在應用層 Service 目錄結構下。領域事件的發佈和訂閱類放在應用層 Event 目錄結構下。

 

領域對象與微服務代碼對象的映射

在完成上面的分析和設計後,就可以建立像下圖一樣的,領域對象與微服務代碼對象的映射關係了。

典型的領域模型

個人客戶領域模型中的個人客戶聚合,就是典型的領域模型,從聚合內可以提取出多個實體和值對象以及它的聚合根。

看一下下面這個圖,對個人客戶聚合做了進一步的分析。提取了個人客戶表單這個聚合根,形成了客戶類型值對象,以及電話、地址、銀行賬號等實體,爲實體方法和服務做了封裝和分層,建立了領域對象的關聯和依賴關係,還有倉儲等設計。關鍵是這個過程,建立了領域對象與微服務代碼對象的映射關係。

表格說明:

  • 層:定義領域對象位於分層架構中的哪一層,比如:接口層、應用層、領域層以及基礎層等。
  • 領域對象:領域模型中領域對象的具體名稱。
  • 領域類型:根據 DDD 知識體系定義的領域對象的類型,包括:限界上下文、聚合、聚合根、實體、值對象、領域事件、應用服務、領域服務和倉儲服務等領域類型。
  • 依賴的領域對象:根據業務對象依賴或分層調用的依賴關係,建立的領域對象的依賴關係,比如:服務調用依賴、關聯對象聚合等。
  • 包名:代碼模型中的包名,對應領域對象所在的軟件包。
  • 類名:代碼模型中的類名,對應領域對象的類名。
  • 方法名:代碼模型中的方法名,對應領域對象實現或操作的方法名。

在建立這種映射關係後,就可以得到如下圖的微服務代碼結構了。

 

非典型領域模型

有些業務場景可能並不能如你所願,可能無法設計出典型的領域模型。這類業務中有多個實體,實體之間相互獨立,是松耦合的關係,這些實體主要參與分析或者計算,找不出聚合根,但就業務本身來說它們是高內聚的。而它們所組合的業務與其它聚合是在一個限界上下文內,也不大可能將它單獨設計爲一個微服務。

這種業務場景其實很常見。比如,在個人客戶領域模型內有客戶歸併的聚合,它掃描所有客戶,按照身份證號碼、電話號碼等是否重複的業務規則,判斷是否是重複的客戶,然後對重複的客戶進行歸併。這種業務場景就找不到聚合根。

那對於這類非典型模型,怎麼辦?

還是可以借鑑聚合的思想,仍然用聚合來定義這部分功能,並採用與典型領域模型同樣的分析方法,建立實體的屬性和方法,對方法和服務進行封裝和分層設計,設計倉儲,建立領域對象之間的依賴關係。唯一可惜的就是依然找不到聚合根,不過也沒關係,除了聚合根管理功能外,還可以用 DDD 的其它設計方法。

 

=======================================================================================================

15 | 邊界:微服務的各種邊界在架構演進中的作用?

課程鏈接:https://time.geekbang.org/column/article/166635

 

微服務的設計要涉及到邏輯邊界、物理邊界和代碼邊界等等。

 

演進式架構

微服務的一個重要特徵——演進式架構。就是以支持增量的、非破壞的變更作爲第一原則,同時支持在應用程序結構層面的多維度變化。

判斷微服務設計是否合理:看他是否滿足下面的情形:隨着業務的發展或需求的變更,在不斷重新拆分或者組合成新的微服務的過程中,不會大幅增加軟件開發和維護的成本,並且這個架構演進的過程是非常輕鬆、簡單的。

這也是微服務設計的重點,就是看微服務設計是否能夠支持架構長期、輕鬆的演進。

用 DDD 方法設計的微服務,不僅可以通過限界上下文和聚合實現微服務內外的解耦,同時也可以很容易地實現業務功能積木式模塊的重組和更新,從而實現架構演進。

 

微服務還是小單體?

有些項目團隊在將集中式單體應用拆分爲微服務時,首先進行的往往不是建立領域模型,而只是按照業務功能將原來單體應用的一個軟件包拆分成多個所謂的“微服務”軟件包,而這些“微服務”內的代碼仍然是集中式三層架構的模式,“微服務”內的代碼高度耦合,邏輯邊界不清晰,這裏暫且稱它爲“小單體微服務”。

而隨着新需求的提出和業務的發展,這些小單體微服務會慢慢膨脹起來。當有一天發現這些膨脹了的微服務,有一部分業務功能需要拆分出去,或者部分功能需要與其它微服務進行重組時,會發現原來這些看似清晰的微服務,不知不覺已經搖身一變,變成了臃腫油膩的大單體了,而這個大單體內的代碼依然是高度耦合且邊界不清的。

“辛辛苦苦好多年,一夜回到解放前啊!”這個時候就需要一遍又一遍地重複着從大單體向單體微服務重構的過程。想想,這個代價是不是有點高了呢?

其實這個問題已經很明顯了,那就是邊界。

這種單體式微服務只定義了一個維度的邊界,也就是微服務之間的物理邊界,本質上還是單體架構模式。微服務設計時要考慮的不僅僅只有這一個邊界,別忘了還要定義好微服務內的邏輯邊界和代碼邊界,這樣才能得到想要的結果。

一定要避免將微服務設計爲小單體微服務,那具體該如何避免呢?清晰的邊界人人想要,可該如何保證呢?DDD 已然給出了答案。

 

微服務邊界的作用

DDD 設計方法裏的限界上下文和聚合,它們就是用來定義領域模型和微服務邊界的。

DDD 的設計過程:

在事件風暴中,會梳理出業務過程中的用戶操作、事件以及外部依賴關係等,根據這些要素梳理出實體等領域對象。根據實體對象之間的業務關聯性,將業務緊密相關的多個實體進行組合形成聚合,聚合之間是第一層邊界。根據業務及語義邊界等因素將一個或者多個聚合劃定在一個限界上下文內,形成領域模型,限界上下文之間的邊界是第二層邊界。爲了方便理解,將這些邊界分爲:邏輯邊界、物理邊界和代碼邊界。

邏輯邊界主要定義同一業務領域或應用內緊密關聯的對象所組成的不同聚類的組合之間的邊界。事件風暴對不同實體對象進行關聯和聚類分析後,會產生多個聚合和限界上下文,它們一起組成這個領域的領域模型。微服務內聚合之間的邊界就是邏輯邊界。一般來說微服務會有一個以上的聚合,在開發過程中不同聚合的代碼隔離在不同的聚合代碼目錄中。

邏輯邊界在微服務設計和架構演進中具有非常重要的意義!

微服務的架構演進並不是隨心所欲的,需要遵循一定的規則,這個規則就是邏輯邊界。微服務架構演進時,在業務端以聚合爲單位進行業務能力的重組,在微服務端以聚合的代碼目錄爲單位進行微服務代碼的重組。由於按照 DDD 方法設計的微服務邏輯邊界清晰,業務高內聚,聚合之間代碼松耦合,因此在領域模型和微服務代碼重構時,就不需要花費太多的時間和精力了。

現在來看一個微服務實例,在下面這張圖中,可以看到微服務裏包含了兩個聚合的業務邏輯,兩個聚合分別內聚了各自不同的業務能力,聚合內的代碼分別歸到了不同的聚合目錄下。

那隨着業務的快速發展,如果某一個微服務遇到了高性能挑戰,需要將部分業務能力獨立出去,就可以以聚合爲單位,將聚合代碼拆分獨立爲一個新的微服務,這樣就可以很容易地實現微服務的拆分。

另外,也可以對多個微服務內有相似功能的聚合進行功能和代碼重組,組合爲新的聚合和微服務,獨立爲通用微服務。現在是不是有點做中臺的感覺呢?

物理邊界主要從部署和運行的視角來定義微服務之間的邊界。不同微服務部署位置和運行環境是相互物理隔離的,分別運行在不同的進程中。這種邊界就是微服務之間的物理邊界。

代碼邊界主要用於微服務內的不同職能代碼之間的隔離。微服務開發過程中會根據代碼模型建立相應的代碼目錄,實現不同功能代碼的隔離。由於領域模型與代碼模型的映射關係,代碼邊界直接體現出業務邊界。代碼邊界可以控制代碼重組的影響範圍,避免業務和服務之間的相互影響。微服務如果需要進行功能重組,只需要以聚合代碼爲單位進行重組就可以了。

 

正確理解微服務的邊界

從上述內容可以知道,按照 DDD 設計出來的邏輯邊界和代碼邊界,讓微服務架構演進變得不那麼費勁了。

微服務的拆分可以參考領域模型,也可以參考聚合,因爲聚合是可以拆分爲微服務的最小單位的。但實施過程是否一定要做到邏輯邊界與物理邊界一致性呢?也就是說聚合是否也一定要設計成微服務呢?答案是不一定的,這裏就涉及到微服務過度拆分的問題了。

微服務的過度拆分會使軟件維護成本上升,比如:集成成本、發佈成本、運維成本以及監控和定位問題的成本等。在項目建設初期,如果不具備較強的微服務管理能力,那就不宜將微服務拆分過細。當具備一定的能力以後,且微服務內部的邏輯和代碼邊界也很清晰,就可以隨時根據需要,拆分出新的微服務,實現微服務的架構演進了。

當然,還要記住一點,微服務內聚合之間的服務調用和數據依賴需要符合高內聚松耦合的設計原則和開發規範,否則也不能很快完成微服務的架構演進。

 

邏輯邊界:微服務內聚合之間的邊界是邏輯邊界。它是一個虛擬的邊界,強調業務的內聚,可根據需要變成物理邊界,也就是說聚合也可以獨立爲微服務。

物理邊界:微服務之間的邊界是物理邊界。它強調微服務部署和運行的隔離,關注微服務的服務調用、容錯和運行等。

代碼邊界:不同層或者聚合之間代碼目錄的邊界是代碼邊界。它強調的是代碼之間的隔離,方便架構演進時代碼的重組。

通過以上邊界,可以讓業務能力高內聚、代碼松耦合,且清晰的邊界,可以快速實現微服務代碼的拆分和組合,輕鬆實現微服務架構演進。但有一點一定要格外注意,邊界清晰的微服務,不是大單體向小單體的演進。

 

 

=======================================================================================================

16 | 視圖:如何實現服務和數據在微服務各層的協作?

課程鏈接:https://time.geekbang.org/column/article/167710

 

服務的協作

1. 服務的類型

先來回顧一下分層架構中的服務。按照分層架構設計出來的微服務,其內部有 Facade 服務、應用服務、領域服務和基礎服務。各層服務的主要功能和職責如下。

  • Facade 服務:位於用戶接口層,包括接口和實現兩部分。用於處理用戶發送的 Restful 請求和解析用戶輸入的配置文件等,並將數據傳遞給應用層。或者在獲取到應用層數據後,將 DO 組裝成 DTO,將數據傳輸到前端應用。
  • 應用服務:位於應用層。用來表述應用和用戶行爲,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果拼裝,對外提供粗粒度的服務。
  • 領域服務:位於領域層。領域服務封裝核心的業務邏輯,實現需要多個實體協作的核心領域邏輯。它對多個實體或方法的業務邏輯進行組合或編排,或者在嚴格分層架構中對實體方法進行封裝,以領域服務的方式供應用層調用。
  • 基礎服務:位於基礎層。提供基礎資源服務(比如數據庫、緩存等),實現各層的解耦,降低外部資源變化對業務應用邏輯的影響。基礎服務主要爲倉儲服務,通過依賴倒置提供基礎資源服務。領域服務和應用服務都可以調用倉儲服務接口,通過倉儲服務實現數據持久化。

2. 服務的調用

看一下下面這張圖。微服務的服務調用包括三類主要場景:微服務內跨層服務調用,微服務之間服務調用和領域事件驅動。

微服務內跨層服務調用

微服務架構下往往採用前後端分離的設計模式,前端應用獨立部署。前端應用調用發佈在 API 網關上的 Facade 服務,Facade 定向到應用服務。應用服務作爲服務組織和編排者,它的服務調用有這樣兩種路徑:

  • 第一種是應用服務調用並組裝領域服務。此時領域服務會組裝實體和實體方法,實現核心領域邏輯。領域服務通過倉儲服務獲取持久化數據對象,完成實體數據初始化。
  • 第二種是應用服務直接調用倉儲服務。這種方式主要針對像緩存、文件等類型的基礎層數據訪問。這類數據主要是查詢操作,沒有太多的領域邏輯,不經過領域層,不涉及數據庫持久化對象。

微服務之間的服務調用

微服務之間的應用服務可以直接訪問,也可以通過 API 網關訪問。由於跨微服務操作,在進行數據新增和修改操作時,需關注分佈式事務,保證數據的一致性。

領域事件驅動

領域事件驅動包括微服務內和微服務之間的事件。微服務內通過事件總線(EventBus)完成聚合之間的異步處理。微服務之間通過消息中間件完成。異步化的領域事件驅動機制是一種間接的服務訪問方式。

當應用服務業務邏輯處理完成後,如果發生領域事件,可調用事件發佈服務,完成事件發佈。

當接收到訂閱的主題數據時,事件訂閱服務會調用事件處理領域服務,完成進一步的業務操作。

3. 服務的封裝與組合

看一下下面這張圖。微服務的服務是從領域層逐級向上封裝、組合和暴露的。

基礎層:基礎層的服務形態主要是倉儲服務。倉儲服務包括接口和實現兩部分。倉儲接口服務供應用層或者領域層服務調用,倉儲實現服務,完成領域對象的持久化或數據初始化。

領域層:領域層實現核心業務邏輯,負責表達領域模型業務概念、業務狀態和業務規則。主要的服務形態有實體方法和領域服務。實體採用充血模型,在實體類內部實現實體相關的所有業務邏輯,實現的形式是實體類中的方法。實體是微服務的原子業務邏輯單元。在設計時主要考慮實體自身的屬性和業務行爲,實現領域模型的核心基礎能力。不必過多考慮外部操作和業務流程,這樣才能保證領域模型的穩定性。DDD 提倡富領域模型,儘量將業務邏輯歸屬到實體對象上,實在無法歸屬的部分則設計成領域服務。領域服務會對多個實體或實體方法進行組裝和編排,實現跨多個實體的複雜核心業務邏輯。對於嚴格分層架構,如果單個實體的方法需要對應用層暴露,則需要通過領域服務封裝後才能暴露給應用服務。

應用層:應用層用來表述應用和用戶行爲,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝,負責不同聚合之間的服務和數據協調,負責微服務之間的事件發佈和訂閱。通過應用服務對外暴露微服務的內部功能,這樣就可以隱藏領域層核心業務邏輯的複雜性以及內部實現機制。應用層的主要服務形態有:應用服務、事件發佈和訂閱服務。應用服務內用於組合和編排的服務,主要來源於領域服務,也可以是外部微服務的應用服務。除了完成服務的組合和編排外,應用服務內還可以完成安全認證、權限校驗、初步的數據校驗和分佈式事務控制等功能。爲了實現微服務內聚合之間的解耦,聚合之間的服務調用和數據交互應通過應用服務來完成。原則上應該禁止聚合之間的領域服務直接調用和聚合之間的數據表關聯。

用戶接口層:用戶接口層是前端應用和微服務之間服務訪問和數據交換的橋樑。它處理前端發送的 Restful 請求和解析用戶輸入的配置文件等,將數據傳遞給應用層。或獲取應用服務的數據後,進行數據組裝,向前端提供數據服務。主要服務形態是 Facade 服務。Facade 服務分爲接口和實現兩個部分。完成服務定向,DO 與 DTO 數據的轉換和組裝,實現前端與應用層數據的轉換和交換。

4. 兩種分層架構的服務依賴關係

回顧一下 DDD 分層架構,分層架構有一個重要的原則就是:每層只能與位於其下方的層發生耦合。

那根據耦合的緊密程度,分層架構可以分爲兩種:嚴格分層架構和鬆散分層架構。在嚴格分層架構中,任何層只能與位於其直接下方的層發生依賴。在鬆散分層架構中,任何層可以與其任意下方的層發生依賴。

詳細分析和比較一下這兩種分層架構。

鬆散分層架構的服務依賴

看一下下面這張圖,在鬆散分層架構中,領域層的實體方法和領域服務可以直接暴露給應用層和用戶接口層。鬆散分層架構的服務依賴關係,無需逐級封裝,可以快速暴露給上層。

但它存在一些問題,第一個是容易暴露領域層核心業務的實現邏輯;第二個是當實體方法或領域服務發生服務變更時,由於服務同時被多層服務調用和組合,不容易找出哪些上層服務調用和組合了它,不方便通知到所有的服務調用方。

再來看一張圖,在鬆散分層架構中,實體 A 的方法在應用層組合後,暴露給用戶接口層 aFacade。abDomainService 領域服務直接越過應用層,暴露給用戶接口層 abFacade 服務。鬆散分層架構中任意下層服務都可以暴露給上層服務。

嚴格分層架構的服務依賴

看一下下面這張圖,在嚴格分層架構中,每一層服務只能向緊鄰的上一層提供服務。雖然實體、實體方法和領域服務都在領域層,但實體和實體方法只能暴露給領域服務,領域服務只能暴露給應用服務。

在嚴格分層架構中,服務如果需要跨層調用,下層服務需要在上層封裝後,纔可以提供跨層服務。比如實體方法需要嚮應用服務提供服務,它需要封裝成領域服務。

這是因爲通過封裝可以避免將核心業務邏輯的實現暴露給外部,將實體和方法封裝成領域服務,也可以避免在應用層沉澱過多的本該屬於領域層的核心業務邏輯,避免應用層變得臃腫。還有就是當服務發生變更時,由於服務只被緊鄰上層的服務調用和組合,只需要逐級告知緊鄰上層就可以了,服務可管理性比鬆散分層架構要好是一定的。

還是看圖,A 實體方法需封裝成領域服務 aDomainService 才能暴露給應用服務 aAppService。abDomainService 領域服務組合和封裝 A 和 B 實體的方法後,暴露給應用服務 abAppService。

數據對象視圖

在 DDD 中有很多的數據對象,這些對象分佈在不同的層裏。它們在不同的階段有不同的形態。

  • 數據持久化對象 PO(Persistent Object),與數據庫結構一一映射,是數據持久化過程中的數據載體。
  • 領域對象 DO(Domain Object),微服務運行時的實體,是核心業務的載體。
  • 數據傳輸對象 DTO(Data Transfer Object),用於前端與應用層或者微服務之間的數據組裝和傳輸,是應用之間數據傳輸的載體。
  • 視圖對象 VO(View Object),用於封裝展示層指定頁面或組件的數據。

結合下面這張圖,看看微服務各層數據對象的職責和轉換過程。

基礎層:基礎層的主要對象是 PO 對象。需要先建立 DO 和 PO 的映射關係。當 DO 數據需要持久化時,倉儲服務會將 DO 轉換爲 PO 對象,完成數據庫持久化操作。當 DO 數據需要初始化時,倉儲服務從數據庫獲取數據形成 PO 對象,並將 PO 轉換爲 DO,完成數據初始化。大多數情況下 PO 和 DO 是一一對應的。但也有 DO 和 PO 多對多的情況,在 DO 和 PO 數據轉換時,需要進行數據重組。

領域層:領域層的主要對象是 DO 對象。DO 是實體和值對象的數據和業務行爲載體,承載着基礎的核心業務邏輯。通過 DO 和 PO 轉換,可以完成數據持久化和初始化。

應用層:應用層的主要對象是 DO 對象。如果需要調用其它微服務的應用服務,DO 會轉換爲 DTO,完成跨微服務的數據組裝和傳輸。用戶接口層先完成 DTO 到 DO 的轉換,然後應用服務接收 DO 進行業務處理。如果 DTO 與 DO 是一對多的關係,這時就需要進行 DO 數據重組。

用戶接口層:用戶接口層會完成 DO 和 DTO 的互轉,完成微服務與前端應用數據交互及轉換。Facade 服務會對多個 DO 對象進行組裝,轉換爲 DTO 對象,向前端應用完成數據轉換和傳輸。

前端應用:前端應用主要是 VO 對象。展現層使用 VO 進行界面展示,通過用戶接口層與應用層採用 DTO 對象進行數據交互。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章