第 11 章:规则引擎:业务正确性的实现机制

11.1 工程问题的本质:规则从哪来、改得动、挡得住

规则驱动的前提是规则本身可靠。三个问题决定规则体系的可信度:规则从哪来(业务逻辑与代码分离,还是散落各处)、规则改得动吗(修改要发版还是改配置)、规则挡得住吗(能否被角色覆盖、被模型绕过、被训练篡改)。

设计原理: 规则体系的核心矛盾是表达能力与可控能力的平衡。规则要有足够的表达力覆盖业务逻辑,又不能有任意能力(否则规则即代码、可训练即可注入)。框架的解法是两层分离:动作能力不可训练(内置 action 集合为 frozenset,运行时不可修改),动作组合可训练(engine_steps 步骤序列存配置,经审批修改)。规则可以训练逻辑,但不能训练出任意代码执行能力——这是可训练的安全边界。

11.2 多态裁决:合规检查与人工决策的分离

规则结果采用三态:pass(通过)/ warn(警告,需审批可继续)/ block(阻断,不可继续)。硬规则(is_hard=True)的阻断不可被任何角色覆盖。

原理: 三态将"合规检查"与"人工决策"分离为两个独立环节。block 是系统的最终裁决——合规检查认为不可行,不再咨询任何人;warn 是系统的建议——合规检查认为有风险,将决策权上交给审批链(第 13 章)。approver_role 字段指明由谁决策,使"谁有权通过"成为规则的一部分而非临时决定。

11.3 数据驱动规则:配置即治理

普通规则逻辑以 engine_steps(JSON 步骤序列)存储在数据库,规则参数(阈值、折扣上限、审批金额线)同样存配置。引擎按步骤顺序执行,任何步骤异常默认返回 block(安全优先)。

原理: 数据驱动规则的意义不在"少写代码",而在把规则的变更纳入治理。规则逻辑是业务事实的一部分,业务事实的变更应当可审计、可回滚、可审批。将规则从代码移入配置,变更路径就从"发版"变为"审批后热更新"——变更记录落在业务表而非 git 历史,审批责任落在审批链而非代码评审。

失败防范: 引擎执行异常默认 block(fail-closed)。这是刻意的选择:规则引擎不确定时,宁可阻断业务,也不静默放行。放行的错误是"一次违规操作",阻断的错误是"一次请求被拒"——前者成本远高于后者。

11.4 沙箱:表达能力的边界

表达式求值在受限沙箱中执行(AST 白名单):禁止 eval/exec/import/任意函数调用,字符串方法白名单仅对 str 实例开放,属性访问拒绝私有与魔术属性(堵 __class__.__bases__ 属性链逃逸)。

原理: 沙箱回答"规则能表达什么、不能表达什么":规则可以比较、取值、分支、汇总——即业务逻辑;规则不能导入模块、执行代码、访问内部对象——即系统能力。可训练的是业务判断,不可训练的是系统权限。 这一边界是"配置可训练"不至于演变为"配置即漏洞"的保障。

11.5 规则引擎的经典理论与 DSL 设计

11.5.1 执行模型:前向链与步骤序列

经典规则引擎(产生式系统)以前向链(forward chaining)执行:工作记忆(working memory)中的事实反复触发规则,直到没有新事实产生。前向链适合"事实驱动、规则间有级联依赖"的场景,代价是执行过程难以预测(规则触发顺序影响结果)。

本框架选择显式步骤序列(engine_steps 顺序执行)而非前向链,这是刻意的工程取舍:

维度前向链(产生式)显式步骤序列
执行可预测性低(触发顺序影响结果)高(按序执行)
调试难度高(需追踪规则触发)低(逐步可断)
表达力高(隐式级联)中(显式编排)
适合场景复杂推理、专家系统业务裁决、流程化校验

原理: 业务规则引擎的首要需求是可预测与可审计,而非表达力最大化。显式步骤序列牺牲了隐式级联的表达力,换取"每一步执行什么、产出什么"完全确定——这与第 8 章"可信结果 = 生成 × 裁决"的裁决确定性要求一致。RETE 算法优化的是前向链的匹配效率,在业务规则(数十条、级联有限)场景中,序列化的确定性价值高于匹配效率。

11.5.2 DSL 设计:语法受限与静态校验

规则 DSL(engine_steps)的工程要点是"语法受限 + 静态可校验":

原理: DSL 的价值不在"语法漂亮",而在把非法规则挡在运行时之外。运行时才暴露的规则错误是"配置型故障"——难定位、影响面大;提交时被拒的规则错误只是"一次编辑失败"。静态校验前置是配置治理的第一道闸门(第 14 章热更新之前)。

11.5.3 裁决路径的选择框架:规则 vs 决策表 vs 模型

业务裁决并非一律用规则。三类路径的适用边界:

路径适用场景不适用的场景
规则(if-then/engine_steps)决策条件明确、数量有限、需可解释条件组合爆炸(需决策表/规则表)
决策表/查找表条件维度多但枚举有界(折扣率×客户等级)条件无界(自由文本判定)
模型裁决无显式规则、需泛化(语义相似判断)需确定性、需审计(不可解释)

原理: 选择框架的依据是"决策是否可枚举、是否需要解释"。可枚举且有界 → 表驱动;可描述但复杂 → 规则引擎;不可描述 → 模型。先问可解释性需求,再选实现路径——审计需求为刚性约束时,模型裁决仅在无替代时引入,且须以 warn(人工复核)而非 block 形态参与(第 12 章审核链第 5 层)。

11.6 失败模式与权衡

失败形态根因设计应对
规则校验静默放行注册表为空、规则未注册进程级共享注册表 + 硬编码安全铁律
沙箱逃逸属性链访问魔术对象AST 白名单 + 私有属性拒绝
规则逻辑漂移双份实现(引擎与回退 check)引擎优先、回退兼容、参数签名适配
配置更新不生效缓存未失效进程级缓存 + 代次校验 + 热更新

权衡: 数据驱动换取可控性的代价是调试难度上升——规则错误不再表现为代码堆栈,而是步骤序列的失败。应对:引擎记录逐步执行日志与规则名,失败信息携带规则标识,将"配置型故障"的排查锚点还给规则本身。

11.7 本章小结