DDD 到底是銀彈還是垃圾
每過一段時間,就會有人跳出來批判 DDD,這東西到底是垃圾還是銀彈?
在某某公司干活的時候,有一批人聲稱要用 DDD 改造老舊系統(tǒng),徹底解決核心流程規(guī)模化之后,項目難以維護(hù)的問題。之前某篇文章里的這張圖,就是在用 DDD 做項目重構(gòu)之前的爛攤子:

大家都很聰明,聰明到最后沒人知道這新需求到底該往哪里寫了。架構(gòu)師們聚在一起學(xué)習(xí) DDD 精神,產(chǎn)出學(xué)習(xí)報告,大半年過去,終于出了一些成果,有些子項目完成了用 DDD 進(jìn)行的重構(gòu),年底可以拿來在酒會上邀功了,這下我們跟上了業(yè)界業(yè)務(wù)開發(fā)的主流方法論,可喜可賀,可喜可賀啊。
年末的時候部門內(nèi)匿名提問的小紙條卻向架構(gòu)師們發(fā)直球:“為什么用了 DDD 以后,代碼更難懂了?”,當(dāng)時引得各位 DDD 推手尷尬無比,只能搪塞過去。
所以你覺得我是要批判么?那倒不是。
在某某司工作期間,到離職前,我把市面上所有 DDD 相關(guān)的書全部看了一遍。對其理論體系進(jìn)行了完整的了解,可以說這套理論還是有些用處的,DDD 的理論誕生時間比較早,微服務(wù)的趨勢是后來才爆發(fā)的。但微服務(wù)剛開始沒有明確的拆分指導(dǎo),人們發(fā)現(xiàn) DDD 里的 bounded context 好像看著正好和服務(wù)的粒度是可以做個對應(yīng)的,DDD 就成為了很多公司做業(yè)務(wù)的絕對主流方法論。
雖然很多技術(shù)人員不愛聽,但是技術(shù)優(yōu)劣和商業(yè)成敗其實沒什么必然的聯(lián)系。同樣的,方法論的對錯和項目的成功與否也沒有必然的關(guān)系。很多大公司做業(yè)務(wù)的人出來講他們的技術(shù)方法論,這些人可能連自己的項目為啥成功都不一定知道,你指望能對你的場景產(chǎn)生直接幫助那可能是想多了。只是當(dāng)聽個樂,得個借鑒那可能還沒什么問題。真的當(dāng)金科玉律去執(zhí)行,那撞一頭包也正常。
DDD 和其它的工程方法論一樣,沒有辦法證偽。放眼望去,純粹堆砌人肉電池,不用 DDD 的項目也那么多成功的,大家的屁股還是在跟著公司的市值跑,哪家公司市值漲到中國第一了,那他們的技術(shù)就牛逼,這叫看市值決定價值觀。如果一家公司靠 996 成功了,那 996 就是商業(yè)致勝的法寶,不學(xué)你就落后了。屁股可以決定腦袋嘛。
不過作為一個矜持的技術(shù)人員,我們在批判方法論的時候,還是應(yīng)該要先對敵人有一些了解。
所以這一篇,我就簡單帶你們看看 DDD 里那些鬼名詞都是什么意思。
戰(zhàn)術(shù)設(shè)計與戰(zhàn)略設(shè)計
整個 DDD 的方法論可以劃分為兩個大模塊,戰(zhàn)術(shù)設(shè)計、戰(zhàn)略設(shè)計。這個你顧名思義,戰(zhàn)術(shù)是小,戰(zhàn)略是大。
- 戰(zhàn)術(shù)設(shè)計指的就是單模塊級的設(shè)計,基本都是純技術(shù)范疇的東西,只DDD 中給代碼命名和模塊設(shè)計給出了一些指導(dǎo)方法
- 戰(zhàn)略設(shè)計指的是大項目的模塊拆分,這個和一線程序員關(guān)系不大,主要是公司內(nèi)怎么在 bu 之間切蛋糕,bu 內(nèi)怎么在 team 之間分贓
現(xiàn)在很多校招程序員可能或多或少都會碰到一些 OOP 方面的面試題,比如三大特性五大原則之類的,這些原則是設(shè)計項目的時候可以參考的原則, DDD 的戰(zhàn)術(shù)設(shè)計就是在單模塊上的各種命名規(guī)則和設(shè)計方法。只不過 OOP 這些原則的發(fā)明人(嚴(yán)格的說應(yīng)該是匯總?cè)?是 uncle bob,就是 《clean code》,《clean architecture》 的作者,這位白胡子爺爺大概率和 DDD 社區(qū)是尿不到一個壺里的,所以 《clean architecture》 這本書里只字未提 DDD。
公司的業(yè)務(wù)要怎么分派給不同的 bu(部門)去完成,這個一般是公司 CTO 或者 GM 要做的事情,部門內(nèi)的項目要怎么分,哪些組做哪些事情。這是戰(zhàn)略設(shè)計的范疇。DDD 聲稱戰(zhàn)略設(shè)計也是要有方法的。這部分也是很多程序員認(rèn)為最沒用的一部分,我們后面來批判一下這些程序員。
戰(zhàn)術(shù)設(shè)計
戰(zhàn)術(shù)設(shè)計是純技術(shù)范疇的東西,最讓人頭痛的就是里面的名詞。
貧血模式和充血模式:DDD 推薦你用充血模式寫代碼,也就是按 OOP 的方式去做抽象,然后把行為掛在對象上,而不是以純過程式 的方法去寫代碼。所謂的充血,就是對象本身有很多關(guān)聯(lián)的行為,而不只是一個單純的數(shù)據(jù)庫的表的字段映射。DDD 聲稱的充血模式的優(yōu)勢是,大部分的行為被封裝到了對象內(nèi)部,這樣我們在閱讀流程代碼的時候,是一目了然的,直接能看到 step 1,step 2,step 3。但實際即使我們不用 OOP 來組織行為,一樣可以把不同的業(yè)務(wù) step 做好封裝和復(fù)用。有些公司的服務(wù)粒度拆的特別細(xì),比如只有 5000-10000 行代碼,在 DDD 里聲稱的充血模式的優(yōu)勢沒有那么明顯。
值對象和實體:這個也挺離譜的,值對象就是純粹的數(shù)值、文本類型,比如:
type?person?struct?{
??age?int
??name?string
}
就是值對象,如果我們給這個 person 加一個 id,讓它能表示 person 的唯一性了;
type?person?struct?{
??id??int
??age?int
??name?string
}
那它就是實體了。
這兩個概念只是給我們?nèi)粘S玫膶ο髠冞M(jìn)行了一個簡單的分類,沒什么大用處。
聚合根:DDD 里所謂的聚合根是事務(wù)粒度的 entity,也就是說,如果我們對 db 進(jìn)行存取,那么我們就需要有一個聚合根,如果在一個事務(wù)里需要操作多張表,那么就需要給多張表關(guān)聯(lián)一個單獨的聚合根。

聚合根可以由一個 entity 組成,也可以由多個 entity 組成,就是你完成一個 db 事務(wù)的時候有多少關(guān)聯(lián)的對象 ,那可能就有多少在同一個聚合根下面的 entity。
六邊形架構(gòu):這個所謂的六邊形架構(gòu),就是除了業(yè)務(wù)以外的所有外部變化都抽象成 adapter interface 做適配。如果你稍微理解一點點點依賴反轉(zhuǎn),那應(yīng)該知道怎么樣去做這種抽象。如果你一點都不了解,那我建議你去看看 go-micro 的代碼。如果看不懂,建議還是盡早轉(zhuǎn)行吧~

六邊形架構(gòu)這東西主要是名字實在起的太奇怪,在 《clean architecture》那本書里,uncle bob 也給過一張圖:

《evolutionary architecture》這本書出自造詞大本營 thoughtworks 的員工之手,里面有一個 plugin architecture,就是有些人特別喜歡說的插件化架構(gòu):

Repo Pattern:DDD 理論認(rèn)為我們業(yè)務(wù)項目的存儲這一層是可能經(jīng)常變化的,所以就專門存儲層的 interface 設(shè)計單獨拿出來,稱為 Repo Pattern,這東西實在沒啥可說的,find,getlist,save,你只要有一點點 orm 經(jīng)驗,里面有啥接口應(yīng)該自己都可以默寫出來。
事實是在 2021 年,我們的存儲系統(tǒng)基本是不太可能做切換的了,即使切換,那些新興的社區(qū)存儲系統(tǒng)也會支持 MySQL 協(xié)議,基礎(chǔ)設(shè)施想要侵入代碼,那簡單是大逆不道啊。
領(lǐng)域事件:其實就是做上下游解耦的 kafka message,我們用 domain event 顯得會更洋氣一些。
領(lǐng)域服務(wù):Domain service,顧名思義,你認(rèn)為是自己部門或者組內(nèi)的局部 api gateway 也是可以的。
綜上,如果你是在大公司一線工作了兩三年的程序員,上面這些東西應(yīng)該馬上就能理解,沒有啥值得說的。如果是為了去架構(gòu)師大會上秀一秀,你總得包裝一下讓自己顯得沒那么土吧?
戰(zhàn)略設(shè)計
Domain:領(lǐng)域,你們公司是干啥的,你都不知道嗎?
Core Domain:你們公司的賣貨的,那賣貨就是你們與其它競爭對手的關(guān)鍵競爭環(huán)節(jié)。這就是核心域,就是核心業(yè)務(wù),為啥聰明人都往核心業(yè)務(wù)擠?核心業(yè)務(wù)的湯也比邊緣業(yè)務(wù)的飯好啊。
SubDomain:你們公司的賣貨的,但是用戶沒法付錢,那也沒法干,支付就是子領(lǐng)域。
Supporting Domain:你們公司是賣貨的,但是客戶想看一些指標(biāo),你總得有系統(tǒng)能支持吧?可能就是些寫寫 SQL 的系統(tǒng)。支持域。
Generic Domain:你管你們公司干什么呢?員工的在職離職,工資發(fā)放總得有系統(tǒng)能支持吧,這些就是通用域。
除了第一個 Domain ,其余四個 domain 重要性逐級遞減,遞減的意思是,如果公司要裁員,那是從下面往上面裁。
前面我說有些程序員覺得 DDD 戰(zhàn)略設(shè)計沒用,你連自己所在的組,從事的工作職責(zé)對于公司來說重不重要都不清楚,那被裁的時候也別哭哦。
統(tǒng)一語言:這個就更好理解了,比如跳水這個詞,你說跳水的時候指的是這個:

而你同事說跳水的時候指的是這個:

這里你們聊的是工作,那說明你們一定不是在同一個上下文里工作,可能你們倆一個在體育賽事部門,另一個可能是在金融部門, DDD 認(rèn)為可以用統(tǒng)一語言來進(jìn)行領(lǐng)域劃分工作。劃分后在同一個上下文內(nèi),同一個名詞大家說出來意思一致。這就是 Bounded Context, ?ain。
既然拆分了,如果我們還在同一個 domain 內(nèi),那完成業(yè)務(wù)流程是需要協(xié)作的,這個不同 Context 的協(xié)作方式就叫 Context Maps 或者 Integration Type。
名詞很惡心,但具體的方法就兩種,兩個微服務(wù)要么通過 RPC 通信,要么通過 MQ 通信。
如果通過 RPC 通信,那 callee 一般是 caller 的爹,很多時候 callee 掛了是要影響 caller 的(當(dāng)然也有熔斷之類的方法避免一起死)。
通過通過 MQ 通信,那上游一般是下游的爹,因為上游一個重構(gòu),下游們可能就都炸了,最終一致都是屁話,多少公司的最終一致都是靠人肉修的。
這種爹和兒子的關(guān)系就是 Conformist。如果爹能多考慮一下兒子的需求,那就是 Customer-Supplier 關(guān)系,畢竟顧客名義上還是上帝。如果跨系統(tǒng)有一些需要共享的定義,比如公司里的業(yè)務(wù)分類,可能大家都要從某個系統(tǒng)的 PHP 文件里解析出來在自己的系統(tǒng)里去用,那這時候可能得去使用別人的代碼,這種叫 Shared-Kernel,Kernel 一改,大家一起死。
最后,有時候我們可以用一個叫 ACL 的東西攔住上游的一些修改對我們的業(yè)務(wù)邏輯侵入:
防腐層:Anti-Corruption-Layer,就是我要把外部系統(tǒng)的變化攔截在對接層,不要讓別人的屎甩到我身上。
講到這里,基本的概念我們已經(jīng)都過一遍了,你要說 DDD 一點用處都沒有,那我也是不同意的,至少看完了這些書,我知道去哪里能賺到更多的錢了。
額外再說一句,DDD 的書寫的都不怎么樣。
