I am an Associate Professor in the School of Engineering and Advanced Technology (SEAT ) at Massey University in New Zealand. I have a Master in Mathematics and a PhD in Computer Science from the University of Leipzig in Germany. I have worked for a couple of years in industry as consultant in Germany, Namibia, Switzerland and the UK, and returned in 2003 to academia. My research interests are in the areas of design pattern and antipattern formalisation and detection, software componentry, agile software engineering techniques and business rule automation. Jens has posted 2 posts at DZone. View Full User Profile

Embedding Rules into Java Programs with the Mandarax Compiler

12.06.2010
| 5924 views |
  • submit to reddit

A common task for programmers is to implement the business logic described in requirement documents. Often, this logic is expressed as business rules: simple statements that describe constraints on and relationships between entities ("business objects"). Using object-oriented or functional programming languages to express those rules is not very productive: the rules have to be mapped to objects, classes, functions, methods and procedural constructs like conditionals and loops. This is error-prone, and important information about the rules is lost in the process. This problem is addressed by rule engines: libraries that can interpret and execute rules formally defined using a rule language. The Mandarax compiler has similar goals, but uses a different approach: instead of interpreting rules, they are compiled into standard Java classes. These classes can then be integrated into applications. The main advantage of this approach is to remove the runtime overhead of the rule engine from the application. A unique feature of the Mandarax compiler is that rule meta data are made accessible in the generated classes. With this feature, applications cannot only compute results based on rules, but also get access to an explanation how these results have been computed.

Defining Rules

In Mandarax, rules are defined using a domain specific language (Mandarax script). The language extends the Java expression syntax. Each script defines a relationship between objects of certain types. For instance, consider the following script: 
package test.org.mandarax.compiler.reldef6;
import test.org.mandarax.compiler.*;
Discount goldDiscount = new Discount(20,true);
Discount silverDiscount = new Discount(10,true);
Discount specialDiscount = new Discount(5,false);
rel Discount(Customer customer,Discount discount) queries
getDiscount(customer),qualifiesForDiscount(customer,discount) {
rule1: c.turnover>1000 -> Discount(c,goldDiscount);
rule2: FrequentCustomer(c) -> Discount(c,silverDiscount);
rule3: c.paymentMethod == "CompanyVisa" -> Discount(c,specialDiscount);
}
rel FrequentCustomer(Customer customer) queries isFrequentCustomer(customer) {
rule1: c.transactionCount>5 -> FrequentCustomer(c);
rule2: c.transactionCount>3 & c.turnover>500 -> FrequentCustomer(c);
}

This script defines two relationships, Discount and FrequentCustomer. The Discount relationship associates customers and discounts, the FrequentCustomer relationship applies to customers (it is technically not a relationship but a so-called unary predicate). The rules define when objects instantiate the relationships. This can be done either by referencing other relationships (for instance, in the second rule for Discount), by using Java expressions (for instance, in the first rule for Discount), or by using a combination of both. Rule are general in the sense that variables are used: the c in rule1 for Discount can represent any instance of Customer.

The expressions used in rules have a syntax that is very similar to Java. However, there are some differences. For instance, c.transactionCount is not a reference to the field transactionCount, but to the bean property getter getTransactionCount(). In this respect, Mandarax script is similar to expression languages such as JUEL, MVEL and OGNL.

Compiling Rules

The following script will compile the rule definition: 
import org.mandarax.compiler.*;
import org.mandarax.compiler.impl.*;
...
private static void compile(File file) throws Exception {
Compiler compiler = new DefaultCompiler();
Location location = new FileSystemLocation(new File("output_folder"));
compiler.compile(location,CompilationMode.RELATIONSHIP_TYPES,file);
compiler.compile(location,CompilationMode.QUERIES,file);
}

The compiler generates Java source code. The Location instance is used to specify where the generated code will be stored. The compiler itself has a compile method that is called twice: the first call (with the CompilationMode.RELATIONSHIP_TYPES parameter) generates classes representing the relationships, while the second call (with the CompilationMode.QUERIES parameter) generated classes with methods to query for instances of the relationship.

  The classes created to represent the relationships are very simple structures representing the relationships. They have public fields for each relationship slot, and equals() and hashCode() methods. These methods are implemented using the Apache commons EqualsBuilder and HashCodeBuilder utilities, respectively.

public class DiscountRel {
public Customer customer = null;
public Discount discount = null;
public DiscountRel() {
super();
}
public DiscountRel(Customer customer, Discount discount) {
super();
this.customer = customer;
this.discount = discount;
}
@Override public boolean equals(Object obj) {...}
@Override public int hashCode() {...}
}

 

The second set of classes generated by the compiler provides methods representing queries. For each query defined in a relationship, a matching static method is generated. The public interface for this class generated for the Discount relationship looks as follows:

 

public class DiscountRelInstances {
public staticDiscount goldDiscount = newDiscount(20, true);
public static Discount silverDiscount = newDiscount(10, true);
public staticDiscount specialDiscount = newDiscount(5, false);

// interface generated for queries
public static ResultSet<discountrel> getDiscount(Customer customer) {...}
public static ResultSet<discountrel> qualifiesForDiscount(Customer customer, Discount discount) {...}

// private methods
...
}
There are several internal methods containing the actual programming logic representing the rules. This code is rather complex. The main challenges are the binding of variables (unification), and backtracking if the evaluation of a precondition of a rule fails. The implementation is based on the combination of iterators such as filtering, chaining and nesting, using ideas from functional programming and projects such as Apache Commons Collection and Google Guava

Using The Generated Code

The query methods return a ResultSet of DiscountRel. ResultSet extends Iterator. Therefore, applications can iterate over the computed results as follows:
Customer customer = new Customer("John");
… // set customer properties here
ResultSet<discountrel> rs = DiscountRelInstances.getDiscount(customer);
while (rs.HasNext()) {
DiscountRel record = rs.next();
System.out.println("Customer " + customer + " qualifies for the following discount: " + record.discount);
}
rs.close();

This script will print out all discounts the respective customer qualifies for. The order of results returned depends on the order in which rules are defined. Therefore, the order of rules is often seen as meaningful, and many applications will only use the first result returned by a query. If there is no result, the query will return an empty iterator. This is an iterator for which hasNext() always returns false, and next() always throws an exception.

Note that ResultSet also has a close() method. The intention of this method is to release resources (such as data base connections) allocated by the computation. The computation of results is lazy. That is, the computation is only performed when the next() methods is invoked by the client application. Therefore, the query methods return the result sets almost instantly to the application.

Semantic Reflection

When implementing a set of rules using a mainstream programming language, methods are written to execute the rules and to compute a result. It is generally not possible to find out how the result is computed. This is often of interest, as users want to understand the logic behind computer systems before making important decisions. The ability of a system to explain how a result was computed makes it more trustworthy. In some cases, adding log statements to program code can be used to achieve this. However, if the algorithms to evaluate the rules are complex, log files become very complex as well. In particular, if the evaluation of the rules has to explore and later discard certain computations (because conditions are not satisfied, this is called backtracking), already written log statements have to be marked as invalid.

The code generated by the Mandarax compiler supports an interface to extract information about the rules used at runtime. We call this semantic reflection - it is similar to standard reflection used in object-oriented programming languages. But instead of revealing information about types used in method and class signatures, it extracts information about the logic used within (the query) methods. Consider the following updated rule set for discount policies:
Discount goldDiscount = new Discount(20,true);
Discount silverDiscount = new Discount(10,true);
Discount specialDiscount = newDiscount(5,false);
@author="jens"
@lastupdated="26/10/10"
rel Discount(Customer customer,Discount discount) queries
getDiscount(customer),qualifiesForDiscount(customer,discount) {
@lastupdated="27/10/10"
@description="golden rule"
rule1: c.turnover>1000 -> Discount(c,goldDiscount);
@description="silver rule"
rule2: FrequentCustomer(c) -> Discount(c,silverDiscount);
@description="special rule"
rule3: c.paymentMethod == "CompanyVisa" -> Discount(c,specialDiscount);
}
@author="jens"
rel FrequentCustomer(Customer customer) queries isFrequentCustomer(customer) {
rule1: c.transactionCount>5 -> FrequentCustomer(c);
rule2: c.transactionCount>3 & c.turnover>500 -> FrequentCustomer(c);
}

The rule definitions are unchanged, but there are some additional annotations (the lines starting with @). Annotations are simple key-value pairs. The compiler essentially eliminates the rules and turns them into methods with an internal logic representing the rules. While doing this, the compiler will add the annotations to the generated classes, and create an API to query the result sets for these annotations. The annotations represent meta data about the rule, and can be extremely helpful to trace code back to requirements.

 Both relationships and rules can be annotated. Annotations on relationships apply to all rules for this relationship. If a rule annotation has the same key as a relationship annotation, it wins (the annotation is overridden).

The following script shows how to find the first applicable discount for a customer, and prints out the meta data for the rules that have been used to compute this result.
Customer customer = new Customer("John");
// set customer properties here
ResultSet<discountrel> rs = DiscountRelInstances.getDiscount(customer);
DiscountRel discount = rs.next();
List<derivationlogentry> computation = rs.getDerivationLog();
DiscountRel record = rs.next();
System.out.println("Customer" + customer + " qualifies for the following discount: " + record.discount);
System.out.println("The following rules have been applied to compute this result:");
for (DerivationLogEntry e:computation) {
System.out.println("rule id: " + e.getName());
System.out.println("description: " + e.getAnnotation("description"));
System.out.println("author: " + e.getAnnotation("author"));
System.out.println("last updated: " + e.getAnnotation("lastupdated"));
}</derivationlogentry></discountrel>

A Complex Example: the UServ Application

 

The UServ insurance application is an example based on a Userv Product Derby scenario published by the Business Rule Forum in order to compare business rule engines. The complete application is available in the Mandarax project repository, and can be executed using web start by pointing your browser to the following URL: http://www-ist.massey.ac.nz/JBDietrich/userv-mdrx/userv.jnlp

 

The application consists of 69 rules, organised in 16 rule sets, each set defining one relationship. The application uses a simple user interface that is organised as follows:

 

Userv application screenshot 1

 

In the top half of the screen, three objects representing a car, a driver and a policy can be manipulated using the controls in this panel. Whenever a change occurs, all rules are re-evaluated (i.e., all queries are executed), and the respective results are displayed in the lower half of the screen. By clicking on the "?" buttons next to the results or double clicking on the values in the lists, a window pops up showing the derivation for this result:

 

Finally, clicking on the "show rules" buttons in the main toolbar displays the rules sets from which the application has been generated.

 

Userv application screenshot 2

 

This application uses some features not discussed in this document, including negation as failure (for instance, used in the DriverCategory rules) and aggregation functions (used in the InsuranceEligibility rules). These features are described in the Mandarax manual.

 

More information about the Mandarax project can be found on the Mandarax project web site.
Published at DZone with permission of its author, Jens Dietrich.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Anees Ur-rehman replied on Mon, 2010/12/06 - 3:39am

How this is different from JBoss Drools.

Jonathan Fisher replied on Mon, 2010/12/06 - 10:01am

I believe Drools is based on a modified RETE algorithm (rules are interpreted in a very efficient manner), whereas Mandarax appears to be a rule compiler (rule are converted to bytecode).... I think.

Jonathan Fisher replied on Mon, 2010/12/06 - 10:07am

The Mandarax project is AGPL licensed... seems like a poor choice for a license; business rules are likely to be proprietary information.

Peter Arrenbrecht replied on Mon, 2010/12/06 - 10:42am

Might also want to look at formulacompiler.org which compiles "rules" modelled as Excel or OpenOffice.org spreadsheets to JVM byte code. This is often more accessible to business people.

Geoffrey De Smet replied on Mon, 2010/12/06 - 11:26am in response to: Peter Arrenbrecht

With the Drools Decision Tables you can model your rules in an Excel table.

Much more intresting is Drools Guvnor, which allows end-users (= domain experts) to write, test and deploy rules with a web interface. Enabling the domain experts (such as financial experts, insurance experts, doctors, ...) to write the rules themselves and to deploy changes in minutes instead of days, is very powerfull.

Because you can reuse Java code peices in the Drools rules and translate them into human language with DSL, a developer is able to supply all the puzzle peices (rule fragments) needed for the domain experts to make the puzzle (business rules).

Drools is ASL licensed (business friendly open sourced).

Mark Proctor replied on Mon, 2010/12/06 - 12:35pm

Mandarax is also a derivation query type rule engine, similar in behaviour ro prolog. This means you have to execute a query and it gives you a result set, so it's more like calling a query on a database.

 

Drools is a reactive system, you insert data and rules fire. It's more like the triggers on a sql view.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.