Easy Rules规则引擎(1-基础篇)

目录

一、序言

最近团队在做一些VisaMaster卡的交易风控,运营团队提供了一些交易风控的规则,比如针对卡号MCC设置单笔交易限额,24小时交易限额,72小时交易限额等等,还有触发风控规则是否拦截交易还是只发告警邮件等等等。

虽然写各种条件判断也能实现,但是随着后面规则增加,维护成本也会越来越高,所以想尝试引入规则引擎,同时考虑到开发和学习成本,还是决定学习轻量级的Easy Rules


二、Easy Rules介绍

Easy Rules是一个Java规则引擎,它提供了规则抽象,通过触发条件和触发后的行为去创建规则。还提供了规则引擎API,通过这些API可以基于一系列的规则去判断规则是否触发,以及触发后执行什么动作。

核心特性:

  • 轻量级Java库,易于学习的API。
  • 注解式编程模型实现基于POJO开发。
  • 通过抽象定义业务规则并且轻松应用规则。
  • 支持通过简单规则可以创建组合规则。
  • 支持通过表达式语言(MVEL、SPEL和JEXL)定义规则。

相关依赖如下:

java 复制代码
<!--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>

三、定义规则(Rules)

1、规则介绍

大多数的业务规则可以通过如下定义来描述:

  • Name:唯一的规则名称。
  • Description:简单规则描述。
  • Priority:规则执行优先级。
  • Facts:触发规则时的一系列事实。
  • Condition:给定事实后,应该被满足的一系列条件。
  • Actions:条件满足时应该执行的一系列行为。

Easy Rules中的规则由Rule接口来代表,如下:

java 复制代码
public interface Rule extends Comparable<Rule> {

    /**
    * 判断规则是否应该被触发,true-是,false-否
    */
    boolean evaluate(Facts facts);

    /**
    * 规则触发后执行的行为
    * @throws Exception 执行时触发的异常
    */
    void execute(Facts facts) throws Exception;
}

2、编程式规则定义

java 复制代码
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

3、声明式规则定义

java 复制代码
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);
	}
}

控制台运行结果如下:

c 复制代码
Hello World
Final Hello World

四、定义事实(Facts)

在Easy Rules中,事实由Fact类来定义,如下:

java 复制代码
public class Fact<T> {
   private final String name;
   private final T value;
}

事实有namevalue两个属性,两者都不能为空,且name属性值充当命名空间的角色需要唯一。

下面是定义事实的例子:

  • 第1种方式
java 复制代码
Fact<String> fact = new Fact("foo", "bar");
Facts facts = new Facts();
facts.add(fact);
  • 第2种方式
java 复制代码
Facts facts = new Facts();
facts.put("foo", "bar");

备注:两者方式都定义了一个namefoovaluebar的事实实例,第二种方式更加简洁。


五、定义规则引擎(Rules Engine)

1、规则引擎介绍

Easy Rules提供了两种规则引擎的实现:

  • DefaultRulesEngine:默认规则引擎,根据规则的自然顺序(默认为优先级)应用规则。
  • InferenceRulesEngine:推理规则引擎,持续性应用单条规则,直到规则触发条件不满足。

Easy Rules规则引擎支持下面参数配置:

参数名称 参数类型 必选 默认值
rulePriorityThreshold int Integer.MAX_VALUE
skipOnFirstAppliedRule boolean false
skipOnFirstFailedRule boolean false
skipOnFirstNonTriggeredRule boolean false
  • skipOnFirstAppliedRule: 当规则被触发并且成功执行行为后是否跳过下条规则。
  • skipOnFirstFailedRule : 当判断规则是否触发抛出异常或者触发成功但行为执行后抛出异常是否跳过下条规则。
  • skipOnFirstNonTriggeredRule : 当规则未被触发是否跳过下条规则。
  • rulePriorityThreshold : 如果规则优先级超过默认阈值,则跳过下条规则。

参数配置示例如下:

java 复制代码
RulesEngineParameters parameters = new RulesEngineParameters()
    .rulePriorityThreshold(10)
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true)
    .skipOnFirstNonTriggeredRule(true);

RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

通过下面的代码可以获取规则引擎参数:

java 复制代码
RulesEngineParameters parameters = myEngine.getParameters();

2、InferenceRulesEngine规则引擎示例

DefaultRulesEngine默认规则引擎的使用示例前面已经有提到过,下面我们看下InferenceRulesEngine推理规则引擎的代码示例。

(1) 定义触发条件

java 复制代码
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;
	}
}

(2) 定义规则触发后的执行行为

java 复制代码
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);
	}
}

(3) 测试用例

java 复制代码
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);
	}
}

控制台输出结果如下:

bash 复制代码
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。

相关推荐
前行的小黑炭3 分钟前
设计模式:为什么使用模板设计模式(不相同的步骤进行抽取,使用不同的子类实现)减少重复代码,让代码更好维护。
android·java·kotlin
Java技术小馆8 分钟前
如何设计一个本地缓存
java·面试·架构
XuanXu1 小时前
Java AQS原理以及应用
java
风象南4 小时前
SpringBoot中6种自定义starter开发方法
java·spring boot·后端
mghio13 小时前
Dubbo 中的集群容错
java·微服务·dubbo
咖啡教室17 小时前
java日常开发笔记和开发问题记录
java
咖啡教室18 小时前
java练习项目记录笔记
java
鱼樱前端18 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea19 小时前
6. RabbitMQ 死信队列的详细操作编写
java·消息队列·rabbitmq