My experiments with technology

JBoss Drools – Decision Tables

March 28, 2009 · 6 Comments

I have been playing around with JBoss Drools(Version 4.0.7) – the open source rules engine.  JBoss supports business rules implementation using three techniques:

  1. Decision Tables
  2. Writing DRL file
  3. Defining business flow using rules flow rf file

This post is going to cover the first technique for business rules definition using Drools Decision table.

Business users regularly crib about the fact that they need IT involvement to make changes to business rules.  One of the typical business rules implementation requirement is a decision table. Business rules can be laid out in tabular fashion and association between conditions and actions clearly defined.

JBoss Drools simplifies the decision table implementation approach by supporting defining decision tables in Microsoft Excel spreadsheets. For better understanding, I am going to use a simple insurance quick quote example.

The first thing to get started is installing Eclipse on your machine. I had Eclipse Europa version installed on my machine, hence I have to pick up the appropriate Drools Engine, Eclipse plug-in download. Next comes the procedure for setting up Drools Engine, IDE et al is available here.

If the Drools Eclipse plug-in setup is working fine, you should be able to use the Eclipse File –> New menu to create a new Rule Project. In the newly created project named say “BusinessRules”, you will see the following folder structures:

BusinessRules|

|—– src/main/java
|
|—– src/main/rules

The src/main/java folder will hold the java class file source code and the rules files (.xls, .drl, .rf) wil be housed in src/main/rules. To create a new Decision table, select File –> New –> New Decision Table. The excel spreadsheet created will have the same features of a normal MS Excel spreadsheet except the rows 1 to 10 which have been created with information specific to Drools.

Please find below a screen shot of the decision table I prepared for an insurance quoting engine. The table decides based on the following if the applicant is eligible for insurance cover or not.

  • Applicant’s gender
  • Applicant’s age
  • Face Amount applied for
  • Policy Issue State
  • Smoker Status
  • Whether the applicant is suffering from any adverse disease.
Drools Decision Table

Drools Decision Table

Now that we have a sample decision tables done let’s set up the java source.

package com.insure.qq;

public class QuickQuoteInputProfile {

	private String state              = null;
	private String gender             = null;
	private int age                   = 0;
	private Double faceAmount         = null;
	private boolean adverseDiagnosis  = false;
	private SmokerProfile smokingProf = null;

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Double getFaceAmount() {
		return faceAmount;
	}

	public void setFaceAmount(Double faceAmount) {
		this.faceAmount = faceAmount;
	}

	public boolean isAdverseDiagnosis() {
		return adverseDiagnosis;
	}

	public void setAdverseDiagnosis(boolean adverseDiagnosis) {
		this.adverseDiagnosis = adverseDiagnosis;
	}

	public SmokerProfile getSmokingProf() {
		return smokingProf;
	}

	public void setSmokingProf(SmokerProfile smokingProf) {
		this.smokingProf = smokingProf;
	}

}

package com.insure.qq;

public class SmokerProfile {

	private String smokerStatus = null;

	public String getSmokerStatus() {
		return smokerStatus;
	}

	public void setSmokerStatus(String smokerStatus) {
		this.smokerStatus = smokerStatus;
	}

}

Now that we have all the code we need, let’s try and understand what have done.

First things first the decision table. The rows C2, C3 and C4 store specific tag information, They specify that their adjoining columns D2, D3 and D4 signify the name of the ruleset, the java class import statements and notes regarding the rules if any. A developer should not add any information in these columns besides the ones specified. The excel spread sheet does allow the developer the flexibility of defining two rule tables. We will limit ourselves to using one. Rows C6 to C10 have their own significances. C6 is used to name the rules table, C7 by itself has not information but the subsequent columns can hold two values “CONDITION” or “ACTION”. Note that the rules will not be valid unless the value in the column is one or the other. In D8 and subsequent columns, we specify the actual java class referenced in the business rule. In our case it is QuickQuoteInputProfile. Note the notation used is <variable name>: <Java Class name>. There should always be a space after the colon or else you would end up debugging some weird Drools error messages. In D9 and the following columns we specify the class attribute and the condition in which it is analyzed. D10 and following columns can be used to give business friendly names to each evaluation parameter.

Now let us understand how each parameter is evaluated.  Note that $param is a standard used to specify the value mentioned in the excel spreadsheet cell.

state in ($param) – It checks if the state requested for is part of the eligible states. Here state is an attribute of the QuickQuoteInputProfile class. Drools implicitly uses the getter method. The ‘in’ operator usage signifies list comparison. As we want multiple String value comparisons, they are defined in the cell using a comma separator. The ” ” around the value specifies that the value is evaluated as a string.

gender == $param – String or any other primitive data type can be compared using the == operator. Putting the apostrophes around the param “$param” converts the evaluation values i.e. MALE/FEMALE into string values.

age >= $1, age <=$2 – In case we need to do a range comparison like an applicant’s age is within a given range, we can use the greater than, less than operators and use $1 and $2 as place holders. You can also use $param1 etc as the name. Also note that the comma separator between the age >= $1, age <= $2 is an implicit AND condition.  This can be changed. To make the condition an OR condition instead of age >= $1, age <= $2 put the condition age >= $1 || age <= $2 and to make the condition more explicit with respect to the other conditions, enclose this condition in brackets like (age >= $1 || age <= $2).

quickQuoteProfile.smokingProf.smokerStatus == “$param” – It is possible that we are evaluating an object composed of objects. Drools makes it possible to navigate the entire hierarchy, using the dot(.) notation. Just ensure that all the objects are instantiated(otherwise you will get NullPointerExceptions) and that the getters will appropriate access modifiers provided.

Sometimes we need to write rules that will be triggered by the mere presence or absence of an object. For example, if SmokerProfile object exists within QuickQuoteInputProfile, then take certain action. In DRL this can be done by exists quickQuoteProfile.smokingProf(), unfortunately this fails in Excel based decision tables as a param is expected. Therefore the work around is to define the condition as exists (quickQuoteProfile.smokingProf.smokerStatus == $param) and in the rule cell write smokerStatus. The rule achieves the same effect as required.

Note that all rules are evaluated with an implicit AND condition between them. For example, rule 1 translates to the following condition:

The policy issue state should be one of the US states “VA”, “PA”, “NY” AND applicant gender should be MALE AND applicant age is greater than 20 and less than 65 AND faceamount greater than 50K and less than 250K AND applicant is a non-smoker AND applicant does not suffer from any disease which could lead to an adverse rating.

I will cover the ACTION using the following screenshot.

Drools Decision Tables - 2

Drools Decision Tables - 2

A typical rule will have a condition, which is evaluated and based on the evaluation an action taken. The decision/action arrived at by rules engine is conveyed to rules engine invoking client via a java object. Drools has a concept of a global variable. It can be defined as specified in row 25. In cell D25, we store the complete class name and the variable name. In our current example it is “com.insure.qq.ro.QuickQuoteResult result” where result is the variable name.  The structure of the Java class QuickQuoteResult is as below:

package com.insure.qq.ro;

public class QuickQuoteResult {

	private boolean eligible = false;

	public boolean isEligible() {
		return eligible;
	}

	public void setEligible(boolean eligible) {
		this.eligible = eligible;
	}

}

How does the decision table know that this is the class to instantiate? In cell J9, we say result.setEligible($param) and in cell C25 and D25 we specify the Variable and variable class name. Note that J9 is a cell defined under the “ACTION” column, thus translating to an action condition.(Hope this write-up is satisfactory “Novice”. Thanks for the rap on the head, must have become lazy.)

Now how do we run the rules engine. Create the following test class:

package com.insure.qq.test;

import java.io.StringReader;

import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.WorkingMemory;
import org.drools.audit.WorkingMemoryFileLogger;
import org.drools.compiler.PackageBuilder;
import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.drools.rule.Package;

import com.insure.qq.QuickQuoteInputProfile;
import com.insure.qq.SmokerProfile;
import com.insure.qq.ro.QuickQuoteResult;

public class QQBRETest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
        try {

        //load up the rulebase
            RuleBase ruleBase = readDecisionTable();
            WorkingMemory workingMemory = ruleBase.newStatefulSession();

            WorkingMemoryFileLogger logger = new WorkingMemoryFileLogger(workingMemory);
            logger.setFileName("C:/drools-audit");

            QuickQuoteInputProfile input = new QuickQuoteInputProfile();
            input.setAge(21);
            input.setFaceAmount(Double.valueOf(200000));
            input.setState("VA");
            input.setGender("MALE");
            input.setAdverseDiagnosis(false);

            SmokerProfile prof = new SmokerProfile();
            prof.setSmokerStatus("Y");

            input.setSmokingProf(prof);
            QuickQuoteResult result = new QuickQuoteResult();

            workingMemory.insert(input);
            workingMemory.setGlobal("result", result);
            workingMemory.fireAllRules();
            logger.writeToDisk();

            System.out.println(result.isEligible());

        } catch (Throwable t) {
            t.printStackTrace();
        }
	}

	private static RuleBase readDecisionTable() throws Exception {
		//read in the source
        final SpreadsheetCompiler converter = new SpreadsheetCompiler();
        final String drl = converter.compile( "/QuickQuoteExample.xls", InputType.XLS );
        System.out.println(drl);
		PackageBuilder builder = new PackageBuilder();
		builder.addPackageFromDrl( new StringReader( drl ) );
		Package pkg = builder.getPackage();
		RuleBase ruleBase = RuleBaseFactory.newRuleBase();
		ruleBase.addPackage( pkg );
		return ruleBase;
	}

}

Note the usage of the WorkingMemoryFileLogger. This will create an audit file named drools-audit.log in C: drive. You can view the contents using the audit view within the Drools Eclipse perspective. In the readDecisionTable method, there is an implementation of converting the decision table spreadsheet into Drools DRL file.

That covers my initiation into the world of JBoss Drools Decision Tables. More drools stuff next time when write in.

Categories: JBoss Drools

6 responses so far ↓

  • Novice // May 13, 2009 at 7:40 pm | Reply

    The article is good but the action part has not been described clearly.

  • JBoss Drools – Defining business rules « My experiments with technology // May 20, 2009 at 2:40 pm | Reply

    [...] last two articles focused on some of the current projects that I was working on. So after kick starting Drools(4.0.7), I lost track and wrote some posts on JBoss clustering. Interesting as they were, I [...]

  • Novice // September 9, 2009 at 3:01 am | Reply

    Hi,
    Thanks for updating the already well-written article to make it Perfect :-) .
    I have a small query. I have a set of rules each one almost like-
    rule “Rule 1″
    salience 1
    no-loop true
    when
    ruleReq : ArrayList()
    k : Key(keyName == “Form1″, keyValue == “101″ || keyValue == “102″ || keyValue == “103″ )
    then
    ruleReq.add(new Key(k.getKeyName(), k.getKeyValue()));
    end

    The action part of this rule is not working fine in the decision table. Can you suggest how can I write the Then part of this rule in Decision table?

    Thanks in advance,
    FanduMax

    • Mr. President // September 10, 2009 at 10:42 am | Reply

      I do not have a ready codebase available to validate, but my first suggestion would be to make ruleReq a global variable.

  • Novice // September 17, 2009 at 2:16 pm | Reply

    Hi Mr. President,

    Thanks for your response. Actually I tried with the global variable but could not get the desired results by adding values to result Array List using decision table. If you have any more clues for this, please post them here.
    Also, If possible, can you explain how to use functions in Decision tables? I’m struggling to get a few functions working with decision tables.
    Thanks in advance,
    Novice

    • Mr. President // September 18, 2009 at 4:21 am | Reply

      Here’s something to try out. I have used the code as per my Drools post titled “Defining business Rules”. I have added the following rule in the sample DRL:

      rule “Check a variation in code”
      salience 88
      when
      $policy : Policy()
      $list: ArrayList()
      from accumulate ( $coverage : Coverage(faceAmount > 25000) from $policy.coverages,
      init (ArrayList list = new ArrayList();),
      action (list.add($coverage);),
      reverse(;),
      result(list)

      )
      then
      result.setCoverages($list);
      end

      Added the following line of code in the PolicyRuleResult class with its appropriate getter and setter.

      private List coverages = new ArrayList();

      I think you will need a similar approach to tackle your key related problem. Hope this helps. As regards using functions in Decision tables, frankly I have not tried and I believe it will be a waste of effort. There is nothing in Drools documentation to suggest ways in implementing the same. IMHO better to code it in DRL.

Leave a Comment