In a project several years ago I built a rule engine from scratch.  In a recent project, which needed a rule engine, I decided to take different route. I decided to give  Drools rule engine from JBOSS a try. It has  worked out  well so far. In this post, I will share my experience with it. I will use insurance as an example to demonstrate how to use it.

Why Rule Engine

You should seriously consider rule engine when the following conditions apply.

  1. You have complex business logic
  2. Business logic  changes often
  3. You want to provide visibility of the business logic to a wider audience, including business analysts
  4. You want to empower business folks with owning and managing business logic and rules

AI Talk

Let’s explore a little the AI background of rule engines. In AI speak a rule engine is a Forward Chaining Expert System.  A forward chaining expert system reasons bottom up based on facts to arrive at some conclusions. In contrast, a Backward Chaining Expert System reasons top down from a hypothesis and tries to establish if the hypothesis id supported by the given facts. The following table is a side by side comparison of forward chaining and backward chaining.

Forward chaining Backward chaining
Bottom up reasoning driven by facts Top down gloal driven reasoning
Works forward to find inference from facts Works backwards to find facts supporting hypothesis
Useful for planning and controlling Useful for diagnosis

When you visit your doctor, what goes on in the doctor’s mind, in my opinion is little bit of forward chaining and backward chaining. Based on the symptoms you have provided, the doctor does forward chaining reasoning to arrive at some hypothesis.

Then as you may have observed, as your conversation with doctor continues, the doctor tries to elicit more facts by asking you about specific additional symptoms. What’s going on at this stage is that the doctor is doing backward chaining to prove the validity of the initial hypothesis by checking with additional facts.

Inside a Rule Engine
For those unfamiliar with rule engine, it’s natural to ask the question “what’s inside a rule engine?”. At it’s heart, a rule engine contains the following

  • A set of facts
  • A set of rules

A fact is essentially a piece of data that is matched with the left hand conditional part of a rule. With Drools, the facts will a be set of Java objects. A rule is essentially like an if statement in a programming language. There is a conditional part a.k.a the  left hand side and an action part, a.k.a as the right hand side.

The rule engine execution logic can be summarized as follows. This is the general behavior of a rule engine.The exact behavior could vary depending on the implementation.

When invoking the rule engine, the client application specifies the rules to use and feeds the data to be used as facts. All the facts resided in so called working memory inside the rule engine.

  1. The rule engine goes through all the rules
  2. It tries to match the match the left hand condition with the facts in working memory
  3. Any rule that matches goes into what’s called an activation queue
  4. It’s executes the rules in the activation queue one at a time.
  5. If the rule action modifies any fact in the working memory or adds new facts ,  the rule engine goes back to step 1
  6. The rule engine quits, when there is not more rule left in the activation queue for execution

One key point to note is that the sequence in which the rules are executed  is not deterministic. In other words, your application can not rely on the sequence in which rules are executed by the rule engine.

Many rule engines including Drools, provide some control over the rule execution order. This is achieved by assigning priority to rules, which essentially sorts the rules inside the activation queue.

The stopping condition of the rule also could be different from what is described above. For example, the rule engine could quit when a user provided predicate evaluates to true. Another option could be to have the rule engine make only one pass through the rule engine, which amounts to skipping step 5 above.

Rules may described  in many syntaxes, e.g., XML, proprietary syntax and associated grammar,  spreadsheet. Some products provide visual GUI tools for rule creation and storage.

Insurance Example

Our application domain is auto insurance. We want to use the rule engine to calculate discount percentage on premium. The discounts could be based on various factors. The discount rules could change often and the management wants to have direct control over the discount rules.

package mawazo.insurance.discount
import mawazo.insurance.discount.server.Customer
rule “marital status”
dialect “mvel”
agenda-group “discount”
when $c : Customer(maritalStatus == “married”)
then $c.discount += 3
end
rule “driving status”
dialect “mvel”
agenda-group “discount”
when $c : Customer(drivingStatus == “good”)
then $c.discount += 7
end
rule “education”
dialect “mvel”
agenda-group “discount”
when $c : Customer(education == “MS” )
then $c.discount += 1
end
rule “medium loyalty”
dialect “mvel”
agenda-group “discount”
when $c : Customer(accountAge >= 5 && accountAge <= 9) then $c.discount += 2 end rule “high loyalty” dialect “mvel” agenda-group “discount” when $c : Customer(accountAge > 9)
then $c.discount += 3
end
rule “discount cap”
dialect “mvel”
agenda-group “discountLimit”
when $c : Customer(discount > 10)
then $c.setDiscount(10)
end

Each when then construct is a rule.   This rule file has 5 rules. Customer is a java class. An instance of  it is passed to the rule engine as fact. Let’s dissect the first rule. If the maritalStatus attribute of the Customer object matches the value “married”, then the object is bound to the variable c. In the action part, a discount of 3 percentage is added to the currently accumulated discount.

We are using a drools construct called agenda group here. An agenda groups allows rules to be partitioned into different sets. Drools allows rule execution control at the agenda group level.

The rules in the agenda group discount calculates and accumulates all the discounts. The agenda group discountLimit puts an upper limit on the total  discount. The code for invocation of rule engine is shown below. Here, we are using the rule engine embedded in our application. The rule engine could also be as a remote server.

