82.7. Rule units in DRL rule sets
Rule units are groups of data sources, global variables, and DRL rules that function together for a specific purpose. You can use rule units to partition a rule set into smaller units, bind different data sources to those units, and then execute the individual unit. Rule units are an enhanced alternative to rule-grouping DRL attributes such as rule agenda groups or activation groups for execution control.
Rule units are helpful when you want to coordinate rule execution so that the complete execution of one rule unit triggers the start of another rule unit and so on. For example, assume that you have a set of rules for data enrichment, another set of rules that processes that data, and another set of rules that extract the output from the processed data. If you add these rule sets into three distinct rule units, you can coordinate those rule units so that complete execution of the first unit triggers the start of the second unit and the complete execution of the second unit triggers the start of third unit.
To define a rule unit, implement the RuleUnit
interface as shown in the following example:
Example rule unit class
package org.mypackage.myunit; public static class AdultUnit implements RuleUnit { private int adultAge; private DataSource<Person> persons; public AdultUnit( ) { } public AdultUnit( DataSource<Person> persons, int age ) { this.persons = persons; this.age = age; } // A data source of `Persons` in this rule unit: public DataSource<Person> getPersons() { return persons; } // A global variable in this rule unit: public int getAdultAge() { return adultAge; } // Life-cycle methods: @Override public void onStart() { System.out.println("AdultUnit started."); } @Override public void onEnd() { System.out.println("AdultUnit ended."); } }
In this example, persons
is a source of facts of type Person
. A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the decision engine uses to evaluate the rule unit. The adultAge
global variable is accessible from all the rules belonging to this rule unit. The last two methods are part of the rule unit life cycle and are invoked by the decision engine.
The decision engine supports the following optional life-cycle methods for rule units:
表82.1 Rule unit life-cycle methods
Method | Invoked when |
---|---|
| Rule unit execution starts |
| Rule unit execution ends |
|
Rule unit execution is suspended (used only with |
|
Rule unit execution is resumed (used only with |
| The consequence of a rule in the rule unit triggers the execution of a different rule unit |
You can add one or more rules to a rule unit. By default, all the rules in a DRL file are automatically associated with a rule unit that follows the naming convention of the DRL file name. If the DRL file is in the same package and has the same name as a class that implements the RuleUnit
interface, then all of the rules in that DRL file implicitly belong to that rule unit. For example, all the rules in the AdultUnit.drl
file in the org.mypackage.myunit
package are automatically part of the rule unit org.mypackage.myunit.AdultUnit
.
To override this naming convention and explicitly declare the rule unit that the rules in a DRL file belong to, use the unit
keyword in the DRL file. The unit
declaration must immediately follow the package declaration and contain the name of the class in that package that the rules in the DRL file are part of.
Example rule unit declaration in a DRL file
package org.mypackage.myunit unit AdultUnit rule Adult when $p : Person(age >= adultAge) from persons then System.out.println($p.getName() + " is adult and greater than " + adultAge); end
Do not mix rules with and without a rule unit in the same KIE base. Mixing two rule paradigms in a KIE base results in a compilation error.
You can also rewrite the same pattern in a more convenient way using OOPath notation, as shown in the following example:
Example rule unit declaration in a DRL file that uses OOPath notation
package org.mypackage.myunit unit AdultUnit rule Adult when $p : /persons[age >= adultAge] then System.out.println($p.getName() + " is adult and greater than " + adultAge); end
OOPath is an object-oriented syntax extension of XPath that is designed for browsing graphs of objects in DRL rule condition constraints. OOPath uses the compact notation from XPath for navigating through related elements while handling collections and filtering constraints, and is specifically useful for graphs of objects.
In this example, any matching facts in the rule conditions are retrieved from the persons
data source defined in the DataSource
definition in the rule unit class. The rule condition and action use the adultAge
variable in the same way that a global variable is defined at the DRL file level.
To execute one or more rule units defined in a KIE base, create a new RuleUnitExecutor
class bound to the KIE base, create the rule unit from the relevant data source, and run the rule unit executer:
Example rule unit execution
// Create a `RuleUnitExecutor` class and bind it to the KIE base: KieBase kbase = kieContainer.getKieBase(); RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase ); // Create the `AdultUnit` rule unit using the `persons` data source and run the executor: RuleUnit adultUnit = new AdultUnit(persons, 18); executor.run( adultUnit );
Rules are executed by the RuleUnitExecutor
class. The RuleUnitExecutor
class creates KIE sessions and adds the required DataSource
objects to those sessions, and then executes the rules based on the RuleUnit
that is passed as a parameter to the run()
method.
The example execution code produces the following output when the relevant Person
facts are inserted in the persons
data source:
Example rule unit execution output
org.mypackage.myunit.AdultUnit started. Jane is adult and greater than 18 John is adult and greater than 18 org.mypackage.myunit.AdultUnit ended.
Instead of explicitly creating the rule unit instance, you can register the rule unit variables in the executor and pass to the executor the rule unit class that you want to run, and then the executor creates an instance of the rule unit. You can then set the DataSource
definition and other variables as needed before running the rule unit.
Alternate rule unit execution option with registered variables
executor.bindVariable( "persons", persons ); .bindVariable( "adultAge", 18 ); executor.run( AdultUnit.class );
The name that you pass to the RuleUnitExecutor.bindVariable()
method is used at run time to bind the variable to the field of the rule unit class with the same name. In the previous example, the RuleUnitExecutor
inserts into the new rule unit the data source bound to the "persons"
name and inserts the value 18
bound to the String "adultAge"
into the fields with the corresponding names inside the AdultUnit
class.
To override this default variable-binding behavior, use the @UnitVar
annotation to explicitly define a logical binding name for each field of the rule unit class. For example, the field bindings in the following class are redefined with alternative names:
Example code to modify variable binding names with @UnitVar
package org.mypackage.myunit; public static class AdultUnit implements RuleUnit { @UnitVar("minAge") private int adultAge = 18; @UnitVar("data") private DataSource<Person> persons; }
You can then bind the variables to the executor using those alternative names and run the rule unit:
Example rule unit execution with modified variable names
executor.bindVariable( "data", persons ); .bindVariable( "minAge", 18 ); executor.run( AdultUnit.class );
You can execute a rule unit in passive mode by using the run()
method (equivalent to invoking fireAllRules()
on a KIE session) or in active mode using the runUntilHalt()
method (equivalent to invoking fireUntilHalt()
on a KIE session). By default, the decision engine runs in passive mode and evaluates rule units only when a user or an application explicitly calls run()
(or fireAllRules()
for standard rules). If a user or application calls runUntilHalt()
for rule units (or fireUntilHalt()
for standard rules), the decision engine starts in active mode and evaluates rule units continually until the user or application explicitly calls halt()
.
If you use the runUntilHalt()
method, invoke the method on a separate execution thread to avoid blocking the main thread:
Example rule unit execution with runUntilHalt()
on a separate thread
new Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
82.7.1. Data sources for rule units
A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the decision engine uses to evaluate the rule unit. A rule unit can have zero or more data sources and each DataSource
definition declared inside a rule unit can correspond to a different entry point into the rule unit executor. Multiple rule units can share a single data source, but each rule unit must use different entry points through which the same objects are inserted.
You can create a DataSource
definition with a fixed set of data in a rule unit class, as shown in the following example:
Example data source definition
DataSource<Person> persons = DataSource.create( new Person( "John", 42 ), new Person( "Jane", 44 ), new Person( "Sally", 4 ) );
Because a data source represents the entry point of the rule unit, you can insert, update, or delete facts in a rule unit:
Example code to insert, modify, and delete a fact in a rule unit
// Insert a fact: Person john = new Person( "John", 42 ); FactHandle johnFh = persons.insert( john ); // Modify the fact and optionally specify modified properties (for property reactivity): john.setAge( 43 ); persons.update( johnFh, john, "age" ); // Delete the fact: persons.delete( johnFh );