文章目录
- drools 引擎工作原理
- 动态生成drl文件示例
- 步骤
- 模板文件 decision_rule_template.drt
- 生成规则文件service
- DecisionNodeFact实体对象
- 生成的drl字符串如下
- KieHealper 执行
- 动态生成drl文件的原理
实际应用过程中,很多时候,规则不是一成不变的,而且对于业务运营人员,去写drl文件也不太现实,因此,动态生成drl文档,可能是更加常用的操作。
drools 引擎工作原理
这部分内容在官网文档第4章。
4. Drools Engine
官网链接:https://docs.drools.org/7.73.0.Final/drools-docs/html_single/index.html#_droolslanguagereferencechapter
Drools引擎是Drools中的规则引擎。Drools引擎存储、处理和评估数据,以执行您定义的业务规则或决策模型。Drools引擎的基本功能是将传入的数据或事实与规则的条件相匹配,并确定是否以及如何执行规则。
Drools引擎主要有以下几部分:
Rules-规则:您定义的业务规则或DMN决策。所有规则必须至少包含触发规则的条件和规则规定的操作。
Facts-事实:在Drools引擎中输入或更改的数据,Drols引擎匹配规则条件以执行适用规则。
Production memory-生产内存:Drools引擎中存储规则的位置。
Working memory-工作内存:Drools引擎中存储事实的位置。
Agenda-议程:注册和排序激活规则(如果适用)以准备执行的位置。
drools引擎工作原理图:
当业务用户或自动化系统在Drools中添加或更新规则相关信息时,该信息将以一个或多个事实的形式插入Drools引擎的工作存储器中。
Drools引擎将这些事实与存储在生产内存中的规则条件相匹配,以确定符合条件的规则执行。(将事实与规则相匹配的过程通常称为模式匹配。)
当满足规则条件时,Drools引擎会激活规则并将其注册到议程中,然后Drools会对优先或冲突规则进行排序,以备执行。
动态生成drl文件示例
动态生成drl有多种方式,给一个参考链接:https://www.jianshu.com/p/650ecc341417
读者可根据自身使用场景选择。
本文只写一个示例,
需求大致描述:业务方要在页面配置生成一系列的规则,且配置较为灵活,每个规则的属性、返回值等都不固定,且属性和结果处理是动态可配置的。
避免涉密,再具体的不描述了。以上描述,是想说明我们定义规则时候的Facts不能是确定的对象,因此采用Map来实现Facts的灵活使用。
步骤
1、定义事实对象,本例采用Map,因此不需要定义特定的Fact对象。
2、定义数据存储结构及方式。本例采用mysql存储。因为有页面交互,因此需要将页面的数据进行存储。
3、定义模板文件drt,本例采用模板drt方式,减少拼接drl字符串的麻烦。
4、从数据库中读取数据,转换成模板对应的map。
5、使用ObjectDataCompiler.compile api完成模板drt转为drl字符串的过程。
6、使用KieHealper加载drl字符串,生成kiesession,完成执行动态加载的规则文件(drl字符串)。
模板文件 decision_rule_template.drt
template header
rule_name_id
nodeId
ruleCondition
ruleResult
package rules
import java.util.Map
global java.util.Map globalMap
template "decision_rule_template"
rule "decision_rule_@{rule_name_id}_@{nodeId}"
lock-on-active
when
$map:Map()
@{ruleCondition}
then
@{ruleResult}
System.out.println("触发规则:decision_rule_@{rule_name_id}_@{nodeId}");
end
end template
生成规则文件service
package com.dcy.drools.service;
import com.dcy.drools.fact.decision.DecisionNodeFact;
import lombok.extern.slf4j.Slf4j;
import org.drools.template.ObjectDataCompiler;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author ding cheng yun
* @description
* @date 2022/11/15
**/
@Slf4j
@Service
public class DecisionLoadRuleService {
public String buildRuleStr(Long decisionId) {
List<DecisionNodeFact> nodeFacts = getRuleNodeDB(decisionId);
List<Map<String, Object>> conditionMapList = new ArrayList<>();
for (int i = 0; i < nodeFacts.size(); i++) {
DecisionNodeFact n = nodeFacts.get(i);
Map<String, Object> conditionMap = new HashMap<>();
conditionMap.put("rule_name_id", decisionId);
conditionMap.put("nodeId", n.getNodeId());
conditionMap.put("ruleCondition", n.getRuleConditionStr());
conditionMap.put("ruleResult", n.getRuleResultStr());
conditionMapList.add(conditionMap);
}
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("templates/decision_rule_template.drt");
ObjectDataCompiler objectDataCompiler = new ObjectDataCompiler();
String drlStr = objectDataCompiler.compile(conditionMapList, inputStream);
log.info(" 规则id:{}, 规则code:{}", "or.getRuleId()", "or.getRuleCode()");
log.info(drlStr);
return drlStr;
}
private List<DecisionNodeFact> getRuleNodeDB(Long decisionId) {
// 根节点
DecisionNodeFact n = new DecisionNodeFact();
n.setDecisionId(decisionId);
n.setNodeId(10L);
n.setNodeName("客户性别分组");
n.setParentNodeId(0L);
n.setRuleConditionStr("");
n.setRuleResultStr("");
// 子节点-1:规则1,父节点=根节点
DecisionNodeFact n1 = new DecisionNodeFact();
n1.setDecisionId(decisionId);
n1.setNodeId(11L);
n1.setNodeName("无姓名规则");
n1.setParentNodeId(10L);
n1.setRuleConditionStr("Object($map[\"sex\"] == \"女\") Object($map[\"userAge\"] >\"20\",$map[\"userName\"] ==\"张三\")");
n1.setRuleResultStr("globalMap.put(\"userScore\",\"100\");globalMap.put(\"validVip\",\"不是vip\");modify($map){put(\"userScore\", \"100\"),put(\"validVip\", \"不是vip\")};");
// 子节点-2:规则2,父节点=根节点
DecisionNodeFact n2 = new DecisionNodeFact();
n2.setDecisionId(decisionId);
n2.setNodeId(12L);
n2.setNodeName("张三规则");
n2.setParentNodeId(10L);
n2.setRuleConditionStr("Object($map[\"sex\"] == \"男\") Object($map[\"userAge\"] <=\"20\")");
n2.setRuleResultStr("globalMap.put(\"userScore\",\"500\");globalMap.put(\"validResult\",\"通过\");modify($map){put(\"userScore\", \"500\"),put(\"validResult\", \"通过\")};");
// 子节点-3:规则3 父节点=规则2
DecisionNodeFact n3 = new DecisionNodeFact();
n3.setDecisionId(decisionId);
n3.setNodeId(13L);
n3.setNodeName("李四规则");
n3.setParentNodeId(12L);
n3.setRuleConditionStr("Object(!($map[\"sex\"] == \"男\" && $map[\"userAge\"] <=\"20\")) Object($map[\"userName\"] ==\"李四\")");
n3.setRuleResultStr("globalMap.put(\"userScore\",\"300\");modify($map){put(\"userScore\", \"300\")};");
List<DecisionNodeFact> nodeFacts = new ArrayList<>();
nodeFacts.add(n);
nodeFacts.add(n1);
nodeFacts.add(n2);
nodeFacts.add(n3);
return nodeFacts;
}
}
DecisionNodeFact实体对象
package com.dcy.drools.fact.decision;
import lombok.Data;
/**
* @author ding cheng yun
* @description
* @date 2022/11/15
**/
@Data
public class DecisionNodeFact {
/**
* 主键
*/
private Long nodeId;
/**
* 父节点id
*/
private Long parentNodeId;
/**
* 决策流id
*/
private Long decisionId;
/**
* 节点名称
*/
private String nodeName;
/**
* 节点规则条件
*/
private String ruleConditionStr;
/**
* 节点规则结果
*/
private String ruleResultStr;
}
生成的drl字符串如下
package rules
import java.util.Map
global java.util.Map globalMap
rule "decision_rule_1_10"
lock-on-active
when
$map:Map()
then
System.out.println("触发规则:decision_rule_1_10");
end
rule "decision_rule_1_11"
lock-on-active
when
$map:Map()
Object($map["sex"] == "女") Object($map["userAge"] >"20",$map["userName"] =="张三")
then
globalMap.put("userScore","100");globalMap.put("validVip","不是vip");modify($map){put("userScore", "100"),put("validVip", "不是vip")};
System.out.println("触发规则:decision_rule_1_11");
end
rule "decision_rule_1_12"
lock-on-active
when
$map:Map()
Object($map["sex"] == "男") Object($map["userAge"] <="20")
then
globalMap.put("userScore","500");globalMap.put("validResult","通过");modify($map){put("userScore", "500"),put("validResult", "通过")};
System.out.println("触发规则:decision_rule_1_12");
end
rule "decision_rule_1_13"
lock-on-active
when
$map:Map()
Object(!($map["sex"] == "男" && $map["userAge"] <="20")) Object($map["userName"] =="李四")
then
globalMap.put("userScore","300");modify($map){put("userScore", "300")};
System.out.println("触发规则:decision_rule_1_13");
end
KieHealper 执行
/**
* 执行drl字符串规则
* @param drlStr 规则字符串
* @param packag 规则包
* @param drlFineName 规则文件名
* @param params 规则执行的入参
* @return 规则执行结果map
* packag 对应该示例,为模板中的pakage ,即rules,drlFineName=decision_rule_1.drl,入参则是调用接口方提供
*/
public Map<String, Object> executeDrlStr(String drlStr,String packag, String drlFineName, Map<String, Object> params) {
// 复制参数到另一个map,防止传递方的param发生变化
Map<String,Object> drlParams = CollectionUtils.isEmpty(params) ? new HashMap<>() : params.entrySet()
.stream()
.collect(Collectors.toMap(p->p.getKey(),p->p.getValue()));
log.info("KieExecuteService.executeDrlStr paramMap:{}", drlParams);
KieSession kieSession = newKieBase.ruleKieBase(drlStr, packag +"/" +drlFineName);
Map<String, Object> globalMap = new HashMap<>();
// 注意这个globalMap必须指定
kieSession.setGlobal("globalMap", globalMap);
kieSession.insert(drlParams);
// 对应该示例, drlFineName.substring(0, drlFineName.lastIndexOf(".drl")) 结果是:decision_rule_1
// 根据模板,一个规则文件中的所有规则都是以文件名开头的
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter(drlFineName.substring(0, drlFineName.lastIndexOf(".drl"))));
kieSession.dispose();
return globalMap;
}
动态生成drl文件的原理
动态的将rules修改到生产内存中,且通知drools引擎。
由于时间关系,源码解读等暂时不写了。若以后有机会再来补充。