ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Building Enterprise Services with Drools Rule Engine

by Birali Hakizumwami
01/17/2007

Using a rule engine provides a framework that allows a way to externalize business logic in a common place. This will in turn empower business users and subject matter experts of the business to easily change and manage the rules. Coding such rules directly into the application makes application maintenance difficult and expensive because the rules change so often. This article goes into detail on how to architect and build a service that uses Drools to provide business decisions. This service can be part of the overall enterprise SOA infrastructure. As such, it can either be a standalone service that is consumed in a one-to-many model by all contracted consumers, or part of a composite service that provides a complex business functionality. To illustrate this point, the article shows how a service using the Drools rule engine can hide the complexity of automating mortgage underwriting decisions that a mortgage company needs to make on a daily basis.

A rule engine such as Drools offers matching algorithms that can determine which rules need to be run and in which order, and provide a means of addressing conflict resolution. It allows facts to be reassessed by rules as they are being evaluated, causing the consequence of one rule to affect the fact base, or causing other rules to fire or retract as needed. By using a rule engine, rapidly changing business policies/rules can be externalized so that they can be maintained separately in one place. The rest of the enterprise applications can access the functionality through a service layer with well-defined contracts. The behavior of the service can then be modified without changing the interface contract or needing to recompile/redeploy. Rules are stored in human-readable form in a file so they can be changed with a text editor or rule editor. Many leading industry analysts have concluded that it is significantly more productive to build and change complex, decision-intensive applications with rules-based systems than with custom code.

Drools Rule Engine

Drools is a JSR-94-compliant rule engine that uses Rete, a matching algorithm developed by Charles Forgy. In the simplest terms, Rete builds a tree from all the rules to form a state machine. It begins with facts entering the tree at the top-level nodes as parameters to the rules, and working their way down the tree--if they match the conditions--until they reach the leaf nodes (rule consequences).

For this article, we use the JBoss Rules Workbench IDE as a tool to write and test the rules. The JBoss Rules workbench is delivered as an Eclipse plugin, which allows you to author and manage rules from within Eclipse. This is an optional tool; any other tool can be used to author and deploy the rules, provided the appropriate Drools libraries are included. Detailed instructions on how to obtain and install this plugin can be found at the Drools documentation site.

The following is a short description of the main libraries that constitute the Drools rule engine:

There are other dependencies that the above components require; for an exhaustive list please refer to the Drools documentation.

Mortgage Underwriting Service Using Drools

The following section goes into detail on how the Drools rule engine can be used to architect an automated mortgage underwriting service as part of an overall enterprise SOA initiative. Even though we are talking about mortgage underwriting as a case study, this discussion can be easily applied to any other business sector other than financial services (healthcare, telecommunications, etc.) that needs to externalize a set of business rules as a enterprise service.

The example has been kept simple for the sake of clarity and emphasis has been put on highlighting Drools rule engine features. Figure 1 shows a simple example of an underwriting engine architecture.

Underwriting Engine Architecture
Figure 1. Underwriting engine architecture

The architecture describes three components:

We start by writing a basic JUnit test that contains two test methods: testSuccessfulApplication() simulates a successful loan application and testAllFeedbackMessages() simulates a failed loan application and displays all the feedback messages.

public class TestUnderwritingService extends TestCase {

    public void testSuccessfulApplication() throws Exception{
        Borrower bo = new Borrower();
        bo.setAge(40);
        bo.setGrossIncome(290000.0);

        // Call the CreditScore Service to fill the credit score
        CreditScoringService css = new CreditScoringService();
        css.setScore(700); // Value hard-coded for testing purpose
        css.getScore(bo);

        // Call the affordability model service
        AffordabilityModelService ams = new AffordabilityModelService();
        ams.runModel(bo);

        Property po = new Property();
        po.setPurpose(Flag.OWNER_OCCUPIED);
        po.setYearBuilt(2003);
        po.setZipCode("22102");

        // Call the PropertyValue service
        PropertyValuationService pvs = new PropertyValuationService();
        pvs.setPropertyValue(800000.0); // hard-coded for testing
        pvs.getPropertyValue(po);

        // Create a Loan Application
        LoanApplication la = new LoanApplication();
        la.setBorrower(bo);
        la.setProperty(po);
        la.setLoanAmount(390000.0);
        la.setLoanToValueRatio(la.getLoanAmount()/po.getValue()*100);

        // Calls the underwriting service
        UnderwritingService ue = new UnderwritingService();
        ue.evaluate(la);

        System.out.println("\nTesting successful application");
        System.out.println("=============================");
        System.out.println(la);
        System.out.println("Feedback message size=" +
                                la.getFeedbackMsgSize());
        System.out.println("Affordability Flag=" + 
                                la.getAffordabilityFlag());
        System.out.println("Underwriting Decision=" + 
                                la.getStatus());

        //assertEquals(la.getStatus(), LoanApplication.PASSED);
        assertEquals(la.getStatus(), Flag.PASSED);

    }

