Mestar 平台 - 规则引擎

0. 为什么要有规则引擎

所有的业务功能全部由代码实现,任何逻辑的调整或者变更都需要变动代码,没有办法添加自己的控制,没有办法动态编译和部署,hard code 的代价非常大。

想要对业务过程可配置化,将业务决策从应用程序代码中分离出来, 并使用预定义的语义模块编写业务,有以下方式。

实现方式 1:脚本语言,完全类开发语言,强大灵活,但过于笨重,只能开发人员掌控;

实现方式 2:规则引擎,通过配置实现业务规则的组合,只需要掌握有限语法,轻量安全,可以由实施人员进行;

1. 规则引擎使用

1.1 规则引擎原理

规则引擎包括约束规则,触发规则。

没有规则引擎之前,从 controller 到 service 都是同步调用与返回,所有业务实现与控制全部通过代码方式,不能加以控制与判断;

引入规则引擎之后,从 service 返回到 controller 之前会加入触发规则埋点,约束规则埋点,并将当前 service 的操作业务作为事件类型,操作内容作为消息发送到规则引擎。规则引擎中根据注册事件类型是否启用,判断埋点是否生效,如果禁用,则 service 直接返回;如果生效,则约束规则的内容会同步执行并影响 service 的返回结果;触发规则的内容会先行写入消息历史,供消息引擎异步处理。

1.2 规则引擎功能节点

事件类型:定义系统中进行了埋点处理的业务类型,系统初始化时预制好,用户可以对事件类型进行启用 / 禁用操作;

规则测试:查询当前系统缓存的触发规则与约束规则,用于判断有些场合规则执行与预期不一致,检查缓存数据是否有影响;

方法注册:定义规则引擎支持的业务方法,或者自定义函数,拓展操作符,系统初始化时预制好,分为基本函数,业务函数,目标对象函数,目标对象函数用于单据转换,与后台代码紧密相关;

消息对象:业务内容的载体,定义了消息的格式,可以在规则配置时取对应消息的字段内容;

触发规则:事件类型发生后,驱动的后续业务流程,对当前的业务流程不影响;

约束规则:事件类型发生时,判断当前事件是否允许发生,对当前的业务流程产生影响;

消息历史:用于检索历史消息的处理记录,以及处理结果,如果系统的消息数据量非常大,且很多业务由消息进行驱动,则消息历史和消息记录一定要进行分区表的处理;

1.3 约束规则配置

事件类型:驱动的业务操作

源对象:业务操作的内容载体

约束类型:约束规则对业务本身流程的影响

过滤规则:一个返回 boolean 的表达式,判断流程是否继续往下

约束规则:一个返回 boolean 的表达式,判断流程是否继续往下

提示信息:当过滤规则或者约束规则不通过的情况下,异常或者提示的消息,一个返回 String 的表达式

常用基本函数或重载操作符:

getSqlVal(String sql, Object… params):返回一个 SQL 结果,底层执行的是 Query.uniqueResult(),要求 SQL 只能返回几条记录

getSqlVal(String sql,String className, Object… params) 根据 SQL 和参数把查询结果,转换成要转换的类

getSqlQuery:返回一个 SQL 结果集,底层执行的是 Query.list()

getSqlString:返回一个字符串,底层执行的是 getSqlQuery,并将 List 结果用逗号拼接为一个字符串

基本语法:

函数名 (“SQL 语句, 参数用? 表示”,[ 参数 1, 参数 2…])

如果没有参数,请传入空的数组 []

示例:

  • getSqlString(“select u.login_name from UEX_STAFF_WORK sw left join PMBB_EMPLOYEE e on sw.emp_gid=e.gid left join MTS_USER u on e.operator_gid=u.gid where sw.is_delete=0 and sw.work_cell_gid=? and trunc(sw.on_date) = trunc(sysdate)”,[${sum.workCellId}])

  • getSqlVal("select cell.work_center_gid from mbb_udi udi left join pmbf_work_cell cell on cell.code = udi.short_name

where udi.code like concat(concat(?,‘-’),concat(?,‘%’))",[${obj.mrlType},${obj.type}])

  • getSqlVal(“select t.from_work_cell from umpp_line_info t where t.trx_id=? and t.work_cell_gid=? and t.type=1 and t.is_delete=0”,[${sum.trxId},${sum.workCellId}])

  • 是否空字符串 (getSqlString(“select u.login_name from UEX_STAFF_WORK sw left join PMBB_EMPLOYEE e on sw.emp_gid=e.gid left join MTS_USER u on e.operator_gid=u.gid where sw.is_delete=0 and sw.work_cell_gid=? and trunc(sw.on_date) = trunc(sysdate)”,[${sum.workCellId}]))?“admin”:getSqlString(“select u.login_name from UEX_STAFF_WORK sw left join PMBB_EMPLOYEE e on sw.emp_gid=e.gid left join MTS_USER u on e.operator_gid=u.gid where sw.is_delete=0 and sw.work_cell_gid=? and trunc(sw.on_date) = trunc(sysdate)”,[${sum.workCellId}])

  • 是否空字符串 ('')?“aa”:“bb”;

  • ${obj.state} 赋值 0

1.4 触发规则配置

2. 规则引擎调试

2.1 规则引擎执行历史

通过消息历史,判断消息是否发送,以及消费是否成功,具体的消费过程,可以通过 message.log 查看每条消息的消费记录,和每个规则的执行结果。

2.2 规则引擎测试

基本参考上面的规则示例,就可以写出我们需要的任意规则或者 SQL 查询,如果需要方便的对规则进行测试,可以通过脚本测试方便的进行操作。

如果需要测试目标对象是否正确,当前数据驱动是否正确,也可以把消息历史作为输入。

3. QLExpress 介绍

3.1 规则引擎底层技术

规则引擎采用阿里开源的 QLExpress,QLExpress 脚本引擎被广泛应用在阿里的电商业务场景,具有以下的一些特性:

  • 1、线程安全,引擎运算过程中的产生的临时变量都是 threadlocal 类型。
  • 2、高效执行,比较耗时的脚本编译过程可以缓存在本地机器,运行时的临时变量创建采用了缓冲池的技术,和 groovy 性能相当。
  • 3、弱类型脚本语言,和 groovy,javascript 语法类似,虽然比强类型脚本语言要慢一些,但是使业务的灵活度大大增强。
  • 4、安全控制, 可以通过设置相关运行参数,预防死循环、高危系统 api 调用等情况。
  • 5、代码精简,依赖最小,250k 的 jar 包适合所有 java 的运行环境,在 android 系统的低端 pos 机也得到广泛运用。

基本调用过程如下

QLExpress 详细功能