Search code examples
drools

Drools Rules Fireing Order when Fact data changes


I am new to Drools and either I misunderstand what Drools can do or I am missing something.

I have a simple Fact class

public class MeterReadTO {

private String status = "";

public String getStatus() {
  return status;
}

public void setStatus(String status) {
   this.status = status;
}

public boolean isInvalid() {
  return status.equals("invalid");
};

}

With the rules

 rule "Rule Invalid"

 dialect "mvel"
 when
     MeterReadTO( isInvalid());
 then
  System.out.println("Rule Invalid Fired");

end

rule "Rule Not Invalid"

 dialect "mvel"
when
   not MeterReadTO(  isInvalid());
then
   System.out.println("Rule Not Invalid Fired");

end

rule "Set Status"

 dialect "mvel"
 when
    $mr: MeterReadTO()
  then
      System.out.println("Rule Set Status Fires");

     modify($mr) {
      setStatus("invalid")
     }
   end

and I run the rules with

@RunWith(SpringRunner.class)
@SpringBootTest
public class Dependancy {


static KieSession ksession;

Logger logger = LoggerFactory.getLogger(Dependancy.class);


MeterReadTO mr;


@BeforeClass
public static void start() {


      KieContainer kieClasspathContainer = KieServices.Factory.get().getKieClasspathContainer();
        ksession = kieClasspathContainer.newKieSession("DependancyTest");
}

@Test
public void contextLoadsAndRetrievesData() {

    MeterReadTO mr = new MeterReadTO();
    mr.setId(50);

    ksession.insert(mr);

    // when
    ksession.fireAllRules();
    ksession.dispose();

    // Then
    System.out.println("After Drools, status is " + mr.getStatus());

}

}

and I get the outcome

Rule Not Invalid Fired
Rule Set Status Fires
After Drools, status is invalid

By using a stateful session I was expecting to see the "rule Invalid" fire after "rule Set Status" fires. If I move the Set Status rule from the bottom of the list of rules to the top, then I get

Rule Set Status Fires
Rule Not Invalid Fired
After Drools, status is invalid

So clearly Drools is not aware of the status change in rule Set Status. What am I missing?

Regards

FOLLOW UP.

Now I'm really confused. If I remove the call to the method isInvalid() in my rules file and reference the getStatus() method instead so the rules now look like

rule "Rule Invalid"

dialect "mvel"
when
  MeterReadTO( getStatus() == "ïnvalid");
then
  System.out.println("Rule Invalid Fired");

end

rule "Rule Not Invalid"

 dialect "mvel"
 when
   MeterReadTO( getStatus() == "");
 then
    System.out.println("Rule Not Invalid Fired");

end

rule "Set Status"

dialect "mvel"

when
    $mr: MeterReadTO()
  then
      System.out.println("Rule Set Status Fires");

  modify($mr) {
    setStatus("ïnvalid")
  }

   end

Then I get the output that I expected

Rule Not Invalid Fired
Rule Set Status Fires
Rule Invalid Fired
After Drools, status is ïnvalid  

Can anyone explain what is happening?


Solution

  • Basically, what's happening is that there is no way for Drools to understand that when you change the status of your fact, the result of the method isInvalid() is affected.

    When you modify a fact, Drools will try to minimize the reevaluation of the rules as much as possible. In your case, Drools has no way to make a connection between the setStatus() and isInvalid() methods.

    In the second case, you are using the getStatus() method in your rules and Drools is then able to do the connections (by simple POJO naming conventions).

    One way to make sure Drools understands the connection between setStatus() and isInvalid() could be to use the @watch annotation in your rule:

    rule "Rule Invalid"
     dialect "mvel"
     when
       MeterReadTO( isInvalid()) @watch(status)
     then
      System.out.println("Rule Invalid Fired");
    end
    
    rule "Rule Not Invalid"
    dialect "mvel"
    when
      not MeterReadTO(  isInvalid()) @watch(status)
    then
      System.out.println("Rule Not Invalid Fired");
    end
    

    The problem with this approach is that your rules get really tied to your model. If the way you check whether a fact is invalid ahs to be modified to involve more fields, you has to go rule by rule modifying the @watch annotation.

    Another solution would be to mark the relationship in the model itself using the @Modifies annotation:

    public class MeterReadTO {
      private String status = "";
    
      public String getStatus() {
        return status;
      }
    
      @Modifies( { "invalid" } )
      public void setStatus(String status) {
        this.status = status;
      }
    
      public boolean isInvalid() {
        return status.equals("invalid");
      };
    
    }
    

    You can find more information about this topic in Drools' official documentation.

    Hope it helps,