3. 规则引擎选型:轻量级规则引擎对比
说实话,选规则引擎这事儿,我纠结了挺久。
当时团队里有人提议直接用 Drools,说它功能强大、社区成熟。也有人觉得 Easy Rules 够轻量,上手快。但我最后选了自研 JSON 规则。为什么?
咱们先看看这三条路各自长什么样。
3.1 三个候选方案速览
| 维度 | Drools | Easy Rules | 自研 JSON 规则 |
|---|---|---|---|
| 学习成本 | 高(DRL 语法、KIE 体系) | 中(注解 + 规则类) | 低(JSON 配置即可) |
| 部署体积 | ~15MB(依赖庞大) | ~200KB | 零额外依赖 |
| 动态更新 | 支持(但需重新编译) | 需重启或自定义加载 | 天然支持(热加载 JSON) |
| API 层适配 | 重,需单独部署或嵌入 | 中等,可嵌入 | 极轻,直接嵌入 |
| 调试难度 | 高(规则堆栈复杂) | 中 | 低(JSON 可读性强) |
核心结论:在 API 层做数据质量校验,我们需要的不是「万能规则引擎」,而是「刚好够用、随时可改、不拖累性能」的轻量方案。
3.2 Drools:杀鸡用牛刀
Drools 确实强。Rete 算法、前向链、后向链、复杂事件处理…… 这些能力在风控、审批流场景里很香。但咱们这是 API 数据质量校验啊。
我举个例子。你只是想检查「手机号不能为空」「金额必须大于 0」,Drools 却要你写 DRL 文件、配置 kmodule.xml、启动 KieContainer。这感觉就像用火箭筒打蚊子。
我在项目中见过一个团队,为了用 Drools 专门配了个规则管理后台,结果上线后每次改规则都要走发布流程。运维同学气得直拍桌子。
避坑:我曾经接手过一个项目,Drools 规则库膨胀到 200+ 条后,单次规则评估耗时从 2ms 飙升到 50ms。排查了三天,发现是规则间冲突导致 Rete 网络退化。对于 API 层来说,这种不确定性不可接受。
3.3 Easy Rules:轻了,但不够灵活
Easy Rules 是个好框架。注解驱动,几行代码就能跑起来。我刚开始也倾向它。
但用着用着就发现问题了:
- 规则定义耦合 Java 代码——每次加规则都要改代码、重新打包。
- 组合逻辑表达能力弱——AND/OR/NOT 嵌套多了,代码可读性直线下降。
- 无法动态下发热加载——虽然可以自己写个 JSON 解析器转成 Easy Rules 的 Rule 对象,但那不就是自研了吗?
说白了,Easy Rules 适合「规则固定、变化少」的场景。但数据质量规则恰恰是变化最频繁的——业务方今天说要加个身份证校验,明天说手机号格式要改。你总不能每次都发版吧?
3.4 自研 JSON 规则:我的选择
好,重点来了。为什么我最终选了自研?
其实就三个字:刚刚好。
3.4.1 规则长什么样
先给你看个例子。这是一条「用户注册」接口的数据质量规则:
{
"ruleId": "RULE001",
"name": "用户注册信息校验",
"description": "校验手机号、邮箱、年龄",
"priority": 1,
"conditions": {
"operator": "AND",
"rules": [
{
"field": "phone",
"operator": "NOT_EMPTY",
"message": "手机号不能为空"
},
{
"field": "phone",
"operator": "REGEX",
"value": "^1[3-9]\\d{9}$",
"message": "手机号格式不正确"
},
{
"field": "email",
"operator": "REGEX",
"value": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"message": "邮箱格式不正确"
},
{
"operator": "OR",
"rules": [
{
"field": "age",
"operator": "BETWEEN",
"value": [18, 60]
},
{
"field": "role",
"operator": "IN",
"value": ["admin", "super_admin"]
}
],
"message": "普通用户年龄需在18-60岁之间,管理员不受限制"
}
]
},
"actions": [
{
"type": "REJECT",
"httpStatus": 400
}
]
}
设计思路:JSON 规则的核心是「条件树」。每个节点可以是叶子节点(字段 + 操作符 + 值),也可以是组合节点(AND/OR/NOT)。这样递归解析,天然支持任意复杂度的嵌套逻辑。
3.4.2 引擎核心代码(简化版)
public class JsonRuleEngine {
public RuleResult evaluate(JsonNode ruleNode, Map<String, Object> data) {
String operator = ruleNode.get("operator").asText();
switch (operator) {
case "AND":
return evaluateAnd(ruleNode.get("rules"), data);
case "OR":
return evaluateOr(ruleNode.get("rules"), data);
case "NOT":
return evaluateNot(ruleNode.get("rules").get(0), data);
default:
return evaluateLeaf(ruleNode, data);
}
}
private RuleResult evaluateLeaf(JsonNode leaf, Map<String, Object> data) {
String field = leaf.get("field").asText();
String op = leaf.get("operator").asText();
Object value = data.get(field);
boolean passed = switch (op) {
case "NOT_EMPTY" -> value != null && !value.toString().isEmpty();
case "REGEX" -> value != null && value.toString().matches(leaf.get("value").asText());
case "BETWEEN" -> {
int v = Integer.parseInt(value.toString());
ArrayNode range = (ArrayNode) leaf.get("value");
yield v >= range.get(0).asInt() && v <= range.get(1).asInt();
}
case "IN" -> {
ArrayNode allowed = (ArrayNode) leaf.get("value");
yield StreamSupport.stream(allowed.spliterator(), false)
.anyMatch(item -> item.asText().equals(value.toString()));
}
default -> false;
};
return new RuleResult(passed, passed ? null : leaf.get("message").asText());
}
}
你看,核心逻辑不到 50 行。递归遍历条件树,每个节点只做一件事。性能稳定,O(n) 复杂度,n 是规则节点数。
3.4.3 为什么我选它
- 零依赖——一个 JSON 解析库就够了,部署包不增肥。
- 热加载——规则存数据库或配置中心,改完秒级生效。
- 可读性强——业务方都能看懂 JSON,自己就能配规则。
- 性能可控——没有黑盒优化,每行代码都看得见。
- 扩展容易——加个操作符?加个枚举就行。
我的经验:自研规则引擎上线后,规则变更周期从「3 天(提测 + 发布)」缩短到「5 分钟(改配置 + 刷新)」。运维同学终于不用半夜爬起来发版了。
3.5 规则引擎选型决策树
为了让你更直观地理解选型逻辑,我画了张图:
3.6 自研的代价与边界
当然,自研不是万能的。我得跟你说清楚它的代价:
- 没有规则冲突检测——Drools 的 Rete 网络会自动检测冲突,自研得自己写。
- 没有规则优先级调度——复杂场景下需要自己实现 Agenda 排序。
- 没有规则可视化编辑器——得自己搭或者让业务方直接写 JSON。
我的建议:如果你的规则超过 500 条,或者需要复杂的推理链(比如 A 规则触发 B 规则,B 规则再触发 C 规则),那还是老老实实用 Drools。自研 JSON 规则最适合「校验型」场景——每条规则独立,不依赖其他规则的执行结果。
说白了,选型没有银弹。你得想清楚自己的场景:
- 是「校验」还是「推理」?
- 是「静态」还是「动态」?
- 是「嵌入」还是「独立部署」?
对于 API 层数据质量校验这个场景,我的答案很明确:自研 JSON 规则。轻、快、可控。够用就好。