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