領域驅動設計四論

作者:larkliu,騰訊 PCG 後開開發工程師

經過多年的研究與思考,實踐與總結,本人逐漸對 DDD 有所領悟,本文以一個較短的篇幅,提綱挈領地梳理出 DDD 的核心脈絡,希望與各位做一探討。

1776 年亞當斯密發表《國富論》,標誌着經濟學的誕生。2004 年,一本名爲《領域驅動設計 · 軟件核心複雜性應對之道》的書問世,開闢了軟件開發的一個新流派:領域驅動設計。看完這本書,十個人有九個人的感覺都是:似懂非懂,若有所得,掩卷長思,一無所得,我個人的感覺同樣如此。出於興趣,多年來仔細研讀了幾十本相關書籍,融匯貫通,逐步形成了自己的一套看法,本文就和各位分享一下。

馮友蘭治哲學,提出 “照着講” 和“接着講”的方法論。近兩年,我斷斷續續梳理出關於領域驅動設計的兩個 PPT:《領域驅動設計》和《領域驅動設計四論》。前者的內容主要是關於 DDD 經典著作的讀書筆記,可視爲照着講,以證明自己學有所本,講的不是野狐禪;後者則是在繼承的基礎上所做的創新闡釋,可視爲接着講,發前人之未發。本文重點圍繞《四論》展開,從四個方面梳理出 DDD 的整個邏輯脈絡。

曾見郭象注莊子,卻是莊子注郭象。一些領域驅動設計的擁躉們,如果看到本文的論述和自己的理解相左,絲毫不用奇怪,本文闡述的四論,不是我注六經,而是六經注我。

本文不準備長篇大論,只是提綱挈領地梳理出 DDD 的核心脈絡。

DDD 四論

在系統開發這一行摸爬滾打多年,開發過衆多業務系統,也接觸過各種技術流派,結合衆多書籍,從技術風格上分爲兩派:

在技術社區裏,熱鬧程度可謂冰火兩重天,江湖一派熱熱鬧鬧,追隨者衆,耳濡目染,無師自通;學院一派則是養在深閨人未識,門可羅雀,亟待挖掘。

有趣的是,兩派似乎從不往來,從兩派的著作來看,江湖派偶爾涉足學院派的領域,但學院派決口不提江湖派的東西。看來在技術圈子裏,同樣也是文人相輕、道不同不相爲謀,各自混各自的圈子。

作爲一個互聯網工程師,長期奮戰在開發第一線,對這種技術分裂深感無奈,總感覺有座巴別塔橫亙其中。結合自身長期在一線工作的經驗積累、對各種經典著作的熟稔以及對軟件開發的持久興趣,本文試圖在兩派之間架起一座橋樑,相互借鑑,熔於一爐。

從《領域驅動設計 · 軟件核心複雜性應對之道》(英文版)在 2004 年第一次發表算起,到現在已有近 20 年,期間闡述 DDD 的書汗牛充棟,但 DDD 具體包含哪些內容,似乎從沒有達成共識,有種包羅萬象的感覺。本文圍繞 DDD 的經典著作,參照學院派的風格,將 DDD 概括爲四部分:

這個劃分,其實體現了本人長期的實踐體悟和理論思考,並不是隨意爲之。王陽明晚年將其畢生所學,濃縮爲四句話:

本人將 DDD 概括爲四論,不是要蓋棺定論,只是希望去掉枝葉保留主幹,避免 DDD 逐漸淪爲一種坊間流傳的野狐禪。

繞不開的複雜度

複雜度

《沒有銀彈:軟件工程的本質性與附屬性工作》(No Silver Bullet—Essence and Accidents of Software Engineering),1986 年發表一篇關於軟件工程的經典論文,打開了複雜度這個潘多拉魔盒。論文中 brooks 把失控的、複雜的軟件項目比作中世紀的狼人,只有銀彈才能殺死它。但是由於軟件開發的本質複雜性,使得真正的銀彈並不存在,即沒有任何技術或管理上的進展, 能夠獨立地許諾十年內使軟件系統項目生產率、 可靠性或簡潔性獲得數量級上的進步。

到現在幾十年過去了,狼人依舊未被殺死,人月依舊是個神話,銀彈在哪?

複雜度也許永遠不能消除,但我們可以分析複雜度,進而管理複雜度。在軟件開發領域,大體上可以將複雜度分爲三類:軟件本身固有的複雜度,業務邏輯帶來的複雜度,以及技術複雜度。

應對之道

從我看過的幾本經典書籍來看,軟件核心複雜性的應對之道,不外乎就是:抓住總體,理清局部

DDD 與複雜度

DDD 提供了一些應對複雜度的具體方法:

什麼是領域

Domain-Driven Design 還是 Model-Driven Design ?

在《領域驅動設計 · 軟件核心複雜性應對之道》這本書裏,對什麼是 “領域”,只有簡單的一句話:“每個軟件程序是爲了執行用戶的某項活動,或是滿足用戶的某種需求。這些用戶應用軟件的問題區域就是軟件的領域。” 除此之外,講的更多的是 “模型” 或者“領域模型”,縱觀全書,“Model-Driven Design” 是其核心概念之一。某種程度上,把“領域驅動設計” 改爲 “模型驅動設計”,一點問題都沒有,甚至“模型驅動設計” 更能體現整本書的精髓。

領域劃分與領域模型

總的來說,“領域”這個詞可能承載了太多含義。領域既可以表示整個業務系統,也可以表示其中的某個核心域或者支撐子域。在本書中,我將盡可能地區分這些概念。當談及到業務系統中的某個方面時,我會使用諸如 “核心域” 或者 “子域” 以示區別。

由於 “領域模型” 包含了 “領域” 這個詞,我們可能會認爲應該爲整個業務系統創建一個單一的、內聚的、全功能式的模型。然而,這並不是我們使用 DDD 的目標。正好相反,在 DDD 中,一個領域被分爲若干子域,領域模型在限界上下文中完成開發。事實上,在開發一個領域模型時,我們關注的通常只是這個業務系統的某個方面。試圖創建一個全功能的領域模型是非常困難的,並且很容易導致失敗。

關於 DDD 的一個野狐禪

本人接觸領域驅動大概已有 10 年時間,直到近期才聽同事說起過 “貧血模型”,然後在網上一查,原來是 martin fowler 在 2003 年發的一個 blog:AnemicDomainModel。其本意只是指出,很多人其實誤用了領域模型,把領域模型的 Model 直接等同於 MVC 的 Model,導致 model 裏只有數據沒有邏輯,fowler 給這種情形取了一個名字叫 “Anemic Domain Model”。

Fowler 把這種誤用概括爲一種反模式 AnemicDomainModel,在流傳過程中,有人把 “Anemic Domain Model” 翻譯爲“貧血模型”,後面又衍生出“充血模型”“失血模型”“脹血模型”,這些就都是穿鑿附會之說,跟 Martin Fowler 無關,跟領域驅動更無關係。很多 blog 都是把 DDD 跟 MVC/DAO/ORM/TDD 放在一個層次來理解,這說明完全不理解 DDD,實際上 DDD 是跟 MBSE/SysML/UML 是一個層次的概念。

在領域驅動的經典著作裏,完全沒有 AnemicDomainModel 的相關提法,把 martin fowler 的說法穿鑿附會亂加引申,將領域模型簡單分類爲 “失血,貧血,充血,脹血”,這個大概就屬於野狐禪了,完全背離了 DDD 的本質精神。

結構論

戰略建模與戰術建模的劃分

DDD 相關經典著作,一般都將 DDD 劃分爲戰略設計和戰術設計兩部分。《領域驅動設計 · 軟件核心複雜性應對之道》和《實現領域驅動設計》都明確有戰略設計的提法,其他兩本書則明確提出了戰略設計和戰術設計。

顧名思義,戰略設計就是宏觀設計,即對系統整體進行建模,稱之爲 “戰略建模”;戰術設計則是微觀設計,即對系統的局部進行細粒度的建模,稱之爲 “戰術建模”。

戰略建模

戰略設計原則必須指導設計決策,以便減少各個部分之間的互相依賴,在使設計意圖更爲清晰的同時而又不失去關鍵的互操作性和協同性。戰略設計原則必須把模型的重點放在捕獲系統的概念核心,也就是系統的 “遠景” 上。而且在完成這些目標的同時又不能爲項目帶來麻煩。爲了幫助實現這些目標,我們提出了戰略設計的 3 大原則:上下文、精煉和大型結構

理解大型系統的常用方法:

DDD 中的上下文(Context)是個讓人迷惑的詞,從一種比較寬泛的視角來看的話,Context 可以對應於 UML 的 class 或者 SysML 的 block,即 Context 可理解爲是一個類或模塊。ContextMap 則對應 UML/SysML 的 Relationship。

戰略精煉:對核心域進一步萃取,過濾掉不必要的雜質,使得其方向更清晰,內容更準確、內核更精幹。

戰略精煉:相關方法可以分爲三大類,即提煉出內核、進一步精煉內核、增強溝通。

戰術建模

戰術建模側重於從微觀層面對系統進行建模。DDD 提到的戰術建模方法主要是構造塊與柔性設計。

構造塊:在類、對象、組合、繼承等層次上對系統進行設計。按照 DDD 的術語,我們可以把服務、事件、實體、值對象歸類爲原子塊,把資源庫、聚合、工廠歸類爲組合塊。

