寫出個(gè)靈活的系統(tǒng)竟然可以如此簡(jiǎn)單!小白也能寫出高級(jí)的Java業(yè)務(wù)!
?最近正好公司里有個(gè)需求,一個(gè)短信業(yè)務(wù)接了多個(gè)第三方供應(yīng)商,某些業(yè)務(wù)需要查詢第三方供應(yīng)商剩余的短信包數(shù)量去選擇剩余量最多的渠道去批量發(fā)送。有些業(yè)務(wù)是指定了某個(gè)短信供應(yīng)商,有些場(chǎng)景需要根據(jù)業(yè)務(wù)的值去動(dòng)態(tài)判斷該用哪個(gè)供應(yīng)商。場(chǎng)景非常復(fù)雜,還經(jīng)常變化。
以前的代碼實(shí)在慘不忍睹,選擇剩余量最多的渠道是一個(gè)個(gè)去查的,然后獲得結(jié)果去比較。至于指定的供應(yīng)商和根據(jù)業(yè)務(wù)的值去判斷選擇供應(yīng)商則是用大量的if else去嵌套各種判斷。每次看到這坨代碼真的覺得太粗糙了。關(guān)鍵是有些供應(yīng)商還經(jīng)常變,新接的供應(yīng)商需要替代舊的供應(yīng)商,加入這一大坨代碼里。業(yè)務(wù)的判斷條件還時(shí)不時(shí)變化一下。
出了幾次問題之后,領(lǐng)導(dǎo)看不下去了,叫我想辦法去優(yōu)化。
我理了理邏輯,整個(gè)關(guān)系圖應(yīng)該是這樣的,其中我把一個(gè)個(gè)去查的變成了并行去查,為了節(jié)約串行去查的IO耗時(shí)問題。
其中有些復(fù)雜查庫(kù)邏輯,判斷冪等性的步驟我就去掉了。只挑選了關(guān)鍵的步驟畫上去。
叫我去重構(gòu)這個(gè)慢慢寫也能寫出,但是關(guān)鍵的是,每個(gè)步驟和判斷邏輯還時(shí)不時(shí)變化下。這就要求我的代碼非常靈活。所以在設(shè)計(jì)時(shí),一直很苦惱該如何去設(shè)計(jì)。
在小組開交流會(huì)的時(shí)候,有其他組的小伙伴和我安利了一個(gè)開源框架-LiteFlow。
經(jīng)過研究這款開源框架,發(fā)現(xiàn)LiteFlow是一個(gè)國(guó)產(chǎn)規(guī)則引擎,能夠編排任意復(fù)雜的流程,還支持熱刷新。這基本上完全契合我的需求?。?/p>
文檔很詳細(xì),很NICE,大概看了半天就全部學(xué)完了。發(fā)現(xiàn)新版本的LiteFlow的規(guī)則是用EL表達(dá)式來寫的。語法總共數(shù)下來也就10個(gè)左右,非常好理解。
比如這種圖:
在LiteFlow用規(guī)則表示就是:
<chain name="chain1">
THEN(
a,
WHEN(b, THEN(c, d)),
e
)
</chain>
其中THEN是代表串行,WHEN代表并行執(zhí)行,這種語法,一看就很好理解。
再來看這個(gè)圖:
在LiteFlow里規(guī)則表示就是:
<chain name="chain1">
THEN(
a,
WHEN(
b,
SWITCH(c).to(d,e)
),
f
)
</chain>
其中SWITCH關(guān)鍵字就是排他網(wǎng)關(guān)的意思,c這個(gè)組件是一個(gè)java類,根據(jù)執(zhí)行的結(jié)果去選擇到底應(yīng)該執(zhí)行d還是e。
所以這樣嵌套多層也應(yīng)該是毫無問題的。
LiteFlow的文檔里作者給出了很詳細(xì)的例子,還有一些復(fù)雜例子,比如:
這種復(fù)雜的例子用LiteFlow的表達(dá)式可以寫成:
<chain name="chain1">
THEN(
A,
WHEN(
THEN(B, C),
THEN(D, E, F),
THEN(
SWITCH(G).to(
THEN(H, I, WHEN(J, K)).id("t1"),
THEN(L, M).id("t2")
),
N
)
),
Z
)
</chain>
它的表達(dá)式還可以進(jìn)行定義子變量,上述表達(dá)式又可以寫成:
<chain name="chain1">
item1 = THEN(B, C);
item2 = THEN(D, E, F);
item3_1 = THEN(H, I, WHEN(J, K)).id("t1");
item3_2 = THEN(L, M).id("t2");
item3 = THEN(SWITCH(G).to(item3_1, item3_2), N);
THEN(
A,
WHEN(item1, item2, item3),
Z
);
</chain>
其實(shí)對(duì)照?qǐng)D,仔細(xì)看,會(huì)覺得這種表達(dá)式還是很清晰的。運(yùn)用到我那個(gè)短信系統(tǒng)里是綽綽有余的。
我研究了下,花了10分鐘時(shí)間,就寫出了我那個(gè)流程的表達(dá)式規(guī)則:
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="channelSenderChain">
selectBestChannel = THEN(
WHEN(
channel1Query, channel2Query, channel3Query,
channel4Query, channel5Query, channel6Query
),
channelSelector
).id("branch1");
selectBizChannel = THEN(
biz1,
SWITCH(if_2).to(
channel3,
channel4,
SWITCH(if_3).to(channel5, channel6).id("s3")
).id("s2")
).id("branch2");
THEN(
packageData,
SWITCH(if_1).to(
channel1,
channel2,
selectBestChannel,
selectBizChannel
),
batchSender
);
</chain>
</flow>
我用了文檔中提到的子變量的方式去寫,這種寫法更加清晰。其實(shí)我總結(jié)了一個(gè)小竅門就是:再?gòu)?fù)雜的圖,都可以拆分成一個(gè)個(gè)局部的整體,先定局部的小變量,然后在主要的流程里去引入這些局部變量就可以了。反正我寫這個(gè)圖的流程就差不多10分鐘。
至于一個(gè)個(gè)小組件。我就跟著文檔里做了一遍,把原來的大邏輯改拆成一個(gè)個(gè)的小邏輯。封裝在不同的組件里,給上相應(yīng)的Id就可以了。
最后通過LiteflowExecutor觸發(fā)下就可以了。
LiteflowResponse response = flowExecutor.execute2Resp("channelSenderChain", null, BatchMessageResultContext.class);
BatchMessageResultContext context = response.getFirstContextBean();
if (response.isSuccess()){
log.info("執(zhí)行成功,最終選擇的渠道是{}", context.getFinalResultChannel());
}else{
log.error("執(zhí)行失敗", response.getCause());
}
非常簡(jiǎn)單有木有?。?!
而且特別優(yōu)雅!?。?/p>
我改成上面這種形式了之后,每一個(gè)小邏輯塊之間就完全解耦了。當(dāng)中數(shù)據(jù)的連接完全是靠上下文進(jìn)行連接的,在研究了LiteFlow的理念之后,我發(fā)現(xiàn)這理念特別好。直接把原先的耦合性特別強(qiáng)的代碼給拆分開來了。
現(xiàn)在業(yè)務(wù)有變動(dòng)的話,我只需要改寫其中一個(gè)組件就可以了。而且組件是可以拿來復(fù)用的。之間的順序也是可以隨意切換的。這一切,只需要改規(guī)則文件即可。代碼是完全不用動(dòng)的。
我仔細(xì)翻看了文檔,這框架還支持完全無縫的熱刷新,雖然我的代碼沒用到這特,但是看起來真的是太厲害了,改變規(guī)則的編排連重啟應(yīng)用都不需要?。?!不過我打死都不會(huì)用這個(gè)特性的,領(lǐng)導(dǎo)叫我改業(yè)務(wù),我還想多報(bào)點(diǎn)工時(shí),這個(gè)如果上線了,我就沒法多報(bào)工時(shí)了。。。??
LiteFlow還有很多高級(jí)特性,比如隱式流程啊,事件回調(diào)啊,聲明式組件,組件切面啊,步驟信息,線程池的自定義,私有投遞,還有簡(jiǎn)單監(jiān)控。這款國(guó)產(chǎn)規(guī)則引擎快要玩出花了,強(qiáng)大!
重點(diǎn)要說下LiteFlow的腳本組件這個(gè)功能 ,這個(gè)功能是我寫好代碼才發(fā)現(xiàn)的。我發(fā)現(xiàn),如果用腳本組件的話會(huì)更靈活。
雖然LiteFlow支持熱刷新,但也僅限于規(guī)則文件改變。你Java代碼改變,還得重啟。
但是LiteFlow的腳本組件連這層都給你捅破了,你可以定義腳本,還支持groovy腳本,這下,連改變邏輯都不用重啟應(yīng)用了。。
介于我上面的私心,我同樣也不會(huì)把這功能告訴領(lǐng)導(dǎo)。
我重構(gòu)完這個(gè)項(xiàng)目之后,發(fā)現(xiàn)LiteFlow這個(gè)框架的可玩點(diǎn)非常多。
雖然官方是宣稱是規(guī)則引擎,適用于用來解耦系統(tǒng),組件編排。但是我發(fā)現(xiàn)用它來做一些簡(jiǎn)單的異步線程編排也是非常nice的。我自己本身對(duì)多線程不太精通,用這個(gè)來寫,太方便了。
LiteFlow除了規(guī)則文件之外,還支持代碼形式的鏈?zhǔn)浇M裝規(guī)則,這個(gè)特性正好用來寫多線程。
比如,我要寫一個(gè)這樣的多線程例子:
要讓我用CompletableFuture來寫,我還真不太會(huì)。但是你用LiteFlow就很容易,在LiteFlow你無需定義線程,框架自己會(huì)為你創(chuàng)建線程,你只需要把你線程里的代碼變成一個(gè)個(gè)組件,然后用代碼定義規(guī)則就可以了。
寫法如下:
String el = "THEN(" +
" main1," +
" WHEN(" +
" THEN(c1, c2, WHEN(c3, c4))," +
" THEN(c5, c6)" +
" )," +
" main2" +
" )";
LiteFlowChainELBuilder.createChain().setChainName("testChain").setEL(el).build();
LiteflowResponse response = flowExecutor.execute2Resp("channelSenderChain", 流程初始參數(shù), 你的上下文.class);
就這樣即完成了這個(gè)看上去有點(diǎn)復(fù)雜的線程編排了。這款框架簡(jiǎn)直是對(duì)不會(huì)寫多線程小白的福音啊。愛了愛了。
順便還要說下的是,官網(wǎng)的文章超詳細(xì)。社區(qū)群也很活躍。
以下是LiteFlow的Gitee的倉(cāng)庫(kù)地址,大家可以關(guān)注下這款國(guó)產(chǎn)規(guī)則引擎
https://gitee.com/dromara/liteFlow
為了方便理解,我特地把我那個(gè)短信的例子進(jìn)行了mock后推到了github倉(cāng)庫(kù),大家可以自行pull下來玩耍。
https://github.com/bryan31/message-demo