- 什么是DDD
- DDD的特點
- 戰略設計、戰術設計
- DDD在微服務中解決的問題
- DDD的好處與局限
- 領域
- 領域、子域
- 核心域、通用域、支撐域
- 通用語言、限界背景關系
- 通用語言
- 限界背景關系
- 物體、值物件
- 物體
- 值物件
- 聚合和聚合根
- 聚合
- 聚合根
- 如何設計聚合
- 聚合的設計原則
什么是DDD
看了一些DDD的介紹、教程,這些教程無一例外都會講一個關于美好邂逅的故事,故事的情節大概是這樣的:DDD是2004年出現的,但一直不溫不火,直到十來年后出現了微服務,大家在落地微服務的時候遇到了各種各樣的問題,其中就有一個很讓人頭疼的:“微服務到底要多微”?,大家總說紛紜,直到有人把DDD的方法論應用到微服務的拆分,兩者一拍即合,過上了幸福的生活...
我對微服務和DDD都沒有太多的了解與實踐,不敢妄加評論,但上面的故事情節總感覺有種童話故事中王子遇青蛙、或者武俠小說中少年偶遇隱者學成神功的故事一般,帶著巧合、虛幻的味道,也許看似巧合的背后,有更深層次的原因,也許看似美好的結合背后,也不過是解決一個問題、又帶來一堆新問題的尷尬,
真實情況到底是什么樣,個人目前無法做出判斷,且待學完DDD再說,
DDD的特點
《DDD實戰課》的作者歐創新認為:
DDD 核心思想是通過領域驅動設計方法定義領域模型,從而確定業務和應用邊界,保證業務模型與代碼模型的一致性,
即主要在于兩點:確定業務邊界、保證模型與代碼一致,但按照之前徐昊在《如何落地業務建模》中的觀點,應該再加上“統一語言”一項,它也是DDD的核心,統一語言作為業務人員和技術人員統一的交流語言,而且與模型關聯,是DDD能夠成功運轉的關鍵,
以下是《如何落地業務建模》的內容摘錄:
Eric 倡導的領域驅動設計是一種模型驅動的設計方法:通過領域模型(Domain Model)捕捉領域知識,使用領域模型構造更易維護的軟體,
模型在領域驅動設計中,其實主要有三個用途:
1.通過模型反映軟體實作(Implementation)的結構;
2.以模型為基礎形成團隊的統一語言(Ubiquitous Language);
3.把模型作為精粹的知識,以用于傳遞,
在 DDD 中,Eric Evans 提倡了一種叫做知識消化(Knowledge Crunching)的方法幫助我們去提煉領域模型:
1.關聯模型與軟體實作;
2.基于模型提取統一語言;
3.開發富含知識的模型;
4.精煉模型;
5.頭腦風暴與試驗,
戰略設計、戰術設計
整體來看,DDD分為戰略設計、戰術設計兩大部分,
戰略設計從業務視角出發,建立領域模型、劃分領域邊界、建立通用語言的限界背景關系;
戰術設計則關注將模型轉化為軟體實作的程序,涉及聚合根、物體、值物件、領域服務、應用服務等概念,
所以戰略設計重在把控方向、建立模型,戰術設計重在軟體實作,現實中,有人只是將DDD戰術設計中用到的工具集、思想拿來使用,比如四層架構、CQRS等,對于這種做法,官方的說法是DDD-Lite,但缺乏戰略設計的DDD是沒有靈魂的,
如何進行戰略設計
戰略設計的主要目標是建立領域模型,這個程序一般采用事件風暴(Event Storming)的方式,
事件風暴分兩個階段:發散、收斂,
- 發散階段:將業務方、技術方召集到一起,然后采用用例分析、場景分析等手段,盡可能全面地分解業務領域,梳理領域之間的關系,這一階段會產生很多物體、事件、命令等領域物件;
- 收斂階段:將發散階段產生的物體、事件、命令等物件,按照不同的維度進行聚合,產生限界背景關系、聚合等邊界,這一程序通常由DDD專家來完成,
收斂程序是建模的關鍵,而且往往變化最大,不同人可能產生不同的結果,
DDD在微服務中解決的問題
前面提到DDD可以用來幫助回答“微服務到底要多微”的問題,那么它是怎么做到的呢?下圖為戰略設計形成的限界背景關系、聚合的示例:
這張圖中存在兩種邊界:聚合邊界、限界背景關系邊界;
限界背景關系就可以作為微服務的邊界,一個微服務中可以包含一個或多個聚合,
DDD的好處與局限
總體來說,DDD 可以給你帶來以下識訓:
DDD 是一套完整而系統的設計方法,它能帶給你從戰略設計到戰術設計的標準設計程序,使得你的設計思路能夠更加清晰,設計程序更加規范,
DDD 善于處理與領域相關的擁有高復雜度業務的產品開發,通過它可以建立一個核心而穩定的領域模型,有利于領域知識的傳遞與傳承,
DDD 強調團隊與領域專家的合作,能夠幫助你的團隊建立一個溝通良好的氛圍,構建一致的架構體系,
DDD 的設計思想、原則與模式有助于提高你的架構設計能力,
無論是在新專案中設計微服務,還是將系統從單體架構演進到微服務,都可以遵循 DDD 的架構原則,
DDD 不僅適用于微服務,也適用于傳統的單體應用,
既然提了這么多好處,凡事都有里面,那么DDD的局限和弊端是什么呢?
首先,DDD應該更適用于業務系統,所謂業務系統即與公司運營、成本相關的系統,比如電商系統之于京東、阿里、打車系統之于滴滴;而類似人工智能、地圖、搜索等系統,則有其特有的設計方法,DDD就不太適用了,
其次,對于業務系統,DDD應該也不是銀彈,DDD應該更適用于業務邏輯非常復雜的系統,
而且,DDD自身也存在不少缺陷,比如建模程序依賴于專家經驗,不具有唯一性,實施成本高,對開發人員要求高等等,
領域
領域、子域
廣義來講,領域(Domain)指的是一種特定的范圍或區域,領域的作用的劃定邊界,DDD中所提的領域就是在某個邊界內要解決的問題域,
《DDD實戰課》用自然科學類比DDD:
DDD 的研究方法與自然科學的研究方法類似,當人們在自然科學研究中遇到復雜問題時,通常的做法就是將問題一步一步地細分,再針對細分出來的問題域,逐個深入研究,探索和建立所有子域的知識體系,當所有問題子域完成研究時,我們就建立了全部領域的完整知識體系了,
作者舉了一個桃樹的例子,桃樹作為研究物件,是一個大的領域,然后對研究物件按照器官、組織、細胞的層級,將問題一步步細分,
那么借用這個類比,桃樹就是最大的問題域(領域),這個領域進一步分成了許多小的領域,稱為子域,領域、子域是將大而復雜的問題,進行細分時的產物,
不過類比是把雙刃劍,既然將DDD類比為自然科學的也就方法,那么是不是也具有自然科學研究方法中“不靠譜”一面呢,自然科學的研究程序大致遵循“提出假設-驗證假設”、“演繹-歸納”的程序,比如觀察到的烏鴉都是黑色的,于是提出“所有的烏鴉都是黑色的”,然后進一步觀察,后面看到的烏鴉也都是黑色的,就可以認為假設成立,但“所有的烏鴉都是黑色的”這個理論是建立在前面觀察的基礎上,萬一哪天真的發現一直白色的烏鴉,那么這個理論,以及所有以這個理論為基礎建立的理論都不成立了,
DDD是否也存在這樣的危險呢?
核心域、通用域、支撐域
根據自身的重要性、功能屬性,可以將子域分類為:核心域、通用域、支撐域
- 核心域:與公司核心競爭力相關的領域;
- 通用域:沒有太多個性化訴求,同時被多個子域使用的領域,比如認證、權限等;
- 支撐域:具備企業特性,但沒有通用性,也不屬于核心的領域,比如資料代碼類的資料字典等,
上面桃樹的例子中,核心域該如何確定,這要視場景而定,如果桃樹生長在公園中,園丁主要關注“人面桃花相映紅”的陽春三月,那么花就是核心域;而如果桃樹生長在果園,果農更關心桃子是否有好的收成,果實成了核心域,
區分核心域、通用域、支撐域的目的,也在于幫助確定哪些是公司的核心競爭力,以便于將有限的資源和預算投入最核心的領域,
一家公司至少要保證對核心域有絕對的掌控和自主研發能力,對于通用域、支撐域在資源不足時甚至可以考慮采購、外包,
通用語言、限界背景關系
通用語言定義背景關系含義,限界背景關系定義領域邊界;
通用語言
在事件風暴中,業務方、技術方都會參與,比如領域專家、專案經理、產品經理、架構師、測驗經理等等,對于相同的概念,不同的角色會有不同的理解、不同的慣用語,通用語言的作用是保證大家順暢溝通,
通用語言:是團隊統一的語言,必須得到團隊內各種角色的認可,能夠簡單、準確、清晰地描述業務的語言,
所以通用語言的作用,首先在于保證團隊溝通順暢,
此外,通用語言還與領域模型關聯,領域模型又與代碼實作關聯,
通用語言包含術語和用例場景,并且能夠直接反映在代碼中,通用語言中的名詞可以給領域物件命名,如商品、訂單等,對應物體物件;而動詞則表示一個動作或事件,如商品已下單、訂單已付款等,對應領域事件或者命令,
通用語言在事件風暴程序中建立,然后在領域物件設計和代碼落地的程序都會被采用,貫穿了DDD的整個設計程序,
限界背景關系
語言都有自己的語意環境,通用語言也不例外,為了避免同一個概念在不同的環境下產生不同的語意,DDD在戰略設計階段提出了限界背景關系的概念,來確定語意所在的背景關系,
限界背景關系(Bounded Context)可以理解為“(領域的)邊界+(語意的)背景關系”,
它用來封裝通用語言和領域物件,提供背景關系環境,保證邊界內的概念都有明確的含義,限界背景關系定義了模型和通用語言的適用范圍,
以電商領域為例,商品在不同的階段有不同的術語,在銷售階段是商品,到了運輸階段就成了貨物,同樣的事物,在不同階段的關注點不同,處在不同的限界背景關系,
物體、值物件
物體和值物件是組成領域模型的基礎單元,都是領域模型中的領域物件,在戰略設計向戰術設計過渡的這個程序中,理解和區分物體和值物件在不同階段的形態是很重要的,畢竟階段不同,它們的形態也會發生變化,這與我們的設計和代碼實作密切相關,
物體
物體擁有唯一的識別符號,且無論歷經何種狀態變化,其識別符號仍然保持不變,對于物體來說,重要的不是屬性,而是其延續性和唯一性,
- 物體的業務形態:在戰略設計階段,物體是領域模型的一種重要物件,是屬性、操作、行為的載體,在事件風暴中,可以根據命令、操作、事件找出產生這些內容的物體物件;
- 物體的代碼形態:在代碼模型中,物體的表現是物體類,是一種包含屬性、行為的充血模型,與物體相關的邏輯都在物體類中實作,跨多個物體的邏輯則在領域服務實作;
- 物體的運行形態:物體以領域物件(DO:Domain Object)的形式存在,具有唯一的ID,不管其它屬性如何變動,始終是同一個物體;
- 物體的資料庫形態:DDD更關注領域模型而不是資料模型,只是在需要時將領域模型持久化為資料模型,物體與資料模型一般為1:1關系,也有不持久化(1:0,比如基于各種計算生成的折扣物體,只需暫時存盤在記憶體中),1:N(比如權限物體對于user與role兩個資料物體),N:1(比如有時為了提升性能,會將多個物體保存到同一張資料表)的特例,
值物件
值物件是通過物件屬性值來識別的物件,它將多個關聯屬性組合為一個概念整體,值物件沒有唯一識別符號,主要用于進行屬性歸類,比如人員資訊中,除了姓名、年齡等,還有省市區縣等地址資訊,這些地址資訊就可以獨立歸類為一個地址值物件,
- 值物件的業務形態:值物件在邏輯上屬于物體的一部分;
- 值物件的代碼形態:如果值物件是單一屬性,可直接嵌入物體;如果值物件是屬性的集合,則作為獨立的類存在,并被物體參考;
public User{ //物體
public string ID; // 唯一標識
public string Name; //單一屬性的值物件
public Address Address; // 屬性集合的值物件
}
public class Address{ //值物件
public string city;
...
}
- 值物件的運行形態:值物件只包含屬性,沒有行為,屬于貧血模型;值物件創建后無法修改,只能用新的值物件替換;
- 值物件的資料庫形態:這是重點討論的話題,
傳統的資料建模遵循資料庫設計范式,會存在表關聯,比如上面的人員-地址例子中,可以把地址表作為人員表的關聯表,但這會導致產生太多的資料表;而如果將人員與地址資訊存放到一張表,又會丟失地址自身含義獨立性和完整性,
DDD以領域建模為主,榷訓資料建模的作用,甚至認為資料庫只是一個保存資料庫的倉庫,不一定要遵守資料庫方式,怎么方便怎么來,
DDD引入值物件就是希望從“資料建模為中心”轉變為“領域建模為中心”,減少資料表的數量,以及復雜的表關聯,
引入值物件后,地址資訊在領域模型中保留了自己獨立的業務含義,而存盤到資料表時,可以繼續保存到人員表,可以在人員表中作為平鋪的屬性,也可以作為json大物件直接存盤到人員.Address欄位,
值物件是把雙刃劍
值物件雖然可以簡化資料庫的設計,但很容易因使用不當而造成問題,
如果將值物件存盤為json大物件,那么根據值物件屬性的查詢會變得非常困難;
而如果將值物件平鋪存盤到物體表,又會導致物體表存在太多缺乏概念獨立性和完整性的欄位,
聚合和聚合根
聚合
物體和值物件是DDD中基礎的領域物件,而聚合是由業務、邏輯緊密關聯的物體和值物件組成的,聚合是資料持久化的基本單元,每個聚合對應一個倉儲,
物體和值物件就像社會的個體,聚合就是由多個個體組成的組織,大家協同作業,朝著一個更大的目標前進,發揮更大的力量,
聚合的邊界按照單一職責、高內聚的原則來確定,
聚合根
聚合根用來統一控制聚合內的業務邏輯,就像組織內的負責人,
聚合根是一種特殊的物體,擁有屬性、行為、自身的業務邏輯;
同時他也是聚合內其它物體的管理者,負責協同聚合的內的物體、值物件協同完成業務邏輯;
此外,聚合根還是聚合對外的介面人,以聚合根ID的形式關聯外部任務和請求,實作背景關系內聚合間的業務協同,
如何設計聚合
在事件風暴程序中找出所有可能的事件和行為,然后找出產生這些事件、行為的物體、值物件等領域物件,接著梳理這些領域物件之間的關系,找出聚合根,將與聚合根功能緊密相關的一些物體、值物件組合為聚合,
以保險行業的投保業務場景為例,具體程序為:
- 事件風暴找出物體和值物件,如投保單、客戶等;
- 找出聚合根,確定聚合根的依據,主要是某個物體是否可以作為管理其它物體的根物體,是否可以創建、修改其它物件,這里的聚合根分別是投保單、客戶;
- 找出聚合:根據業務單一職責、高內聚原則,找出與與聚合根功能緊密相關、有依賴關系的物體、值物件,這些物體、值物件和聚合根一起構成了聚合;
- 梳理物件參考和依賴關系:在聚合內,畫出物體、值物件和聚合根之間的依賴關系;這里被保人、投保人跨聚合參考了客戶聚合的資料,且以值物件的形式在投保聚合保存了值物件在投保那一刻的冗余資料,這樣即使后來客戶資訊發生變化,也不會影響已經生成的投保單;
- 將多個業務密切相關的聚合,有可以進一步劃分到一個限界背景關系,
聚合的設計原則
- 在一致性邊界內建模真正的不變條件,聚合不是簡單的物件組合,聚合內的物體、值物件按照統一的業務規則運行;
- 設計小聚合,聚合如果包含過多的物體,會導致物體的管理太復雜,高頻操作時會出現辦法沖突或資料庫鎖;
- 通過唯一標識參考其它聚合,跨聚合的參考不能通過直接的物件參考,而是通過關聯外部聚合根ID的方式參考,這樣可以降低聚合之間的耦合度;
- 在邊界之外使用最終一致性,聚合內資料強一致性,聚合之間資料最終一致性,在一次事務中,最多只能更改一個聚合的狀態,如何一個業務操作設計多個聚合狀態的更改,可以采用領域事件的方式,實作聚合之間的解耦;
- 通過應用層實作跨聚合的服務呼叫,
參考資料: 歐創新 《DDD實戰課》
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/445590.html
標籤:領域驅動設計
上一篇:【軟體】重構與架構
下一篇:創建型:二. 生成器模式
