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
- 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.
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
ScriptMortgageQualifierclass 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.
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
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.