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