01 簡介
在業務開發的過程中,往往存在平臺程式碼和業務程式碼耦合嚴重難以分離、業務和業務之間程式碼交織缺少拆解的現象。因此不論從程式碼質量,還是從團隊協作的角度來看都嚴重地影響了開發團隊之間的協同效率和開發效率,最終影響到了使用者體驗和業務發展。在閒魚,商品釋出和編輯功能也是如此。本文將以閒魚商品釋出和編輯功能的改造為例,向大家展示閒魚是如何解決此類問題。
02 釋出編輯功能的升級改造
為了實現上述目標,針對釋出和編輯功能,進行了兩輪升級。第一輪的目標在於“平臺和業務分離、業務和業務隔離”;而第二輪將更進一步,目標在於“系統之間的解耦合,提升團隊協同效率”。
1 平臺和業務分離,業務和業務隔離
第一輪改造中,閒魚將原先的商品釋出和編輯功能從老應用中抽取到了新應用item。為了實現“平臺和業務分離、業務和業務隔離”的目標,閒魚自研了一套技術框架SWAK,具體請參考文章《業務程式碼解構利器--SWAK》,該文介紹了其設計思想和實現原理。接入SWAK框架後,平臺邏輯和業務邏輯得到了分離,各個業務(如租房業務、免費送業務)之間的邏輯也不再耦合,而是變成package隔離(當然也是可以做成jar包隔離)。
看一看改造後的應用情況示意圖:
我們根據釋出和編輯的主幹流程,抽象了17個SWAK擴充套件點。
釋出和編輯的主幹流程主要就是對這些擴充套件點的編排。主幹流程的編寫並不需要考慮業務上怎麼實現這些擴充套件點的。
我們根據不同的業務(在SWAK裡面更準確的表述是tag)對這些擴充套件點進行了各自的實現。
根據這樣的開發方式,我們可以把開發同學分成如下的兩種角色:
各業務開發人員。各業務開發人員主要是負責各個業務相關的程式碼。在item應用裡面,業務同學需要維護其業務中和釋出編輯相關的個性化業務邏輯。
主幹開發人員。主幹的人員只需要維護主幹的程式碼,尤其是擴充套件點的抽象。隨著不同業務的不斷接入,原先的擴充套件點也需要隨之調整。
經過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.
簡而言之就是人員組織結構和系統結構之間的一致性。而完成系統之間的解耦合又恰恰是符合康威定律的。這一輪的改造,我們稱之為“業務服務化”。
首先,我們把租房業務給單獨抽取出來。原先的帖子和拍賣業務暫時沒有獨立的團隊來予以維護(但也基本上沒有什麼新需求)因此暫時仍然放在主幹應用中,時機合適將會和租房應用一樣遷移出去。03 業務服務化改造方案
其次,租房業務通過遠端服務的方式給主幹應用提供服務。介面即是主幹業務的提供的擴充套件點。由於現在是優先使用遠端服務來連線主幹應用和垂直應用,考慮到效能問題和安全問題,我們在擴充套件點的定義上也做了一些特殊的改動,後文會有針對性的詳述。
最後,SWAK框架做了一些改變以註冊和呼叫遠端服務。相對於本地服務,遠端服務一般都是有超時、連線異常等問題。然而不同介面對於這些異常情況其處理策略也是截然不同的,後文“SWAK框架的針對性改進”會詳述這些改動。
通過這種方式,我們將主幹應用和各業務應用徹底分離了。仍然以租房業務為例,租房團隊負責開發和維護租房業務的獨立應用rent。租房個性化的釋出和編輯需求只需要開發和部署rent應用,而不必修改主幹應用。主幹應用只由主幹團隊的同學負責維護,不會被其他業務團隊的同學所開發和部署,穩定性更加能得以保障。各業務系統獨立開發、獨立部署。這些都大幅地減少了不必要的溝通成本、提升協同效率。
主幹應用和業務應用是通過薄薄的一層介面所聯絡起來的,這層薄薄的介面都是“宣告”:Interface定義、DO的定義和擴充套件點的預設Reduce策略定義。
1 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表示式進行設定。
2 減少擴充套件點數量
眾所周知,RPC呼叫相對於本地呼叫會增加一部分的網路傳輸和序列化開銷。對於單次呼叫來說,增加若干ms並沒有什麼問題,但對於呼叫10次、20次或更多,這筆開銷就相當可觀而應該引起重視了。為此,如何降低RPC開銷,是一個必須要考慮的問題。
最可靠的方法就是降低RPC的次數。
在實踐中我們發現,很多擴充套件點實際上都是獲取業務配置。如在閒魚業務中,“是否支援多庫存”就是一種配置,如租房不支援多庫存。這些業務配置項是由其業務形態所決定的,基本不會變動。因此可以將一組配置項打包一起呼叫,並且可以快取下來,也可以直接由主幹應用進行維護。在item應用裡,這些配置項關係到主幹的通用儲存過程,目前由各業務方委託主幹開發人員進行維護,目前配置在主幹環境。可以通過阿里的動態配置平臺(如Switch、Diamond)進行動態修改。
另外我們對部分鄰接的擴充套件點進行了合併。這些相鄰擴充套件點之間的邏輯比較簡單,且不會中斷主流程。通過“配置型介面”和“鄰接擴充套件點合併”這兩種操作,我們將擴充套件點數量降低由17個降低到了6個。要注意的是,擴充套件點並不是越少越好。擴充套件點越少,越意味著“過度擬合”,可能會對後續業務變更無法適應導致主幹需要大幅改動,因此需要在數量和擴充套件性之間找到一個平衡。
另外值得一提的是,SWAK為配置型擴充套件點做了相應的小改造,並提供了檢視當前配置型擴充套件點返回值的視覺化介面。開發人員可以直觀地瞭解當前各個業務的配置值。
3 介面物件定義和細節設計
在閒魚,各種業務所需要儲存的東西大同小異,從閒魚的釋出介面上來看就不難發現這一點,都是在基礎物件(如標題、描述、圖片)之外新增一些業務相關的資料,如拍賣業務中指定拍賣的開拍時間等資訊,免費送業務中設定兌換幣值,圖書業務上設定條形碼。即對一本圖書進行拍賣當然也是允許的,這就出現了拍賣業務和圖書業務疊加起來的複合型業務。
對於主幹應用開發人員來說,應該提供單個介面以支援所有業務型別,這樣不用每次修改或者新增業務時都需要提供新介面。從穩定性的角度考慮,這樣的要求很合理。既然是單個介面,那麼DO的定義也應該統一。以商品DO為例,有以下三種方式:
第一種是繼承型結構,該結構不適用於業務疊加的情況。另外主幹需要知曉各個業務的DO,每次業務修改或新增,主幹都需要做變動。
第二種是組合型結構,適用於業務疊加的情況,但同上一種一樣,主幹需要知曉各個業務的DO,每次業務修改或新增,主幹也需要隨之變動。
第三種使用了Map型別類承載各個業務(biz)的定義型別。主幹完全不知道、也不需要知道各個業務DO是如何組成的。這種方式具有最好的擴充套件性(有點無邊界的擴充套件),也分離了主幹應用和業務應用,最接近主幹和業務分離的期望。最終我們選擇了這一種。
使用第三種的物件模型,以新加一種業務為例,其開發流程是:
新業務服務端開發人員和客戶端開發人員約定各業務的DO,這些DO會儲存到bizMap欄位。主幹應用開發人員不需要了解這些約定。
主幹應用新增一份新業務的配置,實際上是新業務的識別資訊和路由資訊。
新業務應用實現主幹擴充套件點。
聯調、測試和上線。
業務應用在擴充套件點返回值中設定需要更新的資料,由主幹應用合併。業務應用不應該也不可以直接修改ItemDO,避免影響其他業務的處理邏輯。對於釋出和編輯這種需要持久化儲存的邏輯來說,必須要強控各業務對ItemDO的修改,否則理論上來說,各業務都有可能將所有的關鍵欄位修改得面目全非。前面提到的“配置型介面”中,就有這樣的配置——該業務是否可以修改屬性欄位、該業務是否可以修改描述欄位等配置。
04 總結&規劃
SWAK框架依然在繼續演進,如部分擴充套件點原則上可以通過並行處理或非同步化處理來提升效能,但暫時還沒有提供支援。在這兩次改造中, 我們還在測試用例的採集、回放、監控告警等方面也有很多積累,敬請關注公眾號期待後續的文章分享。