隨着業務渠道及產品的增加,你的代碼是否開始陷入IF-ELSE組成的泥潭,難以脫身?

持續增加的渠道特性
小碼同學一來到新公司,就負責起了一個新開始,但具有無限想象空間的后台開發項目。就像所有的互聯網項目一樣,業務變化極其迅速,為了減少初期試錯成本,小碼同學選用了流行、便捷的貧血模型,也就是Service+DAO/RPC結構,做了簡單的關注點分離——業務以及基礎設施(存儲/遠程服務)的分離。
業務很給力,主要流程模式已逐漸成型,同時也增加了很多的營銷渠道,有公司內部的 App、有公眾號、小程序、H5,也有各類外部的合作伙伴的渠道,小碼同學一直都在高負荷地工作着,完全來不及思考要怎么優雅地解決這些渠道增加帶來的問題。然而,每個渠道會有一個渠道相關的小特性,這意味着在 登錄、注冊、做業務等等各個Service里,每增加一個渠道時,都要增加一段關於渠道判斷的IF-ELSE判斷語句。量變引起質變,當渠道加到近十個時,小碼懵逼了,理清代碼邏輯脈絡變得極為困難,因為看一遍代碼,需要將要受到近十個不同渠道分支代碼的干擾。同時代碼也變得難以並行開發,多個渠道的拓展會因為同一個Service的修改而更容易發生沖突。

其實這里小碼可能會采取另外一種做法,陷入另外一種困境,小碼同學每增加一個渠道就將原來的代碼復制一份,然后針對渠道進行簡單的修改,然后就可以安全高效地完成業務需求了。然而復制代碼一時爽,一直復制一直爽,當我們需要修改一些渠道公共實現,理清不同渠道實現的區別並修改時,近十個渠道就會讓我們就變得痛不欲生了。

公用邏輯下沉解決方案
小碼同學想了下,無論多忙都要從這個困境中破局,於是他想出了以下方案:

將公共的邏輯下沉,將各個渠道特有的判斷及邏輯都上提。如此一來我們可以從代碼中分離渠道和公用業務邏輯——要理解渠道特性,我們可以從渠道所在模塊(微服務/package/service)的代碼得知,如果要理解通用的信息,則到公共
業務邏輯層查看對應的實現。
但若要使用本解決方案解決目前系統的問題,則需要引入大量的重構,因為該實現需要將大量已有存在的渠道邏輯變更其發生的邏輯時間點,這需要大量的開發及測試人力支持。
擴展點解決方案
於是小碼同學開始在網上搜索相關的解決方案,了解到阿里有個可以解決類似問題的框架實現COLA,並以此為參考開展了自己的擴展點設計
https://github.com/alibaba/COLA
其引入了一種名為 擴展點/插件 的機制(擴展點是一個Interface,擴展點實現為interface的一個特定實現),讓我們可以達到以下效果:

要實現這個效果,強制我們把相關業務語義顯式化,例如 通用業務邏輯Service在沒有引入擴展點前,寫的校驗身份的代碼為
if(is渠道B) {
渠道B的一大堆代碼進行身份校驗
} else {
默認實現的一大堆代碼進行身份校驗
}
而引入擴展點后,在通用業務邏輯Service寫的則是
校驗身份擴展點.執行校驗();
其顯式化了業務語義,並像 公用邏輯下沉 的解決方案一樣 分離了主干的代碼邏輯和特定實現的代碼邏輯,還能保證原有特定渠道邏輯執行的相對位置不變。
在擴展點機制的支持下我們只需要定下規范——在通用業務邏輯層不能出現任何關於Channel渠道的IF-ELSE判斷,這樣就可收獲大量有基礎抽象的通用業務邏輯代碼,提高識別基礎抽象的能力。
擴展點解決方案的本質
擴展點本質上就是個帶有自動路由功能的策略模式,其根據業務上下文信息,自動推斷出應該選用哪個具體的擴展點實現。

擴展點的機制和原理簡單,使用也很簡單,但其給業務系統代碼帶來卻是變革性的。
多維擴展問題的出現
小碼同學利用擴展點已經阻止了渠道增加帶來的代碼腐化加劇問題,小碼以保守起見:
- 對於沒有任何業務變化的代碼保持不變
- 對於新增的渠道使用擴展點來防止代碼進一步腐化
- 對於存在業務變動的代碼,在測試資源的支持下,利用擴展點重構原有代碼,令代碼變得新鮮
但新的問題出現了,項目中也有一個與渠道類似的,會不斷擴展實現的概念——產品。
在小碼的系統中,每增加一個產品也是按照類似先前增加渠道的形式,以IF-ELSE完成擴展。於是,小碼希望直接套用之前的擴展點機制,然而事情並沒有這么順利。
在上一幅圖中,contextCode為
companyY.channelB
其表征的是渠道維度的身份信息,我們以該信息為依據,來匹配最適合的渠道相關的實現。與此類似,我們需要引入產品維度的身份信息,同時也要將不同維度的contextCode加以區分,然而COLA的實現中並不支持此類多維擴展實現,於是小碼開始設計了自己的ConextCode及ImplementCode規范,增加了維度標識符:
channel:companyY.channelB
product:companyY.ProductO.subProductE
通過維度標志,小碼分開了不同維度的擴展點以及其對應的實現,解決了大部分的問題。
多維擴展點沖突問題
引入多維擴展點后,大多數擴展實現都在各自的維度良好運行,井水不犯河水。然而,有少數擴展點卻出現了需要多維同時決定實現的場景,如:
當前若是渠道A且是產品X的情況下需要使用特定的擴展實現。
目前基於維度隔離的擴展點感覺無法支持此類需求,於是,小碼繼續查找相關資料,了解到了阿里TMF2.0框架的實現:
https://segmentfault.com/a/1190000012541958
TMF2.0按文中介紹,其為一個二維的擴展點實現,這兩維分別是:
- 行業維度
- 一個行業維度的代碼即為TMF2.0的業務身份
- 其等價於小碼之前設計的沒帶維度標志的contextCode
- 產品維度
- 與本文中的產品設定完全不同,請勿套入理解
與小碼之前設計的維度隔離擴展點不同,TMF2.0中行業維度與產品維度會共享擴展點,然而當出現擴展點沖突時,TMF2.0會以業務身份為線索,通過可視化界面配置特定業務身份在遇到擴展實現沖突時應該選擇哪一個擴展實現,並將其固化成配置,運行時依據配置選擇最終擴展實現。
然而該設定並不符合項目的現狀,相關UI的開發設計也是一個巨大的工程,因此小碼設計了另外一種折中的設定:
- 擴展點依然按維度隔離
- 當出現擴展點路由需要多維信息決策時,采用嵌套形式
- 不同擴展點不同維度的嵌套的次序,都按照固定的次序進行
如下圖所示

這個設定的實現雖然繁瑣,但是實際情況下,出現多維共同干預擴展實現選擇的情況應該相對少,相對於擴展點維度隔離得到的好處,其應該可以接受。
舒心的小碼
擴展點的理論簡單易用,其使得
- 業務主流程代碼擁有基礎抽象,使得脈絡清晰明了
- 各個渠道、產品的特性高度內聚於各自的package中
- 業務主流程成熟后,新增的產品、渠道再也不需要動業務主流程代碼,只需增加新的渠道包及產品包
- 業務主流程代碼沒變動,減少了全局風險,減少了測試量
- 擴展只涉及對應的渠道、產品的增加,這天然隔離了不同開發組的工作,提高了並行度
從此小碼和他的小伙伴們從此擺脫了996,與基友們過上了幸福快樂的生活。
作者簡介
多年金融行業經驗,現為某Top2互聯網銀行高級搬磚工,曾在兩家TOP3股份制商業銀行及一家互金創業公司工作(架構、核心業務主程),EasyTransaction作者,歡迎關注個人公眾號,在這里我會分享日常工作、生活中對於架構、編碼和業務的思考

