← 全部笔记
技术与工具

本体 DSL 设计笔记:让业务规则可编译、可回归测试

心晴2026-05-22约 6 分钟

把业务规则从制度文档里抽出来之后,第一个工程问题是:用什么格式存?直接写成某个规则引擎的私有配置,或者硬编码进业务系统,我都干过,也都后悔了。这篇笔记记录我现在的做法:设计一个平台中立的本体 DSL,让规则先"可描述",再"可编译",最后"可回归测试"。

为什么不直接用目标平台的格式

规则一旦用某个平台的私有格式写死,就和那个平台绑定了。换引擎要重写;想同时在审批流程和数据校验脚本里用同一条规则,只能维护两份,然后必然漂移。业务规则是业务的资产,不该长在某个技术栈上。

DSL 长什么样

我的 DSL 用声明式的 YAML 承载,核心就四类东西:实体(字段和类型)、规则(条件表达式 + 结论 + 出处)、例外(豁免条件)、参数(可调阈值)。一条规则大概长这样:

rule:
id: R-014
desc: 单笔金额超过阈值须双人复核
when: amount > params.dual_review_threshold
then: REQUIRE_DUAL_REVIEW
source: 《管理办法》第十二条第二款

几个设计决定,事后看都值得:

  • 表达式只允许白名单语法——比较、布尔运算、少数几个函数。表达能力弱是刻意的:弱到写不出"聪明"的规则,才可能被非工程师读懂、被静态检查覆盖;
  • 三值逻辑:字段缺失时表达式的结果不是 false,而是 UNKNOWN,最终判定转人工。这条是踩坑换来的——缺失当 false 处理,等于默许数据不全的单子溜过去;
  • 每条规则必须带 source,指回制度原文。规则可以被质疑,但质疑要落到条款上,而不是落到"当时是谁写的"。

编译到不同载体

DSL 本身不执行,执行靠编译。同一份本体,我目前会编译到三种载体:确定性规则引擎(生产判定用,解析表达式 AST 逐条求值)、数据质量检查脚本(离线批扫历史数据)、以及一份人类可读的规则手册(给业务方评审用,从 DSL 生成而不是手写,保证不漂移)。

单一事实源不是一句架构口号,而是"改一处、处处生效"的具体工程约束。

可回归测试是设计出来的

DSL 声明式、表达能力受限、无副作用,这三点让规则天然可测:给定输入单据,输出判定,是个纯函数。于是评测集可以和本体放在一起版本化,每次改规则先跑全量评测,全绿才允许发布。这部分展开又是一篇笔记,这里先立个 flag。

回头看,DSL 设计的关键不是语法多优雅,而是克制:少一分表达能力,多一分可验证性。业务规则这个领域,我宁可要笨而可靠。