可行,但必须将DSL规则以BSON嵌套结构存储而非字符串;否则无法查询、索引、调试、版本回滚。应将trigger、conditions、actions等拆为原生字段,用Date类型存时间,关键字段建前缀索引,并通过version、scope、effective_at保障可追溯与灰度控制。

直接说结论:用 BSON 存 DSL 规则可行,但必须放弃“把整个规则当字符串塞进去”的偷懒做法;否则后续查询、调试、索引、版本回滚全都会出问题。
为什么不能把规则存成 rule_body: "{...}" 字符串
表面上看,rule_body 存一个 JSON 字符串最省事。但实际踩坑点极多:
- 无法用
$expr或聚合管道对规则内部字段做条件判断(比如“只查trigger.type === "http"的规则”) - 没法给
trigger.timeout_ms单独建索引,慢查询无解 - 规则校验、语法高亮、IDE 自动补全全部失效——你得自己写 parser
- diff 工具看不出是哪条子规则改了,CI/CD 回滚时容易误操作
BSON 原生支持的嵌套结构怎么映射 DSL 语义
MongoDB 的 BSON 天然支持嵌套文档、数组、日期、二进制等类型,DSL 中的关键语义应直接转为字段,而非字符串解析:
-
trigger拆成对象:{ type: "event", source: "kafka", topic: "user-login" } -
conditions用数组存多个原子条件,每个含field、op("$gt"/"$in")、value,便于$filter遍历 -
actions同理,避免拼接脚本字符串;如需执行 JS,明确标记type: "js_eval"+script: "return doc.score > 80",并限制运行沙箱 - 时间字段统一用
Date类型(不是字符串),方便$dateDiff和 TTL 索引
示例文档片段:
{
"_id": "rule_abc123",
"name": "high-risk-login",
"enabled": true,
"trigger": { "type": "event", "source": "auth-service" },
"conditions": [
{ "field": "ip_geo.country", "op": "$eq", "value": "CN" },
{ "field": "fail_count", "op": "$gt", "value": 5 }
],
"actions": [
{ "type": "alert", "channel": "slack", "template_id": "ALERT-001" }
],
"updated_at": { "$date": "2026-05-10T14:22:00.000Z" }
}
聚合阶段如何安全地“执行”规则逻辑
别在应用层做完整解释器。MongoDB 聚合本身就能承担部分规则计算:
- 用
$switch+$cond实现简单分支,替代 if-else 字符串解析 -
$reduce配合$and/$or可批量评估conditions数组是否全满足 - 关键字段(如
trigger.source、conditions.0.field)必须建前缀索引,否则$match阶段会全表扫 - 禁止在
$function中调用外部 API 或读取集合——它只允许纯函数式计算,且 MongoDB 7.0+ 才支持
版本控制与灰度上线的实际约束
规则变更不是发版,而是数据变更,必须带可追溯性:
- 每条规则文档加
version字段(数字递增),不覆盖旧文档,新规则写新_id或用is_latest: true标记 - 加
scope字段(如"env:staging"、"tenant:corp-a"),避免一条规则误触全量流量 - 上线前用
explain("executionStats")检查聚合计划,确认stage中出现IXSCAN而非COLLSCAN - 监控
db.currentOp({ "secs_running": { "$gt": 2 } }),聚合里嵌套太深或$function运行超时会卡住连接
最易被忽略的一点:BSON 的 ObjectId 是时间戳开头,但规则启用时间 ≠ 创建时间。务必用显式的 effective_at 字段控制生效时刻,否则凌晨三点自动启用一条错误规则,没人能睡安稳。











