一文讀懂分層架構

認識分層架構

分層架構是運用最為廣泛的架構模式,幾乎每個軟件系統都需要通過層(Layer)來隔離不同的關注點(Concern Point),以此應對不同需求的變化,使得這種變化可以獨立進行;此外,分層架構模式還是隔離業務複雜度與技術複雜度的利器,《領域驅動設計模式、原理與實踐》寫道:

為了避免將代碼庫變成大泥球(BBoM)並因此減弱領域模型的完整性且最終減弱可用性,系統架構要支持技術複雜性與領域複雜性的分離。引起技術實現發生變化的原因與引起領域邏輯發生變化的原因顯然不同,這就導致基礎設施和領域邏輯問題會以不同速率發生變化。

這裡所謂的“以不同速率發生變化”,其實就是引起變化的原因各有不同,這正好是單一職責原則(Single-Responsibility Principle,SRP)的體現。Robert Martin 認為單一職責原則就是“一個類應該只有一個引起它變化的原因”,換言之,如果有兩個引起類變化的原因,就需要分離。單一職責原則可以理解為架構原則,這時要考慮的就不是類,而是層次。我們為什麼要將業務與基礎設施分開?正是因為引起它們變化的原因不同。

經典分層架構

分層架構由來已久,將一個軟件系統進行分層,似乎已經成為了每個開發人員的固有意識,甚至不必思考即可自然得出。這其中最為經典的就是三層架構以及領域驅動設計提出的四層架構。

經典三層架構

在軟件架構中,經典三層架構自頂向下由用戶界面層(User Interface Layer)、業務邏輯層(Business Logic Layer)與數據訪問層(Data Access Layer)組成。該分層架構之所以能夠流行,是有其歷史原因的。在提出該分層架構的時代,多數企業系統往往較為簡單,本質上都是一個單體架構(Monolithic Architecture)的數據庫管理系統。這種分層架構已經是Client-Server架構的進化了,它有效地隔離了業務邏輯與數據訪問邏輯,使得這兩個不同關注點能夠相對自由和獨立地演化。一個經典的三層架構如下所示:

一文讀懂分層架構

領域驅動設計的經典分層架構

領域驅動設計在經典三層架構的基礎上做了進一步改良,在用戶界面層與業務邏輯層之間引入了新的一層,即應用層(Application Layer)。同時,一些層次的命名也發生了變化。將業務邏輯層更名為領域層自然是題中應有之義,而將數據訪問層更名為基礎設施層(Infrastructure Layer),則突破了之前數據庫管理系統的限制,擴大了這個負責封裝技術複雜度的基礎層次的內涵。下圖為 Eric Evans 在其經典著作《領域驅動設計》中的分層架構:

一文讀懂分層架構

追溯分層架構的本源

當分層架構變得越來越普及時,我們的設計反而變得越來越僵化。一部分軟件設計師並未理解分層架構的本質,只知道依樣畫葫蘆地將分層應用到系統中。要麼採用經典的三層架構,要麼遵循領域驅動設計改進的四層架構,卻未思考和叩問如此分層究竟有何道理?這是分層架構被濫用的根源。

視分層(Layer)為一個固有的架構模式,其濫觴應為 Frank Buschmann 等人著的《面向模式的軟件架構》第一卷《模式系統》。該模式參考了 ISO 對 TCP/IP 協議的分層。《模式系統》對分層的描述為:

分層架構模式有助於構建這樣的應用:它能被分解成子任務組,其中每個子任務組處於一個特定的抽象層次上。

顯然,這裡所謂的“分層”首先是一個邏輯的分層,對子任務組的分解需要考慮抽象層次,一種水平的抽象層次。既然為水平的分層,必然存在層的高與低;而抽象層次的不同,又決定了分層的數量。因此,對於分層架構,我們需要解決如下問題:

  • 分層的依據與原則是什麼?
  • 層與層之間是怎樣協作的?

分層的依據與原則

我們之所以要以水平方式對整個系統進行分層,是我們下意識地確定了一個認知規則:機器為本,用戶至上。機器是運行系統的基礎,而我們打造的系統卻是為用戶提供服務的。分層架構中的層次越往上,其抽象層次就越面向業務,面向用戶;分層架構中的層次越往下,其抽象層次就變得越通用,面向設備。為什麼經典分層架構為三層架構?正是源於這樣的認知規則:其上,面向用戶的體驗與交互;其中,面向應用與業務邏輯;其下,面對各種外部資源與設備。在進行分層架構設計時,我們完全可以基於這個經典的三層架構,沿著水平方向進一步切分屬於不同抽象層次的關注點。因此,分層的第一個依據是基於關注點為不同的調用目的劃分層次。以領域驅動設計的四層架構為例,之所以引入應用層(Application Layer),就是為了給調用者提供完整的業務用例。

分層的第二個依據是面對變化。分層時應針對不同的變化原因確定層次的邊界,嚴禁層次之間互相干擾,或者至少將變化對各層帶來的影響降到最低。例如數據庫結構的修改自然會影響到基礎設施層的數據模型以及領域層的領域模型,但當我們僅需要修改基礎設施層中數據庫訪問的實現邏輯時,就不應該影響到領域層了。層與層之間的關係應該是正交的。所謂“正交”,並非二者之間沒有關係,而是垂直相交的兩條直線。唯一相關的依賴點是這兩條直線的相交點,即兩層之間的協作點。正交的兩條直線,無論哪條直線進行延伸,都不會對另一條直線產生任何影響(指直線的投影)。如果非正交,即“斜交”,當一條直線延伸時,它總是會投影到另一條直線,這就意味著另一條直線會受到它變化的影響。

在進行分層時,我們還應該保證同一層的組件處於同一個抽象層次。這是分層架構的設計原則,它借鑑了 Kent Beck 在 Smalltalk Best Practice Patterns 一書提出的“組合方法”模式。該模式要求一個方法中的所有操作處於相同的抽象層,這就是所謂的“單一抽象層次原則(SLAP)”。這一原則可以運用到分層架構中。例如在一個基於元數據的多租戶報表系統中,我們特別定義了一個引擎層(engine layer),這是一個隱喻,相當於為報表系統提供報表、實體與數據的驅動引擎。引擎層之下,是基礎設施層,提供了多租戶、數據庫訪問與元數據解析與管理等功能。在引擎層之上是一個控制層,通過該控制層的組件可以將引擎層的各個組件組合起來。分層架構的頂端是面向用戶的用戶展現層。如下圖所示:

一文讀懂分層架構

層之間的協作

在我們固有的認識中,分層架構的依賴都是自頂向下傳遞的,這也符合大多數人對分層的認知模型。從抽象層次看,層次越處於下端,就會變得越通用越公共,與具體的業務隔離得越遠。出於重用的考慮,這些通用和公共的功能往往會被單獨剝離出來形成平臺或框架,在系統邊界內的低層,除了面向高層提供足夠的實現外,就都成了平臺或框架的調用者。換言之,越是通用的層,越有可能與外部平臺或框架形成強依賴。若依賴的傳遞方向仍然採用自頂向下,就會導致系統的業務對象也隨之依賴於外部平臺或框架。

依賴倒置原則(Dependency Inversion Principle,DIP)提出了對這種自頂向下依賴的挑戰,它要求“高層模塊不應該依賴於低層模塊,二者都應該依賴於抽象。”這個原則正本清源,給了我們當頭棒喝——誰規定在分層架構中,依賴就一定要沿著自頂向下的方向傳遞?我們常常理解依賴,是因為被依賴方需要為依賴方(調用方)提供功能支撐,這是從功能重用的角度來考慮的。但我們不能忽略變化對系統產生的影響!與建造房屋一樣,我們自然希望分層的模塊“構建”在穩定的模塊之上。誰更穩定?抽象更穩定。因此,依賴倒置原則隱含的本質是:我們要依賴不變或穩定的元素(類、模塊或層)。也就是該原則的第二句話:抽象不應該依賴於細節,細節應該依賴於抽象。

這一原則實際是“面向接口設計”原則的體現,即“針對接口編程,而不是針對實現編程”。高層模塊對低層模塊的實現是一無所知的,帶來的好處是:

  • 低層模塊的細節實現可以獨立變化,避免變化對高層模塊產生汙染
  • 在編譯時,高層模塊可以獨立於低層模塊單獨存在
  • 對於高層模塊而言,低層模塊的實現是可替換的

