Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Implement business logic with the Drools rules engine

Use a declarative programming approach to write your program's business logic

Ricardo Olivieri (roliv@us.ibm.com), Software Engineer, IBM
Ricardo Olivieri is a software engineer in IBM Global Services. His areas of expertise include design and development of enterprise Java applications for WebSphere Application Server, administration and configuration of WebSphere Application Server, and distributed software architectures. During the last few years, Ricardo has become interested in learning about open source projects such as Drools, Spring, WebWork, Hibernate, and JasperReports. He is a certified Java developer and a certified WebSphere Application Server administrator. He has a B.S. in computer engineering from the University of Puerto Rico Mayaguez Campus.

Summary:  Using a rules engine can lower an application's maintenance and extensibility costs by reducing the complexity of components that implement complex business logic. This updated article shows you how to use the open source Drools rules engine to make a Java™ application more adaptive to changes. The Drools project has introduced a new native rule expression language and an Eclipse plug-in, making Drools easier to use than ever before.

Date:  18 Mar 2008 (Published 30 May 2006)
Level:  Intermediate PDF:  A4 and Letter (289KB)Get Adobe® Reader®
Also available in:   Chinese  Russian

Activity:  163501 views
Comments:  

Most of the complexities that requirements impose on today's software products are behavioral and functional, resulting in component implementations with complex business logic. The most common way to implement the business logic in a J2EE or J2SE application is to write Java code that realizes the requirements document's rules and logic. In most cases, this code's intricacy and complexity makes maintaining and updating the application's business logic a daunting task, even for experienced developers. And any change, however simple, incurs recompilation and redeployment costs.

Related articles

Search these articles for more information on business rules and Java rules engines.

A rules engine helps you resolve (or at least reduce) the issues and difficulties inherent in the development and maintenance of an application's business logic. You can think of a rules engine as a framework for implementing complex business logic. Most rules engines let you use declarative programming to express the consequences that are valid given some information or knowledge. You can concentrate on facts that are known to be true and their associated outcomes — that is, on an application's business logic.

Several rules engines are available, including commercial and open source choices. Commercial rules engines usually let you express rules in a proprietary English-like language. Others let you write rules using scripting languages such as Groovy or Python. This updated article introduces you to the Drools engine and uses a sample program to help you understand how to use Drools as part of your business logic layer in a Java application.

The more things change...

As the old saying goes, "the only constant thing is change." This is certainly true for the business logic of software applications. Changes in the component(s) that implement an application's business logic can be necessary for several reasons:

  • To fix code defects found during development or after deployment
  • To accommodate special conditions the client initially didn't mention that the business logic should take into account
  • To deal with a client's changed business objectives
  • To conform to your organization's use of agile or iterative development processes

Given these possibilities, an application that can handle changes in the business logic with no major complications is highly desirable — all the more so if the developer making changes to complex if-else logic isn't the person who wrote the code.

Drools is an open source rules engine, written in the Java language, that uses the Rete algorithm to evaluate the rules you write (see Resources). Drools lets you express your business logic rules in a declarative way. You can write rules using a non-XML native language that is quite easy to learn and understand. And you can embed Java code directly in a rules file, which makes the experience of learning Drools even more attractive. Drools also has other advantages. It is:

  • Supported by an active community
  • Easy to use
  • Quick to execute
  • Gaining popularity among Java developers
  • Compliant with the Java Rule Engine API (JSR 94) (see Resources)
  • Free

The current Drools version

As of this writing, the latest version of the Drools rules engine is 4.0.4. This is a major update. Although some backward-compatibility issues exist, this version's features make Drools even more attractive than before. For instance, the new native language for expressing rules is simpler and more elegant than the XML format some older versions use. This new language requires less coding and has a human-readable form.

Another notable development is that a Drools plug-in for the Eclipse IDE (Versions 3.2 and 3.3) is now available. I highly recommend that you use this plug-in to work with Drools. It simplifies the development of projects that use Drools and will improve your productivity. For instance, the plug-in checks your rules file for syntax errors and offers code completion. It also allows you to debug your rules file, potentially reducing debugging time from hours to minutes. You can add breakpoints to your rules file, which lets you inspect the state of the objects at specific moments during rule execution. This gives you information about the knowledge — a term you'll become familiar with later in this article — that the rules engine possesses at a particular moment in time.


The problem to solve

This article shows how to use Drools as part of the business logic layer in a sample Java application. To follow along, you should be familiar with developing and debugging Java code using the Eclipse IDE. And you should be familiar with the JUnit testing framework and know how to use it within Eclipse.

The following assumptions set the scenario for the fictitious problem the application solves:

  • A company named XYZ builds two types of computer machines: Type1 and Type2. A machine's type is defined by its architecture.

  • An XYZ computer can serve multiple functions. Four functions are currently defined: DDNS Server, DNS Server, Gateway, and Router.

  • XYZ performs several tests on each machine before it is shipped out.

  • The tests performed on each machine depend on each machine's type and functions. Currently, five tests are defined: Test1, Test2, Test3, Test4, and Test5.

  • When tests are assigned to a computer, a tests due date is also assigned to the machine. Tests assigned to the computer should be conducted no later than this due date. The due date's value depends on the tests that were assigned to the machine.

  • XYZ has automated much of the process for executing the tests using an internally developed software application that can determine a machine's type and functions. Then, based on these properties, the application determines which tests to execute and their due date.

  • Currently, the logic that assigns the tests and tests due date to a computer is part of this application's compiled code. The component that contains this logic is written in the Java language.

  • The logic for assigning tests and due dates is changed more than once a month. The developers must go through a tedious process every time they need to implement it in the Java code.

When do you use a rules engine?

Not all applications should use a rules engine. If your business logic code includes a bunch of if-else statements, you should consider using one. Maintaining complex Boolean logic can be a difficult task, and a rules engine can help you organize this logic. Changes are significantly less likely to introduce errors when you can express the logic using a declarative approach instead of an imperative programming language.

You should also consider a rules engine if code changes can cause major financial losses. Many organizations have strict rules about deploying compiled code in their hosting environments. For instance, if you need to modify the logic in a Java class, usually a long, tedious process must occur before the change makes it to the production environment:

  1. The application code must be recompiled.
  2. The code is dropped in a test staging environment.
  3. The code is inspected by data-quality auditors.
  4. The change is approved by the hosting environment architects.
  5. The change is scheduled for deployment.

Even a simple change to one line of code can cost an organization thousands of dollars. If you need to follow such strict rules and find yourself making frequent changes to your business logic code, then it would make sense to consider a rules engine.

Knowledge of your client can also be a factor in this decision. Even if you're working with a simple set of requirements calling for a straightforward implementation in your Java code, you might know from previous projects that your client has a tendency (and the financial and political resources) to add and change business logic requirements frequently during the development cycle and even after deployment. You might be better off in this case choosing to use a rules engine from the beginning.

Because the company incurs high costs whenever changes are made to the logic that assigns tests and due dates to a computer, the XYZ executives have asked their software engineers to find a flexible way to "push" changes to the business rules to the production environment with minimal effort. Here's where Drools comes into play. The engineers have decided that if they use a rules engine to express the conditions for determining which tests should be performed, they can save much time and effort. They would only need to change the content of a rules file and then replace this file in the production environment. This seems simpler and less time consuming to them than changing compiled code and going through the long process mandated by the organization whenever compiled code is to be deployed in the production environment (see the sidebar When do you use a rules engine?).

Currently, these are the business rules that must be followed when assigning tests and their due dates to a machine:

  • If a computer is of Type1, then only Test1, Test2, and Test5 should be conducted on it.

  • If a computer is of Type2 and one of its functions is DNS Server, then Test4 and Test5 should be conducted.

  • If a computer is of Type2 and one of its functions is DDNS Server, then Test2 and Test3 should be conducted.

  • If a computer is of Type2 and one of its functions is Gateway, then Test3 and Test4 should be conducted.

  • If a computer is of Type2 and one of its functions is Router, then Test1 and Test3 should be conducted.

  • If Test1 is among the tests to be conducted on a computer, then the tests due date is three days from the machine's creation date. This rule has priority over all following rules for the tests due date.

  • If Test2 is among the tests to be conducted on a computer, then the tests due date is seven days from the machine's creation date. This rule has priority over all following rules for the tests due date.

  • If Test3 is among the tests to be conducted on a computer, then the tests due date is 10 days from the machine's creation date. This rule has priority over all following rules for the tests due date.

  • If Test4 is among the tests to be conducted on a computer, then the tests due date is 12 days from the machine's creation date. This rule has priority over all following rules for the tests due date.

  • If Test5 is among the tests to be conducted on a computer, then the tests due date is 14 days from the machine's creation date.

The current Java code that captures the preceding business rules for assigning tests and a tests due date to a machine looks similar to code in Listing 1:


Listing 1. Using if-else statements to implement business-rules logic
                
Machine machine = ...
// Assign tests
Collections.sort(machine.getFunctions());
int index;

if (machine.getType().equals("Type1")) {
   Test test1 = ...
   Test test2 = ...
   Test test5 = ...
   machine.getTests().add(test1);
   machine.getTests().add(test2);
   machine.getTests().add(test5);
} else if (machine.getType().equals("Type2")) {
   index = Collections.binarySearch(machine.getFunctions(), "Router");
   if (index >= 0) {
      Test test1 = ...
      Test test3 = ...
      machine.getTests().add(test1);
      machine.getTests().add(test3);
   }
   index = Collections.binarySearch(machine.getFunctions(), "Gateway");
   if (index >= 0) {
      Test test4 = ...
      Test test3 = ...
      machine.getTests().add(test4);
      machine.getTests().add(test3);
   }
...
}

// Assign tests due date
Collections.sort(machine.getTests(), new TestComparator());
...
Test test1 = ...
index = Collections.binarySearch(machine.getTests(), test1);
if (index >= 0) {
   // Set due date to 3 days after Machine was created
   Timestamp creationTs = machine.getCreationTs();
   machine.setTestsDueTime(...);
   return;
}

index = Collections.binarySearch(machine.getTests(), test2);
if (index >= 0) {
   // Set due date to 7 days after Machine was created
   Timestamp creationTs = machine.getCreationTs();
   machine.setTestsDueTime(...);
   return;
}
...

The code in Listing 1 isn't overly complicated, but it's not simple either. If you were to make changes to it, you'd need to be extremely careful. A bunch of entangled if-else statements are trying to capture the business logic that has been identified for the application. If you knew little or nothing about the business rules, the code's intent would not be apparent from a quick look.


Importing the sample program

