Smart Processes Using Rules

0
143
16 min read

(For more resources related to this topic, see here.)

Good old integration patterns

What we’ve learnt from experience about jBPM3 is that a rule engine can become very handy when evaluating different situations and making automatic decisions based on the information available. Based on my experience in consulting, I’ve noticed that people who understand how a process engine works and feel comfortable with it, start looking at rule engines such as Drools. The most intuitive first step is to delegate the business decisions in your processes and the data validations to a rule engine. In the past, adopting one of these two different technologies at the same time was difficult, mostly because of the learning curve as well as the maturity and investment required by a company to learn and use both technologies at once. At the end of the day, companies spend time and money to create in-house integrations and solutions so that they can merge these two worlds.

The following example shows what people have done with jBPM3 and the Drools Rule Engine:

The first and most typical use case is to use a rule engine to choose between different paths in a process. Usually, the information that is sent to the rule engine is the same information that is flowing through the process tasks or just some pieces of it; we expect a return value from the rule engine that will be used to select which path to take. Most of the time, we send small pieces of information (for example, the age or salary of a person, and so on) and we expect to get a Boolean (true/false) value, in the case that we want to decide between just two paths, or a value (integers such as 1, 2, 3, and so on) that will be used to match each outgoing sequence flow. In this kind of integration, the rule engine is considered just an external component. We expect a very stateless behavior and an immediate response from the rule engine.

The previous figure shows a similar situation, when we want to validate some data and then define a task inside our process to achieve this information’s validation or decoration. Usually we send a set of objects that we want to validate or decorate, and we expect an immediate answer from the rule engine. The type of answer that we receive depends on the type of validation or the decoration rules that we write. Usually, these interactions interchange complex data structures, such as a full graph of objects.

And that’s it! Those two examples show the classic interaction from a process engine to a rule engine. You may have noticed the stateless nature of both the examples, where the most interesting features of the rule engine are not being used at all. In order to understand a little bit better why a rule engine is an important tool and the advantages of using it (in contrast to any other service), we need to understand some of the basic concepts behind it.

The following section briefly introduces the Drools Rule Engine and its features, as well as an explanation of the basic topics that we need to know in order to use it.

The Drools Rule Engine

The reason why rule engines are extremely useful is that they allow us to express declaratively what to do in specific scenarios. In contrast to imperative languages such as Java, the Rule Engine provides a declarative language that is used to evaluate the available information.

In this article we will analyze some Drools rules, but this article will not explain in detail the Drools Rule syntax. For more information on this take a look at the official documentation at http://docs.jboss.org/drools/release/5.5.0.Final/drools-expert-docs/html_single/.

Let’s analyze the following simple example to understand the differences between the declarative approaches and the imperative approaches:

rule "over 18 enabled to drive" when $p: Person( age > 18, enabledToDrive == false) then $p.setEnabledToDrive(true); update($p); end rule "over 21 enabled to vote" when $p: Person( age > 21, enabledToVote == false) Then $p.setEnabledToVote(true); update($p); end

Before explaining what these two rules are doing (which is kind of obvious as they are self-descriptive!), let’s analyze the following java code snippet:

if(person.getAge() > 18 && person.isEnabledToDrive() == false){ person.setEnabledToDrive(true); } if(person.getAge() > 21 && person.isEnabledToVote() == false){ person.setEnabledToVote(true); }

Usually most people, who are not familiar with rule engines but have heard of them, think that rule engines are used to extract if/else statements from the application’s code. This definition is far from reality, and doesn’t explain the power of rule engines.

First of all, rule engines provide us a declarative language to express our rules, in contrast to the imperative nature of languages such as Java.

In Java code, we know that the first line is evaluated first, so if the expression inside the if statement evaluates to true, the next line will be executed; if not, the execution will jump to the next if statement. There are no doubts about how Java will analyze and execute these statements: one after the other until there are no more instructions. We commonly say that Java is an imperative language in which we specify the actions that need to be executed and the sequence of these actions.

Java, C, PHP, Python, and Cobol are imperative languages, meaning that they follow the instructions that we give them, one after the other.

Now if we analyze the DRL snippet (DRL means Drools Rule Language), we are not specifying a sequence of imperative actions. We are specifying situations that are evaluated by the rule engine, so when those situations are detected, the rule consequence (the then section of the rule) is eligible to be executed.

Each rule defines a situation that the engine will evaluate. Rules are defined using two sections: the conditional section, which starts with the when keyword that defines the filter that will be applied to the information available inside the rule engine. This example rule contains the following condition:

when $p: Person( age > 18 )

This DRL conditional statement filters all the objects inside the rule engine instance that match this condition. This conditional statement means “match for each person whose age is over 18”. If we have at least one Person instance that matches this condition, this rule will be activated for that Person instance. A rule that is activated is said to be eligible to be fired. When a rule is fired, the consequence side of the rule is executed. For this example rule, the consequence section looks like this:

