droolsmvel

Drools, add a wait after event occurred


I'm new to drools and looking to try to build different rules based on wait times eg...

IF the rule has already triggered. THEN wait 5mins before being active/allowed to trigger again.

Just reading through the documentation, but in the simplest form, any idea on how this might work in this snippet/rule? Also any recommendation on good documentation to read up on to learn more about drools/pam?

package com;

//a rule
rule "TimeBasedRule"

dialect "mvel"

    when
        debug : com.DataObject(fieldName == "ABC" );
        
    then
        System.out.println("Field Name: " + debug.field_name);
end

Solution

  • Generally speaking you shouldn't consider rules first-class citizens. Your first-class citizens are your rule inputs or events. The rules are just logic. It's like being in the habit of considering a specific "if" statement to be an object -- it's fundamentally not the same thing, it's a bit of process control logic.

    The trick here is to change how you're thinking about the scenario and instead of thinking about the rule, to think about the situation that triggers it.

    Taking your very vague description of the problem statement, let's flesh it out a bit. Let's say we have an application which is designed to alert the user when the battery on an appliance is low. Once the battery gets to that low state, we should send the user alerts, but not if the previous alert was raised within the past 15 minutes. So, for example, if we send an alert at 1:00, the next alert can be sent no earlier than 1:15.

    So let's imagine we have an appliance that's sending us readings, for example a thermometer. This thermometer sends us temperature readings, but also includes some state information such as if it has a low battery. Once it starts indicating low battery, then we're going to start having to consider our particular use case, since the thermometer takes readings more often than we want the user to be alerted.


    So how do we implement this? There's various ways, and as usual it's better to think about it logically first and then try to translate it into rules.

    The first way would be with a flag of some sort, for example indicating when the last low batter alert was sent -- you could use a date/time-like representation to track this.

    rule "Low battery alert - tracked via flag"
    when
      // The condition which indicates the battery is low
      ApplianceStatus( lowBattery == true )
    
      // The last time the alert was raised
      $lastAlert: OffsetDateTime()
      
      // Check if the last time the alert was raised >= 15 minutes ago
      Duration( toMinutes() >= 15 ) from Duration.between( $lastAlert, OffsetDateTime.now(ZoneOffset.UTC) )
    then
      // Logic to send the alert
      sendAlert();
    
      // Update the 'last alert' time
      delete( $lastAlert )
      insert( OffsetDateTime.now(ZoneOffset.UTC) )
    end
    

    The downside to this approach is, of course, that we're required to pass the last time the alert was raised into working memory. The rule won't trigger if there is no 'last alert' in working memory. This is easily worked around by writing a rule for that condition --

    rule "Low battery alert - no previous alert"
    when
      ApplianceStatus( lowBattery == true )
      not (OffsetDateTime())
    then
      sendAlert();
      insert( OffsetDateTime.now(ZoneOffset.UTC) );
    end
    

    ... But we have more options. Generally in Drools there's probably at least three ways to solve any given problem, plus a dozen more which leverage the tooling in ways they weren't intended.

    This first example works fine in Cloud mode, which is the default and the mode that most Drools applications are using. If you're running in Stream mode, however, you have several other options.

    One option would be to use the @expires tag on a declaration like this:

    declare Alert
      @role( event )
      @expires( 15m )
    end
    

    This makes each Alert event to automatically expire after 15 minutes. If no other rules use the event, then it can be removed from the KIE session. You can read more about memory management in stream mode here, or specifically about the metadata tags for declarations here. Using the tags you can customize the event's duration or occurrence time, which makes it a powerful tool for additional temporal operation.

    Another option for stream mode would be to use the temporal operators. These operators are written so that you can write rules against events that happen over a period of time. For our low battery alarm scenario, we could conceivably want to write a rule which triggers an alert for the low battery, but only if we get two indicators at least X minutes apart (for example, if the battery indicator was flaky or prone to false-positives.) The documentation I linked to includes some examples of specific scenarios, as well as detailed explanations of all the operators, so I won't reproduce that here.


    Of course, I'd be remiss if I didn't mention the most basic option -- just tracking the last alert date outside the application. If you ever move to a distributed model or want to ensure high availability in case of application failure, you'll need to be tracking this sort of thing outside of the in-memory context of your drools server.