My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
If you don't care about the background behind this, the reasons why you might want to use rules based programming, or a bit of theory, you can skip straight the Drools tutorial.

Background
One of the concepts I love to think about (and do) is raising the level of abstraction in a system. The more often you are telling the computer what, and not how, the better.

Of course, somewhere someone is doing imperative programming (telling it how), but I like to try to hide much of that somewhere and focus more on declarative programming (telling it what). Many times, that's the result of abstraction in general and DSLs and rules-based programming more specifically.

As a side note, let me say that this is not necessarily a zero-sum game. In one aspect you may be declaratively programming while in another you are doing so imperatively. Example:

function constructARecord()
{
  this.name=readFileLineNumber(fileName, 1);
  this.phone=readFileLineNumber(fileName, 2);
  this.address=readFileLineNumber(fileName, 3);
}

You are telling it how to construct a record, but you are not telling it how to read the file. Instead, you are just telling it to read the file.

Anyway, enough of that diversion. I hope I've convinced you.

When I finished writing the (half-working) partial order planner in Ruby, I mentioned I might like "to take actions as functions, which receive preconditions as their parameters, and whose output are effects" (let me give a special thanks to Hugh Sasse for his help and ideas in trying to use TSort for it while I'm on the subject).

Doing so may have worked well when generalizing the solution to a rules engine instead of just a planner (they are conceptually quite similar). That's often intrigued me from both a business application and game programming standpoint.

The good news is (as you probably already know), this has already been done for us. That was the subject of Venkat's talk that I attended at No Fluff Just Stuff at the end of June 2007.

Why use rules-based programming?
After a quick introduction, Venkat jumped right into why you might want to use a rules engine. The most prominent reasons all revolve around the benefits provided by separating concerns: When business rules change almost daily, changes to source code can be costly. Separation of knowledge from implementation reduces this cost by having no requirement to change the source code.

Additionally, instead of providing long chains of if...else statements, using a rule engine allows you the benefits of declarative programming.

A bit of theory
The three most important aspects for specifying the system are facts, patterns, and the rules themselves. It's hard to describe in a couple of sentences, but your intuition should serve you well - I don't think you need to know all of the theory to understand a rule-based system.

Rule Engine Design
Rules Engine based on Venkat's notes

Facts are just bits of information that can be used to make decisions. Patterns are similar, but can contain variables that allow them to be expanded into other patterns or facts. Finally, rules have predicates/premises that if met by the facts will fire the rule which allows the action to be performed (or conclusion to be made).

(Another side note: See JSR 94 for a Java spec for Rules Engines or this google query for some theory. Norvig's and Russell's Artificial Intelligence: A Modern Approach also has good explanations, and is a good introduction to AI in general (though being a textbook, it's pricey at > $90 US)).

(Yet another side note: the computational complexity of this pattern matching can be enormous, but the Rete Algorithm will help, so don't prematurely optimize your rules.)

Drools
Now that we know a bit of the theory behind rules-based systems, let's get into the practical to show how easy it can be (and aid in removing fear of new technology to become a generalist).

First, you can get Drools 2.5 at Codehaus or version 3, 4 or better from JBoss. At the time of writing, the original Drools lets you use XML with Java or Groovy, Python, or Drools' own DSL to implement rules, while the JBoss version (I believe) is (still) limited to Java or the DSL.

Since I avoid XML like the plague (well, not that bad), I'll go the DSL route.

First, you'll need to download Drools and unzip it to a place you keep external Java libraries. I'm working with Drools 4.0.1. After that, I created a new Java project in Eclipse and added a new user library for Drools, then added that to my build path (I used all of the Drools JARs in the library). (And don't forget JUnit if you don't have it come up automatically!)

Errors you might need to fix
For reference for anyone who might run across the problems I did, I'm going to include a few of the errors I came into contact with and how I resolved them. I was starting with a pre-done example, but I will show the process used to create it after trudging through the errors. Feel free to skip this section if you're not having problems.

After trying the minimal additions to the build path I mentioned above, I was seeing an error that javax.rules was not being recognized. I added jsr94-1.1.jar to my Drools library (this is included under /lib in the Drools download) and it was finally able to compile.

When running the unit tests, however, I still got this error:

org.drools.RuntimeDroolsException: Unable to load dialect 'org.drools.rule.builder.dialect.java.JavaDialectConfiguration:java'

At that point I just decided to add all the dependencies in /lib to my Drools library and the error went away. Obviously you don't need Ant, but I wasn't quite in the mood to go hunting for the minimum of what I needed. You might feel differently, however.

Now that the dialect was able to be loaded, I got another error:

org.drools.rule.InvalidRulePackage: Rule Compilation error : [Rule name=Some Rule Name, agendaGroup=MAIN, salience=-1, no-loop=false] com/codeodor/Rule_Some_Rule_Name_0.java (8:349) : Cannot invoke intValue() on the primitive type int

As you might expect, this was happening simply because the rule was receiving an int and was trying to call a method from Integer on it.

After all that, my pre-made example ran correctly, and being comfortable that I had Drools working, I was ready to try my own.

An Example: Getting a Home Loan
From where I sit, the process of determining how and when to give a home loan is complex and can change quite often. You need to consider an applicant's credit score, income, and down payment, among other things. Therefore, I think it is a good candidate for use with Drools.

To keep the tutorial simple (and short), our loan determinizer will only consider credit score and down payment in regards to the cost of the house.

First we'll define the HomeBuyer class. I don't feel the need for tests, because as you'll see, it does next to nothing.

package com.codeodor;

public class HomeBuyer {
        int _creditScore;
        int _downPayment;       
        String _name;
       
        public HomeBuyer(String buyerName, int creditScore, int downPayment) {
                _name = buyerName;
                _creditScore = creditScore;
                _downPayment = downPayment;
        }
       
        public String getName() {
                return _name;
        }
       
        public int getCreditScore(){
                return _creditScore;
        }
       
        public int getDownPayment(){
                return _downPayment;
        }
}

Next, we'll need a class that sets up and runs the rules. I'm not feeling the need to test this directly either, because it is 99% boilerplate and all of the code gets tested when we test the rules anyway. Here is our LoanDeterminizer:

package com.codeodor;
import org.drools.jsr94.rules.RuleServiceProviderImpl;

import javax.rules.RuleServiceProviderManager;
import javax.rules.RuleServiceProvider;
import javax.rules.StatelessRuleSession;
import javax.rules.RuleRuntime;
import javax.rules.admin.RuleAdministrator;
import javax.rules.admin.LocalRuleExecutionSetProvider;
import javax.rules.admin.RuleExecutionSet;
import java.io.InputStream;
import java.util.ArrayList;

public class LoanDeterminizer {
        // the meat of our class
        private boolean _okToGiveLoan;
        private HomeBuyer _homeBuyer;
        private int _costOfHome;
        public boolean giveLoan(HomeBuyer h, int costOfHome) {
                _okToGiveLoan = true;
                _homeBuyer = h;
                _costOfHome = costOfHome;
               
                ArrayList<Object> objectList = new ArrayList<Object>();
                objectList.add(h);
                objectList.add(costOfHome);
                objectList.add(this);
               
                return _okToGiveLoan;
        }
       
        public HomeBuyer getHomeBuyer() { return _homeBuyer; }
        public int getCostOfHome() { return _costOfHome; }
        public boolean getOkToGiveLoan() { return _okToGiveLoan; }
        public double getPercentDown() { return(double)(_homeBuyer.getDownPayment()/_costOfHome); }
       
        // semi boiler plate (values or names change based on name)
        private final String RULE_URI = "LoanRules.drl"; // this is the file name our Rules are contained in
       
        public LoanDeterminizer() throws Exception // the constructor name obviously changes based on class name
    {
        prepare();
    }
       
       
        // complete boiler plate code from Venkat's presentation examples follows
        // I imagine some of this changes based on how you want to use Drools
        private final String  RULE_SERVICE_PROVIDER = "http://drools.org/";
    
    private StatelessRuleSession _statelessRuleSession;
    private RuleAdministrator _ruleAdministrator;
       
    private boolean _clean = false;
            
    protected void finalize() throws Throwable
    {
        if (!_clean)  { cleanUp(); }
    }
    
    private void prepare() throws Exception
    {
        RuleServiceProviderManager.registerRuleServiceProvider(
                RULE_SERVICE_PROVIDER, RuleServiceProviderImpl.class );

        RuleServiceProvider ruleServiceProvider =
                RuleServiceProviderManager.getRuleServiceProvider(RULE_SERVICE_PROVIDER);

        _ruleAdministrator = ruleServiceProvider.getRuleAdministrator( );

        LocalRuleExecutionSetProvider ruleSetProvider =
                _ruleAdministrator.getLocalRuleExecutionSetProvider(null);

        InputStream rules = Exchange.class.getResourceAsStream(RULE_URI);
        RuleExecutionSet ruleExecutionSet =
                ruleSetProvider.createRuleExecutionSet(rules, null);

        _ruleAdministrator.registerRuleExecutionSet(RULE_URI, ruleExecutionSet, null);

        RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();

        _statelessRuleSession =
                (StatelessRuleSession) ruleRuntime.createRuleSession(
                        RULE_URI, null, RuleRuntime.STATELESS_SESSION_TYPE );
    }

    public void cleanUp() throws Exception
    {
        _clean = true;
        _statelessRuleSession.release( );
        _ruleAdministrator.deregisterRuleExecutionSet(RULE_URI, null);
    }
    
}

I told you there was a lot of boilerplate code there. I won't explain it all because:
  1. It doesn't interest me
  2. I don't yet know as much as I should about it
I fully grant that reason number one may be partially or completely dependent on the second one.

In any case, now we can finally write our rules. I'll be starting with a test:

package com.codeodor;

import junit.framework.TestCase;

public class TestRules extends TestCase {
    public void test_poor_credit_rating_gets_no_loan() throws Exception
    {
        LoanDeterminizer ld = new LoanDeterminizer();
        HomeBuyer h = new HomeBuyer("Ima Inalotadebt and Idun Payet", 100, 20000);
        
        boolean result = ld.giveLoan(h, 150000);
        assertFalse(result);
        
        ld.cleanUp();
    }

}

And our test fails, which is what we wanted. We didn't yet create our rule file, LoanRules.drl, so let's do that now.

package com.codeodor

rule "Poor credit score never gets a loan"
  salience 2
  when
    buyer : HomeBuyer(creditScore < 400)
    loan_determinizer : LoanDeterminizer(homeBuyer == buyer)
  then
    System.out.println(buyer.getName() + " has too low a credit rating to get the loan.");
    loan_determinizer.setOkToGiveLoan(false);
end

The string following "rule" is the rule's name. Salience is one of the ways Drools performs conflict resolution. Finally, the first two lines tell it that buyer is a variable of type HomeBuyer with a credit score of less than 400 and loan_determinizer is the LoanDeterminizer passed in with the object list where the homeBuyer is what we've called buyer in our rule. If either of those conditions fails to match, this rule is skipped.

Hopefully that makes some sense to you. If not, let me know in the comments and I'll try again.

And now back to our regularly scheduled test: running it again still results in a red bar. This time, the problem is:

org.drools.rule.InvalidRulePackage: Rule Compilation error : [Rule name=Poor credit score never gets a loan, agendaGroup=MAIN, salience=2, no-loop=false]
  com/codeodor/Rule_Poor_credit_score_never_gets_a_loan_0.java (7:538) : The method setOkToGiveLoan(boolean) is undefined for the type LoanDeterminizer

The key part here is "the method setOkToGiveLoan(boolean) is undefined for the type LoanDeterminizer." Oops, we forgot that one. So let's add it to LoanDeterminizer:

public void setOkToGiveLoan(boolean value) { _okToGiveLoan = value; }

Now running the test results in yet another red bar! It turns out I forgot something pretty big (and basic) in LoanDeterminizer.giveLoan(): I didn't tell it to execute the rules. Therefore, the default case of "true" was the result since the rules were not executed.

Asking it to execute the rules is as easy as this one-liner, which passes it some working data: _statelessRuleSession.executeRules(objectList);

For reference, the entire working giveLoan method is below:

public boolean giveLoan(HomeBuyer h, int costOfHome) throws Exception {
        _okToGiveLoan = true;
        _homeBuyer = h;
        _costOfHome = costOfHome;
       
        ArrayList<Object> objectList = new ArrayList<Object>();
        objectList.add(h);
        objectList.add(costOfHome);
        objectList.add(this);
       
        _statelessRuleSession.executeRules(objectList);
       
        // here you might have some code to process
        // the loan if _okToGiveLoan is true
       
        return _okToGiveLoan;
}

Now our test bar is green and we can add more tests and rules. Thankfully, we're done with programming in Java (except for our unit tests, which I don't mind all that much).

To wrap-up the tutorial I want to focus on two more cases: Good credit score always gets the loan and medium credit score with small down payment does not get the loan. I wrote the tests and rules iteratively, but I'm going to combine them here for organization's sake seeing as I already demonstrated the iterative approach.

public void test_good_credit_rating_gets_the_loan() throws Exception {
        LoanDeterminizer ld = new LoanDeterminizer();
       
        HomeBuyer h = new HomeBuyer("Warren Buffet", 800, 0);
       
        boolean result = ld.giveLoan(h, 100000000);
        assertTrue(result);
        
        // maybe some more asserts if you performed processing in LoanDeterminizer.giveLoan()
      
        ld.cleanUp();
}


public void test_mid_credit_rating_gets_no_loan_with_small_downpayment() throws Exception {
        LoanDeterminizer ld = new LoanDeterminizer();
        
        HomeBuyer h = new HomeBuyer("Joe Middleclass", 500, 5000);
        
        boolean result = ld.giveLoan(h, 150000);
        assertFalse(result);
        
        ld.cleanUp();
}

And the associated rules added to LoanRules.drl:

rule "High credit score always gets a loan"
     salience 1
     when
         buyer : HomeBuyer(creditScore >= 700)
         loan_determinizer : LoanDeterminizer(homeBuyer == buyer)
     then
         System.out.println(buyer.getName() + " has a credit rating to get the loan no matter the down payment.");
         loan_determinizer.setOkToGiveLoan(true);
end

rule "Middle credit score fails to get a loan with small down payment"
     salience 0
     when
         buyer : HomeBuyer(creditScore >= 400 && creditScore < 700)
         loan_determinizer : LoanDeterminizer(homeBuyer == buyer && percentDown < 0.20)
     then
         System.out.println(buyer.getName() + " has a credit rating to get the loan but not enough down payment.");
         loan_determinizer.setOkToGiveLoan(false);
end

As you can see, there is a little bit of magic going on behind the scenes (as you'll also find in Groovy) where here in the DSL, you can call loan_determinizer.percentDown and it will call getPercentDown for you.

All three of our tests are running green and the console outputs what we expected:

Ima Inalotadebt and Idun Payet has too low a credit rating to get the loan.
Warren Buffet has a credit rating to get the loan no matter the down payment.
Joe Middleclass has a credit rating to get the loan but not enough down payment.


As always, questions, comments, and criticism are welcome. Leave your thoughts below. (I know it was the length of a book, so I don't expect many.)

For more on using Drools, see the Drools 4 docs.

Finally, as with my write-ups on Scott Davis's Groovy presentation, his keynote, Stuart Halloway's JavaScript for Ajax Programmers, and Neal Ford's 10 ways to improve your code, I have to give Venkat most of the credit for the good stuff here. I'm just reporting from my notes, his slides, and my memory, so any mistakes you find are probably mine. If they aren't, they likely should have been.

Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!


Comments
Leave a comment

Noting the 3 red bars when writing our first test, perhaps it would have been a good idea to write some tests for the LoanDeterminizer class.

Posted by Sam on Sep 10, 2007 at 12:51 PM UTC - 5 hrs

Te agradezco grandemente tus comenetarios, tenia al menos dos semanas tratando de reslver el mensaje de error. Trate de solucionar el error por muchos opciones.

saludos..

Posted by José Guadalupe García on Sep 11, 2007 at 10:31 PM UTC - 5 hrs

Gracias José. Mi español es no muy bueno (so forgive me). ¿Con qué error le tenían problemas?

(thanks to Babelfish for the correct conjugation of "tener" if that happens to be correct)

Posted by Sam on Sep 12, 2007 at 06:13 AM UTC - 5 hrs

JSR94 is a terrible standard with no redeeming features :) almost anyone that uses it ends up having to use proprietary extensions, via the properties map, thus defeating the point of the standard. So I would really recommend you use the Drools api directly. Other than great blog, it's really nice to see people out in the community doing this sort of thing :)

Thanks

Mark
http://blog.athico.com

Posted by Mark Proctor on Sep 12, 2007 at 07:36 AM UTC - 5 hrs

Thanks for the comment, Mark. I have read as much about JSR94 (that it stinks), but I forgot to include that bit in the post for some reason unbeknownst to me now. Always great to hear it straight from the horse's mouth though!

And I'm definitely looking forward to getting deeper into developing with Drools.

Posted by Sam on Sep 12, 2007 at 10:41 AM UTC - 5 hrs

Hi,

With respect to JBoss Rules,
We are currently planning to implement JBoss Rules using service-oriented architecture (SOA) in our mission critical project - We have used MS Excel file as templates where-in we have custom created and managed formula, for which we have properties exposed for get/set in C#.

The Drools Rules Language (DRL) and XML files are compiled from the source excel template file and are used to validate the results which have been placed in a shared folder. Currently we have hit a limitation in that the formula could not be extended based on the current design using excel file templates.

Can anyone throw more light on how well excel formula can be used in JBoss Rules Excel templates and in the compiled binaries (Drools Rule Language)

Also, do let me know if there are any limitations on using Excel files as templates. Thanks!

Kumar

Posted by Kumar on Oct 15, 2007 at 01:43 AM UTC - 5 hrs

Kumar,

I've yet to do anything like that before. I suspect if you are doing the integration yourself that there would be no limitation. But, if there is already a feature to do it for you, you might be running into problems.

I'm not sure I understand what is happening when you say "the formula could not be extended based on the current design using excel file templates." Are you saying you have to recompile each time?

Is there a way to code it such that you read the excel file each time and construct the rules on the fly? (and would that solve your problem?)

In any case, you might find better answers from the Rules-Users community mailing list at https://lists.jboss.org/mailman/listinfo/rules-use... ... although, I am always happy to help any way I can.

Posted by Sam on Oct 15, 2007 at 10:32 AM UTC - 5 hrs

HI,
I don't known the meaning of " buyer : HomeBuyer(creditScore >= 700)
loan_determinizer : LoanDeterminizer(homeBuyer == buyer)
",and I am a learner,Could you expain it ?Thank you very much

Posted by LanJIe Zhao on Feb 02, 2008 at 12:47 AM UTC - 5 hrs

Basically, it's saying set "buyer" variable (for reuse in the "then" part) to the HomeBuyer object that gets passed in. The creditScore >= 700 part is a condition, where if the credit score is less than that the "then" part won't execute.

The loan_determinizer part follows the same pattern.

Posted by Sammy Larbi on Feb 02, 2008 at 11:18 AM UTC - 5 hrs

hello,
we are migrating from drools 2.5 to drools 4.0
rest all is working fine but just we are finding problem in the ejb-jar.xml file.
In each <session> and <entity> tags one tag gets added when we try to build the project.
The line which is expected is : <session>
but the generated line is : <session xml:base="file:E:/ ... >
can anyone help!

Posted by Anupam on Feb 19, 2008 at 01:01 AM UTC - 5 hrs

Hi,
We are developing a travel site and in front end we are using struts framework,at back end we are using Hibernate frame work along with DAO's and between the layers we are using DTO's. In our application we need to use a rule engine for this we are using Drools4.0.4,now our problem is How to integrate the rule engine between struts and DAO which uses DTO's for transfering of data. We need to search flight fare and if the flight fare is 2000 then we need to give a discount of 20% on this. I read the article in Onjava (http://www.onjava.com/lpt/a/6160) but I am not understanding how the rule engine gets data from DTO's and again sets it.

please help me in this

regards
sudarshan

Posted by sudarshan on Feb 19, 2008 at 04:43 AM UTC - 5 hrs

@Anupam: I think you might find better answers on the mailing list at https://lists.jboss.org/mailman/listinfo/rules-use...

@sudarshan: Forget about DAOs and DTOs for the time being. What makes them so special they can't be used with the above tutorial? The tutorial shows how to get and set data in objects from the rules, and from the rule runner. There should be nothing about TLA (three-letter acronym) objects that I can see would be a problem.

Hope that helps!

Posted by Sammy Larbi on Feb 19, 2008 at 06:31 AM UTC - 5 hrs

org.drools.RuntimeDroolsException: Unable to load dialect 'org.drools.rule.builder.dialect.java.JavaDialectConfiguration:java'......for this exception you no need to import all the jars. This if the due to jdt jar not in the class path.import the com.springsource.org.apache.jasper.org.eclipse.jdt.springsource-6.0.20.S2-r5956.jar file from the drools bin distribution to the class path.

Posted by Prabakar on Jul 07, 2010 at 10:06 AM UTC - 5 hrs

Is there an update to this with the latest version of drools. Many of the APIs used in LoanDeterminizer seem depricated in the latest version.

Posted by ranga on Feb 22, 2011 at 05:17 AM UTC - 5 hrs

ranga- No, I have not had a chance to update this, and I'm afraid I don't have the sources any longer.

If you (or anyone else) find or create an updated tutorial I'll gladly post it or link to it from here!

Posted by Sammy Larbi on Feb 23, 2011 at 10:11 AM UTC - 5 hrs

"Exchange" cannot be resolved to a type. I've this error. Someone could tell me where Exchange come from or its type. Thanks.

Posted by Cyril on May 24, 2011 at 04:57 AM UTC - 5 hrs

Cyril,

This is from a fairly old version of Drools. If you are running a newer version, you probably need some updated code.

I wish I could be more helpful, but I don't recall where it's from myself, and searching the web was not helpful from what I found.

Posted by Sammy Larbi on Jun 12, 2011 at 12:42 PM UTC - 5 hrs

This comment is regarding the exception

org.drools.RuntimeDroolsException: Unable to load dialect 'org.drools.rule.builder.dialect.java.JavaDialectConfiguration:

When you encounter this exception, it means that some of the dependencies of drools are missing. In this case the following jars are to be added as the dependicies,

drools-compiler-<VERSION>.BRMS

drools-core--<VERSION>.BRMS

drools-decisiontables-<VERSION>.BRMS

drools-jsr94-<VERSION>.BRMS

Posted by Dwarka on Jun 13, 2011 at 06:15 AM UTC - 5 hrs

I had a quick question about Drools is there a simple way to export the business rules in Drools to a text file, excel file, etc. Trying to understand how to read business rules outside of Drools.

Posted by Kman on Jun 13, 2011 at 01:48 PM UTC - 5 hrs

Kman, the .drl file is just text, so can you open that and read it?

If you're looking for something to make that more readable / less codelike, I'm afraid I'm not aware of anything.

Posted by Sammy Larbi on Jul 06, 2011 at 06:02 AM UTC - 5 hrs

"Exchange" cannot be resolved to a type. I've this error.
Can some one help me in fixing this error.

Posted by Srinivasan on Nov 07, 2012 at 02:09 PM UTC - 5 hrs

Leave a comment

Leave this field empty
Your Name
Email (not displayed, more info?)
Website

Comment:

Subcribe to this comment thread
Remember my details
Google
Web CodeOdor.com

Me
Picture of me

Topics
.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and Cplusplus (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (76)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (8)
Windows (1)
With (1)
YAGNI (10)

Resources
Agile Manifesto & Principles
Principles Of OOD
ColdFusion
CFUnit
Ruby
Ruby on Rails
JUnit



RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner