Skip to main content

DRL

DRL — the Drools Rule Language — is the native language of the Drools rule engine. Where DMN gives business analysts a visual, standardized notation, DRL gives developers the full expressive power of the engine in plain text files that live in your source tree, next to your Java code, under version control.

A rule says when something is true, then do something. You never call a rule; you give the engine facts and it works out which rules match, in what order, and re-evaluates as those facts change.

A first rule

package org.acme.orders

import org.acme.orders.Order

rule "Free shipping over 100"
when
$order : Order( total > 100, freeShipping == false )
then
modify( $order ) { setFreeShipping( true ) };
end

when holds the conditions — patterns matched against the facts the engine knows about. Order( total > 100 ) reads as "there is an Order whose total is greater than 100"; $order binds the match so the then block can use it. modify changes a fact and tells the engine it changed, so any other rule that cares about freeShipping gets reconsidered. That is the part that makes a rule engine different from a pile of if statements: rules react to each other, and you never wrote the control flow.

Rule units

A loose bag of rules and a shared session works, but it gets hard to reason about at scale. Rule units are the modern way to organize DRL: a unit is a typed container that declares the data its rules operate on, so the rules and their inputs are one cohesive, injectable thing.

The data holder is a plain Java class:

public class HelloWorldUnit implements RuleUnitData {

private final DataStore<String> strings = DataSource.createStore();
private final List<String> results = new ArrayList<>();

public DataStore<String> getStrings() { return strings; }
public List<String> getResults() { return results; }
}

The DRL binds itself to that unit and navigates its data sources with OOPath expressions — the /strings[ ... ] syntax:

package org.acme;
unit HelloWorldUnit;

rule HelloWorld
when
/strings [ this == "Hello World" ]
then
results.add("it worked!");
end

And running it is three lines, with no session bootstrapping to speak of:

HelloWorldUnit unit = new HelloWorldUnit();
unit.getStrings().add("Hello World");

try (RuleUnitInstance<HelloWorldUnit> instance =
RuleUnitProvider.get().createRuleUnitInstance(unit)) {
instance.fire();
}

Rule units are also what Kogito generates REST endpoints from, so the same unit you unit-test locally becomes a decision service without rewriting it.

Beyond simple matching

DRL is a language, not a table format, and the interesting parts show up once the conditions stop being row-shaped.

Absence is a first-class condition. not, exists and forall let you match on facts that aren't there — something no lookup table expresses naturally:

rule "Flag unreviewed high-value orders"
when
$order : Order( total > 10000 )
not Review( order == $order )
then
$order.flagForReview();
end

Aggregation over the whole working memory, via accumulate:

rule "Bulk discount"
when
$customer : Customer()
Number( intValue >= 10 ) from accumulate(
$o : Order( customer == $customer, status == "OPEN" ),
count( $o ) )
then
$customer.applyBulkDiscount();
end

Queries ask the engine questions instead of firing actions, and they can recurse — which is how you get backward chaining:

query isContainedIn( String x, String y )
/locations[thing := x, location := y]
or
( /locations[z := thing, location := y] and isContainedIn(x, z;) )
end

Truth maintenance: facts inserted logically are retracted automatically when the reason for them stops holding, so you never write the cleanup pass.

Execution control when you need it — salience to order rules, no-loop to stop a rule re-triggering itself, agenda-group to partition the agenda into phases.

Events and time

Declare a type as an event and the same language becomes a complex event processing engine. Events carry timestamps and durations, they can be reasoned about with temporal operators, and they expire on their own:

declare TemperatureReading
@role( event )
end

rule "Sustained overheating"
when
$r : TemperatureReading( celsius > 90 ) over window:time( 10s )
from entry-point SensorStream
then
alerts.add( new Alert( $r ) );
end

over window:time( 10s ) slides a ten-second window over the stream, and from entry-point keeps that stream partitioned from the rest of working memory. Because events expire, long-running streams don't grow the session without bound.

Other ways to write the same rules

DRL text is one surface. The same semantics are reachable through decision tables in Excel (drools-decisiontables), rule templates, and the executable model API for building rules programmatically in Java — all of which compile down to the same engine.

Rules also compose with the rest of the platform: a BPMN business rule task in jBPM calls straight into a DRL rule set, in the same service, with no remote hop.

Resources

Drools documentation: the DRL language reference is the complete specification — every keyword, operator and conditional element. The rule engine guide covers how matching, the agenda and truth maintenance actually work underneath.

Ready to write some? Get started.