1. 程式人生 > >老代碼多=過度耦合=if else?阿裏巴巴工程師這樣捋直老代碼

老代碼多=過度耦合=if else?阿裏巴巴工程師這樣捋直老代碼

接口 字段修改 nic info 否則 執行 部分 然而 恰恰

簡介
在業務開發的過程中,往往存在平臺代碼和業務代碼耦合嚴重難以分離、業務和業務之間代碼交織缺少拆解的現象。平臺和業務代碼交織導致不易修改,不同業務的代碼交織增加了不同負責團隊之間的協同成本。因此不論從代碼質量,還是從團隊協作的角度來看都嚴重地影響了開發團隊之間的協同效率和開發效率,最終影響到了用戶體驗和業務發展。在閑魚,商品發布和編輯功能也是如此。本文將以閑魚商品發布和編輯功能的改造為例,向大家展示閑魚是如何解決此類問題,從而更有效地協同更多團隊更快更穩定地支撐各種業務的。

發布編輯功能的升級改造
為了實現上述目標,針對發布和編輯功能,進行了兩輪升級。第一輪的目標在於“平臺和業務分離、業務和業務隔離”;而第二輪將更進一步,目標在於“系統之間的解耦合,提升團隊協同效率”。

1.平臺和業務分離,業務和業務隔離
第一輪改造中,閑魚將原先的商品發布和編輯功能從老應用中抽取到了新應用item。為了實現“平臺和業務分離、業務和業務隔離”的目標,閑魚自研了一套技術框架SWAK,具體請參考文章《業務代碼解構利器--SWAK》,該文介紹了其設計思想和實現原理。接入SWAK框架後,平臺邏輯和業務邏輯得到了分離,各個業務(如租房業務、免費送業務)之間的邏輯也不再耦合,而是變成package隔離(當然也是可以做成jar包隔離)。

看一看改造後的應用情況示意圖:

技術分享圖片

我們根據發布和編輯的主幹流程,抽象了17個SWAK擴展點。
發布和編輯的主幹流程主要就是對這些擴展點的編排。主幹流程的編寫並不需要考慮業務上怎麽實現這些擴展點的。

我們根據不同的業務(在SWAK裏面更準確的表述是tag)對這些擴展點進行了各自的實現。
根據這樣的開發方式,我們可以把開發同學分成如下的兩種角色:

各業務開發人員。各業務開發人員主要是負責各個業務相關的代碼。在item應用裏面,業務同學需要維護其業務中和發布編輯相關的個性化業務邏輯。
主幹開發人員。主幹的人員只需要維護主幹的代碼,尤其是擴展點的抽象。隨著不同業務的不斷接入,原先的擴展點也需要隨之調整。
如在之前的《業務代碼解構利器--SWAK》一文中指出的一樣,經過SWAK改造後,獲得了如下的幾個優點:

代碼邏輯清晰,可變和不可變一目了然。
代碼復用度變高。
可變邏輯按照標簽進行隔離,單個標簽的實現不會影響到其他標簽的實現,降低開發和測試成本。無論是按照“類型”分還是按照類目分,對應的開發和測試同學只需要關註對應的邏輯即可。

新接手的開發人員能夠快速理解,輕松上手。
2.系統之間的解耦合,提升團隊協同效率
以租房為例——租房業務的同學需要在item應用中維護一套租房發布編輯相關的邏輯(如校驗地小區數據、地鐵數據真實性等);租房業務的同學還需要在詳情應用的邏輯中維護一套和租房詳情相關的邏輯(如展示地圖,展示內部設施標簽);租房業務的同學還需要在交易應用的邏輯中維護一套和租房交易相關的邏輯(如預約看房)等等。租房的同學不僅僅需要著手於自己的代碼邏輯,還需要修改發布和編輯應用item、還需要修改詳情應用,還需要修改交易應用......這種體驗是非常糟糕的,有極大的可能性接手一個簡單業務就需要修改和發布四五個應用。

另一方面,從主幹開發人員的角度來說,其應用不僅僅由自己或自己的小團隊來維護,還有很多業務開發人員也在修改和發布此應用,且頻率會遠遠超過主幹開發任務的發布和部署頻次(否則就是主幹擴展點邏輯抽取得不好了)。這不利於整個應用的穩定性。A業務服務掛了,應該只影響A業務,而不應該影響主幹。依此邏輯,最好能做到JVM隔離。本質上來說,第一輪改造完成了業務之間的解耦合,而第二輪則是系統之間的解耦合。

