Decisions (DMN)#
A business rule task evaluates a DMN decision: a decision table (or a literal expression) kept in its own file, next to the models. The process passes it data, and saves its result in the instance data.
A business rule task#
<bpmn:businessRuleTask id="check" name="Check the loan"
camunda:decisionRef="loanApproval" camunda:resultVariable="loan">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="credit_score">$data.creditScore</camunda:inputParameter>
<camunda:inputParameter name="income">$data.income</camunda:inputParameter>
</camunda:inputOutput>
</bpmn:extensionElements>
</bpmn:businessRuleTask>
| Attribute | |
|---|---|
camunda:decisionRef |
a decision file <name>.dmn.xml in the models folder (definitionsPath), or a decision's id in one of them |
camunda:resultVariable |
where the result goes in the instance data (data.loan); without it, the result is only the item's output |
camunda:mapDecisionResult |
how the result is shaped (below); without it, as the hit policy returns it |
The decision's inputs are the instance data, overridden by the task's input parameters. An input parameter is an
expression when it starts with $ ($data.income); without it, it is text.
Decision tables#
Each input column has an input expression: the name of the value it reads (income, or a FEEL expression
such as applicant.age). Its type (integer, number, string, boolean, date) converts the value
first: "800" from a form is 800 in an integer column.
Cells are FEEL unary tests:
| Cell | Matches |
|---|---|
- |
anything |
>= 750 |
a comparison |
[600..700], (600..700] |
a range, [ ] inclusive, ( ) exclusive |
"gold","platinum" |
one of a list |
not("rejected") |
anything but |
null |
no value |
Output cells are FEEL expressions: "Approved", 0.1 * income, income * 3. They can use the inputs.
Hit policies#
| Policy | Result |
|---|---|
| UNIQUE (default) | the one rule that matches; more than one is an error |
| FIRST | the first rule that matches, in table order |
| PRIORITY | the matching rule whose output comes first in the output's output values list |
| ANY | any matching rule; they must agree |
| OUTPUT ORDER | every matching rule, ordered by output values |
| RULE ORDER | every matching rule, in table order |
| COLLECT | every matching rule; with SUM, MIN, MAX or COUNT, that one number |
A single-hit table returns an object of the outputs ({ approvalStatus: "Approved", maxAmount: 50000 }), or null
when no rule matches. A multi-hit table returns a list, empty when none matches.
Shaping the result#
camunda:mapDecisionResult:
| Value | Result |
|---|---|
singleEntry |
the first output of the one matching rule: "Approved" |
singleResult |
the one matching rule's outputs: { approvalStatus: "Approved", maxAmount: 50000 } |
collectEntries |
the first output of each matching rule: ["Approved", "Conditional Approval"] |
resultList |
each matching rule's outputs, as a list |
singleEntry and singleResult are an error when more than one rule matches.
Several decisions in a file#
A file may hold several decisions. A decision that requires others (a decision requirements diagram) is evaluated
after them, each result available to it by the required decision's id and by its name, and a single result's outputs by
their own names. A camunda:decisionRef naming the file
evaluates its top decision, the one no other decision requires; naming a decision id evaluates that one.
Literal expression decisions (one FEEL expression instead of a table) are evaluated too.
Evaluating decisions from code#
import { DMNEngine } from 'bpmn-server';
const found = await DMNEngine.find(definitionsPath, 'loanApproval'); // a file name or a decision id
const engine = await new DMNEngine().load(found.filePath, found.decision.id);
const result = engine.evaluate({ credit_score: 780, income: 90000 });
DMNParser.check(await DMNParser.loadFile(file)) lists a file's problems without evaluating it.
Testing a decision#
The decision editor (/model/decision/<name>) has a Test tab, as the model editor has for processes. A test
gives the decision's inputs and what to check:
- an output: its name, or a path into the result (
approval.status;0.namein a multi-hit list), compared with=,<>,>,<,>=or<=; - the rules matched: how many (0: none matched).
A run evaluates the decision as a business rule task does, and shows the result and the rules that matched. A test with no checks passes when the decision evaluates. Use as checks makes a run's result the test's checks. Each test keeps its runs.
A test names a decision of the file, or uses the file's top decision. A file's tests are saved beside it, in
<name>.dmn.tests.json, and the Design dashboard runs them with the models' tests.
When something is wrong#
- A decision that can not be found, a cell that is not FEEL, or an evaluation error stops the task with a message naming the decision and the rule.
- The instance log says which rules matched.
verifyModelchecks business rule tasks against the decision files:decision-not-named,decision-not-found,decision-invalid(an unknown hit policy, wrong cell counts, FEEL that does not parse),decision-result-not-saved, anddecision-input-from-data(info: an input the task does not pass, read from the instance data).parameter-is-textwarns of an input parameter written like an expression but without its$.
Changed from 3.2#
The earlier evaluator read columns by their label and mis-read most FEEL. Tables that relied on it may need their
input expressions set and their cells written as FEEL. When no rule matches, a single-hit table now returns
null (it returned {}).