領(lǐng)域驅(qū)動設(shè)計詳解:是什么、為什么、怎么做?
什么是領(lǐng)域驅(qū)動設(shè)計?傳統(tǒng)分層架構(gòu)在實(shí)際開發(fā)中存在哪些問題?業(yè)務(wù)開發(fā)人員如何設(shè)計并搭建自己的領(lǐng)域模型?阿里文娛技術(shù)專家戰(zhàn)獒將為大家一一解答,并分享文娛在領(lǐng)域驅(qū)動設(shè)計上的實(shí)踐。
一 什么是領(lǐng)域驅(qū)動設(shè)計
領(lǐng)域驅(qū)動設(shè)計的概念是2004年Evic Evans在他的著作《Domain-Driven Design : Tackling Complexity in the Heart of Software》(中文譯名:領(lǐng)域驅(qū)動設(shè)計:軟件核心復(fù)雜性應(yīng)對之道)中提出的,從領(lǐng)域驅(qū)動設(shè)計提出距今已經(jīng)有15年的時間,為什么最近才開始在中國的互聯(lián)網(wǎng)圈大行其道?似乎一夜之間大家都在談?wù)?,那么領(lǐng)域驅(qū)動設(shè)計到底幫我們解決了什么問題?帶著這些疑問,一起來看下阿里巴巴文娛是如何實(shí)踐領(lǐng)域驅(qū)動設(shè)計的。
二 領(lǐng)域驅(qū)動設(shè)計大行其道的必然原因
軟件系統(tǒng)從來都不是憑空而來,而是以軟件的形式解決特定的問題。當(dāng)我們面臨現(xiàn)實(shí)世界的復(fù)雜問題時,如何以軟件的形式落地?領(lǐng)域驅(qū)動設(shè)計是一套方法論,指導(dǎo)我們將復(fù)雜問題進(jìn)行拆分、拆分出各個子系統(tǒng)間的關(guān)聯(lián)以及是如何運(yùn)轉(zhuǎn)的,幫助我們解決大型的復(fù)雜系統(tǒng)在落地中遇到的問題。
Evic Evans在著作中將軟件系統(tǒng)的設(shè)計分為2個部分:戰(zhàn)略設(shè)計和戰(zhàn)術(shù)設(shè)計。在戰(zhàn)略設(shè)計層面提出了域、子域、限界上下文等重要概念;在戰(zhàn)術(shù)設(shè)計層面提出了實(shí)體、值對象、領(lǐng)域服務(wù)、領(lǐng)域事件、聚合、工廠、資源庫等重要概念。如圖1所示:
圖1 戰(zhàn)略設(shè)計與戰(zhàn)術(shù)設(shè)計
戰(zhàn)略設(shè)計部分指導(dǎo)我們?nèi)绾尾鸱忠粋€復(fù)雜的系統(tǒng),戰(zhàn)術(shù)部分指導(dǎo)我們對于拆分出來的單個子系統(tǒng)如何進(jìn)行落地,在落地過程中應(yīng)該遵循哪些原則。
以大家熟知的電子商務(wù)系統(tǒng)舉例,早期的電商系統(tǒng)因?yàn)闃I(yè)務(wù)相對簡單,用戶量和團(tuán)隊(duì)規(guī)模也較小,一個單體應(yīng)用就可以搞定,隨著容量上升可以將單體應(yīng)用進(jìn)行橫向擴(kuò)容,比如早期的淘寶就是這樣做的。拆分過程中我們可以把電商系統(tǒng)這個單體應(yīng)用拆分成訂單子系統(tǒng)、庫存子系統(tǒng)、物流子系統(tǒng)、搜索推薦子系統(tǒng)等等,如圖2所示:
圖2 電商系統(tǒng)微服務(wù)劃分
領(lǐng)域驅(qū)動設(shè)計在戰(zhàn)略層面上的域、子域、限界上下文的劃分思想和微服務(wù)的劃分不謀而合。域?qū)?yīng)一個問題空間,也就是上例中的電商系統(tǒng);子域是把域這個大的問題空間拆分成若干個小的更容易解決的問題空間,也就是單體應(yīng)用向微服務(wù)演進(jìn)過程中劃分出來的各個子系統(tǒng);限界上下文是解決方案空間,每個子域?qū)?yīng)一個或多個解決方案空間。微服務(wù)的劃分是也是將一個大的問題拆分成若干個小的問題,每一個小的問題用一個或多個微服務(wù)來解決。
對于大多數(shù)開發(fā)同學(xué)來說都沒有機(jī)會接觸系統(tǒng)的劃分,這些工作一般是公司的技術(shù)領(lǐng)導(dǎo)層與架構(gòu)師來做的,普通的開發(fā)同學(xué)日常工作中接觸到的只是某一個具體微服務(wù)或微服務(wù)中某一個模塊的落地,那是不是說領(lǐng)域驅(qū)動設(shè)計對于普通開發(fā)同學(xué)來說就沒有用了?當(dāng)然不是這樣,領(lǐng)域驅(qū)動設(shè)計中的戰(zhàn)術(shù)設(shè)計部分就是指導(dǎo)我們?nèi)绾温涞匾粋€系統(tǒng)才可以使系統(tǒng)具備高可擴(kuò)展性、高可讀性。
所有的系統(tǒng)最終都要以代碼的形式落地,而落地的工作都是由普通的開發(fā)同學(xué)來做的,系統(tǒng)是否具備高可擴(kuò)展性、高可讀性直接影響了整個團(tuán)隊(duì)的效率。
三 傳統(tǒng)分層架構(gòu)存在的問題
對于大多數(shù)開發(fā)同學(xué)來說,大部分時間都花在落地一個個微服務(wù)上,下面我們來看阿里文娛是如何結(jié)合領(lǐng)域驅(qū)動設(shè)計的思想將微服務(wù)進(jìn)行戰(zhàn)術(shù)落地的。
目前筆者接觸過的微服務(wù)大多數(shù)都是分層架構(gòu)并且在Service層與Manager層實(shí)現(xiàn)具體的業(yè)務(wù)邏輯,使用DO、DTO、BO、VO等進(jìn)行數(shù)據(jù)傳輸,數(shù)據(jù)和行為基本完全隔離。這種分層結(jié)構(gòu)圖3是《Java開發(fā)手冊》中的標(biāo)準(zhǔn)分層結(jié)構(gòu)。
該規(guī)范中定義了各層的職責(zé),其中最重要的兩層Service層和Manager層是這樣規(guī)范的(以下兩層解釋摘抄自《Java 開發(fā)手冊》):
Service層
相對具體的業(yè)務(wù)邏輯服務(wù)層。
Manager層
通用業(yè)務(wù)處理層,它有如下特征:
- 對第三方平臺封裝的層,預(yù)處理返回結(jié)果及轉(zhuǎn)化異常信息。
- 對Service層通用能力的下沉,如緩存方案、中間件通用處理。
- 與DAO層交互,對多個DAO的組合復(fù)用。
圖3 傳統(tǒng)的分層結(jié)構(gòu)
阿里文娛早期的項(xiàng)目分層也基本都采用這種架構(gòu)形式。上面的分層并沒有問題,但是這種分層架構(gòu)采用的是包的形式進(jìn)行的層與層的隔離,需要每一位開發(fā)同學(xué)理解并且自覺遵守以上規(guī)范,但是在實(shí)際工作中我們發(fā)現(xiàn)很多同學(xué)對Service層和Manager層的區(qū)別并不是特別的清楚,即使清楚的同學(xué)大部分也并沒有完全遵守手冊中的規(guī)范,這種現(xiàn)象導(dǎo)致Manager層除了沉底一些通用能力以外和Service層并沒有什么本質(zhì)區(qū)別。
在實(shí)際的業(yè)務(wù)代碼中Service層和Manager層都充斥了大量的第三方依賴,對系統(tǒng)的穩(wěn)定性有很大的影響。每依賴一個第三方服務(wù)都要各種異常問題,這些異常處理的代碼往往會和業(yè)務(wù)代碼混在一起,當(dāng)這種代碼多了以后會使代碼的可讀性非常差。
阿里文娛業(yè)務(wù)的復(fù)雜度提升很快,業(yè)務(wù)迭代速度也很快, Service層和Manager層代碼量迅速膨脹,業(yè)務(wù)邏輯變得越來越復(fù)雜。在這種業(yè)務(wù)場景下,大文娛引入了領(lǐng)域驅(qū)動設(shè)計并設(shè)計了一套完整的領(lǐng)域驅(qū)動模型評估與演進(jìn)的解決方案來輔助開發(fā)同學(xué)將領(lǐng)域驅(qū)動設(shè)計的思想真正的落地。
四 文娛領(lǐng)域驅(qū)動設(shè)計實(shí)踐
領(lǐng)域驅(qū)動設(shè)計的關(guān)鍵在于識別業(yè)務(wù)的模型,而模型又是會隨著業(yè)務(wù)的發(fā)展而演進(jìn)的,對于新的業(yè)務(wù)來說能效平臺提供了業(yè)務(wù)模型分析的功能,開發(fā)同學(xué)可以在能效平臺設(shè)計并搭建自己的領(lǐng)域模型,搭建出來后能效平臺可以評估領(lǐng)域模型設(shè)計的是否合理,如果模型設(shè)計合理則可以基于以上設(shè)計的模型符合領(lǐng)域模型規(guī)范的代碼。對于已有應(yīng)用,能效平臺設(shè)計了一套領(lǐng)域注解并以SDK的形式提供出去:
- 第一步:開發(fā)同學(xué)按照領(lǐng)域設(shè)計的原則對業(yè)務(wù)代碼進(jìn)行分析并打上注解。
- 第二步:能效平臺可自動掃描該項(xiàng)目并收集該項(xiàng)目中的領(lǐng)域模型。
- 第三步:模型收集后,開發(fā)同學(xué)可以在能效平臺改進(jìn)業(yè)務(wù)模型并重新按照領(lǐng)域模型的規(guī)范生成代碼。
完整流程如下圖所示:
圖4 領(lǐng)域模型的生命周期
1 模型采集
對于已有的準(zhǔn)備重構(gòu)的應(yīng)用,我們設(shè)計了一套領(lǐng)域模型的注解,開發(fā)同學(xué)可以將注解加到對應(yīng)的類、屬性、方法上。當(dāng)系統(tǒng)是按數(shù)據(jù)模型落地而不是按領(lǐng)域模型的方式落地時,可以先找到系統(tǒng)的數(shù)據(jù)模型,然后在能效平臺對數(shù)據(jù)模型進(jìn)行組織生成領(lǐng)域模型。
圖5 領(lǐng)域模型注解
2 模型搭建
對于新應(yīng)用或者已經(jīng)進(jìn)行完模型采集的應(yīng)用,開發(fā)同學(xué)可以在能效平臺進(jìn)行模型的搭建和修改,如圖6所示。
圖6 領(lǐng)域模型
3 健康度評估
對于已經(jīng)搭建完的模型能效平臺,根據(jù)領(lǐng)域驅(qū)動設(shè)計的規(guī)范創(chuàng)建了一套完整的校驗(yàn)規(guī)則,模型搭建完成在生成腳手架之前會根據(jù)校驗(yàn)規(guī)則進(jìn)行打分,當(dāng)打分通過時可以將模型生成腳手架。
圖7 模型校驗(yàn)
4 腳手架生成
當(dāng)模型搭建完畢并且校驗(yàn)通過后可以將模型生成腳手架,其代碼結(jié)構(gòu)是按照六邊形架構(gòu)的標(biāo)準(zhǔn)生成的,六邊形架構(gòu)也成為端口與適配器架構(gòu),該架構(gòu)的思想是將內(nèi)部核心的領(lǐng)域邏輯與外界依賴進(jìn)行隔離,這里的依賴是指所有對其他微服務(wù)的依賴、http的依賴、數(shù)據(jù)庫依賴、緩存依賴、消息中間件依賴等等,所有的這些依賴都通過適配器進(jìn)行轉(zhuǎn)換成應(yīng)用可理解可識別的最小化信息。
在實(shí)際的項(xiàng)目中,每種依賴都要考慮各種異常情況并進(jìn)行處理,而這些處理實(shí)際上并不數(shù)據(jù)領(lǐng)域邏輯,卻耦合到了業(yè)務(wù)代碼里,當(dāng)這種依賴多了對系統(tǒng)的穩(wěn)定性會產(chǎn)生很大的影響,傳統(tǒng)的分層架構(gòu)雖然也會讓我們將自身的領(lǐng)域邏輯和依賴進(jìn)行分離,在阿里巴巴規(guī)范手冊中提到所有的依賴都應(yīng)該放到Manager層,但是這種規(guī)范是非常容易被打破的。六邊形架構(gòu)從應(yīng)用分層上讓我們更容易去遵守這樣的規(guī)范。
圖8 六邊形架構(gòu)
根據(jù)六邊形架構(gòu)的指導(dǎo)思想,在實(shí)際的應(yīng)用分層中一般劃分為四層,分別是:
- 用戶接口層:負(fù)責(zé)用戶展現(xiàn)相關(guān)的邏輯。
- 應(yīng)用層:負(fù)責(zé)對一個用例進(jìn)行流程編排(將接口用例分成若干個步驟,但是不負(fù)責(zé)每步的具體實(shí)施)。
- 領(lǐng)域?qū)樱贺?fù)責(zé)實(shí)現(xiàn)核心的領(lǐng)域邏輯即業(yè)務(wù)邏輯(負(fù)責(zé)實(shí)現(xiàn)具體的業(yè)務(wù)邏輯)。
- 基礎(chǔ)設(shè)施層:所有依賴的具體實(shí)現(xiàn)。
但是從應(yīng)用架構(gòu)的角度看,層級組織形式可以分為兩種:
傳統(tǒng)分層架構(gòu)
如圖9左側(cè),這種分層架構(gòu)是Evic Evans在《Domain-Driven Design : Tackling Complexity in the Heart of Software》中提出的,其中用戶接口層、應(yīng)用層、領(lǐng)域?qū)涌芍苯右蕾嚮A(chǔ)設(shè)施層,與圖3的傳統(tǒng)架構(gòu)并無本質(zhì)區(qū)別,因?yàn)樗袑佣贾苯右蕾嚵嘶A(chǔ)設(shè)施層。這種方式需要強(qiáng)制開發(fā)同學(xué)將所有的依賴進(jìn)行下沉,隨著時間的推移這種規(guī)范非常容易被打破。
圖9 層依賴關(guān)系
依賴倒置的分層架構(gòu)
如圖9右側(cè),這種分層架構(gòu)是依賴倒置的分層架構(gòu),特點(diǎn)是:
- 基礎(chǔ)設(shè)計層可直接依賴其他三層,反之則不行。
- 用戶接口層、應(yīng)用層、領(lǐng)域?qū)尤绻褂没A(chǔ)設(shè)施層中的能力,只能通過IOC的方式進(jìn)行依賴注入,這也遵從了面向?qū)ο缶幊讨械囊蕾嚨怪迷瓌t。
當(dāng)開發(fā)同學(xué)要在以上三層中直接引用第三方依賴時,是找不到具體的類信息的,也就是不能import。同時這種方式對單元測試的規(guī)范也可以起到很大的作用,當(dāng)我們編寫單元測試時可以為領(lǐng)域?qū)幼⑷胍粋€測試運(yùn)行時的依賴,這樣應(yīng)用運(yùn)行單元測試可以不依賴下游服務(wù),在代碼層面上也更加規(guī)范。
五 總結(jié)
經(jīng)典的三層或多層架構(gòu)雖然是目前最普遍的架構(gòu),但是在隔離方面做得并不夠好。在業(yè)務(wù)架構(gòu)選型時要結(jié)合自身業(yè)務(wù)特點(diǎn),而不能千篇一律的選擇某一種業(yè)務(wù)架構(gòu),合適的業(yè)務(wù)架構(gòu)可以延長項(xiàng)目的生命周期,降低項(xiàng)目的重構(gòu)頻率,最終達(dá)到降低人力成本的目的。