DMN Elements
Decision Model and Notation (DMN) allows you to externalize business rules from BPMN processes into separate, version-controlled decision tables. Priostack supports DMN 1.3 decision tables: input entries are unary tests, and output entries are FEEL expressions evaluated by the engine's FEEL implementation (see the FEEL Reference for what it covers).
Decision Table Structure
A decision table consists of:
- Hit Policy - determines what happens when multiple rows match
- Input columns - the variables being tested
- Output columns - the values produced when a row matches
- Rules (rows) - each row is a combination of input conditions and output values
- Annotations - optional comments explaining each rule
Hit Policies
| Policy | Code | Description |
|---|---|---|
| UNIQUE | U | Only one rule may match. If multiple rules match, evaluation throws an error. Use when rules are mutually exclusive. |
| FIRST | F | The first matching rule (in row order) is returned. Rules are evaluated top to bottom; once a match is found, evaluation stops. |
| RULE ORDER | R | All matching rules are returned in the order they appear in the table. Output is a list. |
| COLLECT | C | All matching rules are returned as a list (order not guaranteed). Can be combined with aggregators: C+ (sum), C< (min), C> (max), C# (count). |
| ANY | A | Multiple rules may match, but all matching rules must produce the same output. Returns a single result. |
| OUTPUT ORDER | O | All matching rules are returned, ranked by the first output's declared output values (outputValues), most preferred first. Without declared values they stay in rule order. |
| PRIORITY | P | The single matching rule whose first output ranks highest in that output's declared output values. |
POST /api/v1/decisions/{decisionId}/evaluate returns one row: {"result":{...}} holds the first row of the result, with output values as strings. For RULE ORDER, OUTPUT ORDER and COLLECT without an aggregator, the other matching rows are not returned by that call.
Example: Order Discount Decision
The following decision table determines the discount percentage based on customer tier and order amount:
| Order Discount (Hit Policy: FIRST) | |||
|---|---|---|---|
| Rule | Customer Tier (Input) | Order Amount (Input) | Discount % (Output) |
| 1 | "gold" | > 500 | 15 |
| 2 | "gold" | [100..500] | 10 |
| 3 | "silver" | > 200 | 8 |
| 4 | "silver" | < 200 | 5 |
| 5 | - | - | 0 |
DMN XML for the above table
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/"
namespace="http://priostack.com"
name="Order Discount"
id="orderDiscount">
<decision id="discount" name="Order Discount">
<decisionTable id="decisionTable" hitPolicy="FIRST">
<input id="input1" label="Customer Tier">
<inputExpression id="inputExpr1" typeRef="string">
<text>customerTier</text>
</inputExpression>
</input>
<input id="input2" label="Order Amount">
<inputExpression id="inputExpr2" typeRef="number">
<text>orderAmount</text>
</inputExpression>
</input>
<output id="output1" label="Discount %" name="discountPercent" typeRef="number" />
<rule id="rule1">
<inputEntry id="in1"><text>"gold"</text></inputEntry>
<inputEntry id="in2"><text>> 500</text></inputEntry>
<outputEntry id="out1"><text>15</text></outputEntry>
</rule>
<rule id="rule2">
<inputEntry id="in3"><text>"gold"</text></inputEntry>
<inputEntry id="in4"><text>[100..500]</text></inputEntry>
<outputEntry id="out2"><text>10</text></outputEntry>
</rule>
<rule id="rule3">
<inputEntry id="in5"><text>"silver"</text></inputEntry>
<inputEntry id="in6"><text>> 200</text></inputEntry>
<outputEntry id="out3"><text>8</text></outputEntry>
</rule>
<rule id="rule4">
<inputEntry id="in7"><text>"silver"</text></inputEntry>
<inputEntry id="in8"><text>< 200</text></inputEntry>
<outputEntry id="out4"><text>5</text></outputEntry>
</rule>
<rule id="rule5">
<inputEntry id="in9"><text></text></inputEntry>
<inputEntry id="in10"><text></text></inputEntry>
<outputEntry id="out5"><text>0</text></outputEntry>
</rule>
</decisionTable>
</decision>
</definitions>
Deploy it with POST /api/v1/models, then evaluate. Each row of the table has a case with the answer the engine gives (FIRST hit policy):
curl -X POST https://priostack.com/api/v1/decisions/discount/evaluate \
-H "X-API-Key: $PRIOSTACK_KEY" \
-H "Content-Type: application/json" \
-d '{"customerTier":"silver","orderAmount":300}'
# {"result":{"discountPercent":"8"}}
# gold / 600 -> "15" (rule 1)
# gold / 500 -> "10" (rule 2)
# silver / 300 -> "8" (rule 3)
# silver / 100 -> "5" (rule 4)
# silver / 200 -> "0" (rule 5: 200 is neither > 200 nor < 200)
# gold / 50 -> "0" (rule 5)
Input Expressions
Input expressions are FEEL expressions evaluated against the current process variables. The result is compared to each rule's input entry.
| Input Entry Syntax | Meaning |
|---|---|
"gold" | Exact string match |
> 500 | Greater than 500 |
[100..500] | Between 100 and 500 (inclusive) |
"gold", "platinum" | One of these values |
not("cancelled") | Any value except "cancelled" |
< date("2026-01-01") | Before that date |
< dueDate | Less than the value of the variable dueDate |
(empty) or - | Matches any value (wildcard) |
A cell is a unary test against its column's input expression, not a full boolean expression. The deploy refuses (422) a cell such as status in ["pending", "review"] or dueDate < today(); even < today() is refused, because a function call is not a readable endpoint.
Referencing DMN from BPMN
Use a Business Rule Task in your BPMN to invoke a DMN decision. The engine evaluates it inline; no worker is involved. This snippet is a fragment for a document that declares xmlns:zeebe="http://camunda.org/schema/zeebe/1.0".
<businessRuleTask id="applyDiscount" name="Calculate Discount">
<extensionElements>
<zeebe:calledDecision decisionId="discount" />
</extensionElements>
</businessRuleTask>
decisionId names the decision; a resultVariable attribute is not read. The decision's outputs are written into the process under their own output names: after this task, the process variable discountPercent holds "10" (a string). Reference it in later FEEL expressions as discountPercent.
POST /api/v1/models, which detects the model kind (1 credit, refunded if the deploy fails); POST /api/v1/process-definitions deploys BPMN only and answers 422 for a DMN file. Deploy the decision before any process that calls it: a business rule task whose decision is missing puts the instance in INCIDENT.
FEEL in Decision Conditions
Input entries are unary tests; output entries and input expressions may be full expressions. When a condition needs a function, put the function in the column's input expression and let the cell test its result:
<!-- Input entry (column: status): one of several values -->
<inputEntry id="ie1"><text>"pending", "review"</text></inputEntry>
<!-- Output entry: compute a value -->
<outputEntry id="oe1"><text>amount * 0.1</text></outputEntry>
<!-- Date comparison: the function goes in the input expression... -->
<input id="overdue" label="Overdue">
<inputExpression id="overdueExpr" typeRef="boolean">
<text>date(dueDate) < today()</text>
</inputExpression>
</input>
<!-- ...and the cell tests its result -->
<inputEntry id="ie2"><text>true</text></inputEntry>
For a complete FEEL reference, see the FEEL Reference page.