implement business logic with the drools rules engine

30
Implement business logic with the Drools rules engine Use a declarative programming approach to write your program's business logic Skill Level: Intermediate Ricardo Olivieri ([email protected]) Software Engineer EMC 30 May 2006 Updated 18 Mar 2008 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. 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. Implement business logic with the Drools rules engine Trademarks © Copyright IBM Corporation 2006, 2008. All rights reserved. Page 1 of 30

Upload: others

Post on 04-Feb-2022

2 views

Category:

Documents


0 download

TRANSCRIPT

Implement business logic with the Drools rulesengineUse a declarative programming approach to write yourprogram's business logic

Skill Level: Intermediate

Ricardo Olivieri ([email protected])Software EngineerEMC

30 May 2006

Updated 18 Mar 2008

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

Most of the complexities that requirements impose on today's software products arebehavioral and functional, resulting in component implementations with complexbusiness logic. The most common way to implement the business logic in a J2EE orJ2SE application is to write Java code that realizes the requirements document'srules and logic. In most cases, this code's intricacy and complexity makesmaintaining and updating the application's business logic a daunting task, even forexperienced developers. And any change, however simple, incurs recompilation andredeployment costs.

Related articlesSearch these articles for more information on business rules andJava rules engines.

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 1 of 30

A rules engine helps you resolve (or at least reduce) the issues and difficultiesinherent in the development and maintenance of an application's business logic. Youcan think of a rules engine as a framework for implementing complex business logic.Most rules engines let you use declarative programming to express theconsequences that are valid given some information or knowledge. You canconcentrate on facts that are known to be true and their associated outcomes — thatis, 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-likelanguage. Others let you write rules using scripting languages such as Groovy orPython. This updated article introduces you to the Drools engine and uses a sampleprogram to help you understand how to use Drools as part of your business logiclayer in a Java application.

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

• To fix code defects found during development or afterdeployment

• To accommodate special conditions the client initiallydidn't mention that the business logic should take intoaccount

• To deal with a client's changed business objectives

• To conform to your organization's use of agile or iterativedevelopment processes

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

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

• Supported by an active community

• Easy to use

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 2 of 30

• 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 amajor update. Although some backward-compatibility issues exist, this version'sfeatures make Drools even more attractive than before. For instance, the new nativelanguage for expressing rules is simpler and more elegant than the XML formatsome older versions use. This new language requires less coding and has ahuman-readable form.

Another notable development is that a Drools plug-in for the Eclipse IDE (Versions3.2 and 3.3) is now available. I highly recommend that you use this plug-in to workwith Drools. It simplifies the development of projects that use Drools and will improveyour productivity. For instance, the plug-in checks your rules file for syntax errorsand offers code completion. It also allows you to debug your rules file, potentiallyreducing debugging time from hours to minutes. You can add breakpoints to yourrules file, which lets you inspect the state of the objects at specific moments duringrule execution. This gives you information about the knowledge — a term you'llbecome familiar with later in this article — that the rules engine possesses at aparticular moment in time.

The problem to solve

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

The following assumptions set the scenario for the fictitious problem the applicationsolves:

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

• An XYZ computer can serve multiple functions. Four functions arecurrently 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

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 3 of 30

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 assignedto the machine. Tests assigned to the computer should be conducted nolater than this due date. The due date's value depends on the tests thatwere assigned to the machine.

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

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

• The logic for assigning tests and due dates is changed more than once amonth. The developers must go through a tedious process every timethey 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 logiccode includes a bunch of if-else statements, you should considerusing one. Maintaining complex Boolean logic can be a difficult task,and a rules engine can help you organize this logic. Changes aresignificantly less likely to introduce errors when you can express thelogic using a declarative approach instead of an imperativeprogramming language.

You should also consider a rules engine if code changes can causemajor financial losses. Many organizations have strict rules aboutdeploying compiled code in their hosting environments. Forinstance, if you need to modify the logic in a Java class, usually along, tedious process must occur before the change makes it to theproduction 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 environmentarchitects.

5. The change is scheduled for deployment.

Even a simple change to one line of code can cost an organizationthousands of dollars. If you need to follow such strict rules and findyourself making frequent changes to your business logic code, thenit would make sense to consider a rules engine.

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 4 of 30

Knowledge of your client can also be a factor in this decision. Evenif you're working with a simple set of requirements calling for astraightforward implementation in your Java code, you might knowfrom previous projects that your client has a tendency (and thefinancial and political resources) to add and change business logicrequirements frequently during the development cycle and evenafter deployment. You might be better off in this case choosing touse a rules engine from the beginning.

Because the company incurs high costs whenever changes are made to the logicthat assigns tests and due dates to a computer, the XYZ executives have askedtheir software engineers to find a flexible way to "push" changes to the businessrules to the production environment with minimal effort. Here's where Drools comesinto play. The engineers have decided that if they use a rules engine to express theconditions for determining which tests should be performed, they can save muchtime and effort. They would only need to change the content of a rules file and thenreplace this file in the production environment. This seems simpler and less timeconsuming to them than changing compiled code and going through the longprocess mandated by the organization whenever compiled code is to be deployed inthe production environment (see the sidebar When do you use a rules engine?).

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

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

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

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

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

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

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

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

• If Test3 is among the tests to be conducted on a computer, then the testsdue date is 10 days from the machine's creation date. This rule has

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 5 of 30

priority over all following rules for the tests due date.

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

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

The current Java code that captures the preceding business rules for assigning testsand 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 testsCollections.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 dateCollections.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 createdTimestamp creationTs = machine.getCreationTs();machine.setTestsDueTime(...);return;

}

index = Collections.binarySearch(machine.getTests(), test2);if (index >= 0) {

// Set due date to 7 days after Machine was createdTimestamp creationTs = machine.getCreationTs();machine.setTestsDueTime(...);return;

}

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 6 of 30

...

The code in Listing 1 isn't overly complicated, but it's not simple either. If you were tomake changes to it, you'd need to be extremely careful. A bunch of entangled if-elsestatements are trying to capture the business logic that has been identified for theapplication. If you knew little or nothing about the business rules, the code's intentwould 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 aZIP archive. The program uses a Drools rules file to express in a declarative way thebusiness rules defined in the preceding section. It contains an Eclipse 3.2 Javaproject that was developed using the Drools plug-in and version 4.0.4 of the Droolsrules 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

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 7 of 30

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, asshown in Figure 2:.Figure 2. Sample program imported into your workspace

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 8 of 30

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

Examining the code

Now you'll take a look at the code in the sample program. The core set of Javaclasses for this program is in the demo package. There you'll find the Machine andTest domain object classes. An instance of the Machine class represents acomputer to which tests and a tests due date are assigned. Take a look at the

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 9 of 30

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

• functions (represented as a list) - Holds the functions for a machine.

• testsDueTime (represented as a timestamp variable) - Holds theassigned 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 canhave one or more functions.

For the sake of simplicity, the creation-time value for a machine is set to the currenttime when an instance of the Machine class is created. If this were a real-worldapplication, the creation time would be set to the actual time when the machine isfinally 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;

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 10 of 30

private String name;private String description;public Test() {

super();}...

The sample program uses the Drools rules engine to evaluate instances of theMachine class. Based on the values of a Machine instance's type andfunctions properties, the rules engine determines which values should beassigned 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. Thisdata access object is extremely simple; it does not connect to any externalresources (such as relational databases) to obtain Test instances. Instead, apredefined set of Test instances is hardcoded in its definition. In a real-worldscenario, you would probably have a data access object that does connect to anexternal resource to retrieve Test objects.

The RulesEngine class

One of the more important classes (if not the most important) in the demo package isRulesEngine. An instance of this class serves as a wrapper object thatencapsulates the logic to access the Drools classes. You could easily reuse thisclass in your own Java projects, because the logic it contains is not specific to thesample 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 fileReader source = new InputStreamReader(RulesEngine.class

.getResourceAsStream("/" + rulesFile));// Use package builder to build up a rule packagePackageBuilder builder = new PackageBuilder();// This parses and compiles in one stepbuilder.addPackageFromDrl(source);// Get the compiled packagePackage 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);}

}

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 11 of 30

...

As you can see in Listing 4, the RulesEngine class's constructor takes as anargument a string value that represents the name of the file that contains a set ofbusiness rules. This constructor uses an instance of the PackageBuilder class toparse and compile the rules contained in the source file. (Note: This code assumesthe rules file is located in a folder called rules in the program's classpath.) Once thisis done, the PackageBuilder instance is used to merge all the compiled rules intoa binary Package instance. This instance is then used to configure an instance ofthe Drools RuleBase class, which is assigned to the RulesEngine class's rulesproperty. You can think of an instance of this class as an in-memory representationof 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 codehappens. Invoking this method executes the rules that were previously loaded in theclass's constructor. An instance of the Drools WorkingMemory class is used toassert or declare the knowledge that the rules engine should use to determine whichconsequences should be executed. (If all the conditions of a rule are met, then theconsequences of that rule are executed.) Think of knowledge as the data orinformation that a rules engine should use to determine whether the rules should befired. For instance, a rules engine's knowledge can consist of the current state ofone or more objects and their properties.

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

Listing 6. WorkingEnvironmentCallback interface

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 12 of 30

public interface WorkingEnvironmentCallback {void initEnvironment(WorkingMemory workingMemory) throws FactException;

}

So, it's up to the caller of the executeRules() method to insert the knowledge intothe 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 rulesEngineproperty is an instance of the RulesEngine class. The testDAO property holds areference to a concrete implementation of the TestDAO interface. TherulesEngine object is instantiated using the "testRules1.drl" string as theparameter for its constructor. The testRules1.drl file captures the business rules inThe problem to solve in a declarative way. The TestsRulesEngine class'sassignTests() method invokes the RulesEngine class's executeRules()method. In this method, an anonymous instance of theWorkingEnvironmentCallback interface is created, which is then passed as aparameter to the executeRules() method.

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

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 13 of 30

If you need your rules engine to have references to objects that are not to be usedas knowledge when evaluating conditions, you should use the WorkingMemoryclass's setGlobal() method. In the sample program, the setGlobal() methodpasses a reference to the TestDAO instance to the rules engine. The rules enginethen 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 thatcontains logic pertaining specifically to the implementation of the business rules forassigning tests and a tests due date to machines. The logic in this class may neverneed 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 rulesengine should follow to assign tests and a tests due date to a machine. It uses theDrools native language to express the rules it contains.

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

The best way to understand the composition of a Drools rules file is to look at a realone. 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 engineknow 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 beaccessible from within your rules but that it should not be part of the knowledge usedto evaluate the rules' conditions. You can think of global declarations as globalvariables 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, variablename). This identifier name in the global declaration should match the identifiervalue that was used when the WorkingMemory class's setGlobal() method wasinvoked, which in this case is testDAO (see Listing 7).

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 14 of 30

The function keyword is used to define a Java function (see Listing 9). If you seecode that's repeated in your consequences (which I discuss soon), then you shouldprobably extract that code and write it as a Java function. However, when doing thisyou should be careful to avoid writing complex Java code in the Drools rules file. TheJava functions defined in a rules file should be short and easy to follow. This is not atechnical limitation of Drools. If you want to write complex Java code in your rulesfile, you can. But doing so will probably make your code harder to test, debug, andmaintain. Complex Java code should be part of a Java class. If you need the Droolsrules execution engine to invoke complex Java code, then you can pass a referenceto 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 100when

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 uniquelyidentifies it. You can also see that the when keyword defines the conditional part of arule, and the then keyword defines the consequence part. The rule shown in Listing10 has one conditional element that references a Machine object. If you go back toListing 7, you'll see that a Machine object was inserted into the WorkingMemoryobject. That same object is the one used in this rule. The conditional elementevaluates the Machine instance (which is part of the knowledge) to determinewhether the consequences of the rule should be executed. If the conditional element

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 15 of 30

evaluates to true, the consequences are then fired or executed. You can also seein Listing 10 that a consequence is simply a Java language statement. By taking aquick look at this rule, you can easily recognize that it's the implementation of thefollowing business rule:

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

Therefore, this rule's conditional element checks whether the value of the typeproperty (of the Machine object) is Type1. (In a conditional element, you canaccess an object's properties without needing to invoke the getter methods, as longas the object follows the Java bean pattern.) If this evaluates to true, then areference to the Machine instance is assigned to the machine identifier. Thisreference is then used in the consequence part of the rule to assign tests to theMachine object.

The only statements in this rule that might look somewhat strange are the last threeconsequence 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 thetests that are assigned to the machine. So the tests that are assigned to a machineneed to become part of the knowledge that the rules execution engine should usewhen evaluating the rules. This is exactly what the three statements do. Thesestatements use a method named insert to update the knowledge in the rulesengine.

Determining rule execution order

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

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

Listing 11. Remaining rules in testRules1.drl that pertain to assignment oftests

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 16 of 30

rule "Tests for type2, DNS server machine"salience 100when

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 100when

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 100when

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 100when

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 probablyguessed, these rules pertain to the assignment of the tests due date:

Listing 12. Rules in testRules1.drl that pertain to assignment of the tests duedate

rule "Due date for Test 5"salience 50when

machine : Machine()

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 17 of 30

Test( id == Test.TEST5 )then

setTestsDueTime(machine, 14);end

rule "Due date for Test 4"salience 40when

machine : Machine()Test( id == Test.TEST4 )

thensetTestsDueTime(machine, 12);

end

rule "Due date for Test 3"salience 30when

machine : Machine()Test( id == Test.TEST3 )

thensetTestsDueTime(machine, 10);

end

rule "Due date for Test 2"salience 20when

machine : Machine()Test( id == Test.TEST2 )

thensetTestsDueTime(machine, 7);

end

rule "Due date for Test 1"salience 10when

machine : Machine()Test( id == Test.TEST1 )

thensetTestsDueTime(machine, 3);

end

