<< JSR-299 & @Produces | Home | 100% code coverage, Hibernate Validator and Design by Contract >>

A really simple but powerful rule engine

UPDATE: Version 2.0 of the library now exists which supports Scala. It is a breaking change in that "Action" instances as shown below are now "AbstractAction", and the "Action" class is only supported in Scala, where functions can be passed to the action constructor instead of having to override the AbstractAction. Scala collections are also supported in the engine.

I have the requirement to use a rule engine. I want something light weight and fairly simple, yet powerful. While there are products out there which are super good, I don't want something with the big learning overhead. And, I fancied writing my own!

Here are my few basic requirements:

  • Use some kind of expression language to write the rules,
  • It should be possible to store rules in a database,
  • Rules need a priority, so that only the best can be fired,
  • It should also be possible to fire all matching rules,
  • Rules should evaluate against one input which can be an object like a tree, containing all the information which rules need to evaluate against
  • Predefined actions which are programmed in the system should be executed when certains rules fire.

So to help clarify those requirements, imagine the following examples:

1) In some forum system, the administrator needs to be able to configure when emails are sent.

Here, I would write some rules like "when the config flag called sendUserEmail is set to true, send an email to the user" and "when the config flag called sendAdministratorEmail is true and the user has posted less than 5 postings, send an email to the administrator".

2) A tarif system needs to be configurable, so that the best tarif can be offered to customers.

For that, I could write rules like these: "when the person is younger than 26 the Youth tarif is applicable", "when the person is older than 59, the Senior tarif is applicable", and "when the person is neither a youth, nor a senior, then they should be given the Default tarif, unless they have had an account for more than 24 months, in which case they should be offered the Loyalty tarif".

3) A train ticket can be considered to be a product. Depending upon the travel request, different products are suitable.

A rule here, could be something like: "if the travel distance is more than 100km and first class is desired, then product A is to be sold."

Finally, a more complex example, involving some iteration of the input, rather than just property evaluation:

4) Timetable software needs to deterimine when students can leave school.

A rule for that might read: "If a class contains any student under age 10, the entire class gets to leave early. Otherwise, they leave at the normal time."

So, with those requirements in mind, I went to look for an expression language. I started with the unified expression language specified in JSP 2.1. Using the jasper jar used in Tomcat and Apache Commons EL jar, I got something up and running very quickly. Then I discovered the MVEL library from Codehaus.org, which incidentally is used in Drools (the leading Java rule engine?) and it worked even better. It offers far more functionality as far as I can tell.

So, I designed my rule engine to work like this:

1) An engine is configured with some rules.

2) A rule has these attributes:
    - namespace: an engine may contain many rules, but only some may be relevant to a particular call and this namespace can be used for filtering
    - name: a unique name within a namespace
    - expression: an MVEL expression for the rule
    - outcome: a string which the engine might use if this rules expression evaluates to true
    - priority: an integer. The bigger the value, the higher the priority.
    - description: a useful description to aid the management of rules.

3) The engine is given an input object and evaluates all rules (optionally within a namespace) and either:
    a) returns all rules which evaluate to true,
    b) returns the outcome (string) from the rule with the highest priority, out of all rules evaluating to true,
    c) execute an action (defined within the application) which is associated with the outcome of the rule with the highest priority, out of all rules evaluating to true.

4) "Action"s are instances of classes which the application programmer can supply. An action is given a name. When the engine is asked to execute an action based on the rules, the name of the action matching the "winning" rule's outcome is executed.

5) A rule can be built up of "sub-rules". A subrule is only ever used as a building block on which to base more complex rules. When evaluating rules, the engine will never select a subrule to be the best (highest priority) "winning" rule, i.e. one evaluating to true. Subrules make it easier to build complex rules, as I shall show shortly.

So, time for some code!

First, let's look at the code for the tarif system:

