Creating a simple rules engine using the Java scripting API

Part 2 of my IBM developerWorks article, Invoke dynamic languages dynamically, creates a simple rules engine using the Java scripting API. Business rules, written in a combination of Ruby, Groovy, and JavaScript, determine whether a borrower qualifies for a variety of home loans. I used a rules engine as a sample application because it seemed more compelling than another hello-world application, and it also seemed like an interesting use of the scripting API.

The Java scripting API, also known as JSR-223, works as a viable basis for a rules engine when a full-blown business rules engine isn't needed because it offers several of the benefits you get from using a regular rules engine. For instance, when business rules are stored as external scripts, the scripting API:
  • Allows you to work easily with large sets of rapidly changing rules
  • Allows frequent and flexible additions and changes to rules
  • Separates rules from processing logic
  • Centralizes rules and makes them easier to manage
The Java scripting API fulfills those design goals because scripting code can be kept external to the main Java application, and can be discovered, read and invoked at run time. These same advantages are provided by rules-engine products such as Drools, Jess, or JRules. However, you derive additional advantages by using scripting languages to hold your rules and the Java scripting API to invoke them:
  • Easy to program: Use a scripting language -- or several -- of your choice
  • Free and easy to set up (partially built into Java SE 6)
  • Small number of required external dependencies
  • No need to learn a complex declarative business-rules language. For example, here's a sample from Drools:
    rule "Approve if not rejected"
    salience -100
    agenda-group "approval"
    when
    not Rejection()
    p : Policy(approved == false, policyState:status)
    exists Driver(age > 25)
    Process(status == policyState)
    then
    log("APPROVED: due to no objections.");
    p.setApproved(true);
    end
    
What would the design of a rules engine based on the Java scripting API look like? The ScriptMortgageQualifier class in part 2 of my article shows one such design. It stores business objects that the external rules will use in decision-making in the ScriptEngine's context, and receives rule execution results in a separate shared Java object stored in the ScriptEngine context. Rules (scripts) are responsible for storing results of their decisions in the shared Java object, which the main Java code inspects after the rules are run to determine what action to take.

In my sample application, I use individual files to store the rules. The application scans the rules directory on each pass and executes whatever rule scripts it finds there. An advantage of using the Java scripting API to find the rule scripts is the rules can be written in any of dozens of languages supported by script-engine implementations. The rules engine doesn't care what language the rules are written in as long as the applicable script engine and interpreter can be loaded at runtime, such as being supplied by JARs in the classpath. In my sample, I coded rules in Groovy, JavaScript, and Ruby.

Another possible way of structuring rule logic would be to have the rules themselves set additional attributes that other rules could then use (that is, learn from). For instance, say one set of rules runs and determines that the prospective home purchaser has a bank balance of $10 million. The rule could set a property (a global script variable) called VIP (very important person) to true. As a global variable, the property would be available in the ScriptEngine context and passed along to the next rule to be run. That next rule could use different logic based on the fact that this borrower is a VIP.

The above example begins to reveal the shortcomings of designing a rules engine around the scripting API. Most formal rules engines have the notion that all rules are considered to be in effect at all time. Setting a fact such as "customer has VIP status" in one rule should be taken into consideration by all rules to determine if that new fact changes other facts. But satisfying that feature by invoking external rules stored as scripts would require script writers to order the rules in the proper sequence. Trying to sequence your business rules correctly to account for fact-dependencies is error prone -- and impossible when the rules have mutual dependencies. This limitation of requiring rules to be run in a proper sequence is certainly where you would want to consider using a better rules engine.

Rule sequencing isn't the only disadvantage to executing rules stored as external scripts. Writing business rules in Groovy, Ruby or another scripting language has the disadvantage of:
  • Rules in scripting languages are written imperatively rather than declaratively
  • Complex business logic written imperatively might require deeply nested conditional statements, which makes the rules hard to read and prone to error
  • To avoid the above problem of coding deeply nested if-then statements in your script, you might be tempted to write code that processes a decision table -- reinventing the wheel built by better rules engines
  • The temptation to write your business rules in multiple scripting languages could become a maintenance headache
In other words, the Java scripting API will not always work as the best solution when your application needs a rules engine. However, the Java scripting API allows business rules to be stored externally, to be written in a language that probably is easier to read than Java, and lets the rules change regularly and fairly easily without having to rebuild your application. If you don't mind writing your business rules in a procedural language instead of a dedicated, declarative rules language, the scripting API could be a good solution. It fills the gap between those times when writing business rules as Java code inside your application has gotten out of hand and when graduating to a fully fledged rules engine isn't yet necessary.

If you're trying to decide whether your application calls for a dedicated rules engine, the Jess website has a good article, Some Guidelines For Deciding Whether To Use A Rules Engine.