A sample program that uses the Drools rules engine is provided with this article in a ZIP archive. The program uses a Drools rules file to express in a declarative way the business rules defined in the preceding section. It contains an Eclipse 3.2 Java project that was developed using the Drools plug-in and version 4.0.4 of the Drools rules engine. Follow these steps to set up the sample program:

  1. Download the ZIP archive (see Download).
  2. Download and install the Drools Eclipse plug-in (see Resources).
  3. In Eclipse, select the option to import Existing Projects into Workspace, as shown in Figure 1:

    Figure 1. Importing the sample program into your Eclipse workspace
    Importing the sample program into your Eclipse workspace

  4. Select the archive file you downloaded and import it into your workspace. You'll find a new Java project named DroolsDemo in your workspace, as shown in Figure 2:.

    Figure 2. Sample program imported into your workspace
    Importing the sample program into your Eclipse workspace

If you have the Eclipse Build automatically option enabled, then the code should be compiled and ready to use by now. Otherwise, build the DroolsDemo project now.


Examining the code

Now you'll take a look at the code in the sample program. The core set of Java classes for this program is in the demo package. There you'll find the Machine and Test domain object classes. An instance of the Machine class represents a computer to which tests and a tests due date are assigned. Take a look at the Machine class, shown in Listing 2:


Listing 2. Instance variables for the Machine class
                
public class Machine {

   private String type;
   private List functions = new ArrayList();
   private String serialNumber;
   private Collection tests = new HashSet();
   private Timestamp creationTs;
   private Timestamp testsDueTime;

   public Machine() {
     super();
     this.creationTs = new Timestamp(System.currentTimeMillis());
   }
   ...

You can see in Listing 2 that among the properties for the Machine class are:

  • type (represented as a string property) - Holds the type value for a machine.
  • functions (represented as a list) - Holds the functions for a machine.
  • testsDueTime (represented as a timestamp variable) - Holds the assigned tests due date value.
  • tests (a Collection object) - Holds the set of assigned tests.

Note that more than one test can be assigned to a machine and that a machine can have one or more functions.

For the sake of simplicity, the creation-time value for a machine is set to the current time when an instance of the Machine class is created. If this were a real-world application, the creation time would be set to the actual time when the machine is finally built and ready to be tested.

An instance of the Test class represents a test that can be assigned to a machine. A Test instance is uniquely described by its id and name, as shown in Listing 3:


Listing 3. Instance variables for the Test class
                
public class Test {

   public static Integer TEST1 = new Integer(1);
   public static Integer TEST2 = new Integer(2);
   public static Integer TEST3 = new Integer(3);
   public static Integer TEST4 = new Integer(4);
   public static Integer TEST5 = new Integer(5);

   private Integer id;
   private String name;
   private String description;
   public Test() {
      super();
   }
   ...

The sample program uses the Drools rules engine to evaluate instances of the Machine class. Based on the values of a Machine instance's type and functions properties, the rules engine determines which values should be assigned to the tests and testsDueTime properties.

In the demo package, you'll also find an implementation of a data access object (TestDAOImpl) for Test objects, which lets you find Test instances by ID. This data access object is extremely simple; it does not connect to any external resources (such as relational databases) to obtain Test instances. Instead, a predefined set of Test instances is hardcoded in its definition. In a real-world scenario, you would probably have a data access object that does connect to an external resource to retrieve Test objects.

The RulesEngine class

One of the more important classes (if not the most important) in the demo package is RulesEngine. An instance of this class serves as a wrapper object that encapsulates the logic to access the Drools classes. You could easily reuse this class in your own Java projects, because the logic it contains is not specific to the sample program. Listing 4 shows the properties and constructor of this class:


Listing 4. Instance variables and constructor for the RulesEngine class
                
public class RulesEngine {

   private RuleBase rules;
   private boolean debug = false;

   public RulesEngine(String rulesFile) throws RulesEngineException {
      super();
      try {
         // Read in the rules source file
         Reader source = new InputStreamReader(RulesEngine.class
            .getResourceAsStream("/" + rulesFile));
         // Use package builder to build up a rule package
         PackageBuilder builder = new PackageBuilder();
         // This parses and compiles in one step
         builder.addPackageFromDrl(source);
         // Get the compiled package
         Package pkg = builder.getPackage();
         // Add the package to a rulebase (deploy the rule package).
         rules = RuleBaseFactory.newRuleBase();
         rules.addPackage(pkg);
      } catch (Exception e) {
         throw new RulesEngineException(
            "Could not load/compile rules file: " + rulesFile, e);
      }
   }
   ...

As you can see in Listing 4, the RulesEngine class's constructor takes as an argument a string value that represents the name of the file that contains a set of business rules. This constructor uses an instance of the PackageBuilder class to parse and compile the rules contained in the source file. (Note: This code assumes the rules file is located in a folder called rules in the program's classpath.) Once this is done, the PackageBuilder instance is used to merge all the compiled rules into a binary Package instance. This instance is then used to configure an instance of the Drools RuleBase class, which is assigned to the RulesEngine class's rules property. You can think of an instance of this class as an in-memory representation of the rules contained in your rules file.

Listing 5 shows the RulesEngine class's executeRules() method:


Listing 5. executeRules() method of the RulesEngine class
                
public void executeRules(WorkingEnvironmentCallback callback) {
   WorkingMemory workingMemory = rules.newStatefulSession();
   if (debug) {
      workingMemory
         .addEventListener(new DebugWorkingMemoryEventListener());
   }
   callback.initEnvironment(workingMemory);
   workingMemory.fireAllRules();
}

The executeRules() method is pretty much where all the magic in the Java code happens. Invoking this method executes the rules that were previously loaded in the class's constructor. An instance of the Drools WorkingMemory class is used to assert or declare the knowledge that the rules engine should use to determine which consequences should be executed. (If all the conditions of a rule are met, then the consequences of that rule are executed.) Think of knowledge as the data or information that a rules engine should use to determine whether the rules should be fired. For instance, a rules engine's knowledge can consist of the current state of one or more objects and their properties.

Execution of a rule's consequences occurs when the WorkingMemory object's fireAllRules() method is invoked. You might be wondering (and I hope you are) how the knowledge is inserted into the WorkingMemory instance. If you take a closer look at this method's signature, you'll notice that the argument that is passed in is an instance of the WorkingEnvironmentCallback interface. Callers of the executeRules() method need to create an object that implements this interface. This interface requires developers to implement only one method, as shown in Listing 6:


Listing 6. WorkingEnvironmentCallback interface
                
public interface WorkingEnvironmentCallback {
   void initEnvironment(WorkingMemory workingMemory) throws FactException;
}

So, it's up to the caller of the executeRules() method to insert the knowledge into the WorkingMemory instance. I'll show how this is done soon.

The TestsRulesEngine class

Listing 7 shows the TestsRulesEngine class, also found in the demo package:


Listing 7. TestsRulesEngine class
                
public class TestsRulesEngine {

   private RulesEngine rulesEngine;
   private TestDAO testDAO;

   public TestsRulesEngine(TestDAO testDAO) throws RulesEngineException {
      super();
      rulesEngine = new RulesEngine("testRules1.drl");
      this.testDAO = testDAO;
   }

   public void assignTests(final Machine machine) {
      rulesEngine.executeRules(new WorkingEnvironmentCallback() {
         public void initEnvironment(WorkingMemory workingMemory) {
            // Set globals first before asserting/inserting any knowledge!
            workingMemory.setGlobal("testDAO", testDAO);
            workingMemory.insert(machine);
         };
      });
   }
}

The TestsRulesEngine class has only two instance variables. The rulesEngine property is an instance of the RulesEngine class. The testDAO property holds a reference to a concrete implementation of the TestDAO interface. The rulesEngine object is instantiated using the "testRules1.drl" string as the parameter for its constructor. The testRules1.drl file captures the business rules in The problem to solve in a declarative way. The TestsRulesEngine class's assignTests() method invokes the RulesEngine class's executeRules() method. In this method, an anonymous instance of the WorkingEnvironmentCallback interface is created, which is then passed as a parameter to the executeRules() method.

If you take a look at the implementation of the assignTests() method, you can see how knowledge is inserted into the WorkingMemory instance. The WorkingMemory class's insert() method is called to state the knowledge that the rules engine should use when evaluating rules. In this case, the knowledge consists of an instance of the Machine class. Objects that are inserted are used to evaluate a rule's conditions.

If you need your rules engine to have references to objects that are not to be used as knowledge when evaluating conditions, you should use the WorkingMemory class's setGlobal() method. In the sample program, the setGlobal() method passes a reference to the TestDAO instance to the rules engine. The rules engine then uses the TestDAO instance to look up any Test instances it might need.

The TestsRulesEngine class is the only Java code in the sample program that contains logic pertaining specifically to the implementation of the business rules for assigning tests and a tests due date to machines. The logic in this class may never need to change, even if the business rules need to be updated.


The Drools rules file

As I previously mentioned, the testRules1.drl file contains the rules that the rules engine should follow to assign tests and a tests due date to a machine. It uses the Drools native language to express the rules it contains.

A Drools rules file has one or more rule declarations. Each rule declaration is composed of one or more conditional elements, and one or more consequences or actions to execute. A rules file can also have multiple (that is, zero or more) import declarations, multiple global declarations, and multiple function declarations.

The best way to understand the composition of a Drools rules file is to look at a real one. Take a look at the first section of the testRules1.drl file, shown in Listing 8:


Listing 8. First section of the testRules1.drl file
                
package demo;

import demo.Machine;
import demo.Test;
import demo.TestDAO;
import java.util.Calendar;
import java.sql.Timestamp;
global TestDAO testDAO;

In Listing 8, you can see how the import declarations let the rules execution engine know where to find the class definitions of the objects you'll be using in your rules. The global declaration lets the rules engine know that an object should be accessible from within your rules but that it should not be part of the knowledge used to evaluate the rules' conditions. You can think of global declarations as global variables within your rules. For a global declaration, you need to specify its type (that is, class name) and the identifier you want to use to refer to it (that is, variable name). This identifier name in the global declaration should match the identifier value that was used when the WorkingMemory class's setGlobal() method was invoked, which in this case is testDAO (see Listing 7).

The function keyword is used to define a Java function (see Listing 9). If you see code that's repeated in your consequences (which I discuss soon), then you should probably extract that code and write it as a Java function. However, when doing this you should be careful to avoid writing complex Java code in the Drools rules file. The Java functions defined in a rules file should be short and easy to follow. This is not a technical limitation of Drools. If you want to write complex Java code in your rules file, you can. But doing so will probably make your code harder to test, debug, and maintain. Complex Java code should be part of a Java class. If you need the Drools rules execution engine to invoke complex Java code, then you can pass a reference to the Java object that contains the complex code to the rules engine as global data.


Listing 9. Java functions defined in the testRules1.drl file
                
function void setTestsDueTime(Machine machine, int numberOfDays) {
   setDueTime(machine, Calendar.DATE, numberOfDays);
}

function void setDueTime(Machine machine, int field, int amount) {
   Calendar calendar = Calendar.getInstance();
   calendar.setTime(machine.getCreationTs());
   calendar.add(field, amount);
   machine.setTestsDueTime(new Timestamp(calendar.getTimeInMillis()));
}
 ...

Listing 10 shows the first rule in the testRules1.drl file:


Listing 10. First rule defined in testRules1.drl
                
rule "Tests for type1 machine"
salience 100
when
   machine : Machine( type == "Type1" )
then
   Test test1 = testDAO.findByKey(Test.TEST1);
   Test test2 = testDAO.findByKey(Test.TEST2);
   Test test5 = testDAO.findByKey(Test.TEST5);
   machine.getTests().add(test1);
   machine.getTests().add(test2);
   machine.getTests().add(test5);
   insert( test1 );
   insert( test2 );
   insert( test5 );
end

As you can see in Listing 10, the rule declaration has a name that uniquely identifies it. You can also see that the when keyword defines the conditional part of a rule, and the then keyword defines the consequence part. The rule shown in Listing 10 has one conditional element that references a Machine object. If you go back to Listing 7, you'll see that a Machine object was inserted into the WorkingMemory object. That same object is the one used in this rule. The conditional element evaluates the Machine instance (which is part of the knowledge) to determine whether the consequences of the rule should be executed. If the conditional element evaluates to true, the consequences are then fired or executed. You can also see in Listing 10 that a consequence is simply a Java language statement. By taking a quick look at this rule, you can easily recognize that it's the implementation of the following business rule:

  • If a computer is of Type1, then Test1, Test2, and Test5 should be the only tests conducted on this machine.

Therefore, this rule's conditional element checks whether the value of the type property (of the Machine object) is Type1. (In a conditional element, you can access an object's properties without needing to invoke the getter methods, as long as the object follows the Java bean pattern.) If this evaluates to true, then a reference to the Machine instance is assigned to the machine identifier. This reference is then used in the consequence part of the rule to assign tests to the Machine object.

The only statements in this rule that might look somewhat strange are the last three consequence statements. Recall from the business rules in "The problem to solve" section that the value that should be assigned as a tests due date depends on the tests that are assigned to the machine. So the tests that are assigned to a machine need to become part of the knowledge that the rules execution engine should use when evaluating the rules. This is exactly what the three statements do. These statements use a method named insert to update the knowledge in the rules engine.

Determining rule execution order

Another important aspect of a rule is the optional salience attribute. You use it to let the rules execution engine know the order in which it should fire the consequence statements of your rules. The consequence statements of the rule with the highest salience value are executed first, the consequence statements of the rule with the second-highest salience value are executed second, and so on. This is important when you need your rules to be fired in a predefined order, as you'll see in a moment.

The next four rules in the testRules1.drl file implement the remaining business rules that pertain to the assignment of tests to machines (see Listing 11). These rules are similar to the first rule I just discussed. Note that the salience attribute value is the same for these first five rules; the outcome of executing these five rules will be the same regardless of the order in which they are fired. If the outcome were affected by the order in which your rules are fired, then you would need to specify a different salience value for your rules.


Listing 11. Remaining rules in testRules1.drl that pertain to assignment of tests
                
rule "Tests for type2, DNS server machine"
salience 100
when
   machine : Machine( type == "Type2", functions contains "DNS Server")
then
   Test test5 = testDAO.findByKey(Test.TEST5);
   Test test4 = testDAO.findByKey(Test.TEST4);
   machine.getTests().add(test5);
   machine.getTests().add(test4);
   insert( test4 );
   insert( test5 );
end

rule "Tests for type2, DDNS server machine"
salience 100
when
   machine : Machine( type == "Type2", functions contains "DDNS Server")
then
   Test test2 = testDAO.findByKey(Test.TEST2);
   Test test3 = testDAO.findByKey(Test.TEST3);
   machine.getTests().add(test2);
   machine.getTests().add(test3);
   insert( test2 );
   insert( test3 );
end

rule "Tests for type2, Gateway machine"
salience 100
when
   machine : Machine( type == "Type2", functions contains "Gateway")
then
   Test test3 = testDAO.findByKey(Test.TEST3);
   Test test4 = testDAO.findByKey(Test.TEST4);
   machine.getTests().add(test3);
   machine.getTests().add(test4);
   insert( test3 );
   insert( test4 );
end

rule "Tests for type2, Router machine"
salience 100
when
   machine : Machine( type == "Type2", functions contains "Router")
then
   Test test3 = testDAO.findByKey(Test.TEST3);
   Test test1 = testDAO.findByKey(Test.TEST1);
   machine.getTests().add(test3);
   machine.getTests().add(test1);
   insert( test1 );
   insert( test3 );
end
...

Listing 12 shows the remaining rules in the Drools rules file. As you've probably guessed, these rules pertain to the assignment of the tests due date:


Listing 12. Rules in testRules1.drl that pertain to assignment of the tests due date
                
rule "Due date for Test 5"
salience 50
when
   machine : Machine()
   Test( id == Test.TEST5 )
then
   setTestsDueTime(machine, 14);
end

rule "Due date for Test 4"
salience 40
when
   machine : Machine()
   Test( id == Test.TEST4 )
then
   setTestsDueTime(machine, 12);
end

rule "Due date for Test 3"
salience 30
when
   machine : Machine()
   Test( id == Test.TEST3 )
then
   setTestsDueTime(machine, 10);
end

rule "Due date for Test 2"
salience 20
when
   machine : Machine()
   Test( id == Test.TEST2 )
then
   setTestsDueTime(machine, 7);
end

rule "Due date for Test 1"
salience 10
when
   machine : Machine()
   Test( id == Test.TEST1 )
then
   setTestsDueTime(machine, 3);
end

The implementation of these rules is a little simpler than the implementation of the rules for assigning tests, but I find them a little more interesting, for four reasons.

First, note that the order in which these rules should execute does matter. The outcome (that is, the value assigned to a Machine instance's testsDueTime property) is affected by the order in which these rules are fired. If you review the business rules detailed in The problem to solve, you'll notice that the rules for assigning a tests due date have a precedence order. For instance, if Test3, Test4, and Test5 have been assigned to a machine, then the tests due date should be 10 days from the machine's creation date. The reason is that the tests due date rule for Test3 has precedence over the tests due date rules for Test4 and Test5. How do you express this in a Drools rules file? The answer is the salience attribute. The value of the salience attribute of the rules that set a value to the testsDueTime property is different. The tests due date rule for Test1 has precedence over all the other tests due date rules, so this should be the last rule to be fired. In other words, the value assigned by this rule is the one that should prevail in the case that Test1 is among the tests that were assigned to a machine. So, the value of the salience attribute for this rule is the lowest one: 10.

Second, each one of these rules has two conditional elements. The first element simply checks for the existence of a Machine instance in the working memory. (Note that no comparisons are performed on the Machine object's properties.) Whenever this element evaluates to true, it assigns a reference to the Machine object that is then used in the consequence part of the rule. Without this reference assignment, there would be no way to assign a tests due date to the Machine object. The second conditional element checks the id property of the Test object. Only when both conditional elements evaluate to true are the rule's consequence elements executed.

Third, the conditional parts of these rules are not (and cannot be) evaluated by the Drools rules execution engine until an instance of the Test class is part of the knowledge (that is, contained in the working memory). This seems quite logical, because if an instance of the Test class isn't in the working memory yet, the rules execution engine has no way to perform the comparison contained in these rules' conditions. If you're wondering when a Test instance becomes part of the knowledge, recall that one or more Test instances are inserted into the working memory during the execution of the consequences of the rules that pertain to the assignment of tests (see Listing 10 and Listing 11).

Fourth, note that the consequence part of these rules is quite short and simple. The reason is that in all of them an invocation is made to the setTestsDueTime() Java method that was defined earlier in the rules file using the function keyword. This method is where the actual assignment of a value to the testsDueTime property occurs.


Testing the code

Now that you've gone over the code that implements the business-rules logic, it's time to see if it works. To execute the sample program, run the TestsRulesEngineTest JUnit test found in the demo.test package.

In this test, five Machine objects are created, each one with a different set of properties (serial numbers, types, and functions). The TestsRulesEngine class's assignTests() method is invoked, iteratively, for each one of these five Machine objects. Once the assignTests() method finishes its execution, assertions are performed to verify that the business-rules logic specified in the testRules1.drl is correct (see Listing 13). You could modify the TestsRulesEngineTest JUnit class to add a few more Machine instances with different properties and then use assertions to verify that the outcome is as expected.


Listing 13. Assertions in testTestsRulesEngine() to verify that the business logic's implementation is correct
                
public void testTestsRulesEngine() throws Exception {
   while (machineResultSet.next()) {
      Machine machine = machineResultSet.getMachine();
      testsRulesEngine.assignTests(machine);
      Timestamp creationTs = machine.getCreationTs();
      Calendar calendar = Calendar.getInstance();
      calendar.setTime(creationTs);
      Timestamp testsDueTime = machine.getTestsDueTime();

      if (machine.getSerialNumber().equals("1234A")) {
         assertEquals(3, machine.getTests().size());
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST1)));
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST2)));
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST5)));
         calendar.add(Calendar.DATE, 3);
         assertEquals(calendar.getTime(), testsDueTime);

      } else if (machine.getSerialNumber().equals("1234B")) {
         assertEquals(4, machine.getTests().size());
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST5)));
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST4)));
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST3)));
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST2)));
         calendar.add(Calendar.DATE, 7);
         assertEquals(calendar.getTime(), testsDueTime);
