關(guān)于組件化鏈路設(shè)計(jì)的分享
編者按:本文作者是支付寶技術(shù)部 ATeam 后端工程師肆合。支付寶技術(shù)部 ATeam 旨在挑選優(yōu)秀技術(shù)應(yīng)屆生,通過(guò)集中「精兵輔導(dǎo)」+「項(xiàng)目歷練」,打造支付寶技術(shù)的高潛作戰(zhàn)力量團(tuán)隊(duì),培養(yǎng)支付寶技術(shù)部未來(lái)的核心人才梯隊(duì)!在這里我們以公司級(jí)核心戰(zhàn)略項(xiàng)目為重要練兵場(chǎng),圍繞平臺(tái)基建/數(shù)智化專(zhuān)項(xiàng)/業(yè)務(wù)戰(zhàn)役,通過(guò)多維技術(shù)成長(zhǎng)體系助力同學(xué)全面成長(zhǎng),打造高精尖戰(zhàn)力的“未來(lái)之星”!
提高代碼代碼可讀性、復(fù)用性、可擴(kuò)展性,從而提高開(kāi)發(fā)體驗(yàn)和效率是基礎(chǔ)素養(yǎng)。減少重復(fù)代碼,對(duì)重復(fù)代碼進(jìn)行抽象、下沉,遵守設(shè)計(jì)原則,應(yīng)用設(shè)計(jì)模式,都有一個(gè)共同的目的:發(fā)現(xiàn)變化,封裝變化,提高代碼的可復(fù)用性,減少需求變化影響的范圍,從而使軟件、系統(tǒng)、云服務(wù)、網(wǎng)站等能夠可控的修改與升級(jí),具有更長(zhǎng)的生命周期。
一、最常見(jiàn)的三層架構(gòu)
以我們平常接觸較多的三層架構(gòu)開(kāi)始:biz-core-common。

在開(kāi)發(fā)的過(guò)程中,從三層架構(gòu)的角度考慮,最簡(jiǎn)單的認(rèn)識(shí)便是對(duì)于業(yè)務(wù)邏輯,在 biz 層去編排處理。而對(duì)于一些與業(yè)務(wù)邏輯無(wú)關(guān),可復(fù)用的邏輯,從業(yè)務(wù)邏輯中抽離出來(lái),在 core 層進(jìn)行處理。底層的模型、DAO 方法、外部服務(wù)的門(mén)面等,放在 common 層處理。這樣對(duì)于新增的業(yè)務(wù),可以復(fù)用 core 層的通用方法。?
這是最簡(jiǎn)單的理解與要求,在這之上,很多應(yīng)用都根據(jù)自身的特點(diǎn),從不同的角度用不同的方法提高代碼的可復(fù)用性。本文則介紹一種組件化鏈路設(shè)計(jì)思想。
二、為什么要用組件化鏈路
假設(shè)這樣一種場(chǎng)景,有一種產(chǎn)品,處理的邏輯很復(fù)雜,代碼很長(zhǎng)。隨著業(yè)務(wù)的發(fā)展,這個(gè)產(chǎn)品衍生出很多其它具有類(lèi)似性質(zhì),但處理鏈路各有差異的產(chǎn)品。此時(shí),怎樣去設(shè)計(jì)代碼的結(jié)構(gòu),才能更好地提高代碼的復(fù)用性呢?
想象一個(gè)產(chǎn)品,對(duì)于一個(gè)業(yè)務(wù)流程,就要涉及復(fù)雜的邏輯,要經(jīng)歷十余種不同的業(yè)務(wù)處理階段,還要考慮一些不影響主鏈路的弱依賴邏輯。隨著業(yè)務(wù)的發(fā)展,又衍生出了不同的產(chǎn)品,彼此之間的邏輯有相似但又有差異。例如對(duì)于一個(gè)專(zhuān)門(mén)處理碼的系統(tǒng),各種各樣的條碼、二維碼、在線碼、離線碼,需要定制的邏輯就有百余種。每一種碼的每一個(gè)流程之間都有差異性,但也有共性,若不做特殊處理,每一種碼都在 biz 層編排業(yè)務(wù)邏輯,復(fù)用 core 層的方法,其開(kāi)發(fā)的復(fù)雜性也是很大的,并且隨著產(chǎn)品的持續(xù)增多,代碼中的分支會(huì)越來(lái)越多,影響理解、維護(hù)與新增。
那么如何最大化的把通用邏輯抽離出來(lái),提高其復(fù)用性、可維護(hù)性以及可拓展性呢?組件化鏈路的設(shè)計(jì)是其中的一種方式。
三、組件化鏈路設(shè)計(jì)
3.1 組件化鏈路思想
對(duì)于涉及多個(gè)業(yè)務(wù)處理階段的邏輯,若每個(gè)階段之間有所聯(lián)系,但又彼此獨(dú)立,便可以把每個(gè)流程抽象成一個(gè)個(gè)節(jié)點(diǎn),對(duì)于一個(gè)特定業(yè)務(wù)流程而言,每一個(gè)子流程都可作為一個(gè)節(jié)點(diǎn),依次執(zhí)行流程節(jié)點(diǎn),傳遞節(jié)點(diǎn)參數(shù)即可。如下

