1、规则引擎(Rule Engine)简介
在大型商业系统中,业务规则、商业逻辑等等都会比较复杂。而且在很多大型系统当中,很多业务规则、商业逻辑并不是一成不变的。甚至当系统进入生产阶段时,客户的业务规则、商业逻辑也会改变。某些系统要求甚至更高,要求能24小时不停机,并且能够实时修改商业规则。这就对商业系统提出了较大的挑战。如果将这些可变的规则直接编写到代码里面的话,业务规则一旦改变,就要修改代码。并由此带来编译、打包、发布等等问题。这对于生产系统来说是极不方便的。因此,如何考虑把一些可变的业务规则抽取到外面,使这些业务规则独立于程序代码。并最好是能够实时的修改业务规则,这样就可以做到不用打包编译发布等等。
值得庆幸的是现在出现了一些规则引擎(Rule Engine),专门解决以上所述的问题。利用它,我们就可以在应用系统中分离客户的商业决策逻辑和应用开发者的技术决策,并把这些商业规额则放在中心数据库或其他统一的地方,让它们能在运行时可以动态地管理和修改。
◆ 那么什么是规则引擎呢?
规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。
◆ 使用规则引擎的好处
■ 声明式编程:规则引擎允许你描述做什么而不是如何去做
■ 逻辑与数据分离:数据保存在系统对象中,逻辑保存在规则中。这根本性的打破了面向对象系统中将数据和逻辑耦合起来的局面
■ 速度及可测量性:Rete算法、Leaps算法,以及由此衍生出来的Drools的Rete、Leaps算法,提供了对系统数据对象非常有效率的匹配
■ 知识集中化:通过使用规则,将建立一个可执行的规则库。这意味着规则库代表着现实中的业务策略的唯一对应,理想情况下可读性高的规则还可以被当作文档使用
■ 工具集成:例如Eclipse(将来可能在基于Web的界面上)这样的工具为规则的修改与管理、即时获得反馈、内容验证与修补提供了办法。审查与调试工具同样也可用了
■ 解释机制:通过将规则引擎的决断与决断的原因一起记录下来,规则系统提供了很好的“解释机制”
■ 易懂的规则:通过建立对象模型以及DSL(域定义语言),你可以用接近自然语言的方式来编写规则。这让非技术人员与领域专家可以用他们自己的逻辑来理解规则(因为程序的迷宫已经被隐藏起来了)
◆ 适合使用规则引擎系统的场合
■ 用传统的代码开发比较复杂、繁琐
■ 问题虽然不复杂,但是用传统的代码开发比较脆弱,也就是经常修改
■ 没有优雅的算法
■ 业务规则频繁改变
■ 有很多业务专家、不懂技术开发
◆ 不适合使用规则引擎系统的场合
虽然规则系统看起来比较不错,但是并不是任何地方都可以使用规则系统。很多简单、固定的业务系统,可以不用使用规则系统。规则系统也不能用来作为标示重要的业务流程、不能用来作为工作流引擎。
有很多程序员把规则系统当成是一种动态修改配置。也就是把一部分代码逻辑抽取到外面,统一存放起来。这样,当一些配置修改的话,通过修改规则,就能修改代码的一部分逻辑。如果把规则仅仅用在这个场合下的话,可以考虑采用脚本引擎。比如BeanShell、JEXL、Groovy等等。
2、Drools(JbossRules)简介
Drools是为Java量身定制的基于Charles Forgy的RETE算法的规则引擎的实现。具有了OO接口的RETE,使得商业规则有了更自然的表达。
JBoss Rules 的前身是Codehaus的一个开源项目叫Drools。最近被纳入JBoss门下,更名为JBoss Rules,成为了JBoss应用服务器的规则引擎。
既然Drools是一个商业规则引擎,那我们就要先知道到底什么是Rules,即规则。在Drools中,规则是如何被表示的。
2.1、Rules 规则语言
一条规则是对商业知识的编码。一条规则有 attributes,一个 Left Hand Side ( LHS )和一个 Right Hand Side ( RHS )。Drools 允许下列几种 attributes:salience,agenda-group,no-loop,auto-focus,duration,activation-group。attributes 的作用说明后续学习笔记会提到。
rule "name" < attribute1 > < value > < attribute2 > < value > ... when < LHS > then < RHS > end
规则的name可以放双引号内,也可以不需要双引号,但如果name里面有空格,就必须放在双引号内。
规则的attributes可以为零个或多个,多个换行隔开。
规则的 LHS 由零个或多个条件(Conditions)组成。零个默认为true,当所有的条件(Conditions)都满足并为真时, RHS 将被执行。
RHS 被称为结果(Consequence)。
LHS 和 RHS 类似于:
if ( < LHS > ) { < RHS > }
2.2、Drools 规则文件
在 Drools 当中,一个标准的规则文件就是一个以“.drl”结尾的文本文件,标准的规则文件格式:
package package_name imports globals functions queries rules
包名是必须的,并放在第一行。规则可以通过 package 关键字同一个命名空间(namespace)相关联;其他的规则引擎可能称此为规则集(Rule Set)。这里的package也是跟java不太一样的概念,用package关键字并不会创建相应的目录层次。相同的package下定义的function和query等可以直接使用。
import:导入规则文件需要使用到的外部变量,这里的使用方法跟java相同,但是不同于java的是,这里的import导入的不仅仅可以是一个类,也可以是这个类中的某一个可访问的静态方法。
比如:import com.esom.tech.drools.Message.staticMethodxx;
一个规则文件例子:
package com.esom.tech.drools.drl import com.esom.tech.drools.Message; function void say(Message $message) { System.out.println( $message.getInfo() ); } rule "HelloWorld" when $message : Message(status == Message.HELLO) then say($message); end
上面的这个规则类同于:
public void helloWorld(Message $message){
if( $message.getStatus() == Message.HELLO ){
say($message);
}
}
3、Hello World:构建并测试一个简单例子
国际惯例,通过从最基础开始,构建helloworld例子,学习Drools的一些基础用法。
3.1、框架搭建
首先搭建一个可供进行Drools开发的框架,该demo项目使用Eclipse IDE,将下面包引入到项目:
如果使用Maven进行开发,在pom.xml引入以下依赖:
<!--drools--> <dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>5.5.0.Final</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>5.5.0.Final</version> </dependency> <!-- JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency>
3.2、代码文件
项目搭建好后,添加以下3个文件:一个java文件,一个drl文件,一个测试类
1)Message.java
-
package com.esom.tech.drools; public class Message { public static final int HELLO = 0; public static final int GOODBYE = 1; private String info; private int status; //get、set方法... }
2)MsgRule.drl
package com.esom.tech.drools.drl import com.esom.tech.drools.Message; rule "HelloWorld" no-loop true when $msg : Message(status == Message.HELLO, $info: info ) then System.out.println( $info ); $msg.setInfo( "Goodbye World" ); $msg.setStatus( Message.GOODBYE ); update( $msg ); end rule "GoodBye" when $m : Message( status == Message.GOODBYE, info != null ) then System.out.println( $m.getInfo() ); end
3)测试类 DroolsTest.java
package com.esom.tech.drools;
import java.io.InputStreamReader;
import java.io.Reader;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.WorkingMemory;
import org.drools.compiler.PackageBuilder;
import org.junit.Test;
import junit.framework.TestCase;
public class DroolsTest extends TestCase{
@Test
public void testFire() throws Exception {
RuleBase ruleBase = readRule();
WorkingMemory workingMemory = ruleBase.newStatefulSession();
Message message = new Message();
message.setInfo( "Hello World" );
message.setStatus( Message.HELLO );
workingMemory.insert( message );
workingMemory.fireAllRules();
assertEquals(message.getInfo(), "Goodbye World");
}
private static RuleBase readRule() throws Exception {
// 读写DRL文件
Reader source = new InputStreamReader(
DroolsTest.class.getResourceAsStream("/com/esom/tech/drools/MsgRule.drl"));
//添加DRL源到PackageBuilder,可以添加多个
PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl(source);
//添加package到RuleBase(部署规则集)
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackages(builder.getPackages());
return ruleBase;
}
}
测试通过,绿了。
打印输出:
Hello World
Goodbye World
可以得出:
1)规则引擎实现了数据同逻辑的完全解耦。规则并不能被直接调用,因为它们不是方法或函数,规则的激发是对 WorkingMemory 中数据变化的响应(包括Fact对象的添加,修改,删除)。结果(Consequence ,即 RHS)作为 LHS events 完全匹配的 Listener 。
2)当一个JavaBean 插入到WorkingMemory 当中变成Fact 之后,Fact 对象不是对原来的JavaBean 对象进行Clone,而是原来JavaBean 对象的引用,这也就是
可以通过的原因。
3)内置了update方法,通过该方法重启规则匹配,从而激活匹配上的 rule "GoodBye"。
4)在attribute里面可以有多个条件,用逗号隔开;也可以Clone Fact的属性,比如:
3.3 其他尝试
1)将rule "HelloWorld"改为:
rule "HelloWorld" //no-loop true when $msg : Message(status == Message.HELLO, $info: info ) then System.out.println( $info ); //$msg.setInfo( "Goodbye World" ); //$msg.setStatus( Message.GOODBYE ); update( $msg ); end
执行输出:
Hello World
Hello World
...
可以发现rule激活进入无限递归循环,而将 no-loop 设为 true ,可以防止这个 rule 的再次激活。而内置方法update不一定需要fact对象真正有改变也会生效。
2)
rule "HelloWorld" when $msg : Message(status == Message.HELLO, $info: info ) then System.out.println( $info ); $msg.setInfo( "Goodbye World" ); $msg.setStatus( Message.GOODBYE ); insert( $msg ); end
输出:
Hello World
rule "HelloWorld" when $msg : Message(status == Message.HELLO, $info: info ) then System.out.println( $info ); $msg.setInfo( "Goodbye World" ); $msg.setStatus( Message.GOODBYE ); Message m = new Message(); m.setInfo( "Goodbye Again" ); m.setStatus( Message.GOODBYE ); insert( m ); end
输出:
Hello World
Goodbye Again
Drools可以通过向WorkingMemory中插入Fact对象的方式来实现规则引擎与业务数据的交互,这个Fact对象可以在rule里面创建和插入。对于相同Fact对象多次使用insert,只有的第一次会激活规则匹配。
也可在Java里面通过workingMemory.insert( fact);插入,但必须在后面调fireAllRules才开始激活规则匹配。
这里对新的数据和被修改的数据进行规则的匹配称为模式匹配(Pattern Matching )。进行匹配的引擎称为推理机(Inference Engine)。被访问的规则称为 ProductionMemory ,被推理机进行匹配的数据称为 WorkingMemory 。
3)加载多个drl,如:MsgRule.drl 和 MsgRule2.drl
如果他们的package_name相同,则以最后一次加载的使用,前面加载的给剔除。
同过package里面不能有同名的rule