天天看點

領域驅動設計(DDD)入門介紹

領域驅動設計(DDD)作為一種軟體開發方法,它可以幫助我們設計高品質的軟體模型。

————《實作領域驅動設計》
           

一、戰略模組化

1,領域

領域即是一個組織所做的事情以及其中所包含的一切。領域可以表示整個業務系統,也可以表示其中的某個核心域或支撐子域。

一個例子,零售商領域可以分為4個子域:産品目錄,訂單,發票,物流,他們組成一個電子商務系統,外部還包括庫存、外部預測系統兩個子域。

領域驅動設計(DDD)入門介紹

2,限界上下文

一個由顯示邊界限定的特定職責。領域模型便存在于這個邊界之内。在邊界内,每一個模型概念,包括它的屬性和操作,都具有特殊的含義。

一個給定的業務領域會包含多個限界上下文,想與一個限界上下文溝通,則需要通過顯示邊界進行通信。系統通過确定的限界上下文來進行解耦,而每一個上下文内部緊密組織,職責明确,具有較高的内聚性。

3,上下文映射圖

比較容易的一種是畫一個簡單的框圖來表示兩個或多個限定上下文之間的映射關系。另一種方式是內建限界上下文,這種方式後面會介紹。

限界上下文之間的映射關系

合作關系(Partnership):兩個上下文緊密合作的關系,一榮俱榮,一損俱損。

共享核心(Shared Kernel):兩個上下文依賴部分共享的模型。

客戶方-供應方開發(Customer-Supplier Development):上下文之間有組織的上下遊依賴。

遵奉者(Conformist):下遊上下文隻能盲目依賴上遊上下文。

防腐層(Anticorruption Layer):一個上下文通過一些适配和轉換與另一個上下文互動。

開放主機服務(Open Host Service):定義一種協定來讓其他上下文來對本上下文進行通路。

釋出語言(Published Language):通常與OHS一起使用,用于定義開放主機的協定。

大泥球(Big Ball of Mud):混雜在一起的上下文關系,邊界不清晰。

另謀他路(SeparateWay):兩個完全沒有任何聯系的上下文。

在上下文映射圖中,我們使用一下縮寫來表示各種關系:

ACL表示防腐層

OHS表示開放主機服務,

PL表示釋出語言

U表示上遊

D表示下遊

這是一個上下文映射圖的例子:

領域驅動設計(DDD)入門介紹

二、戰術模組化

1,實體

當一個對象由其辨別(而不是屬性)區分時,這種對象稱為實體(Entity)。

例如最簡單的,公安系統的身份資訊錄入,對于人的模拟,即認為是實體,因為每個人是獨一無二的,且其具有唯一辨別(如公安系統分發的身份證号碼)。

2,值對象

當一個對象用于對事務進行描述而沒有唯一辨別時,它被稱作值對象(Value Object)。

例:比如顔色資訊,我們隻需要知道{“name”:“黑色”,”css”:“#000000”}這樣的值資訊就能夠滿足要求了,這避免了我們對辨別追蹤帶來的系統複雜性。

值對象很重要,在習慣了使用資料庫的資料模組化後,很容易将所有對象看作實體。使用值對象,可以更好地做系統優化、精簡設計。

它具有不變性、相等性和可替換性。

在實踐中,需要保證值對象建立後就不能被修改,即不允許外部再修改其屬性。在不同上下文內建時,會出現模型概念的公用,如商品模型會存在于電商的各個上下文中。在訂單上下文中如果你隻關注下單時商品資訊快照,那麼将商品對象視為值對象是很好的選擇。

在實踐中,我們發現雖然一些領域對象符合值對象的概念,但是随着業務的變動,很多原有的定義會發生變更,值對象可能需要在業務意義具有唯一辨別,而對這類值對象的重構往往需要較高成本。是以在特定的情況下,我們也要根據實際情況來權衡領域對象的選型。

3,聚合根

Aggregate(聚合)是一組相關實體和值對象的集合,作為一個整體被外界通路,聚合根(Aggregate Root)是這個聚合的根節點。

聚合是一個非常重要的概念,核心領域往往都需要用聚合來表達。其次,聚合在技術上有非常高的價值,可以指導詳細設計。

4,貧血症和失憶症

貧血領域對象(Anemic Domain Object)是指僅用作資料載體,而沒有行為和動作的領域對象。

對象是面向對象語言的核心,而對象是将資料和行為封裝在一起的。如果一個對象隻是資料的載體,沒有行為,隻有簡單的get和set方法,那麼這個對象就具有貧血症。

按照我們通常思路實作,我們的業務邏輯都是寫在Service中的,而各種封裝資料的對象充其量隻是個資料載體,沒有任何行為。簡單的業務系統采用這種貧血模型和過程化設計是沒有問題的,但在業務邏輯複雜了,業務邏輯、狀态會散落到在大量方法中,原本的代碼意圖會漸漸不明确,我們将這種情況稱為由貧血症引起的失憶症。

更好的是采用領域模型的開發方式,将資料和行為封裝在一起,并與現實世界中的業務對象相映射。各類具備明确的職責劃分,将領域邏輯分散到領域對象中。

5,如何建立好的聚合?

邊界内的内容具有一緻性:在一個事務中隻修改一個聚合執行個體。如果你發現邊界内很難接受強一緻,不管是出于性能或産品需求的考慮,應該考慮剝離出獨立的聚合,采用最終一緻的方式。

設計小聚合:大部分的聚合都可以隻包含根實體,而無需包含其他實體。即使一定要包含,可以考慮将其建立為值對象。

通過唯一辨別來引用其他聚合或實體:當存在對象之間的關聯時,建議引用其唯一辨別而非引用其整體對象。如果是外部上下文中的實體,引用其唯一辨別或将需要的屬性構造值對象。 如果聚合建立複雜,推薦使用工廠方法來屏蔽内部複雜的建立邏輯。

6,領域事件

表示領域模型中發生的重要事件。有多種方式可以對領域事件模組化。在對聚合進行指令操作是,聚合本省将釋出領域事件。

7,子產品

子產品(Module)是DDD中明确提到的一種控制限界上下文的手段,在我們的工程中,一般盡量用一個子產品來表示一個領域的限界上下文。

在代碼中,一般的工程中包的組織方式為{com.公司名.組織架構.業務.上下文.*},這樣的組織結構能夠明确的将一個上下文限定在包的内部,我們可以将子產品看成這種包結構。子產品中包含的領域對象應該是内聚在一起的。

8,資源庫

領域對象需要資源存儲,存儲的手段可以是多樣化的,常見的無非是資料庫,分布式緩存,本地緩存等。資源庫(Repository)的作用,就是對領域的存儲和通路進行統一管理的對象。

9,領域服務

一些重要的領域行為或操作,可以歸類為領域服務。它既不是實體,也不是值對象的範疇。

當一些領域業務邏輯,既不适合放在實體、值對象、聚合中是,應該将其放在領域服務中。

上文中,我們将領域行為封裝到領域對象中,将資源管理行為封裝到資源庫中,将外部上下文的互動行為封裝到防腐層中。此時,我們再回過頭來看領域服務時,能夠發現領域服務本身所承載的職責也就更加清晰了,即就是通過串聯領域對象、資源庫和防腐層等一系列領域内的對象的行為,對其他上下文提供互動的接口。

當我們采用了微服務架構風格,一切領域邏輯的對外暴露均需要通過領域服務來進行。如原本由聚合根暴露的業務邏輯也需要依托于領域服務。

10,上下文內建

通常內建上下文的手段有多種,常見的手段包括開放領域服務接口、開放HTTP服務以及消息釋出-訂閱機制。

三、架構

DDD的一大好處便是它不需要使用特定的架構。由于核心域位于限界上下文中,我們可以在整個系統中使用多種風格的架構。有些架構包圍着領域模型,能夠全局性的影像系統,而有些架構則滿足了某些特定的需求。我們的目标是選擇合适自己的架構和架構模式。

微服務架構衆所周知,此處不做贅述。我們建立微服務時,需要建立一個高内聚、低耦合的微服務。而DDD中的限界上下文則完美比對微服務要求,可以将該限界上下文了解為一個微服務程序。

以下介紹一些常見的微服務架構:

1,分層架構

分層架構模式被認為是所有架構的始祖,它支援N層架構系統,是以被廣泛的應用。在這種架構中,我們将一個程式或者系統分為不同的層次。

下圖為一個典型的DDD系統所采用的分層架構:

領域驅動設計(DDD)入門介紹

使用者界面隻用于處理使用者顯示和使用者請求,它不應該包含領域或業務邏輯。使用者可能是人,也可能是其他系統,此時使用者界面層采用OHS(開放主機服務)的方式向外提供API。

應用服務位于應用層,應用服務是很輕量的,它本身不處理業務邏輯,主要用于協調對領域對象的操作,比如聚合。

領域服務位于領域層,主要處理該領域的業務邏輯。

基礎設施層主要實作對資源庫的通路。

分層架構一個重要的原則是:每層隻能與位于其下方的層發生耦合。

分層架構的好處是顯而易見的。

首先,由于層間松散的耦合關系,使得我們可以專注于本層的設計,而不必關心其他層的設計,也不必擔心自己的設計會影響其它層,對提高軟體品質大有裨益。其次,分層架構使得程式結構清晰,更新和維護都變得十分容易,更改某層的代碼,隻要本層的接口保持穩定,其他層可以不必修改。即使本層的接口發生變化,也隻影響相鄰的上層,修改工作量小且錯誤可以控制,不會帶來意外的風險。

2,六邊形架構(端口與擴充卡)

六邊形每條不同的邊代表了不同的類型的端口,端口要麼處理輸入,要麼處理輸出。

這種架構中,不同的客戶通過“平等”的方式與系統互動,新增客戶隻需要添加一個新的擴充卡将用戶端的輸入轉換成能被系統API所了解的參數就行。同時系統輸出,也通過不同的擴充卡轉化。

六邊形架構中,内部業務邏輯(應用層和領域模型)與外部資源(APP,WEB 應用以及資料庫資源等)完全隔離,僅通過擴充卡進行互動。它解決了業務邏輯與使用者界面的代碼交錯的主要問題,進而可以很好的實作前後端分離。

領域驅動設計(DDD)入門介紹

六邊形架構包含兩層

  1. 代表傳達機制和基礎設施的外層;
  2. 代表業務邏輯的内層。

3,洋蔥架構

洋蔥架構與六邊形架構有着相同的思路,它們都通過編寫擴充卡代碼将應用核心從對基礎設施的關注中解放出來,避免基礎設施代碼滲透到應用核心之中。這樣應用使用的工具和傳達機制都可以輕松地替換,可以一定程度地避免技術、工具或者供應商鎖定。

另外,它還有着脫離真實基礎設施和傳達機制應用仍然可以運作的便利,這樣可以使用 mock 代替它們友善測試。

然而,洋蔥架構還告訴我們,企業應用中存在着不止兩個層次,它在業務邏輯中加入了一些在領域驅動設計的過程中被識别出來的層次:

領域驅動設計(DDD)入門介紹

洋蔥架構的關鍵原則:

圍繞獨立的對象模型建構應用

内層定義接口,外層實作接口

依賴的方向指向圓心

所有的應用代碼可以獨立于基礎設施編譯和運作

—— Jeffrey Palermo 2008, The Onion Architecture: part 3

還有,任何一個外部層次都可以直接調用任何一個内部層次,這樣既不會破壞耦合的方向,也避免了僅僅為了追求分層模式而建立一些沒有任何業務邏輯的代理方法甚至代理類。這和 Martin Flowler 表達的偏好一緻。

洋蔥架構在端口和擴充卡架構的基礎之上增加了一些的應用業務邏輯的内部組織,這些組織基于領域驅動設計的概念劃分的。

4,CQRS(指令與查詢職責分離)

CQRS是對CQS模式的進一步改進成的一種簡單模式。 它由Greg Young在CQRS, Task Based UIs, Event Sourcing agh! 這篇文章中提出。“CQRS隻是簡單的将之前隻需要建立一個對象拆分成了兩個對象,這種分離是基于方法是執行指令還是執行查詢這一原則來定的(這個和CQS的定義一緻)”。

CQRS使用分離的接口将資料查詢操作(Queries)和資料修改操作(Commands)分離開來,這也意味着在查詢和更新過程中使用的資料模型也是不一樣的。這樣讀和寫邏輯就隔離開來了。

這個例子是一個簡單的線上記日志(Diary)系統,實作了日志的增删改查功能。整體結構如下:

領域驅動設計(DDD)入門介紹

上圖很清晰的說明了CQRS在讀寫方面的分離,在讀方面,通過QueryFacade到資料庫裡去讀取資料,這個庫有可能是ReportingDB。在寫方面,比較複雜,操作通過Command發送到CommandBus上,然後特定的CommandHandler處理請求,産生對應的Event,将Eevnt持久化後,通過EventBus特定的EevntHandler對資料庫進行修改等操作。

查詢模型是一種非規範化資料模型,它不反映領域行為,隻用于資料查詢和顯示;指令模型執行領域行為,在領域行為執行完成後通知查詢模型。

指令模型如何通知到查詢模型呢?如果查詢模型和領域模型共享資料源,則可以省卻這一步;如果沒有共享資料源,可以借助于釋出訂閱的消息模式通知到查詢模型,進而達到資料最終一緻性。

Martin 在 blog 中指出:CQRS 适用于極少數複雜的業務領域,如果不是很适合反而會增加複雜度;另一個适用場景是為了擷取高性能的查詢服務。

對于寫少讀多的共享類通用資料服務(如主資料類應用)可以采用讀寫分離架構模式。單資料中心寫入資料,通過釋出訂閱模式将資料副本分發到多資料中心。通過查詢模型微服務,實作多資料中心資料共享和查詢。

四、設計領域模型的一般步驟

設計領域模型的一般步驟如下:

  1. 根據需求劃分出初步的領域和限界上下文,以及上下文之間的關系;
  2. 進一步分析每個上下文内部,識别出哪些是實體,哪些是值對象;
  3. 對實體、值對象進行關聯和聚合,劃分出聚合的範疇和聚合根;
  4. 為聚合根設計倉儲,并思考實體或值對象的建立方式;
  5. 在工程中實踐領域模型,并在實踐中檢驗模型的合理性,倒推模型中不足的地方并重構。

五、DDD和MVC的對比

領域驅動設計(DDD)入門介紹

六、參考文獻

1,《實作領域驅動設計》Vaughn Vernon著;

2,http://deepoove.com/blog/#/posts/69(領域驅動設計DDD和CQRS落地)

3,https://www.edjdhbb.com/(複雜度應對之道 - 阿裡的COLA應用架構)

4,https://www.jianshu.com/p/d87d5389c92a(洋蔥架構)

5,https://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html(淺談指令查詢職責分離(CQRS)模式)

6,https://www.jianshu.com/p/d3e8b9ac097b(清晰架構(01): 融合 DDD、洋蔥架構、整潔架構、CQRS…(譯))

7,02.DDD、業務中台化、微服務設計——理論篇

8,https://github.com/Sayi/ddd-cargo(GitHub的領域驅動設計項目實戰)

DDD

繼續閱讀