one
Recently, there is a demand in the company. One SMS service has received multiple third-party suppliers. Some businesses need to check the remaining number of SMS packages from the third-party suppliers to select the channel with the most remaining amount to send in batches. In some businesses, a certain SMS provider is specified, and in some scenarios, it is necessary to dynamically determine which provider to use based on the value of the business. The scene is very complex and changes frequently.
The previous code was really terrible. Select the channels with the most remaining amount to check them one by one, and then get the results to compare. As for the specified supplier and the selection of suppliers based on the value of the business, a large number of if elses are used to nest various judgments. Every time I see this piece of code, I feel really rough. The key is that some suppliers change frequently, and new suppliers need to replace the old suppliers and join this big code. The business judgment conditions also change from time to time.
After a few problems, the leader couldn't stand it any longer and asked me to find a way to optimize it.
I figured out the logic, and the whole relationship diagram should be like this, in which I changed the checking one by one into parallel checking, in order to save the IO time-consuming problem of serial checking.
Some of them are complex database checking logic, and I have removed the steps to judge idempotency. Only the key steps are selected and drawn.
Ask me to refactor this slowly and write it out, but the key is that each step and judgment logic change from time to time. This requires my code to be very flexible. So when designing, I have been very worried about how to design.
two
When the group held an exchange meeting, some friends from other groups and I developed an open source framework - LiteFlow .
After researching this open source framework, I found that LiteFlow is a domestic rule engine that can arrange arbitrarily complex processes and also supports hot refresh. This basically suits my needs exactly!
The document is very detailed and NICE, and it took about half a day to learn it all. The rules found in newer versions of LiteFlow are written in EL expressions. There are about 10 grammars in total, which is very easy to understand.
For example this picture:
In LiteFlow, the rules are expressed as:
<chain name="chain1">
THEN(
a,
WHEN(b, THEN(c, d)),
e
)
</chain>
Among them, THEN stands for serial, and WHEN stands for parallel execution. This syntax is easy to understand at a glance.
Look at this picture again:
The rule representation in LiteFlow is:
<chain name="chain1">
THEN(
a,
WHEN(
b,
SWITCH(c).to(d,e)
),
f
)
</chain>
The SWITCH keyword means an exclusive gateway, and the c component is a java class. It is determined whether d or e should be executed according to the execution result.
So there should be no problem with nesting multiple layers like this.
In the LiteFlow documentation, the author gives very detailed examples, as well as some complex examples, such as:
Such a complex example can be written using LiteFlow expressions:
<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>
Its expression can also define sub-variables, and the above expression can be written as:
<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>
In fact, if you look closely at the picture, you will feel that this expression is still very clear. It is more than enough to apply to my SMS system.
three
I researched it and spent 10 minutes writing out the expression rules for my process:
<?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>
I wrote it in the way of sub-variables mentioned in the documentation, which is more clear. In fact, I summed up a little trick: no matter how complex the graph is, it can be divided into a local whole, first set the local small variables, and then introduce these local variables in the main process. Anyway, the process of writing this diagram took about 10 minutes.
As for a small component. I followed the document and did it again, and changed the original big logic into small logics one by one. Encapsulated in different components, just give the corresponding Id.
Finally, it can be triggered by 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());
}
Very simple to have wood! ! !
And very elegant! ! !
three
After I changed it to the above form, each small logic block was completely decoupled. The connection of data is completely connected by 上下文
. After studying the concept of LiteFlow, I found that this concept is particularly good. Directly split the original code with strong coupling.
If the business changes now, I only need to rewrite one of the components. And components can be reused. The order between them can also be switched at will. All this, just need to change the rules file. The code is completely untouched.
I read the documentation carefully, this framework also supports completely seamless hot refresh, although my code doesn't use this feature, but it looks really amazing, the arrangement of changing rules doesn't even require restarting the application! ! ! However, I wouldn't use this feature even if I was killed. My boss told me to change my business, and I wanted to report more working hours. If this goes online, I won't be able to report more working hours. . . 🤣
LiteFlow also has many advanced features, such as implicit processes, event callbacks, declarative components, component aspects, step information, thread pool customization, private delivery, and simple monitoring. This domestic rule engine is about to play out, powerful!
The key point is to talk about the function of the script component of LiteFlow. This function was discovered after I wrote the code. I've found that it's more flexible to use script components.
Although LiteFlow supports hot refresh, it is limited to rule file changes. If your Java code changes, you have to restart.
But the script component of LiteFlow has even broken through this layer for you. You can define scripts and support groovy scripts. Now, you don't even need to restart the application to change the logic. .
Because of my selfishness above, I also will not tell the leader about this function 😅.
Four
After I refactored this project, I found that the LiteFlow framework has a lot of playable points.
Although it is officially declared to be a rule engine, it is suitable for decoupling systems and component orchestration. But I found it to be very nice to use it to do some simple asynchronous thread orchestration. I am not very proficient in multithreading myself, so it is very convenient to use this to write.
In addition to rule files, LiteFlow also supports chain assembly rules in the form of code, which is just used to write multi-threading.
For example, I want to write a multithreading example like this:
For me to use CompletableFuture to write, I really do not know. But it's easy for you to use LiteFlow. In LiteFlow, you don't need to define threads. The framework will create threads for you. You only need to turn the code in your threads into components, and then define rules with code.
It is written as follows:
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("testChain", 流程初始参数, 你的上下文.class);
In this way, this seemingly complicated thread arrangement is completed. This framework is simply good news for novices who can't write multi-threading. love love.
five
By the way, the article on the official website is super detailed. Community groups are also very active.
In order to facilitate understanding, I specially mocked the example of my text message and pushed it to the github repository. You can pull it down and play by yourself.
https://github.com/bryan31/message-demo
By the way, put down the warehouse address of LiteFlow's Gitee, you can pay attention to this domestic rule engine
https://gitee.com/dromara/liteFlow
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。