康威定律告訴我們:

Any organization that designs a system (defined more broadly here than just information systems) will inevitably produce a design whose structure is a copy of the organization‘s communication structure.

簡而言之就是人員組織結構和系統結構之間的一致性。而完成系統之間的解耦合又恰恰是符合康威定律的。這一輪的改造,我們稱之為“業務服務化”。

業務服務化改造方案
技術分享圖片

首先,我們把租房業務給單獨抽取出來。原先的帖子和拍賣業務暫時沒有獨立的團隊來予以維護(但也基本上沒有什麽新需求)因此暫時仍然放在主幹應用中,時機合適將會和租房應用一樣遷移出去。
其次,租房業務通過遠程服務的方式給主幹應用提供服務。接口即是主幹業務的提供的擴展點。由於現在是優先使用遠程服務來連接主幹應用和垂直應用,考慮到性能問題和安全問題,我們在擴展點的定義上也做了一些特殊的改動,後文會有針對性的詳述。
最後,SWAK框架做了一些改變以註冊和調用遠程服務。相對於本地服務,遠程服務一般都是有超時、連接異常等問題。然而不同接口對於這些異常情況其處理策略也是截然不同的,後文“SWAK框架的針對性改進”會詳述這些改動。
通過這種方式,我們將主幹應用和各業務應用徹底分離了。仍然以租房業務為例,租房團隊負責開發和維護租房業務的獨立應用rent。租房個性化的發布和編輯需求只需要開發和部署rent應用,而不必修改主幹應用。主幹應用只由主幹團隊的同學負責維護,不會被其他業務團隊的同學所開發和部署,穩定性更加能得以保障。各業務系統獨立開發、獨立部署。這些都大幅地減少了不必要的溝通成本、提升協同效率。

主幹應用和業務應用是通過薄薄的一層接口所聯系起來的,這層薄薄的接口都是“聲明”:Interface定義、DO的定義和擴展點的默認Reduce策略定義。

SWAK框架的針對性改進
在之前的《業務代碼解構利器--SWAK》一文中指出了,SWAK框架在應用啟動的時候會通過各種註冊器(registery)註冊框架所需的信息。其中最重要的信息就是——業務tag及其對應的SWAK接口的實現類類名或者類實例instance。大多RPC框架都會在client端提供一個代理,代理掉內部的服務發現、保活、序列化、網絡通信、反序列化等一系列操作。實際上,SWAK為了支持遠程服務調用,只需要將業務tag,以及這些RPC的client的instance的對應關系註冊進去就可以了。在閑魚,RPC使用的是阿裏通用的HSF框架(其類似的一個開源框架是Dubbo),這裏的RPC的client就是HSF中的ConsumerBean。

上文還提到了RPC調用會引入服務超時、連接異常的概念。為何要限制超時?是因為不能被單個應用的超時占據了主幹應用的服務資源而引起其他服務和整個應用系統受到影響(如大多數線程阻塞在超時調用上)。無論是超時異常還是連接異常,在業務上也有對應的處理策略。在這裏,我們定義了三種異常處理策略,通過在配置上設置相應的註解,SWAK框架會自動按照策略來處理異常。這三種策略是:

IGNORE。 即,直接向上層拋出異常。
SKIP。對於一個接口有多個tag執行的時候,本tag下該擴展點將跳過,繼續執行其他tag下該擴展點的實現。
DEFAULT_VALUE。返回默認值。默認值通過spel表達式進行設置。
減少擴展點數量
眾所周知,RPC調用相對於本地調用會增加一部分的網絡傳輸和序列化開銷。對於單次調用來說,增加若幹ms並沒有什麽問題,但對於調用10次、20次或更多,這筆開銷就相當可觀而應該引起重視了。為此,如何降低RPC開銷,是一個必須要考慮的問題。

最可靠的方法就是降低RPC的次數。

在實踐中我們發現,很多擴展點實際上都是獲取業務配置。如在閑魚業務中,“是否支持多庫存”就是一種配置,如租房不支持多庫存。這些業務配置項是由其業務形態所決定的,基本不會變動。因此可以將一組配置項打包一起調用,並且可以緩存下來,也可以直接由主幹應用進行維護。在item應用裏,這些配置項關系到主幹的通用存儲過程,目前由各業務方委托主幹開發人員進行維護,目前配置在主幹環境。可以通過阿裏的動態配置平臺(如Switch、Diamond)進行動態修改。

另外我們對部分鄰接的擴展點進行了合並。這些相鄰擴展點之間的邏輯比較簡單,且不會中斷主流程。通過“配置型接口”和“鄰接擴展點合並”這兩種操作,我們將擴展點數量降低由17個降低到了6個。要註意的是,擴展點並不是越少越好。擴展點越少,越意味著“過度擬合”,可能會對後續業務變更無法適應導致主幹需要大幅改動,因此需要在數量和擴展性之間找到一個平衡。

另外值得一提的是,SWAK為配置型擴展點做了相應的小改造,並提供了查看當前配置型擴展點返回值的可視化界面。開發人員可以直觀地了解當前各個業務的配置值。

接口對象定義和細節設計
在閑魚,各種業務所需要存儲的東西大同小異,從閑魚的發布界面上來看就不難發現這一點,都是在基礎對象(如標題、描述、圖片)之外添加一些業務相關的數據,如拍賣業務中指定拍賣的開拍時間等信息,免費送業務中設置兌換幣值,圖書業務上設置條形碼。即對一本圖書進行拍賣當然也是允許的,這就出現了拍賣業務和圖書業務疊加起來的復合型業務。
技術分享圖片

對於主幹應用開發人員來說,應該提供單個接口以支持所有業務類型,這樣不用每次修改或者新增業務時都需要提供新接口。從穩定性的角度考慮,這樣的要求很合理。既然是單個接口,那麽DO的定義也應該統一。以商品DO為例,有以下三種方式:

技術分享圖片

第一種是繼承型結構,該結構不適用於業務疊加的情況。另外主幹需要知曉各個業務的DO,每次業務修改或新增,主幹都需要做變動。
第二種是組合型結構,適用於業務疊加的情況,但同上一種一樣,主幹需要知曉各個業務的DO,每次業務修改或新增,主幹也需要隨之變動。
第三種使用了Map類型類承載各個業務(biz)的定義類型。主幹完全不知道、也不需要知道各個業務DO是如何組成的。這種方式具有最好的擴展性(有點無邊界的擴展),也分離了主幹應用和業務應用,最接近主幹和業務分離的期望。最終我們選擇了這一種。
使用第三種的對象模型,以新加一種業務為例,其開發流程是:

技術分享圖片

新業務服務端開發人員和客戶端開發人員約定各業務的DO,這些DO會存儲到bizMap字段。主幹應用開發人員不需要了解這些約定。
主幹應用新增一份新業務的配置,實際上是新業務的識別信息和路由信息。
新業務應用實現主幹擴展點。
聯調、測試和上線。
業務應用在擴展點返回值中設置需要更新的數據,由主幹應用合並。業務應用不應該也不可以直接修改ItemDO,避免影響其他業務的處理邏輯。對於發布和編輯這種需要持久化存儲的邏輯來說,必須要強控各業務對ItemDO的修改,否則理論上來說,各業務都有可能將所有的關鍵字段修改得面目全非。前面提到的“配置型接口”中,就有這樣的配置——該業務是否可以修改屬性字段、該業務是否可以修改描述字段等配置。

總結
閑魚的商品發布和編輯功能基於SWAK框架經過了兩次改造升級,第一次升級完成了平臺和業務之間的解耦合以及業務和業務之間的解耦合,第二次升級通過平臺和業務間使用RPC調用完成了系統和系統之間的解耦合。改造之後,能更有效地協同更多團隊更快更穩定地支撐各種業務。SWAK框架依然在繼續演進,如部分擴展點原則上可以通過並行處理或異步化處理來提升性能,但暫時還沒有提供支持。在這兩次改造中, 我們還在測試用例的采集、回放、監控告警等方面也有很多積累,敬請期待後續的文章分享。

老代碼多=過度耦合=if else?阿裏巴巴工程師這樣捋直老代碼