相信大家都非常清楚,如何編寫可讀性強的代碼是一個合格程序員的必修課。

我在之前的文章 談談什麼是好的代碼》 中談了一些自己對整潔代碼的感悟,代碼並不是獨立存在的,成百上千個類的系統在企業應用中非常常見,如何將代碼進行有效的組織,保持高可讀性,高可維護性,則是一個好的架構需要考慮的事情。本文從原則切入,聊聊組件的分層和解耦,淺談下Bob大叔提出的整潔架構,感興趣的同學也可以發表下自己的看法。

原則

原則屬於做事情的指導方針,在討論架構前,先來看看相關的一些原則。有適用於代碼層面的原則,有適用於再高一層級——組件的原則。

 代碼原則

代碼的原則有SOLID,迪米特法則,組合複用原則等等,我在 談談什麼是好的代碼 中也列出來過,關於這些原則討論的文章非常之多,大家也比較熟悉,本文主要談架構方面的,這裏就不展開了。

 組件原則

組件是一組代碼的集合,拿蓋房子來打比方,代碼原則指導如何使用磚塊建造房間,而組件原則指導如何將房間構建成高樓大廈。組件的構建要遵循一些原則,否則即使牆砌的再好,房間修建的再漂亮,不按照規範建造,房子可能就歪歪扭扭,整棟樓房的質量也堪憂。

組件原則包括組件內的關係(組件聚合)以及組件間的關係(組件耦合)。

組件聚合

組件聚合方面的原則有以下幾個:

  • REP:複用/發佈等同原則

  • CCP:共同閉包原則

  • CRP:共同複用原則

REP是組件聚合總的指導原則,表示組件內的類和模塊是彼此緊密相關的。CCP和CRP是對REP的補充,CCP可以看做是組件級別的單一職責原則 (SRP): 由於相同原因修改,並且需要同時修改的東西放一起; 不同原因修改,並且不同時修改的東西分開。 CRP可以看做是組件級別的接口隔離原則(ISP): 不要依賴不需要的東西。

可以看出通過這三個原則構建的組件,擁有以下幾個特點:

  • 組件內的類和模塊緊密相關,需求變更時通常需要同時修改;

  • 需求變更時,需要進行的修改只涉及很少的組件甚至在一個組件內;

  • 複用這個組件時,通常組件內的功能均是用戶需要的,而不是有一些不相關的功能;

這裏貼下書中的張力圖:

項目的初期,更多關注的是維護性而犧牲複用性,隨着項目逐漸成熟,項目重心會逐漸傾向於複用性。

組件耦合

組件耦合方面的原則有以下幾個:

  • ADP:無依賴環原則

  • SDP:穩定依賴原則

  • SAP:穩定抽象原則

組件間的依賴如果存在環,則維護性將大大降低,我們應該避免組件間的循環依賴。


組件間的依賴關係應該是指向更穩定的方向,每個組件的穩定性都低於其依賴的組件穩定性。組件越穩定,則其抽象程度需要更高;組件越不穩定,則其抽象程度需要更低,越具體。

有時候,可能會新引入一個引用,導致一個穩定的組件依賴了一個不穩定的組件,此時就可以使用依賴倒置原則(DIP)將依賴關係反轉,保持穩定依賴。

分層與解耦

談了組件相關的原則,現在來談談組件間的分層和隔離的方式。通過有效的分層手段,可以有效隔離不同功能的組件。

  水平分層

得益於MVC模式的普及,水平分層在我們的系統中已經非常普及了,通常有以下幾層:

  • 表現層/UI層:負責系統的界面展示,通常包括Controller和VO等;

  • 領域層/業務邏輯層:負責處理系統的業務邏輯,通常包括Service,Manager和DTO等;

  • 數據層:負責與DB等底層數據存儲介質通信,通常包括Mapper和DO等;

  垂直分層

但是,僅僅做到水平分層是不夠的。 當業務邏輯比較複雜時,涉及的用例會非常多,這些用例之間的變更原因幾乎肯定是不同的,所以還要進行垂直分層,將變更原因不一樣的用例切分開。

例如,很多系統的修改操作和刪除操作的變更原因和變更頻率是不一樣的,這時候可以考慮將修改和刪除進行解耦。

不論是水平分層還是垂直分層,其核心目的都是將更新頻率不同的代碼給分開,放入不同的組件中。

 解耦方式

分層後的解耦方式有多種:

  • 源碼層次:通過控制源代碼模塊間的依賴關係進行解耦,部署時仍然一起部署,適用於項目早期剛起步時;

  • 部署層次:通過控制部署單元(例如jar包等)之間依賴進行解耦,當系統對部署和開發方面有更高的要求時,部分組件需要獨立出去形成新的部署單元;

  • 服務層次:通過將組件的依賴關係降低到數據結構級別,然後通過服務進行通信來解耦,當系統足夠大的時候,就需要服務層次的解耦了;

但需要注意的是,不論通過哪種解耦方式進行代碼的隔離,並不意味着這樣就萬事大吉,擁有良好可擴展和可維護性了。這也是Bob在《架構整潔之道》中提到的“橫跨型變更”(雖然作者是在服務的這一章提出的,但我認爲也適用於其他兩種解耦層次)。

請看下面的出租車調度系統的服務架構圖

圖中可以看出,Taxi UI依賴 Taxi Finder查找符合條件的出租車,依賴Taxi Selector進行出租車調度。而Taxi Finder通過多個Taxi Supplier服務獲取車輛信息,Taxi Selector依賴Taxi Dispatcher進行最終的派單。

各個組件都是服務化的。可以看到,各個組件都是具體的類,雖然各個組件隔離部署,但其實他們之間是強耦合的,並沒有真正的解耦:加入現在出租車公司準備推出運送貓咪的服務,則所有的組件都需要進行更改,同時有些同學的更改方式就是在原有的類中增加if...else,這顯然是不可取的。

正確的做法應該是在組件最初的設計中,就應該考慮抽象化和多態,如下圖,使用策略模式或者模板方法進行解耦:

注意看我紅框標出來的,除了服務間的隔離外,在組件內部其實也存在隔離,而這個的隔離更加重要。這也就是書中講的:

服務邊界並不能代表系統的架構邊界,服務內部的組件邊界纔是。

架構

從代碼和組件原則到組件分層和解耦,我們逐漸對系統底層的一些元素有了比較深入的瞭解,那麼上層的架構到底是什麼呢?

  什麼是架構

並沒有非常明確的定義,這裏引用書中的一些描述,大家應該有一些體會:

軟件架構的實質就是規劃如何將系統切分成組件,並安排好組件之間的排列關係,以及組件之間互相通信的方式。
軟件架構的終極目標是,用最小的人力成本滿足構建和維護該系統的需求

需要指出的是,架構和框架並不是相同的東西:

  • 架構一定是業務相關的,包含了業務屬性,並且這個業務屬性是系統的核心價值;

  • 框架一般都是業務無關的,是我們編碼實現架構的的工具,屬於實現細節。

最初設計系統架構時,並不需要過多考慮使用什麼框架,而更多的是關注自身業務。

此外,很多人可能對架構有些誤解:設計那麼多有什麼用,代碼不還照樣得寫?

是的,代碼還得寫,架構並不能讓你不寫代碼了(有時可能還會讓你多寫代碼)。但是,好的架構會讓寫代碼變得更容易了。

容易不一定是體現在需要變更的代碼量多少上,好的架構可以讓你更快速的找出需要變更的範圍,並且很容易的就修改掉——這對於一個運行維護了多年的系統尤爲重要。大家可以回想下這樣的場景是不是很熟悉:明明是一個看起來很正常很合理的需求,看起來變更範圍也不大,但真的去擼代碼時發現需要改的地方好多,甚至無從下手去改。這個時候可能就要去看看之前的架構設計是不是不夠合理,有哪些需要優化改進的。

六邊形架構

六邊形架構,又名端口適配器架構(我更喜歡這個名字,因爲六邊形老讓人感覺有六個什麼東西跟它對應),DDD極力推崇該架構。系統的領域模型是系統最爲重要的部分,而其他的諸如DB,UI,緩存,消息隊列等等均通過適配器與領域層進行通信,也就是依賴關係是由外到內的(依賴倒置)。之前在商家規模化運營項目中也嘗試過 使用DDD來進行架構設計

  整潔架構

Bob綜合六邊形架構和其他幾個架構的特點提出了整潔架構,它具有以下幾個特點:

  • 獨立於框架

  • 可被測試

  • 獨立於UI

  • 獨立於數據庫

  • 獨立於任何外部機構

架構最內部是業務實體,代表了系統關鍵業務邏輯。

再外一層是用例——特定應用場景下的業務邏輯,在Bob看來,用例是架構設計中非常重要的一環,架構也是基於這些用例進行設計的,如果缺失了用例,就無從談起架構設計了。

緊接着是控制器,網關和展示器,是一層接口適配器。

最外層則是框架和驅動程序,這裏麪包含了數據庫,web,工具等等,這一層一般只包含了一些通信的黏合性代碼。

也並不一定只有四層,真實的情況可能會超過四層,但總的原則不變:外層依賴內存,最內部是最通用、最高層的策略,最外層是最具體的實現細節。

外層的是底層組件,內層的是高層組件,底層組件作爲高層的插件存在,換句話說外層的這個組件是可以被其他的組件像插件一樣替換掉的。

這裏需要明確的一點是:有時候,軟件運行的方向與依賴的方向是不同的(這個很多同學可能會沒有注意到)。比如業務實體從數據庫取數據,運行方向是業務實體->數據庫,而通過依賴反轉(DI,業務實體定義查詢接口,數據庫層實現),我們的依賴關係是業務實體<-數據庫。這裏講到的保持底層組件對高層組件的依賴,高層組件的穩定性和抽象性,也就是前面講的SDP和SAP。

總結

本文從代碼和組件的原則講起,講到組件內和組件間的關係,以及如何進行組件的分層和隔離,接着引出了架構相關的討論,列舉了六邊形架構和整潔架構,並談了一些自己的理解。

後續會結合自身做過的項目,談一談具體的一些架構模式。

參考:

《架構整潔之道》 Robert C. Martin

《領域驅動設計》 Eric Evans

《企業應用架構模式》Martin Fowler

文末福利

知識圖譜獲取

星標置頂 「淘系技術」微信公衆號,截圖發送後臺。

文章作者

阿里巴巴 淘系技術 開發工程師——馬飛翔(花名:澤畔)

圖譜來源

阿里巴巴 ICBU技術部 高級無線開發工程師——韓帥

✿  拓展閱讀

作者| 馬飛翔(澤畔)

編輯| 橙子君

出品| 阿里巴巴新零售淘系技術

相關文章