分層架構(gòu)風(fēng)格,也稱為n層架構(gòu)風(fēng)格,是最常見(jiàn)的架構(gòu)風(fēng)格之一。這種風(fēng)格的架構(gòu)是大多數(shù)應(yīng)用程序的事實(shí)標(biāo)準(zhǔn),主要是因?yàn)樗?jiǎn)單、友好并且成本低。根據(jù)康威定律,使用分層架構(gòu)開(kāi)發(fā)應(yīng)用程序也是一種非常自然的方式,設(shè)計(jì)系統(tǒng)的組織最終會(huì)產(chǎn)生與組織架構(gòu)相同的設(shè)計(jì)。在大多數(shù)組織中,都有用戶界面(UI)開(kāi)發(fā)人員、后端開(kāi)發(fā)人員、規(guī)則開(kāi)發(fā)人員和數(shù)據(jù)庫(kù)專家(DBA)這些角色。這些組織層很好地適應(yīng)了傳統(tǒng)分層架構(gòu)的各個(gè)層,使其成為許多業(yè)務(wù)應(yīng)用程序的自然選擇。分層架構(gòu)風(fēng)格也分為幾種架構(gòu)反模式,包括隱含架構(gòu)反模式和偶然架構(gòu)反模式。如果一個(gè)開(kāi)發(fā)人員或架構(gòu)師不確定他們正在使用哪種架構(gòu)風(fēng)格,或者一個(gè)敏捷開(kāi)發(fā)團(tuán)隊(duì)“剛開(kāi)始編碼”,那么很有可能他們正在采用分層架構(gòu)風(fēng)格。
拓?fù)浣Y(jié)構(gòu)
組件在分層架構(gòu)中被組織成邏輯水平層,每個(gè)層在應(yīng)用中執(zhí)行特定的角色(例如表示邏輯或業(yè)務(wù)邏輯)。盡管對(duì)于必須存在的層的數(shù)量和類型沒(méi)有特定的限制,但是大多數(shù)分層體系結(jié)構(gòu)由四個(gè)標(biāo)準(zhǔn)層組成:表示層、業(yè)務(wù)層、持久層和數(shù)據(jù)庫(kù)層,如圖10-1所示。在某些情況下,業(yè)務(wù)層和持久性層被組合成一個(gè)業(yè)務(wù)層,特別是當(dāng)持久性邏輯(如SQL或HSQL)嵌入到業(yè)務(wù)層組件中時(shí)。因此,較小的應(yīng)用可能只有三層,而更大和更復(fù)雜的業(yè)務(wù)應(yīng)用可能包含五層或更多。

圖10-2從物理分層(部署)的角度說(shuō)明了各種拓?fù)浣Y(jié)構(gòu)變體。第一個(gè)變體將表示層、業(yè)務(wù)層和持久層組合到一個(gè)部署單元中,數(shù)據(jù)庫(kù)層通常表示為一個(gè)單獨(dú)的外部物理數(shù)據(jù)庫(kù)(或文件系統(tǒng))。第二個(gè)變體在物理上將表示層劃分為單獨(dú)的部署單元,業(yè)務(wù)層和持久層合并為第二個(gè)部署單元。同樣,使用此變體,數(shù)據(jù)庫(kù)層在物理上通常通過(guò)外部數(shù)據(jù)庫(kù)或文件系統(tǒng)進(jìn)行分離。第三個(gè)變體將所有四個(gè)標(biāo)準(zhǔn)層組合到一個(gè)部署中,包括數(shù)據(jù)庫(kù)層。對(duì)于具有內(nèi)部嵌入式數(shù)據(jù)庫(kù)或內(nèi)存數(shù)據(jù)庫(kù)的較小應(yīng)用程序,此變體可能很有用。許多內(nèi)部(“免費(fèi)”)產(chǎn)品都是使用第三種變體建設(shè)并交付給客戶的。

