Previously, we've discussed how rules should attempt to be atomic and work together to achieve management of complex scenarios. This is very well aligned with CEP as each different rule can deal with one aspect of the aggregation, composition, or abstraction of other events. They can work together to achieve real-time resolution of very complex event situations. We will still need a few added features of Drools to be able to do so, as follows:
In the next subsections, we will see how to achieve this using the DRL syntax.
Before we get into the detail about how to define an event, we need to understand a few characteristics of the events. The first characteristic marks a difference between two main types of events—punctual and interval events—as shown in the following:
Also, regardless of being punctual or interval events, they share a set of conceptual characteristics worth mentioning before we look into the code:
The first thing we'll need to do in order to create the CEP rules is to specify the type of objects that need to be treated as events to the engine. That is, the objects that should have the time metadata. This will allow the Kie Session to apply temporal reasoning to these types. There are a few ways to define that a specific type should be treated as an event, but they all define the same set of metadata. The properties to be defined are as shown in the following:
Now that we understand the properties, let's see the different ways to apply them to our types. This metadata can be directly defined as class-level annotations in our Java beans, as follows:
@org.kie.api.definition.type.Role(Role.Type.EVENT) @org.kie.api.definition.type.Duration("durationAttr") @org.kie.api.definition.type.Timestamp("executionTime") @org.kie.api.definition.type.Expires("2h30m") public class TransactionEvent implements Serializable { private Date executionTime; private Long durationAttr; /* class content skipped */ }
As you can see in the previous code section, we can define the annotations for the role, duration, timestamp, and expiration properties of an event type. Duration should identify an attribute of the Long
type and Timestamp should identify an attribute of the Date
type. This way, the Kie Session will be able to understand the inserted objects of said type as events.
Another way to define these properties is in the declared types. Similar annotations can be used to define a declared type as an event, as follows:
declare PhoneCallEvent @role(event) @timestamp(whenDidWeReceiveTheCall) @duration(howLongWasTheCall) @expires(2h30m) whenDidWeReceiveTheCall: Date howLongWasTheCall: Long callInfo: String end
The previous code section shows that we can create our own declared type and annotate it accordingly in order to make it an event.
Another way to declare the events is to grab an existing class and declare it as an event inside the DRL. This is very common when events are created to be shared between different applications and we cannot directly modify them in order to have the annotations on the Java bean. We can do something as shown in the following code section to declare the existing Java beans as events:
import path.to.my.shared.ExternalEvent; ... declare ExternalEvent @role(event) end
Just as the previous code section shows, we can redeclare this non-annotated Java bean inside the DRL to be treated as an event. As previously stated, all the annotations are optional. The only necessary annotation to treat the declared type as an event type is to have the @role(event)
annotation. You can see the examples of events like these in the chapter-06/chapter-06-events
project of the code bundle.
Now that we've seen how to declare our event types, we need to start seeing how to compare them. To do so, we will review the existing temporal operators.
Once we define our event types, we need a way to compare the events based on their timestamp. To do so, there are 13 temporal operators that we can use in Drools. Some of these operators only make sense for comparing interval events, but given two events, they can compare them as the following code snippet shows:
declare MyEvent @role(event) @timestamp(executionTime) End rule "my first time operators example" when $e1: MyEvent() $e2: MyEvent(this after[5m] $e1) Then System.out.println("We have two events" + " 5 minutes apart"); end
In the previous example, we make use of the after
operator to determine whether an event is at least five minutes newer than another event. As you can see, the comparison is done on the specific event instances. Internally, the time comparison will happen against the timestamp attribute called executionTime
, but we can disregard that fact when dealing with events. This provides an advantage if we need to modify the timestamp nature of an event type in the future as we don't have to change the CEP rules where it is used.
Also, we can notice the use of a parameter in the operator, passed inside the square brackets. Each temporal operator will be prepared to receive between zero and four parameters to make use of the operator in a more specific way. In the previous scenario, we pass a 5m
parameter to specify that an event should be at least five minutes after the other.
There are many temporal operators with which we can work. Here's a list of them and what they mean:
The previous diagram shows the different temporal operators and how they will compare between different events. They all share certain qualities, as shown in the following:
Date
objects as dates are, by definition, the most minimalistic representation of an event (only the temporal information without any extra data).Temporal Operators
title.One more thing worth mentioning about events is that they are still facts too. The engine will add the temporal features to the event types, but we can still compare any of their internal attributes and methods to create conditions and constraints on the rules, like we have done in the previous chapters.
In order to get familiar with a CEP rule, let's analyze one of the rules that we can find in the chapter-06/chapter-06-rules
project of the code bundle and aim to detect fraud attempts, as follows:
rule "More than 10 transactions in an hour from one client" when $t1: TransactionEvent($cId: customerId) Number(intValue >= 10) from accumulate($t2: TransactionEvent(this != $t1, customerId == $cId, this meets[1h] $t1),count($t2) ) not (SuspiciousCustomerEvent(customerId == $cId, reason == "Many transactions")) then insert(new SuspiciousCustomerEvent($cId, "Many transactions")); end
This example DRL file can be found at chapter-06-rules/src/main/resources/chapter06/cep/cep-rules.drl
. In order to run this example, we start with our previously defined TransactionEvent
event type. We will check two main things in our rule: whether we have 10 transactions from the same customer within an hour, and that we still don't have a complex event to reflect this situation.
The first condition is written inside an accumulate. We count the number of TransactionEvent
objects we have that contain the same customer ID and we also check whether they happened within an hour of the original reference transaction using this meets
[1h]
$t1
.
The consequence of this rule is not a particular action against the outside. Instead, we just detect a complex event called SuspiciousCustomerEvent
(a declared type in our example). This will represent an aggregation of our transaction events.
The second condition is a simple not
clause, where we just check whether we haven't already fired this rule for the specific customer by checking the SuspiciousCustomerEvent
object, which we need to add in the consequence if in case it hasn't been already added.
This rule will only detect the situation as that's the smallest responsibility we can break it down to. We could do a lot with suspicious customers, but this rule only has the responsibility of understanding a specific situation where a customer acts suspiciously. We need to remember to always keep our rules as atomic as possible. Other rules might detect a suspicious activity from a customer by other means.
Once the suspicious customer is detected, another rule can take care of deciding what to do when we detect a few suspicious customer events. For that case, we will create a different rule:
rule "More than 3 suspicious cases: warn the owner" when SuspiciousCustomerEvent($cId: customerId) not (AlarmTriggered(customerId == $cId)) Number(intValue >= 2) from accumulate($s: SuspiciousCustomerEvent(customerId==$cId),count($s) ) then //warn the owner System.out.println("WARNING: Suspicious fraud" +" case. Client " + $cId); insert(new AlarmTriggered($cId)); end
As we previously stated, we can have multiple rules detecting suspicious customer activities. This rule will trigger when two or more of these rules get triggered for the same customer. Once this happens, we send a warning to the owner. In this example, it is represented as a system output for simplicity, but it could just as easily be a helper method or global variable method programmed to send an e-mail or SMS.
As we can see from the previous examples, we can break down our complex event processing cases into multiple rules, each one connected to the rest of the CEP scenario by the events it consumes or produces. These aggregations of events lead to a special kind of architecture for our systems, where events and their relation with isolated application components allow us to create very decoupled, highly extensible components. This architecture is known as event driven architecture, and we'll describe it in the next subsection.
3.141.47.25