Rule r1 = new Rule("YouthTarif", "input.person.age < 26", "YT2011", 3, "ch.maxant.someapp.tarifs", null);
Rule r2 = new Rule("SeniorTarif", "input.person.age > 59", "ST2011", 3, "ch.maxant.someapp.tarifs", null);
Rule r3 = new Rule("DefaultTarif", "!#YouthTarif && !#SeniorTarif", "DT2011", 3, "ch.maxant.someapp.tarifs", null);
Rule r4 = new Rule("LoyaltyTarif", "#DefaultTarif && input.account.ageInMonths > 24", "LT2011", 4, "ch.maxant.someapp.tarifs", null);
List<Rule> rules = Arrays.asList(r1, r2, r3, r4);

Engine engine = new Engine(rules, true);

TarifRequest request = new TarifRequest();
request.setPerson(new Person("p"));
request.setAccount(new Account());

request.getPerson().setAge(24);
request.getAccount().setAgeInMonths(5);
String tarif = engine.getBestOutcome(request);


So, in the above code, I have added 4 rules to the engine, and told the engine to throw an exception if any rule cannot be pre-compiled. Then, I created a TarifRequest, which is the input object. That object is passed into the engine, when I ask the engine to give me the best outcome. In this case, the best outcome is the string "YT2011", the name of the most suitable tarif for the customer I added to the tarif request.

How does it all work? When the engine is given the rules, it does some validation on them, and pre-compiles the rules (to improve overall performance). Notice how the first two rules refer to an object called "input"? That is the object passed into the "getBestOutcome" method on the engine. The engine passes the input object to the MVEL class together with each rules expression. Anytime an expression evaluates to "true", the rule is put to the side as a candidate to be the winner. At the end, the candidates are sorted in order of priority, and the outcome field of the rule with the highest priority is returned by the engine.

Notice how the third and fourth rules contain the '#' character. That is not standard MVEL expression language. The engine examines all rules when they are passed to it, and it replaces any token starting with a hash symbol, with the expression found in the rule named the same as the token. It wraps the expression in brackets. The logger outputs the full rule after reference rules have been resolved and replaced, just in case you want to check the rule.

In the above business case, we were only interested in the best tarif for the customer. Equally, we might have been interested in a list of possible tarifs, so that we could offer the customer a choice. In that case, we could have called the "getMatchingRules" method on the engine, which would have returned all rules, sorted by priority. The tarif names are (in this case) the "outcome" field of the rules.

In the above example, I wanted to receive any of the four outcomes, from the four rules. Sometimes however, you might want to build complex rules based on building blocks, but you might never want those building blocks to be a winning outcome. The train trip example from above can be used to show what I mean here:

Rule rule1 = new SubRule("longdistance", "input.distance > 100", "ch.maxant.produkte", null);
Rule rule2 = new SubRule("firstclass", "input.map[\"travelClass\"] == 1", "ch.maxant.produkte", null);
Rule rule3 = new Rule("productA", "#longdistance && #firstclass", "productA", 3, "ch.maxant.produkte", null);
List<Rule> rules = Arrays.asList(rule1, rule2, rule3);

Engine e = new Engine(rules, true);

TravelRequest request = new TravelRequest(150);
request.put("travelClass", 1);
List rs = e.getMatchingRules(request); 


In the above code, I build rule3 from two subrules. But I never want the outcomes of those building blocks to be output from the engine. So I create them as SubRules. SubRules don't have an outcome field or priority. They are simply used to build up more complex rules. After the engine has used the sub-rules to replace all tokens beginning in a hash during initialisation, it discards the SubRules - they are not evaluated.

The TravelRequest above takes a distance in the constructor, and contains a map of additional parameters. MVEL let's you easily access map values using the syntax shown in rule 2.

Next, consider the business case of wanting to configure an forum system. The code below introduces actions. Actions are created by the application programmer and supplied to the engine. The engine takes the outcomes (as described in the first example), and searches for actions with the same names as those outcomes, and calls the "execute" method on those actions (they all implement the IAction interface). This functionality is useful when a system must be capable of predefined things, but the choice of what to do needs to be highly configurable and independent of deployment.

Rule r1 = new Rule("SendEmailToUser", "input.config.sendUserEmail == true", "SendEmailToUser", 1, "ch.maxant.someapp.config", null);
Rule r2 = new Rule("SendEmailToModerator", "input.config.sendAdministratorEmail == true and input.user.numberOfPostings < 5", "SendEmailToModerator", 2, "ch.maxant.someapp.config", null);
List<Rule> rules = Arrays.asList(r1, r2);
		
final List<String> log = new ArrayList<String>();
		
Action<ForumSetup, Void> a1 = new Action<ForumSetup, Void>("SendEmailToUser") {
  @Override
  public Void execute(ForumSetup input) {
    log.add("Sending email to user!");
    return null;
  }
};
Action<ForumSetup, Void> a2 = new Action<ForumSetup, Void>("SendEmailToModerator") {
  @Override
  public Void execute(ForumSetup input) {
    log.add("Sending email to moderator!");
    return null;
  }
};

Engine engine = new Engine(rules, true);

ForumSetup setup = new ForumSetup();
setup.getConfig().setSendUserEmail(true);
setup.getConfig().setSendAdministratorEmail(true);
setup.getUser().setNumberOfPostings(2);
			
engine.executeAllActions(setup, Arrays.asList(a1, a2));


In the code above, the actions are passed to the engine when we call the "executeAllActions" method. In this case, both actions are executed, because the setup object causes both rules to evaluate to true. Note that the actions are executed in the order of highest priority rule first. Each action is only ever executed once - it's name is noted after execution and it will not be executed again, until the engines "execute*Action*" method is called again. Also, if you only want the action associated with the best outcome to be executed, call the "executeBestAction" method instead of "executeAllActions".

Finally, let's consider the classroom example.

String expression = 
    "for(student : input.students){" +
    "	if(student.age < 10) return true;" +
    "}" +
    "return false;";

Rule r1 = new Rule("containsStudentUnder10", expression , "leaveEarly", 1, "ch.maxant.rules", "If a class contains a student under 10 years of age, then the class may go home early");
		
Rule r2 = new Rule("default", "true" , "leaveOnTime", 0, "ch.maxant.rules", "this is the default");
		
Classroom classroom = new Classroom();
classroom.getStudents().add(new Person(12));
classroom.getStudents().add(new Person(10));
classroom.getStudents().add(new Person(8));

Engine e = new Engine(Arrays.asList(r1, r2), true);
		
String outcome = e.getBestOutcome(classroom);


The outcome above is "leaveEarly", because the classroom contains one student whose age is less than 10. MVEL let's you write some pretty comprehensive expressions, and is really a programming language in it's own right. The engine simply requires a rule to return true, if the rule is to be considered a candidate for firing.

There are more examples in the JUnit tests contained in the source code.

So, the requirements are fulfiled, except for "It should be possible to store rules in a database". While this library doesn't support reading and writing rules to / from a database, rules are String based. So it wouldn't be hard to create some JDBC or JPA code which reads rules out of a database and populates Rule objects and passes them to the Engine. I haven't added this to the library, because normally these things as well as the management of rules is something quite project specific. And because my library will never be as cool or popular as Drools, I'm not sure it would be worth my while to add such functionality.

I've put the rule engine into an OSGi library with the LGPL licence and it can be downloaded from my tools site. This library depends on MVEL, which can be downloaded here (I used version 2.0.19). If you like it, let me know!

© 2011, Ant Kutschera

Social Bookmarks :  Add this post to Slashdot    Add this post to Digg    Add this post to Reddit    Add this post to Delicious    Add this post to Stumble it    Add this post to Google    Add this post to Technorati    Add this post to Bloglines    Add this post to Facebook    Add this post to Furl    Add this post to Windows Live    Add this post to Yahoo!


Re: A really simple but powerful rule engine

This entry is rock. You did a great job. Thanks.

Re: A really simple but powerful rule engine

I have been looking for similar and simple rule engine for long time. The implementation is very clean and precise. I am trying to extend this design to work with large number of rules which is highly scalable and reliable.

Re: A really simple but powerful rule engine

I do like it~

Re: A really simple but powerful rule engine

Hello, I am trying to use the Rule Engine, Can you please help me understand how can I write contains method inside a Rule, For eg, I want to check if the value is present in a pre-defined list Something like this List<String> wageUnitsHour = new ArrayList<String>(); wageUnitsHour.add("Hour"); wageUnitsHour.add("Hr"); Rule wageUnitHour = new Rule("prewailingWageUnit", "input.wageUnit in wageUnitsHour" , "HOUR",3, "com.rules.wage.unit", null);

Re: A really simple but powerful rule engine

Good question. Have you looked at http://mvel.codehaus.org/MVEL+2.0+Operators? In the worst case you might have to build a string out of the contents and use the "contains" operator to see if that string contains the value you are looking for, but perhaps it works on arrays or other collections directly. If you find a solution, please let me know!

Re: A really simple but powerful rule engine

Try adding two fields to the input object: wageUnit and wageUnitsHour (which is your arraylist). Then a rule like this might work: "if(input.wageUnitsHour.contains(input.wageUnit))". Alternatively, can you build the list inside the rule?

Re: A really simple but powerful rule engine

Great, thank you so much! It works I added a List to the input object and am evaluating the "contains" in the rule like below. Rule wageUnitHour = new Rule("prewailingWageUnit", "input.wageUnitsHour contains input.wageUnit " , "HOUR",3, "com.rules.wage.unit", null);

Re: A really simple but powerful rule engine

simple and useful tool; is there a Java-only source and/or binary? thanks;

Re: A really simple but powerful rule engine

Yes, see http://www.maxant.co.uk/tools.jsp => version 1 of the tool was written purely in Java. The difference between v1 and v2 is minimal.

Re: A really simple but powerful rule engine

thanks for your prompt response; v1 works fine in an all java environment; the code looks thread safe & re-entrant; meaning the the Engine instance, once compiled, can be called by several threads in a concurrent application; thanks again for a great swiss-army-knife to make Java rule-based code simple and useful;

Re: A really simple but powerful rule engine

Extremely nice tool - simply love it. I managed to learn the essentials in 1-2 hours. Just one question: I used ver. 1 as there seemed to be some Scala dependencies in ver. 2. Is this something I can avoid?

Re: A really simple but powerful rule engine

Version 2 is fully compatible with Java - see the tests. The differences between v1 and v2 are, so far as I remember: a) licence changed to LGPL since someone asked if they could modify the code; b) Action class renamed to AbstractAction, since Action is now only useful for Scala (see "update" note at top of blog entry); c) OSGi no longer supported since I couldn't find a scala bundle. Other than that, v2 should work absolutely fine with Java. If it doesn't please let me know by adding a comment here!

Re: A really simple but powerful rule engine

I did the following (Eclipse): * Created a new Java project, added the libs ch.maxant.rules_2.0.0.201302122116.jar mvel2-2.1.3.Final.jar * Dragged in the class ch.maxant.rules.blackbox.RuleTest * Added JUnit4 * Run the test java.lang.NoClassDefFoundError: scala/Product Trying ch.maxant.rules.blackbox.EngineTest gives me a compile error: The type scala.collection.Iterable cannot be resolved. It is indirectly referenced from required .class files Thanks Jan

Re: A really simple but powerful rule engine

You need to also add scala-library-2.10.2.jar to your classpath. You can get it from here: http://mvnrepository.com/artifact/org.scala-lang/scala-library/2.10.2 (download, or add as mvn dependency).

Re: A really simple but powerful rule engine

Ok, yes, but I was under the impression that you didn't need to have any dependencies on Scala (unless you wanted so explicitly). Thanks

Re: A really simple but powerful rule engine

Hi Ant, I have a question: I noticed that SubRules are as well added to the uniqueOutcomes set (Set<String>). I believe this causes a problem if SubRules are used with actions, which I assume is perfectly valid, right? As validateActions() does not distinguis between Rule and Subrule, a NoActionFoundException is thrown. Is this a bug or a feature?

Re: A really simple but powerful rule engine

Yes that is a bug. I've fixed it and released version 2.0.1. Thanks for finding that! See http://www.maxant.co.uk/tools.jsp#simpleRuleEngine

Re: A really simple but powerful rule engine

please maintain a java-only source; I'd like to avoid including the scala-library-2.10.2.jar dependency; thanks for a very useful tool;

Re: A really simple but powerful rule engine

Actually, i would like to second that. I don't want to introduce a Scala dependency if I'm not using the language. Thanks Jan

Re: A really simple but powerful rule engine

Due to popular demand, version 2.0.2 now only needs Scala if you use the libraries optional Scala API, i.e. if you want to construct the Engine using a Scala collection, or use Actions written in Scala. For those using just Java, there is no need to include the Scala library in the dependencies. Any problems, please let me know!

Re: A really simple but powerful rule engine

Sounds good, but I get a compile error on Engine e = new Engine(rules, true); The message is "The type scala.collection.Iterable cannot be resolved. It is indirectly referenced from required .class files". I'm using the lib ch.maxant.rules_2.0.2.201308042200.jar Am I missing something? Thanks

Re: A really simple but powerful rule engine

Just goes to show that I should properly test before I release :-) I've now taken my unit tests and put them in their own Eclipse project and after reproducing your problem I fixed it by pushing all Scala code into a subclass of Engine named ScalaEngine. This way, your code should compile again. Version 2.0.3.201308060830 contains the fix (http://www.maxant.co.uk/tools.jsp). See the Eclipse projects for Scala unit tests, if you want to use the rule engine from Scala. Sorry for the trouble!

Re: A really simple but powerful rule engine

Great, much better now.

Re: A really simple but powerful rule engine

Hi Ant, Can you add the dependency on maven? Thanks, Nipun

Re: A really simple but powerful rule engine

Hello Ant,
A very nice article.
I am trying to come with a solution to validate multiple objects (using JSR 303/ Bean Validation) .... but am not sure, if I would need something like your rule engine. Would appreciate if you can reply to my post on coderanch.
(<< "http://www.coderanch.com/t/589406/jsr/jsr/validation-framework?OWASP_CSRFTOKEN=LK9D-Y3FZ-9SVN-WQPN-6NDK-RV8J-IP6T-GRFW" >>)
Thank you

Re: A really simple but powerful rule engine

JSR-303 lets you do form-validation / cross-field-validation / cross-validation by adding @AssertTrue to a method. The rule engine is a rule engine, not a validation engine. That said, you could be creative and use if to validate objects, but I'm not sure that I would.

Re: A really simple but powerful rule engine

How do you handle null checks?
Stack Trace:
Testing phone number: null Exception in thread "main" java.lang.RuntimeException: cannot invoke getter: getPhoneNumber [declr.class: rules.vo.CustomerVO; act.class: rules.vo.CustomerVO] (see trace) at org.mvel2.optimizers.impl.refl.nodes.GetterAccessor.getValue(GetterAccessor.java:74)

Re: A really simple but powerful rule engine

It might be more a question of how MVEL handles null. See http://mvel.codehaus.org/Value+Tests

Re: A really simple but powerful rule engine

My bad. I got it. I added a wrapper around the engine to break after a null check failure. It was otherwise continuing with other checks with trim() that was breaking since the input was null. I hope you won't mind me playing around the code to suit my needs.

Re: A really simple but powerful rule engine

Thanks Ant. Thanks a lot, for helping me simplify our application that was using overly complicated logic for business rules. Things I tested, and found that the engine performs well: Thread safe: 10,000 threads, each performing 4-12 rules based on conditions. Fast: 10,000 threads, each executing 8 rules, took about 15 seconds. Clean. Thank you, once more.

Re: A really simple but powerful rule engine

Sorry, forgot to select my email in my last comment with thank you note.

Add a comment Send a TrackBack