说在前面
工作中经常会遇到这样的场景:
帮忙把小贝拉门店 商品金额在5w以内,产康订单最多95折。
帮忙把圣贝拉门店 开业时间在6个月内,折扣低于7折要发起审批
帮忙把宁波太平洋店设置独立合同模板
帮忙把节假日成本变成1000
...
有些三五月变一次,有些一月变一次,有些一周变几次,每次修改都是去巴拉代码,不胜其烦。
系统中的规则分散在各各PRD,有些人脑袋里也存了一份,但是随着人员的迭代,需求的迭代,最新永远散落在各初的代码中。这会带来诸多问题:
学习成本高
维护成本高
开发成本高
测试成本高
于是有了创建一个规则的管理与运营平台,以及支撑规则的解释与执行的框架的想法。用以消除样板式代码,解放生产力,让规则的变化变得的简单。
RuleLink的定位
RuleLink是一款可视化表达式引擎。致力于解决业务开发过程中,规则变化成本高, 规则管理分散,维护成本高,学习成本高,大量样板式代码等一系列的问题。
RuleLink 是基于 Aviatorscript 实现 规则的解析,基于开源项目 "rule-engine-builder-ui"实现表达式生成与渲染。(我只是代码搬运工,所以取名非全自研)
基于当下我们的业务规则量,RuleLink并未使用性能更好的 Rete算法,而是使用了传统的模式匹配。
为什么要建RuleLink
除了前面介绍的之外,另一个原因则是想弥补一下上一份的工作中的一点点小遗憾。
可视化表达式引擎的建设有两个难点:
表达式的解释与执行
表达式的生成与解析
第1点相对简单,于是在1月份时,捣鼓了一些基础代码,做了一些尝试,当时还因为部署Drools 的workbench 搞得停服20分钟,拿了人生第一C绩效。
到4月份接到这样一个需求,某业务线不同订单订单需要接不同的聚合支付账号。因为原来就换过一次了,为了支持快速切换,我们开始在Q1的代码基础做了一些调整,开始有了RuleLink的雏形。后来又陆续接入了一些场景:
新业务支付支持不同主体
订单折扣配置
...
更多的场景的接入,让可视化配置的需求变得比较必要。于是开始正式着手构建RuleLink,解决这一类的问题。
整体结构
目前主要使用Aviatorscript 解析表达式,并支持SpE
这是从其他复制的一张图(忘记出处),因为和自己的场景几乎一模一样,就直接引用了。
存储模型
Rule_Scene 规则场景
定义场景,目前场景是固定,现在只支持支付,未来有新场景再加入
RuleFactObj 事实对象
RuleFactObjfield 事实对象字段
定义场景下的事实字段,主要用于将来前端可可视化操作。
RuleBase 规则库
定义规则,目前只支持表达式(未来考虑支持 特定脚本,比如groovy),目前只是定义规则命中返回 简单或者复杂数据类型,未来可以考虑执行某个运行(action)。
日期格式处理
日期格式原来的实现是SpEL的方式,这会引发一些问题,所以修改了其源码并重新编译生成支持Aviatorscript 支持的自定义函数方式
1 /** 2 * @Author: jijunjian 3 * @CreateTime: 2023-08-25 17:52 4 * @Description: 自定义函数初始化 5 */ 6 @Component 7 @Slf4j 8 public class CustomFunctionInitializer implements ApplicationContextAware { 9 10 @Override 11 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 12 log.info("初始化自定义函数"); 13 AviatorEvaluator.addFunction(new StringToTimestamp()); 14 log.info("初始化自定义函数完成"); 15 } 16 17 /** 18 * 自定义函数 19 * 时间字符串转时间戳 20 */ 21 class StringToTimestamp extends AbstractFunction { 22 @Override 23 public AviatorObject call(Map<String, Object> env, 24 AviatorObject arg1, AviatorObject arg2) { 25 String timeString = FunctionUtils.getStringValue(arg1, env); 26 String format = FunctionUtils.getStringValue(arg2, env); 27 // 10位时间戳 28 long timeStamp = DateUtil.parse(timeString, format).getTime()/1000; 29 return AviatorLong.valueOf(timeStamp); 30 } 31 32 @Override 33 public String getName() { 34 return "string_to_timestamp"; 35 } 36 } 37 }