    public void testAllFeedbackMessages() throws Exception{
        Borrower bo = new Borrower();
        bo.setAge(17);
        bo.setGrossIncome(170000.0);

        // Call the CreditScore Service to fill the credit score
        CreditScoringService css = new CreditScoringService();
        css.setScore(500);
        css.getScore(bo);

        // Call the affordability model service
        AffordabilityModelService ams = new AffordabilityModelService();
        ams.runModel(bo);

        Property po = new Property();
        po.setPurpose(Flag.INVESTMENT);
        po.setYearBuilt(1961);
        po.setZipCode("22102");

        // Call the PropertyValue service
        PropertyValuationService pvs = new PropertyValuationService();
        pvs.setPropertyValue(800000.0);
        pvs.getPropertyValue(po);

        // Create a Loan Application
        LoanApplication la = new LoanApplication();
        la.setBorrower(bo);
        la.setProperty(po);
        la.setLoanAmount(700000.0);
        la.setLoanToValueRatio(la.getLoanAmount()/po.getValue()*100);

        //    Calls the underwriting service
        UnderwritingService ue = new UnderwritingService();
        ue.evaluate(la);

        System.out.println("\nTesting all feedback messages");
        System.out.println("=============================");
        System.out.println(la);
        System.out.println("Feedback message size=" + la.getFeedbackMsgSize());
        System.out.println("Affordability Flag=" + la.getAffordabilityFlag());
        System.out.println("Underwriting Decision=" + la.getStatus());

        assertEquals(la.getStatus(), Flag.FAILED);

    }
}

The test case instantiates the business objects Borrower, Property, and LoanApplication.

public class LoanApplication {

    private double loanAmount;
    private int creditScore;
    private double loanToValueRatio;
    private Flag affordabilityFlag=Flag.AFFORDABLE;
    private int feedbackMsgSize;

    private Borrower borrower;
    private Property prop;

    private Flag status=Flag.PASSED;

    private ArrayList<String> feedbackMessages =
        new ArrayList<String>();

  .../...  // getters and setters
}

public class Borrower {
    int age;
    double setAffordableLoanAmount;
    double grossIncome;
    int creditScore;
    .../...
}

public class Property {
    private Flag type;
    private Flag purpose;
    private String zipCode;
    private int yearBuilt;
    private double value;

    .../...
}

The following enumeration class contains all the flags used in this article:

public enum Flag {
    AFFORDABLE, NOT_AFFORDABLE, PASSED, FAILED, INVESTMENT, OWNER_OCCUPIED;
}

As I mentioned before, the support services CreditScoringService, AffordabilityModelService, and PropertyValuationService populate parts of the application, but for this article, they are dummied out and return hardcoded values.

The other class that is being used by our JUnit test is the UnderwritingService, which contains the call to the Drools rule engine APIs.

public class UnderwritingService {
    public void evaluate(LoanApplication la)
        throws Exception{

        doRun("/Underwriting.drl", la);
        doRun("/Decision.drl", la);

    }

    private void doRun(String ruleFileName, LoanApplication la)
        throws Exception {

        RuleBase ruleBase = readRule(ruleFileName);
        WorkingMemory wm = ruleBase.newWorkingMemory();
        wm.assertObject(la);
        wm.assertObject(la.getBorrower());
        wm.assertObject(la.getProperty());
        wm.fireAllRules();

    }

    private RuleBase readRule(String ruleFileName) 
        throws Exception {

        Reader source = new InputStreamReader(
                UnderwritingService.class.
                    getResourceAsStream(ruleFileName) );

        PackageBuilder builder = new PackageBuilder();
        builder.addPackageFromDrl( source );
        Package pkg = builder.getPackage();

        RuleBase ruleBase = RuleBaseFactory.newRuleBase();
        ruleBase.addPackage( pkg );
        return ruleBase;
    }

}

