droolsrule-engine

DROOLS DRL Rule Unit with RuleUnitData declaration isn't working


I have embedded DROOLS 8 engine in a simple standalone app for testing.

So far I was able to instantiate a KIE session, load and build a DRL file from disk.

If the DRL is a simple rule files, it works out of the box, when I try to implement a RuleUnit by DRL file (as in the guide https://docs.drools.org/8.44.0.Final/drools-docs/drools/language-reference/index.html#con-drl-rule-units_drl-rules)

package org.mortgages;
unit MortgageRules;

import org.drools.ruleunits.api.RuleUnitData;
import org.drools.ruleunits.api.DataStream;
import org.drools.ruleunits.api.DataSource;

declare Person
    name : String
    dateOfBirth : String
    address : String
end

declare MortgageRules extends RuleUnitData
  persons: DataStream<Person>
end

rule "Using a rule unit with a declared type"
  when
    $p : /persons[ name == "James" ]
  then   // Insert Mark, who is a customer of James.
    Person mark = new Person();
    mark.setName( "Mark" );
    persons.append( mark );
end

then I have an error, when it reaches the part of persons.append and the error is:

java.lang.NullPointerException: Cannot invoke "org.drools.ruleunits.api.DataStream.append(Object)" because "persons" is null

If the same class MortgageRules is provided by a java class implementation (handwritten in code)

public class MortgageRules implements RuleUnitData {
    @Getter
    private final DataStream<Person> persons = DataSource.createStream();
}

it works! seems like buildAll is not generating/instantiating the RuleUnitData piece of code.

Main is

KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
KieFileSystem kfs = ks.newKieFileSystem();

drlStream = ClassLoader.getSystemClassLoader().getResourceAsStream("mortgage.drl");
kfs.write("src/main/resources/KBase1/mortgage.drl", ks.getResources().newInputStreamResource(drlStream));

KieBuilder kb = ks.newKieBuilder(kfs);

kb.buildAll(); // kieModule is automatically deployed to KieRepository if successfully built.
if (kb.getResults().hasMessages(Message.Level.ERROR)) {
    throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}

KieContainer kContainer = ks.newKieContainer(kr.getDefaultReleaseId());
KieSession kSession = kContainer.newKieSession();

var eps = kSession.getEntryPoints();
var eventsStream = kSession.getEntryPoint("DEFAULT");

// Thread pushing Person instances in eventsStream

kSession.fireUntilHalt();

Solution

  • Firstly, if you want to use RuleUnit, you need to use RuleUnit APIs, which you can start with the archetype example https://docs.drools.org/8.44.0.Final/drools-docs/drools/getting-started/index.html#first-rule-project_getting-started

    Your Main class uses traditional APIs, so you would need to use traditional DRL syntax instead of RuleUnit. Unfortunately, the Drools 8 document doesn't have a chapter for "traditional DRL syntax", but it's fully supported. To learn the traditional DRL syntax, please refer to the Drools 7 docs (https://docs.drools.org/7.74.1.Final/drools-docs/html_single/#drl-rules-con_drl-rules). We plan to bring back the traditional DRL syntax chapter to the new version documentation. Sorry for your inconvenience.

    Secondary, the DRL declared Rule Unit is a bit tricky. Indeed, Drools 8 has a capability of generating RuleUnit java class by "declare", but it's meaningful only when you also auto-generate the application java code to use the generated RuleUnit, because the class doesn't exist until the project is built by maven. Such a use case can be found in drools-quarkus integration which also generates REST endpoint (https://github.com/apache/incubator-kie-drools/blob/main/drools-quarkus-extension/drools-quarkus-ruleunit-integration-test/src/main/resources/org/drools/quarkus/ruleunit/test/AlertingService.drl). I filed an issue to improve the document : https://github.com/apache/incubator-kie-drools/issues/5752

    Having said that, the guidance would be

    A) If you want to use RuleUnit with Quarkus and rely on auto generated REST endpoints, follow the drools-quarkus integration approach.

    B) If you want to use RuleUnit without Quarkus or not rely on auto generated REST endpoints, write Rule Unit java class instead of "declare". Also please make sure to use RuleUnit APIs.

    C) If you are fine with the traditional DRL syntax, use the traditional DRL syntax instead of Rule Unit. In this case, you can keep using the traditional APIs like the Main code.