領(lǐng)域驅(qū)動(dòng)架構(gòu)的演進(jìn)

我們回顧了經(jīng)典三層架構(gòu)與領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)四層架構(gòu),然后又對(duì)分層架構(gòu)模式的產(chǎn)生與設(shè)計(jì)原則做了一次歷史回顧。我們先后參考了 Robert Martin 的整潔架構(gòu)、Cockburn 的六邊形架構(gòu)以及 Toby Clemson 給出的微服務(wù)架構(gòu)模型?,F(xiàn)在,是時(shí)候?yàn)轭I(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的架構(gòu)模型做一次總結(jié)陳詞了。然而事情并未結(jié)束,因?yàn)槿魏渭夹g(shù)結(jié)論都并非句點(diǎn),而僅僅代表了滿足當(dāng)時(shí)技術(shù)背景的一種判斷,技術(shù)總是在演進(jìn),領(lǐng)域驅(qū)動(dòng)架構(gòu)亦是如此。與其關(guān)心結(jié)果,不如將眼睛投往這個(gè)演進(jìn)的過程,或許風(fēng)景會(huì)更加動(dòng)人。

根據(jù)“依賴倒置原則”與 Robert Martin 提出的“整潔架構(gòu)”思想,我們推翻了 Eric Evans 在《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》書中提出的分層架構(gòu)。Vaughn Vernon 在《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書中給出了改良版的分層架構(gòu),他將基礎(chǔ)設(shè)施層奇怪地放在了整個(gè)架構(gòu)的最上面:

點(diǎn)擊這里了解「領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)踐」詳細(xì)內(nèi)容

整個(gè)架構(gòu)模型清晰地表達(dá)了領(lǐng)域?qū)觿e無依賴的特質(zhì),但整個(gè)架構(gòu)卻容易給人以一種錯(cuò)亂感。單以這個(gè)分層模型來看,雖則沒有讓高層依賴低層,卻又反過來讓低層依賴了高層,這仍然是不合理的。當(dāng)然你可以說此時(shí)的基礎(chǔ)設(shè)施層已經(jīng)變成了高層,然而從之前分析的南向網(wǎng)關(guān)與北向網(wǎng)關(guān)來說,基礎(chǔ)設(shè)施層存在被“肢解”的可能。坦白講,這個(gè)架構(gòu)模型仍然沒有解決人們對(duì)分層架構(gòu)的認(rèn)知錯(cuò)誤,例如它并沒有很好地表達(dá)依賴倒置原則與依賴注入。還需要注意的是,這個(gè)架構(gòu)模型將基礎(chǔ)設(shè)施層放在了整個(gè)分層架構(gòu)的最頂端,導(dǎo)致它依賴了用戶展現(xiàn)層,這似乎并不能自圓其說。我們需要重新梳理領(lǐng)域驅(qū)動(dòng)架構(gòu),展示它的演進(jìn)過程。

該怎么演進(jìn)領(lǐng)域驅(qū)動(dòng)架構(gòu)?可以從兩個(gè)方向著手:

  • 避免領(lǐng)域模型出現(xiàn)貧血模型
  • 保證領(lǐng)域模型的純粹性

避免貧血的領(lǐng)域模型

我們需要回顧經(jīng)典的 Java 三層架構(gòu)對(duì)領(lǐng)域模型的設(shè)計(jì)。在這個(gè)三層架構(gòu)中,領(lǐng)域邏輯被定義在業(yè)務(wù)邏輯層的 Service 對(duì)象中,至于反映了領(lǐng)域概念的領(lǐng)域?qū)ο髣t被定義為 Java Bean,這些 Java Bean 并沒有包含任何領(lǐng)域邏輯,因此被放在了數(shù)據(jù)訪問層。注意,這是經(jīng)典三層架構(gòu)的關(guān)鍵,即代表領(lǐng)域概念的 Java Bean 被放在了數(shù)據(jù)訪問層,而非業(yè)務(wù)邏輯層。經(jīng)典三層架構(gòu)采用了 J2EE 開發(fā)的 DAO 模式,即將訪問數(shù)據(jù)庫的邏輯封裝到數(shù)據(jù)訪問對(duì)象(Data Access Object)中。這些 DAO 對(duì)象僅負(fù)責(zé)與數(shù)據(jù)庫的交互,并實(shí)現(xiàn)領(lǐng)域?qū)ο蟮綌?shù)據(jù)表的 CRUD(增刪改查)操作,因而也被放到了數(shù)據(jù)訪問層中,如下圖所示:

image

如果以面向?qū)ο笤O(shè)計(jì)范式進(jìn)行領(lǐng)域建模,我們需要遵循面向?qū)ο蟮脑O(shè)計(jì)原則,其中最重要的設(shè)計(jì)原則就是“數(shù)據(jù)與行為應(yīng)該封裝在一起”,這也是 GRASP 模式中“信息專家模式”的體現(xiàn)。前面提及的 Java Bean 由于僅包含了訪問私有字段的 get 和 set 方法,可以說是對(duì)面向?qū)ο笤O(shè)計(jì)原則的“背叛”,Martin Fowler 則將這種沒有任何業(yè)務(wù)行為的對(duì)象稱之為“貧血對(duì)象”。基于這樣的貧血對(duì)象進(jìn)行領(lǐng)域建模,得到的模型則被稱之為“貧血模型”。這種貧血模型被認(rèn)為是簡(jiǎn)單的,卻不具備對(duì)象的豐富表達(dá)能力,當(dāng)業(yè)務(wù)邏輯變得復(fù)雜時(shí),在表達(dá)領(lǐng)域模型方面就會(huì)變得“力不從心”,無法有效應(yīng)對(duì)重用與變化,且可能導(dǎo)致臃腫的“上帝類”。貧血模型的種種問題會(huì)在戰(zhàn)術(shù)設(shè)計(jì)中再做深入探討,這里我們姑且給出一個(gè)結(jié)論,即:在面向?qū)ο笤O(shè)計(jì)背景下,當(dāng)我們面對(duì)相對(duì)復(fù)雜的業(yè)務(wù)邏輯時(shí),應(yīng)避免設(shè)計(jì)出貧血模型。

點(diǎn)擊這里了解「領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)踐」詳細(xì)內(nèi)容

要避免貧血模型,就需要合理地將操作數(shù)據(jù)的行為分配給這些領(lǐng)域模型對(duì)象(Domain Model),即戰(zhàn)術(shù)設(shè)計(jì)中的 Entity 與 Value Object,而不是前面提及的 Service 對(duì)象。由于領(lǐng)域模型對(duì)象包含了領(lǐng)域邏輯,就需要從數(shù)據(jù)訪問層轉(zhuǎn)移到業(yè)務(wù)邏輯層。至于那些不屬于任何領(lǐng)域模型對(duì)象的領(lǐng)域邏輯,仍然放到 Service 對(duì)象中。由于 DAOs 對(duì)象需要操作這些領(lǐng)域模型對(duì)象,使得處于數(shù)據(jù)訪問層的 DAOs 對(duì)象必須依賴領(lǐng)域?qū)拥念I(lǐng)域模型對(duì)象,也就是說,要避免貧血的領(lǐng)域模型,就不可能避免底層的數(shù)據(jù)訪問層對(duì)業(yè)務(wù)邏輯層的依賴。

