Camunda DMN 决策引擎实战:独立运行与 BPMN 业务规则任务集成指南
【免费下载链接】camunda-bpm-platformCamunda 7 CE is End of Life (EoL). Please check out Camunda 8 instead (https://github.com/camunda/camunda) or read about Camunda 7 Enterprise End of Life (https://camunda.com/blog/2025/02/camunda-7-enterprise-end-of-life-extension/) – Camunda 7 CE was a flexible framework for workflow and decision automation using BPMN and DMN.项目地址: https://gitcode.com/GitHub_Trending/ca/camunda-bpm-platform
camunda-engine-dmn是 Camunda 7 平台中基于 Java 编写的轻量级 DMN(Decision Model and Notation)决策执行引擎,能够解析 DMN 决策模型并求值决策。本文将围绕 engine-dmn/README.md 的核心内容展开:先讲解如何以独立(Standalone)方式把引擎引入 Maven 工程并用纯 Java API 解析、求值决策;再演示如何让 BPMN 流程中的业务规则任务(Business Rule Task)通过camunda:decisionRef无缝引用 DMN 决策,将决策逻辑编排进工作流。读完本文,你将掌握 DMN 引擎的最小可运行工程搭建、决策表求值结果 API、底层求值与命中策略实现,以及它与 Camunda 流程引擎的完整集成链路。
一、模块定位:可独立运行、也可与 BPMN/CMMN 组合使用
根据 engine-dmn/README.md 的定位说明,该决策引擎以 Java 实现,核心价值在于"轻量"(Lightweight Execution Engine for DMN),并且既可以与 BPMN、CMMN 无缝组合使用,也可以完全独立运行。
从仓库结构看,engine-dmn模块是一个由多个子模块组成的多模块工程:
engine-dmn/engine:引擎主体,提供DmnEngine接口、默认实现与全部内部机制;engine-dmn/feel-api:FEEL 表达式的 SPI 接口(FeelEngine、FeelEngineFactory);engine-dmn/feel-juel:将 FEEL 语法翻译为 JUEL 求值的兼容实现;engine-dmn/feel-scala:对 Scala FEEL Engine 的集成实现(参见 feel-scala/README.md),是现代版本默认使用的 FEEL 引擎。
从 engine-dmn/engine/pom.xml 的依赖声明可以印证引擎的组成:它依赖camunda-dmn-model(DMN 模型 API)、camunda-engine-feel-api、camunda-engine-feel-juel、camunda-engine-feel-scala、feel-engine(scala-shaded)以及camunda-juel(表达式语言求值)、camunda-commons-typed-values(类型化变量)与camunda-commons-utils。这意味着一个引擎实例内部实际串联起了 DMN 模型解析、表达式求值与类型化变量三大能力。
二、独立使用:三分钟跑通一个 DMN 引擎
2.1 引入 Maven 坐标
独立使用方式下,只需在项目中加入如下依赖(原文档示例,groupId 为org.camunda.bpm.dmn):
<dependency> <groupId>org.camunda.bpm.dmn</groupId> <artifactId>camunda-engine-dmn</artifactId> <version>${version.camunda}</version> </dependency>${version.camunda}需要替换为具体的版本号。需要说明的是,当前仓库的engine-dmn/engine/pom.xml中版本为7.24.0-SNAPSHOT,且注明7.24.0 是 Camunda 7 社区版在 Maven Central 发布的最后一个版本,后续该构件不会再发布新版本(企业版提供扩展维护),使用时应留意这一版本前提。
2.2 编写第一个求值程序
原文档给出了一个完整的独立使用示例,核心步骤分为"构建引擎 → 解析决策 → 准备输入数据 → 求值决策表"四步:
public class DmnApp { public static void main(String[] args) { // configure and build the DMN engine DmnEngine dmnEngine = DmnEngineConfiguration.createDefaultDmnEngineConfiguration().buildEngine(); // parse a decision DmnDecision decision = dmnEngine.parseDecision("orderDecision", "CheckOrder.dmn"); Map<String, Object> data = new HashMap<String, Object>(); data.put("status", "gold"); data.put("sum", 354.12d); // evaluate a decision DmnDecisionTableResult result = dmnEngine.evaluateDecisionTable(decision, data); } }各步骤的关键点如下:
构建引擎:
DmnEngineConfiguration.createDefaultDmnEngineConfiguration().buildEngine()。从源码看,DmnEngineConfiguration.java 是抽象类,createDefaultDmnEngineConfiguration()返回默认实现DefaultDmnEngineConfiguration,buildEngine()在 DefaultDmnEngineConfiguration.java 中会先调用init()(依次初始化指标收集器、决策表求值监听器、决策求值监听器、脚本引擎解析器、表达式语言默认值、EL Provider 与 FEEL 引擎),随后返回new DefaultDmnEngine(this)。解析决策:
parseDecision("orderDecision", "CheckOrder.dmn")。其中orderDecision是 DMN 文件中<dmn:decision>元素的id属性(即决策 key),第二个参数可以是InputStream或DmnModelInstance。DmnEngine.java 中定义了完整的 API 面,除parseDecision外还提供:parseDecisions(...):解析文件中的全部决策;parseDecisionRequirementsGraph(...):解析决策需求图(DRG);evaluateDecisionTable(...):以决策表结果形式求值;evaluateDecision(...):求值任意支持的决策逻辑(决策表、字面量表达式等),返回更通用的DmnDecisionResult。 若给定的 key 找不到对应决策,DefaultDmnEngine.java 会抛出unableToFindDecisionWithKey异常。
准备输入数据:普通
Map<String, Object>即可,引擎内部会通过Variables.fromMap(variables).asVariableContext()将其转换为类型化的VariableContext,供表达式求值使用。求值决策表:
evaluateDecisionTable返回DmnDecisionTableResult。注意该方法要求目标决策必须实现为决策表(decision table),否则抛出decisionIsNotADecisionTable异常。
2.3 读取求值结果:DmnDecisionTableResult API
evaluateDecisionTable的返回值是DmnDecisionTableResult(本质是List<DmnDecisionRuleResult>)。DmnDecisionTableResult.java 定义了以下便捷方法:
| 方法 | 语义 |
|---|---|
getFirstResult() | 返回第一条命中的决策规则结果,无命中则返回 null |
getSingleResult() | 返回唯一命中结果,若命中多于一条则抛出DmnEngineException |
collectEntries(outputName) | 按输出名收集所有命中规则在该输出列上的值 |
getResultList() | 返回所有命中规则的输出名→值映射列表 |
getSingleEntry() | 断言"仅一条命中且仅一个输出",返回该输出值 |
getSingleEntryTyped() | 同getSingleEntry(),但返回类型化值TypedValue |
在"唯一命中 + 单输出"的典型场景(例如 UNIQUE 命中策略的审批人决策)中,一行result.getSingleEntry()即可取回最终决策值。
三、决策表求值原理:输入列逐列过滤、命中策略收敛
为了让"求值"不只是黑盒调用,这里结合源码补充其底层实现。DecisionTableEvaluationHandler.java 是决策表求值的核心处理器,其evaluate流程为:
- 逐输入列求值:对每个输入列,先求值输入表达式(Input Expression),得到类型化输入值(
TypedValue),并把当前输入值注入局部VariableContext(可通过输入列的inputVariable在规则条件中引用); - 逐列过滤规则:从全部规则开始,用每一列的输入项(Input Entry,即条件表达式)对候选规则做过滤,只有条件求值为
true的规则进入下一轮,最终得到全部匹配规则(Matching Rules); - 求值输出:对匹配规则逐列求值输出项(Output Entry),并将原始值按输出列的类型定义(Type Definition)转换为类型化值(可参考
type包下的StringDataTypeTransformer、IntegerDataTypeTransformer、BooleanDataTypeTransformer、DateDataTypeTransformer、DoubleDataTypeTransformer、LongDataTypeTransformer等内置类型转换器); - 应用命中策略:调用决策表配置的
HitPolicyHandler收敛匹配规则(见下文); - 通知监听器:向所有注册的决策表求值监听器广播
DmnDecisionTableEvaluationEvent。
其中输入项(条件)若为 FEEL 表达式,会走evaluateFeelSimpleUnaryTests,即用 FEEL 引擎的evaluateSimpleUnaryTests求值简单一元测试(simple unary tests);空白输入项恒视为true。
3.1 命中策略(Hit Policy)全景
命中策略决定多条规则命中时如何处理输出。DefaultHitPolicyHandlerRegistry.java 中的默认注册表完整覆盖了 DMN 规范定义的策略:
| Hit Policy | 聚合器 | 行为 |
|---|---|---|
| UNIQUE | 无 | 恰好命中一条规则,否则异常 |
| FIRST | 无 | 返回命中顺序中第一条规则输出 |
| ANY | 无 | 多条命中时要求所有输出一致,否则抛DmnHitPolicyException |
| RULE ORDER | 无 | 返回所有命中规则的输出(保持规则顺序) |
| COLLECT | 无 | 返回所有命中规则的输出 |
| COLLECT | COUNT | 返回命中规则数量 |
| COLLECT | SUM | 返回命中规则输出的总和 |
| COLLECT | MIN | 返回命中规则输出的最小值 |
| COLLECT | MAX | 返回命中规则输出的最大值 |
以 ANY 为例,AnyHitPolicyHandler.java 在apply中会比较所有匹配规则的输出映射:若全部相等则只保留第一条并返回;若存在不等则抛出anyHitPolicyRequiresThatAllOutputsAreEqual异常。该注册表支持通过addHandler扩展自定义命中策略处理器(SPI 位于impl.spi.hitpolicy)。
四、表达式语言与引擎配置
4.1 默认表达式语言
DMN 引擎支持 FEEL 与 JUEL 两类表达式。DefaultDmnEngineConfiguration.java 定义了四类表达式语言默认值:
defaultInputExpressionExpressionLanguage:输入表达式;defaultInputEntryExpressionLanguage:输入项(条件);defaultOutputEntryExpressionLanguage:输出项(结论);defaultLiteralExpressionLanguage:字面量表达式。
在不开启 FEEL 遗留行为时,四者默认均为 FEEL;若设置enableFeelLegacyBehavior(true),则输入表达式与输出项回退为 JUEL,仅输入项保持 FEEL。注意表达式若在 DMN 文件中显式声明了expressionLanguage,将优先使用显式声明,配置值仅作用于未声明的表达式。
4.2 引擎默认依赖链
从 engine-dmn/engine/pom.xml 可以看到 FEEL 支持的完整依赖链:默认使用feel-engine(scala-shaded)与camunda-engine-feel-scala;而camunda-engine-feel-juel则作为 FEEL 遗留行为的实现(对应 DefaultDmnEngineConfiguration.java 中enableFeelLegacyBehavior为 true 时使用FeelEngineFactoryImpl,否则使用ScalaFeelEngineFactory的逻辑)。camunda-juel为 JUEL 表达式提供底层 EL 求值能力。
4.3 常用可配置项
DmnEngineConfiguration(及默认实现DefaultDmnEngineConfiguration)还支持以下常用扩展点:
- 指标收集器:
engineMetricCollector(...),默认实现为DefaultEngineMetricCollector,内部以AtomicLong统计累计执行的决策实例数(executed decision instances)与决策元素数(executed decision elements),并提供clearExecutedDecisionInstances()/clearExecutedDecisionElements()清零方法(见 DefaultEngineMetricCollector.java); - 求值监听器:
customPreDecisionTableEvaluationListeners(...)/customPostDecisionTableEvaluationListeners(...),以及决策级customPreDecisionEvaluationListeners(...)/customPostDecisionEvaluationListeners(...),可用于审计、日志或指标采集; - 脚本引擎解析器:
scriptEngineResolver(...),用于解析表达式语言对应的脚本引擎; - EL Provider:
elProvider(...),默认JuelElProvider; - FEEL 自定义函数:
feelCustomFunctionProviders(...),向 Scala FEEL 引擎注册自定义函数; - 转换器:
transformer(...),默认DefaultDmnTransformer,负责把 DMN 模型实例转换为引擎内部结构; - 空白输出处理:
setReturnBlankTableOutputAsNull(true)时,空白输出项也作为null写入结果(默认策略是丢弃空白输出项)。该选项有专门的测试类 ReturnBlankTableOutputAsNullTest.java 覆盖。
配置接口的完整方法清单参见 DmnEngineConfiguration.java,引擎的解析/求值 API 参见 DmnEngine.java。
五、在 BPMN 流程中实现业务规则任务
独立运行之外,更常见的生产场景是把 DMN 决策嵌入 BPMN 流程。原文档的第二部分给出了完整方案。
5.1 依赖与准备
在流程引擎应用中,需要引入流程引擎本体与内存数据库(示例中 H2 仅用于测试作用域):
<dependency> <groupId>org.camunda.bpm</groupId> <artifactId>camunda-engine</artifactId> <version>${version.camunda}</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.3.168</version> <scope>test</scope> </dependency>5.2 在 BPMN 中引用 DMN 决策
在 BPMN 流程文件里声明一个业务规则任务(Business Rule Task),并通过 Camunda 扩展属性camunda:decisionRef指向 DMN 决策:
<bpmn:businessRuleTask id="assignApprover" camunda:decisionRef="invoice-assign-approver" camunda:resultVariable="approverGroups" name="Assign Approver Group(s)"> </bpmn:businessRuleTask>camunda:decisionRef:必填,值为 DMN 文件中决策的id(注意是id而非name),即invoice-assign-approver;camunda:resultVariable:可选,指定将决策结果保存到流程变量名,示例中为approverGroups;不声明时结果默认写入流程变量decisionResult。
对应的 DMN 文件片段:
<dmn:decision id="invoice-assign-approver" name="Assign Approver"> ... </dmn:decision>从流程引擎的解析源码看,camunda:decisionRef的处理位于 BpmnParse.java:解析器读取CAMUNDA_BPMN_EXTENSIONS_NS命名空间下的decisionRef属性,并将其包装为参数值提供者(ParameterValueProvider),同时支持以下配套绑定属性:
camunda:decisionRefBinding:绑定方式(latest/deployment/version/versionTag);camunda:decisionRefVersion:结合version绑定指定决策版本;camunda:decisionRefVersionTag:结合versionTag绑定指定版本标签;camunda:decisionRefTenantId:指定多租户场景下的租户。
5.3 部署并启动流程
最后,把 BPMN 文件与 DMN 文件作为一个 Deployment 一起部署,再按流程 key 启动流程实例:
public class App { public static void main(String[] args) { ProcessEngine processEngine = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration() .buildProcessEngine(); try { processEngine.getRepositoryService() .createDeployment() .name("invoice deployment") .addClasspathResource("invoice.bpmn") .addClasspathResource("assign-approver-groups.dmn") .deploy(); processEngine.getRuntimeService() .startProcessInstanceByKey("invoice", createVariables() .putValue("invoceNumber", "2323")); } finally { processEngine.close(); } } }要点说明:
createStandaloneInMemProcessEngineConfiguration()创建基于内存数据库的独立流程引擎,适合测试与演示;addClasspathResource同时注册 BPMN 与 DMN 资源,流程引擎会在部署阶段解析 DMN 文件并建立decisionRef与决策 id 的映射;startProcessInstanceByKey("invoice", ...)中的invoice是 BPMN 流程的id,启动后流程流转至业务规则任务时会自动执行对应的 DMN 决策,并把结果写入resultVariable指定的流程变量;finally中的processEngine.close()用于释放引擎资源。
5.4 组合使用时的求值结果形态
需要留意的是,当 DMN 决策被流程引擎调用时,引擎内部同样走DmnEngine的求值链路(决策表求值 → 命中策略收敛 → 监听器通知),最终结果以DmnDecisionTableResult形式映射为流程变量。因此,第四节中关于命中策略与输出行为的说明,同样适用于 BPMN 集成场景。
六、版本说明与注意事项
- 当前仓库
engine-dmn/engine的版本为7.24.0-SNAPSHOT,根据 engine-dmn/engine/pom.xml 的说明,7.24.0 是 Camunda 7 社区版在 Maven Central 的最后一个发布版本,之后社区版不再发布新版本,如需要扩展维护请关注企业版方案;在实际项目中应把${version.camunda}替换为可用的正式版本号。 camunda:decisionRef引用的是 DMN 决策的id属性,拼写错误或文件中不存在该 id 时,求值/解析阶段会抛出异常(unableToFindDecisionWithKey)。evaluateDecisionTable只适用于决策表类型的决策;若决策以字面量表达式(literal expression)等其他决策逻辑实现,应使用evaluateDecision通用求值 API(见 DmnEngine.java)。
七、延伸阅读
- 引擎完整 API:DmnEngine.java
- 配置与构建:DmnEngineConfiguration.java、DefaultDmnEngineConfiguration.java
- 决策表求值实现:DecisionTableEvaluationHandler.java
- 命中策略注册表:DefaultHitPolicyHandlerRegistry.java
- 求值结果 API:DmnDecisionTableResult.java
- 引擎功能测试示例:DmnEngineApiTest.java、EvaluateDecisionTest.java、HitPolicyTest.java
- BPMN
decisionRef解析逻辑:BpmnParse.java
【免费下载链接】camunda-bpm-platformCamunda 7 CE is End of Life (EoL). Please check out Camunda 8 instead (https://github.com/camunda/camunda) or read about Camunda 7 Enterprise End of Life (https://camunda.com/blog/2025/02/camunda-7-enterprise-end-of-life-extension/) – Camunda 7 CE was a flexible framework for workflow and decision automation using BPMN and DMN.项目地址: https://gitcode.com/GitHub_Trending/ca/camunda-bpm-platform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考