Early this year, I was leading the development efforts of a project that had acquired a lot of technical debt in the form of business rules. Like many projects in their early stages, these business rules were defined in a growing tangled mess of if/then/else statements. It didn't take long before it was obvious that all of those tightly coupled if/then/else rules needed an abstraction where each rule could be defined independently, while still somehow chained together.

Finding the Perfect Rules Engine

First, we looked at Drools, but we quickly decided that Drools was much more than we needed. Besides that, many developers were unfamiliar with Drools. So, Drools would be yet another technical hurdle on a project that was already using a slew of new tech and with a team that was still struggling to keep up.

Next, we looked at Easy Rules. Easy Rules seemed a lot easier than Drools. After all, it was just Java. But it was also kind of heavy on ceremony (likely due to its support of older versions of Java), it had its own way of doing things, and ordering rules wasn't entirely straightforward.

Finally, we decided on just using a simple Chain of Responsibility Pattern implementation with a little syntactic sugar to reduce the ceremony required. As time went on, I started thinking about using lambdas (introduced in Java8) to facilitate a simple given/when/then Domain Specific Language (DSL) where rules could be defined independently — but also chained together and be able to share state from one rule to the next. What emerged was RuleBook.

Enter RuleBook

RuleBook leverages lambda expressions to allow rules to be defined independently using a simple given/when/then DSL as part of a "RuleBook." A rule can literally be defined in a single line of code. And since all rules are chained together, there is no guesswork involved when it comes to the order of execution; each rule is executed in the order in which it is added to the chain.

Like many rules engines, RuleBook uses the concept of facts, which are just data supplied to rules (and RuleBooks). RuleBook also allows rules to change the state of facts, which can then be read and/or changed by rules further down the chain. And for those RuleBooks whose purpose is to read in data (i.e. facts) of one type and output a result of a different type, there are Decisions, which are perfectly tailored to that scenario.

Getting Started With RuleBook

Rules in RuleBook are defined in well, a RuleBook. This can be illustrated in the example below.

public class ExampleRuleBook extends RuleBook { @Override public void defineRules() { //first rule prints "Hello" addRule(StandardRule.create().when(f -> true).then(f -> { System.out.print("Hello"); return NEXT; //continue to the next Rule }); //second rule prints "World" addRule(StandardRule.create().when(f -> true).then(f -> { System.out.println("World"); return BREAK; //it doesn't matter if NEXT or BREAK is returned here since it's the last Rule }); } }





Rules are created using a Camel-style method chaining DSL. The when() method accepts a Predicate<Fact> functional interface. The then() method accepts a Function<Fact, RuleState> functional interface. By accepting functional interfaces as method parameters in the when/then methods, Lambda can be used to significantly streamline the definition of rules as seen above.

In the above example, two rules were defined using the provided DSL and Lambda for defining the required functions. In the first rule, when() always evaluates to true, which causes then() to be invoked. Then() prints "Hello" and returns NEXT to continue to the next rule. The second rule also has when() always evaluating to true. Then() prints "World" and the rule chain completes.

The code to invoke the execution of a RuleBook is simple too, as seen by the following code snippet.

public class ExampleMainClass { public static void main(String[] args) { RuleBook exampleRuleBook = new ExampleRuleBook(); exampleRuleBook.run(); } }





If facts were needed by any of the rules in the RuleBook, they could be added to the RuleBook and the RuleBook would then apply its facts to all of the rules it contains. And adding facts to a RuleBook is as simple as...

public class ExampleMainClass { public static void main(String[] args) { Fact<String> fact1 = new Fact("fact1name", "fact1value"); Fact<String> fact2 = new Fact("fact2name", "fact2value"); RuleBook exampleRuleBook = new ExampleRuleBook(); exampleRuleBook.given(fact1, fact2).run(); } }





More examples can be found on the RuleBook GitHub site.

RuleBook makes defining rules simple and straightforward. However, in order to achieve that, some Java8 features were heavily relied upon. So, unfortunately, if you have to work in a pre-Java8 environment, RuleBook is not going to be a good option for you. But for those of us who are able to work with Java8, RuleBook can be a very simple and lightweight option for defining rules.

Adding RuleBook to Your Project

If you are using Maven, adding RuleBook to your project is as simple as adding it to your project dependencies; it's in the Maven Central Repository.

<dependency> <groupId>com.deliveredtechnologies</groupId> <artifactId>rulebook</artifactId> <version>0.1</version> </dependency>





You can also easily add RuleBook to your project if you're using Gradle.

compile 'com.deliveredtechnologies:rulebook:0.1'





What's Next for RuleBook?

The more I started playing with RuleBook, the more I liked it. Rules were easy to define. Finding which rules were defined in a RuleBook and what those rules did was as easy as looking inside the RuleBook.

But then I started thinking... what if I wanted to define rules outside of a RuleBook? On one hand, changing rules in a RuleBook wouldn't require a change to the code inside a RuleBook. But on the other, some of the simplicity of finding rules would be lost, unless... some intelligent defaults were used.

So, the next minor release of RuleBook will allow rules to be defined outside of a RuleBook. But more than that, you will be able to define rules as POJOs in a package. And the whole package will be used as a RuleBook. It should be pretty cool. And because it will still use the work that's already been done in RuleBook, it won't break any of the great existing RuleBook functionality (or interfaces).