業務程式碼解構利器--SWAK
簡介
業務的不斷髮展、商品型別的不斷增多、不斷新增的業務需求使得閒魚的程式碼出現“bad smell”——平臺程式碼和業務程式碼耦合嚴重難以分離;業務和業務之間程式碼交織缺少拆解。這也是行業中的通病。為解決此類問題,閒魚自研了一套技術框架——SWAK。本文帶大家一起看看SWAK是怎麼解構閒魚程式碼的。
SWAK是Swiss Army Knife的簡稱,眾所周知,瑞士軍刀是一款小巧靈活、適用於多種場景的工具。在閒魚服務端,SWAK框架也是這樣一種小巧靈活、適用於多種場景的技術框架, 它所要使用的場景都具有同一個特點——多實現間的規則化執行。本文將以一個例子開篇,來詳細介紹其中的概念。
多實現和規則化執行
熟悉閒魚的朋友們應該知道,在閒魚App裡面,商品有豐富的表現形式,不妨叫做型別A、型別B和型別C,各種型別也可以有各自的子型別。每種型別的業務邏輯存在一定的共性,但是也存在部分差異——如在分享頁面中,subtitle欄位的展示邏輯就不盡相同:
這種單一的實現通常會被寫成如下的程式碼:
if(A型別) { if(A1型別) { doSomething1(); }else if(A2型別) { doSomething2(); } } else if(B型別) { doSomething3(); } else if(C型別) { if(C1型別) { doSomething4(); }else if(C2型別) { doSomething5(); } }
類似的程式碼大家應該都寫過不少。邏輯簡單的時候寫成這樣無可厚非,但當邏輯開始變複雜的時候這種寫法會具有較多的壞處:
- 難以抽出公共的邏輯,程式碼塊愈發臃腫。
- 有較多相同點少量異同點的新型別的實現很難複用原先的程式碼。
- 各個型別的程式碼實際上融合在一塊,更改程式碼可能會影響到其他型別,提高上線風險和測試迴歸成本。
- 對於新接手的開發人員來說,理解成本高,上手難度大,無形中降低開發效率。
按照面向物件的思想,獲取title的方式對於所有型別都是一致的,應該沉澱成平臺邏輯,而獲取subtitle就可以抽象成一個介面方法,而型別A、型別B和型別C的寶貝都具有各自的實現而已。對於 獲取subtitle這個介面方法來說,它有著多種實現。
那麼什麼是規則化執行呢?在上面的例子中,我們按照了商品的型別(type)進行了邏輯的分離,但通常情況下並非能分隔地如此徹底。舉一個例子,運營團隊的劃分可能也按照商品型別(type)做劃分,也有可能按照類目(category,如手機、3C數碼、服飾、圖書等)體系來做劃分,甚至還有可能按照地域進行劃分。那麼一個商品可能既會受到商品型別體系的約束,又會受到類目體系的約束,還會受到地域的約束。如果幾種約束不一致的話,就會產生衝突。比如subtitle欄位,從型別A的視角上來看應該顯示價格,在圖書類目的視角下或許應該透出出版社——畢竟愛讀書的人大多更關注質量而出版社是衡量質量的一個重要標準。是展示價格,還是出版社?或者都展示?如果都展示的話先展示價格還是先展示出版社?如果一行不夠放下所有內容又怎麼辦?無論是上述的哪一種展示方式,背後都是“規則”(在設計模式裡,稱之為“策略”),程式碼也無非是按照“規則”進行編寫而已。
以上的例子是多實現規則化執行的一個經典場景。類似地,如ABTest、雙寫等邏輯也是多實現規則化執行的應用場景。
基本思想
在上面的例子中,按照商品的型別或者按照商品的類目進行區分會產生衝突。其實無所謂型別或者類目,對於商品這個物件來說,無非是給其貼上了不同的標籤而已——如一個型別A的圖書類目寶貝被貼上“型別A”和“圖書”兩個標籤。“型別A”的獲取subtitle介面方法對應著一種實現,而“圖書”的獲取subtitle介面方法又對應著另一個實現。當一個物件被貼多個標籤的時候,多個標籤對應的實現就會產生衝突。
衝突的解決依賴於“規則”。“規則”最重要的兩個部分是——優先順序(Priority)和歸約(Reduce)策略;執行的先後順序由優先順序決定,而顯示第一個實現的結果、顯示第二個實現的結果還是兩個實現結果的拼接等都是歸約策略。“規則”還可以包含如“並行執行方式”和“異常處理方式”等其他組成部分。
如上,可以得出SWAK的基本思想:
- 分析物件所具有的標籤。
- 分離出不可變的邏輯和可變的邏輯。可變的邏輯抽象成介面。
- 可變的邏輯根據標籤的不同有多種實現。每種實現是獨立的,即每種實現是互相隔離的。
- 當物件同時具有多個標籤時,使用優先順序和歸約策略來解決衝突問題。
值得一提的是,SWAK的基本思想借鑑自阿里巴巴中臺的TMF架構,關於TMF的細節可以參考《盡在雙11--阿里巴巴技術演進與超越》一書的《基於TMF框架的交易平臺架構》章節。
相應地,使用SWAK框架將帶來如下的好處:
- 程式碼邏輯清晰,可變和不可變一目瞭然。
- 程式碼複用度變高。
- 可變邏輯按照標籤進行隔離,單個標籤的實現不會影響到其他標籤的實現,降低開發和測試成本。無論是按照“型別”分還是按照類目分,對應的開發和測試同學只需要關注對應的邏輯即可。
- 新接手的開發人員能夠快速理解,輕鬆上手。
實現原理
相較於執行期才進行根據標籤去掃描並載入實現類的方式,SWAK框架更傾向於在靜態期就能分析出具有某幾個標籤的物件在不同的實現方法下會有著怎樣的執行邏輯。一方面通過快取可以明顯降低響應時間,另一方面也便於在開發期間發現和排查問題。整體的實現原理可以分成兩個部分:註冊和 執行。基本流程如下:
在註冊過程中,SWAK框架將會掃描檔案(多實現介面、歸約策略、衝突優先順序採用了Java註解或者XML檔案進行了配置,下面的程式碼示例中介紹多實現介面和其實現類是如何配置的),掃描出的結果都註冊到了本地快取中,而在執行過程中SWAK框架會從本地快取中直接查詢其所需的衝突優先順序配置和歸約策略等,這樣有助於減少響應時間。另外,使用統一的本地快取有助於進行“視覺化的展現”——開發人員可以直觀地看到並分析出程式的執行流程;產品經理也可以直觀地看到哪些功能點可以方便擴充套件,哪些地方的優先順序需要更新等等,甚至有助於需求的估時和排期。使用統一的本地快取也為“視覺化的配置”提供了可能性,結合阿里內部的Diamond或者Switch框架(輕量級的開關和動態配置項管理框架),可以無需更新程式碼,僅需推送配置就可以更新衝突優先順序,為開發和測試提供了極大的便利。
/**
* 此處用一個簡單的demo演示下基本的配置,實際的業務要遠比demo複雜
*/
@SwakInterface(desc="獲取subtitle") // 使用註解宣告這是一個多實現介面
public interface SubtitleFetcher {
@SwakMethod
String fetchSubtitle();
}
@SwakTag(tags = {"tagA"}) // 使用SwakTag繫結tagA的實現
@Component
public class TagASubtitleFetcher implements SubtitleFetcher {
@Override
public String fetchSubtitle() {
return "我是TagA";
}
}
@Component
@SwakTag(tags = {"tagB"}) // 使用SwakTag繫結tagB的實現
public class TagBSubtitleFetcher implements SubtitleFetcher {
@Override
public String fetchSubtitle() {
return "我是TagB";
}
}
閒魚服務端應用基本都基於Spring框架。為了便於在服務端應用上使用SWAK框架,在設計之初,我們就要求SWAK需要100%地相容Spring框架。最終的實現版本做到了這一點,無論是業務的bean還是SWAK框架自身引入的bean,都完全由Spring容器託管。框架還使用了cglib代理了上圖裡執行過程中的一系列流程,完全由框架執行,對開發同學是完全透明、無感知的,使用起來如普通的單實現的介面一般,如下程式碼塊所示。
@Autowired
private SubtitleFetcher subtitleFetcher;
//省略大段程式碼.......
String subtitle = subtitleFetcher.fetchSubtile();
//省略大段程式碼.......
在閒魚的應用情況
目前,SWAK框架在閒魚已經在商品釋出和編輯的部分流程上得以應用,我們正在積極將SWAK框架擴充套件到到更多的流程上。下圖是基於SWAK框架的商品域核心功能的改造計劃。經過基於SWAK的升級改造,閒魚商品域核心功能按照業務隔離,各業務開發同學僅需關係其對應業務的開發即可,其通用邏輯和業務隔離由基於SWAK框架的一層和二層充分保證。程式碼質量和開發效率將獲得顯著提升。
總結
閒魚自研的SWAK這一多實現規則化執行框架,可以很好地解決平臺程式碼和業務程式碼耦合嚴重難以分離、業務和業務之間程式碼交織缺少拆解的問題。並且SWAK 100%相容Spring,使用方便,快速上手。名副其實地,SWAK框架就像瑞士軍刀一樣可以適用於多種場景,小巧方便。當然,SWAK仍在不斷進化,特性和功能仍在不斷豐富。類似地,在閒魚還有很多有意思的、創造性的嘗試。
原文連結
本文為雲棲社群原創內容,未經允許不得轉載。