原文:https://herbertograca.com/2017/08/03/layered-architecture/
這篇文章是軟件架構(gòu)編年史(譯)的一部分,這部編年史由一系列關(guān)于軟件架構(gòu)的文章組成。在這一系列文章中,我將寫下我對軟件架構(gòu)的學(xué)習(xí)和思考,以及我是如何運(yùn)用這些知識的。如果你閱讀了這個系列中之前的文章,本篇文章的的內(nèi)容將更有意義。
分層是一種常見的根據(jù)系統(tǒng)中的角色/職責(zé)拆分和組織代碼單元的常規(guī)實(shí)踐。
在一個面向?qū)ο蟮某绦蚶?,UI、數(shù)據(jù)庫和其它支撐代碼會被寫到業(yè)務(wù)對象里。額外的業(yè)務(wù)邏輯也會被嵌到 UI 控件和數(shù)據(jù)庫腳本里。這是由于在短期內(nèi)完成夠用的代碼是更簡單的選擇。
當(dāng)領(lǐng)域相關(guān)的代碼擴(kuò)散到這樣大規(guī)模的其它代碼中,要發(fā)現(xiàn)和理解這些代碼會相當(dāng)困難。表面上對 UI 的修改實(shí)際上也會改變業(yè)務(wù)邏輯。要修改業(yè)務(wù)規(guī)則就得小心翼翼地追蹤 UI 代碼、數(shù)據(jù)庫代碼,或者其它的編程元素。實(shí)現(xiàn)內(nèi)聚的、模型驅(qū)動的對象變得不切實(shí)際。自動化測試也變得尷尬。如果每個活動都要卷入所有的技術(shù)和邏輯,程序必須非常簡單,要不然就完全無法理解。
——Eric Evans 2014, Domain-Driven Design Reference
分層意味著什么
在一個分層系統(tǒng)中,每一層:
- 依賴它之下的層;
- 和它之上的層無關(guān),對使用(依賴)它的層次無感知。
在分層架構(gòu)中,分層的使用可以嚴(yán)格地限制:分層只知道直接的下層,或者可以寬松一些:分層可以訪問它之下的任何分層。Martin Fowler 和我自己的經(jīng)驗(yàn)都是第二種方式實(shí)際中會更好,因?yàn)樗苊饬嗽谥虚g分層創(chuàng)建代碼方法(或者完整的代理類),也避免了退化成千層面的反模式(下文會詳細(xì)探討)。
有時分層會這樣安排,領(lǐng)域?qū)訉?shù)據(jù)源完全隱藏不讓展現(xiàn)層看到。但是,更多的時候展現(xiàn)層會直接訪問數(shù)據(jù)存儲。這不那么純粹,但實(shí)際卻工作得更好?!狥owler 2002, Patterns of Enterprise Application Architecture
它的優(yōu)勢有:
- 我們只需要了解我們工作的那層之下的層次;
- 每個層次都可以用等價的實(shí)現(xiàn)替換,而不會影響到其它層次;
- 層次是標(biāo)準(zhǔn)化的最佳候選;
- 層次可以被多個不同的上級層次使用。
它的劣勢在于:
- 分層并不能封裝一切(UI 中添加的字段,很可能也要添加到數(shù)據(jù)庫) ;
- 額外的分層會影響性能,尤其是位于不同物理層的時候。
20 世紀(jì) 60 年代和 70 年代
盡管上世紀(jì) 50 年代軟件開發(fā)就開始了,它真正發(fā)展成我們今天所見的這樣是在 60 年代和 70 年代,隨著構(gòu)建可以被發(fā)行、部署并可以被除了開發(fā)者自己之外其它人使用的應(yīng)用的活動發(fā)展起來的。
然而,當(dāng)時的應(yīng)用程序和今天的應(yīng)用程序截然不同。那時還沒有 GUI(大概 80 年代末 90 年代初才出現(xiàn)),所有的應(yīng)用程序要通過命令行使用,顯示在一個啞終端里,它只是將用戶的輸入傳輸給應(yīng)用程序,應(yīng)用程序很可能就在同一臺電腦中被使用。

應(yīng)用程序此時還十分簡單,還不需要分層,它們被部署到一臺電腦之中被使用。它們實(shí)際上是單層的應(yīng)用程序,盡管有時啞客戶端還是遠(yuǎn)程的。盡管這些應(yīng)用程序非常簡單,但它們卻無法伸縮,例如,如果我們需要升級軟件的新版本,我們要在每臺安裝了該軟件的電腦上升級。
20 世紀(jì) 80 年代和 90 年代的分層
在 20 世紀(jì) 80 年代,企業(yè)應(yīng)用出現(xiàn)了,在公司里有多個用戶開始使用桌面電腦通過網(wǎng)絡(luò)訪問應(yīng)用。
這時它們多半分成三層:
-
用戶界面(展現(xiàn)):用戶界面就是網(wǎng)頁、命令行或者原生桌面應(yīng)用;
- 例如:作為(富)客戶端的 Windows 應(yīng)用,普通用戶在桌面電腦上使用,和服務(wù)器器通信才能完成工作??蛻舳素?fù)責(zé)應(yīng)用的流程和用戶輸入的校驗(yàn);
-
業(yè)務(wù)邏輯(領(lǐng)域):應(yīng)用之所以存在的邏輯;
- 例如:應(yīng)用服務(wù)器,包含業(yè)務(wù)邏輯并從原生客戶端接收請求,采取行動并將數(shù)據(jù)保存到數(shù)據(jù)存儲;
-
數(shù)據(jù)源:數(shù)據(jù)的持久化機(jī)制(數(shù)據(jù)庫),或者是和其它應(yīng)用之間的通信。
- 例如:數(shù)據(jù)庫服務(wù)器,應(yīng)用服務(wù)器用它來完成數(shù)據(jù)持久化。

隨著使用上下文的變遷,分層實(shí)踐開始得到應(yīng)用,盡管它真正得到大范圍的實(shí)踐是在 C/S 系統(tǒng)崛起的 20 世紀(jì) 90 年代。這實(shí)際上是一種 兩層 應(yīng)用,客戶端是一個用為應(yīng)用界面的富客戶端應(yīng)用程序,而業(yè)務(wù)邏輯和數(shù)據(jù)源放在服務(wù)器。
這種架構(gòu)模式解決了伸縮性問題,因?yàn)楹芏嘤脩艨梢元?dú)立地使用應(yīng)用,只需要另外一臺安裝了客戶端應(yīng)用程序的桌面電腦就行。然而,如果我們有數(shù)百或者數(shù)十個客戶端而我們想用更新應(yīng)用程序的話,操作會特別復(fù)雜,因?yàn)槲覀円粋€一個地更新客戶端。
20 世紀(jì) 90 年代中期之后的分層
大約在 1995 年和 2005 年之間,隨著普遍遷移到云的趨勢,應(yīng)用用戶的增長,應(yīng)用復(fù)雜性和基礎(chǔ)設(shè)施復(fù)雜性的增加,我們終于看到了分層方案的變化。新的分層的典型實(shí)現(xiàn)如下:
- 原生瀏覽器應(yīng)用程序,渲染和運(yùn)行用戶界面,向服務(wù)器應(yīng)用發(fā)送請求;
- 應(yīng)用服務(wù)器,包括了展現(xiàn)層、應(yīng)用層、領(lǐng)域?qū)雍统志没瘜樱?/li>
- 數(shù)據(jù)庫服務(wù)器,應(yīng)用服務(wù)器用它來完成數(shù)據(jù)的持久化。

