背景
近期遇到一个规则引擎项目,甲方明确要求用Drools规则引擎,之前想象中Drools应该应用在风控、预警类场景中。但实际这个项目是个简单场景,只是要一个动态函数调用。
项目规则应用场景
- 设计期:业务人员可以在线配置一个业务规则,发布后立刻生效。
- 运行期:客户端根据规则编号去调用规则引擎合规检查,引擎计算完成后将结果返回给客户端。
结合上述场景,实在与Drools的RETE算法不搭边,感觉用Nashorn或Groovy脚本搞定更适合。最终迫于无奈,还是举起了Drools这把牛刀。
我对Drools的期望
- 能够动态编译运行我生成的规则脚本并计算结果
- 规则新增、变更后最好能够增量编译,以提升效率
验证过程
Drools (v7.28.0.Final)规则引擎支持使用KieFileSystem
内存文件系统方式,编译规则字符串并运行,官方简单的示例代码如下:
public class KieFileSystemExample {
public void go(PrintStream out) {
KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
KieFileSystem kfs = ks.newKieFileSystem();
kfs.write("src/main/resources/rules/test.drl", getRule());
KieBuilder kb = ks.newKieBuilder(kfs);
kb.buildAll(); // kieModule is automatically deployed to KieRepository if successfully built.
if (kb.getResults().hasMessages(Level.ERROR)) {
throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}
ReleaseId defaultReleaseId = kr.getDefaultReleaseId();
KieContainer kContainer = ks.newKieContainer(defaultReleaseId);
KieSession kSession = kContainer.newKieSession();
kSession.setGlobal("out", out);
kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
kSession.fireAllRules();
}
public static void main(String[] args) {
new KieFileSystemExample().go(System.out);
}
private static String getRule() {
String s = "" +
"package org.drools.example.api.kiefilesystem \n\n" +
"import org.drools.example.api.kiefilesystem.Message \n\n" +
"global java.io.PrintStream out \n\n" +
"rule \"rule 1\" when \n" +
" m : Message( ) \n" +
"then \n" +
" out.println( m.getName() + \": \" + m.getText() ); \n" +
"end \n" +
"rule \"rule 2\" when \n" +
" Message( text == \"Hello, HAL. Do you read me, HAL?\" ) \n" +
"then \n" +
" insert( new Message(\"HAL\", \"Dave. I read you.\" ) ); \n" +
"end";
return s;
}
}
上述示例代码简洁易懂,但示例中没有提供模块化的方式写法。每次均为全量编译覆盖。
那么动态更新,增量编译该如何处理?
经过验证,可以利用drools的多模块特性,给每个模块绑定不同的ReleaseId,不同模块创建不同的KieContainer即可。在当前项目中由于需要根据规则编号调用规则,因此每个规则就是一个独立模块,可以独立编译、运行。
简单封装了一个DynamicRuleService
动态规则编译运行的类,供大家参考。
示例代码如下:
- 运行入口
public class DynamicRuleDemo {
public static void main(String[] args) {
System.out.println("************run rule 11111");
DynamicRuleService.getInstance().buildRuleContent( "rule-123123123", "1.0.0",getRule11111111()); // 如果规则有变化,则调用此方法编译规则。
KieSession kSession = DynamicRuleService.getInstance().getRuleSession("rule-123123123", "1.0.0");
kSession.setGlobal("out", System.out);
kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
kSession.fireAllRules();
System.out.println("************run rule 22222");
DynamicRuleService.getInstance().buildRuleContent("rule-456456456", "1.0.0", getRule2222222()); // 如果规则有变化,则调用此方法编译规则。
KieSession kSession2 = DynamicRuleService.getInstance().getRuleSession("rule-456456456", "1.0.0");
kSession2.setGlobal("out", System.out);
kSession2.insert(new Message("Dave", "Hello, Dog. Do you read me, Dog?"));
kSession2.fireAllRules();
System.out.println("************run rule 11111 again ");
kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
kSession.fireAllRules();
System.out.println("************run rule 22222 again ");
kSession2.insert(new Message("Dave", "Hello, Dog. Do you read me, Dog?"));
kSession2.fireAllRules();
}
private static String getRule11111111() {
String s = "" +
"package org.kaw.demo.rule.demo \n\n" +
"import org.kaw.demo.rule.demo.DynamicRuleDemo.Message \n\n" +
"global java.io.PrintStream out \n\n" +
"rule \"rule 1\" when \n" +
" m : Message( ) \n" +
"then \n" +
" out.println( m.getName() + \": \" + m.getText() ); \n" +
"end \n" +
"rule \"rule 2\" when \n" +
" Message( text == \"Hello, HAL. Do you read me, HAL?\" ) \n" +
"then \n" +
" insert( new Message(\"HAL\", \"Dave. I read you.\" ) ); \n" +
"end";
return s;
}
private static String getRule2222222() {
String s = "" +
"package org.kaw.demo.rule.demo \n\n" +
"import org.kaw.demo.rule.demo.DynamicRuleDemo.Message \n\n" +
"global java.io.PrintStream out \n\n" +
"rule \"rule 0\" when \n" +
" m : Message( ) \n" +
"then \n" +
" out.println( m.getName() + \": \" + m.getText() ); \n" +
"end \n" +
"rule \"rule 3\" when \n" +
" Message( text == \"Hello, Dog. Do you read me, Dog?\" ) \n" +
"then \n" +
" insert( new Message(\"Dog\", \"Ignore any message.\" ) ); \n" +
"end";
return s;
}
public static class Message {
private String name;
private String text;
public Message(String name, String text) {
this.text = text;
this.name = name;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
- 动态规则服务类
public class DynamicRuleService {
public static final String RESOURCE_PATH = "src/main/resources/";
public static final String DEFAULT_GROUP_ID = "com.kaw.rule";
/**
* 函数调用型规则中,每个规则文件的 ruleCode+ version 对应一个 KieContainer
* 后续需要考虑缓存容量
*/
protected Map<ReleaseId, KieContainer> containerMapper = new ConcurrentHashMap<>();
private static DynamicRuleService INSTANCE = new DynamicRuleService();
private DynamicRuleService() {
}
public static DynamicRuleService getInstance() {
return INSTANCE;
}
/**
* 获取规则会话,用来调用规则
* @param ruleCode
* @param version
* @return
*/
public KieSession getRuleSession(String ruleCode, String version) {
return getRuleSession(DEFAULT_GROUP_ID, ruleCode, version);
}
/**
* 获取规则会话,用来调用规则
* @param ruleCode
* @param version
* @return
*/
public KieSession getRuleSession(String groupId, String ruleCode, String version) {
KieContainer container = getRuleContainer(groupId, ruleCode, version);
return container.newKieSession();
}
/**
* 编译规则内容;会覆盖上次编译
*
* @param content
*/
public void buildRuleContent(String ruleCode, String version, String content) {
buildRuleContent(DEFAULT_GROUP_ID, ruleCode, version, content);
}
/**
* 编译规则内容;会覆盖上次编译
*
* @param content
*/
public void buildRuleContent(String groupId ,String ruleCode, String version, String content) {
KieServices kService = KieServices.Factory.get();
ReleaseId releaseId = kService.newReleaseId(groupId, ruleCode, version);
;
KieFileSystem fileSystem = kService.newKieFileSystem();
fileSystem.generateAndWritePomXML(releaseId);
String path = DynamicRuleService.RESOURCE_PATH + releaseId.getGroupId() + "/" + releaseId.getArtifactId() + "-"
+ releaseId.getVersion() + ".drl";
System.out.println("### build rule content : " + path);
fileSystem.write(path, content);
KieBuilder kbuilder = kService.newKieBuilder(fileSystem);
kbuilder.buildAll();
if (kbuilder.getResults().hasMessages(Level.ERROR)) {
throw new RuntimeException("Rule Build Errors:\n" + kbuilder.getResults().toString());
}
}
private ReentrantLock initContainerLock = new ReentrantLock();
private KieContainer getRuleContainer(String groupId, String ruleCode, String version) {
KieServices kService = KieServices.Factory.get();
ReleaseId releaseId = kService.newReleaseId(groupId, ruleCode, version);
KieContainer kContainer = containerMapper.get(releaseId);
if (kContainer == null) {
initContainerLock.lock();
try {
if (kContainer == null) {
kContainer = kService.newKieContainer(ruleCode, releaseId);
containerMapper.put(releaseId, kContainer);
}
} finally {
initContainerLock.unlock();
}
}
return kContainer;
}
}
- 运行结果
************run rule 11111
### build rule content : src/main/resources/com.kaw.rule/rule-123123123-1.0.0.drl
ave: Hello, HAL. Do you read me, HAL?
HAL: Dave. I read you.
************run rule 22222
### build rule content : src/main/resources/com.kaw.rule/rule-456456456-1.0.0.drl
Dave: Hello, Dog. Do you read me, Dog?
Dog: Ignore any message.
************run rule 11111 again
Dave: Hello, HAL. Do you read me, HAL?
HAL: Dave. I read you.
************run rule 22222 again
Dave: Hello, Dog. Do you read me, Dog?
Dog: Ignore any message.
验证结果
不出所料,Drools符合我的期望,能够动态增量编译运行规则。有类似场景的同学们,可以参考上述示例代码,结合规则文件的管理功能,就能实现类似的需求。
转载本文需注明出处:Drools 规则动态编译运行