一文讀懂架構整潔之道
分類:互聯(lián)網(wǎng)熱點
編輯:新網(wǎng)小青年
瀏覽量:433
2020-07-13 16:55:23
相信大家都非常清楚,如何編寫可讀性強的代碼是一個合格程序員的必修課。
我在之前的文章《談談什么是好的代碼》中談了一些自己對整潔代碼的感悟,代碼并不是獨立存在的,成百上千個類的系統(tǒng)在企業(yè)應用中非常常見,如何將代碼進行有效的組織,保持高可讀性,高可維護性,則是一個好的架構需要考慮的事情。本文從原則切入,聊聊組件的分層和解耦,淺談下Bob大叔提出的整潔架構,感興趣的同學也可以發(fā)表下自己的看法。
# 原則
原則屬于做事情的指導方針,在討論架構前,先來看看相關的一些原則。有適用于代碼層面的原則,有適用于再高一層級——組件的原則。
? 代碼原則
代碼的原則有SOLID,迪米特法則,組合復用原則等等,我在談談什么是好的代碼中也列出來過,關于這些原則討論的文章非常之多,大家也比較熟悉,本文主要談架構方面的,這里就不展開了。
? 組件原則
組件是一組代碼的集 合,拿蓋房子來打比方,代碼原則指導如何使用磚塊建造房間,而組件原則指導如何將房間構建成高樓大廈。組件的構建要遵循一些原則,否則即使墻砌的再好,房間修建的再漂亮,不按照規(guī)范建造,房子可能就歪歪扭扭,整棟樓房的質(zhì)量也堪憂。
組件原則包括組件內(nèi)的關系(組件聚合)以及組件間的關系(組件耦合)。
**組件聚合**
組件聚合方面的原則有以下幾個:
- REP:復用/發(fā)布等同原則
- CCP:共同閉包原則
- CRP:共同復用原則
REP是組件聚合總的指導原則,表示組件內(nèi)的類和模塊是彼此緊密相關的。CCP和CRP是對REP的補充,CCP可以看做是組件級別的單一職責原則(SRP):由于相同原因修改,并且需要同時修改的東西放一起;不同原因修改,并且不同時修改的東西分開。CRP可以看做是組件級別的接口隔離原則(ISP):不要依賴不需要的東西。
可以看出通過這三個原則構建的組件,擁有以下幾個特點:
- 組件內(nèi)的類和模塊緊密相關,需求變更時通常需要同時修改;
- 需求變更時,需要進行的修改只涉及很少的組件甚至在一個組件內(nèi);
- 復用這個組件時,通常組件內(nèi)的功能均是用戶需要的,而不是有一些不相關的功能;
這里貼下書中的張力圖:
項目的初期,更多關注的是維護性而犧牲復用性,隨著項目逐漸成熟,項目重心會逐漸傾向于復用性。
**組件耦合**
組件耦合方面的原則有以下幾個:
- ADP:無依賴環(huán)原則
- SDP:穩(wěn)定依賴原則
- SAP:穩(wěn)定抽象原則
組件間的依賴如果存在環(huán),則維護性將大大降低,我們應該避免組件間的循環(huán)依賴。
組件間的依賴關系應該是指向更穩(wěn)定的方向,每個組件的穩(wěn)定性 都低于其依賴的組件穩(wěn)定性。組件越穩(wěn)定,則其抽象程度需要更高;組件越不穩(wěn)定,則其抽象程度需要更低,越具體。
有時候,可能會新引入一個引用,導致一個穩(wěn)定的組件依賴了一個不穩(wěn)定的組件,此時就可以使用依賴倒置原則(DIP)將依賴關系反轉(zhuǎn),保持穩(wěn)定依賴。
# 分層與解耦
談了組件相關的原則,現(xiàn)在來談談組件間的分層和隔離的方式。通過有效的分層手段,可以有效隔離不同功能的組件。
? 水平分層
得益于MVC模式的普及,水平分層在我們的系統(tǒng)中已經(jīng)非常普及了,通常有以下幾層:
表現(xiàn)層/UI層:負責系統(tǒng)的界面展示,通常包括Controller和VO等;
領域?qū)?業(yè)務邏輯層:負責處理系統(tǒng)的業(yè)務邏輯,通常包括Service,Manager和DTO等;
數(shù)據(jù)層:負責與DB等底層數(shù)據(jù)存儲介質(zhì)通信,通常包括M
apper和DO等;
? 垂直分層
但是,僅僅做到水平分層是不夠的。當業(yè)務邏輯比較復雜時,涉及的用例會非常多,這些用例之間的變更原因幾乎肯定是不同的,所以還要進行垂直分層,將變更原因不一樣的用例切分開。
例如,很多系統(tǒng)的修改操作和刪除操作的變更原因和變更頻率是不一樣的,這時候可以考慮將修改和刪除進行解耦。
不論是水平分層還是垂直分層,其核心目的都是將更新頻率不同的代碼給分開,放入不同的組件中。
? 解耦方式
分層后的解耦方式有多種:
- 源碼層次:通過控制源代碼模塊間的依賴關系進行解耦,部署時仍然一起部署,適用于項目早期剛起步時
- 部署層次:通過控制部署單元(例如jar包等)之間依賴進行解耦,當系統(tǒng)對部署和開發(fā)方面有更高的要求時,部分組件需要獨立出去形成新的部署單元;
- 服務層次:通過將組件的依賴關系降低到數(shù)據(jù)結構級別,然后通過服務進行通信來解耦,當系統(tǒng)足夠大的時候,就需要服務層次的解耦了;
但需要注意的是,不論通過哪種解耦方式進行代碼的隔離,并不意味著這樣就萬事大吉,擁有良好可擴展和可維護性了。這也是Bob在《架構整潔之道》中提到的“橫跨型變更”(雖然作者是在服務的這一章提出的,但我認為也適用于其他兩種解耦層次)。
請看下 面的出租車調(diào)度系統(tǒng)的服務架構圖
圖中可以看出,Taxi UI依賴 Taxi Finder查找符合條件的出租車,依賴Taxi Selector進行出租車調(diào)度。而Taxi Finder通過多個Taxi Supplier服務獲取車輛信息,Taxi Selector依賴Taxi Dispatcher進行最終的派單。
各個組件都是服務化的??梢钥吹?,各個組件都是具體的類,雖然各個組件隔離部署,但其實他們之間是強耦合的,并沒有真正的解耦:加入現(xiàn)在出租車公司準備推出運送貓咪的服務,則所有的組件都需要進行更改,同時有些同學的更改方式就是在原有的類中增加if...else,這顯然是不可取的。
正確的做法應該是在組件最初的設計中,就應該考慮抽象化和多態(tài),如下圖,使用策略模式或者模板方法進行解耦:
采用面向?qū)ο蟮姆椒▉硖幚頇M跨型變更
注意看我紅框標出來的,除了服務間的隔離外,在組件內(nèi)部其實也存在隔離,而這個的隔離更加重要。這也就是書中講的:
服務邊界并不能代表系統(tǒng)的架構邊界,服務內(nèi)部的組件邊界才是。
# 架構
從代碼和組件原則到組件分層和解耦,我們逐漸對系統(tǒng)底層的一些元素有了比較深入的了解,那么上層的架構到底是什么呢?
? 什么是架構
并沒有非常明確的定義,這里引用書中的一些描述,大家應該有一些體會:
軟件架構的實質(zhì)就是規(guī)劃如何將系統(tǒng)切分成組件,并安排好組件之間的排列關系,以及組件之間互相通信的方式。
軟件架構的終極目標是,用最小的人力成本滿足構建和維護該系統(tǒng)的需求
需要指出的是,架構和框架并不是相同的東西:
- 架構一定是業(yè)務相關的,包含了業(yè)務屬性,并且這個業(yè)務屬性是系統(tǒng)的核心價值;
- 框架一般都是業(yè)務無關的,是我們編碼實現(xiàn)架構的的工具,屬于實現(xiàn)細節(jié)。
最初設計
系統(tǒng)架構時,并不需要過多考慮使用什么框架,而更多的是關注自身業(yè)務。
此外,很多人可能對架構有些誤解:設計那么多有什么用,代碼不還照樣得寫?
是的,代碼還得寫,架構并不能讓你不寫代碼了(有時可能還會讓你多寫代碼)。但是,好的架構會讓寫代碼變得更容易了。
容易不一定是體現(xiàn)在需要變更的代碼量多少上,好的架構可以讓你更快速的找出需要變更的范圍,并且很容易的就修改掉——這對于一個運行維護了多年的系統(tǒng)尤為重要。大家可以回想下這樣的場景是不是很熟悉:明明是一個看起來很正常很合理的需求,看起來變更范圍也不大,但真的去擼代碼時發(fā)現(xiàn)需要改的地方好多,甚至無從下手去改。這個時候可能就要去看看之前的架構設計是不是不夠合理,有哪些需要優(yōu)化改進的。
# 六邊形架構
六邊形架構,又名端口適配器架構(我更喜歡這個名字,因為六邊形老讓人感覺有六個什么東西跟它對應),DDD極力推崇該架構。系統(tǒng)的領域模型是系統(tǒng)最為重要的部分,而其他的諸如DB,UI,緩存,消息隊列等等均通過適配器與領域?qū)舆M行通信,也就是依賴關系是由外到內(nèi)的(依賴倒置)。之前在商家規(guī)?;\營項目中也嘗試過使用DDD來進行架構設計。
? 整潔架構
Bob綜合六邊形架構和其他幾個架構的特點提出了整潔架構,它具有以下幾個特點:
- 獨立于框架
- 可被測試
- 獨立于UI
- 獨立于
數(shù)據(jù)庫
- 獨立于任何外部機構
架構最內(nèi)部是業(yè)務實體,代表了系統(tǒng)關鍵業(yè)務邏輯。
再外一層是用例——特定應用場景下的業(yè)務邏輯,在Bob看來,用例是架構設計中非常重要的一環(huán),架構也是基于這些用例進行設計的,如果缺失了用例,就無從談起架構設計了。
緊接著是控制器,網(wǎng)關和展示器,是一層接口適配器。
最外層則是框架和驅(qū)動程序,這里面包含了數(shù)據(jù)庫,
web,工具等等,這一層一般只包含了一些通信的黏合性代碼。
也并不一定只有四層,真實的情況可能會超過四層,但總的原則不變:外層依賴內(nèi)存,最內(nèi)部是最通用、最高層的策略,最外層是最具體的實現(xiàn)細節(jié)。
外層的是底層組件,內(nèi)層的是高層組件,底層組件作為高層的插件存在,換句話說外層的這個組件是可以被其他的組件像插件一樣替換掉的。
這里需要明確的一點是:有時候,軟件運行的方向與依賴的方向是不同的(這個很多同學可能會沒有注意到)。比如業(yè)務實體從數(shù)據(jù)庫取數(shù)據(jù),運行方向是業(yè)務實體->數(shù)據(jù)庫,而通過依賴反轉(zhuǎn)(DI,業(yè)務實體定義查詢接口,數(shù)據(jù)庫層實現(xiàn)),我們的依賴關系是業(yè)務實體<-數(shù)據(jù)庫。這里講到的保持底層組件對高層組件的依賴,高層組件的穩(wěn)定性和抽象性,也就是前面講的SDP和SAP。
# 總結
本文從代碼和組件的原則講起,講到組件內(nèi)和組件間的關系,以及如何進行組件的分層和隔離,接著引出了架構相關的討論,列舉了六邊形架構和整潔架構,并談了一些自己的理解。
后續(xù)會結合自身做過的項目,談一談具體的一些架構模式。
參考:
《架構整潔之道》 Robert C. Martin
《領域驅(qū)動設計》 Eric Evans
《企業(yè)應用架構模式》Martin Fowler
聲明:免責聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻自行上傳,本網(wǎng)站不擁有所有權,也不承認相關法律責任。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,請發(fā)
送郵件至:operations@xinnet.com進行舉報,并提供相關證據(jù),一經(jīng)查實,本站將立刻刪除涉嫌侵權內(nèi)容。本站原創(chuàng)內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時
需注明出處:新網(wǎng)idc知識百科