The implementation of these rules is a little simpler than the implementation of therules 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. Theoutcome (that is, the value assigned to a Machine instance's testsDueTimeproperty) is affected by the order in which these rules are fired. If you review thebusiness rules detailed in The problem to solve, you'll notice that the rules forassigning 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 10days from the machine's creation date. The reason is that the tests due date rule forTest3 has precedence over the tests due date rules for Test4 and Test5. How doyou express this in a Drools rules file? The answer is the salience attribute. Thevalue of the salience attribute of the rules that set a value to the testsDueTimeproperty is different. The tests due date rule for Test1 has precedence over all theother 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 isamong the tests that were assigned to a machine. So, the value of the salienceattribute for this rule is the lowest one: 10.

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 18 of 30

Second, each one of these rules has two conditional elements. The first elementsimply 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 Machineobject that is then used in the consequence part of the rule. Without this referenceassignment, there would be no way to assign a tests due date to the Machineobject. The second conditional element checks the id property of the Test object.Only when both conditional elements evaluate to true are the rule's consequenceelements executed.

Third, the conditional parts of these rules are not (and cannot be) evaluated by theDrools rules execution engine until an instance of the Test class is part of theknowledge (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 rulesexecution engine has no way to perform the comparison contained in these rules'conditions. If you're wondering when a Test instance becomes part of theknowledge, recall that one or more Test instances are inserted into the workingmemory during the execution of the consequences of the rules that pertain to theassignment of tests (see Listing 10 and Listing 11).

Fourth, note that the consequence part of these rules is quite short and simple. Thereason 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 testsDueTimeproperty occurs.

Testing the code

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

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

Listing 13. Assertions in testTestsRulesEngine() to verify that the businesslogic's implementation is correct

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 19 of 30

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, youcan also modify objects in it or retract them from it. You can do this within a rule'sconsequence part. If an object that's part of the current knowledge is modified in aconsequence statement, and if the modified property is used in a conditionalelement to determine whether a rule should be fired, you should invoke theupdate() 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 beenupdated and that any conditional element (of any rule) that references this object (forinstance, inspects the value of one or more of the object's properties) should bereevaluated to determine if the condition's result is now true or false. This meansthat even the conditions of the current active rule (the rule that modifies the object inits consequence part) can be reevaluated, which might cause the rule to be firedagain and could lead to an infinite recursion. If you don't want the active rule to bereevaluated, you should include the rule's optional no-loop attribute and give it avalue 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 tolet the rules execution engine know about this change, which should trigger areevaluation of the conditional elements of the rules that reference objectA. Hence,the condition for firing Rule 1 should be reevaluated. And because this conditionshould evaluate again to true (the value of property2 is still the same because itwas not changed in the consequence part of the rule), Rule 1 should be firedagain, resulting in the execution of an infinite loop. To prevent this situation, you add

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 20 of 30

the no-loop attribute and give it a value of true, which prevents the current activerule from reexecuting.

Listing 14. Modifying an object in the working memory and using the ruleelement's no-loop attribute

...rule "Rule 1"salience 100no-loop truewhen

objectA : ClassA (property2().equals(...))then

Object value = ...objectA.setProperty1(value);update( objectA );

end

rule "Rule 2"salience 100when

objectB : ClassB()objectA : ClassA ( property1().equals(objectB) )...

then...

end...

If an object should no longer be part of the knowledge, then you should retract thatobject from the working memory (see Listing 15). You do this by calling theretract() method in the consequence part of your rule. When an object isremoved from the working memory, any conditional elements (of any rule) that had areference to this object cannot be evaluated now. Because the object no longerexists 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 100when

objectB : ...objectA : ...

thenObject value = ...objectA.setProperty1(value);retract(objectB);

end

rule "Rule 2"salience 90when

objectB : ClassB ( property().equals(...) )then...

end...

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 21 of 30

Listing 15 contains pseudocode for the definition of two rules. Assume the conditionsfor firing both rules evaluate to true. Then, Rule 1 should be fired first becauseRule 1 has a higher salience value than Rule 2. Now, note that in theconsequence part of Rule 1, objectB is retracted from the working memory (thatis, objectB is no longer part of the knowledge). This action alters the "executionagenda" of the rules engine because Rule 2 won't be fired now. The reason is thatthe condition for firing Rule 2 that was once true is not true anymore because itreferences an object (objectB) that is no longer part of the knowledge. If Listing 15contained more rules referencing objectB that have not been fired yet, they wouldnow 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 thesame as outlined in "The problem to solve" section. However, I'll use a differentimplementation for the rules to achieve the same results. With this approach, aMachine instance is the only knowledge available to the working memory at alltimes. In other words, the conditional elements of the rules will perform comparisonsonly 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). Thisnew implementation of the rules is captured in the sample application'stestRules2.drl file. Listing 16 shows the rules in testRules2.drl that pertain to testsassignment:

Listing 16. Rules in testRules2.drl that pertain to assignment of tests

rule "Tests for type1 machine"lock-on-active truesalience 100

whenmachine : Machine( type == "Type1" )

thenTest 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 truesalience 100

whenmachine : Machine( type == "Type2", functions contains "DNS Server")

thenTest 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"

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 22 of 30

lock-on-active truesalience 100

whenmachine : Machine( type == "Type2", functions contains "DDNS Server")

thenTest 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 truesalience 100

whenmachine : Machine( type == "Type2", functions contains "Gateway")

thenTest 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 truesalience 100

whenmachine : Machine( type == "Type2", functions contains "Router")

thenTest 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 inListing 10, you can see that instead of inserting the Test instances that wereassigned to the Machine object into the working memory, the consequence part ofthe rule invokes the update() method to let the rules engine know that theMachine object has been modified. (Test instances were added/assigned to it.) Ifyou look at the remaining rules in Listing 16, you should see that this is the approachalso taken whenever tests are assigned to a Machine object: one or more Testinstances are assigned to a Machine instance and, consequently, the workingknowledge is modified, and the rules engine is notified about it.

Note also the active-lock attribute used for all rules shown in Listing 16. Thevalue of this attribute is set to true; if it weren't, you'd end up with an infinite loopwhen executing these rules. Setting it to true ensures that when a rule updates theknowledge in the working memory, you don't end up with rules being reevaluatedand reexecuted, which could then cause an infinite recursion. You can think of theactive-lock attribute as a stronger version of the no-loop attribute. Theno-loop attribute ensures that the rule modifying the knowledge is not invoked

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 23 of 30

again as a consequence of its own update, whereas the active-lock attributeensures that any rules (with this attribute set to true) in your file are not reexecutedas 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 duedate

rule "Due date for Test 5"salience 50when

machine : Machine(tests contains (testDAO.findByKey(Test.TEST5)))then

setTestsDueTime(machine, 14);end

rule "Due date for Test 4"salience 40when

machine : Machine(tests contains (testDAO.findByKey(Test.TEST4)))then

setTestsDueTime(machine, 12);end

rule "Due date for Test 3"salience 30when

machine : Machine(tests contains (testDAO.findByKey(Test.TEST3)))then

setTestsDueTime(machine, 10);end

rule "Due date for Test 2"salience 20when

machine : Machine(tests contains (testDAO.findByKey(Test.TEST2)))then

setTestsDueTime(machine, 7);end

rule "Due date for Test 1"salience 10when

machine : Machine(tests contains (testDAO.findByKey(Test.TEST1)))then

setTestsDueTime(machine, 3);end

These rules' conditional elements now inspect a Machine object's tests collectionto 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 workingmemory (a Machine instance), as opposed to multiple objects (Machine and Testinstances).

To test the testRules2.drl file, just edit the TestsRulesEngine class provided inthe sample application (see Listing 7): change the "testRules1.drl" string to"testRules2.drl" and then run the TestsRulesEngineTest JUnit test. All

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 24 of 30

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 breakpointsin your rules file. Be aware that these breakpoints are enabled only when you debugyour program as a "Drools Application." Otherwise, the debugger ignores them.

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

Figure 3. Drools application launch configuration for TestsRulesEngineTestclass (Main tab)

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

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 25 of 30

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

Conclusion

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

The Drools classes that this article showed you are Drools-specific. If you were touse another rules engine implementation with the sample program, the code wouldneed a few changes. Because Drools is JSR 94-compliant, you could use the JavaRule 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 youuse this API, then you can change your rules engine implementation to a differentone without needing to change the Java code, as long as this other implementationis also JSR 94-compliant. JSR 94 does not address the structure of the rules file thatcontains your business rules (testRules1.drl in this article's sample application). The

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 26 of 30

file's structure would still depend on the rules engine implementation you choose. Asan exercise, you can modify the sample application so that it uses the Java RuleEngine API instead of referencing the Drools-specific classes in the Java code.

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 27 of 30

Downloads

Description Name Size Downloadmethod

Sample Java project that uses Drools j-Drools4Demo.zip 45KB HTTP

Information about download methods

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 28 of 30

Resources

Learn

• Drools Web site: Get more information about the Drools rules engine project.

• WebSphere ILOG Business Rule Management Systems: Learn about BusinessRule Management offerings from IBM.

• "The Logic of the Bottom Line: An Introduction to The Drools Project" (N. AlexRupp, TheServerSide.com, May 2004): A good introduction to the Drools rulesengine.

• "Getting Started With the Java Rule Engine API (JSR 94): Toward Rule-BasedApplications" (Qusay H. Mahmoud, Sun Developer Network, July 2005): Anintroduction to the Java Rule Engine API.

• JSR 94: Java Rule Engine API: The official JSR 94 specification.

• Drools documentation: The Drools documentation library.

• "The Rete Matching Algorithm" (Bruce Schneier, Dr. Dobb's Portal, December1992): A description of the pattern-matching algorithm underlying Drools.

• Technology bookstore: Browse for books on these and other technical topics.

• The Java technology zone: Hundreds of articles about every aspect of Javaprogramming.

Get products and technologies

• 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

• developerWorks blogs: Get involved in the developerWorks community.

About the author

Ricardo OlivieriRicardo Olivieri is a software engineer in IBM Global Services. Hisareas of expertise include design and development of enterprise Javaapplications for WebSphere Application Server, administration andconfiguration of WebSphere Application Server, and distributedsoftware architectures. During the last few years, Ricardo has becomeinterested in learning about open source projects such as Drools,Spring, WebWork, Hibernate, and JasperReports. He is a certified Java

ibm.com/developerWorks developerWorks®

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 29 of 30

developer and a certified WebSphere Application Server administrator.He has a B.S. in computer engineering from the University of PuertoRico Mayaguez Campus.

Trademarks

Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in theUnited States, other countries, or both.

developerWorks® ibm.com/developerWorks

Implement business logic with the Drools rules engine Trademarks© Copyright IBM Corporation 2006, 2008. All rights reserved. Page 30 of 30