分層架構(gòu)風(fēng)格的每一層在架構(gòu)中都有特定的角色和責(zé)任。例如,表示層將負(fù)責(zé)處理所有用戶界面和瀏覽器通信邏輯,而業(yè)務(wù)層將負(fù)責(zé)執(zhí)行與請(qǐng)求相關(guān)聯(lián)的特定業(yè)務(wù)規(guī)則。架構(gòu)中的每一層圍繞滿足特定業(yè)務(wù)請(qǐng)求所需完成的工作形成一個(gè)抽象。例如,表示層不需要知道或擔(dān)心如何獲取客戶數(shù)據(jù);它只需要在屏幕上以特定格式顯示這些信息。同樣,業(yè)務(wù)層也不需要關(guān)心如何格式化客戶數(shù)據(jù)以便在屏幕上顯示,甚至不需要知道客戶數(shù)據(jù)來(lái)自何處;業(yè)務(wù)層只需要從持久層獲取數(shù)據(jù),對(duì)數(shù)據(jù)執(zhí)行業(yè)務(wù)邏輯(例如計(jì)算值或聚合數(shù)據(jù)),并將這些信息傳遞到表示層。分層架構(gòu)風(fēng)格中的關(guān)注概念分離使得在架構(gòu)中構(gòu)建有效的角色和責(zé)任模型變得容易。例如,表示層中的組件只處理顯示邏輯,而駐留在業(yè)務(wù)層中的組件只處理業(yè)務(wù)邏輯。這使得開(kāi)發(fā)人員能夠利用他們特定的技術(shù)專長(zhǎng)來(lái)專注于特定領(lǐng)域的技術(shù)方面的內(nèi)容(例如表示邏輯或持久性邏輯)。然而,這種好處的代價(jià)是缺乏整體靈活性(快速響應(yīng)變化的能力)。
分層架構(gòu)是從技術(shù)上劃分的架構(gòu)(與領(lǐng)域劃分的架構(gòu)相反)。組件分組不是按領(lǐng)域(如客戶)分組,而是按它們?cè)诩軜?gòu)中的技術(shù)角色(如表示層或業(yè)務(wù)層)分組。這導(dǎo)致任何特定的業(yè)務(wù)領(lǐng)域都會(huì)分布在架構(gòu)的所有層中。例如,“客戶”領(lǐng)域邏輯包含在表示層、業(yè)務(wù)層、規(guī)則層、服務(wù)層和數(shù)據(jù)庫(kù)層中,因此很難更改該領(lǐng)域的邏輯。因此,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)方法不太適用于使用分層架構(gòu)風(fēng)格。
隔離層
分層架構(gòu)風(fēng)格中的每個(gè)層可以是封閉的,也可以是開(kāi)放的。封閉層意味著當(dāng)一個(gè)請(qǐng)求自上而下地從一個(gè)層移動(dòng)到另一個(gè)層時(shí),請(qǐng)求不能跳過(guò)任何層,而是必須通過(guò)它下面的層到達(dá)下一層(如圖10-3)。例如,在一個(gè)封閉的分層架構(gòu)中,來(lái)自表示層的請(qǐng)求必須首先經(jīng)過(guò)業(yè)務(wù)層,然后到達(dá)持久層,最后才能到達(dá)數(shù)據(jù)庫(kù)層。

在圖10-3中,表示層繞過(guò)任何不必要的層(在21世紀(jì)初被稱為快速通道閱讀器模式)直接訪問(wèn)數(shù)據(jù)庫(kù)以進(jìn)行簡(jiǎn)單的檢索請(qǐng)求會(huì)更快、更容易。要做到這一點(diǎn),業(yè)務(wù)層和持久層必須是開(kāi)放的,允許請(qǐng)求繞過(guò)其他層。開(kāi)放層和封閉層哪個(gè)更好?這個(gè)問(wèn)題的答案在于一個(gè)被稱為隔離層的關(guān)鍵概念。
隔離層概念意味著在架構(gòu)的一個(gè)層中所做的更改通常不會(huì)影響其他層中的組件,只要這些層之間的契約保持不變。每一層都獨(dú)立于其他層,因此對(duì)架構(gòu)中其他層的內(nèi)部工作原理知之甚少或一無(wú)所知。但是,為了支持隔離層,與請(qǐng)求的流動(dòng)主干相關(guān)的層必須是封閉的。如果表示層可以直接訪問(wèn)持久層,那么對(duì)持久層所做的更改將同時(shí)影響業(yè)務(wù)層和表示層,從而產(chǎn)生一個(gè)耦合非常緊密的應(yīng)用,組件之間具有層間的相互依賴關(guān)系。這種類型的架構(gòu)會(huì)變得非常脆弱,變更會(huì)非常困難而且成本高昂。
隔離層概念還允許在不影響任何其他層的情況下替換架構(gòu)中的任何層(同樣,假設(shè)定義良好的契約和使用業(yè)務(wù)委托模式)。例如,您可以利用分層架構(gòu)風(fēng)格中的隔離層概念,在不會(huì)影響應(yīng)用中的任何其他層的情況下將舊的JavaServer Faces(JSF)表示層替換為React.js。
添加層
雖然封閉層有助于隔離層,因此有助于隔離架構(gòu)中的更改,但有時(shí)某些層開(kāi)放是有意義的。例如,假設(shè)業(yè)務(wù)層中有共享對(duì)象,這些對(duì)象包含業(yè)務(wù)組件的公共功能(例如日期和字符串實(shí)用程序類、審計(jì)類、日志類等等)。假設(shè)有一個(gè)架構(gòu)決策,表示層不能使用這些共享業(yè)務(wù)對(duì)象。這個(gè)約束如圖10-4所示,虛線從表示層組件到業(yè)務(wù)層中的共享業(yè)務(wù)對(duì)象。這個(gè)場(chǎng)景很難管理和控制,因?yàn)樵诩軜?gòu)上,表示層可以訪問(wèn)業(yè)務(wù)層,因此可以訪問(wèn)該層中的共享對(duì)象。

從架構(gòu)上強(qiáng)制執(zhí)行此限制的一種方法是向架構(gòu)添加一個(gè)包含所有共享業(yè)務(wù)對(duì)象的新服務(wù)層。現(xiàn)在添加這個(gè)新的層從架構(gòu)上限制表示層訪問(wèn)共享業(yè)務(wù)對(duì)象,因?yàn)闃I(yè)務(wù)層是封閉的(見(jiàn)圖10-5)。但是,新的服務(wù)層必須標(biāo)記為“開(kāi)放”;否則業(yè)務(wù)層將被迫通過(guò)服務(wù)層來(lái)訪問(wèn)持久層。將服務(wù)層標(biāo)記為“開(kāi)放”允許業(yè)務(wù)層訪問(wèn)該層(如實(shí)心箭頭所示),或者繞過(guò)該層并向下轉(zhuǎn)到下一層(如圖10-5中的虛線箭頭所示)。

利用開(kāi)放層和封閉層的概念有助于定義架構(gòu)層次和請(qǐng)求流之間的關(guān)系。它還為開(kāi)發(fā)人員提供了了解架構(gòu)中的各種層的訪問(wèn)限制的必要信息和指導(dǎo)。如果不能記錄或正確地傳達(dá)架構(gòu)中的哪些層是開(kāi)放的和封閉的(以及為什么),通常會(huì)導(dǎo)致緊密耦合和脆弱的架構(gòu),這些架構(gòu)將很難測(cè)試、維護(hù)和部署。
其他考慮事項(xiàng)
當(dāng)還不知道最終將使用哪種架構(gòu)風(fēng)格時(shí),分層架構(gòu)可以作為大多數(shù)應(yīng)用的一個(gè)良好的起點(diǎn)。在許多微服務(wù)工作的普遍實(shí)踐中,當(dāng)架構(gòu)師仍在確定微服務(wù)是否是正確的架構(gòu)選擇時(shí),但開(kāi)發(fā)必須開(kāi)始。然而,當(dāng)使用這種技術(shù)時(shí),一定要將重用保持在最低限度,并使對(duì)象層次結(jié)構(gòu)(繼承樹(shù)的深度)做夠的淺,以便保持良好的模塊化水平。這將有助于以后轉(zhuǎn)向另一種架構(gòu)風(fēng)格。
對(duì)于分層架構(gòu),需要注意的一點(diǎn)是不要陷入污水池反模式。當(dāng)請(qǐng)求以簡(jiǎn)單的傳遞處理方式從一層移動(dòng)到另一層,而每個(gè)層中并沒(méi)有執(zhí)行業(yè)務(wù)邏輯時(shí),就會(huì)出現(xiàn)這種反模式。例如,假設(shè)表示層響應(yīng)用戶的一個(gè)簡(jiǎn)單請(qǐng)求,以檢索基本的客戶數(shù)據(jù)(例如名稱和地址)。表示層將請(qǐng)求傳遞給業(yè)務(wù)層,業(yè)務(wù)層只將請(qǐng)求傳遞給規(guī)則層,而規(guī)則層只將請(qǐng)求傳遞給持久層,后者對(duì)數(shù)據(jù)庫(kù)層進(jìn)行簡(jiǎn)單的SQL調(diào)用以檢索客戶數(shù)據(jù)。然后,數(shù)據(jù)將一直回傳上去,而不需要額外的處理或邏輯來(lái)聚合、計(jì)算、應(yīng)用規(guī)則或轉(zhuǎn)換數(shù)據(jù)。這會(huì)導(dǎo)致不必要的對(duì)象實(shí)例化和處理,從而影響內(nèi)存消耗和性能。
每一個(gè)分層架構(gòu)都至少有一些場(chǎng)景陷入架構(gòu)污水池反模式。確定是否陷入架構(gòu)污水池反模式的關(guān)鍵是分析屬于這一類的請(qǐng)求的百分比。80-20規(guī)則通??梢宰鳛楹芎玫呐袛鄬?shí)踐。例如,如果只有20%的請(qǐng)求是污水池,則可以接受。但是,如果80%的請(qǐng)求都是污水池,那么對(duì)于問(wèn)題域來(lái)說(shuō)分層架構(gòu)并不是合適的架構(gòu)風(fēng)格。另一種解決架構(gòu)污水池反模式的方法是讓架構(gòu)中的所有層都開(kāi)放,當(dāng)然,也要意識(shí)到,代價(jià)是在架構(gòu)中管理變更的難度會(huì)增加。
為什么使用分層架構(gòu)風(fēng)格
分層架構(gòu)風(fēng)格是小型、簡(jiǎn)單的應(yīng)用程序或網(wǎng)站的好選擇。
對(duì)于預(yù)算和時(shí)間限制非常緊張的情況,分層架構(gòu)也是一個(gè)很好的架構(gòu)選擇,尤其是在開(kāi)始階段。由于架構(gòu)簡(jiǎn)單性和開(kāi)發(fā)人員和架構(gòu)師之間對(duì)這種架構(gòu)的熟悉度,分層架構(gòu)可能是成本最低的架構(gòu)風(fēng)格之一,有助于小型應(yīng)用的開(kāi)發(fā)。當(dāng)架構(gòu)師仍在分析業(yè)務(wù)需求并且不確定哪種架構(gòu)風(fēng)格是最合適的時(shí)候,分層架構(gòu)風(fēng)格也是一個(gè)不錯(cuò)的選擇。
隨著使用分層架構(gòu)風(fēng)格的應(yīng)用程序的不斷迭代,諸如可維護(hù)性、敏捷性、可測(cè)試性和可部署性等特性將受到不利影響。因此,使用分層架構(gòu)的大型應(yīng)用和系統(tǒng)可能更適合使用其他更模塊化的架構(gòu)風(fēng)格。
架構(gòu)特性評(píng)級(jí)
特性評(píng)級(jí)表中的一星級(jí)評(píng)級(jí)(如圖10-6所示)意味著特定的架構(gòu)特性在某種架構(gòu)中沒(méi)有得到很好的支持,而五星評(píng)級(jí)意味著架構(gòu)特性是某種架構(gòu)風(fēng)格中最強(qiáng)大的特性之一。記分卡中確定的每個(gè)特性的定義見(jiàn)第4章。

成本低廉和簡(jiǎn)單性是分層架構(gòu)風(fēng)格的主要優(yōu)勢(shì)。分層架構(gòu)本質(zhì)上是一個(gè)單體,它不具有與分布式架構(gòu)風(fēng)格相關(guān)的復(fù)雜性,它簡(jiǎn)單易懂并且構(gòu)建和維護(hù)的成本相對(duì)較低。然而,值得注意的是,隨著整體分層架構(gòu)變得越來(lái)越大,從而變得越來(lái)越復(fù)雜,這些評(píng)級(jí)開(kāi)始迅速降低。
這種架構(gòu)風(fēng)格的可部署性和可測(cè)試性都非常低??刹渴鹦栽u(píng)級(jí)低是由于部署成本高、風(fēng)險(xiǎn)高和缺乏頻繁部署能力。在分層架構(gòu)中對(duì)一個(gè)類文件進(jìn)行簡(jiǎn)單的三行更改需要重新部署整個(gè)部署單元,原來(lái)的改動(dòng)暗中會(huì)引起潛在的數(shù)據(jù)庫(kù)更改、配置更改或其他編碼更改。此外,這簡(jiǎn)單的三行更改通常會(huì)引起其他幾十處的更改,因此會(huì)增加部署的風(fēng)險(xiǎn)(也會(huì)增加部署的頻率)。低可測(cè)試性評(píng)級(jí)也反映了這種情況;通過(guò)簡(jiǎn)單的三行更改,大多數(shù)開(kāi)發(fā)人員不會(huì)花費(fèi)數(shù)小時(shí)來(lái)執(zhí)行整個(gè)回歸測(cè)試套件(即使測(cè)試套件一開(kāi)始就存在),尤其是在同一時(shí)間對(duì)單體應(yīng)用還有其他幾十處更改的情況下。我們給可測(cè)試性一個(gè)雙星評(píng)級(jí)(而不是一星),因?yàn)樗軌驅(qū)M件(甚至整個(gè)層)使用mock或者stub模擬測(cè)試,這簡(jiǎn)化了整個(gè)測(cè)試工作。
在這種架構(gòu)風(fēng)格中,總體可靠性為中等(三星級(jí)),主要是由于沒(méi)有在大多數(shù)分布式架構(gòu)中存在的網(wǎng)絡(luò)流量、帶寬和延遲問(wèn)題。由于單體應(yīng)用部署的本質(zhì),再加上可測(cè)試性(測(cè)試的完整性)和部署風(fēng)險(xiǎn)的低評(píng)級(jí),我們只對(duì)分層架構(gòu)的可靠性給出了三星級(jí)的評(píng)級(jí)。
分層架構(gòu)的彈性和可擴(kuò)展性非常低(一星),主要是由于單體部署和缺乏架構(gòu)模塊化能力。雖然在一個(gè)單體應(yīng)用內(nèi)實(shí)現(xiàn)某些功能的擴(kuò)展是可能的,但這種工作通常需要非常復(fù)雜的設(shè)計(jì)技術(shù),如多線程、內(nèi)部消息傳遞和其他在這種架構(gòu)中不適合使用的并行處理實(shí)踐和技術(shù)。然而,由于用戶界面、后端處理和數(shù)據(jù)庫(kù)的單一性,分層架構(gòu)始終是一個(gè)單一的系統(tǒng)量子,因此應(yīng)用只能在單個(gè)量子的基礎(chǔ)上擴(kuò)展到一定的程度。
性能一直是分層架構(gòu)的一個(gè)有趣的特性。我們只給了它兩顆星評(píng)級(jí),這種架構(gòu)風(fēng)格因?yàn)槿狈Σ⑿刑幚?、封閉分層和架構(gòu)污水池反模式的原因根本不適合于高性能系統(tǒng)。與可擴(kuò)展性一樣,性能也可以通過(guò)緩存、多線程等來(lái)解決,但這并不是這種架構(gòu)風(fēng)格的自然特征;架構(gòu)師和開(kāi)發(fā)人員必須通過(guò)大量的工作才能實(shí)現(xiàn)這一切。
由于單體部署和缺乏架構(gòu)模塊性,分層架構(gòu)不支持容錯(cuò)。如果分層架構(gòu)的一小部分發(fā)生內(nèi)存不足問(wèn)題,則整個(gè)應(yīng)用單元將受到影響并崩潰。此外,由于大多數(shù)單體應(yīng)用普遍存在較高的平均恢復(fù)時(shí)間(MTTR),總體可用性受到影響,啟動(dòng)時(shí)間從小型應(yīng)用的2分鐘,到大多數(shù)大型應(yīng)用的15分鐘或更長(zhǎng)。