當(dāng)我們把所有業(yè)務(wù)處理的子流程都抽象成節(jié)點(diǎn)以后,每一個(gè)業(yè)務(wù)流程的處理便可以通過(guò)節(jié)點(diǎn)間的排列組合去完成。如
NodeWrapper 便是每個(gè)子流程抽像出來(lái)的節(jié)點(diǎn),對(duì)于一個(gè)完整的業(yè)務(wù)處理流程xxxProcess,只需要去依次處理每個(gè)節(jié)點(diǎn)的業(yè)務(wù)邏輯就好了。通過(guò)這樣的方式,便可以通過(guò)一個(gè)統(tǒng)一的流程處理方法,執(zhí)行所有組件化的業(yè)務(wù)流程,如下所示。
通過(guò)業(yè)務(wù)上下文 context 保存每個(gè)節(jié)點(diǎn)執(zhí)行的結(jié)果并向下傳遞,首先執(zhí)行 nodeProcess 主鏈路強(qiáng)依賴的節(jié)點(diǎn),節(jié)點(diǎn)的執(zhí)行結(jié)果影響鏈路推進(jìn),而后 extensionProcess 執(zhí)行弱依賴的節(jié)點(diǎn),執(zhí)行失敗并不影響主流程。這樣我們對(duì)于業(yè)務(wù)邏輯的處理就變?yōu)榱烁鶕?jù)傳參識(shí)別業(yè)務(wù)身份->執(zhí)行對(duì)應(yīng)流程->返回業(yè)務(wù)執(zhí)行流程上下文 context。而對(duì)于業(yè)務(wù)流程的處理,都只用在xml配置一下 node,便可復(fù)用現(xiàn)有的邏輯。?
更進(jìn)一步地,對(duì)于 ProcessModel 以及 NodeWrapper,我們應(yīng)該怎么設(shè)計(jì)才能承載上述的功能實(shí)現(xiàn)?
3.2 流程節(jié)點(diǎn)的具體設(shè)計(jì)
3.2.1 節(jié)點(diǎn)設(shè)計(jì)
ProcessModel 與 NodeWrapper 之間的關(guān)系如下

processModel 內(nèi)部其實(shí)就是一堆節(jié)點(diǎn),是一種一對(duì)多的關(guān)系,將強(qiáng)依賴的主鏈路節(jié)點(diǎn)與弱依賴節(jié)點(diǎn)分開(kāi),如下
自然,節(jié)點(diǎn)執(zhí)行的能力要交予節(jié)點(diǎn)自己,一個(gè)節(jié)點(diǎn)的內(nèi)部參數(shù)如下
一個(gè)節(jié)點(diǎn)會(huì)有節(jié)點(diǎn)名稱(chēng) nodeName、真正的執(zhí)行節(jié)點(diǎn) node、以及參數(shù)轉(zhuǎn)換的 nodeParametersMaping。每個(gè)參數(shù)的設(shè)計(jì)都有其特殊的考慮,首先是業(yè)務(wù)節(jié)點(diǎn)名稱(chēng),這個(gè)參數(shù)有什么意義呢?其實(shí)是在業(yè)務(wù)執(zhí)行的過(guò)程中,可能會(huì)有分支鏈路的出現(xiàn),根據(jù)不同的結(jié)果,可能會(huì)跳過(guò)一些節(jié)點(diǎn)。此時(shí)便可以指定下一個(gè)執(zhí)行節(jié)點(diǎn)的 nodeName,實(shí)現(xiàn)節(jié)點(diǎn)跳轉(zhuǎn)的功能。?
對(duì)于 ProcessNode 來(lái)說(shuō),它才是嚴(yán)格意義上的執(zhí)行節(jié)點(diǎn)。
processNode 是一個(gè)接口,每個(gè)節(jié)點(diǎn)給予其具體實(shí)現(xiàn)。此處的入?yún)⑹荕ap<String, Object> params,上文已經(jīng)說(shuō)到,節(jié)點(diǎn)處理是通過(guò) context 傳遞的,但對(duì)于每個(gè)節(jié)點(diǎn)而言,并不需要所有 context 的所有參數(shù)。所以對(duì)于下一節(jié)點(diǎn)執(zhí)行的時(shí)候,只需通過(guò) context 取出需要的部分執(zhí)行即可。這引發(fā)了下一個(gè)問(wèn)題,如何設(shè)計(jì)一種通用的方法,去轉(zhuǎn)換參數(shù)呢,這就是 NodeParametersMaping所要做的。
這里 contextToNode 便是用于從 context 中取出某些參數(shù),轉(zhuǎn)換成節(jié)點(diǎn)入?yún)⒌呐渲谩?br>
nodeToContextMapping 便是節(jié)點(diǎn)執(zhí)行結(jié)果放進(jìn) context 的配置。那這里為什么是一個(gè) Map<String, NodeToContextMaping> 類(lèi)型的數(shù)據(jù)呢,主要是因?yàn)楣?jié)點(diǎn)執(zhí)行可能成功可能失敗,失敗的結(jié)果也有很多,此處主要是根據(jù)不同的執(zhí)行結(jié)果,取出相應(yīng)的 NodeToContextMaping,往 context 里填充數(shù)據(jù)。NodeToContextMaping 如下
這里的 processMethod 便是用于指定下一個(gè)執(zhí)行節(jié)點(diǎn)的,是執(zhí)行下一個(gè)執(zhí)行節(jié)點(diǎn),還是跳轉(zhuǎn)節(jié)點(diǎn)。nodeToContext 便是將節(jié)點(diǎn)執(zhí)行結(jié)果放進(jìn) context 的配置文件,不同的執(zhí)行結(jié)果會(huì)有不同的配置。該配置也是事先在 xml 中配置實(shí)現(xiàn)的
以上述為例,當(dāng) key=1 時(shí),說(shuō)明發(fā)生錯(cuò)誤,該節(jié)點(diǎn) processMethod 是ERROR_NODE 不需要向下執(zhí)行。當(dāng) key=3 時(shí),將執(zhí)行結(jié)果中的 result 放入context 中的 result,并且 processMethod 是默認(rèn)值,執(zhí)行下一節(jié)點(diǎn)。?
至此,關(guān)于節(jié)點(diǎn)的設(shè)計(jì)思路便結(jié)束了。通過(guò)這樣的設(shè)計(jì),節(jié)點(diǎn)執(zhí)行的順序、不同的分支邏輯都可以覆蓋到了,并且對(duì)于新增業(yè)務(wù)邏輯,在 xml 中配置即可,不需要重新編排復(fù)雜的業(yè)務(wù)邏輯。此時(shí)又有一個(gè)問(wèn)題,context 和 node 間具體傳遞參數(shù)的邏輯是怎樣實(shí)現(xiàn)的呢?
3.2.2 參數(shù)轉(zhuǎn)換的實(shí)現(xiàn)
context 是一個(gè)業(yè)務(wù)執(zhí)行上下文,上下文類(lèi)內(nèi)中包含了所有執(zhí)行過(guò)的節(jié)點(diǎn)結(jié)果,那是如何設(shè)計(jì)一個(gè)通用的轉(zhuǎn)換方法,使得只需要在 xml 中配置參數(shù)轉(zhuǎn)換 map,就可以實(shí)現(xiàn)節(jié)點(diǎn)參數(shù)與 context 之間的參數(shù)轉(zhuǎn)換的呢?以一個(gè) context 中取參數(shù)轉(zhuǎn)換 node 入?yún)⒌?case 為例,一個(gè) contexToNode 的定義如下
這里的配置的 key:modelInfo.index,表示的含義是從 context 中取值,相當(dāng)于取出 context.modelInfo.index,而 value:index 表示的含義是節(jié)點(diǎn) node 的入?yún)?params 的 key。大概的含義是 params.put(index, context.modelInfo.index)。具體的實(shí)現(xiàn)方式為
通過(guò) PropertyUtils.getProperty() 方法,取出 context.modelInfo.index 的值,再通過(guò) PropertyUtils.setProperty() 方法,將值放入 params 當(dāng)中。從而實(shí)現(xiàn) params.put(index, context.modelInfo.index) 的作用。
四、總結(jié)
組件化鏈路是一種解決節(jié)點(diǎn)間復(fù)用問(wèn)題的設(shè)計(jì)思想,在老鏈路的基礎(chǔ)上,提高了代碼復(fù)用性、可維護(hù)性以及可拓展性。某種特定的方法并不是最重要的,最重要的還是提高代碼復(fù)用性,解耦的思想。