then $p.setEnabledToDrive(true); update($p);

In the rule consequence, you can write any Java code you want. This code will be executed as regular Java code. In this case, we are getting the object that matches the filter—Person ( age > 18 )—that is bonded to the variable called $p and changing one of its attributes. The second line inside the consequence notifies the rule engine of this change so it can be used by other rules.

A rule is composed of a conditional side, also called Left-Hand Side (LHS for short) and a consequence side, also called Right-Hand Side (RHS for short).

rule "Person over 60 – apply discount" when // LHS $p: Person(age > 60) then // RHS $p.setDiscount(40); end

We will be in charge of writing these rules and making them available to a rule engine that is prepared to host a large number of rules.

To understand the differences and advantages between the following lines, we need to understand how a rule engine works. The first big difference is behavior: we cannot force the rule engine to execute a given rule. The rule engine will pick up only the rules that match with the expressed conditions.

if(person.getAge() > 18)

And

$p: Person( age > 18 )

If we try to compare rules with imperative code, we usually analyze how the declarative nature of rule languages can help us to create more maintainable code. The following example shows how application codes usually get so complicated, that maintaining them is not a simple task:

If(…){ If(){ If(){ } }else(){ if(…){ } } }

All of the evaluations must be done in a sequence. When the application grows, maintaining this spaghetti code becomes complex—even more so when the logic that it represents needs to be changed frequently to reflect business changes. In our simple example, if the person that we are analyzing is 19 years old, the only rule that will be evaluated and activated is the rule called “over 18 enabled to drive“. Imagine that we had mixed and nested if statements evaluating different domain entities in our application. There would be no simple way to do the evaluations in the right order for every possible combination. Business rules offer us a simple and atomic way to describe the situations that we are interested in, which will be analyzed based on the data available. When the number of these situations grows and we need to frequently apply changes to reflect the business reality, a rule engine is a very good alternative to improve readability and maintenance.

Rules represent what to do for a specific situation. That’s why business rules must be atomic. When we read a business rule, we need to clearly identify what’s the condition and exactly what will happen when the condition is true.

To finish this quick introduction to the Drools Rule Engine, let’s look at the following example:

rule "enabled to drive must have a car" When $p: Person( enabledToDrive == true ) not(Car(person == $p)) then insert(new Car($p)); end rule "person with new car must be happy" when $p: Person() $c: Car(person == $p) then $p.setHappy(true); end rule "over 18 enabled to drive" when $p: Person( age > 18, enabledToDrive == false) then $p.setEnabledToDrive(true); update($p); end

When you get used to the Drools Rule Language, you can easily see how the rules will work for a given situation. The rule called “over 18 enabled to drive” checks the person’s age in order to see if he/she is enabled to drive or not. By default, persons are not enabled to drive. When this rule finds one instance of the Person object that matches with this filter, it will activate the rule; and when the rule’s consequence gets executed, the enabledToDrive attribute will be set to true and we will notify the engine of this change. Because the Person instance has been updated, the rule called “enabled to drive must have a car” is now eligible to be fired. Because there is no other active rule, the rule’s consequence will be executed, causing the insertion of a new car instance. As soon as we insert a new car instance, the last rule’s conditions will be true. Notice that the last rule is evaluating two different types of objects as well as joining them. The rule called “person with new car must be happy” is checking that the car belongs to the person with $c: Car(person == $p). As you may imagine, the $p: creates a binding to the object instances that match the conditions for that pattern. In all the examples in this book, I’ve used the $ sign to denote variables that are being bound inside rules. This is not a requirement, but it is a good practice that allows you to quickly identify variables versus object field filters.

Please notice that the rule engine doesn’t care about the order of the rules that we provide; it will analyze them by their conditional sections, not by the order in which we provide the rules.

This article provides a very simple project implementing this scenario, so feel free to open it from inside the chapter_09 directory and experiment with it. It’s called drools5-SimpleExample. This project contains a test class called MyFirstDrools5RulesTest, which tests the previously introduced rules. Feel free to change the order of the rules provided in the /src/test/resources/simpleRules.drl file. Please take a look at the official documentation at www.drools.org to find more about the advantages of using a rule engine.

What Drools needs to work

If you remember the jBPM5 API introduction section, you will recall the StatefulKnowledgeSession interface that hosts our business processes. This stateful knowledge session is all that we need in order to host and interact with our rules as well. We can run our processes and business rules in the same instance of a knowledge session without any trouble. In order to make our rules available in our knowledge session, we will need to use the knowledge builder to parse and compile our business rules and to create the proper knowledge packages. Now we will use the ResourceType.DRL file instead of the ResourceType.BPMN2 file that we were using for our business processes.

So the knowledge session will represent our world. The business rules that we put in it will evaluate all the information available in the context. From our application side, we will need to notify the rule engine which pieces of information will be available to be analyzed by it. In order to inform and interact with the engine, there are four basic methods provided by the StatefulKnowledgeSession object that we need to know.

We will be sharing a StatefulKnowledgeSession instance between our processes and our rules. From the rule engine perspective, we will need to insert information to be analyzed. These pieces of information (which are Java objects) are called facts according to the rule engine’s terminology. Our rules are in charge of evaluating these facts against our defined conditions.

The following four methods become a fundamental piece of our toolbox:

FactHandle insert(Object object) void update(FactHandle handle, Object object) void retract(FactHandle handle) int fireAllRules()

The insert() method notifies the engine of an object instance that we want to analyze using our rules. When we use the insert() method, our object instance becomes a fact. A fact is just a piece of information that is considered to be true inside the rule engine. Based on this assumption, a wrapper to the object instance will be created and returned from the insert() method. This wrapper is called FactHandle and it will allow us to make references to an inserted fact. Notice that the update() and retract() methods use this FactHandle wrapper to modify or remove an object that we have previously inserted.

Another important thing to understand at this point is that only top-level objects will be handled as facts, which implies the following:

FactHandle personHandle = ksession.insert(new Person());

This sentence will notify the engine about the presence of a new fact, the Person instance. Having the instances of Person as facts will enable us to write rules using the pattern Person() to filter the available objects. What if we have a more complex structure? Here, for example, the Person class defines a list of addresses as:

class Person{ private String name; private List<Address> addresses; }

In such cases we will need to define if we are interested in making inferences about addresses. If we just insert the Person object instance, none of the addresses instances will be treated as facts by the engine. Only the Person object will be filtered. In other words, a condition such as the following would never be true:

when $p: Person() $a: Address()

This rule condition would never match, because we don’t have any Address facts. In order to make the Address instances available to the engine, we can iterate the person’s addresses and insert them as facts.

ksession.insert(person); for(Address addr : person.getAddresses()){ ksession.insert(addr); }

If our object changes, we need to notify the engine about the changes. For that purpose, the update() method allows us to modify a fact using its fact handler. Using the update() method will ensure that only the rules that were filtering this fact type gets re-evaluated. When a fact is no longer true or when we don’t need it anymore, we can use the retract() method to remove that piece of information from the rule engine.

Up until now, the rule engine has generated activations for all the rules and facts that match with those rules. No rule’s consequence will be executed if we don’t call the fireAllRules() method. The fireAllRules() method will first look for activations inside our ksession object and select one. Then it will execute that activation, which can cause new activations to be created or current ones canceled. At this point, the loop begins again; the method picks one activation from the Agenda (where all the activations go) and executes it. This loop goes on until there are no more activations to execute. At that point the fireAllRules() method returns control to our application.

The following figure shows this execution cycle:

This cycle represents the inference process, since our rules can generate new information (based on the information that is available), and new conclusions can be derived by the end of this cycle.

Understanding this cycle is vital in working with the rule engine. As soon as we understand the power of making data inferences as opposed to just plain data validation, the power of the rule engine is unleashed. It usually takes some time to digest the full range of possibilities that can be modeled using rules, but it’s definitely worth it.

Another characteristic of rule engines that you need to understand is the difference between stateless and stateful sessions. In this book, all the examples use the StatefulKnowledgeSession instance to interact with processes and rules. A stateless session is considered a very simple StatefulKnowledgeSession that can execute a previously described execution cycle just once. Stateless sessions can be used when we only need to evaluate our data once and then dispose of that session, because we are not planning to use it anymore. Most of the time, because the processes are long running and multiple interactions will be required, we need to use a StatefulKnowledgeSession instance. In a StatefulKnowledgeSession, we will be able to go throughout the previous cycle multiple times, which allows us to introduce more information over time instead of all at the beginning. Just so you know, the StatelessKnowledgeSession instance in Drools exposes an execute() method that internally inserts all the facts provided as parameters, calls the fireAllRules() method, and finally disposes of the session. There have been several discussions about the performance of these two approaches, but inside the Drools Engine, both stateful and stateless sessions perform the same, because StatelessKnowledgeSession uses StatefulKnowledgeSession under the hood.

There is no performance difference between stateless and stateful sessions in Drools.

The last function that we need to know is the dispose() method provided by the StatefulKnowledgeSession interface. Disposing of the session will release all the references to our domain objects that are kept, allowing those objects to be collected by the JVM’s garbage collector. As soon as we know that we are not going to use a session anymore, we should dispose of it by using dispose().


Subscribe to the weekly Packt Hub newsletter

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here