简体   繁体   中英

Drools: get the 3 most recent Events

I'm working on a small Drools project, because I want to learn more about using rule engines. I have a class called Event that has the following fields:

  • String tag; A tag which can be any string.
  • long millis; A timestamp. (Actually, this is converted from a JodaTime LocalDate field which is also in Event .)
  • int value; A value that I want reason about.

I insert several hundreds of Event instances into my knowledge base, and now I want to get the 3 most recent events that are tagged with "OK" . I came up with the following code, which works:

rule "Three most recent events tagged with 'OK'"
when
    $e1 : Event( tag == "OK",
                 $millis1 : millis )
    $e2 : Event( tag == "OK",
                 millis < $millis1, $millis2 : millis )
    $e3 : Event( tag == "OK",
                 millis < $millis2, $millis3 : millis )

    not Event( tag == "OK",
               millis > $millis1 )
    not Event( tag == "OK",
               millis > $millis2 && millis < $millis1 )
    not Event( tag == "OK",
               millis > $millis3 && millis < $millis2 )
then
  # Do something with $e1.value, $e2.value and $e3.value
end

But I have a feeling there should be a better way to do this. This is quite verbose and not easily re-used: what if I want to get the five most recent events with value > 10 , for example? I would end up copy-pasting a lot of code, and I don't want to do that :). Also, the code doesn't look very 'beautiful' to me. I don't really like the repeated not Event... constraints, and I also don't like having to repeat the same tag condition over and over. (This example is a heavily simplified version of my real app, in which the condition is actually a lot more complex.)

How can I improve this code?

Assuming you are using the STREAM event processing mode and your events are ordered in the stream:

rule "3 most recent events"
when
    accumulate( $e : Event( tag == "OK" ) over window:length(3),
                $events : collectList( $e ) )
then
    // $events is a list that contains your 3 most recent 
    // events by insertion order
end

===== edit ====

Based on your comment bellow, here is how to achieve what you want in Drools 5.4+:

declare window LastEvents
    Event() over window:length(3)
end

rule "OK events among the last 3 events"
when
    accumulate( $e : Event( tag == "OK" ) from window LastEvents,
                $events : collectList( $e ) )
then
    // $events is a list that contains the OK events among the last 3 
    // events by insertion order
end

Just double check the syntax as I am doing this by heart, but it should be close to this.

I was able to simplify 'not logic' like this

rule "Three most recent events tagged with 'OK'"
when
    $e1 : Event( tag == "OK")
    $e2 : Event( tag == "OK", millis < $e1.millis )
    $e3 : Event( tag == "OK", millis < $e2.millis )
    not Event( this != $e2, tag == "OK", $e3.millis < millis, millis < $e1.millis )
then
    System.out.printf("%s - %s - %s%n", $e1, $e2, $e3);
end

There was nothing said about cleaning events. Usually this is desirable, so you can achieve the same logic with deletion of the last event:

rule "Three most recent events tagged with 'OK'"
when
    $e1 : Event( tag == "OK")
    $e2 : Event( tag == "OK", millis < $e1.millis )
    $e3 : Event( tag == "OK", millis < $e2.millis )
then
    System.out.printf("%s - %s - %s%n", $e1, $e2, $e3);
    retract ($e3)
end

Let say each second you'll insert an event one 'OK' another empty '', here is the test:

@DroolsSession("classpath:/test3.drl")
public class PlaygroundTest {
    
    @Rule
    public DroolsAssert drools = new DroolsAssert();
    
    @Test
    public void testIt() {
        for (int i = 0; i < 10; i++) {
            drools.advanceTime(1, SECONDS);
            drools.insertAndFire(new Event(i % 2 == 0 ? "OK" : "", i));
        }
    }
}

all three variants will produce the same triggering logic:

00:00:01 --> inserted: Event[tag=OK,millis=0]
00:00:01 --> fireAllRules
00:00:02 --> inserted: Event[tag=,millis=1]
00:00:02 --> fireAllRules
00:00:03 --> inserted: Event[tag=OK,millis=2]
00:00:03 --> fireAllRules
00:00:04 --> inserted: Event[tag=,millis=3]
00:00:04 --> fireAllRules
00:00:05 --> inserted: Event[tag=OK,millis=4]
00:00:05 --> fireAllRules
00:00:05 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [Event, Event, Event]
OK4 - OK2 - OK0
00:00:06 --> inserted: Event[tag=,millis=5]
00:00:06 --> fireAllRules
00:00:07 --> inserted: Event[tag=OK,millis=6]
00:00:07 --> fireAllRules
00:00:07 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [Event, Event, Event]
OK6 - OK4 - OK2
00:00:08 --> inserted: Event[tag=,millis=7]
00:00:08 --> fireAllRules
00:00:09 --> inserted: Event[tag=OK,millis=8]
00:00:09 --> fireAllRules
00:00:09 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [Event, Event, Event]
OK8 - OK6 - OK4
00:00:10 --> inserted: Event[tag=,millis=9]
00:00:10 --> fireAllRules

variant with window:length(3) will also deal with last 3 OK events. It is different on the beginning though: it will be triggered for 1 and 2 first OK events too. It will be also triggered once with empty list on the beginning if session doesn't contain any events. According to documentation , sliding windows start to match immediately and defining a sliding window does not imply that the rule has to wait for the sliding window to be "full" in order to match. For instance, a rule that calculates the average of an event property on a window:length(10) will start calculating the average immediately, and it will start at 0 (zero) for no-events, and will update the average as events arrive one by one.

00:00:01 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[]
00:00:01 --> inserted: Event[tag=OK,millis=0]
00:00:01 --> fireAllRules
00:00:01 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK0]
00:00:02 --> inserted: Event[tag=,millis=1]
00:00:02 --> fireAllRules
00:00:03 --> inserted: Event[tag=OK,millis=2]
00:00:03 --> fireAllRules
00:00:03 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK0, OK2]
00:00:04 --> inserted: Event[tag=,millis=3]
00:00:04 --> fireAllRules
00:00:05 --> inserted: Event[tag=OK,millis=4]
00:00:05 --> fireAllRules
00:00:05 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK0, OK2, OK4]
00:00:06 --> inserted: Event[tag=,millis=5]
00:00:06 --> fireAllRules
00:00:07 --> inserted: Event[tag=OK,millis=6]
00:00:07 --> fireAllRules
00:00:07 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK2, OK4, OK6]
00:00:08 --> inserted: Event[tag=,millis=7]
00:00:08 --> fireAllRules
00:00:09 --> inserted: Event[tag=OK,millis=8]
00:00:09 --> fireAllRules
00:00:09 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK4, OK6, OK8]
00:00:10 --> inserted: Event[tag=,millis=9]
00:00:10 --> fireAllRules

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM