(資料圖片)
阿里妹導讀
本文主要講述我們?nèi)绾瓮ㄟ^一個主干業(yè)務流程承接多個業(yè)務場景并在數(shù)據(jù)上可適配到多端型多場景,實現(xiàn)在服務端高質(zhì)量高效率的“包接口”。
一、背景
前臺業(yè)務同學在業(yè)務承接過程中總是抱怨大部分業(yè)務無法通過設計模式來承接,寫的代碼是越來越?jīng)]有追求,理由是我無法預測未來的業(yè)務的發(fā)展,且設計模式更多的是在框架或中間件中使用。然而設計模式是對能力抽象出的通用模式,從哲學的角度來看世間萬物皆塵土,事物都是可以抽象出共同的本質(zhì)的東西。所以,難道只有底層能力可以抽象,業(yè)務邏輯部分就不可以抽象了?必須可以才是啊。
在前臺業(yè)務承接過程中除了能力可以抽象,還有可以抽象出業(yè)務流程,假設在有這樣一些業(yè)務場景,品搜和圖搜、直播間評論和點贊、公域直播會場和私域商詳透直播等等,這些各領域內(nèi)的業(yè)務流程“大同小異”,因此都可以抽象出通用的業(yè)務流程節(jié)點。
但是通常在一個主干流程需要承接的場景有很多,比如直播間互動這個主干流程包括了直播間評論、點贊、求講解、看證書、進場等等場景,所以我們需要通過主要流程進行進行多場景承接。但是這樣還不夠,在面對多端型多場景的情況下需要處理返回不同的數(shù)據(jù)模型。
綜上所述,我們?nèi)绾瓮ㄟ^一個主干業(yè)務流程承接多個業(yè)務場景并在數(shù)據(jù)上可適配到多端型多場景,實現(xiàn)在服務端高質(zhì)量高效率的“包接口”,下面會詳細介紹。
二、業(yè)務承接
如果你面臨的問題是在同一個業(yè)務域上承接多種類似的業(yè)務場景,每天在適配各種端型或者各種場景而對外提供接口時,為了保證應用系統(tǒng)的可擴展性和高效承接業(yè)務,那么可以按照如下步驟進行設計。
2.1 業(yè)務流程抽象
首先需要進行業(yè)務建模,抽象出用戶用例或者user story,當然具體的粒度可以自己把控,具體如下:
2.1.1 用戶用例
在直播間互動領域內(nèi)的用戶用例如下:
?
從整個系統(tǒng)出發(fā),挖掘出面向不同的用戶提供的能力有哪些,在這些用例背后需要進行的流程和節(jié)點又是什么。通過這些流程和節(jié)點才能進行后續(xù)的系統(tǒng)時序和流程抽象,舉例如下
在互動領域內(nèi)的流程和節(jié)點如下:
風控檢查
評論持久化
評論上屏
評論進溝通
2.2.2 系統(tǒng)時序
基于用戶用例進行分析,這些用例都需要經(jīng)過什么的流程節(jié)點進行處理,然后將這些流程按照系統(tǒng)時序進行呈現(xiàn)。
到此基于上述的用例和時序是不是可以抽象出具體互動流程了,顯而易見。
2.2.3 業(yè)務流程抽象
有了系統(tǒng)用例和系統(tǒng)時序這一步就比較簡單,從系統(tǒng)時序里很容易可以抽象出具體的流程和流程中的處理節(jié)點,具體如下:
對于直播間互動領域可以抽象出的業(yè)務流程:風控檢查->互動內(nèi)容持久化-》消息上屏-》互動進IM
對于直播間分發(fā)領域可以抽象出的業(yè)務流程:直播推薦-》直播間基礎信息-》直播流信息-》直播間品信息
到此,大家可以按照上述步驟在大腦里對自己的業(yè)務域進行抽象出來了。
2.2.4 設計模式固化主流程
按照業(yè)務主流程可以通過模板模式將處理流程固定下來,如下所示:
@Override@LiveLog(logResult = true)public InteractionResult interactionSubmit(MobileInteractionRequest request, InteractionLiveRoom liveRoom) { Boolean needSave = MapUtils.getBoolean(request.getExtInfo(), LiveInteractionConstant.NEED_SAVE); // 默認保存 InteractionResult saveResult = null; if (Objects.isNull(request.getExtInfo()) || Objects.isNull(needSave) || needSave) { saveResult = save(request, liveRoom); if(Objects.nonNull(saveResult) && !saveResult.isSuccess()) { return saveResult; } } // 默認進溝通 InteractionResult chatResult; if (Objects.isNull(request.getSendToChat()) || Boolean.parseBoolean(request.getSendToChat())) { chatResult = sendToChat(request); if(Objects.nonNull(chatResult) && !chatResult.isSuccess()) { return chatResult; } } if(Objects.nonNull(saveResult) && saveResult.isSuccess()) { return saveResult; } return null;}/** * 互動行為保存到數(shù)據(jù)庫或者緩存中 * * @param request * @return */protected abstract InteractionResult save(MobileInteractionRequest request, InteractionLiveRoom liveRoom);/** * 進溝通 * * @param request * @return */protected abstract InteractionResult sendToChat(MobileInteractionRequest request);2.2 業(yè)務流程擴展
因在上述模版模式中預留了兩個擴展點,所以在子類中可以通過擴展點進行擴展,舉例如下:?
?
如果有更多的場景就需要擴展實現(xiàn)上述兩個擴展點進行擴展即可,這樣保證了業(yè)務的高效承接。這里會有兩個問題:
在程序運行時如何根據(jù)具體的場景選擇哪個子類進行邏輯處理
如何進行適配端和場景返回的數(shù)據(jù)模型
針對第一個問題,其實就是如何去if else的問題,這里也給出比較經(jīng)典的方案:
枚舉法
表驅(qū)動法
策略模式+工廠模式
其中枚舉法和表驅(qū)動法比較簡單易用,原理就是將映射關(guān)系封裝在枚舉類或本地緩存中,這里簡單介紹下如何通過策略模式消除if else。
// 策略接口public interface Opt { int apply(int a, int b);}// 策略實現(xiàn)類@Component(value = "addOpt")public class AddOpt implements Opt { @Autowired xxxAddResource resource; // 這里通過Spring框架注入了資源 @Override public int apply(int a, int b) { return resource.process(a, b); }}// 策略實現(xiàn)類@Component(value = "devideOpt")public class devideOpt implements Opt { @Autowired xxxDivResource resource; // 這里通過Spring框架注入了資源 @Override public int apply(int a, int b) { return resource.process(a, b); }}// 策略處理@Componentpublic class OptStrategyContext{ private Map總結(jié)偽代碼:
// 抽象類固定業(yè)務流程 預留擴展點public abstract class AbstractXxxx { doXxx(Object context) { // 節(jié)點1 doNode1(context); // 節(jié)點2 doNode2(context); // 節(jié)點3 doNode3(context); // 節(jié)點n ... } // 擴展點1 protected abstract Result doNode1(Object context); // 擴展點2 protected abstract Result doNode2(Object context); // 擴展點3 protected abstract Result doNode3(Object context);} // 策略處理public class OptStrategyContext{ private Map2.3 多場景多端型適配
上面我們只是通過模版模式抽象出了主干業(yè)務流程,但是如何適配不同的端型和不同的場景,返回不同的數(shù)據(jù)模型呢,這里有兩種答案,一種是模版模式、另一種是“棒棒糖”模式,下面逐一介紹。
2.3.1 模版模式適配
既然是模版模式,這里的主干流程又是什么呢?主要跟我們解決的問題有關(guān)系,按照2.1中的流程步驟,可以抽象出固定的流程為:請求入?yún)⑻幚?》業(yè)務邏輯處理-》結(jié)果返回處理。
其中業(yè)務邏輯處理可以選定為2.2中介紹的通過策略模式選擇業(yè)務擴展的子類,來處里業(yè)務部分;請求入?yún)⒑徒Y(jié)果返回處理部分可以設置為擴展點,供子類擴展。具體偽代碼如下:
// 抽象類固定業(yè)務流程 預留擴展點 適配多端型多場景public abstract class AbstractSceneAdapter {注:因要適配不同端型不同場景返回不同的數(shù)據(jù)模型,所以上述偽代碼中主流程最終返回的結(jié)果是一個泛型,在子類實現(xiàn)的時候進行確定具體返回的類型。
2.3.1 棒棒糖模式適配
通過模版模式來適配時會有一個小問題,當需要有多個請求入?yún)⑻幚砥骰蛘叨鄠€結(jié)果包裝器的時候需要在模版里增加處理節(jié)點,但其實這些節(jié)點是有共性的可抽象出來的。因此可以針對入?yún)⑻幚砥骱徒Y(jié)果包裝器定義單獨的接口,需要多個處理器時同時實現(xiàn)接口進行處理。然后這些實現(xiàn)類打包放在單獨的類中依次執(zhí)行即可。當然其中的業(yè)務處理部分也可以定義接口動態(tài)實現(xiàn)。偽代碼如下:
// 入?yún)⑻幚砥鱬ublic interface IRequestFilter<> { void doFilter(T t);}// 結(jié)果包裝器public interface IResultWrapper三、接口設計
基于上述兩種設計模式來適配時我們的接口又該如何設計,是設計面向通用的業(yè)務層接口還是面向定制化的業(yè)務接口,兩種方式各有優(yōu)缺點:
優(yōu)點 | 缺點 | |
通用業(yè)務接口 | 1,擴展性強 2,業(yè)務承接效率高,無需頻繁發(fā)布代碼 | 1,業(yè)務邊界不清晰,問題排查效率低 2,接口數(shù)據(jù)冗余 |
定制化業(yè)務接口 | 1,業(yè)務邊界清晰 2,接口出入?yún)?shù)據(jù)無冗余 | 1,無擴展 2,頻繁包接口發(fā)布代碼 |
對于接口提供者來說肯定不希望頻繁改動代碼發(fā)布代碼,但是又希望能夠在業(yè)務承接過程中能夠高效適配多端型多場景,因此這里總結(jié)了下接口設計原則:
1、對于越底層的接口應該越通用,例如HSF接口、領域服務、中間件提供的接口;
2、對于越上層的接口應該越定制化,例如對于不同的UI適配、不同的場景適配等;
3、對于業(yè)務領域內(nèi)的接口應該通用化,例如直播業(yè)務域的分發(fā)領域、互動領域內(nèi)的接口盡可能的通用化;
四、總結(jié)
在承接業(yè)務過程中會面臨頻繁包接口、一個view層的數(shù)據(jù)模型充滿了小100個屬性,系統(tǒng)的擴展性遇到瓶頸,這些問題除了通過平臺化配置化的能力來解決,但是回歸到代碼本身我們?nèi)稳豢梢酝ㄟ^抽象的設計模式來解決。
基于抽象的理論達到復用、高內(nèi)聚低耦合,降低系統(tǒng)復雜度的目標,設計模式不只是用在底層能力或中間件中,在業(yè)務承接過程中亦有大的功效。
千萬不要為了用設計模式而刻意使用設計模式,帶來的效果適得其反,在選擇設計模式時也要三思,落地后再改動成本將會巨大。
在前臺業(yè)務開發(fā)中,需要劃分主各個業(yè)務領域,在領域中抽象出該業(yè)務的處理流程,基于流程可設計相關(guān)的擴展和編排能力,方式有很多種,包括SPI、設計模式、DSL等,本文主要通過模版模式和棒棒糖模式來解決問題。
接口設計應該按照越底層越通用,越上層越定制化的原則進行設計,當然在業(yè)務域內(nèi)的接口應盡可能的通用話。
阿里云開發(fā)者社區(qū),千萬開發(fā)者的選擇
阿里云開發(fā)者社區(qū),百萬精品技術(shù)內(nèi)容、千節(jié)免費系統(tǒng)課程、豐富的體驗場景、活躍的社群活動、行業(yè)專家分享交流,歡迎點擊【閱讀原文】加入我們。