柔性設計:列舉了一些設計原則,類似於常見的軟件設計原則,只是換了一種說法。例如通過明確概念、避免概念過載、處理副作用等做法,得到的是一個高內聚低耦合的設計。

軟件設計原則,說到底不外乎,模塊內高內聚,模塊間去耦合。下面是學院派總結的一些原則,可以對照來看。

過程論

DDD 關於開發過程的論述

關於開發過程,《領域驅動設計 · 軟件核心複雜性應對之道》這本書第三部分用 6 章的內容重點論述了重構,但對於軟件工程,只是簡單提及 “敏捷開發過程”。整體而言,作者更傾向於用重構等手段不斷迭代,開發人員和業務專家緊密配合,讓協作貫穿整個項目的生命週期。本小節從下面三個方面對 DDD 的開發過程作一擴展性論述:

通過重構加深理解

領域驅動設計的重點在系統設計階段,但領域驅動設計同樣將重構作爲重要內容。《領域驅動設計 · 軟件核心複雜性應對之道》這本書第三部分共 6 章的篇幅在介紹重構。看其內容,名爲重構,實則仍然是設計,例如概念建模、柔性設計、分析模式、設計模式等等,基本都是一些偏宏觀的設計內容。內容上,跟 Martin Fowler 的《重構:改善既有代碼的設計》還是有很大區別。

論重構,當首推軟件開發一代宗師 Martin Fowler 的《重構:改善既有代碼的設計》,無人能出其右。

《重構:改善既有代碼的設計》,這本書的主要內容,用一個腦圖展示如下。

從這本書開始,” 代碼壞味道” 成爲開發領域的一個標準術語。在重構的過程中,有一些基於經驗的原則可供參考。

就重構的具體做法上,可以從函數、數據、業務邏輯三個方面來展開。

某種程度而言,軟件工程師仍然是手工業者,軟件開發仍然沒有銀彈,重構仍然是軟件在生長過程中不可或缺的調校手段。

因此,我們也不用迷信什麼銀彈,也不必忌諱什麼過度設計與設計不足,通過多次重構迭代,讓正確的設計逐步顯現。

敏捷開發與持續交付

在《領域驅動設計》出版的年代(2004),正值敏捷開發和極限編程大行其道的年代,《領域驅動設計》儘管不侷限於某種固定的開發過程,但主要還是面向 “敏捷開發過程” 這一新體系。

二十年後的今天,敏捷開發和極限編程早已式微,但喬梁老師的著作《持續交付》給我們提供了新的視角。喬梁老師在其著作《持續交付》裏梳理了軟件工程的進化史。最近兩三年,作爲騰訊外聘高級管理顧問,其 “價值探索 - 快速驗證” 的持續交付 2.0 雙環模型,令人印象深刻。

引入軟件工程的規範方法

國內最新的一本著作《解構領域驅動設計》,作者試圖參考 RUP,給 DDD 建立一種類似的規範過程。

《解構領域驅動設計》出版於 2021,據瞭解作者張逸是業內知名 DDD 專家,也是《實現領域驅動設計》一書的審校。在《解構領域驅動設計》這本書裏,作者試圖將 DDD 與 RUP 融合起來,提出了 DDDUP(領域驅動設計統一過程)模型,能否成功,我們拭目以待。

《解構領域驅動設計》這本書很厚,500 多頁,值得一讀,讀完能體會到作者深厚的行業經驗。

領域驅動設計統一過程(DDDUP)參考了統一過程(rationalunified process,RUP)的二維開發模型。整個過程的二維模型圖所示,橫軸代表推動領域驅動設計在構建過程中的時間,體現了過程的動態結構,構成元素主要爲 3 個階段(phase),每個階段可以由多個迭代構成;縱軸表現了領域驅動設計在各個階段中執行的活動,體現了過程的靜態結構,構成元素包括工作流(workflow)和元模型(meta model)。

語言論

統一語言

UBIQUITOUS LANGUAGE,有的書翻譯爲通用語言,有的書翻譯爲統一語言,其核心要點爲:

業務領域的例子

業務領域有業務領域的語言,不同業務有不同語言。下面是騰訊動漫的一個例子。

注:圖片摘自公司同事的 km 文章

技術領域的例子

以下是幾個技術領域的例子,不同層次有不同層次的語言。

在《領域驅動設計 · 軟件核心複雜性應對之道》這本書裏,明確提到了設計模式,這可以看做比編程語言更高一個層級的語言,提高了思維的抽象層次。

《微服務架構設計模式》和《面向模式的軟件架構》在架構設計層面建立起了一套完整的模式語言,比設計模式再高一個層級。

建模論

江湖派 vs 學院派

儘管在各自的著作中,兩派是互不感冒,但在平時的工作中,通常不分派別,哪派管用用哪派。在診斷方法上,中醫只能講出望聞問切,西醫能講的東西就太多太多。在系統建模這個事情上,學院派能講的東西,無論深度廣度還是精度,都遠超江湖派。

Model 是結合點

DDD、UML 和 Sysml 的底層邏輯全都指向 Model,Model 是三種技術的結合點。

限界上下文是理解 DDD 的鑰匙

限界上下文(Bounded Context)是 DDD 關鍵概念之一,同時可能是 DDD 裏面最令人迷惑的一個概念。可以說,理解了限界上下文也就理解了 DDD。技術上,我們可以把限界上下文建模爲 UML 的 Object/Class,或者是 SysML 的 Block,理解了這一點:Bounded Context = Object/Class = Block ,所有祕密迎刃而解。

有趣的是,《解構領域驅動設計》的作者,對限界上下文的理解也經歷了一個禪宗式的參悟的過程:參禪之初,看山是山,看水是水;禪有悟時,看山不是山,看水不是水;禪中徹悟,看山仍然山,看水仍然是水。

大概所有研究 DDD 的人,都會經歷類似的過程吧。從這個角度看,把一種技術方法講成玄學,需要學習的人去慢慢參悟,這很難說不是一種退步。學院一派雖然繁瑣但至少規範,而 DDD 在術語上的模棱兩可進而導致很大的解釋空間,讓學習的人經常誤入歧途,這是 DDD 的缺點之一。

UML/SysML 是重裝備

學院一派,仍在不斷精進,從多年前發展起來的 UML,到新近在其基礎上發展起來的 SysML,建模技術正在從軟件行業逐步拓展到一般技術行業。UML 強綁定於面向對象,SysML 去除了這種綁定,因而可用於更廣泛的領域。在軟件開發領域,一般學會 UML 就可以了,如果所屬業務不採用面向對象開發模式的話,SysML 則是更適合的建模工具。

下面是一些用 UML 做的圖,總體視覺效果看着還不錯。

架起橋樑

有一段時間,天天在琢磨 DDD/UML/SysML,腦子裏各派的技術體系在翻江倒海地打架,偶然有一次把幾幅圖放在一起,突然間眼前靈光乍現,剎那間頓悟了,那一刻終於找到了解開密碼的鑰匙。從此困擾自己很久的 DDD 的幾個核心概念,終於理清了頭緒,完成了整個 DDD 大廈的最後一塊拼圖,感覺自己終於可以從路的這頭走到路的那頭,再從路的那頭走到路的這頭。注意下面這張圖中左下角的表格,將 DDD/UML/SysML 之間的核心概念對應起來,三者之間的橋樑得以建立,從此一通百通。

寫在最後

DDD 自創立以來,到現在近 20 年,這期間 DDD 野蠻生長,逐漸變爲一個雜亂無章的的技術叢林,一百本書有一百種講法,包羅萬象。而同樣源遠流長的 UML/SysML/RUP 等專業方法,卻逐漸式微。

DDD 爲什麼這麼香?其中的奧祕在哪?就 DDD 本身而言,頂多算一種思想體系,遠未發展爲一種規範方法,其解釋空間很大導致人們的發揮空間也很大,這就非常適合技術諮詢界拿來做包裝講故事。相應的,UML/SysML/RUP 等專業方法,因爲嚴謹所以可發揮的空間就少,最關鍵的是,UML/SysML/RUP 等都有版權保護,各自也都推出了自己的專業認證體系,這就阻止了衆多技術諮詢公司的進入。

在中文社區裏,主要是衆多諮詢公司在熱情推動 DDD,沉寂多年的 DDD 進而又重出江湖。最近新出的一些書,各種 DDD 培訓課,就其內容而言,要麼是引經據典照着書本講,要麼就是頂着 DDD 的帽子講的卻是 UML 的內容。總的來說,DDD 沒多少新東西,只是把同樣的東西,以區別於學院派的語言,重新歸納了一遍,從而看起來像是新的東西。例如,把概要設計講成戰略建模,把詳細設計講成戰術建模,沒有本質區別,聽起來高大上而已。

本人學院派出身,對學院一派的技術更熟悉也更擅長,奈何江湖一派大行其道,從耳濡目染到深入研究,歷時數載,若有所得,在方法論上認識到一點:以學院一派的知識體系爲根基,用江湖一派的方法去講故事,兩派融合方可修得上乘武功。有點類似中西醫結合,以西醫打底,以中醫行走江湖,雖不中亦不遠。毛澤東曾說,自己對詞的欣賞趣味是 “偏於豪放,不廢婉約”,我對不同技術流派的態度是 “偏於學院,不廢江湖”。

當人們都在做多的時候,我選擇做少,正本清源,理清主脈。試圖在江湖派和學院派之間架起一座溝通的橋樑,是我一直想做的事情,本文只是一個起點。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/SMAycMJPWr018sU04kbfbQ