【轉載】DDD分層架構的三種模式

引言

在討論DDD分層架構的模式以前,咱們先一塊兒回顧一下DDD和分層架構的相關知識。html

DDD

DDD(Domain Driven Design,領域驅動設計)做爲一種軟件開發方法,它能夠幫助咱們設計高質量的軟件模型。在正確實現的狀況下,咱們經過DDD完成的設計偏偏就是軟件的工做方式。
UL(Ubiquitous Language,通用語言)是團隊共享的語言,是DDD中最具威力的特性之一。無論你在團隊中的角色如何,只要你是團隊的一員,你都將使用UL。因爲UL的重要性,因此須要讓每一個概念在各自的上下文中是清晰無歧義的,因而DDD在戰略設計上提出了模式BC(Bounded Context,限界上下文)。UL和BC同時構成了DDD的兩大支柱,而且它們是相輔相成的,即UL都有其肯定的上下文含義,而BC中的每一個概念都有惟一的含義。
一個業務領域劃分紅若干個BC,它們之間經過Context Map進行集成。BC是一個顯式的邊界,領域模型便存在於這個邊界以內。領域模型是關於某個特定業務領域的軟件模型。一般,領域模型經過對象模型來實現,這些對象同時包含了數據和行爲,而且表達了準確的業務含義。
從廣義上來說,領域便是一個組織所作的事情以及其中所包含的一切,表示整個業務系統。因爲「領域模型」包含了「領域」這個詞,咱們可能會認爲應該爲整個業務系統建立一個單一的、內聚的和全功能式的模型。然而,這並非咱們使用DDD的目標。正好相反,領域模型存在於BC內。程序員

在微服務架構實踐中,人們大量地使用了DDD中的概念和技術:算法

  1. 微服務中應該首先創建UL,而後再討論領域模型。
  2. 一個微服務最大不要超過一個BC,不然微服務內會存在有歧義的領域概念。
  3. 一個微服務最小不要小於一個聚合,不然會引入分佈式事務的複雜度。
  4. 微服務的劃分過程相似於BC的劃分過程,每一個微服務都有一個領域模型。
  5. 微服務間的集成能夠經過Context Map來完成,好比ACL(Anticorruption Layer,防腐層)。
  6. 微服務間最好採用Domain Event(領域事件)來進行交互,使得微服務能夠保持鬆耦合。
  7. ...

分層架構

分層架構的一個重要原則是每層只能與位於其下方的層發生耦合。分層架構能夠簡單分爲兩種,即嚴格分層架構和鬆散分層架構。在嚴格分層架構中,某層只能與位於其直接下方的層發生耦合,而在鬆散分層架構中,則容許某層與它的任意下方層發生耦合。spring

分層架構的好處是顯而易見的。首先,因爲層間鬆散的耦合關係,使得咱們能夠專一於本層的設計,而沒必要關心其餘層的設計,也沒必要擔憂本身的設計會影響其它層,對提升軟件質量大有裨益。其次,分層架構使得程序結構清晰,升級和維護都變得十分容易,更改某層的具體實現代碼,只要本層的接口保持穩定,其餘層能夠沒必要修改。即便本層的接口發生變化,也隻影響相鄰的上層,修改工做量小且錯誤能夠控制,不會帶來意外的風險。
要保持程序分層架構的優勢,就必須堅持層間的鬆散耦合關係。設計程序時,應先劃分出可能的層次,以及此層次提供的接口和須要的接口。設計某層時,應儘可能保持層間的隔離,僅使用下層提供的接口。
關於分層架構的優勢,Martin Fowler在《Patterns of Enterprise Application Architecture》一書中給出了答案:數據庫

  1. 開發人員能夠只關注整個結構中的某一層。
  2. 能夠很容易的用新的實現來替換原有層次的實現。
  3. 能夠下降層與層之間的依賴。
  4. 有利於標準化。
  5. 利於各層邏輯的複用。

「金無足赤,人無完人」,分層架構也不可避免具備一些缺陷:編程

  1. 下降了系統的性能。這是顯然的,由於增長了中間層,不過能夠經過緩存機制來改善。
  2. 可能會致使級聯的修改。這種修改尤爲體如今自上而下的方向,不過能夠經過依賴倒置來改善。

在每一個BC中爲了凸顯領域模型,DDD中提出了分層架構模式。最近幾年,筆者在實踐DDD的過程當中,也常用分層架構模式,本文主要分享DDD分層架構中比較經典的三種模式。後端

模式一:四層架構

Eric Evans在《領域驅動設計-軟件核心複雜性應對之道》這本書中提出了傳統的四層架構模式,以下圖所示:瀏覽器

 
ddd-l4.png
  1. User Interface爲用戶界面層(或表示層),負責向用戶顯示信息和解釋用戶命令。這裏指的用戶能夠是另外一個計算機系統,不必定是使用用戶界面的人。
  2. Application爲應用層,定義軟件要完成的任務,而且指揮表達領域概念的對象來解決問題。這一層所負責的工做對業務來講意義重大,也是與其它系統的應用層進行交互的必要渠道。應用層要儘可能簡單,不包含業務規則或者知識,而只爲下一層中的領域對象協調任務,分配工做,使它們互相協做。它沒有反映業務狀況的狀態,可是卻能夠具備另一種狀態,爲用戶或程序顯示某個任務的進度。
  3. Domain爲領域層(或模型層),負責表達業務概念,業務狀態信息以及業務規則。儘管保存業務狀態的技術細節是由基礎設施層實現的,可是反映業務狀況的狀態是由本層控制而且使用的。領域層是業務軟件的核心,領域模型位於這一層。
  4. Infrastructure層爲基礎實施層,向其餘層提供通用的技術能力:爲應用層傳遞消息,爲領域層提供持久化機制,爲用戶界面層繪製屏幕組件,等等。基礎設施層還可以經過架構框架來支持四個層次間的交互模式。

傳統的四層架構都是限定型鬆散分層架構,即Infrastructure層的任意上層均可以訪問該層(「L」型),而其它層遵照嚴格分層架構緩存

筆者在四層架構模式的實踐中,對於分層的本地化定義主要爲:多線程

  1. User Interface層主要是Restful消息處理,配置文件解析,等等。
  2. Application層主要是多進程管理及調度,多線程管理及調度,多協程調度和狀態機管理,等等。
  3. Domain層主要是領域模型的實現,包括領域對象的確立,這些對象的生命週期管理及關係,領域服務的定義,領域事件的發佈,等等。
  4. Infrastructure層主要是業務平臺,編程框架,第三方庫的封裝,基礎算法,等等。

說明:嚴格意義上來講,User Interface指的是用戶界面,Restful消息和配置文件解析等處理應該放在Application層,User Interface層沒有的話就空缺。但User Interface也能夠理解爲用戶接口,因此將Restful消息和配置文件解析等處理放在User Interface層也行。

模式二:五層架構

James O. Coplien和Trygve Reenskaug在2009年發表了一篇論文《DCI架構:面向對象編程的新構想》,標誌着DCI架構模式的誕生。有趣的是James O. Coplien也是MVC架構模式的創造者,這個大叔一生就幹了兩件事,即年輕時創造了MVC和年老時創造了DCI,其餘時間都在思考,讓我輩可望不可即。
面向對象編程的本意是將程序員與用戶的視角統一於計算機代碼之中:對提升可用性和下降程序的理解難度來講,都是一種恩賜。但是雖然對象很好地反映告終構,但在反映系統的動做方面卻失敗了,DCI的構想是指望反映出最終用戶的認知模型中的角色以及角色之間的交互。

傳統上,面向對象編程語言拿不出辦法去捕捉對象之間的協做,反映不了協做中往來的算法。就像對象的實例反映出領域結構同樣,對象的協做與交互一樣是有結構的。協做與交互也是最終用戶心智模型的組成部分,但你在代碼中找不到一個內聚的表現形式去表明它們。在本質上,角色體現的是通常化的、抽象的算法。角色沒有血肉,並不能作實際的事情,歸根結底工做仍是落在對象的頭上,而對象自己還擔負着體現領域模型的責任。
人們心目中對「對象」這個統一的總體卻有兩種不一樣的模型,即「系統是什麼」和「系統作什麼」,這就是DCI要解決的根本問題。用戶認知一個個對象和它們所表明的領域,而每一個對象還必須按照用戶心目中的交互模型去實現一些行爲,經過它在用例中所扮演的角色與其餘對象聯結在一塊兒。正由於最終用戶能把兩種視角合爲一體,類的對象除了支持所屬類的成員函數,還能夠執行所扮演角色的成員函數,就好像那些函數屬於對象自己同樣。換句話說,咱們但願把角色的邏輯注入到對象,讓這些邏輯成爲對象的一部分,而其地位卻絲絕不弱於對象初始化時從類所獲得的方法。咱們在編譯時就爲對象安排好了扮演角色時可能須要的全部邏輯。若是咱們再聰明一點,在運行時才知道了被分配的角色,而後注入恰好要用到的邏輯,也是能夠作到的。

算法及角色-對象映射由Context擁有。Context「知道」在當前用例中應該找哪一個對象去充當實際的演員,而後負責把對象「cast」成場景中的相應角色(cast 這個詞在戲劇界是選角的意思,此處的用詞至少符合該詞義,另外一方面的用意是聯想到cast 在某些編程語言類型系統中的含義)。在典型的實現裏,每一個用例都有其對應的一個Context 對象,而用例涉及到的每一個角色在對應的Context 裏也都有一個標識符。Context 要作的只是將角色標識符與正確的對象綁定到一塊兒。而後咱們只要觸發Context裏的「開場」角色,代碼就會運行下去。

因而咱們有了完整的DCI架構(Data、Context和Interactive三層架構):

  1. Data層描述系統有哪些領域概念及其之間的關係,該層專一於領域對象的確立和這些對象的生命週期管理及關係,讓程序員站在對象的角度思考系統,從而讓「系統是什麼」更容易被理解。
  2. Context層:是儘量薄的一層。Context每每被實現得無狀態,只是找到合適的role,讓role交互起來完成業務邏輯便可。可是簡單並不表明不重要,顯示化context層正是爲人去理解軟件業務流程提供切入點和主線。
  3. Interactive層主要體如今對role的建模,role是每一個context中複雜的業務邏輯的真正執行者,體現「系統作什麼」。role所作的是對行爲進行建模,它聯接了context和領域對象。因爲系統的行爲是複雜且多變的,role使得系統將穩定的領域模型層和多變的系統行爲層進行了分離,由role專一於對系統行爲進行建模。該層每每關注於系統的可擴展性,更加貼近於軟件工程實踐,在面向對象中更多的是以類的視角進行思考設計。

DCI目前普遍被看做是對DDD的一種發展和補充,用在基於面向對象的領域建模上。顯式的對role進行建模,解決了面向對象建模中的充血模型和貧血模型之爭。DCI經過顯式的用role對行爲進行建模,同時讓role在context中能夠和對應的領域對象進行綁定(cast),從而既解決了數據邊界和行爲邊界不一致的問題,也解決了領域對象中數據和行爲高內聚低耦合的問題。

面向對象建模面臨的一個棘手問題是數據邊界和行爲邊界每每不一致。遵循模塊化的思想,咱們經過類將行爲和其緊密耦合的數據封裝在一塊兒。可是在複雜的業務場景下,行爲每每跨越多個領域對象,這樣的行爲若是放在某一個對象中必然會致使別的對象須要向該對象暴漏其內部狀態。因此面向對象發展的後來,領域建模出現兩種派別之爭,一種傾向於將跨越多個領域對象的行爲建模在領域服務中。若是這種作法使用過分,則會致使領域對象變成只提供一堆get方法的啞對象,這種建模結果被稱之爲貧血模型。而另外一派則堅決的認爲方法應該屬於領域對象,因此全部的業務行爲仍然被放在領域對象中,這樣致使領域對象隨着支持的業務場景變多而變成上帝類,並且類內部方法的抽象層次很難一致。另外因爲行爲邊界很難恰當,致使對象之間數據訪問關係也比較複雜,這種建模結果被稱之爲充血模型。

關於多角色對象,舉個生活中的例子:

人有多重角色,不一樣的角色履行的職責不一樣:

  1. 做爲父母:咱們要給孩子講故事,陪他們玩遊戲,哄它們睡覺。
  2. 做爲子女:咱們要孝敬父母,聽取他們的人生建議。
  3. 做爲下屬:咱們要服從上司的工做安排,並高質量完成任務。
  4. 做爲上司:咱們要安排下屬的工做,並進行培養和激勵。
  5. ...

這裏人(大對象)聚合了多個角色(小類),人在某種場景下,只能扮演特定的角色:

  1. 在孩子面前,咱們是父母。
  2. 在父母面前,咱們是子女。
  3. 在上司面前,咱們是下屬。
  4. 在下屬面前,咱們是上司。
  5. ...

引入DCI後,DDD四層架構模式中的Domain層變薄了,之前Domain層對應DCI中的三層,而如今:

  1. Domain層只保留了DCI中的Data層和Interaction層,咱們在實踐中一般將這兩層使用目錄隔離,即經過兩個目錄object和role來分離層Data和Interaction。


     
    object-role-dir.png
  2. DCI中的Context層從Domain層上移變成Context層。

所以,DDD分層架構模式就變成了五層,以下圖所示:


 
ddd-l5.png

筆者在實踐中,將這五層的本地化定義爲:

  1. User Interface是用戶接口層,主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Application層的接口。
  2. Application層是應用層,負責多進程管理及調度、多線程管理及調度、多協程調度和維護業務實例的狀態模型。當調度層收到用戶接口層的請求後,委託Context層與本次業務相關的上下文進行處理。
  3. Context是環境層,以上下文爲單位,將Domain層的領域對象cast成合適的role,讓role交互起來完成業務邏輯。
  4. Domain層是領域層,定義領域模型,不只包括領域對象及其之間關係的建模,還包括對象的角色role的顯式建模。
  5. Infrastructure層是基礎實施層,爲其餘層提供通用的技術能力:業務平臺,編程框架,持久化機制,消息機制,第三方庫的封裝,通用算法,等等。

DDD五層架構模式討論完了嗎?故事尚未結束...

筆者參與的不少DDD落地實踐,都是面向控制面或管理面且消息交互比較多的系統。這類系統的一次業務,包含一組同步消息或異步消息構成的序列,若是都放在Context層,會致使該層的代碼比較複雜,因而咱們考慮:

  1. Context層在面向控制面或管理面且消息交互比較多的系統中又分裂成兩層,即Context層和大Context層。
  2. Context層處理單位爲Action,對應一條同步消息或異步消息。
  3. 大Context層對應一個事務處理,由一個Action序列組成,通常經過Transaction DSL實現,因此咱們習慣把大Context層叫作Transaction DSL層。
  4. Application層在面向控制面或管理面且消息交互比較多的系統中常常會作一些調度相關的工做,因此咱們習慣把Application層叫作Scheduler層。

所以,在面向控制面或管理面且消息交互比較多的系統中,DDD分層架構模式就變成了六層,以下圖所示:

 
ddd-l6.png

筆者在實踐中,將這六層的本地化定義爲:

  1. User Interface是用戶接口層,主要用於處理用戶發送的Restful請求和解析用戶輸入的配置文件等,並將信息傳遞給Scheduler層的接口。
  2. Scheduler是調度層,負責多進程管理及調度、多線程管理及調度、多協程調度和維護業務實例的狀態模型。當調度層收到用戶接口層的請求後,委託Transaction層與本次操做相關的事務進行處理。
  3. Transaction是事務層,對應一個業務流程,好比UE Attach,將多個同步消息或異步消息的處理序列組合成一個事務,並且在大多場景下,都有選擇結構。萬一事務執行失敗,則當即進行回滾。當事務層收到調度層的請求後,委託Context層的Action進行處理,經常還伴隨使用Context層的Specification(謂詞)進行Action的選擇。
  4. Context是環境層,以Action爲單位,處理一條同步消息或異步消息,將Domain層的領域對象cast成合適的role,讓role交互起來完成業務邏輯。環境層一般也包括Specification的實現,即經過Domain層的知識去完成一個條件判斷。
  5. Domain層是領域層,定義領域模型,不只包括領域對象及其之間關係的建模,還包括對象的角色role的顯式建模。
  6. Infrastructure層是基礎實施層,爲其餘層提供通用的技術能力:業務平臺,編程框架,持久化機制,消息機制,第三方庫的封裝,通用算法,等等。

事務層的核心是事務模型,事務模型的框架代碼通常放在基礎設施層。關於事務模型,筆者之前分享過一篇文章—《Golang事務模型》,感興趣的同窗能夠看看。

綜上所述,DDD六層架構能夠看作是DDD五層架構在特定領域的變體,咱們統稱爲DDD五層架構,而DDD五層架構與傳統的四層架構相似,都是限定型鬆散分層架構

模式三:六邊形架構

有一種方法能夠改進分層架構,即依賴倒置原則(Dependency Inversion Principle, DIP),它經過改變不一樣層之間的依賴關係達到改進目的。

依賴倒置原則由Robert C. Martin提出,正式定義爲:
高層模塊不該該依賴於底層模塊,二者都應該依賴於抽象。
抽象不該該依賴於細節,細節應該依賴於抽象。

根據該定義,DDD分層架構中的低層組件應該依賴於高層組件提供的接口,即不管高層仍是低層都依賴於抽象,整個分層架構好像被推平了。若是咱們把分層架構推平,再向其中加入一些對稱性,就會出現一種具備對稱性特徵的架構風格,即六邊形架構。六邊形架構是Alistair Cockburn在2005年提出的,在這種架構中,不一樣的客戶經過「平等」的方式與系統交互。須要新的客戶嗎?不是問題。只須要添加一個新的適配器將客戶輸入轉化成能被系統API所理解的參數就行。同時,對於每種特定的輸出,都有一個新建的適配器負責完成相應的轉化功能。

六邊形架構也稱爲端口與適配器,以下圖所示:

 
ddd-hex.png

六邊形每條不一樣的邊表明了不一樣類型的端口,端口要麼處理輸入,要麼處理輸出。對於每種外界類型,都有一個適配器與之對應,外界經過應用層API與內部進行交互。上圖中有3個客戶請求均抵達相同的輸入端口(適配器A、B和C),另外一個客戶請求使用了適配器D。假設前3個請求使用了HTTP協議(瀏覽器、REST和SOAP等),然後一個請求使用了AMQP協議(好比RabbitMQ)。端口並無明確的定義,它是一個很是靈活的概念。不管採用哪一種方式對端口進行劃分,當客戶請求到達時,都應該有相應的適配器對輸入進行轉化,而後端口將調用應用程序的某個操做或者嚮應用程序發送一個事件,控制權由此交給內部區域。
應用程序經過公共API接收客戶請求,使用領域模型來處理請求。咱們能夠將DDD戰術設計的建模元素Repository的實現看做是持久化適配器,該適配器用於訪問先前存儲的聚合實例或者保存新的聚合實例。正如圖中的適配器E、F和G所展現的,咱們能夠經過不一樣的方式實現資源庫,好比關係型數據庫、基於文檔的存儲、分佈式緩存或內存存儲等。若是應用程序向外界發送領域事件消息,咱們將使用適配器H進行處理。該適配器處理消息輸出,而上面提到的處理AMQP消息的適配器則是處理消息輸入的,所以應該使用不一樣的端口。

咱們在實際的項目開發中,不一樣層的組件能夠同時開發。當一個組件的功能明確後,就能夠當即啓動開發。因爲該組件的用戶有多個,而且這些用戶的側重點不一樣,因此須要提供多個不一樣的接口。同時,這些用戶的認識也是不斷深刻的,可能會屢次重構相關的接口。因而,組件的多個用戶常常會找組件的開發者討論這些問題,無形中下降了組件的開發效率。
咱們換一種方式,組件的開發者在明確了組件的功能後就專一於功能的開發,確保功能穩定和高效。組件的用戶本身定義組件的接口(端口),而後基於接口寫測試,並不斷演進接口。在跨層集成測試時,由組件開發者或用戶再開發一個適配器就能夠了。

六邊形架構模式的演變

儘管六邊形架構模式已經很好,可是沒有最好只有更好,演變沒有盡頭。在六邊形架構模式提出後的這些年,又依次衍生出三種六邊形架構模式的變體,感興趣的讀者能夠點擊連接自行學習:

  1. Jeffrey Palermo在2008年提出了洋蔥架構,六邊形架構是洋蔥架構的一個超集。
  2. Robert C. Martin在2012年提出了乾淨架構(Clean Architecture),這是六邊形架構的一個變體。
  3. Russ Miles在2013年提出了Life Preserver設計,這是一種基於六邊形架構的設計。

小結

本文先和讀者一塊兒回顧了DDD和分層架構的相關知識,而後將DDD分層架構中經常使用的三種模式(四層架構、五層架構和六邊形架構)結合實踐經驗分別進行詳細闡述,使得讀者深入理解DDD分層架構模式,以便在微服務的開發實踐中根據具體狀況選擇最合適的DDD分層架構模式,從而交付結構清晰且易維護的軟件產品。

做者:_張曉龍_連接:https://www.jianshu.com/p/a775836c7e25來源:簡書簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索