認(rèn)識(shí)分層架構(gòu)
分層架構(gòu)是運(yùn)用最為廣泛的架構(gòu)模式,幾乎每個(gè)軟件系統(tǒng)都需要通過(guò)層(Layer)來(lái)隔離不同的關(guān)注點(diǎn)(Concern Point),以此應(yīng)對(duì)不同需求的變化,使得這種變化可以獨(dú)立進(jìn)行;此外,分層架構(gòu)模式還是隔離業(yè)務(wù)復(fù)雜度與技術(shù)復(fù)雜度的利器,《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)模式、原理與實(shí)踐》寫道:
為了避免將代碼庫(kù)變成大泥球(BBoM)并因此減弱領(lǐng)域模型的完整性且最終減弱可用性,系統(tǒng)架構(gòu)要支持技術(shù)復(fù)雜性與領(lǐng)域復(fù)雜性的分離。引起技術(shù)實(shí)現(xiàn)發(fā)生變化的原因與引起領(lǐng)域邏輯發(fā)生變化的原因顯然不同,這就導(dǎo)致基礎(chǔ)設(shè)施和領(lǐng)域邏輯問(wèn)題會(huì)以不同速率發(fā)生變化。
這里所謂的“以不同速率發(fā)生變化”,其實(shí)就是引起變化的原因各有不同,這正好是單一職責(zé)原則(Single-Responsibility Principle,SRP)的體現(xiàn)。Robert Martin 認(rèn)為單一職責(zé)原則就是“一個(gè)類應(yīng)該只有一個(gè)引起它變化的原因”,換言之,如果有兩個(gè)引起類變化的原因,就需要分離。單一職責(zé)原則可以理解為架構(gòu)原則,這時(shí)要考慮的就不是類,而是層次。我們?yōu)槭裁匆獙I(yè)務(wù)與基礎(chǔ)設(shè)施分開?正是因?yàn)橐鹚鼈冏兓脑虿煌?/p>
經(jīng)典分層架構(gòu)
分層架構(gòu)由來(lái)已久,將一個(gè)軟件系統(tǒng)進(jìn)行分層,似乎已經(jīng)成為了每個(gè)開發(fā)人員的固有意識(shí),甚至不必思考即可自然得出。這其中最為經(jīng)典的就是三層架構(gòu)以及領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)提出的四層架構(gòu)。
經(jīng)典三層架構(gòu)
在軟件架構(gòu)中,經(jīng)典三層架構(gòu)自頂向下由用戶界面層(User Interface Layer)、業(yè)務(wù)邏輯層(Business Logic Layer)與數(shù)據(jù)訪問(wèn)層(Data Access Layer)組成。該分層架構(gòu)之所以能夠流行,是有其歷史原因的。在提出該分層架構(gòu)的時(shí)代,多數(shù)企業(yè)系統(tǒng)往往較為簡(jiǎn)單,本質(zhì)上都是一個(gè)單體架構(gòu)(Monolithic Architecture)的數(shù)據(jù)庫(kù)管理系統(tǒng)。這種分層架構(gòu)已經(jīng)是Client-Server架構(gòu)的進(jìn)化了,它有效地隔離了業(yè)務(wù)邏輯與數(shù)據(jù)訪問(wèn)邏輯,使得這兩個(gè)不同關(guān)注點(diǎn)能夠相對(duì)自由和獨(dú)立地演化。一個(gè)經(jīng)典的三層架構(gòu)如下所示:
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的經(jīng)典分層架構(gòu)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)在經(jīng)典三層架構(gòu)的基礎(chǔ)上做了進(jìn)一步改良,在用戶界面層與業(yè)務(wù)邏輯層之間引入了新的一層,即應(yīng)用層(Application Layer)。同時(shí),一些層次的命名也發(fā)生了變化。將業(yè)務(wù)邏輯層更名為領(lǐng)域?qū)幼匀皇穷}中應(yīng)有之義,而將數(shù)據(jù)訪問(wèn)層更名為基礎(chǔ)設(shè)施層(Infrastructure Layer),則突破了之前數(shù)據(jù)庫(kù)管理系統(tǒng)的限制,擴(kuò)大了這個(gè)負(fù)責(zé)封裝技術(shù)復(fù)雜度的基礎(chǔ)層次的內(nèi)涵。下圖為 Eric Evans 在其經(jīng)典著作《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中的分層架構(gòu):
追溯分層架構(gòu)的本源
當(dāng)分層架構(gòu)變得越來(lái)越普及時(shí),我們的設(shè)計(jì)反而變得越來(lái)越僵化。一部分軟件設(shè)計(jì)師并未理解分層架構(gòu)的本質(zhì),只知道依樣畫葫蘆地將分層應(yīng)用到系統(tǒng)中。要么采用經(jīng)典的三層架構(gòu),要么遵循領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)改進(jìn)的四層架構(gòu),卻未思考和叩問(wèn)如此分層究竟有何道理?這是分層架構(gòu)被濫用的根源。
視分層(Layer)為一個(gè)固有的架構(gòu)模式,其濫觴應(yīng)為 Frank Buschmann 等人著的《面向模式的軟件架構(gòu)》第一卷《模式系統(tǒng)》。該模式參考了 ISO 對(duì) TCP/IP 協(xié)議的分層?!赌J较到y(tǒng)》對(duì)分層的描述為:
分層架構(gòu)模式有助于構(gòu)建這樣的應(yīng)用:它能被分解成子任務(wù)組,其中每個(gè)子任務(wù)組處于一個(gè)特定的抽象層次上。
顯然,這里所謂的“分層”首先是一個(gè)邏輯的分層,對(duì)子任務(wù)組的分解需要考慮抽象層次,一種水平的抽象層次。既然為水平的分層,必然存在層的高與低;而抽象層次的不同,又決定了分層的數(shù)量。因此,對(duì)于分層架構(gòu),我們需要解決如下問(wèn)題:
分層的依據(jù)與原則是什么?
層與層之間是怎樣協(xié)作的?
分層的依據(jù)與原則
我們之所以要以水平方式對(duì)整個(gè)系統(tǒng)進(jìn)行分層,是我們下意識(shí)地確定了一個(gè)認(rèn)知規(guī)則:機(jī)器為本,用戶至上。機(jī)器是運(yùn)行系統(tǒng)的基礎(chǔ),而我們打造的系統(tǒng)卻是為用戶提供服務(wù)的。分層架構(gòu)中的層次越往上,其抽象層次就越面向業(yè)務(wù),面向用戶;分層架構(gòu)中的層次越往下,其抽象層次就變得越通用,面向設(shè)備。為什么經(jīng)典分層架構(gòu)為三層架構(gòu)?正是源于這樣的認(rèn)知規(guī)則:其上,面向用戶的體驗(yàn)與交互;其中,面向應(yīng)用與業(yè)務(wù)邏輯;其下,面對(duì)各種外部資源與設(shè)備。在進(jìn)行分層架構(gòu)設(shè)計(jì)時(shí),我們完全可以基于這個(gè)經(jīng)典的三層架構(gòu),沿著水平方向進(jìn)一步切分屬于不同抽象層次的關(guān)注點(diǎn)。因此,分層的第一個(gè)依據(jù)是基于關(guān)注點(diǎn)為不同的調(diào)用目的劃分層次。以領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的四層架構(gòu)為例,之所以引入應(yīng)用層(Application Layer),就是為了給調(diào)用者提供完整的業(yè)務(wù)用例。
分層的第二個(gè)依據(jù)是面對(duì)變化。分層時(shí)應(yīng)針對(duì)不同的變化原因確定層次的邊界,嚴(yán)禁層次之間互相干擾,或者至少將變化對(duì)各層帶來(lái)的影響降到最低。例如數(shù)據(jù)庫(kù)結(jié)構(gòu)的修改自然會(huì)影響到基礎(chǔ)設(shè)施層的數(shù)據(jù)模型以及領(lǐng)域?qū)拥念I(lǐng)域模型,但當(dāng)我們僅需要修改基礎(chǔ)設(shè)施層中數(shù)據(jù)庫(kù)訪問(wèn)的實(shí)現(xiàn)邏輯時(shí),就不應(yīng)該影響到領(lǐng)域?qū)恿?。層與層之間的關(guān)系應(yīng)該是正交的。所謂“正交”,并非二者之間沒有關(guān)系,而是垂直相交的兩條直線。唯一相關(guān)的依賴點(diǎn)是這兩條直線的相交點(diǎn),即兩層之間的協(xié)作點(diǎn)。正交的兩條直線,無(wú)論哪條直線進(jìn)行延伸,都不會(huì)對(duì)另一條直線產(chǎn)生任何影響(指直線的投影)。如果非正交,即“斜交”,當(dāng)一條直線延伸時(shí),它總是會(huì)投影到另一條直線,這就意味著另一條直線會(huì)受到它變化的影響。
在進(jìn)行分層時(shí),我們還應(yīng)該保證同一層的組件處于同一個(gè)抽象層次。這是分層架構(gòu)的設(shè)計(jì)原則,它借鑒了 Kent Beck 在 Smalltalk Best Practice Patterns 一書提出的“組合方法”模式。該模式要求一個(gè)方法中的所有操作處于相同的抽象層,這就是所謂的“單一抽象層次原則(SLAP)”。這一原則可以運(yùn)用到分層架構(gòu)中。例如在一個(gè)基于元數(shù)據(jù)的多租戶報(bào)表系統(tǒng)中,我們特別定義了一個(gè)引擎層(engine layer),這是一個(gè)隱喻,相當(dāng)于為報(bào)表系統(tǒng)提供報(bào)表、實(shí)體與數(shù)據(jù)的驅(qū)動(dòng)引擎。引擎層之下,是基礎(chǔ)設(shè)施層,提供了多租戶、數(shù)據(jù)庫(kù)訪問(wèn)與元數(shù)據(jù)解析與管理等功能。在引擎層之上是一個(gè)控制層,通過(guò)該控制層的組件可以將引擎層的各個(gè)組件組合起來(lái)。分層架構(gòu)的頂端是面向用戶的用戶展現(xiàn)層。如下圖所示:
層之間的協(xié)作
在我們固有的認(rèn)識(shí)中,分層架構(gòu)的依賴都是自頂向下傳遞的,這也符合大多數(shù)人對(duì)分層的認(rèn)知模型。從抽象層次看,層次越處于下端,就會(huì)變得越通用越公共,與具體的業(yè)務(wù)隔離得越遠(yuǎn)。出于重用的考慮,這些通用和公共的功能往往會(huì)被單獨(dú)剝離出來(lái)形成平臺(tái)或框架,在系統(tǒng)邊界內(nèi)的低層,除了面向高層提供足夠的實(shí)現(xiàn)外,就都成了平臺(tái)或框架的調(diào)用者。換言之,越是通用的層,越有可能與外部平臺(tái)或框架形成強(qiáng)依賴。若依賴的傳遞方向仍然采用自頂向下,就會(huì)導(dǎo)致系統(tǒng)的業(yè)務(wù)對(duì)象也隨之依賴于外部平臺(tái)或框架。
依賴倒置原則(Dependency Inversion Principle,DIP)提出了對(duì)這種自頂向下依賴的挑戰(zhàn),它要求“高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象?!边@個(gè)原則正本清源,給了我們當(dāng)頭棒喝——誰(shuí)規(guī)定在分層架構(gòu)中,依賴就一定要沿著自頂向下的方向傳遞?我們常常理解依賴,是因?yàn)楸灰蕾嚪叫枰獮橐蕾嚪剑ㄕ{(diào)用方)提供功能支撐,這是從功能重用的角度來(lái)考慮的。但我們不能忽略變化對(duì)系統(tǒng)產(chǎn)生的影響!與建造房屋一樣,我們自然希望分層的模塊“構(gòu)建”在穩(wěn)定的模塊之上。誰(shuí)更穩(wěn)定?抽象更穩(wěn)定。因此,依賴倒置原則隱含的本質(zhì)是:我們要依賴不變或穩(wěn)定的元素(類、模塊或?qū)樱?。也就是該原則的第二句話:抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
這一原則實(shí)際是“面向接口設(shè)計(jì)”原則的體現(xiàn),即“針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程”。高層模塊對(duì)低層模塊的實(shí)現(xiàn)是一無(wú)所知的,帶來(lái)的好處是:
低層模塊的細(xì)節(jié)實(shí)現(xiàn)可以獨(dú)立變化,避免變化對(duì)高層模塊產(chǎn)生污染
在編譯時(shí),高層模塊可以獨(dú)立于低層模塊單獨(dú)存在
對(duì)于高層模塊而言,低層模塊的實(shí)現(xiàn)是可替換的
倘若高層依賴于低層的抽象,必然會(huì)面對(duì)一個(gè)問(wèn)題:如何將具體的實(shí)現(xiàn)傳遞給高層的類?由于在高層通過(guò)接口隔離了對(duì)具體實(shí)現(xiàn)的依賴,就意味著這個(gè)具體依賴被轉(zhuǎn)移到了外部,究竟使用哪一種具體實(shí)現(xiàn),由外部的調(diào)用者來(lái)決定。只有在運(yùn)行調(diào)用者代碼時(shí),才將外面的依賴傳遞給高層的類。Martin Fowler 形象地將這種機(jī)制稱為“依賴注入(dependency injection)”。
為了更好地解除高層對(duì)低層的依賴,我們往往需要將依賴倒置原則與依賴注入結(jié)合起來(lái)。
層之間的協(xié)作并不一定是自頂向下的傳遞通信,也有可能是自底向上通信,例如在 CIMS(計(jì)算機(jī)集成制造系統(tǒng))中,往往會(huì)由低層的設(shè)備監(jiān)測(cè)系統(tǒng)監(jiān)測(cè)(偵聽)設(shè)備狀態(tài)的變化。當(dāng)狀態(tài)發(fā)生變化時(shí),需要將變化的狀態(tài)通知到上層的業(yè)務(wù)系統(tǒng)。如果說(shuō)自頂向下的消息傳遞往往被描述為“請(qǐng)求(或調(diào)用)”,則自底向上的消息傳遞則往往被形象地稱之為“通知”。倘若我們顛倒一下方向,自然也可以視為這是上層對(duì)下層的觀察,故而可以運(yùn)用觀察者模式(Observer Pattern),在上層定義 Observer 接口,并提供 update() 方法供下層在感知狀態(tài)發(fā)生變更時(shí)調(diào)用?;蛘?,我們也可以認(rèn)為這是一種回調(diào)機(jī)制。雖然本質(zhì)上這并非回調(diào),但設(shè)計(jì)原理是一樣的。
如果采用了觀察者模式,則與前面講述的依賴倒置原則有差相仿佛之意,因?yàn)橄聦訛榱送ㄖ蠈樱枰{(diào)用上層提供的 Observer 接口。如此看來(lái),無(wú)論是上層對(duì)下層的“請(qǐng)求(或調(diào)用)”,抑或下層對(duì)上層的“通知”,都顛覆了我們固有思維中那種高層依賴低層的理解。
現(xiàn)在,我們對(duì)分層架構(gòu)有了更清醒的認(rèn)識(shí)。我們必須要打破那種談分層架構(gòu)必為經(jīng)典三層架構(gòu)又或領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)推薦的四層架構(gòu)這種固有思維,而是將分層視為關(guān)注點(diǎn)分離的水平抽象層次的體現(xiàn)。既然如此,架構(gòu)的抽象層數(shù)就不是固定的,甚至每一層的名稱也未必遵循固有(經(jīng)典)的分層架構(gòu)要求。設(shè)計(jì)系統(tǒng)的層需得結(jié)合該系統(tǒng)的具體業(yè)務(wù)場(chǎng)景而定。當(dāng)然,我們也要認(rèn)識(shí)到層次多少的利弊:過(guò)多的層會(huì)引入太多的間接而增加不必要的開支,層太少又可能導(dǎo)致關(guān)注點(diǎn)不夠分離,導(dǎo)致系統(tǒng)的結(jié)構(gòu)不合理。
我們還需要正視架構(gòu)中各層之間的協(xié)作關(guān)系,打破高層依賴低層的固有思維,從解除耦合(或降低耦合)的角度探索層之間可能的協(xié)作關(guān)系。另外,我們還需要確定分層的架構(gòu)原則(或約束),例如是否允許跨層調(diào)用,即每一層都可以使用比它低的所有層的服務(wù),而不僅僅是相鄰低層。這就是所謂的“松散分層系統(tǒng)(Relaxed Layered System)”。
該怎么演進(jìn)領(lǐng)域驅(qū)動(dòng)架構(gòu)?
我們?cè)谏衔闹谢仡櫫私?jīng)典三層架構(gòu)與領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)四層架構(gòu),然而任何技術(shù)結(jié)論都并非句點(diǎn),而僅僅代表了滿足當(dāng)時(shí)技術(shù)背景的一種判斷,技術(shù)總是在演進(jìn),領(lǐng)域驅(qū)動(dòng)架構(gòu)亦是如此。與其關(guān)心結(jié)果,不如將眼睛投往這個(gè)演進(jìn)的過(guò)程,或許風(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)的最上面:
整個(gè)架構(gòu)模型清晰地表達(dá)了領(lǐng)域?qū)觿e無(wú)依賴的特質(zhì),但整個(gè)架構(gòu)卻容易給人以一種錯(cuò)亂感。單以這個(gè)分層模型來(lái)看,雖則沒有讓高層依賴低層,卻又反過(guò)來(lái)讓低層依賴了高層,這仍然是不合理的。當(dāng)然你可以說(shuō)此時(shí)的基礎(chǔ)設(shè)施層已經(jīng)變成了高層,然而從之前分析的南向網(wǎng)關(guān)與北向網(wǎng)關(guān)來(lái)說(shuō),基礎(chǔ)設(shè)施層存在被“肢解”的可能。坦白講,這個(gè)架構(gòu)模型仍然沒有解決人們對(duì)分層架構(gòu)的認(rèn)知錯(cuò)誤,例如它并沒有很好地表達(dá)依賴倒置原則與依賴注入。還需要注意的是,這個(gè)架構(gòu)模型將基礎(chǔ)設(shè)施層放在了整個(gè)分層架構(gòu)的最頂端,導(dǎo)致它依賴了用戶界面層,這似乎并不能自圓其說(shuō)。我們需要重新梳理領(lǐng)域驅(qū)動(dòng)架構(gòu),展示它的演進(jìn)過(guò)程。
那么到底該怎么演進(jìn)領(lǐng)域驅(qū)動(dòng)架構(gòu)?感興趣的同學(xué)可以在我的達(dá)人課《領(lǐng)域驅(qū)動(dòng)戰(zhàn)略設(shè)計(jì)實(shí)踐》里了解更系統(tǒng)的架構(gòu)知識(shí)內(nèi)容。這是專門為了想提高軟件架構(gòu)設(shè)計(jì)能力的軟件架構(gòu)師量身定制的系統(tǒng)課程,也是是國(guó)內(nèi)第一個(gè)全面講解 DDD 的原創(chuàng)課程。