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