這就是三層架構(gòu)模式,也叫 N 層架構(gòu)。它是可伸縮的解決方案,盡管用戶界面是在客戶端瀏覽器中渲染和運(yùn)行,但由于用戶界面存放于服務(wù)器上并在服務(wù)器上編譯,它“解決了客戶端的更新問題”。
新世紀(jì)之后的分層
2003 年, Eric Evans 出版了他的標(biāo)志性著作 Domain-Driven Design: Tackling Complexity in the Heart of Software。在書中提出的許多關(guān)鍵概念之中,也有對軟件系統(tǒng)分層的展望:

-
用戶界面
負(fù)責(zé)繪制用戶和應(yīng)用交互的界面,并將用戶輸入轉(zhuǎn)換成應(yīng)用的命令。值得注意的是,“用戶”可以是人類也可以是其它應(yīng)用。它和 Ivar Jacobson 的 EBI 架構(gòu)(后面其它文章會介紹更多細(xì)節(jié))中的邊界對象不謀而合;
-
應(yīng)用層
指揮領(lǐng)域?qū)ο笸瓿捎脩粢蟮娜蝿?wù)。它不包括業(yè)務(wù)邏輯。它和 Ivar Jacobson 的 EBI 架構(gòu)中的交互器對象對應(yīng),唯一不同的是 Jacobson 的交互器可以是任意和界面或?qū)嶓w無關(guān)的對象;
-
領(lǐng)域?qū)?/h2>
這一層包含了所有的業(yè)務(wù)邏輯、實(shí)體、事件或者其它任何包含業(yè)務(wù)邏輯的對象類型。顯然它和 EBI 中的實(shí)體對象類型相對應(yīng)。這是系統(tǒng)的心臟;
-
基礎(chǔ)設(shè)施
支撐上面所有層次的技術(shù)能力,如持久化機(jī)制和消息機(jī)制。
反模式:千層面架構(gòu)

千層面架構(gòu)常常說的就是分層架構(gòu)的反模式。以下這些情況發(fā)會出現(xiàn):
- 我們決定使用嚴(yán)格的分層方法,也就是分層只感知得到它的直接下層。這種情況下,我們最終會創(chuàng)建代理方法甚至代理類,必須通過中間層次訪問而不是直接訪問需要的層次;
- 熱衷于創(chuàng)建完美的系統(tǒng)導(dǎo)致項(xiàng)目過度抽象;
- 小更新也會波及應(yīng)用的方方面面,例如,整理一個層次也會是風(fēng)險巨大和收效甚微的大動作。
- 層次太多,增加了整個系統(tǒng)的復(fù)雜性;
- 物理層次太多,不但增加了整個系統(tǒng)的復(fù)雜性,還降低了系統(tǒng)的性能;
- 我們明確地按照層次(UI、領(lǐng)域、數(shù)據(jù)庫)來組織我們的單體,而不是根據(jù)子域/組件(例如,產(chǎn)品、支付、付款)來組織它,并因此破壞了領(lǐng)域概念的模塊化和封裝。
總結(jié)
分層架構(gòu)是另一種根據(jù)代碼在應(yīng)用中的功能角色對代碼單元進(jìn)行劃分的方式,它帶來了關(guān)注點(diǎn)的分離、封裝性和解耦。
然而,和生活中的很多事情一樣,過猶不及!所以,最重要的一條經(jīng)驗(yàn)是:只使用必要的層次和物理層次,夠用就行!我們千萬不要得意忘形地追逐架構(gòu)的圣杯,它根本就不存在。存在的只是需求,和最可能恰好符合它的架構(gòu)。順便說一句,這也是精益所提倡的。
此外,還有一點(diǎn)值得注意,上/下這種縱向的分層方式已經(jīng)過時了?,F(xiàn)代的軟件開發(fā)中我們不應(yīng)該使用這種方式了,應(yīng)用的層次有更好的新思路。我會在接下來的文章中進(jìn)行探討。
引用來源
2002 – Martin Fowler – Patterns of Enterprise Application Architecture
2003 – Eric Evans – Domain-Driven Design: Tackling Complexity in the Heart of Software
2011 – Chris Ostrowski – Understanding Oracle SOA – Part 1 – Architecture