ODM to Drools migration: Can my needs be met?

670 Views Asked by At

I'm working on a POC to see if we can migrate from ODM to Drools, but I'm not able to identify certain features within Drools that we utilize in our ODM apps.

Example:

  • In ODM we utilize a Decision Operation that has inputs and outputs, which is invoked from a Spring app. This ODM operation invokes a complex ruleflow (parent/sub flows), which organizes rule execution based on different levels of metadata. The rules, if condition is met, builds a set of output data that the Spring app uses to behave differently based on the result. Keep in mind the Decision Operation waits for the ruleflow to complete before responding back to the spring app with that output.

In Drools, I understand I can build a spring boot app and invoke a rule flow, but the rule flow is then async. Drools doesn't behave the same as ODM so doesn't appear I can use a rule flow. There are options to wait and get the state of the rule flow, but we receive millions of requests a day and can't justify putting in waits. I thought about executing rules utilizing agenda-group/activation-group to control our execution, but the data we are evaluating is complex, lots of cross data-validating, and the data we are validating (POJOs) can be 10 levels deep. Yes, our structure is massive and there can be up to 500+ rules. I just can't see utilizing agenda-group/activation-group to achieve what we need due to our complexity.

Would anyone have any thoughts on possibly achieving this type of behavior? Any input is appreciated :)

2

There are 2 best solutions below

3
On

I don't know anything about ODM, so I can't comment about the similarities/differences. But from what is described in the question about the use case, I think the problem is that you're using Drools wrong. :)

Generally speaking, we'd invoke rules like this:

KieContainer kieContainer = ...
KieBase rules = this.kieContainer.getKieBase("validation"); // call the validation rules
KieSession session = rules.newKieSession();
try {
    session.insert( ... ); // add facts into working memory
    session.fireAllRules();
} finally {
    session.dispose();
}

fireAllRules is a synchronous, blocking method.

Alternatively, if you want to do batch processing, you'd do something like this (example taken from the documentation):

StatelessKieSession ksession = kbase.newStatelessKieSession();

List cmds = new ArrayList();
cmds.add( CommandFactory.newInsertObject( new Cheese( "stilton", 1), "stilton") );
cmds.add( CommandFactory.newStartProcess( "process cheeses" ) );
cmds.add( CommandFactory.newQuery( "cheeses" ) );
ExecutionResults bresults = ksession.execute( CommandFactory.newBatchExecution( cmds ) );
Cheese stilton = ( Cheese ) bresults.getValue( "stilton" );
QueryResults qresults = ( QueryResults ) bresults.getValue( "cheeses" );

Notice we call startProcess in the list of commands, but not directly. Further we still wait for the results at the execute call, so while startProcess does proceed asynchronously, we still block for all executions to complete before continuing at execute.


Your stated use case was:

The rules, if condition is met, builds a set of output data that the Spring app uses to behave differently based on the result.

There's nothing here that precludes you from using Drools. At my previous company, we had a set of rules that actually routed requests between services based on the state of the data in the request, including modifying the request in flight. We also had rules that could do normal processing like validation, calculation, and so on.

Your model depth is also not really a hindrance -- you can write rules against your model no matter how it's structured. There are some types which are inherently inferior (please don't pass Maps into the rules) but generally if it's in working memory you can work with it.

Finally, processing time is a factor mostly of how well written your rules are. There are structures in Drools that allow you to modify working memory and rerun the rules -- this will cause them to take inherently more time because (duh) you're running the rules a second time! But a simple fall-through sort of rules -- trigger which ever ones trigger and keep moving -- those can be very fast. At my last job I had a single service with 1.4 million rules that had a sub-second SLA; it was so fast because there were no 'update' calls, and none of the rules cared about which of the other rules also fired. The rules were effectively stateless, and the worst-case performance was 300 milliseconds (average was 30 ms; and that's round trip through the entire spring-boot app and not just the actual rule evaluation.)


For rules that depend on complex data objects many levels deep in a POJO, how would you derive that data and not causing rules to care about other rules? Let's say my object structure is something like Person -> Vehicle -> Make -> Model -> Options. If I'm running rules specifically on "Options", how are getting to the "model" data in the Options rules? What if model or make is empty/null to block Options from running?

Parsing out the example, we have these models (assume getters and setters on all fields):

class Options {}
class Model {
  private Options options;
}
class Make {
  private Model model;
}
class Vehicle {
  private Make make;
}
class Person {
  private Vehicle vehicle;
}

To get to the 'options', we just pull them out of the intermediate models. Assuming you put a Person instance into working memory:

rule "Options"
when
  // this is the Person in working memory; get the vehicle out.
  Person( $v: vehicle )

  // then drill down to Options by repeating the process
  Vehicle( $make: make ) from $v
  Make( $model: model ) from $make
  Model( $opts: options ) from $model

  // now we can do stuff with $opts
then
  // $opts is available for use here too
end

Drools is generally pretty good at doing defensive 'gets', but you can be defensive in your own right by adding a null check. For example:

rule "Options with explicit null checks"
when
  Person( $v: vehicle != null )
  Vehicle( $make: make != null ) from $v
  Make( $model: model != null ) from $make
  Model( $opts: options != null ) from $model
then
  // ...
end

Alternatively if you want to fail fast and just discard any inputs with bad data, you can retract them from working memory. This is potentially dangerous because it's a premature exit from the workflow, and can cause potential bugs downstream if you don't realize it's there. (It's somewhat analogous to having multiple "return" statements in your Java code; if you're debugging down near the bottom of a method and aren't aware that there's an early return much farther up, that might cause you to waste time or introduce unexpected behavior.)

rule "Retract incomplete date"
salience 100
when
  $person: Person ($v: vehicle != null)

  // as an example, this rule is going to trap for missing Make
  Vehicle( make == null ) from $v
then
  retract($person);
end

Word of caution: once you call retract, that item is gone from working memory, so if any subsequent rules were relying on it, they'll no longer fire.

The salience isn't necessary but historically I've found it easier to mentally track early retractions like this when they're explicitly called out as happening earlier than the regular rules. Generally speaking it's better practice to simply write rules that are mutually exclusive, but you may find marginally better performance using an early retract.

3
On

in Drools, I understand I can build a spring boot app and invoke a rule flow, but the rule flow is then async. Drools doesn't behave the same as ODM so doesn't appear I can use a rule flow.

There is the same type of ODM ruleflow features in Drools. It is less sophisticated but you can achieve the same result which is :

  • organise your decision into smaller sets of rules
  • sequence as a flow these differents tasks/sets to perform a decision orchestration

see Ruleflow examples in Drools doc 7.67

Just like in ODM , you can call this ruleflow :

  • directly as a web service in the Kieserver (drools concept for Rule Execution Server/HTDS) in JSON/XML (you need to install kieserver first).
  • from a Java client that calls the kieserver

Regarding object navigation in rules, rule authoring is the same , the main difference is the DRL langage.

Best Emmanuel