[php]
public class DiscountCalculator {
private StatefulKnowledgeSession kSession;
private FactHandle ruleHandle;

public void execute(String maritalStatus, String drivingStatus, int accountAge){
if (null == kSession) {
//load rule and create stateful rule session, if it’s not cached
KnowledgeBuilder kbuilder =
KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newFileResource( "/home/pranab/Projects/ins/discount.drl" ), ResourceType.DRL);
if ( kbuilder.hasErrors() ) {
System.err.println( kbuilder.getErrors().toString() );
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());

kSession = kbase.newStatefulKnowledgeSession();
}

//create fact
Customer fact = new Customer();
fact.setMaritalStatus(maritalStatus);
fact.setDrivingStatus(drivingStatus);
fact.setAccountAge(accountAge);
fact.setDiscount(0);

//insert fact and invoke first agenda group
kSession.getAgenda().getAgendaGroup("discount").setFocus();
ruleHandle = kSession.insert(fact);
kSession.fireAllRules();
kSession.retract(ruleHandle);

//reinsert fact and invoke the second agenda group
kSession.getAgenda().getAgendaGroup("discountLimit").setFocus();
ruleHandle = kSession.insert(fact);
kSession.fireAllRules();

System.out.println("discount: " + fact.getDiscount());
kSession.retract(ruleHandle);
}

}
[/php]

First, we load the rule file and create the rule session. Drools supports two kinds of rule sessions, stateless and state full. If you are going invoke your rule engine multiple times for for the same set of rules but for different facts, state full rule session makes more sense. For one off usage, stateless rule session is appropriate.

Next, we create the fact object and insert into the rule engine. The fact ends up in the working memory of the rule engine. Then we fire all the rules. When the rule engine returns, the discount field of the Customer object should be populated with total discount.

We insert the fact into the rule engine again and invoke the rule engine for the second agenda group. When this call returns, the discount value should be capped to the maximum, in case it exceeded the maximum.

The rule session is created lazily, the first time execute() is called and then saved as a class member. For subsequent calls, the saved instance is used. This is one of the benefits of state full rule session. It has a longer life span, compared to state less rule session. So, it can be created once and used multiple times. This is a better option, because there is some overhead in creating rule session, including rule compilation.

It is expected that discounts will be calculated for a batch of customers. The client code will create an instance of DiscountCalculator and will call execute() repeatedly for all the customers.

The Customer is a simple Java class with some properties and bunch of setter and getter methods as follows
[php]
public class Customer {
private String maritalStatus;
private String drivingStatus;
private String education;
private int accountAge;
private int discount;

/**
* @return the maritalStatus
*/
public String getMaritalStatus() {
return maritalStatus;
}

/**
* @param maritalStatus the maritalStatus to set
*/
public void setMaritalStatus(String maritalStatus) {
this.maritalStatus = maritalStatus;
}

/**
* @return the drivingStatus
*/
public String getDrivingStatus() {
return drivingStatus;
}

/**
* @param drivingStatus the drivingStatus to set
*/
public void setDrivingStatus(String drivingStatus) {
this.drivingStatus = drivingStatus;
}

/**
* @return the education
*/
public String getEducation() {
return education;
}

/**
* @param education the education to set
*/
public void setEducation(String education) {
this.education = education;
}

/**
* @return the accountAge
*/
public int getAccountAge() {
return accountAge;
}

/**
* @param accountAge the accountAge to set
*/
public void setAccountAge(int accountAge) {
this.accountAge = accountAge;
}

/**
* @return the discount
*/
public int getDiscount() {
return discount;
}

/**
* @param discount the discount to set
*/
public void setDiscount(int discount) {
this.discount = discount;
}

}
[/php]
We are basically done. In this simple example, we have used only the very basic features of Drools. For complex problems, some of the advanced features of Drools. You can read more about it in on line Drools documentation in JBOSS web site.

Rule Template

One of the features I like and some thing I have used is rule template. Instead of hard coding the constants in the rule definitions, they could be placeholder variables. The variables are bound to actual values during rule engine invocation.

In our case, we could replace all the discount values with variables and bind them to the actual values at run time. The big advantage is that the rules don’t need to be edited when there is a simple constant value change. It will only be necessary to modify the rule file when there is change in structure and logic of the rules

The rules as a template will look like this. In our case, all the discount related data could be stored in a database and be used to instantiate rules at run time.

template 
header 
marriedDiscount 
goodDriverDiscount 
MSDegDiscount 
medLoyaltyDiscount 
highLoyaltyDiscount 
discountUpLimit 
package mawazo.insurance.discount 
import mawazo.insurance.discount.server.Customer 
template discountRule 
rule "marital status" 
dialect "mvel" 
agenda-group "discount" 
when $c : Customer(maritalStatus == "married") 
then $c.discount += @{marriedDiscount} 
end 
rule "driving status" dialect "mvel" 
agenda-group "discount" 
when $c : Customer(drivingStatus == "good") 
then $c.discount += @{goodDriverDiscount} 
end 
rule "education" 
dialect "mvel" 
agenda-group "discount" 
when $c : Customer(education == "MS" ) 
then $c.discount += @{MSDegDiscount} 
end 
rule "medium loyalty" 
dialect "mvel" agenda-group "discount" 
when $c : Customer(accountAge >= 5 && accountAge <= 9) 
then $c.discount += @{medLoyaltyDiscount} 
end 
rule "high loyalty" 
dialect "mvel" 
agenda-group "discount" 
when $c : Customer(accountAge > 9) 
then $c.discount += @{highLoyaltyDiscount} 
end 
rule "discount cap" 
dialect "mvel" 
agenda-group "discountLimit" 
when $c : Customer(discount > @{discountUpLimit}) 
then $c.setDiscount(@{discountUpLimit}) 
end 
end 
template 

You may have noticed that all the variables used are declared under the section template header. All the rules are enclosed by a template and end template section. When using templates, the Drools rule engine API is slightly different from what we have used here. You can find the details in the Drools documentation.

Further Reading

The complete Drools documentation is it’s full glory can be found here. You may  also find this blog post on Drools interesting and informative with a detailed example.

Originally posted here.