寫出個靈活的系統竟然可以如此簡單!小白也能寫出高級的Java業務!
一
最近正好公司里有個需求,一個短訊業務接了多個第三方供應商,某些業務需要查詢第三方供應商剩餘的短訊包數量去選擇剩餘量最多的渠道去批量發送。有些業務是指定了某個短訊供應商,有些場景需要根據業務的值去動態判斷該用哪個供應商。場景非常複雜,還經常變化。
以前的代碼實在慘不忍睹,選擇剩餘量最多的渠道是一個個去查的,然後獲得結果去比較。至於指定的供應商和根據業務的值去判斷選擇供應商則是用大量的if else去嵌套各種判斷。每次看到這坨代碼真的覺得太粗糙了。關鍵是有些供應商還經常變,新接的供應商需要替代舊的供應商,加入這一大坨代碼里。業務的判斷條件還時不時變化一下。
出了幾次問題之後,領導看不下去了,叫我想辦法去優化。
我理了理邏輯,整個關係圖應該是這樣的,其中我把一個個去查的變成了並行去查,為了節約串行去查的IO耗時問題。
其中有些複雜查庫邏輯,判斷冪等性的步驟我就去掉了。只挑選了關鍵的步驟畫上去。
叫我去重構這個慢慢寫也能寫出,但是關鍵的是,每個步驟和判斷邏輯還時不時變化下。這就要求我的代碼非常靈活。所以在設計時,一直很苦惱該如何去設計。
二
在小組開交流會的時候,有其他組的小夥伴和我安利了一個開源框架-LiteFlow。
經過研究這款開源框架,發現LiteFlow是一個國產規則引擎,能夠編排任意複雜的流程,還支持熱刷新。這基本上完全契合我的需求啊!
文檔很詳細,很NICE,大概看了半天就全部學完了。發現新版本的LiteFlow的規則是用EL表達式來寫的。語法總共數下來也就10個左右,非常好理解。
比如這種圖:
在LiteFlow用規則表示就是:
<chain name="chain1">
THEN(
a,
WHEN(b, THEN(c, d)),
e
)
</chain>
其中THEN是代表串行,WHEN代表並行執行,這種語法,一看就很好理解。
再來看這個圖:
在LiteFlow里規則表示就是:
<chain name="chain1">
THEN(
a,
WHEN(
b,
SWITCH(c).to(d,e)
),
f
)
</chain>
其中SWITCH關鍵字就是排他網關的意思,c這個組件是一個java類,根據執行的結果去選擇到底應該執行d還是e。
所以這樣嵌套多層也應該是毫無問題的。
LiteFlow的文檔里作者給出了很詳細的例子,還有一些複雜例子,比如:
這種複雜的例子用LiteFlow的表達式可以寫成:
<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>
它的表達式還可以進行定義子變量,上述表達式又可以寫成:
<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>
其實對照圖,仔細看,會覺得這種表達式還是很清晰的。運用到我那個短訊系統里是綽綽有餘的。
三
我研究了下,花了10分鐘時間,就寫出了我那個流程的表達式規則:
<?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>
我用了文檔中提到的子變量的方式去寫,這種寫法更加清晰。其實我總結了一個小竅門就是:再複雜的圖,都可以拆分成一個個局部的整體,先定局部的小變量,然後在主要的流程里去引入這些局部變量就可以了。反正我寫這個圖的流程就差不多10分鐘。
至於一個個小組件。我就跟着文檔里做了一遍,把原來的大邏輯改拆成一個個的小邏輯。封裝在不同的組件里,給上相應的Id就可以了。
最後通過LiteflowExecutor觸發下就可以了。
LiteflowResponse response = flowExecutor.execute2Resp("channelSenderChain", null, BatchMessageResultContext.class);
BatchMessageResultContext context = response.getFirstContextBean();
if (response.isSuccess()){
log.info("執行成功,最終選擇的渠道是{}", context.getFinalResultChannel());
}else{
log.error("執行失敗", response.getCause());
}
非常簡單有木有!!!
而且特別優雅!!!
三
我改成上面這種形式了之後,每一個小邏輯塊之間就完全解耦了。當中數據的連接完全是靠上下文
進行連接的,在研究了LiteFlow的理念之後,我發現這理念特別好。直接把原先的耦合性特彆強的代碼給拆分開來了。
現在業務有變動的話,我只需要改寫其中一個組件就可以了。而且組件是可以拿來複用的。之間的順序也是可以隨意切換的。這一切,只需要改規則文件即可。代碼是完全不用動的。
我仔細翻看了文檔,這框架還支持完全無縫的熱刷新,雖然我的代碼沒用到這特,但是看起來真的是太厲害了,改變規則的編排連重啟應用都不需要!!!不過我打死都不會用這個特性的,領導叫我改業務,我還想多報點工時,這個如果上線了,我就沒法多報工時了。。。🤣
LiteFlow還有很多高級特性,比如隱式流程啊,事件回調啊,聲明式組件,組件切面啊,步驟信息,線程池的自定義,私有投遞,還有簡單監控。這款國產規則引擎快要玩出花了,強大!
重點要說下LiteFlow的腳本組件這個功能 ,這個功能是我寫好代碼才發現的。我發現,如果用腳本組件的話會更靈活。
雖然LiteFlow支持熱刷新,但也僅限於規則文件改變。你Java代碼改變,還得重啟。
但是LiteFlow的腳本組件連這層都給你捅破了,你可以定義腳本,還支持groovy腳本,這下,連改變邏輯都不用重啟應用了。。
介於我上面的私心,我同樣也不會把這功能告訴領導😅。
四
我重構完這個項目之後,發現LiteFlow這個框架的可玩點非常多。
雖然官方是宣稱是規則引擎,適用於用來解耦系統,組件編排。但是我發現用它來做一些簡單的異步線程編排也是非常nice的。我自己本身對多線程不太精通,用這個來寫,太方便了。
LiteFlow除了規則文件之外,還支持代碼形式的鏈式組裝規則,這個特性正好用來寫多線程。
比如,我要寫一個這樣的多線程例子:
要讓我用CompletableFuture來寫,我還真不太會。但是你用LiteFlow就很容易,在LiteFlow你無需定義線程,框架自己會為你創建線程,你只需要把你線程里的代碼變成一個個組件,然後用代碼定義規則就可以了。
寫法如下:
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", 流程初始參數, 你的上下文.class);
就這樣即完成了這個看上去有點複雜的線程編排了。這款框架簡直是對不會寫多線程小白的福音啊。愛了愛了。
五
順便還要說下的是,官網的文章超詳細。社區群也很活躍。
為了方便理解,我特地把我那個短訊的例子進行了mock後推到了github倉庫,大家可以自行pull下來玩耍。
順便放下LiteFlow的Gitee的倉庫地址,大家可以關注下這款國產規則引擎