1、什么是规则引擎
规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。
因为规则引擎将复杂的业务逻辑从业务代码中剥离出来,所以可以显著降低业务逻辑实现难度;同时,剥离的业务规则使用规则引擎实现,这样可以使多变的业务规则变得可维护,配合规则引擎提供的良好的业务规则设计器,不用编码就可以快速实现复杂的业务规则,同样,即使是完全不懂编程的业务人员,也可以轻松上手使用规则引擎来定义复杂的业务规则。
业务系统运行过程中难免会发生业务规则变化的情形,有了规则引擎,业务规则部分采用的是规则引擎实现,这样在系统正常运行的情况就可以利用规则引擎对业务规则进行修改,从而实现业务规则的随需应变。
常见的规则引擎高级视图如下:
2、为什么要使用规则引擎
规则一般使用如下一阶逻辑对知识表示进行推理:
when
<conditions>
then
<actions>
规则引擎一般具备以下好处:
-
声明式编程
规则引擎允许您说“该怎么做”,而不是“怎么做”。
使用规则可以使表达困难问题的解决方案变得容易,因此可以验证这些解决方案。规则比代码容易阅读。
规则系统能够解决非常非常棘手的问题,并解释了解决方案的产生方式以及沿途做出每个“决定”的原因。
-
逻辑与数据分离
数据在域对象中,逻辑在规则中。这从根本上打破了数据和逻辑的耦合,这可能是优点还是缺点。但是解耦逻辑可以更容易维护。可以将逻辑全部组织在一个或多个非常不同的规则文件中,而不是将逻辑分布在许多域对象或控制器中。
-
速度和可扩展性
Rete算法,Leaps算法及其后代(如Drools的ReteOO)提供了非常有效的方式来将规则模式与域对象数据进行匹配。当数据集变化很小时,这些规则特别有效,因为规则引擎可以记住过去的匹配项。具备良好的实战证明。
-
知识集中
通过使用规则,您可以创建可执行的知识库(知识库)。这意味着,规则具有很高的可读性,因此它们也可以用作文档。
-
工具整合
诸如Eclipse之类的工具(以及基于Web的用户界面)提供了编辑和管理规则以及获得即时反馈,验证和内容帮助的方法。还提供审计和调试工具。
-
说明设施
规则系统能够记录规则引擎做出的决定以及做出决定的原因,从而有效地提供了“解释工具”。
-
可理解的规则
通过创建对象模型以及(可选)为问题域建模的领域特定语言,您可以设置自己编写与自然语言非常接近的规则。以自己的语言表达自己的逻辑,可以让非技术领域的专家容易理解,并且所有程序都经过检查,而技术知识则隐藏在常规代码中。
规则引擎主要完成的就是将业务规则从代码中分离出来。
在规则引擎中,利用规则语言将规则定义为 if-then 的形式,if 中定义了规则的条件,then 中定义了规则的结果。规则引擎会基于数据对这些规则进行计算,找出匹配的规则。这样,当规则需要修改时,无需进行代码级的修改,只需要修改对应的规则,可以有效减少代码的开发量和维护量。
3、有哪些常用的规则引擎
Java 开源的规则引擎有:Drools、Easy Rules、Mandarax、IBM ILOG。使用最为广泛并且开源的是 Drools。
通过类型对规则引擎进行简单分类,一般有以下3类:
-
通过界面配置的成熟规则引擎:这种规则引擎相对来说就比较重,但是因为功能全,也有部分业务会选择这个,一般出名的有:Drools、IBM ILOG、URule
-
基于JVM脚本语言:这种其实不是一个成熟的规则引擎,应该算是规则引擎中的核心技术,因为Drools这种相对太重了,很多互联网公司会基于一些jvm的脚本语言,开发一个轻量级的规则引擎,这里比较出名的有:Groovy、AviatorScript、qlexpress。
-
基于java代码的规则引擎:上面是基于jvm脚本语言去做的,会有一些语法学习的成本,所以就有基于java代码去做的规则引擎,比如通过一些注解实现抽象的方式去做到规则的扩展,比较出名的有: EasyRules
下面针对其中几个开源免费的规则引擎进行简单介绍:
3.1 、JBOSS Drools
官方文档:http://www.drools.org/learn/documentation.html
Drools 是一个基于 Charles Forgy’s 的 RETE 算法的,易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师人员或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
Drools 是用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。
相关概念
-
事实(Fact): 对象之间及对象属性之间的关系
-
规则(rule): 是由条件和结论构成的推理语句,一般表示为 if…Then。一个规则的 if 部分称为 LHS,then 部分称为 RHS。
-
模式(module): 就是指 IF 语句的条件。这里 IF 条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。
Drools 通过事实、规则和模式相互组合来完成工作,drools 在开源规则引擎中使用率最广,但是在国内企业使用偏少,保险、支付行业使用稍多。
一个规则通常包括三个部分:属性部分(attribute)、条件部分(LHS)和结果部分(RHS)
代码演示
1、pom.xml 添加依赖
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
2、规则实体类
package com.poly.droolsdemo.domain;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class Project {
/**
* 物业类型
*/
private String propertyType;
/**
* 面积
*/
private BigDecimal area;
/**
* 户型
*/
private Integer unitType;
/**
* 成交总套数
*/
private Integer dealCount;
/**
* 成交总金额
*/
private BigDecimal dealTotalAmount;
/**
* 服务费(计算得出)
*/
private BigDecimal fee;
}
3、规则定义-DRL文件
/
//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
package com.poly;
import com.poly.droolsdemo.domain.Project
import java.math.BigDecimal
import com.sun.javafx.binding.StringFormatter
rule "规则1:物业类型=住宅 & 0<面积<100 & 成交套数>1"
salience 1 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
$project : Project( propertyType=="住宅" && area > 0 && area<100 && dealCount>=1 );
then
$project.setFee(new BigDecimal(10000));
System.out.println("规则1 被执行");
end;
rule "规则2:物业类型=住宅 & 0<面积<100 & 成交套数>5"
salience 2 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true
when
$project : Project( propertyType=="住宅" && area > 0 && area<100 && dealCount>5 );
then
$project.setFee(new BigDecimal(15000));
System.out.printf("%s 被执行",drools.getRule().getName());
end;
rule "规则3:物业类型=别墅&面积>100"
salience 1
no-loop true
when
$project : Project( propertyType=="别墅" && area > 100 );
then
$project.setFee(new BigDecimal(20000));
System.out.println("规则3 被执行");
drools.halt(); //后面的规则不再执行
end;
4、规则定义-XLS文件(基于决策表)
5、规则调用
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resourceArray = resourcePatternResolver.getResources("rules/**/*.drl");
for (Resource file : resourceArray) {
kieFileSystem.write(ResourceFactory.newClassPathResource("rules/" + file.getFilename(), "UTF-8"));
}
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
KieModule kieModule = kb.getKieModule();
KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
KieSession kieSession = kieContainer.newKieSession();
kieSession.insert(project);
//只执行一个符合条件的rule,需要配合salience优先级使用
kieSession.fireAllRules(1);
kieSession.dispose();
6、XSL转换为DRL
String drl = null;
File file = new File("...\\rules\\Fee.xls");
InputStream is = new FileInputStream(file);
// DRL 解析类 :compile 方法解析文件 可以解析 XLS 和 CSV 两种文件格式
SpreadsheetCompiler conCompiler = new SpreadsheetCompiler();
drl = conCompiler.compile(is, InputType.XLS);
System.out.println(drl);
if(drl!=null) {
KieHelper helper = new KieHelper();
helper.addContent(drl, ResourceType.DRL);
verifyDrl(helper);
KieSession kieSession = helper.build().newKieSession();
kieSession.insert(project);
int i = kieSession.fireAllRules();
System.out.println("规则执行了" + i + "次,返回计算结果为"+project.getFee());
kieSession.dispose();
}
private void verifyDrl(KieHelper kieHelper) {
Results verify = kieHelper.verify();
if (verify.hasMessages(Message.Level.WARNING, Message.Level.ERROR)) {
List<Message> messages = verify.getMessages(Message.Level.WARNING, Message.Level.ERROR);
for (Message message : messages) {
System.out.println("Error: " + message.getText());
}
throw new IllegalStateException("Compilation errors were found. Check the logs.");
}
}
7、 调试
传入对应的project对象,例如:
Project project = new Project();
project.setPropertyType("住宅");
project.setArea(new BigDecimal(80));
project.setUnitType(1);
project.setDealCount(2);
project.setDealTotalAmount(new BigDecimal(5000));
输出:
规则执行了1次,返回计算结果为100
热更新
热更新只需要重新初始化KieContainer就OK,新的规则即可生效。
3.2、URule
官网:http://www.bstek.com/
官方文档:http://www.bstek.com/resources/doc/
URule Pro是一款由上海锐道信息技术有限公司
自主研发的一款纯Java规则引擎,它可以运行在Windows、Linux、Unix等各种类型的操作系统之上; URule Pro的规则设计器采用业内首创的纯浏览器编辑模式,无须安装任何工具,打开浏览器即可完成复杂规则的设计与测试。
系统架构图:
功能简介
URule提供了两个版本:一个是基于Apache-2.0协议开源免费版本,URule开源版本第一款基于Apache-2.0协议开源的中式规则引擎;另一个是商用PRO版本,点击http://www.bstek.com 了解更多关于URule商用Pro版更多信息。
URULE PRO版与开源版主要功能比较 | ||
特性 | URULE PRO版 | URULE开源版 |
向导式决策集 | 有 | 有 |
脚本式决策集 | 有 | 有 |
决策树 | 有 | 有 |
决策流 | 有 | 有 |
决策表 | 有 | 有 |
交叉决策表 | 有 | 无 |
复杂评分卡 | 有 | 无 |
文件名、项目名重构 | 有 | 无 |
参数名、变量常量名重构 | 有 | 无 |
Excel决策表导入 | 有 | 无 |
规则集模版保存与加载 | 有 | 无 |
中文项目名和文件名支持 | 有 | 无 |
服务器推送知识包到客户端功能的支持 | 有 | 无 |
知识包优化与压缩的支持 | 有 | 无 |
客户端服务器模式下大知识包的推拉支持 | 有 | 无 |
规则集中执行组的支持 | 有 | 无 |
规则流中所有节点向导式条件与动作配置的支持 | 有 | 无 |
循环规则多循环单元支持 | 有 | 无 |
循环规则中无条件执行的支持 | 有 | 无 |
导入项目自动重命名功能 | 有 | 无 |
规则树构建优化 | 有 | 无 |
对象查找索引支持 | 有 | 无 |
规则树中短路计算的支持 | 有 | 无 |
规则条件冗余计算缓存支持 | 有 | 无 |
基于方案的批量场景测试功能 | 有 | 无 |
知识包调用监控 | 有 | 无 |
更为完善的文件读写权限控制 | 有 | 无 |
知识包版本控制 | 有 | 无 |
SpringBean及Java类的热部署 | 有 | 无 |
技术支持 | 有 | 无 |
代码演示
URule提供一个完善的web界面,我们只需要在web项目中引入以下依赖即可,
注意urule-console-pro是收费的,需要购买lisence或者应用开源版本。
<dependency>
<groupId>com.bstek.urule</groupId>
<artifactId>urule-console-pro</artifactId>
<version>3.0.3</version>
</dependency>
开源版本git地址:
https://gitee.com/youseries/urule
下载源码后,使用IDE打开,并修改urule-springboot的pon.xml文件
运行web页面
http://localhost:8080/urule/frame
然后就可以根据官方文档的说明进行配置了。
3.3、Easy Rule
官网:https://github.com/j-easy/easy-rules
Easy Rules是一个Java规则引擎,灵感来自一篇名为《Should I use a Rules Engine?》的文章。
Easy Rules它提供Rule抽象以创建具有条件和动作的规则,并提供RuleEngine API,该API通过一组规则运行以评估条件并执行动作。
相关概念
-
Name : 一个命名空间下的唯一的规则名称
-
Description : 规则的简要描述
-
Priority : 相对于其他规则的优先级
-
Facts : 事实,可立即为要处理的数据
-
Conditions : 为了应用规则而必须满足的一组条件
-
Actions : 当条件满足时执行的一组动作
代码演示
Easy Rules简单易用,只需两步:
首先,定义规则,方式有很多种
方式一:注解
@Rule(name = "weather rule", description = "if it rains then take an umbrella")
public class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void takeAnUmbrella() {
System.out.println("It rains, take an umbrella!");
}
}
方式二:链式编程
Rule weatherRule = new RuleBuilder()
.name("weather rule")
.description("if it rains then take an umbrella")
.when(facts -> facts.get("rain").equals(true))
.then(facts -> System.out.println("It rains, take an umbrella!"))
.build();
方式三:表达式
Rule weatherRule = new MVELRule()
.name("weather rule")
.description("if it rains then take an umbrella")
.when("rain == true")
.then("System.out.println(\"It rains, take an umbrella!\");");
方式四:yml配置文件
例如:weather-rule.yml
name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
- "System.out.println(\"It rains, take an umbrella!\");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule weatherRule = ruleFactory.createRule(new FileReader("weather-rule.yml"));
接下来,应用规则
public class Test {
public static void main(String[] args) {
// define facts
Facts facts = new Facts();
facts.put("rain", true);
// define rules
Rule weatherRule = ...
Rules rules = new Rules();
rules.register(weatherRule);
// fire rules on known facts
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
}
maven依赖
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.1.0</version>
</dependency>
总结
规则引擎不是银弹,规则引擎只是把业务规则从应用程序代码中分离出来,通过配置文件独立管理,本质上就是把原来的Java代码转化成脚本来动态解析执行而已,还是需要写代码。解耦后虽然可以在一定程度上支持快速调整业务规则,但是由于为了实现通用性,避免与业务场景强关联,所以规则引擎都是以DSL或独立web页面进行维护,对开发人员和业务人员都具备一定的学习成本,而且调整也会比较繁琐,很多时候即使培训了业务人员也不懂。
综上,
如果只追求无需硬编码,并且配置人员懂得简单编码可以使用通用的规则引擎,引入规则引擎可以简化编码,而且让逻辑易于维护;
如果还要追求配置界面的可读性,配置人员无需了解代码,开发人员就必须往前走一步,做每个业务类型的配置界面,然后再做一个界面到规则DSL语句的转化功能。或者类型URule这样做一个通用的配置界面,但是通用界面也代表了牺牲可交互性。
半通用的规则配置参考截图: