Drools Canonical Model – Pure Java Rules

Rule engines, like Drools, typically  make use of a custom languages to define a set of rules. For example, he Drools compiler translates a drl file to an internal representation (the KiePackages) that is subsequently used to generate the ReteOO/Phreak network that will perform the rules evaluation.

This internal representation was never really intended to be generated or consumed by end users. This complication makes it difficult to write rules programmatically and the suggestion is instead to generate text rules at the DRL level.  This means that drl itself is currently the only practical formal notation to define a set of rules in Drools.

Drools internals were developed with several assumptions at the time, that are no longer true or desirable. Prior to Java 8, perm gen was a concern, so various solutions were utilized to address this – such as MVEL for reflective based evaluation. Java 8 now puts code on the heap, so this is no longer necessary.  A the engine level it also inspected and produced indexes, which tied it to the expressions produced by DRL – this makes polyglot impractical. Lastly it liberally uses classloaders and reflection, which makes it difficult to transpiler for execution on different environments.

An engine independent rule model

To overcome this limitation and offer the possibility of programmatically define a set of rule in pure Java we developed a model aimed to provide a canonical representation of a rule set and a fluent DSL to conveniently create an instance of this model. The model itself is totally independent from Drools and could in theory be re-used by other engines, It also introduces layers that fully separates the engine from having to be aware of any language. For example it will not inspect and generate indexes, instead it expects to be provided those indexes, from the layers above. Another advantage is it now means Drools has a developer friendly view of what it’s executing, as it’s all just pure low-level pojo rules.

This model, other than giving to all Java developers a clean way to write rules in plain Java, will also enable our team to experiment with new features faster, freeing us from the burden of also implementing the corresponding parser and compiler parts that integrate the new feature with the drl notation.
Let’s see a practical example of the model of a rule defined using the before mentioned DSL:

Rule rule = rule( "Persons older than Mark" )
        expr("exprA", markV, p -> p.getName().equals("Mark"))
            .indexedBy( String.class, ConstraintType.EQUAL, Person::getName, "Mark" )
            .reactOn( "name", "age" ),
        expr("exprB", olderV, p -> !p.getName().equals("Mark"))
            .indexedBy( String.class, ConstraintType.NOT_EQUAL, Person::getName, "Mark" )
            .reactOn( "name" ),
        expr("exprC", olderV, markV, (p1, p2) -> p1.getAge() > p2.getAge())
            .indexedBy( int.class, ConstraintType.GREATER_THAN, Person::getAge, Person::getAge )
            .reactOn( "age" )
    .then(on(olderV, markV)
         .execute((p1, p2) -> System.out.println( p1.getName() + " is older than " + p2.getName())));

This is the equivalent of the following rule expressed in drl:

rule "Persons older than Mark" when
    $p1 : Person(name == "Mark")
    $p2 : Person(name != "Mark", age > $p1.age)
    System.out.println($p2.getName() + " is older than " + $p1.getName());

It is evident that the DSL version is much more verbose and requires to specify many more details that conversely are inferred by the drools compiler when it parses the rule written in drl.
As anticipated this has been done on purpose because the model has to explicitly contain all the information required to generate the section of ReteOO/Phreak network intended to evaluate that rule, without the need of performing any bytecode inspection or using any other complex, brittle and non-portable introspection technique. In those simple examples the index contains the same logic as the expr, as per DRL expressions can contain more than just the index. The index would be inferred and implicitly added by the upper language layers.
To clarify this aspect let’s analyze a single LHS constraint in a bit more detail:

expr("exprA",                                                                 [1] 
     markV,                                                                   [2]
     p -> p.getName().equals("Mark") )                                        [3] 
    .indexedBy( String.class, ConstraintType.EQUAL, Person::getName, "Mark" ) [4]
    .reactOn( "name", "age" )                                                 [5]

In this statement you can notice the following parts
[1] This is a label for the constraint used to determine its identity. Two identical constraint must have the same label and two different ones must have different labels in order to let the engine properly implement the node sharing where possible. This label can be optionally omitted and in this case it will be generated from an automatic introspection of the bytecode of the lambda expression implementing the constraint. However, as anticipated, it’s preferable to avoid any introspection mechanism and then to explicitly provide a constraint label whenever is possible.
[2] This is the Variable defined before creating the rule and used to bind an actual fact with the formal parameter of the lambda expression used to evaluate a condition on it. It is the equivalent of the variable declaration in rule written with the drl notation.
[3] The lambda expression performing the constraint evaluation.
[4] The specification of the type of this constraint and how it has to be indexed by the engine.
[5] The name of the properties of the Person object for which this constraint has to be reactive.

Building and executing the model

You can programmatically add this and other rules of your rule base to a model:

Model model = new ModelImpl().addRule( rule );

Once you have this model, which again is totally independent from Drools algorithms and data structures, you can use a second project, this time dependent both on Drools and on the model itself, to create a KieBase out of it.

KieBase kieBase = KieBaseBuilder.createKieBaseFromModel( model );

What this builder internally does is recreating the KiePackages out of the model and then building them using the existing drools compiler. The resulting KieBase is identical to the one you may obtain by compiling the corresponding drl and then you can use it exactly in the same way, creating a KieSession out of it and populating it with the facts of your domain. Here you can find more test cases showing the Drools features currently supported by the model and the DSL constructs allowing to use them, while here you can find a more complete example.

Embedding the executable model inside the kjar

At the moment a kjar contains some pregenerated classes implementing the constraints and the consequences. However all of the drl files need to be parsed and compiled from scratch, making limited savings on building it from source only.The executable model will be also useful to speed this process up. Embedding the classes implementing the model of the rule base inside the kjar, makes possible to quickly recreate the KiePackages, instead of having to restart the whole compilation process from the drl files. A proof concept of how this could work is available here.

DRL compiler to Executable Model

We are working on a new compiler that will take existing DRL and directly output the executable model, which is stored in the kjar. This will result in much faster loading and building times. We are also looking into other ways to speed this process up, by pre-caching more information in the kjar, which can be determined during the initial kjar build.

Pojo, Polyglot and DRLX Rules

The executable model is low level and because you have to provide all the information it needs, this makes it a little verbose. This is done by design, to support innovation in the language level. While it is technically still pojo-rules, it is not desirable for everyday use. In the near future we will work on a less verbose pojo rules layer, that will use an annotation processor to inject things like indexes and reactOn. We also hope that it will result in several rule languages, not just one – scala rules, closure rules, mini-dsl rules etc. Nor is it necessary for a language to expose all of the engine capabilities, people can write mini-dsls exposing the subset.

Finally we are also working on a next generation DRL language (DRLX), that will be a superset of Java. This is still in the design phases and will not be available for some time, but we will publish some of the proposals for this, once an early draft spec is ready.


Comments are closed.