4. 规则定义语言设计:JSON Schema 定义规则
规则引擎的核心,说白了就是「怎么把业务语言翻译成机器能懂的结构」。我见过不少团队,一开始用 Excel 管理规则,后来用 YAML,最后都逃不过 JSON Schema。为什么?因为 JSON Schema 既能做校验,又能做文档,还能直接对接规则引擎的解析器。
我个人习惯把规则定义分成三层:元信息层(谁、什么时候、什么类型)、逻辑层(表达式怎么算)、动作层(触发了怎么办)。今天我们先聊前两层。
4.1 规则结构:骨架要稳
先看一个完整的规则定义长什么样。我在项目中踩过最大的坑,就是规则结构设计得太灵活——灵活到解析器都不知道该怎么处理。所以,我建议你一开始就定死结构。
{
"ruleId": "DQ-001",
"ruleName": "订单金额非负校验",
"ruleType": "FIELD_CHECK",
"description": "确保订单金额字段不为负数",
"version": "1.0.0",
"status": "ACTIVE",
"expression": {
"operator": "gte",
"field": "order_amount",
"value": 0
},
"threshold": {
"type": "COUNT",
"operator": "lte",
"value": 10
},
"severity": "CRITICAL",
"dimension": "COMPLETENESS",
"dataSource": "order_db",
"schedule": "0 0 2 * * ?"
}
嗯,这里要注意:ruleId 一定要有命名规范。我见过有人用自增 ID,结果规则一多,根本不知道 DQ-1024 是干什么的。我建议用「数据域-序号」的格式,比如 ORDER-001、USER-023。
核心字段说明:
- ruleId:全局唯一,建议带业务前缀
- ruleType:决定表达式怎么解析,常见类型有 FIELD_CHECK(字段级)、RECORD_CHECK(记录级)、CROSS_TABLE(跨表)、STATISTICAL(统计)
- severity:严重级别,直接影响告警渠道和响应速度
- threshold:阈值,不是所有规则都需要,但统计类规则必须有
4.2 规则类型:分而治之
规则类型决定了表达式的语法和校验逻辑。我把它分成四大类,你想想看,是不是覆盖了 90% 的数据质量场景?
| 规则类型 | 适用场景 | 示例 |
|---|---|---|
| FIELD_CHECK | 单字段值校验 | 年龄在 0-150 之间 |
| RECORD_CHECK | 记录内多字段关系 | 开始日期 ≤ 结束日期 |
| CROSS_TABLE | 表与表之间一致性 | 订单表的用户 ID 必须在用户表中存在 |
| STATISTICAL | 聚合结果异常检测 | 今日订单量相比昨日波动不超过 20% |
我曾经在做一个金融项目时,把 CROSS_TABLE 和 FIELD_CHECK 混在一起定义,结果表达式解析器写得像意大利面条。后来我强制要求:每种规则类型对应一个独立的解析器实现,代码瞬间清爽了。
4.3 规则表达式语法设计:核心中的核心
表达式是规则引擎的大脑。我见过最糟糕的设计,是把表达式写成字符串,然后用 eval 执行——嗯,你懂的,那是灾难。我推荐用嵌套的 JSON 结构来表达逻辑,既安全又好解析。
4.3.1 基本操作符
{
"operator": "and",
"operands": [
{
"operator": "gte",
"field": "age",
"value": 18
},
{
"operator": "lte",
"field": "age",
"value": 60
}
]
}
这个结构表达的是:age >= 18 AND age <= 60。每个操作符都是一个节点,可以无限嵌套。我建议你只支持有限的操作符集合,别想着支持所有 SQL 操作符——没必要。
我建议的操作符清单:
- 比较类:eq, neq, gt, gte, lt, lte
- 逻辑类:and, or, not
- 集合类:in, not_in, between
- 空值类:is_null, is_not_null
- 字符串类:contains, starts_with, ends_with, regex
4.3.2 字段引用与函数
规则里经常需要引用字段、调用函数。我设计了一套引用语法:
{
"operator": "gte",
"field": "order_amount",
"value": {
"function": "avg",
"field": "order_amount",
"window": "last_30_days"
}
}
这个表达的意思是:当前订单金额 ≥ 过去 30 天的平均订单金额。函数调用也是 JSON 结构,而不是字符串拼接。为什么?因为这样解析器可以直接递归处理,不需要写复杂的字符串解析逻辑。
避坑指南:我曾经在表达式里支持了自定义函数,结果业务方写了一个死循环函数,直接把规则引擎跑挂了。后来我加了两个限制:
- 函数必须有超时机制(默认 5 秒)
- 函数不能修改外部状态(纯函数)
4.4 阈值与严重级别:告警的开关
阈值不是所有规则都需要,但一旦需要,它决定了「这条规则到底算不算触发」。我见过有人把阈值和表达式混在一起,结果逻辑变得特别拧巴。
我建议把阈值独立出来,专门处理「量」的概念:
{
"threshold": {
"type": "RATE", // COUNT | RATE | VALUE
"operator": "gte", // 触发条件
"value": 0.05, // 5% 的错误率
"window": "1h" // 统计窗口
}
}
严重级别我一般分四级,对应不同的响应策略:
| 级别 | 颜色 | 响应方式 | 示例 |
|---|---|---|---|
| CRITICAL | 红色 | 立即告警 + 阻断流程 | 主键重复、金额为负 |
| HIGH | 橙色 | 立即告警 + 人工确认 | 字段缺失率 > 10% |
| MEDIUM | 黄色 | 定时汇总告警 | 格式不规范 |
| LOW | 蓝色 | 记录日志,周报体现 | 轻微偏差 |
我个人习惯把 CRITICAL 和 HIGH 的规则数量控制在 20% 以内。为什么?因为告警太多,团队会麻木。你想想看,如果每天收到 100 条 CRITICAL 告警,最后大家连看都不看了。
4.5 规则定义的整体架构
下面这张图,是我在项目中沉淀下来的规则定义架构。它把规则拆成了四个层次,每一层只关心自己的事。
你看,从上到下,信息越来越具体。元信息层告诉引擎「这条规则是谁」;表达式层告诉引擎「怎么算」;阈值层告诉引擎「算到什么程度算触发」;动作层告诉引擎「触发了怎么办」。每一层都可以独立升级,互不影响。
我的经验:在设计规则定义语言时,一定要先写 JSON Schema 校验文件。这样规则一提交,就能自动校验结构对不对,而不是等到运行时才发现。我一般用 https://json-schema.org/draft/2020-12/schema 这个版本,支持条件校验和递归结构。