Invoke Decisions from Processes and Cases
BPMN & CMMN Integration
This section explains how to invoke DMN decision from BPMN and CMMN.
BPMN Business Rule Task
The BPMN business rule task can reference a deployed decision definition. The decision definition is evaluated when the task is executed.
<definitions id="taskAssigneeExample"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
targetNamespace="Examples">
<process id="process">
<!-- ... -->
<businessRuleTask id="businessRuleTask"
camunda:decisionRef="myDecision"
camunda:mapDecisionResult="singleEntry"
camunda:resultVariable="result" />
<!-- ... -->
</process>
</definitions>
For more information on how to reference a decision definition from a business rule task, please refer to the BPMN 2.0 reference.
DMN Decision Task
The CMMN decision task references a deployed decision definition. The decision definition is invoked when the task is activated.
<definitions id="definitions"
xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL"
xmlns:camunda="http://camunda.org/schema/1.0/cmmn"
targetNamespace="Examples">
<case id="case">
<casePlanModel id="CasePlanModel_1">
<planItem id="PI_DecisionTask_1" definitionRef="DecisionTask_1" />
<decisionTask id="DecisionTask_1"
decisionRef="myDecision"
camunda:mapDecisionResult="singleEntry"
camunda:resultVariable="result">
</decisionTask>
</casePlanModel>
</case>
</definitions>
For more information on how to reference a decision definition from a decision task, please refer to the CMMN 1.1 reference.
The Decision Result
The output of the decision, also called decision result, is a complex object of
type DmnDecisionResult
. Generally, it is a list of key-value pairs.
If the decision is implemented as decision table then each entry in the list represents one matched rule. The output entries of this rule are represented by the key-value pairs. The key of a pair is specified by the name of the output.
Instead, if the decision is implemented as decision literal expression then the list contains only one entry. This entry represents the expression value and is mapped by the variable name.
The type DmnDecisionResult
provides methods from the List
interface
and some convenience methods like getSingleResult()
or getFirstResult()
to
get the result of a matched rule. The rule results provide methods from the
Map
interface and also convenience methods like getSingleEntry()
or
getFirstEntry()
.
If the decision result contains only a single output value (e.g., evaluating a decision literal expression) then
the value can be retrieved from the result using the getSingleEntry()
method
which combines getSingleResult()
and getSingleEntry()
.
For example, the following code returns the output entry with name result
of
the only matched rule.
DmnDecisionResult decisionResult = ...;
Object value = decisionResult
.getSingleResult()
.getEntry("result");
It also provides methods to get typed output entries like
getSingleEntryTyped()
. Please refer to the User Guide for
details about typed values. A complete list of all methods can be found in the
Java Docs
.
The decision result is available in the local scope of the executing task as a
transient variable named decisionResult
. It can be passed into a variable by
using a predefined or a custom mapping of the decision result, if necessary.
Predefined Mapping of the Decision Result
The engine includes predefined mappings of the decision result for common use cases. The mapping is similar to an output variable mapping. It extracts a value from the decision result which is saved in a process/case variable. The following mappings are available:
Mapper | Result | Is suitable for |
---|---|---|
singleEntry | TypedValue | decision literal expressions and decision tables with no more than one matching rule and only one output |
singleResult | Map<String, Object> | decision tables with no more than one matching rule |
collectEntries | List<Object> | decision tables with multiple matching rules and only one output |
resultList | List<Map<String, Object>> | decision tables with multiple matching rules and multiple outputs |
Only the singleEntry
mapper returns a typed value that
wraps the value of the output entry and additional type information. The
other mappers return collections which contain the value of the output entries
as normal Java objects without additional type information.
Note that the mapper throws an exception if the decision result is not
suitable. For example, the singleEntry
mapper throws an exception if the
decision result contains more than one matched rule.
Limitations of Serialization
If you are using one of the predefined mappers singleResult
, collectEntries
or resultList
then you should consider the limitations of serialization.
To specify the name of the process/case variable to store the result of the
mapping, the camunda:resultVariable
attribute is used.
BPMN:
<businessRuleTask id="businessRuleTask"
camunda:decisionRef="myDecision"
camunda:mapDecisionResult="singleEntry"
camunda:resultVariable="result" />
CMMN:
<decisionTask id="DecisionTask_1"
decisionRef="myDecision"
camunda:mapDecisionResult="singleEntry"
camunda:resultVariable="result">
Name of the Result Variable
The result variable should not have the name decisionResult
since the
decision result itself is saved in a variable with this name. Otherwise an
exception is thrown while saving the result variable.
Custom Mapping of the Decision Result
Instead of a predefined mapping, a custom decision result mapping can be used to pass the decision result into variables.
Limitations of Serialization
If you pass a collection or a complex object to a variable then you should consider the limitations of serialization.
Custom Mapping to Process Variables
If a business rule task is used to invoke a decision inside a BPMN process, then the decision result can be passed into process variables by using an output variable mapping.
For example, if the decision result has multiple output values which should be saved in separate process variables this can be done achieved by defining an output mapping on the business rule task.
<businessRuleTask id="businessRuleTask" camunda:decisionRef="myDecision">
<extensionElements>
<camunda:inputOutput>
<camunda:outputParameter name="result">
${decisionResult.getSingleResult().result}
</camunda:outputParameter>
<camunda:outputParameter name="reason">
${decisionResult.getSingleResult().reason}
</camunda:outputParameter>
</camunda:inputOutput>
</extensionElements>
</businessRuleTask>
In addition to an output variable mapping, the decision result can also be processed by an execution listener, which is attached to the business rule task.
<businessRuleTask id="businessRuleTask" camunda:decisionRef="myDecision">
<extensionElements>
<camunda:executionListener event="end"
delegateExpression="${myDecisionResultListener}" />
</extensionElements>
</businessRuleTask>
public class MyDecisionResultListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) throws Exception {
DmnDecisionResult decisionResult = (DmnDecisionResult) execution.getVariable("decisionResult");
String result = decisionResult.getSingleResult().get("result");
String reason = decisionResult.getSingleResult().get("reason");
// ...
}
}
Custom Mapping to Case Variables
If a decision task is used to invoke a decision inside a CMMN case, the decision result can be passed to a case variable by using a case execution listener which is attached to the decision task.
<decisionTask id="decisionTask" decisionRef="myDecision">
<extensionElements>
<camunda:caseExecutionListener event="complete"
class="org.camunda.bpm.example.MyDecisionResultListener" />
</extensionElements>
</decisionTask>
public class MyDecisionResultListener implements CaseExecutionListener {
@Override
public void notify(DelegateCaseExecution caseExecution) throws Exception;
DmnDecisionResult decisionResult = (DmnDecisionResult) caseExecution.getVariable("decisionResult");
String result = decisionResult.getSingleResult().get("result");
String reason = decisionResult.getSingleResult().get("reason");
// ...
caseExecution.setVariable("result", result);
// ...
}
}
Limitations of the Serialization of the Mapping Result
The predefined mappings singleResult
, collectEntries
and resultList
map
the decision result to Java collections. The implementation of the collections
depends on the used JDK and contains untyped values as Objects. When a collection
is saved as process/case variable then it is serialized as object value because
there is no suitable primitive value type. Depending on the used object value
serialization, this can lead to deserialization problems.
In case you are using the default built-in object serialization, the variable can not be deserialized if the JDK is updated or changed and contains an incompatible version of the collection class. Otherwise, if you are using another serialization like JSON then you should ensure that the untyped value is deserializable. For example, a collection of date values can not be deserialized using JSON because JSON has no registered mapper for date by default.
The same problems can occur by using a custom output variable mapping since
DmnDecisionResult
has methods that return the same collections as the
predefined mappers. Additionally, it is not recommended to save a
DmnDecisionResult
or a DmnDecisionResultEntries
as process/case variable because
the underlying implementation can change in a new version of Camunda Platform.
To prevent any of these problems, you should use primitive variables only. Alternatively, you can use a custom object for serialization that you control by yourself.
Accessing Variables from Decisions
DMN Decision tables and Decision Literal Expressions contain multiple expressions which will be evaluated by the DMN engine. For more information about the expressions of a decision please see our DMN 1.1 reference. These expressions can access all process/case variables which are available in the scope of the calling task. The variables are provided through a read-only variable context.
As a shorthand, process/case variables can be directly referenced by name in
expressions. For example, if a process variable foo
exists, then this
variable can be used in an input expression, input entry and output entry of a decision table
by its name.
<input id="input">
<!--
this input expression will return the value
of the process/case variable `foo`
-->
<inputExpression>
<text>foo</text>
</inputExpression>
</input>
The returned value of the process/case variable in the expression will
be a normal object and not a typed value. If you want
to use the typed value in your expression, you have to get the variable
from the variable context. The following snippet does the same as the above
example. It gets the variable foo
from the variable context and returns
its unwrapped value.
<input id="input">
<!--
this input expression uses the variable context to
get the typed value of the process/case variable `foo`
-->
<inputExpression>
<text>
variableContext.resolve("foo").getValue()
</text>
</inputExpression>
</input>
Expression Language Integration
By default, the DMN engine uses JUEL as expression language for input expressions, output entries and literal expressions. It uses FEEL as expression language for input entries. Please see the DMN engine guide for more information about expression languages.
Accessing Beans
If the DMN engine is invoked by the Camunda Platform, it uses the same JUEL configuration as the Camunda Platform engine. Therefore, it is also possible to access Spring and CDI Beans from JUEL expressions in decisions. For more information on this integration, please see the corresponding section in the Spring and CDI guides.
Extending the Expression Language
Use of Internal API
These APIs are not part of the public API and may change in later releases.
It is possible to add own functions which can be used inside JUEL expressions. Therefore a new FunctionMapper has to be implemented. The function mapper than has to be added to the process engine configuration after it was initialized.
ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine
.getProcessEngineConfiguration();
processEngineConfiguration
.getExpressionManager()
.addFunctionMapper(new MyFunctionMapper());
This can be done, for example, by creating a process engine plugin.
Please note that these functions are available in all JUEL expressions in the platform, not only in DMN decisions.