分層架構(gòu)風(fēng)格碌廓,也稱為n層架構(gòu)風(fēng)格,是最常見的架構(gòu)風(fēng)格之一蝙云。這種風(fēng)格的架構(gòu)是大多數(shù)應(yīng)用程序的事實(shí)標(biāo)準(zhǔn)氓皱,主要是因?yàn)樗唵巍⒂押貌⑶页杀镜筒佟8鶕?jù)康威定律波材,使用分層架構(gòu)開發(fā)應(yīng)用程序也是一種非常自然的方式,設(shè)計(jì)系統(tǒng)的組織最終會產(chǎn)生與組織架構(gòu)相同的設(shè)計(jì)身隐。在大多數(shù)組織中廷区,都有用戶界面(UI)開發(fā)人員、后端開發(fā)人員贾铝、規(guī)則開發(fā)人員和數(shù)據(jù)庫專家(DBA)這些角色隙轻。這些組織層很好地適應(yīng)了傳統(tǒng)分層架構(gòu)的各個層,使其成為許多業(yè)務(wù)應(yīng)用程序的自然選擇垢揩。分層架構(gòu)風(fēng)格也分為幾種架構(gòu)反模式玖绿,包括隱含架構(gòu)反模式和偶然架構(gòu)反模式。如果一個開發(fā)人員或架構(gòu)師不確定他們正在使用哪種架構(gòu)風(fēng)格叁巨,或者一個敏捷開發(fā)團(tuán)隊(duì)“剛開始編碼”斑匪,那么很有可能他們正在采用分層架構(gòu)風(fēng)格。
拓?fù)浣Y(jié)構(gòu)
組件在分層架構(gòu)中被組織成邏輯水平層锋勺,每個層在應(yīng)用中執(zhí)行特定的角色(例如表示邏輯或業(yè)務(wù)邏輯)蚀瘸。盡管對于必須存在的層的數(shù)量和類型沒有特定的限制,但是大多數(shù)分層體系結(jié)構(gòu)由四個標(biāo)準(zhǔn)層組成:表示層庶橱、業(yè)務(wù)層贮勃、持久層和數(shù)據(jù)庫層,如圖10-1所示苏章。在某些情況下寂嘉,業(yè)務(wù)層和持久性層被組合成一個業(yè)務(wù)層,特別是當(dāng)持久性邏輯(如SQL或HSQL)嵌入到業(yè)務(wù)層組件中時。因此垫释,較小的應(yīng)用可能只有三層丝格,而更大和更復(fù)雜的業(yè)務(wù)應(yīng)用可能包含五層或更多。
圖10-2從物理分層(部署)的角度說明了各種拓?fù)浣Y(jié)構(gòu)變體棵譬。第一個變體將表示層显蝌、業(yè)務(wù)層和持久層組合到一個部署單元中,數(shù)據(jù)庫層通常表示為一個單獨(dú)的外部物理數(shù)據(jù)庫(或文件系統(tǒng))订咸。第二個變體在物理上將表示層劃分為單獨(dú)的部署單元曼尊,業(yè)務(wù)層和持久層合并為第二個部署單元。同樣脏嚷,使用此變體骆撇,數(shù)據(jù)庫層在物理上通常通過外部數(shù)據(jù)庫或文件系統(tǒng)進(jìn)行分離。第三個變體將所有四個標(biāo)準(zhǔn)層組合到一個部署中父叙,包括數(shù)據(jù)庫層神郊。對于具有內(nèi)部嵌入式數(shù)據(jù)庫或內(nèi)存數(shù)據(jù)庫的較小應(yīng)用程序,此變體可能很有用趾唱。許多內(nèi)部(“免費(fèi)”)產(chǎn)品都是使用第三種變體建設(shè)并交付給客戶的涌乳。
分層架構(gòu)風(fēng)格的每一層在架構(gòu)中都有特定的角色和責(zé)任。例如甜癞,表示層將負(fù)責(zé)處理所有用戶界面和瀏覽器通信邏輯夕晓,而業(yè)務(wù)層將負(fù)責(zé)執(zhí)行與請求相關(guān)聯(lián)的特定業(yè)務(wù)規(guī)則。架構(gòu)中的每一層圍繞滿足特定業(yè)務(wù)請求所需完成的工作形成一個抽象悠咱。例如蒸辆,表示層不需要知道或擔(dān)心如何獲取客戶數(shù)據(jù);它只需要在屏幕上以特定格式顯示這些信息析既。同樣躬贡,業(yè)務(wù)層也不需要關(guān)心如何格式化客戶數(shù)據(jù)以便在屏幕上顯示,甚至不需要知道客戶數(shù)據(jù)來自何處眼坏;業(yè)務(wù)層只需要從持久層獲取數(shù)據(jù)逗宜,對數(shù)據(jù)執(zhí)行業(yè)務(wù)邏輯(例如計(jì)算值或聚合數(shù)據(jù)),并將這些信息傳遞到表示層空骚。分層架構(gòu)風(fēng)格中的關(guān)注概念分離使得在架構(gòu)中構(gòu)建有效的角色和責(zé)任模型變得容易。例如擂仍,表示層中的組件只處理顯示邏輯囤屹,而駐留在業(yè)務(wù)層中的組件只處理業(yè)務(wù)邏輯。這使得開發(fā)人員能夠利用他們特定的技術(shù)專長來專注于特定領(lǐng)域的技術(shù)方面的內(nèi)容(例如表示邏輯或持久性邏輯)逢渔。然而肋坚,這種好處的代價(jià)是缺乏整體靈活性(快速響應(yīng)變化的能力)。
分層架構(gòu)是從技術(shù)上劃分的架構(gòu)(與領(lǐng)域劃分的架構(gòu)相反)。組件分組不是按領(lǐng)域(如客戶)分組智厌,而是按它們在架構(gòu)中的技術(shù)角色(如表示層或業(yè)務(wù)層)分組诲泌。這導(dǎo)致任何特定的業(yè)務(wù)領(lǐng)域都會分布在架構(gòu)的所有層中。例如铣鹏,“客戶”領(lǐng)域邏輯包含在表示層敷扫、業(yè)務(wù)層、規(guī)則層诚卸、服務(wù)層和數(shù)據(jù)庫層中葵第,因此很難更改該領(lǐng)域的邏輯。因此合溺,領(lǐng)域驅(qū)動設(shè)計(jì)方法不太適用于使用分層架構(gòu)風(fēng)格卒密。
隔離層
分層架構(gòu)風(fēng)格中的每個層可以是封閉的,也可以是開放的棠赛。封閉層意味著當(dāng)一個請求自上而下地從一個層移動到另一個層時哮奇,請求不能跳過任何層,而是必須通過它下面的層到達(dá)下一層(如圖10-3)睛约。例如鼎俘,在一個封閉的分層架構(gòu)中,來自表示層的請求必須首先經(jīng)過業(yè)務(wù)層痰腮,然后到達(dá)持久層而芥,最后才能到達(dá)數(shù)據(jù)庫層。
在圖10-3中膀值,表示層繞過任何不必要的層(在21世紀(jì)初被稱為快速通道閱讀器模式)直接訪問數(shù)據(jù)庫以進(jìn)行簡單的檢索請求會更快棍丐、更容易。要做到這一點(diǎn)沧踏,業(yè)務(wù)層和持久層必須是開放的歌逢,允許請求繞過其他層。開放層和封閉層哪個更好翘狱?這個問題的答案在于一個被稱為隔離層的關(guān)鍵概念秘案。
隔離層概念意味著在架構(gòu)的一個層中所做的更改通常不會影響其他層中的組件,只要這些層之間的契約保持不變潦匈。每一層都獨(dú)立于其他層阱高,因此對架構(gòu)中其他層的內(nèi)部工作原理知之甚少或一無所知。但是茬缩,為了支持隔離層赤惊,與請求的流動主干相關(guān)的層必須是封閉的。如果表示層可以直接訪問持久層凰锡,那么對持久層所做的更改將同時影響業(yè)務(wù)層和表示層未舟,從而產(chǎn)生一個耦合非常緊密的應(yīng)用圈暗,組件之間具有層間的相互依賴關(guān)系。這種類型的架構(gòu)會變得非常脆弱裕膀,變更會非常困難而且成本高昂员串。
隔離層概念還允許在不影響任何其他層的情況下替換架構(gòu)中的任何層(同樣,假設(shè)定義良好的契約和使用業(yè)務(wù)委托模式)昼扛。例如寸齐,您可以利用分層架構(gòu)風(fēng)格中的隔離層概念,在不會影響應(yīng)用中的任何其他層的情況下將舊的JavaServer Faces(JSF)表示層替換為React.js野揪。
添加層
雖然封閉層有助于隔離層访忿,因此有助于隔離架構(gòu)中的更改,但有時某些層開放是有意義的斯稳。例如海铆,假設(shè)業(yè)務(wù)層中有共享對象,這些對象包含業(yè)務(wù)組件的公共功能(例如日期和字符串實(shí)用程序類挣惰、審計(jì)類卧斟、日志類等等)。假設(shè)有一個架構(gòu)決策憎茂,表示層不能使用這些共享業(yè)務(wù)對象珍语。這個約束如圖10-4所示,虛線從表示層組件到業(yè)務(wù)層中的共享業(yè)務(wù)對象竖幔。這個場景很難管理和控制板乙,因?yàn)樵诩軜?gòu)上,表示層可以訪問業(yè)務(wù)層拳氢,因此可以訪問該層中的共享對象募逞。
從架構(gòu)上強(qiáng)制執(zhí)行此限制的一種方法是向架構(gòu)添加一個包含所有共享業(yè)務(wù)對象的新服務(wù)層。現(xiàn)在添加這個新的層從架構(gòu)上限制表示層訪問共享業(yè)務(wù)對象馋评,因?yàn)闃I(yè)務(wù)層是封閉的(見圖10-5)放接。但是,新的服務(wù)層必須標(biāo)記為“開放”留特;否則業(yè)務(wù)層將被迫通過服務(wù)層來訪問持久層纠脾。將服務(wù)層標(biāo)記為“開放”允許業(yè)務(wù)層訪問該層(如實(shí)心箭頭所示),或者繞過該層并向下轉(zhuǎn)到下一層(如圖10-5中的虛線箭頭所示)蜕青。
利用開放層和封閉層的概念有助于定義架構(gòu)層次和請求流之間的關(guān)系苟蹈。它還為開發(fā)人員提供了了解架構(gòu)中的各種層的訪問限制的必要信息和指導(dǎo)。如果不能記錄或正確地傳達(dá)架構(gòu)中的哪些層是開放的和封閉的(以及為什么)右核,通常會導(dǎo)致緊密耦合和脆弱的架構(gòu)汉操,這些架構(gòu)將很難測試、維護(hù)和部署蒙兰。
其他考慮事項(xiàng)
當(dāng)還不知道最終將使用哪種架構(gòu)風(fēng)格時磷瘤,分層架構(gòu)可以作為大多數(shù)應(yīng)用的一個良好的起點(diǎn)。在許多微服務(wù)工作的普遍實(shí)踐中搜变,當(dāng)架構(gòu)師仍在確定微服務(wù)是否是正確的架構(gòu)選擇時采缚,但開發(fā)必須開始。然而挠他,當(dāng)使用這種技術(shù)時扳抽,一定要將重用保持在最低限度,并使對象層次結(jié)構(gòu)(繼承樹的深度)做夠的淺殖侵,以便保持良好的模塊化水平贸呢。這將有助于以后轉(zhuǎn)向另一種架構(gòu)風(fēng)格。
對于分層架構(gòu)拢军,需要注意的一點(diǎn)是不要陷入污水池反模式楞陷。當(dāng)請求以簡單的傳遞處理方式從一層移動到另一層,而每個層中并沒有執(zhí)行業(yè)務(wù)邏輯時茉唉,就會出現(xiàn)這種反模式固蛾。例如,假設(shè)表示層響應(yīng)用戶的一個簡單請求度陆,以檢索基本的客戶數(shù)據(jù)(例如名稱和地址)艾凯。表示層將請求傳遞給業(yè)務(wù)層,業(yè)務(wù)層只將請求傳遞給規(guī)則層懂傀,而規(guī)則層只將請求傳遞給持久層趾诗,后者對數(shù)據(jù)庫層進(jìn)行簡單的SQL調(diào)用以檢索客戶數(shù)據(jù)。然后蹬蚁,數(shù)據(jù)將一直回傳上去恃泪,而不需要額外的處理或邏輯來聚合、計(jì)算缚忧、應(yīng)用規(guī)則或轉(zhuǎn)換數(shù)據(jù)悟泵。這會導(dǎo)致不必要的對象實(shí)例化和處理,從而影響內(nèi)存消耗和性能闪水。
每一個分層架構(gòu)都至少有一些場景陷入架構(gòu)污水池反模式糕非。確定是否陷入架構(gòu)污水池反模式的關(guān)鍵是分析屬于這一類的請求的百分比。80-20規(guī)則通城蛴埽可以作為很好的判斷實(shí)踐朽肥。例如,如果只有20%的請求是污水池持钉,則可以接受衡招。但是,如果80%的請求都是污水池每强,那么對于問題域來說分層架構(gòu)并不是合適的架構(gòu)風(fēng)格始腾。另一種解決架構(gòu)污水池反模式的方法是讓架構(gòu)中的所有層都開放州刽,當(dāng)然,也要意識到浪箭,代價(jià)是在架構(gòu)中管理變更的難度會增加穗椅。
為什么使用分層架構(gòu)風(fēng)格
分層架構(gòu)風(fēng)格是小型、簡單的應(yīng)用程序或網(wǎng)站的好選擇奶栖。
對于預(yù)算和時間限制非常緊張的情況匹表,分層架構(gòu)也是一個很好的架構(gòu)選擇,尤其是在開始階段宣鄙。由于架構(gòu)簡單性和開發(fā)人員和架構(gòu)師之間對這種架構(gòu)的熟悉度袍镀,分層架構(gòu)可能是成本最低的架構(gòu)風(fēng)格之一,有助于小型應(yīng)用的開發(fā)冻晤。當(dāng)架構(gòu)師仍在分析業(yè)務(wù)需求并且不確定哪種架構(gòu)風(fēng)格是最合適的時候苇羡,分層架構(gòu)風(fēng)格也是一個不錯的選擇。
隨著使用分層架構(gòu)風(fēng)格的應(yīng)用程序的不斷迭代明也,諸如可維護(hù)性宣虾、敏捷性、可測試性和可部署性等特性將受到不利影響温数。因此绣硝,使用分層架構(gòu)的大型應(yīng)用和系統(tǒng)可能更適合使用其他更模塊化的架構(gòu)風(fēng)格。
架構(gòu)特性評級
特性評級表中的一星級評級(如圖10-6所示)意味著特定的架構(gòu)特性在某種架構(gòu)中沒有得到很好的支持撑刺,而五星評級意味著架構(gòu)特性是某種架構(gòu)風(fēng)格中最強(qiáng)大的特性之一鹉胖。記分卡中確定的每個特性的定義見第4章。
成本低廉和簡單性是分層架構(gòu)風(fēng)格的主要優(yōu)勢够傍。分層架構(gòu)本質(zhì)上是一個單體甫菠,它不具有與分布式架構(gòu)風(fēng)格相關(guān)的復(fù)雜性,它簡單易懂并且構(gòu)建和維護(hù)的成本相對較低冕屯。然而寂诱,值得注意的是,隨著整體分層架構(gòu)變得越來越大安聘,從而變得越來越復(fù)雜痰洒,這些評級開始迅速降低。
這種架構(gòu)風(fēng)格的可部署性和可測試性都非常低浴韭∏鹩鳎可部署性評級低是由于部署成本高、風(fēng)險(xiǎn)高和缺乏頻繁部署能力念颈。在分層架構(gòu)中對一個類文件進(jìn)行簡單的三行更改需要重新部署整個部署單元泉粉,原來的改動暗中會引起潛在的數(shù)據(jù)庫更改、配置更改或其他編碼更改。此外嗡靡,這簡單的三行更改通常會引起其他幾十處的更改跺撼,因此會增加部署的風(fēng)險(xiǎn)(也會增加部署的頻率)。低可測試性評級也反映了這種情況叽躯;通過簡單的三行更改财边,大多數(shù)開發(fā)人員不會花費(fèi)數(shù)小時來執(zhí)行整個回歸測試套件(即使測試套件一開始就存在),尤其是在同一時間對單體應(yīng)用還有其他幾十處更改的情況下点骑。我們給可測試性一個雙星評級(而不是一星),因?yàn)樗軌驅(qū)M件(甚至整個層)使用mock或者stub模擬測試谍夭,這簡化了整個測試工作黑滴。
在這種架構(gòu)風(fēng)格中,總體可靠性為中等(三星級)紧索,主要是由于沒有在大多數(shù)分布式架構(gòu)中存在的網(wǎng)絡(luò)流量袁辈、帶寬和延遲問題。由于單體應(yīng)用部署的本質(zhì)珠漂,再加上可測試性(測試的完整性)和部署風(fēng)險(xiǎn)的低評級晚缩,我們只對分層架構(gòu)的可靠性給出了三星級的評級。
分層架構(gòu)的彈性和可擴(kuò)展性非常低(一星)媳危,主要是由于單體部署和缺乏架構(gòu)模塊化能力荞彼。雖然在一個單體應(yīng)用內(nèi)實(shí)現(xiàn)某些功能的擴(kuò)展是可能的,但這種工作通常需要非常復(fù)雜的設(shè)計(jì)技術(shù)待笑,如多線程鸣皂、內(nèi)部消息傳遞和其他在這種架構(gòu)中不適合使用的并行處理實(shí)踐和技術(shù)。然而暮蹂,由于用戶界面寞缝、后端處理和數(shù)據(jù)庫的單一性,分層架構(gòu)始終是一個單一的系統(tǒng)量子仰泻,因此應(yīng)用只能在單個量子的基礎(chǔ)上擴(kuò)展到一定的程度荆陆。
性能一直是分層架構(gòu)的一個有趣的特性。我們只給了它兩顆星評級集侯,這種架構(gòu)風(fēng)格因?yàn)槿狈Σ⑿刑幚肀惶洹⒎忾]分層和架構(gòu)污水池反模式的原因根本不適合于高性能系統(tǒng)。與可擴(kuò)展性一樣浅悉,性能也可以通過緩存趟据、多線程等來解決,但這并不是這種架構(gòu)風(fēng)格的自然特征术健;架構(gòu)師和開發(fā)人員必須通過大量的工作才能實(shí)現(xiàn)這一切汹碱。
由于單體部署和缺乏架構(gòu)模塊性,分層架構(gòu)不支持容錯荞估。如果分層架構(gòu)的一小部分發(fā)生內(nèi)存不足問題咳促,則整個應(yīng)用單元將受到影響并崩潰稚新。此外,由于大多數(shù)單體應(yīng)用普遍存在較高的平均恢復(fù)時間(MTTR)跪腹,總體可用性受到影響褂删,啟動時間從小型應(yīng)用的2分鐘,到大多數(shù)大型應(yīng)用的15分鐘或更長冲茸。