從分層的職責(zé)和意義講,一個(gè)系統(tǒng)的基礎(chǔ)不僅僅限于對(duì)數(shù)據(jù)庫的訪問,還包括訪問諸如網(wǎng)絡(luò)、文件、消息隊(duì)列或者其他硬件設(shè)施,因此 Eric Evans 將其更名為“基礎(chǔ)設(shè)施層”是非常合理的。至于將業(yè)務(wù)邏輯層更名為領(lǐng)域?qū)右彩穷}中應(yīng)有之義。遵循整潔架構(gòu)思想,基礎(chǔ)設(shè)施層屬于架構(gòu)的外層,它依賴于處于內(nèi)部的領(lǐng)域?qū)右嗍钦_的做法。在領(lǐng)域?qū)?,封裝了領(lǐng)域邏輯的 Services 對(duì)象則可能需要持久化領(lǐng)域?qū)ο螅踔量赡芤蕾嚮A(chǔ)設(shè)施層的其他組件。于是,之前的分層架構(gòu)就演進(jìn)為:

image

保證領(lǐng)域模型的純粹性

若將整個(gè)層次看做一個(gè)整體,在剛才給出的分層架構(gòu)圖中,加粗的兩條依賴線可以清晰地看到領(lǐng)域?qū)优c基礎(chǔ)設(shè)施層之間產(chǎn)生了“雙向依賴”。在實(shí)際開發(fā)中,若這兩層又被定義為兩個(gè)模塊,雙向依賴就成為了設(shè)計(jì)壞味,它導(dǎo)致了兩個(gè)層次的緊耦合。此時(shí),領(lǐng)域模型變得不再純粹,根由則是高層直接依賴了低層,而不是因?yàn)榈蛯右蕾嚵烁邔?。故而我們需要去掉右?cè) Services 指向 DAOs 的依賴。

DAOs 負(fù)責(zé)訪問數(shù)據(jù)庫,其實(shí)現(xiàn)邏輯是容易變化的?;凇胺€(wěn)定依賴原則”,我們需要讓領(lǐng)域?qū)咏⒃谝粋€(gè)更加穩(wěn)定的基礎(chǔ)上。抽象總是比具體更穩(wěn)定,因此,改進(jìn)設(shè)計(jì)的方式是對(duì) DAOs 進(jìn)行抽象,然后利用依賴注入對(duì)數(shù)據(jù)訪問的實(shí)現(xiàn)邏輯進(jìn)行注入,如下圖所示:

DAOs 的抽象到底該放在哪里?莫非需要為基礎(chǔ)設(shè)施層建立一個(gè)單獨(dú)的抽象層嗎?這牽涉到我們對(duì)數(shù)據(jù)庫訪問的認(rèn)知。任何一個(gè)軟件系統(tǒng)的領(lǐng)域?qū)ο蠖即嬖谄渖芷冢眍I(lǐng)域邏輯的業(yè)務(wù)方法其實(shí)就是在創(chuàng)造它,發(fā)現(xiàn)它,更新它的狀態(tài),最后通常也會(huì)銷毀它。倘若部署軟件系統(tǒng)的計(jì)算機(jī)足夠強(qiáng)勁與穩(wěn)定,就不再需要任何外部資源了;這時(shí),對(duì)領(lǐng)域?qū)ο蟮纳芷诠芾砭妥兂闪藢?duì)普通對(duì)象的內(nèi)存管理。因此,從業(yè)務(wù)角度看,管理對(duì)象的生命周期是必須的,訪問外部資源卻并非必須。只是因?yàn)橛?jì)算機(jī)資源不足以滿足這種穩(wěn)定性,才不得已引入外部資源罷了。也就是說,訪問這些領(lǐng)域?qū)ο髮儆跇I(yè)務(wù)要素,而如何訪問這些領(lǐng)域?qū)ο螅ㄈ缤ㄟ^外部資源),則屬于具體實(shí)現(xiàn)的技術(shù)要素。

從編碼角度看,領(lǐng)域?qū)ο髮?shí)例的容身之處不過就是一種數(shù)據(jù)結(jié)構(gòu)而已,區(qū)別僅在于存儲(chǔ)的位置。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)將管理這些對(duì)象的數(shù)據(jù)結(jié)構(gòu)抽象為資源庫(Repository)。通過這個(gè)抽象的資源庫訪問領(lǐng)域?qū)ο?,自然就?yīng)該看作是一種領(lǐng)域行為。倘若資源庫的實(shí)現(xiàn)為數(shù)據(jù)庫,并通過數(shù)據(jù)庫持久化的機(jī)制來實(shí)現(xiàn)領(lǐng)域?qū)ο蟮纳芷诠芾?,則這個(gè)持久化行為就是技術(shù)因素。