倘若高層依賴於低層的抽象,必然會面對一個問題:如何將具體的實現傳遞給高層的類?由於在高層通過接口隔離了對具體實現的依賴,就意味著這個具體依賴被轉移到了外部,究竟使用哪一種具體實現,由外部的調用者來決定。只有在運行調用者代碼時,才將外面的依賴傳遞給高層的類。Martin Fowler 形象地將這種機制稱為“依賴注入(dependency injection)”。

為了更好地解除高層對低層的依賴,我們往往需要將依賴倒置原則與依賴注入結合起來。

層之間的協作並不一定是自頂向下的傳遞通信,也有可能是自底向上通信,例如在 CIMS(計算機集成製造系統)中,往往會由低層的設備監測系統監測(偵聽)設備狀態的變化。當狀態發生變化時,需要將變化的狀態通知到上層的業務系統。如果說自頂向下的消息傳遞往往被描述為“請求(或調用)”,則自底向上的消息傳遞則往往被形象地稱之為“通知”。倘若我們顛倒一下方向,自然也可以視為這是上層對下層的觀察,故而可以運用觀察者模式(Observer Pattern),在上層定義 Observer 接口,並提供 update() 方法供下層在感知狀態發生變更時調用。或者,我們也可以認為這是一種回調機制。雖然本質上這並非回調,但設計原理是一樣的。

如果採用了觀察者模式,則與前面講述的依賴倒置原則有差相彷彿之意,因為下層為了通知上層,需要調用上層提供的 Observer 接口。如此看來,無論是上層對下層的“請求(或調用)”,抑或下層對上層的“通知”,都顛覆了我們固有思維中那種高層依賴低層的理解。

現在,我們對分層架構有了更清醒的認識。我們必須要打破那種談分層架構必為經典三層架構又或領域驅動設計推薦的四層架構這種固有思維,而是將分層視為關注點分離的水平抽象層次的體現。既然如此,架構的抽象層數就不是固定的,甚至每一層的名稱也未必遵循固有(經典)的分層架構要求。設計系統的層需得結合該系統的具體業務場景而定。當然,我們也要認識到層次多少的利弊:過多的層會引入太多的間接而增加不必要的開支,層太少又可能導致關注點不夠分離,導致系統的結構不合理。

我們還需要正視架構中各層之間的協作關係,打破高層依賴低層的固有思維,從解除耦合(或降低耦合)的角度探索層之間可能的協作關係。另外,我們還需要確定分層的架構原則(或約束),例如是否允許跨層調用,即每一層都可以使用比它低的所有層的服務,而不僅僅是相鄰低層。這就是所謂的“鬆散分層系統(Relaxed Layered System)”。

該怎麼演進領域驅動架構?

我們在上文中回顧了經典三層架構與領域驅動設計四層架構,然而任何技術結論都並非句點,而僅僅代表了滿足當時技術背景的一種判斷,技術總是在演進,領域驅動架構亦是如此。與其關心結果,不如將眼睛投往這個演進的過程,或許風景會更加動人。

根據“依賴倒置原則”與 Robert Martin 提出的“整潔架構”思想,我們推翻了Eric Evans 在《領域驅動設計》書中提出的分層架構。Vaughn Vernon 在《實現領域驅動設計》一書中給出了改良版的分層架構,他將基礎設施層奇怪地放在了整個架構的最上面:

一文讀懂分層架構

整個架構模型清晰地表達了領域層別無依賴的特質,但整個架構卻容易給人以一種錯亂感。單以這個分層模型來看,雖則沒有讓高層依賴低層,卻又反過來讓低層依賴了高層,這仍然是不合理的。當然你可以說此時的基礎設施層已經變成了高層,然而從之前分析的南向網關與北向網關來說,基礎設施層存在被“肢解”的可能。坦白講,這個架構模型仍然沒有解決人們對分層架構的認知錯誤,例如它並沒有很好地表達依賴倒置原則與依賴注入。還需要注意的是,這個架構模型將基礎設施層放在了整個分層架構的最頂端,導致它依賴了用戶界面層,這似乎並不能自圓其說。我們需要重新梳理領域驅動架構,展示它的演進過程。

如果大家喜歡這篇文章的話,希望大家能夠收藏,轉發 謝謝!更多相關資訊可以關注西安華美校區,免費獲得java零基礎教程!額外附送excel教程!


分享到:


相關文章: