软件建模E08:用DMN真实执行资格规则与阈值变更
设备已经放行,距离预约开始还有一小时,是否允许进入下一步?如果政策要求至少提前一小时,答案是允许;政策改成两小时之后,同一输入就应被拒绝。用图表表达规则,必须能观察到这项变化影响实际求值结果。
本篇选择受限 DMN 决策表实验:两个输入、一个输出和三条规则。模型由真实 Camunda DMN 引擎解析执行,程序检查阈值边界、规则重叠和缺少输入的行为。样本没有启动完整流程平台,也没有调用租赁服务完成真实预订。
先确定需要执行的语义
一项租赁申请可以包含多种问题。领取、归还和通知需要描述先后动作;工作人员处理资料不完整的申请时,可能根据新材料选择后续工作;资格政策则根据输入条件得到判断。这些问题分别涉及流程、案例与决策建模,不应因为三者都能画图就互相替代。
当前问题只需要计算资格,没有等待、人工任务、消息关联或运行中的实例状态。因此实验选择 DMN 决策表,将操作顺序留在应用层。关于 BPMN 或 CMMN 的语义,本篇仅作范围区分,没有声称执行或验证过它们的引擎行为。OMG BPMN 2.0.2;OMG CMMN 1.1
模型使用 DMN 1.3 的 XML 命名空间。OMG 发布页同时列出规范文档与机器可读模式,这决定模型属于哪一代规范;Camunda 7.23.0 则决定本次使用哪一个实现。规范版本与引擎版本是两个独立标识,不能把数字相近理解为相互对应。OMG DMN 1.3
固定版本的目的在于让结果可复现,并不说明该引擎是最新版本或适合所有部署。样本只使用简单布尔条件、整数比较和字符串结果,结论覆盖这组表达式。复杂日期、外部函数和多决策依赖图都没有出现在实验输入中。
三条规则定义资格
models/E08/eligibility.dmn 声明 released 与 leadHours 两个输入,输出为 result。released 表示设备是否已被前置政策放行;leadHours 是已经计算好的提前小时数。这两个变量不负责查询设备状态或计算时区差值,调用方应当提供确定的值。
第一条规则匹配未放行设备,提前时间使用通配条件,输出 BLOCKED。第二条匹配已放行且提前时间小于一小时,输出 TOO_SOON。第三条匹配已放行且至少提前一小时,输出 ELIGIBLE。输出是资格结论,不能直接等同于预留成功。
flowchart TD
R[released 放行状态] --> D[eligibility 决策表]
H[leadHours 提前小时数] --> D
D --> B[false 及任意时间 BLOCKED]
D --> S[true 且小于一小时 TOO_SOON]
D --> E[true 且至少一小时 ELIGIBLE]
E --> A[应用继续检查日历并提交预留]
图中最后一步属于应用设计,没有由本实验执行。设备可能在资格计算之后被另一请求占用,资格结果也可能基于过时状态。真正提交预留时仍需主线的不变量检查和并发控制。把决策表结果命名为 SUCCESS,会误导调用方把政策通过理解成事务完成。
XML 对输入表达式和输出表达式显式指定 FEEL,整数比较中的小于号采用 XML 转义。程序将原文件读成字符串后交给引擎,既不手工解析规则来模拟求值,也不在 Java 中写一套相同条件当作引擎替身。
UNIQUE检查重叠,不承诺覆盖全部输入
模型的命中策略为 UNIQUE,要求同一次输入不能命中多条规则。官方教程说明如何设置该策略;本篇再用真实冲突输入验证引擎表现,而不是只根据名称推断结果。Camunda DMN教程
原始模型中,提前一小时只符合至少一小时的规则。反例把小于一小时改成小于等于一小时,保留另一条至少一小时不变。对于已放行且提前一小时,两条规则同时匹配;引擎抛出 DmnHitPolicyException,消息列出 soon 与 allowed 两条规则。
这次异常发生在求值时,不能据此声称模型在装载阶段就完成了所有重叠分析。脚本捕获预期引擎异常以核对负例;独立失败入口则让异常向外传播,进程返回一。两种运行都保留原始输出,避免捕获异常之后只留下一个看似成功的总状态。
UNIQUE 也没有要求每种输入必须至少匹配一条。实验只传 released 而缺少 leadHours 时,原始表返回空结果。这个可观察行为说明调用方必须显式处理无结果,不能未经检查就把单一结果读取接口的返回值当作业务允许。
缺少输入的观察限于当前引擎与当前表达式。它不是通用输入校验方案,更不能推导所有错误类型都会返回空集合。应用接口可以在调用前拒绝缺值,或者把无匹配翻译成明确的无法判定;采用哪种政策需要与领域用例保持一致。
一小时改为两小时
变化卡同时修改两条边界:小于一变为小于二,至少一变为至少二。程序从同一原始 XML 派生变更模型,保持放行条件和输出不变。两个阈值必须共同变化,只改其中一个可能产生重叠或空档。
原始表在提前零小时时输出 TOO_SOON,在一小时时输出 ELIGIBLE。变化后,一小时改为 TOO_SOON,两小时输出 ELIGIBLE。未放行设备仍输出 BLOCKED。这几项检查把政策变化限制在明确的输入范围,能够判断是否误伤前置放行条件。
flowchart LR
O[原XML 阈值一小时] --> T[同一输入 released=true 且一小时]
N[变更XML 阈值两小时] --> T
T --> A[原模型 ELIGIBLE]
T --> B[新模型 TOO_SOON]
N --> F[布尔状态乘零至三小时 共八组]
F --> C[每组恰好一个结果]
变更表进一步遍历两个放行值与零至三小时,共八组输入,每一组结果数量都等于一。有限覆盖让阈值周围的规则组合得到直接检查,但没有覆盖所有整数、空值、浮点数或错误字符串。把八组通过写成全输入域正确,会扩大证据范围。
字符串替换只用于这个固定教学模型,脚本不是通用 DMN 编辑器。生产修改应当通过模型 API 或受控编辑工具,保留模型版本并验证差异。若原文件将一小时表达式换一种写法,当前替换就可能失效;已有边界断言会暴露预期输出未变化。
真引擎的依赖与入口
实验锁定 camunda-engine-dmn 7.23.0,连同所需运行库共十七个 jar。依赖清单记录 Maven Central 固定路径及 SHA256;构建清单时还核对了下载缓存中的公开 SHA1 文件。运行入口检查每个缓存文件,摘要不符直接失败,不会继续使用损坏依赖。
Java 源文件通过 javac --release 21 -Xlint:all -Werror 编译,再用同一组依赖执行。编译结果放在临时目录,结束后删除,源码包不携带 class 文件或第三方 jar。首次复跑需要网络获取依赖,后续可复用已核验缓存。
本次实跑使用 Corretto 21.0.11。日志中保留了 SLF4J 未绑定实现的提示,这是依赖在未配置日志后端时发出的信息,不能删掉后宣称完全没有诊断输出。决策返回值和预期异常由程序直接记录,十五项断言全部通过,编译也没有被降级为忽略警告。
依赖可重现与业务可重现仍有不同边界。锁定 jar 可以减少实现变化,但若调用方传入的提前时间计算方式改变,同一真实预约仍可能产生不同输入。外部时钟、取整规则、时区和状态读取版本应由应用契约说明,本实验使用合成整数避免引入这些未建模变量。
把规则版本放进调用契约
一次资格判定至少需要保存模型版本、关键输入和结果,才便于解释后续争议。旧订单曾经在一小时阈值下通过,并不意味着切换到两小时政策后可以重新判定为从未有效。如何处理已接受申请属于迁移政策,不能通过覆盖模型文件隐式决定。
若新旧模型并行评估,可以针对同一输入比较输出差异,再决定发布范围。该实验已经展示单个边界输入的前后结果,但没有实现流量回放、灰度开关或运行中流程迁移。它提供可用于这些机制的决策函数和边界案例,没有将静态变更测试扩写成生产切换证据。
决策返回的字符串还应在应用边界翻译为明确结果类型。BLOCKED 可以要求设备重新评审,TOO_SOON 可以提示调整开始时间,无匹配需要单独的错误或待补材料状态。把三种情况都压成 false,会让界面和审计失去原因;直接暴露引擎异常则会把实现细节混入业务接口。
主线的 漂移往返与版本 讨论变更后的对应关系。这里保持模型是资格规则的事实源,Java 代码只提供场景、调用与断言。如果以后又在服务中复制同样阈值,必须有差异检查,否则两处政策可能独立漂移。
复跑
下载 实验包,将 JAVA_HOME 指向 Java 21,保留源码目录结构执行以下单行命令。缓存目录可以换成任意可写位置。
1 | |
正常运行输出十五项通过结果并返回零;命令末尾追加 overlap,引擎因 UNIQUE 冲突返回一。examples/software-modeling/evidence/E08/ 保存两种运行日志与退出码。包中模型、源码与依赖摘要足以重建本实验,下载失败、摘要不符或编译失败都会终止入口。