結(jié)合前面對(duì)整潔架構(gòu)的探討,抽象的資源庫接口代表了領(lǐng)域行為,應(yīng)該放在領(lǐng)域?qū)?;?shí)現(xiàn)資源庫接口的數(shù)據(jù)庫持久化,需要調(diào)用諸如 MyBatis 這樣的第三方框架,屬于技術(shù)實(shí)現(xiàn),應(yīng)該放在基礎(chǔ)設(shè)施層。于是,分層架構(gòu)就演進(jìn)為:

由于抽象的 Repositories 被搬遷至領(lǐng)域?qū)?,圖中的領(lǐng)域?qū)泳筒辉僖蕾嚾魏纹渌麑哟蔚慕M件或類,成為一個(gè)純粹的領(lǐng)域模型。我們的演進(jìn)正逐步邁向整潔架構(gòu)!

點(diǎn)擊這里了解「領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)踐」詳細(xì)內(nèi)容

用戶展現(xiàn)層的變遷

現(xiàn)代軟件系統(tǒng)變得日趨復(fù)雜,對(duì)于一個(gè)偏向業(yè)務(wù)領(lǐng)域的分層架構(gòu),領(lǐng)域?qū)拥恼{(diào)用者決不僅限于用戶展現(xiàn)層的 UI 組件,比如說可以是第三方服務(wù)發(fā)起對(duì)領(lǐng)域邏輯的調(diào)用。即使是用戶展現(xiàn)層,也可能需要不同的用戶交互方式與呈現(xiàn)界面,例如 Web、Windows 或者多種多樣的移動(dòng)客戶端。因此在分層架構(gòu)中,無法再用“用戶展現(xiàn)層”來涵蓋整個(gè)業(yè)務(wù)系統(tǒng)的客戶端概念。通常,我們需要采用前后端分離的架構(gòu)思想,將用戶展現(xiàn)層徹底分離出去,形成一個(gè)完全松耦合的前端層。

不管前端的展現(xiàn)方式如何,它的設(shè)計(jì)思想是面向調(diào)用者,而非面向領(lǐng)域。因此,我們在討論領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)時(shí),通常不會(huì)將前端設(shè)計(jì)納入到領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的范圍。有人嘗試將領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)引入到前端設(shè)計(jì)中,那是將前端自身當(dāng)做一種領(lǐng)域。在設(shè)計(jì)后端 API 時(shí),我們確乎需要從調(diào)用者的角度考慮 API 的定義,并確定從 Domain Model(或者 Service Model,又或者是 Resource Model)到 View Model 的轉(zhuǎn)換,又或者考慮引入所謂“DTO(Data Transfer Object,數(shù)據(jù)傳輸對(duì)象)”,但這些都只限于后端 API 協(xié)議的設(shè)計(jì)。

準(zhǔn)確地講,前端可以視為是與基礎(chǔ)設(shè)施層組件進(jìn)行交互的外部資源,如前面整潔架構(gòu)中的 Web 組件與 UI 組件。為了簡(jiǎn)化前端與后端的通信集成,我們通常會(huì)為系統(tǒng)引入一個(gè)開放主機(jī)服務(wù)(OHS),為前端提供統(tǒng)一而標(biāo)準(zhǔn)的服務(wù)接口。該接口實(shí)際上就是之前整潔架構(gòu)中提及的 Controllers 組件,也即我提出的基礎(chǔ)設(shè)施層的北向網(wǎng)關(guān)。于是,分層架構(gòu)就演變?yōu)椋?/p>

點(diǎn)擊這里了解「領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)踐」詳細(xì)內(nèi)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容