Let's highlight some important methods from this class:

Underwriting Rules Using Drools .drl Files

We will start by describing the underwriting process and policies and how to define rules from these policies. Two .drl files will be provided: Underwriting.drl contains the underwriting policy rules and Decision.drl contains the underwriting decisions rules. The underwriting process typically has two stages:

Once these rules have been evaluated, an underwriting decision needs to be made based on the outcome. The following rules from the Decision.drl are used:

rule "Underwriting decision"

    when
        $loanApp : (LoanApplication(affordabilityFlag == Flag.NOT_AFFORDABLE) or
        LoanApplication( feedbackMsgSize > 0))
    then
        $loanApp.setStatus(Flag.FAILED);

end

Conflict Resolution

Once all facts are inserted into the working memory, where they may be modified or retracted, the rule engine will find all possible matches for the facts. All candidate rules found during this matching stage are added to the rule engine's agenda. The agenda manages the execution order of these conflicting rules using a conflict resolution strategy. Conflict resolution is required when there are multiple rules on the agenda that are candidates for execution. The rules that are candidates for execution are examined to determine the next set of rule actions to execute based on a predetermined resolution scheme. Since firing a rule may affect the state of the working memory, the rule engine needs to know in what order the rules should fire.

Drools uses different strategies for conflict resolution (please refer to the Drools manual for an in-depth discussion). For this article we use salience, which is the most-used strategy. For this strategy, the rule author specifies that a particular rule has a higher priority (the higher salience rule) than the other matching rules. The highest priority rule is picked from all the matching rules.

When writing and authoring rules, one shouldn't make an assumption that the rules will fire in any particular order or follow some sort of flow. This minimizes the need to resort to any of the conflict resolution strategies described above.

Let's illustrate how conflict resolution is applied to the affordability assessment example described earlier:

    rule "Income multiples"
        salience -3
        when
            Borrower( $grossIncome : grossIncome )
                Property( value > (new Double($grossIncome.doubleValue()*3)))
                $loanApp : LoanApplication()
        then
            $loanApp.setAffordabilityFlag(Flag.NOT_AFFORDABLE);
    end

    rule "Affordability Model"
        salience -4
        when
                Borrower( $affordableLoanAmount : affordableLoanAmount )
                Property( value > (new Double($affordableLoanAmount.doubleValue())))
            $loanApp : LoanApplication()
        then
            $loanApp.setAffordabilityFlag(Flag.NOT_AFFORDABLE);
    end

To make an affordability assessment, lenders can apply either the income multiples rule or use the affordability model rule. The application of the affordability model rule may result in a value that exceeds the value obtained from the income multiple rule. When this happens, lenders usually will disregard the affordability model rule and make their final affordability assessment from the income multiple rule. This is accomplished in Drools by giving the income multiple rule a higher salience value.

Testing the Mortgage Underwriting Example

The example was tested using JBoss Drools 3.0.4 and JDK 1.5. A run of the JUnit test displays the following results:

Testing successful application
=============================
Feedback message size=0
Affordability Flag=AFFORDABLE
Underwriting Decision=PASSED


Testing all feedback messages
=============================
Property should be built after 1965
Type of property should be Owner Occupied
Credit score should be greater than 600
Borrower minimum age should be 18
Loan to value ratio should not be greater than 80
Loan Amount should be between $100,000 and $400,000

Feedback message size=6
Affordability Flag=NOT_AFFORDABLE
Underwriting Decision=FAILED

A feedback report can be created out of this data and displayed to the lender for further analysis.

Conclusion

In this article, I have shown how the Drools rule engine can be used to abstract complex business rules in a service that can be part of a larger enterprise service framework. A service that automates mortgage underwriting decisions was used as a case study; the patterns used to implement this service using Drools can be applied to many other problem domains that are well-suited to a rules-based approach. The article highlights through a real-world example important features of the Drools rule engine: externalization of rapidly changing business policies using rule-based programming, runtime evaluation of rules, and conflict resolution. The article did not cover deployment considerations or change-management issues.

Resources

Birali Hakizumwami is currently working as an application architect.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.