How to infere intermediate values in Drools rules

I’ve been recommended to use Drools for score calculation. I’m new to this framework, but after a small research I see that it’s indeed a good solution, because scoring rules are going to be updated/adjusted frequently and it’s easy (it seems) to write/change/update Drools rules.

Background: The scoring algorithm that I have to use calculates intermediate values and then based on these values calculates the final score. So there’s the model called Person, it has a lot of properties, some of them might be null or empty. And the scoring algorithm considers all these fields and makes final decision (score).

UPD: It’s possible to skip intermediate values calculation (in theory), but I’m 100% sure the rules will become unclear and messy.

The question is: How do I save(persist) these intermediate values between individual rules? From what I can see from the documentation, it’s not possible (?) or I’m missing something. There’s no such thing as variable in rules.

Ideally I would have some global variables which are accessible only on this rule set. They have initial values (like null or 0).

Let’s say I have calcIntrmdt1, calcIntrmdt2, calcIntrmdt3 and one rule calcFinalScore which is ran after all previous rules. How to pass to the calcFinalScore what the previous rules have calculated?

P.S. Maybe my whole approach is wrong, correct me if so

Answer

Drools does support “global variables” — called globals — but you can’t write rules against them. They’re generally discouraged, but back in the day it’s how you’d usually go about returning values from your rule set.

Here’s a simple example with a List as a global:

global java.util.List result;

rule "All people whose name starts with 'M' will attend"
when
  $person: Person( name str[startsWith] "M" )
then
  result.add($person);
end

List<Person> attendees = new ArrayList<>();

KieSession session = this.getSession();
session.insert(person);
session.insert(person1);
session.insert(person2);
session.insert(person3);
session.insert(person4);
session.setGlobal("result", attendees);
session.fireAllRules();

// at this point, 'attendees' is populated with the result of the rules

This won’t work for you, though because you can’t interact with these globals on the left hand side (“when”).


What you need, instead, is an intermediate object to haul around your intermediate calculations. Usually I’d suggest storing these values on the objects themselves, but if you have truly derived data, there’s nowhere appropriate to store it on your models.

Here’s another simple example. Here I track some results in an adhoc object, so I can key off of them in a subsequent rule.

declare Calculations {
  intermediateValue1: int
  intermediateValue2: double
}

rule "Create tracker object"
when
  not(Calculations())
then
  insert(new Calculations())
end

rule "Calculate some intermediate value 1"
when
  $calc: Calculations()
  // some other conditions
then
  modify($calc) {
    setIntermediateValue1( 42 )
  }
end

rule "Calculate some other value using value 1 when > 20"
when
  $calc: Calculations( $value1: intermediateValue1 > 20 )
  // other conditions
then
  modify( $calc ) {
    setIntermediateValue2( $value1 * 3 )
  }
end

rule "Final calculation"
when
  $calc: Calculation( $value1: intermediateValue1 > 0,
                      $value2: intermediateValue2 > 0 )
  // other conditions
then
  // do the final calculation
end

The declare keyword is used to define, effectively, a lightweight class within the DRL itself. It doesn’t exist outside of the DRL and can’t be referenced in Java. Since we’re just tracking intermediate values, that’s ok.

The first rule looks to see if we have an instance of our calculated results present in working memory. If it’s not, it inserts one.

The inserts keyword is critical here. It tells Drools that there’s new data in its working memory, and that it needs to reevaluate any subsequent rules to determine if they’re now valid to fire.

The middle two rules interact with the results object and modify values. Note that instead of just calling the setter (eg. $calc.setIntermediateValue1( 42 )) I instead use modify. Similar to insert, this lets Drools know that this particular object in working memory has been modified, so it reevaluates any rules that rely on this object and determine if it’s now valid to execute.

The last rule takes all of the intermediate calculated values and (presumably) does something with them to figure out the ‘final’ calculated value.

This other answer of mine talks a bit about the different operations (insert, etc.) that make data changes visible to other rules.