最近正好公司里有个需求,一个短信业务接了多个第三方供应商,某些业务需要查询第三方供应商剩余的短信包数量去选择剩余量最多的渠道去批量发送。有些业务是指定了某个短信供应商,有些场景需要根据业务的值去动态判断该用哪个供应商。场景非常复杂,还经常变化。

以前的代码实在惨不忍睹,选择剩余量最多的渠道是一个个去查的,然后获得结果去比较。至于指定的供应商和根据业务的值去判断选择供应商则是用大量的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下来玩耍。

https://github.com/bryan31/message-demo

顺便放下LiteFlow的Gitee的仓库地址,大家可以关注下这款国产规则引擎

https://gitee.com/dromara/liteFlow

写出个灵活的系统竟然可以如此简单!小白也能写出高级的Java业务!的更多相关文章

  1. 初学shell,为了练习sed,写了个简单的批量修改文件名的脚本,后来执行时发现系统竟然自带有一个rename命令,顺便也记下了

    1 #!/bin/bash   2 <<Comment   3 批量修改文件名的脚本   4 2015/10/24   5 webber   6 Comment   7 ARGS=2   ...

  2. 烧写u_boot系统和linux系统

    今天下午准备烧写一下u_boot还有linux系统,因为是笔记本电脑,吐槽一下,笔记本电脑的usb转串口不是怎么稳定,dnw下对应的驱动也不怎么好用,导致在笔记本电脑上烧写系统的成功率比较低,本来三点 ...

  3. 迅为iMX6Q/PLUS开发板烧写设备树内核 Qt 系统

    迅为iMX6Q 和 iMX6PLUS 两个硬件版本,设备树镜像的烧写方法以及镜像所在目录,镜像名称全部一致. 如果用的是 iMX6Q 版本,想要烧写设备树版本镜像,请使用 iMX6Q 设备树版本的光盘 ...

  4. C#写的NoSQL开源项目/系统(系列)

    http://www.cnblogs.com/unruledboy/archive/2013/01/07/CSharpNoSQL.html 闲扯 好久没写开源项目了,也没写对新开源项目的介绍,今晚看了 ...

  5. Linux系统下MongoDB的简单安装与基本操作

    这篇文章主要介绍了Linux系统下MongoDB的简单安装与基本操作,需要的朋友可以参考下   Mongo DB ,是目前在IT行业非常流行的一种非关系型数据库(NoSql),其灵活的数据存储方式,备 ...

  6. C# L该系统的应用istView简单的图像浏览器

    最近有同学问我怎么用ListView加载图像列表,在...前面"C#系统中的应用"于TreeView+ListView+ContextMenuStrip控件实现树状图显示磁盘文件夹 ...

  7. 简单明朗的 RNN 写诗教程

    目录 简单明朗的 RNN 写诗教程 数据集介绍 代码思路 输入 and 输出 训练集构建 生成一首完整的诗 代码实现 读取文件 统计字数 构建word 与 id的映射 转成one-hot代码 随机打乱 ...

  8. Davinci 可视化系统部署安装及简单使用

    Davinci 是一个目前比较热门的国内开源BI系统,功能比较完善,各种可视化效果也挺不错.主要获取数据的方式是通过编写SQL 创建数据视图来展示各种图表的. Davinci面向业务人员/数据工程师/ ...

  9. CI中的控制器中要用model中的方法,是统一写在构造器方法中,还是在每一个方法中分别写

    Q: CI中的控制器中要用model中的方法,是统一写在构造器方法中,还是在每一个方法中分别写 A: 建议统一写,CI框架会自动识别已经加载过的类,所以不用担心重复加载的问题 class C_User ...

随机推荐

  1. 【面试普通人VS高手系列】Redis和Mysql如何保证数据一致性

    今天分享一道一线互联网公司高频面试题. "Redis和Mysql如何保证数据一致性". 这个问题难倒了不少工作5年以上的程序员,难的不是问题本身,而是解决这个问题的思维模式. 下面 ...

  2. 使用Proftpd支持FTP/SFTP权限管控

    简介 FTP 文件传输协议,FTP由FTP服务器(存储文件)和FTP客户端(通过FTP协议访问FTP服务器上的资源)组成 传输方式 主动模式(Port) 客户端与服务器端的TCP 21端口建立连接 - ...

  3. VDO虚拟数据优化

    VDOVirtual Data Optimize 虚拟数据优化 是一种通过压缩或删除存储设备上的数据来优化存储空间的技术. VDO 是红帽公司收购了 Permabit 公司后获取的新技术,并与2019 ...

  4. vue - Vue脚手架

    今天的内容vue脚手架,越来越有内味了,也慢慢地开始有点难度了哈哈,但是没有关系,慢慢学慢慢琢磨,我倒是感觉有点越来越像node了,不知道怎么回事,这是要向后端发展的节奏啊 一.初始化Vue脚手架 1 ...

  5. Dapr 不是服务网格,只是我长的和他很像

    概述 我们快速看一遍官方文档:https://docs.dapr.io/concepts/service-mesh/#how-dapr-and-service-meshes-compare ,看看 D ...

  6. 简单的 useState 实现

    简单的 useState 实现 本文写于 2020 年 10 月 21 日 以下是一段非常简单的 React 代码: const App = () => { const [n, setN] = ...

  7. 个人冲刺(四阶段)——体温上报app(一阶段)

    任务:完成了后台数据库的类模块 MyDBHelper.java package com.example.helloworld; import android.content.Context; impo ...

  8. asp.net6 blazor 文件上传

    微软在asp.net6中给blazor新增了一个IJSStreamReference的接口. 我们今天的所有内容,都要依赖这个接口,因为它可以把流直接传到c#中,这样我们就可以做很多的骚操作了. 今天 ...

  9. python常用标准库(时间模块 time和datetime)

    常用的标准库 time时间模块 import time time -- 获取本地时间戳 时间戳又被称之为是Unix时间戳,原本是在Unix系统中的计时工具. 它的含义是从1970年1月1日(UTC/G ...

  10. 【Java】在IDEA中将Javafx项目打包成为可运行的.jar文件

    在使用Javafx制作一个图形化界面程序的时候,我遇到了打包文件的难题. 按照网上给出的解决方案构建出来的jar文件总是没有办法运行. 以下是我最终的解决方案. 我使用的IDE是IntelliJ ID ...