...


A few more comments on knowledge

It's worth mentioning that besides inserting objects into the working memory, you can also modify objects in it or retract them from it. You can do this within a rule's consequence part. If an object that's part of the current knowledge is modified in a consequence statement, and if the modified property is used in a conditional element to determine whether a rule should be fired, you should invoke the update() method in the rule's consequence part. When you invoke the update() method, you let the Drools rules execution engine know that an object has been updated and that any conditional element (of any rule) that references this object (for instance, inspects the value of one or more of the object's properties) should be reevaluated to determine if the condition's result is now true or false. This means that even the conditions of the current active rule (the rule that modifies the object in its consequence part) can be reevaluated, which might cause the rule to be fired again and could lead to an infinite recursion. If you don't want the active rule to be reevaluated, you should include the rule's optional no-loop attribute and give it a value of true.

Listing 14 demonstrates this situation with pseudocode for the definition of two rules. Rule 1 modifies property1 of objectA. It then invokes the update() method to let the rules execution engine know about this change, which should trigger a reevaluation of the conditional elements of the rules that reference objectA. Hence, the condition for firing Rule 1 should be reevaluated. And because this condition should evaluate again to true (the value of property2 is still the same because it was not changed in the consequence part of the rule), Rule 1 should be fired again, resulting in the execution of an infinite loop. To prevent this situation, you add the no-loop attribute and give it a value of true, which prevents the current active rule from reexecuting.


Listing 14. Modifying an object in the working memory and using the rule element's no-loop attribute
                
...
rule "Rule 1"
salience 100
no-loop true
when
   objectA : ClassA (property2().equals(...))
then
   Object value = ...
   objectA.setProperty1(value);
   update( objectA );
end

rule "Rule 2"
salience 100
when
   objectB : ClassB()
   objectA : ClassA ( property1().equals(objectB) )
   ...
then
   ...
end
...

If an object should no longer be part of the knowledge, then you should retract that object from the working memory (see Listing 15). You do this by calling the retract() method in the consequence part of your rule. When an object is removed from the working memory, any conditional elements (of any rule) that had a reference to this object cannot be evaluated now. Because the object no longer exists as part of the knowledge, the rule has no chance of being fired.


Listing 15. Retracting an object from the working memory
                
...
rule "Rule 1"
salience 100
when
   objectB : ...
   objectA : ...
then
   Object value = ...
   objectA.setProperty1(value);
   retract(objectB);
end

rule "Rule 2"
salience 90
when
   objectB : ClassB ( property().equals(...) )
then
  ...
end
...

Listing 15 contains pseudocode for the definition of two rules. Assume the conditions for firing both rules evaluate to true. Then, Rule 1 should be fired first because Rule 1 has a higher salience value than Rule 2. Now, note that in the consequence part of Rule 1, objectB is retracted from the working memory (that is, objectB is no longer part of the knowledge). This action alters the "execution agenda" of the rules engine because Rule 2 won't be fired now. The reason is that the condition for firing Rule 2 that was once true is not true anymore because it references an object (objectB) that is no longer part of the knowledge. If Listing 15 contained more rules referencing objectB that have not been fired yet, they would now no longer be fired.

As a concrete example of how to modify current knowledge in the working memory, I'll rewrite the rules source file I discussed earlier. The business rules are still the same as outlined in "The problem to solve" section. However, I'll use a different implementation for the rules to achieve the same results. With this approach, a Machine instance is the only knowledge available to the working memory at all times. In other words, the conditional elements of the rules will perform comparisons only on properties of a Machine object. This is different from the earlier approach, which also made comparisons of properties of Test objects (see Listing 12). This new implementation of the rules is captured in the sample application's testRules2.drl file. Listing 16 shows the rules in testRules2.drl that pertain to tests assignment:


Listing 16. Rules in testRules2.drl that pertain to assignment of tests
                
rule "Tests for type1 machine"
lock-on-active true
salience 100

when
   machine : Machine( type == "Type1" )
then
   Test test1 = testDAO.findByKey(Test.TEST1);
   Test test2 = testDAO.findByKey(Test.TEST2);
   Test test5 = testDAO.findByKey(Test.TEST5);
   machine.getTests().add(test1);
   machine.getTests().add(test2);
   machine.getTests().add(test5);
   update( machine );
end

rule "Tests for type2, DNS server machine"
lock-on-active true
salience 100

when
   machine : Machine( type == "Type2", functions contains "DNS Server")
then
   Test test5 = testDAO.findByKey(Test.TEST5);
   Test test4 = testDAO.findByKey(Test.TEST4);
   machine.getTests().add(test5);
   machine.getTests().add(test4);
   update( machine );
end

rule "Tests for type2, DDNS server machine"
lock-on-active true
salience 100

when
   machine : Machine( type == "Type2", functions contains "DDNS Server")
then
   Test test2 = testDAO.findByKey(Test.TEST2);
   Test test3 = testDAO.findByKey(Test.TEST3);
   machine.getTests().add(test2);
   machine.getTests().add(test3);
   update( machine );
end

rule "Tests for type2, Gateway machine"
lock-on-active true
salience 100

when
   machine : Machine( type == "Type2", functions contains "Gateway")
then
   Test test3 = testDAO.findByKey(Test.TEST3);
   Test test4 = testDAO.findByKey(Test.TEST4);
   machine.getTests().add(test3);
   machine.getTests().add(test4);
   update( machine );
end

rule "Tests for type2, Router machine"
lock-on-active true
salience 100

when
   machine : Machine( type == "Type2", functions contains "Router")
then
   Test test3 = testDAO.findByKey(Test.TEST3);
   Test test1 = testDAO.findByKey(Test.TEST1);
   machine.getTests().add(test3);
   machine.getTests().add(test1);
   update( machine );
end
...

If you compare the definition of the first rule in Listing 16 to the definition shown in Listing 10, you can see that instead of inserting the Test instances that were assigned to the Machine object into the working memory, the consequence part of the rule invokes the update() method to let the rules engine know that the Machine object has been modified. (Test instances were added/assigned to it.) If you look at the remaining rules in Listing 16, you should see that this is the approach also taken whenever tests are assigned to a Machine object: one or more Test instances are assigned to a Machine instance and, consequently, the working knowledge is modified, and the rules engine is notified about it.

Note also the active-lock attribute used for all rules shown in Listing 16. The value of this attribute is set to true; if it weren't, you'd end up with an infinite loop when executing these rules. Setting it to true ensures that when a rule updates the knowledge in the working memory, you don't end up with rules being reevaluated and reexecuted, which could then cause an infinite recursion. You can think of the active-lock attribute as a stronger version of the no-loop attribute. The no-loop attribute ensures that the rule modifying the knowledge is not invoked again as a consequence of its own update, whereas the active-lock attribute ensures that any rules (with this attribute set to true) in your file are not reexecuted as a consequence of modifying the knowledge.

Listing 17 shows how the remaining rules were changed:


Listing 17. Rules in testRules2.drl that pertain to assignment of the tests due date
                
rule "Due date for Test 5"
salience 50
when
   machine : Machine(tests contains (testDAO.findByKey(Test.TEST5)))
then
   setTestsDueTime(machine, 14);
end

rule "Due date for Test 4"
salience 40
when
   machine : Machine(tests contains (testDAO.findByKey(Test.TEST4)))
then
   setTestsDueTime(machine, 12);
end

rule "Due date for Test 3"
salience 30
when
   machine : Machine(tests contains (testDAO.findByKey(Test.TEST3)))
then
   setTestsDueTime(machine, 10);
end

rule "Due date for Test 2"
salience 20
when
   machine : Machine(tests contains (testDAO.findByKey(Test.TEST2)))
then
   setTestsDueTime(machine, 7);
end

rule "Due date for Test 1"
salience 10
when
   machine : Machine(tests contains (testDAO.findByKey(Test.TEST1)))
then
   setTestsDueTime(machine, 3);
end

These rules' conditional elements now inspect a Machine object's tests collection to determine if it contains a specific Test instance. Hence, as I mentioned earlier, with this approach the rules engine is dealing with only one object in the working memory (a Machine instance), as opposed to multiple objects (Machine and Test instances).

To test the testRules2.drl file, just edit the TestsRulesEngine class provided in the sample application (see Listing 7): change the "testRules1.drl" string to "testRules2.drl" and then run the TestsRulesEngineTest JUnit test. All tests should succeed just as they did with testRules1.drl as the rules source.


Notes on breakpoints

As I mentioned before, the Drools plug-in for Eclipse allows you to place breakpoints in your rules file. Be aware that these breakpoints are enabled only when you debug your program as a "Drools Application." Otherwise, the debugger ignores them.

For example, suppose you want to debug the TestsRulesEngineTest JUnit test class as a "Drools Application." Open the general Debug dialog in Eclipse. In this dialog window, you should see a "Drools Application" category. Under this category, create a new launch configuration. In the Main tab for this new configuration, you should see a Project field and a Main class field. For the Project field, select the Drools4Demo project. For the Main class field, enter junit.textui.TestRunner (see Figure 3).


Figure 3. Drools application launch configuration for TestsRulesEngineTest class (Main tab)
Drools Application launch configuration for TestsRulesEngineTest class (Main tab)

Now select the Arguments tab and enter -t demo.test.TestsRulesEngineTest as a program argument (see Figure 4). Once you are done entering the argument, save your new launch configuration by clicking on the Apply button on the bottom right corner of the dialog. You can then click the Debug button to start debugging the TestsRulesEngineTest JUnit class as a "Drools Application." If you added breakpoints to testRules1.drl or testRules2.drl, the debugger should stop at them when using this launch configuration.


Figure 4. Drools Application launch configuration for TestsRulesEngineTest class (Arguments tab)
Drools Application launch configuration for TestsRulesEngineTest class (Arguments tab)

Conclusion

Using a rules engine can significantly reduce the complexity of components that implement the business-rules logic in your Java applications. An application that uses a rules engine to express rules using a declarative approach has a higher chance of being more maintainable and extensible than one that doesn't. As you've seen, Drools is a powerful and flexible rules engine implementation. Using Drools' features and capabilities, you should be able to implement the complex business logic of your application in a declarative manner. And as you've seen, Drools makes learning and using declarative programming quite easy for Java developers.

The Drools classes that this article showed you are Drools-specific. If you were to use another rules engine implementation with the sample program, the code would need a few changes. Because Drools is JSR 94-compliant, you could use the Java Rule Engine API (as specified in JSR 94) to interface with Drools-specific classes. (The Java Rule Engine API is for rules engines what JDBC is for databases.) If you use this API, then you can change your rules engine implementation to a different one without needing to change the Java code, as long as this other implementation is also JSR 94-compliant. JSR 94 does not address the structure of the rules file that contains your business rules (testRules1.drl in this article's sample application). The file's structure would still depend on the rules engine implementation you choose. As an exercise, you can modify the sample application so that it uses the Java Rule Engine API instead of referencing the Drools-specific classes in the Java code.



Download

DescriptionNameSizeDownload method
Sample Java project that uses Droolsj-Drools4Demo.zip45KBHTTP

Information about download methods


Resources

Learn

Get products and technologies

  • IBM WebSphere ILOG JRules provides the capabilities you need to create, change, and deploy rules with confidence. Try it today!

  • Drools: Download the latest Drools distribution and Drools plug-in for Eclipse.

  • Eclipse: Download the latest version of the Eclipse IDE for the Java platform.

Discuss

About the author

Ricardo Olivieri

Ricardo Olivieri is a software engineer in IBM Global Services. His areas of expertise include design and development of enterprise Java applications for WebSphere Application Server, administration and configuration of WebSphere Application Server, and distributed software architectures. During the last few years, Ricardo has become interested in learning about open source projects such as Drools, Spring, WebWork, Hibernate, and JasperReports. He is a certified Java developer and a certified WebSphere Application Server administrator. He has a B.S. in computer engineering from the University of Puerto Rico Mayaguez Campus.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=124932
ArticleTitle=Implement business logic with the Drools rules engine
publish-date=03182008
author1-email=roliv@us.ibm.com
author1-email-cc=