最近团队在做一些Visa
、Master
卡的交易风控,运营团队提供了一些交易风控的规则,比如针对卡号MCC设置单笔交易限额,24小时交易限额,72小时交易限额等等,还有触发风控规则是否拦截交易还是只发告警邮件等等等。
虽然写各种条件判断也能实现,但是随着后面规则增加,维护成本也会越来越高,所以想尝试引入规则引擎,同时考虑到开发和学习成本,还是决定学习轻量级的Easy Rules
。
Easy Rules是一个Java规则引擎,它提供了规则抽象,通过触发条件和触发后的行为去创建规则。还提供了规则引擎API,通过这些API可以基于一系列的规则去判断规则是否触发,以及触发后执行什么动作。
核心特性:
(MVEL、SPEL和JEXL)
定义规则。相关依赖如下:
<!--Easy Rule-->
<!--核心库-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<!--组合规则支持-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.1.0</version>
</dependency>
<!--SPEL表达式语言支持-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-spel</artifactId>
<version>4.1.0</version>
</dependency>
<!--MVEL表达式语言支持-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.1.0</version>
</dependency>
大多数的业务规则可以通过如下定义来描述:
Easy Rules
中的规则由Rule接口来代表,如下:
public interface Rule extends Comparable<Rule> {
/**
* 判断规则是否应该被触发,true-是,false-否
*/
boolean evaluate(Facts facts);
/**
* 规则触发后执行的行为
* @throws Exception 执行时触发的异常
*/
void execute(Facts facts) throws Exception;
}
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
/**
* 编程式规则定义
* @author Nick Liu
* @date 2023/8/3
*/
public class ProgrammaticHelloWorldRule implements Rule {
@Override
public boolean evaluate(Facts facts) {
return facts.get("enabled");
}
@Override
public void execute(Facts facts) throws Exception {
System.out.println("Hello World");
}
@Override
public int compareTo(Rule o) {
return 0;
}
public static void main(String[] args) {
// 定义事实
Facts facts = new Facts();
facts.put("enabled", true);
// 注册编程式规则
Rules rules = new Rules();
rules.register(new ProgrammaticHelloWorldRule());
// 使用默认规则引擎根据事实触发规则
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
}
备注:运行程序控制台会输出
Hello World
。
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
/**
* 声明式规则定义
* @author Nick Liu
* @date 2023/8/3
*/
@Rule(name = "Hello world rule", description = "Always say hello world")
public class DeclarativeHelloWorldRule {
@Condition
public boolean when(@Fact("enabled") boolean enabled) {
return enabled;
}
@Action(order = 1)
public void then(@Fact("enabled") boolean enabled) throws Exception {
System.out.println("Hello World");
}
@Action(order = 2)
public void finalAction(Facts facts) throws Exception {
System.out.println("Final Hello World");
}
public static void main(String[] args) {
Facts facts = new Facts();
facts.put("enabled", true);
Rules rules = new Rules();
rules.register(new DeclarativeHelloWorldRule());
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
}
控制台运行结果如下:
Hello World
Final Hello World
在Easy Rules中,事实由Fact
类来定义,如下:
public class Fact<T> {
private final String name;
private final T value;
}
事实有name
和value
两个属性,两者都不能为空,且name
属性值充当命名空间的角色需要唯一。
下面是定义事实的例子:
Fact<String> fact = new Fact("foo", "bar");
Facts facts = new Facts();
facts.add(fact);
Facts facts = new Facts();
facts.put("foo", "bar");
备注:两者方式都定义了一个name
为foo
,value
为bar
的事实实例,第二种方式更加简洁。
Easy Rules
提供了两种规则引擎的实现:
Easy Rules
规则引擎支持下面参数配置:
参数名称 | 参数类型 | 必选 | 默认值 |
---|---|---|---|
rulePriorityThreshold | int | 否 | Integer.MAX_VALUE |
skipOnFirstAppliedRule | boolean | 否 | false |
skipOnFirstFailedRule | boolean | 否 | false |
skipOnFirstNonTriggeredRule | boolean | 否 | false |
skipOnFirstAppliedRule
: 当规则被触发并且成功执行行为后是否跳过下条规则。skipOnFirstFailedRule
: 当判断规则是否触发抛出异常或者触发成功但行为执行后抛出异常是否跳过下条规则。skipOnFirstNonTriggeredRule
: 当规则未被触发是否跳过下条规则。rulePriorityThreshold
: 如果规则优先级超过默认阈值,则跳过下条规则。参数配置示例如下:
RulesEngineParameters parameters = new RulesEngineParameters()
.rulePriorityThreshold(10)
.skipOnFirstAppliedRule(true)
.skipOnFirstFailedRule(true)
.skipOnFirstNonTriggeredRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
通过下面的代码可以获取规则引擎参数:
RulesEngineParameters parameters = myEngine.getParameters();
DefaultRulesEngine
默认规则引擎的使用示例前面已经有提到过,下面我们看下InferenceRulesEngine
推理规则引擎的代码示例。
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Facts;
/**
* @author Nick Liu
* @date 2023/8/5
*/
public class HighTemperatureCondition implements Condition {
@Override
public boolean evaluate(Facts facts) {
int temperature = facts.get("temperature");
return temperature > 25;
}
}
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Facts;
/**
* @author Nick Liu
* @date 2023/8/5
*/
public class DecreaseTemperatureAction implements Action {
@Override
public void execute(Facts facts) throws Exception {
int temperature = facts.get("temperature");
System.out.printf("Current temperature: %d, It's hot! cooling air...%n", temperature);
facts.put("temperature", temperature - 1);
}
}
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.InferenceRulesEngine;
import org.jeasy.rules.core.RuleBuilder;
/**
* @author Nick Liu
* @date 2023/8/5
*/
public class AirConditionLauncher {
public static void main(String[] args) {
Facts facts = new Facts();
facts.put("temperature", 30);
// 通过规则构建API定义规则
Rule rule = new RuleBuilder()
.name("Air Condition Rule")
.when(new HighTemperatureCondition())
.then(new DecreaseTemperatureAction())
.build();
Rules rules = new Rules();
rules.register(rule);
// 基于事实重复应用规则的推理规则引擎,直到规则不再满足
RulesEngine rulesEngine = new InferenceRulesEngine();
rulesEngine.fire(rules, facts);
}
}
控制台输出结果如下:
Current temperature: 30, It's hot! cooling air...
Current temperature: 29, It's hot! cooling air...
Current temperature: 28, It's hot! cooling air...
Current temperature: 27, It's hot! cooling air...
Current temperature: 26, It's hot! cooling air...
备注:可以看到定义的规则会持续触发,直到
temperature
的值为25。