目录
当弄清楚了一个规则的设计和执行逻辑后,接下来需要考虑的就是许多的规则如何组织了,即规则集的抽象设计。
来看一些例子
回顾easy-rules的rules执行
普通的规则集合
public class BasicRule implements Rule {
/**
* Rule name.
*/
protected String name;
/**
* Rule description.
*/
protected String description;
/**
* Rule priority.
*/
protected int priority;
@Override
public int compareTo(final Rule rule) {
if (getPriority() < rule.getPriority()) {
return -1;
} else if (getPriority() > rule.getPriority()) {
return 1;
} else {
return getName().compareTo(rule.getName());
}
}
使用TreeSet存储规则,具体是依靠规则的priority和name来排序存储到TreeSet中
public class Rules implements Iterable<Rule> {
private Set<Rule> rules = new TreeSet<>();
回到规则的执行逻辑org.jeasy.rules.core.DefaultRulesEngine#doFire
void doFire(Rules rules, Facts facts) {
if (rules.isEmpty()) {
LOGGER.warn("No rules registered! Nothing to apply");
return;
}
logEngineParameters();
log(rules);
log(facts);
LOGGER.debug("Rules evaluation started");
// 1。直接遍历执行
for (Rule rule : rules) {
final String name = rule.getName();
final int priority = rule.getPriority();
// 2。依据引擎参数和规则设定进行一些跳过处理
if (priority > parameters.getPriorityThreshold()) {
LOGGER.debug("Rule priority threshold ({}) exceeded at rule '{}' with priority={}, next rules will be skipped",
parameters.getPriorityThreshold(), name, priority);
break;
}
if (!shouldBeEvaluated(rule, facts)) {
LOGGER.debug("Rule '{}' has been skipped before being evaluated", name);
continue;
}
// 3。规则的执行
boolean evaluationResult = false;
try {
evaluationResult = rule.evaluate(facts);
} catch (RuntimeException exception) {
LOGGER.error("Rule '" + name + "' evaluated with error", exception);
triggerListenersOnEvaluationError(rule, facts, exception);
// give the option to either skip next rules on evaluation error or continue by considering the evaluation error as false
if (parameters.isSkipOnFirstNonTriggeredRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstNonTriggeredRule is set");
break;
}
}
// 4。如果规则执行命中
if (evaluationResult) {
LOGGER.debug("Rule '{}' triggered", name);
triggerListenersAfterEvaluate(rule, facts, true);
try {
triggerListenersBeforeExecute(rule, facts);
// 执行规则动作
rule.execute(facts);
LOGGER.debug("Rule '{}' performed successfully", name);
triggerListenersOnSuccess(rule, facts);
// 判断规则引擎是否设定了命中第一个规则后就跳过
if (parameters.isSkipOnFirstAppliedRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstAppliedRule is set");
break;
}
} catch (Exception exception) {
LOGGER.error("Rule '" + name + "' performed with error", exception);
triggerListenersOnFailure(rule, exception, facts);
if (parameters.isSkipOnFirstFailedRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstFailedRule is set");
break;
}
}
} else {
LOGGER.debug("Rule '{}' has been evaluated to false, it has not been executed", name);
triggerListenersAfterEvaluate(rule, facts, false);
if (parameters.isSkipOnFirstNonTriggeredRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstNonTriggeredRule is set");
break;
}
}
}
}
见注释:
- 按照优先级遍历执行,会判断是否有跳过规则
- 规则执行,如果命中,则执行规则下的动作,同时判断是否要结束此次规则引擎的执行
- 规则引擎可以设定是否执行了一个规则命中就跳出整个执行
这已经符合大部分的规则逻辑和实际情况了:符合所有条件或者只需要某个条件就够了。
如何想规则集合的构造
以普通人的经历来说基本可以有两种规则组织
第一种规则:
- 九年义务教育,高中,大学,硕士,博士…
按照正常的流程走,优先级必须是这样,每一关都得关关过,过不了就止步了。当然有例外了:
- 天资聪慧的可以进行跳级,即有些关可以不用闯了,跳过(可以看成规则的跳过逻辑)
- 在每一关过后,可能家庭、学校、社会有些小奖励(可以看成规则命中后的动作)
第二种规则:
当步入社会后,比较复杂起来了, 都是一个一个十字路口,掺杂着一些平行线
规则错综复杂,像一棵树,而第一种规则也适用于其中的某些阶段。可以看成规则命中后的动作
而在计算机的世界中表示上述两种规则集的组织结构显然是线性结构和树形结构。
规则集合
public interface RuleSet<T extends Rule> extends Identifiable<String>, Evaluable<RuleResult>, EvalTraceable {
/**
* 查找规则
*
* @param ruleId 规则id
* @return 规则
*/
T get(String ruleId);
/**
* 获取所有正式规则
*
* @return 规则集合
*/
Collection<T> getRules();
/**
* 对规则集进行求值
*
* @param context 引擎执行上下文
* @return 求值结果
*/
@Override
RuleResult eval(EngineExecutionContext context);
}
定义普通规则集和执行
List<Rule>
对于普通的规则集执行,仍然可以堪称规则下的表达式执行一样:把规则堪称操作数,规则的结果进行or操作
RuleSet的抽象
public interface RuleSet<T extends Rule> extends Identifiable<String>, Evaluable<RuleResult> {
/**
* 根据id获取某个规则
*
* @param ruleId 规则id
* @return 规则
*/
T get(String ruleId);
/**
* 获取所有规则
*
* @return 规则集合
*/
Collection<T> getRules();
/**
* 对规则集进行求值
*
* @param context 引擎执行上下文
* @return 求值结果
*/
@Override
RuleResult eval(EngineExecutionContext context);
}
第一个规则如果执行为true,那么第二个规则则会跳过执行,如下图所示:
public interface Identifiable<T> {
/**
* 返回标识
*
* @return
*/
T getId();
/**
* 设置标识
*
* @param id
*/
void setId(T id);
}
普通的RuleSet
public class DefaultRuleSet<E extends Rule> extends AbstractShortcutableEvaluable<E, RuleResult> implements RuleSet<E> {
public final RuleResult INIT = new RuleResult(EvalResult.False);
public final RuleResult HIT = new RuleResult(EvalResult.True);
protected String id;
protected ConcurrentMap<String, E> rulesMap;
public DefaultRuleSet(String id, List<E> rules) {
this.id = id;
this.operands = rules;
this.rulesMap = this.operands.stream().collect(Collectors.toConcurrentMap(Rule::getId, Function.identity()));
}
@Override
protected BinaryOperator<RuleResult> getCombiner(RuleResult s) {
return (r1, r2) -> {
// r1执行是false, 就返回r2; 否则返回r1(有短路逻辑,即规则之间是 or 的关系,有命中则返回)
if (r1 == INIT) {
return r2;
} else if (r1.evalResult != EvalResult.True && r2.evalResult == EvalResult.True) {
return r2;
} else {
return r1;
}
};
}
@Override
public RuleResult eval(EngineExecutionContext context) {
long begin = System.nanoTime();
RuleResult ruleResult = null;
try {
ruleResult = buildRuleResult(super.eval(context));
return ruleResult;
} finally {
if (ruleResult != null) {
ruleResult.setCost(System.nanoTime() - begin);
}
}
}
如下:
借用规则的执行逻辑,在规则集执行的时候: 遍历每一个规则,如果执行返回true,则后面会跳过执行,否则继续执行下一个
return (r1, r2) -> {
// r1执行是false, 就返回r2; 否则返回r1(有短路逻辑,即规则之间是 or 的关系,有命中则返回)
if (r1 == INIT) {
return r2;
} else if (r1.evalResult != EvalResult.True && r2.evalResult == EvalResult.True) {
return r2;
} else {
return r1;
}
};
@Override
public RuleResult eval(EngineExecutionContext context) {
long begin = System.nanoTime();
RuleResult ruleResult = null;
try {
ruleResult = buildRuleResult(super.eval(context));
return ruleResult;
} finally {
if (ruleResult != null) {
ruleResult.setCost(System.nanoTime() - begin);
}
}
}
规则集测试执行
public static void main(String[] args) {
String logic = "1 &(2|3)";
List<Condition> conditions = new ArrayList<>();
Condition condition1 = new Condition();
condition1.setItemId(1);
Operand operandLeft = new Operand();
operandLeft.setIsVar(true);
operandLeft.setValue("age");
operandLeft.setModifier("age");
condition1.setLeft(operandLeft);
condition1.setOperator(Operator.GT);
Operand operandRight = new Operand();
operandRight.setIsVar(false);
operandRight.setValue("18");
operandRight.setType("int");
condition1.setRight(operandRight);
conditions.add(condition1);
Condition condition2 = new Condition();
condition2.setItemId(2);
Operand operandLeft2 = new Operand();
operandLeft2.setIsVar(false);
operandLeft2.setValue("2");
condition2.setLeft(operandLeft2);
condition2.setOperator(Operator.LT);
Operand operandRight2 = new Operand();
operandRight2.setIsVar(false);
operandRight2.setValue("1");
condition2.setRight(operandRight2);
conditions.add(condition2);
Condition condition3 = new Condition();
condition3.setItemId(3);
Operand operandLeft3 = new Operand();
operandLeft3.setIsVar(true);
operandLeft3.setValue("number");
operandLeft3.setModifier("number");
condition3.setLeft(operandLeft3);
condition3.setOperator(Operator.CONTAINS_STRING);
Operand operandRight3 = new Operand();
operandRight3.setIsVar(false);
operandRight3.setValue("666");
condition3.setRight(operandRight3);
conditions.add(condition3);
Expression expression = new Expression(logic, conditions);
RuleEntry ruleEntry = new RuleEntry();
ruleEntry.setId("1");
ruleEntry.setExpression(expression);
Rule rule = new Rule(ruleEntry);
Condition condition4 = new Condition();
condition4.setItemId(1);
Operand operandLeft4 = new Operand();
operandLeft4.setIsVar(true);
operandLeft4.setValue("number2");
operandLeft4.setModifier("number2");
condition4.setLeft(operandLeft4);
condition4.setOperator(Operator.CONTAINS_STRING);
Operand operandRight4 = new Operand();
operandRight4.setIsVar(false);
operandRight4.setValue("666");
condition4.setRight(operandRight4);
Expression expression2 = new Expression("1", Arrays.asList(condition4));
RuleEntry ruleEntry2 = new RuleEntry();
ruleEntry2.setId("2");
ruleEntry2.setExpression(expression2);
Rule rule2 = new Rule(ruleEntry2);
EngineExecutionContext engineExecutionContext = new EngineExecutionContext();
Map<String, Object> ctx = new HashMap<>();
ctx.put("age", 19);
ctx.put("number", "666abc");
ctx.put("number2", "66abc");
engineExecutionContext.setData(ctx);
List<Rule> rules = Arrays.asList(rule, rule2);
DefaultRuleSet ruleSet = new DefaultRuleSet("1", rules);
RuleResult ruleResult = ruleSet.eval(engineExecutionContext);
System.out.println(ruleResult);
}
规则集最后返回命中
定义树形规则集
TreeSet<Rule>
,最基础的就是二叉树了,对形如如下的规则集执行:
定义树形结构
json串表达如下:
{
"id": 1,
"decision": {
"ruleId": "1",
"yes": {
"ruleId": "2",
"yes": {
"ruleId": "4"
},
"no": {
"ruleId": "4"
}
},
"no": {
"ruleId": "3",
"yes": {
"ruleId": "4"
},
"no": {
"ruleId": "4"
}
}
}
}
- DecisionTreeNode
public class DecisionTreeNode {
private String ruleId; // 当前执行的规则ID
private DecisionTreeNode yes; // 当前命中,左孩子规则
private DecisionTreeNode no; // 当前不命中,右孩子规则
private DecisionTreeNode anyway; // 无论当前是否命中,都跑到该规则
public DecisionTreeNode() {
}
public DecisionTreeNode(String ruleId) {
this.ruleId = ruleId;
}
public DecisionTreeNode(String ruleId, DecisionTreeNode yes, DecisionTreeNode no) {
super();
this.ruleId = ruleId;
this.yes = yes;
this.no = no;
}
public DecisionTreeNode(String ruleId, DecisionTreeNode yes, DecisionTreeNode no, DecisionTreeNode anyway) {
super();
this.ruleId = ruleId;
this.yes = yes;
this.no = no;
this.anyway = anyway;
}
public String getRuleId() {
return ruleId;
}
public void setRuleId(String ruleId) {
this.ruleId = ruleId;
}
public DecisionTreeNode getYes() {
return yes;
}
public void setYes(DecisionTreeNode yes) {
this.yes = yes;
}
public DecisionTreeNode getNo() {
return no;
}
public void setNo(DecisionTreeNode no) {
this.no = no;
}
public DecisionTreeNode getAnyway() {
return anyway;
}
public void setAnyway(DecisionTreeNode anyway) {
this.anyway = anyway;
}
@Override
public String toString() {
return "DecisionTreeNode [ruleId=" + ruleId + ", yes=" + yes + ", no=" + no + ", anyway=" + anyway + "]";
}
}
树形结构规则集
- TreeRuleSet
public class TreeRuleSet<T extends Rule> extends DefaultRuleSet<T> {
/** 树规则的入口规则节点 **/
private volatile DecisionTreeNode decision;
public TreeRuleSet(String id, List<T> rules, DecisionTreeNode decision) {
super(id, rules);
this.decision = decision;
}
/** 具体的执行逻辑 **/
@Override
public RuleResult eval(EngineExecutionContext context) {
long begin = System.nanoTime();
RuleResult ruleResult = null;
RuleSetResult ruleSetResult = new RuleSetResult();
try {
decision(ruleSetResult, getDecision(), context);
ruleResult = ruleSetResult.getResult();
ruleResult = buildRuleResult(ruleResult);
return ruleResult;
} finally {
// rule trace打印
for (RuleResult ruleResult1 : ruleSetResult.getTrace()) {
System.out.println("trace:" + ruleResult1);
}
ruleResult.setCost(System.nanoTime() - begin);
}
}
private void decision(RuleSetResult ruleSetResult, DecisionTreeNode decisionTree, EngineExecutionContext context) {
Rule rule = this.get(decisionTree.getRuleId());
if (null == rule) {
return;
}
// 执行当前规则,设置结果
RuleResult ruleResult = rule.eval(context);
ruleSetResult.append(ruleResult);
ruleSetResult.setResult(ruleResult);
// 1. 命中走左节点
boolean ruleHit = EvalResult.True == ruleResult.evalResult;
if (null != decisionTree.getYes() && ruleHit) {
decision(ruleSetResult, decisionTree.getYes(), context);
}
// 2. 不命中走右节点
if (null != decisionTree.getNo() && (EvalResult.False == ruleResult.evalResult)) {
decision(ruleSetResult, decisionTree.getNo(), context);
}
// 3. 走随意的那个节点
if (null != decisionTree.getAnyway()) {
decision(ruleSetResult, decisionTree.getAnyway(), context);
}
}
public DecisionTreeNode getDecision() {
return decision;
}
}
在树形结构的执行过程中,如果遇到规则执行为命中,则可以表示规则集命中,当然如果有异常则有需要表示出来;同时也可以记录规则执行的路径,即属性结构的执行路径
public class RuleSetResult {
// 记录树结构规则的执行路径
private List<RuleResult> trace;
private RuleResult result;
public RuleSetResult() {
trace = new ArrayList<>();
result = new RuleResult(EvalResult.False);
}
// trace一个执行结果
public void append(RuleResult ruleResult) {
Objects.requireNonNull(ruleResult);
trace.add(ruleResult);
}
/**
* 整个树执行结果,Unknown > True > False > Null
* 即:有异常最后结果是异常,否则有一个命中是命中,并记录第一个命中
*/
public void setResult(RuleResult rsl) {
Objects.requireNonNull(rsl);
if (null == result.getRule() || EvalResult.False == result.evalResult || EvalResult.Unknown == rsl.evalResult) {
this.result = rsl;
} else if (EvalResult.True == rsl.evalResult) {
this.result = rsl;
}
}
public List<RuleResult> getTrace() {
return trace;
}
public RuleResult getResult() {
return result;
}
}
树形结构的执行测试
public class TreeRuleSetTest {
public static void main(String[] args) {
List<Rule> rules = getRules();
DecisionTreeNode decisionTreeNode = getDecisionRootNode();
TreeRuleSet treeRuleSet = new TreeRuleSet("1", rules, decisionTreeNode);
EngineExecutionContext engineExecutionContext = new EngineExecutionContext();
Map<String, Object> ctx = new HashMap<>();
ctx.put("age", 19);
ctx.put("number", "666abc");
ctx.put("number2", "66abc");
engineExecutionContext.setData(ctx);
RuleResult ruleResult = treeRuleSet.eval(engineExecutionContext);
System.out.println("treeRule result:" + ruleResult);
}
public static List<Rule> getRules(){
String logic = "1 &(2|3)";
List<Condition> conditions = new ArrayList<>();
Condition condition1 = new Condition();
condition1.setItemId(1);
Operand operandLeft = new Operand();
operandLeft.setIsVar(true);
operandLeft.setValue("age");
operandLeft.setModifier("age");
condition1.setLeft(operandLeft);
condition1.setOperator(Operator.GT);
Operand operandRight = new Operand();
operandRight.setIsVar(false);
operandRight.setValue("18");
operandRight.setType("int");
condition1.setRight(operandRight);
conditions.add(condition1);
Condition condition2 = new Condition();
condition2.setItemId(2);
Operand operandLeft2 = new Operand();
operandLeft2.setIsVar(false);
operandLeft2.setValue("2");
condition2.setLeft(operandLeft2);
condition2.setOperator(Operator.LT);
Operand operandRight2 = new Operand();
operandRight2.setIsVar(false);
operandRight2.setValue("1");
condition2.setRight(operandRight2);
conditions.add(condition2);
Condition condition3 = new Condition();
condition3.setItemId(3);
Operand operandLeft3 = new Operand();
operandLeft3.setIsVar(true);
operandLeft3.setValue("number");
operandLeft3.setModifier("number");
condition3.setLeft(operandLeft3);
condition3.setOperator(Operator.CONTAINS_STRING);
Operand operandRight3 = new Operand();
operandRight3.setIsVar(false);
operandRight3.setValue("666");
condition3.setRight(operandRight3);
conditions.add(condition3);
Expression expression = new Expression(logic, conditions);
RuleEntry ruleEntry = new RuleEntry();
ruleEntry.setId("1");
ruleEntry.setExpression(expression);
Rule rule = new Rule(ruleEntry);
Condition condition4 = new Condition();
condition4.setItemId(1);
Operand operandLeft4 = new Operand();
operandLeft4.setIsVar(true);
operandLeft4.setValue("number2");
operandLeft4.setModifier("number2");
condition4.setLeft(operandLeft4);
condition4.setOperator(Operator.CONTAINS_STRING);
Operand operandRight4 = new Operand();
operandRight4.setIsVar(false);
operandRight4.setValue("666");
condition4.setRight(operandRight4);
Expression expression2 = new Expression("1", Arrays.asList(condition4));
RuleEntry ruleEntry2 = new RuleEntry();
ruleEntry2.setId("2");
ruleEntry2.setExpression(expression2);
Rule rule2 = new Rule(ruleEntry2);
Expression expression3 = new Expression("1", Arrays.asList(condition4));
RuleEntry ruleEntry3 = new RuleEntry();
ruleEntry3.setId("3");
ruleEntry3.setExpression(expression3);
Rule rule3 = new Rule(ruleEntry3);
Expression expression4 = new Expression("1", Arrays.asList(condition4));
RuleEntry ruleEntry4 = new RuleEntry();
ruleEntry4.setId("4");
ruleEntry4.setExpression(expression4);
Rule rule4 = new Rule(ruleEntry4);
List<Rule> rules = Arrays.asList(rule, rule2, rule3, rule4);
return rules;
}
public static DecisionTreeNode getDecisionRootNode(){
DecisionTreeNode decisionTreeNode4 = new DecisionTreeNode();
decisionTreeNode4.setRuleId("4");
DecisionTreeNode decisionTreeNode3 = new DecisionTreeNode();
decisionTreeNode3.setRuleId("3");
decisionTreeNode3.setAnyway(decisionTreeNode4);
DecisionTreeNode decisionTreeNode2 = new DecisionTreeNode();
decisionTreeNode2.setRuleId("2");
decisionTreeNode2.setAnyway(decisionTreeNode4);
DecisionTreeNode decisionTreeNode1 = new DecisionTreeNode();
decisionTreeNode1.setRuleId("1");
decisionTreeNode1.setYes(decisionTreeNode2);
decisionTreeNode1.setNo(decisionTreeNode3);
return decisionTreeNode1;
}
}
如下: 按照1 -> 2 -> 4
的规则结构走下去
1
/
2
\
4