分層架構:一個經典卻得不到優的難題

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"經典的分層架構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當系統變的複雜,無論是爲了便於維護還是爲了便於理解,將系統進行分層都是一個經典的操作,這也更貼近人理解事物和拆解問題的方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回顧一些經典的分層模型,最貼近用戶的層往往提供更高的價值,離用戶最遠的層往往是更底層的技術實現,如經典的三層架構(表現層、邏輯層、數據訪問層)和OSI的七層網絡模型(應用層、表示層、會話層、傳輸層、網絡層、數據鏈路層、物理層)。對用戶而言,隨着“層距離”的增加,對層的理解難度會逐漸提高,甚至到無法理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分層通過對每層的職責定義以及層與層之間的交互方式定義,讓知識在層與層之間做到合理的切割。對不同的用戶而言,需要理解的層次是不同的,也就做到了知識的隔離。","attrs":{}}]},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"分層架構實現並不容易","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分層架構結構簡單,非常易於理解,但這樣簡單的架構實現起來並不那麼容易,我們來看兩個例子:","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"例子一:越俎代庖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9d/9df248c2851e0c7e380d47753c1eb2ab.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"BFF作爲架構中的一層,一定程度上起到了隔離前端UI展示邏輯和後端業務邏輯的作用,使得前後端都能夠獨立的演進。BFF通過組合後端業務邏輯接口,實現前端展示需求,這裏的重點是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"實現前端展示需求","attrs":{}},{"type":"text","text":",那麼BFF是否應該組合後端的各種能力,提供一種新的業務能力呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實施落地過程中,爲了快速滿足一個業務上的需求,BFF作爲有着上帝視角的存在,掌握了所有後端業務服務的接口,可以很容易調度能用的後端服務接口有序的完成了業務功能。在這種實現下,BFF這一層的知識就不再限於前端的展示邏輯,還越俎代庖地實現了一些業務邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而,當業務邏輯變更時,很難在後端業務服務中找到全部的邏輯,極端的情況是修改遍佈了整個調用鏈。這種做法顯然是不可取的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"例子二:臃腫的中間層","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c9/c9a42e1c55061f4d32ac7775dbdb4d84.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個例子相信大家也不陌生(左側原圖參見","attrs":{}},{"type":"link","attrs":{"href":"[Martin Fowler微服務進程內架構設計](https://martinfowler.com/)","title":"","type":null},"content":[{"type":"text","text":"[Martin Fowler微服務進程內架構設計](https://martinfowler.com/)","attrs":{}}]},{"type":"text","text":"),我在圖中用不同模塊的大小來表達每個模塊在實現過程中佔的比重,雖然老馬最初的設計圖並無此意圖。即便在最初做了各種設計,希望每個模塊各司其職,但實現過程中將所有的邏輯寫到Service層卻是司空見慣,甚至是一定會發生的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這些邏輯可能包括對請求參數的校驗,對數據模型的轉換和拼接,對業務模型屬性的操作,對數據庫讀寫的控制,對其他服務調用的控制等等,而這些應該卻是其他模塊的職責。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"臃腫的中間層把一個服務的進程內代碼全部揉在一起,就像一個“大單體”,其他層的組件僅僅是一層薄薄的和其他模塊集成的必須代碼,這種分層幾乎等於沒有分層,自然無法享有分層帶來的任何好處。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"其他會遇到的問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面兩個例子來看,無論進程間組件之間,還是進程內組件之間,分層本應該能很好的起到隔離作用,而不好的實現會將這種作用化爲烏有。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不僅如此,層與層的邊界不清或交互混亂還會帶來很多新的問題(這感覺就像單體架構拆分成微服務架構,以前不必關心的問題現在卻成了難題),典型的問題還有:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"穿透性修改","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"層之間沒有做到合理的切割,一個業務功能的實現從最外層一直貫穿到最內層,當業務功能變更時,每層都要做調整才能實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"層過多或過少引發的問題","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這有點像拆分微服務,當層過多時,層與層之間的依賴關係很難在設計時理清,交互邏輯複雜,甚至會出現層之間的循環依賴;而過少的層很難起到知識的隔離作用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"跨層調用讓系統喪失進化能力","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"跨層調用不是一定有問題,但我仍然建議不要採用跨層調用,特別是一些基礎的組件層。一方面跨層調用破壞了架構的統一,也就破壞了知識的邊界。另一方面也破壞了層的進化能力。例如希望在某一層增加一些通用邏輯,比如權限校驗或緩存時,如果這一層被跳過,這個進化就無法實現。","attrs":{}}]},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"分層架構能做到“優”嗎","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"問題的深層次原因","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"造成上面問題的原因主要是層與層之間沒有做到很好的隔離,各層的職責和邊界不清晰。在看看能做點什麼之前,我們先來探究下其深層次原因:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"架構設計之初並沒有嚴格的層級和邊界設計","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"架構師們通常有意識和能力在開發設計過程中判斷每個功能的歸屬,和每層需要實現的工作。如果這種原則只存在於架構師的頭腦中,而沒有在團隊內形成共識,完全依賴每個開發人員的經驗和對當前架構的理解,就會不受控,很容易演變成層與層之間只是物理代碼的隔離,邏輯上卻嚴重耦合。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"有層級設計,但每層的職責設計難以支撐業務演進","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以微服務爲例,如何設計微服務,不同的架構師觀點也不盡相同。在我經歷的項目裏,有些微服務曾被設計成了”貧血“的服務,服務端僅保存了實體模型和簡單的邏輯,如果要實現複雜的業務功能,需要由前端來實現;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種設計給我們帶來了很多運維上的問題,當前端不止一端時,相似的功能需要在每一端都實現一次。當某一端的實現有調整時(特別是不同的端由不同團隊維護時),跨前端的邏輯不一致,導致後端數據在兩個端相互覆蓋。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,層的職責是會相互影響的,如果某一層沒有承擔起它該有的責任,其他層只能把這個責任擔起來,從而引入不必要的問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"開發團隊的認知水平和協作水平低下","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"軟件開發是一個團隊協作共同創作的過程,如果在每個階段都完全依靠每個個體的能力,軟件的架構和質量取決於團隊內最差的個體的能力和認知水平。這也是我在標題上寫分層架構是“","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"經典卻很得不到優","attrs":{}},{"type":"text","text":"”的原因。架構設計只是個開始,能否把架構按設計完美的實現纔是關鍵。就像我們從來不缺創業的點子,缺的是把點子真正落地的實力。","attrs":{}}]},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"我們能做點什麼","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"軟件架構的終極目標是,用最小的人力成本來滿足構建和維護該系統的需求。 —— 《架構整潔之道》","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了實現這個目標,開發團隊需要做到以下幾點來消滅經典的分層架構在落地過程中產生的問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"合理設計分層,清晰定義每層的職責","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這一點毋庸置疑。但我要強調的是,這個設計要是全團隊的共識,是頂在開發團隊頭上的第一個原則,任何的架構變更都要回到這裏,來審視是否破壞了最初的設計原則。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":2},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"通過技術手段守護架構","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當定義清楚了分層架構,必然也就有了分層的一些原則,在《演進式架構》中推薦儘早確定系統的適應度函數,並定期的審查,根據業務和技術的需求修改當前的適應度函數或增加新的適應度函數,已保證架構能夠按照設計的方向發展。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在進程內,有一些自動化工具可以通過測試的方式來驗證代碼的架構是否遵循了預先設計的原則(如","attrs":{}},{"type":"link","attrs":{"href":"[ArchUnit](https://www.archunit.org/)","title":"","type":null},"content":[{"type":"text","text":"[ArchUnit](https://www.archunit.org/)","attrs":{}}]},{"type":"text","text":"),可以高效的幫團隊識別出在開發過程中破壞原則的代碼實現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":3},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"提升團隊整體認知水平和協作水平","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在團隊不斷成熟的過程中,很難保證所有的開發人員都能夠有能力守護架構,因此除了通過技術手段守護架構,也非常有必要採取一定的手段來提升整個團隊的認知水平和協作水平。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先可以在團隊內建立架構評審委員會,關於架構的關鍵決策需要由這個團隊來拍板,而不是每個開發人員都可以決策。另外在做詳細的實現設計時,要由有經驗有能力守護架構的同學進行設計,並將工作內容拆分成更加可操作的Task。通過這種方式將整個團隊的認知水平底線提升到可以守護架構的程度。","attrs":{}}]},{"type":"horizontalrule","attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結語","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"軟件系統的腐敗背後似乎有一雙非常有力的手在推動着,即便是簡單容易理解的分層架構,在業務需求和技術都快速變更的大背景下,也很難在相對長的時間內做到“優”的水平。作爲架構師,更應該保持一個時刻應對變化的心態,帶領團隊披荊斬棘,解決架構演進路上的各種問題。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章