1、是什么
- 基于java轻量级的规则引擎,学习成本更低、适用性更强
- 本质一个函数,y=f(x1,x2,…,xn)
- Easy Rules 每条规则都有一个条件(condition)和一个动作(action),简单地说,可以将其看作是一组 IF THEN 或类似SQL的when-then语句
2、作用
- 解决业务代码和业务规则分离,实现了将业务决策从应用程序代码中分离。 接受数据输入,解释业务规则,并根据业务规则做出业务决策。
业务系统在应用过程中,常常包含着要处理"复杂、多变"的部分,这部分往往是"业务规则"或者是"数据的处理逻辑"。因此这部分的动态规则的问题,往往需要可配置,并对系统性能和热部署有一定的要求。从开发与业务的视角主要突出以下的一些问题:
- 从开发人员视角来看
1)逻辑复杂,要使用大量if-else来实现,或者使用设计模式。但过于复杂的规则逻辑,使用设计模式也往往是存在大量并且关系复杂的类,导致代码难于维护,对新加入的同学极不友好。
2)变更时需要从头梳理逻辑,在适当的地方进行if…else…代码逻辑调整,耗费大量时间进行梳理。
3)开发周期较长,当需求发生变更时,需要研发人员安排开发周期上线,对于当下快速变化的业务,传统的开发工作方式显得捉襟见肘。
- 从业务人员视角来看
1)业务人员期望友好的管理界面,不需要专业的开发技能就能够完成规则的管理、发布。
2)期望能够实现热部署,由业务人员配置好之后即配即用。
3)减少业务规则对开发人员的依赖。
4)降低需求变动的时间成本,快速验证发布
3、怎么做:
你可以自己构建一个简单的规则引擎。你所需要做的就是创建一组带有条件和动作的规则对象rule,将它们存储在一个集合中rules,然后遍历它们以评估(fire)条件(condition)并执行这些动作(action)。
pom
大多数业务规则可以由以下定义表示:
-
名称name:规则命名空间中的唯一规则名称
-
说明description:规则的简要说明
-
优先级priority:相对于其他规则的规则优先级,较小的值表示较高的优先级
-
事实fact:去匹配规则时的一组已知事实
-
条件condition:为了匹配该规则,在给定某些事实的情况下应满足的一组条件
如果规则条件为true,则规则将被触发执行。否则,规则将被忽略
-
动作action:当条件满足时要执行的一组动作(可以添加/删除/修改事实)
它可以用于实现各种应用程序逻辑,例如更新数据、发送消息等。
抽象规则Rule
基础规则BasicRule
事实类Facts:map
- 扩展:可以将普通对象(包含属性|方法)facts.put()。也可以将rpc对象put进来
条件接口
动作接口
四种规则定义方式
注解方式
1、eg1(也可以实现Rule接口)
2、纯注解
RuleBuilder 链式
1、eg1
2、eg2
Mvel和Spel表达式
1、eg1:Mvel表达式
Mvel表达式
- when:对象属性
when方法参数,只能是facts对象中key对象,内部定义的属性或方法
2、补充:
- when、then内容,可以从自定义的json文件中读取并解析成Mvel表达式形式。解析过程可参考场景6
3、eg2:Spel表达式同理
Spel表达式
Yml配置
eg1: weather-rule.yml
- condition:对象方法
eg2:一个yml文件创建多个规则
- 读取多条规则
常用规则类
DefaultRule
SpELRule(Spring的表达式注入)
表达式注入
- when方法-SpELCondition
- then方法- SpELAction
组合规则
抽象CompositeRule类由一组规则组成。这是一个典型地组合设计模式的实现。可以以不同方式触发组合规则。
三种CompositeRule具体子类:
UnitRuleGroup : 要么应用所有规则,要么不应用任何规则(AND逻辑)
ActivationRuleGroup : 它触发第一个适用规则,并忽略组中的其他规则(XOR逻辑)
ConditionalRuleGroup : 如果具有最高优先级的规则计算结果为true,则触发其余规则
UnitRuleGroup
- 规则1
- 规则2
- 执行
- 输出
先输出规则2(优先级为777),规则1(优先级为默认值:2147483646),值越小,优先级越高
引擎接口
引擎抽象类
AbstractRuleEngine作用就是抽出多个引擎类共有的,不需要再各自额外重复去实现
引擎类-DefaultRulesEngine
规则引擎参数(决定规则之间是否互斥|中断|跳过等)
RulesEngineParameters
场景1- 恒打印
规则description
默认打印Hello World
规则
规则引擎:使用DefaultRulesEngine
执行
输出
fire方法执行流程
场景2-yml
1、规则description
if it rains then take an umbrella
2、定义规则
weather-rule.yml
3、自定义规则引擎:使用默认的
4、执行
场景3 简单if-else
1、功能描述
从1数到100,并且:
- 需求1:如果数字是5的倍数,则打印"i :是5的倍数"
- 需求2:如果数字是7的倍数,请打印"i :是7的倍数"
- 需求3:如果数字是5和7的倍数,请打印"i :是5的倍数、7的倍数"
- 需求4:否则打印数字本身
2、常规实现方法
3、使用Easy Rules规则引擎实现
将每个需求编写一条规则:
- rule1
- rule2
- rule3
- rule4
- 执行
-
这里规则引擎参数:skipOnFirstAppliedRule(true):告诉引擎,被触发时跳过后面的规则。
即当i = 5时,满足规则1(mod5),执行完action1,就不会再去匹配其他rule2、3、4规则了。使用场景是:各个规则之间互斥
场景4-动态规则MVEL表达式+Json字符串
如图1
输入facts:NORMAL_NUMBER和ERROR_NUMBER两个值,使用Mvel表达式解析facts是否满足上述json中定义的condition
- 操作符枚举类(加减乘除,> 、<)
- 逻辑运算符(&&、||、and、or)
- 工具类(负责解析自定义json字符串中的condition和action内容,赋值给Mvel规则when、then)
- 测试
结果分析:显然结果匹配成功。原因如图2
facts:NORMAL_NUMBER = 10、ERROR_NUMBER = 10
condition:如图1
显然NORMAL_NUMBER = 10,满足第一个条件 < 11,直接返回true。
如果我们设置fact:NORMAL_NUMBER = 12,则NORMAL_NUMBER 不满足第一个条件。
但是fact中ERROR_NUMBER = 10 <= 11满足第二个条件,直接返回True
场景5-QLExpress
1、使用阿里的QLExpress
- MetaRuleResult
2、使用EasyRules实现上述功能
- 规则1
这里的规则是原子规则
- 规则2
- 自定义组合规则
作用:这里的规则是组合规则,是原子规则的组合形式,可扩展
这里的自定义规则组合,是快速失败机制:即l&&o中如果lRule的condiotion为false,则直接失败,使用facts记录一个失败原因。也可以自定义将每个rule-condition为false的原因都记录下来
- 自定义condition-after-listeren
作用:组合规则,执行结果。成功|失败,已经失败原因
- 执行
- 扩展
1)db获取规则表达式:
先根据网店+品类+角色+修改类型,查询db获取组合规则,比如l&&o
2)工厂模式解析组合规则
然后根据l&&o,解析出规则为l和o,组合成l&&o
3)facts获取数据
自定义策略模式,key为枚举类型,value为对应的rpc查询接口
这里的queryData方法,根据规则类型o,获取对应的Rpc接口-ORGateway,然后查询or值,然后比较结果
4)组合规则中,判断每个原子规则是否执行通过,失败则记录对应执行失败原因
5)在condition-after中自定义listeren,如果组合规则condition为false,则记录组合规则整体的执行失败以及失败原因
6)如果组合规则整体执行失败,则本次结果为false
场景6- 动态规则Mvel + Json文件
1、背景
动态规则就是由于业务场景的变化,之前的规则已经不适用现在的业务场景,需要更改相对应的规则。
例如:之前是满300减30,现在是满200-30
- 正常情况下我们需要改规则类里面的比较参数代码,然后打包上线。
- 如果使用动态规则,修改一下配置中的规则json文件即可,线上代码会读取最新的规则配置文件,无需上线
2、前提说明
1)规则类中的condtion方法,可以入参传入Facts参数,然后使用facts.get()方法获取内容 ,但是规则文件(eg:json)的condtion中无法传入Facts参数,也就无法使用此参数
2) 自定义RuleListener监听会作用到所有执行的规则,如何仅处理我们指定的规则
3、场景
-
输入一个人的信息,信息中包含了这个人的学历等级,作为规则事实
- 方式一:condition中制定(推荐,可以动态配置0和11)
- @Condition修饰方法入参( @Fact(“person”) Person person )
- 方式二:将condition条件判断,自定在fact-key中对象的属性和方法中(不推荐)
-
如果学历等级符合规则,则去查询学历证书情况(集合存储)
-
查出完学历证书后,在检测学历证书与他的学历等级是否匹配,匹配规则为:
- 学历证书数量与学历等级相同
- 最大学历证书的学历等级与学历等级一致
-
匹配通过则学历真实,信息中会添加真实学历匹配结果
-
未匹配通过则学历造假嫌疑,信息中会添加造假学历信息
上线
2、前提说明
1)规则类中的condtion方法,可以入参传入Facts参数,然后使用facts.get()方法获取内容 ,但是规则文件(eg:json)的condtion中无法传入Facts参数,也就无法使用此参数
2) 自定义RuleListener监听会作用到所有执行的规则,如何仅处理我们指定的规则
3、场景
-
输入一个人的信息,信息中包含了这个人的学历等级,作为规则事实
- 方式一:condition中制定(推荐,可以动态配置0和11)
- @Condition修饰方法入参( @Fact(“person”) Person person )
- 方式二:将condition条件判断,自定在fact-key中对象的属性和方法中(不推荐)
-
如果学历等级符合规则,则去查询学历证书情况(集合存储)
-
查出完学历证书后,在检测学历证书与他的学历等级是否匹配,匹配规则为:
- 学历证书数量与学历等级相同
- 最大学历证书的学历等级与学历等级一致
-
匹配通过则学历真实,信息中会添加真实学历匹配结果
-
未匹配通过则学历造假嫌疑,信息中会添加造假学历信息
场景7-履约缺货处罚金额计算
1、背景
如果商家发生了履约缺货,则需要对商家进行罚款,罚款金额计算的逻辑如下:
这里以skuId=1,skuName=“苹果”, 供应商id=777为例
- 假设供应商777,履约缺货了3件,即less = 3 pcs
- 此sku的销售Gmv即saleGmv = 100元
- 此sku的销售件数即saleCount = 5 pcs
则需要对777这个商家,关于skuId=1的品进行罚款,罚款金额Java代码实现如下
- 缺点1
这里商家skuId=1缺货了3件,按照这个逻辑Java代码实现没有问题,加入skuid=1缺货了300件,则不能按照这个罚款的计算逻辑,计算逻辑就要重新Java代码实现,或者大量的if-else。这里仅有6中分支,加入分支很多很多,则代码变得异常臃肿。
- 缺点2
假如代码实现后,那天业务变更了计算公式的一个值,或者干脆改变了计算逻辑max->min,则只能通过代码上线。
- 使用when-then形式解决大量分支问题,而且支持动态加载,无需上线代码
实现如下
2、实现
3.监听器
通过监听器,如果then执行成功了,则一定会执行监听器的onSuccess方法。故在此再执行一遍then表达式,获取then的执行结果存入map中(这里之所以要通过监听器来存then的结果,原因是原生的MvelRule规则的execute方法返回值类型是void,它只关系then中的表达式是否执行成功,不关系表达式执行的结果)
补充:
1.调用类的带参数方法
2.调用类不带参方法
场景8:Mvel表达式when-then调用类方法
1、背景
当满足when(指定类的方法),则执行then(执行类的方法)
2、举例
- 定义类的when-then方法
- 执行
场景9:终极模式之规则引擎+职责链
我mjp愿称之为EasyRules最强实战场景2024.01.07
1、背景
综合上述场景7和8。
我们知道规则的处罚动作actions可能不止一个。除了对商家进行罚款,还可以对商家停排期,甚至可以降低商家商品的曝光度等等一系列处罚动作。
基于此,我们可以将处罚actions配置为json字符串
2、特殊化
规则1
- when是 0<less<=10
- actions是
规则2
- when是 0<less<=10
- actions是
即规则2的处罚动作只有罚款
那么问题来了,如何为每个规则,定制化处罚actions动作
答案:通过职责链模式
3、思想
- 我们代码中使用职责链模式,定义一系列的Handler处理器。
然后每一条规则的actions =》映射成List handlerList,然后遍历执行处理器。
- 因为不同的规则,处罚动作actions可能不一样,所以,其处理器handlerList也不尽相同,需要自己定制化
4、代码实现
- 业务代码执行
- 方法类Demo
- 处理器接口
-
处理器实现类
罚款处理器
停排处理器
-
根据入参定制化处理器集合
处理器接口默认都不参与,根据json的key添加谁参与
5、罚款处理器扩展
罚款处理器中,也可以使用Mvel表达式,直接计算罚款多少钱
补充:MVEL直接调用类方法
1.调用类的带参数方法
2.调用类不带参方法