Drools ~ Rules attributes

In this post we will review the rule attributes that are available in Drools, which help us to influence the behavior of our rules.

We will start by explaining each Rule Attribute and then we will review a Drools Project with examples for the attributes.

[ Leer en español ]

This post belongs to a series of posts that will cover the following topics:

  • Drools Basic Concepts
  • Drools Project Configuration
  • Drools Project Example
    • Basic rules
    • Rules attributes (This post!)
    • Debugging / Logging
    • Temporal reasoning
  • jBPM Basic Concepts
  • jBPM Project Configuration
  • jBPM Project Example
    • Script Task
    • Service Task
    • Human Task
    • Subprocesses
  • Drools + jBPM Project Example
    • Business Rule Task

Drools Agenda

Before starting with the definition of the attributes, let’s go back to the first post of this series and review the concept of Drools Agenda. It is a collection of activated rules (all conditions are met, so its consequence can be executed) and it is responsible of managing the execution order of rules.

When the engine fires the rules, Drools picks one rule from the Agenda and executes its consequence, which may or may not cause further activations or deactivations (if data in the Working Memory is changed by the consequence, other rules can be activated or deactivated). This continues until there are no more rules to be fired in the Drools Agenda.

Rule attributes

In this section we will review the rule attributes described in the Drools Documentation and also give an example for each one. By adding attributes to the a definition of a rule, we can influence its behavior in different ways. I will divide the attributes in two sections: single rule attributes and grouping attributes.

Single rule attributes

These attributes have effect only in single rules.

dialect

The dialect specifies the language that will be used for code expressions. There are currently two available dialects: Java and MVEL. The dialect can be specified at package level and then it can be overridden for specific rules. Possible values: “java” or “mvel”.

date-effective

It is a String attribute containing a date and time definition. The rule will only get activated if the current date and time are after the date-effective attribute. If the current date and effective date are exactly the same, the rule will not get activated.

date-expires

It is a String attribute containing a date and time definition. The rule will only get activated if the current date and time are before the date-expires attribute. If the current date is equals to the expiration date, it is considered expired and the rule will not get activated.

duration

It is a deprecated attribute. It was used to indicate the time to wait before firing the rule. The rule was fired if the condition was still met after the duration period. Rules now support both interval and cron based timers, which replace the duration attribute. More information is available in the Drools Documentation: Timers and Calendars.

salience

It is an integer attribute that gives a priority to the rule. The salience value can be a positive or negative integer, where a higher value represents a higher priority. By default all the rules have salience equals to 0.

no-loop

With this boolean attribute we can avoid an infinite loop caused by the activation of the same rule when its consequence is fired. When no-loop is set to true, the consequence of the rule will not trigger again the same rule for the facts that triggered it for the first time.

Grouping attributes

These attributes will be reviewed in the next post:

  • agenda-group
  • auto-focus
  • ruleflow-group
  • activation-group
  • lock-on-active

Examples

Source code

Source code for the examples is available in Github: https://github.com/ezequielgrande/droolsjbpm-quickstart-guide/tree/1.1/

Project

The project drools-attributes is a Maven Module that includes the following dependencies from its parent:

  • org.kie:kie-api:6.1.0.Final
  • org.drools:drools-compiler:6.1.0.Final

Both dependencies are defined using Drools BOM.

Additionaly, dependencies for SLF4J and JUnit are configured in order to Test the Business Rules.

Dialect

In the file dialect.drl you will find two rules, one written with Java dialect and the other one with MVEL dialect.

Java

The following rule is configured for using java dialect:

Rule: Java dialect rule – Identify adult people

rule "Java dialect rule - Identify adult people"
dialect "java"
when
  $p: Person(age > 21)
then
  adults.add($p);
end

Where the variable adults is an instance of List<Person>.

This rule is tested by the JUnit DroolsAttributesTestCase.testDialect() which adds instances of Person to the working memory and then asserts that the global variable adults contains the expected instance of Person.

MVEL

The following rule is configured for using mvel dialect:

Rule: Mvel dialect rule – Give chocolates to my friends

rule "Mvel dialect rule - Give chocolates to my friends"
dialect "mvel"
when
  $p: Person()
  eval(["John", "Mary", "Paul"] contains $p.name)
then
   friendName = $p.name;
   // the following array will be used in a foreach block
   // it contains only one element, so the foreach block will only iterate once
   // this code is here just to show some mvel syntax 
   dummyArray = {'onlyOneIteration'};
   foreach (iteration : dummyArray) {
   gifts.add(new Gift().{
     description = 'Chocolates',
     recipient = friendName
   });
   }
end

Where the variable gifts is an instance of List<Gift>.

This rule is tested by the JUnit DroolsAttributesTestCase.testDialect() which adds instances of Person to the working memory and then asserts that the global variable gifts contains the expected instance of Gift. Note that in the condition and consequence there are code expressions of MVEL. For more information of MVEL, please visit MVEL Language Guide and MVEL 2.0 Block WITH Operator

Date effective

In date-effective.drl there are two rules, one with a date-effective date in the year 2010 and the other one in the year 2150. The only one triggered, at least for the next 135 years :P, will be the one with a date of year 2010 in its date-effective attribute.  You can find the test case for it in DroolsAttributesTestCase.testDateEffective().

rule "Give chocolates as a gift, effective from January 1st 2010"
dialect "java"
date-effective "01-Jan-2010"
when
 (...)
then
  (...)
end

Date expires

In date-expires.drl we show a similar example to the date-effective. The only difference is that now the dates set when the rules expire. The Test Case for it can be found in DroolsAttributesTestCase.testDateExpires().

Duration

In duration.drl we show  how to use the duration attribute. Remember that this attribute is deprecated and it is recommended to use Timers and Calendars instead. Note that for the Test Cases DroolsAttributesTestCase.testDuration() and DroolsAttributesTestCase.testDuration_NotMet() we use a Pseudo Clock in order to be able to tune the system clock and advance time.

Salience

In salience.drl there are three rules with the following salience values:

  • Salience 1: It will be executed in first place since it is the highest value of the three rules.
  • No salience is defined, so it will have the default value 0. This rule will be executed in second place, since it has lower priority than the first one and higher than the third one.
  • Salience -1: It will be executed in last place, since it is the lowest value of the three rules.

The three rules have their conditions empty so they are triggered one time. In their consequence they add a String to a List of Strings and then in the Test Case DroolsAttributesTestCase.testSalience() we assert that the Strings were added in the expected order.

No Loop

For the example of the no-loop attribute we will have three different files:

  • no-loop-disabled-no-modify.drl: Shows the Engine behavior when the modify(object) {} block is not used. It can be checked with the JUnit DroolsAttributesTestCase.testNoLoop_Disabled_NoModify().
  • no-loop-disabled.drl: Shows when a rule is triggered once and again by its own consequence, causing an infinite loop. This case is tested in the JUnits DroolsAttributesTestCase.testNoLoop_Disabled_Stateless() and DroolsAttributesTestCase.testNoLoop_Disabled_Stateful(). The only difference between these Test Cases is that one uses a Stateless session and the other one a Stateful one. These tests have the following characteristics:
    • The session has configured an AgendaEventListener that will keep a count of the number of Rule Matches that were executed. This will help us to know how many times the rule was activated.
    • The fireAllRules() method is executed in a different thread. Since we know that there will be an infinite loop, we will keep it running in a separate thread so we can continue the execution in the main thread to assert the results. The listener’s method getMatchCount() will return the number of matches for the rules.
  • no-loop-enabled.drl: Represents the behavior when the ‘no-loop’ attribute is added to the rule. This case is tested in the JUnits DroolsAttributesTestCase.testNoLoop_Enabled_Stateless() and DroolsAttributesTestCase.testNoLoop_Enabled_Stateful(). The only difference between these Test Cases is that one uses a Stateless session and the other one a Stateful one. In these tests the session has configured an AgendaEventListener that will keep a count of the number of Rule Matches that were executed. This will help us to know how many times the rule was activated. Compared with the previous test, there is no need to create a new thread since there will be no infinite loop that might freeze the test execution. Finally, we assert that there was only one rule match.

Next post

In the next post we will explain the remaining Drools Attributes:

  • agenda-group
  • auto-focus
  • ruleflow-group
  • activation-group
  • lock-on-active

I hope that you have enjoyed this post 🙂

References

Drools ~ Basic Rules example

In the previous post we learned how to configure a Drools Project. Now is time to create a Drools Project and learn the different ways of interacting with the Drools Session in order to insert objects into the memory and let the engine do its work.

First of all, we will review the project configuration and the data model involved in this example.

After that, we will review the Drools API and explain the decisions that we want to make and the rules that will help us to meet our goal. Finally, Test Cases for our project are explained step by step.

[ Leer en español ]

This post belongs to a series of posts that will cover the following topics:

Project Description

Configuration

The project drools-examples is a Maven Project that depends on the following maven artifacts:

  • org.kie:kie-api:6.1.0.Final
  • org.drools:drools-compiler:6.1.0.Final

Both dependencies are defined using Drools BOM.

 Additionaly, dependencies for SLF4J and JUnit are configured in order to Test the Business Rules.

Use Case / Data Model

In this example we will see different rules that might be applied to a Purchase.

The data model for this example is very simple, so we can focus on the rules definition and execution.

  • Purchase
    • Customer name
    • Subtotal
    • Payment Method
    • Discount
  • Payment methods
    • Cash
    • Credit Card
    • Debit Card
  • Potential Customer
    • Customer name
    • Credit Limit

Source code

Source code for this example is available in Github:

https://github.com/ezequielgrande/droolsjbpm-quickstart-guide/tree/master/drools-examples

Here is a screenshot of the Project Explorer, files will be explained in the Test Case section of each group of rules.

drools-examples-project-explorer

Business Rules

We will work on two groups of rules, one group for setting a discount to a Purchase and the other group to detect Potential Customers.

 Discount Rules

  • Cash purchases have no discount
    • When the Payment method is Cash, there is no discount.
    • Total = Subtotal
  • Debit Card purchases have 5% of discount
    • When the Payment method is Debit Card, apply a 5% of discount.
    • Total = Subtotal – (5% of Subtotal)
  • Credit Card purchases have 10% of discount
    • When the Payment method is Credit Card, apply a 10% of discount.
    • Total = Subtotal – (10% of Subtotal)

 Potential Customer

  • Identify potential customers
    • A Potential Customer is someone who spends more than $300 in cash
    • We will offer them a credit limit equals to the 80% of the purchase subtotal
  • Send an email offer to Potential Customers with credit limit lower or equal than $500
    • An Email Service (Mock) will be called from the rules
  • Call Potential Customers with credit limit over $500
    • For this example we will just log when the rule is executed. The real rule can send a notification to the call center or even start a process instance of a specific Business Process

 This graphic represents the decisions to be taken:

Example_Purchase - New Page

Drools API

First of all, let’s review some Drools Concepts by reading their javadoc:

    • KieBase: repository of all the application’s knowledge definitions. It will contain rules, processes, functions, type models. The KieBase itself does not contain runtime data, instead sessions are created from the KieBase in which data can be inserted and process instances started.
    • KieModule: container of all the resources necessary to define a set of KieBases like a pom.xml defining its ReleaseId, a kmodule.xml file declaring the KieBases names and configurations together with all the KieSession that can be created from them and all the other files necessary to build the KieBases themselves.
    • KieContainer: A container for all the KieBases of a given KieModule.
    • KieFileSystem: in memory file system used to programmatically define the resources composing a KieModule.
    • KieBuilder: a builder for the resources contained in a KieModule.

Now let’s see how to create these objects.

KieContainer

This is the way to programmatically create a KieContainer in Drools 6:

       KieServices ks = KieServices.Factory.get();
       // Create the in-memory File System and add the resources files  to it
       KieFileSystem kfs = ks.newKieFileSystem();
       kfs.write(ResourceFactory.newClassPathResource(resourceFilePath));
       // Create the builder for the resources of the File System
       KieBuilder kbuilder = ks.newKieBuilder(kfs);
       // Build the KieBases
       kbuilder.buildAll();
       // Check for errors
       if (kbuilder.getResults().hasMessages(Level.ERROR)) {
           throw new IllegalArgumentException(kbuilder.getResults().toString());
       }
       // Get the Release ID (mvn style: groupId, artifactId,version)
       ReleaseId relId = kbuilder.getKieModule().getReleaseId();
       // Create the Container, wrapping the KieModule with the given ReleaseId
       KieContainer kcontainer = ks.newKieContainer(relId);

KieBase

Once we have created the KieContainer, we create the KieBase:

     // Configure and create the KieBase
  KieBaseConfiguration kbconf = ks.newKieBaseConfiguration();
  KieBase kbase = kcontainer.newKieBase(kbconf);

Stateless Session

Once we have created the KieBase, we create the StatelessKieSession:

      // Configure and create the StatelessKieSession
  KieSessionConfiguration ksconf = ks.newKieSessionConfiguration();
  StatelessKieSession ksession = kbase.newStatelessKieSession(ksconf);

Stateful Session

Once we have created the KieBase, we create the KieSession (Stateful):

     // Configure and create the KieSession
  KieSessionConfiguration ksconf = ks.newKieSessionConfiguration();
 KieSession ksession = kbase.newKieSession(ksconf, null);

Discount Rules

Rules definition

The following rules are defined in the file discount.drl. They use a global variable named ‘logger’, which will have a reference to an instance of a SLFJ Logger.

1.- Cash purchases have no discount

rule "Cash purchases have no discount"
when
  $p:Purchase(paymentMethod == PaymentMethod.CASH)
then
  $p.setDiscount(0);
  logger.info("\t==> Executing RULE 'Cash purchases have no discount' for Object: " + $p);
end
Condition
Purchase(paymentMethod == PaymentMethod.CASH)

As we saw in the ‘Drools Basic Concepts’ section, this condition will be valid for each instance of the class Purchase found in the working memory, which has its attribute paymentMethod equals to PaymentMethod.CASH. In this condition we use pattern binding in order to keep a reference to matched instances of Purchase. We named it ‘$p’, so it will work as a variable and it can be accessed from other conditions and also the consequence of the rule.

Consequence
$p.setDiscount(0);
logger.info("\t==> Executing RULE 'Cash purchases have no discount' for Object: " + $p);

In the consequence of this Rule we set the discount to 0 and then we log a message, using the Global Variable logger.

2.- Debit Card purchases have 5% of discount

rule "Debit Card purchases have 5% of discount"
when
  $p:Purchase(paymentMethod == PaymentMethod.DEBIT)
then
  $p.setDiscount(0.05);
  logger.info("\t==> Executing RULE 'Debit Card purchases have 5% of discount' for Object: "  + $p);
end

This rule is very similar to the first one, the only difference is that gets fired for Purchases payed with Debit Card and the discount is 5%.

 3.- Credit Card purchases have 10% of discount

rule "Credit Card purchases have 10% of discount"
when
  $p:Purchase(paymentMethod == PaymentMethod.CREDIT)
then
  $p.setDiscount(0.1);
  logger.info("\t==> Executing RULE ‘Credit Card purchases have 10% of discount' for Object: " + $p);
end

This rule is also very similar to the first one, the only difference is that gets fired for Purchases payed with Credit Card and the discount is 10%. 

Test Cases

Discount Rules

Tests cases for the Business Rules included in the file discount.drl can be found in the JUnit DiscountRulesTestCase.

A Global Variable is used for Logging purposes. To set a Global Variable in the session, use the methods KieSession#setGlobal(String, Object) and StatelessKieSession#setGlobal(String, Object). In the DRL file it must be defined in this way:

global com.package.ClassName variableName

For example,

global org.slf4j.Logger logger

Note that in this example Business Rules update a field of the objects in Memory. Rules do not insert new objects into the Working Memory. Insertion of new objects will be covered in the following group of rules.

Test #1: testStatelessSession

This Test Case validates the behavior of the Business Rules with a StatelessKieSession. Note that a Stateless Session wraps a StatefulKieSession. These are the actions taken by the engine when the StatelessKieSession#execute(Iterable) method is called:

  • A new Working Memory is created
  • Objects are inserted into the Working Memory
  • All rules are fired
  • Session is disposed

At the beginning, the session is created and a Global Variable is set in order to reference to a SLF4J Logger. This logger will be accessible from the Business Rules.

 // Create the Stateless Session
StatelessKieSession session = TestUtil.createStatelessKieSession(DRL_PATH);
// Add SLF4j Logger as a Global Variable
session.setGlobal("logger", logger);

After the session is created, we instantiate the objects that are going to be inserted into the Working Memory:

Purchase cashPurchase = new Purchase("john", 100, PaymentMethod.CASH);
Purchase debitPurchase = new Purchase("peter", 100, PaymentMethod.DEBIT);
Purchase creditPurchase = new Purchase("george", 100, PaymentMethod.CREDIT);

So, in this example there will be three purchases:

  • John pays $100 in Cash
  • Peter pays $100 in Debit Card
  • George pays $100 in Credit Card

Since we are working with a Stateless Session, we need to call the execute() method in order to insert the objects and let the engine fire the rules that might get activated:

session.execute(Arrays.asList(cashPurchase, debitPurchase, creditPurchase));

When the three purchases are inserted into the memory, this pattern matching is done:

Quickstart_Guide_BasicRules_PatternMatching - New Page (1)

So, the three rules are added to the Drools Agenda including reference to the matched facts. When the engine is ready to fire the rules, it will fire these three since they were added to the Agenda.

The consequence (RHS) of each rule sets different discount values, so if everything worked well we should have these discounts:

Example_DiscountRules_01_testStateless - New Page (1)

We will assert these values with JUnit:

Assert.assertEquals(0d, cashPurchase.getDiscount());
Assert.assertEquals(0.05, debitPurchase.getDiscount());
Assert.assertEquals(0.1, creditPurchase.getDiscount()); 
Test #2: testStatefulSession

Tests Business Rules with a Stateful Session, by following these steps:

  • Objects are inserted into the Working Memory.
  • Assert that the objects weren’t modified by the rules (since the rules weren’t fired yet)
  • Fire all rules
  • Assert that the objects were modified by the rules

 In this case, the data to be inserted is the same one as in the previous example but now using a Stateful Session (KieSession). The main difference in the test is that we need to insert the data into the working memory and after that we have to explicitly tell the engine that it is time to fire all rules.

It is important to call session.dispose() when we finish working with the session, in order to release resources.

Coffee break

Here finishes the description of the Discount Rules, now we will review the Potential Customer rules.

Let’s take a break, and explore the temples of Angkor, Cambodia with Google Maps:

Now we are ready to get back to work! 🙂

Potential Customer

Rules definition (First approach)

The following rules are defined in the file potentialCustomer01.drl. They use two global variables:

  • logger: instance of SLFJ Logger
  • emailService: instance of EmailService

1.- Identify potential customers

rule "Identify potential customers"
when
  $p:Purchase(paymentMethod == PaymentMethod.CASH, subtotal > 300)
then
  logger.info("\t==> Potential Customer found! " + $p);
  // Create a new Potential Customer object
  PotentialCustomer pc = new PotentialCustomer($p.getCustomerName(), $p.getSubtotal()*0.80);
  // Insert the Potential Customer object into the Working Memory
  insert(pc);
end
Condition
Purchase(paymentMethod == PaymentMethod.CASH, subtotal > 300)

This condition will be valid for each instance of the class Purchase found in the working memory, which has its attribute paymentMethod equals to PaymentMethod.CASH and its subtotal is greater than 300.

Objects that matches this condition will be referenced to the variable $p, which is accessible from the Right Hand Side (RHS) of the rule.

Consequence

In the RHS of this Rule we create a new instance of Potential Customer, setting the Limit Credit to the 80% of the subtotal. After the object is created, we insert it into the session with the “insert(Object)” method. After this insertion, the inserted objected can generate more rule matches. As we saw in the Drools Concept post, this process is also known as “inference”. This graphic shows how this works:

Example_Purchase_PotentialCustomer - New Page (1)

NOTE: This rule will be fired for each Cash Purchase > $300, so we might be inserting more than one PotentialCustomer object for a same Customer (if there is more than one Purchase for the same Customer). The fix for this is shown in the next example (potentialCustomer02.drl). Let’s explain this situation with a graphic:

Example_Purchase_PotentialCustomer_2 - New Page (1)

2.- Send an email offer to Potential Customers with credit limit lower or equal than $500

rule "Send an email offer to Potential Customers with credit limit lower or equal than $500"
when
  $pc:PotentialCustomer(creditLimit<=500)
then
  logger.info("\t==> Sending email to Potential Customer: " + $pc);
  // Here an email service will send the email...
  emailService.sendCreditCardOffer($pc)
end
Condition
PotentialCustomer(creditLimit <= 500)

The idea of this rule is to detect the PotentialCustomer instances that are inserted into the memory. This rule will get fired for each instance of the class PotentialCustomer found in the working memory, which has its attribute creditLimit  lower o equals to 500.

Objects that matches this condition will be referenced to the variable $pc, which is accessible from the Right Hand Side (RHS) of the rule.

Consequence

In the RHS of this Rule we call a method of the Email Service, which is referenced by the Global Variable emailService. Note that the value for the global variable must be set in the session, as it is explained in the Test Cases section.

3.- Call Potential Customers with credit limit over $500

rule "Call Potential Customers with credit limit over $500"
when
  $pc:PotentialCustomer(creditLimit>500)
then
  logger.info("\t==> Calling Potential Customer: " + $pc);
  // Here we could notify someone in the call center
     to call the Potential customer
  // ...
  // We could also start a process instance
     of a Business Process
end 

This rule is very similar to the previous one, the only difference is that gets fired for Potential Customers with a credit limit greater than 500.

In the consequence we just log a message. In a real-world example we could notify someone in the call center to call a Potential Customer or we could also start a process instance of a Business Process. In addition, we could call a service as we did in the previous rule.

Rules definition (Final approach)

The following rules are defined in the file potentialCustomer02.drl. The only difference between potentialCustomer01 and potentialCustomer02 is the pattern of the first rule:

1.- Identify potential customers

rule "Identify potential customers"
when
  $p:Purchase(paymentMethod == PaymentMethod.CASH, subtotal > 300)
  not PotentialCustomer(customerName == $p.getCustomerName())
then
  logger.info("\t==> Potential Customer found! " + $p);
  // Create a new Potential Customer object
  PotentialCustomer pc = new PotentialCustomer($p.getCustomerName(), $p.getSubtotal()*0.80);
  // Insert the Potential Customer object into the Working Memory
  insert(pc);
end
Condition (fixed)
Purchase(paymentMethod == PaymentMethod.CASH, subtotal > 300)
and
not PotentialCustomer(customerName == $p.getCustomerName())

 Note that this rule includes the fix that we mentioned in potentialCustomer01.drl.

In this LHS we validate that it does not exist a PotentialCustomer object in the Working Memory for the current customer of the Purchase being evaluated.

 So, this condition will be valid for each instance of the class Purchase found in the working memory, which has its attribute paymentMethod equals to PaymentMethod.CASH and its subtotal is greater than 300. In addition, the customerName of the Purchase must NOT have a PotentialCustomer object associated with it.

Consequence

The consequence has no changes, it is the same one as the described before.

Test Cases

Tests cases for the Business Rules included in the files potentialCustomer01.drl and potentialCustomer02.drl can be found in the JUnit PotentialCustomerRulesTestCase.

Two Global Variables are used for logging purposes and to access a Service from the Business Rules.

NOTE: There is an intentional error in the rules of ‘potentialCustomer01.drl’ since the rule that identifies Potential Customers will be fired for each Cash Purchase > $300. Because of this, we might have more than one PotentialCustomer object for a same Customer. The fix for this is shown in ‘potentialCustomer02.drl’.

Test #1: testIdentifyPotentialCustomer

Tests the Rule ‘Identify potential customers’ of the DRL file ‘potentialCustomer01.drl’. It will insert the following Purchases into the Working Memory in order to evaluate if they are correctly evaluated by the rules:

  • Customer ‘john’ spends $250 in Cash
  • Customer ‘mary’ spends $450 in Cash
  • Customer ‘peter’ spends $100 in Debit Card
  • Customer ‘george’ spends $500 in Credit Card

The second Purchase will be the only one that will trigger the Rule that identifies a Potential Customer (since the purchase is being paid by Cash and the subtotal is greater than 300). Because of this, after firing the Rules there will be only one “PotentialCustomer” object in the working memory (inserted by the triggered Rule).

 At the beginning, the session is created and the Global Variables are set in order to access from the Business Rules to a Logger and an Email Service.

// Create the Stateful Session
 KieSession session = TestUtil.createKieSession(DRL01_PATH);
// Add SLF4j Logger as a Global Variable
session.setGlobal("logger", logger);
// Add the Email Service as a Global Variable
session.setGlobal("emailService", EmailService.getInstance());

After the session is created, we instantiate the objects that are going to be inserted into the Working Memory:

// Create objects that will be inserted into the Session
 Purchase cashPurchaseLowAmount = new Purchase("john", 250, PaymentMethod.CASH);
 Purchase cashPurchasePotentialCustomer = new Purchase("mary", 450, PaymentMethod.CASH);
 Purchase debitPurchase = new Purchase("peter", 100, PaymentMethod.DEBIT);
 Purchase creditPurchase = new Purchase("george", 500, PaymentMethod.CREDIT);

After creating the objects, we insert them into the Working Memory:

// Insert objects into the working memory
session.insert(cashPurchaseLowAmount);
session.insert(cashPurchasePotentialCustomer);
session.insert(debitPurchase);
session.insert(creditPurchase); 

Since we are working with a Stateful Session, the rules won’t get fired if we don’t explicitly do it. So, the engine should not have fired the rules and there should not exist a PotentialCustomer object in memory (since it is inserted by the consequence of a rule).

For validating this, we need to inspect the objects in memory and be sure that there are no instances of the PotentialCustomer class.

When an object is inserted into the Working Memory, the engine keeps a reference to it and it is represented by the interface FactHandle. This is a way for retrieving the available FactHandles from the Working Memory:

Collection<FactHandle> factHandles = session.getFactHandles(new ClassObjectFilter(PotentialCustomer.class));

Now, we assert that there were no FactHandles for the PotentialCustomer class:

Assert.assertEquals(0, factHandles.size());

 After this we will fire all the rules:

session.fireAllRules();

Finally, we will assert that a PotentialCustomer object was inserted after firing all the rules. We know that Mary will be a Potential Customer since she spent more than $300 in cash. So, we need to validate that there is only one instance of PotentialCustomer in memory and the Customer Name of this instance is “mary”. We retrieve the available FactHandles of PotentialCustomer in the same way than we did before:

// After firing the rules, the Potential Customer has been inserted
factHandles = session.getFactHandles(new ClassObjectFilter(PotentialCustomer.class)); 

But now we assert that there is only 1 FactHandle in Memory:

Assert.assertEquals(1, factHandles.size());

In order to validate the Customer Name, we need to retrieve the real object that is referenced by the FactHandle:

FactHandle fh = factHandles.iterator().next();
 PotentialCustomer pc = (PotentialCustomer) ((DefaultFactHandle) fh).getObject(); 

Now that we have the PotentialCustomer instance, we can assert the customer name:

Assert.assertEquals("mary", pc.getCustomerName()); 

Finally, don’t forget to dispose the session in order to release resources:

session.dispose();

Test #2: testIdentifyPotentialCustomer_Two_Purchases_Wrong

Tests the Rule ‘Identify potential customers’ of the DRL file ‘potentialCustomer01.drl’. It will insert the following Purchases into the Working Memory in order to evaluate if they are correctly evaluated by the rules:

  • Customer ‘john’ spends $350 in Cash
  • Customer ‘mary’ spends $250 in Cash
  • Customer ‘john’ spends $400 in Cash
  • Customer ‘george’ spends $500 in Credit Card

Note that ‘john’ has two associated Purchases which will trigger the Rule being tested. Because of this, after firing the Rules the working memory will have two “PotentialCustomer” objects for the same Customer. This error is fixed in the DRL file ‘potentialCustomer02.drl’.

 At the beginning, the session is created and the Global Variables are set in order to access from the Business Rules to a Logger and an Email Service.

// Create the Stateful Session
 KieSession session = TestUtil.createKieSession(DRL01_PATH);
// Add SLF4j Logger as a Global Variable
session.setGlobal("logger", logger);
// Add the Email Service as a Global Variable
session.setGlobal("emailService", EmailService.getInstance());

After the session is created, we instantiate the objects that are going to be inserted into the Working Memory:

// Create objects that will be inserted into the Session
 Purchase cashPurchasePotentialCustomer1 = new Purchase("john", 350, PaymentMethod.CASH);
 Purchase cashPurchaseLowAmount = new Purchase("mary", 250, PaymentMethod.CASH);
 Purchase cashPurchasePotentialCustomer2 = new Purchase("john", 400, PaymentMethod.CASH);
 Purchase creditPurchase = new Purchase("george", 500, PaymentMethod.CREDIT);

After creating the objects, we insert them into the Working Memory:

// Insert objects into the working memory
session.insert(cashPurchasePotentialCustomer1);
session.insert(cashPurchaseLowAmount);
session.insert(cashPurchasePotentialCustomer2);
session.insert(creditPurchase);

As we did in the previous test case, we validate that the objects are inserted only after the rules were fired.

After firing the rules, we validate that there are two Potential Customer objects in memory and both of them are associated with John:

factHandles = session.getFactHandles(new ClassObjectFilter(PotentialCustomer.class));
 Assert.assertEquals(2, factHandles.size());
for (FactHandle fh : factHandles) {
 PotentialCustomer pc = (PotentialCustomer) ((DefaultFactHandle) fh).getObject();
 Assert.assertEquals("john", pc.getCustomerName());
 }

 Finally, don’t forget to dispose the session in order to release resources:

session.dispose(); 

With this example we have just shown that the rules are fired for each object that matches their conditions. In the following example we will see how to avoid this behavior, in case that is needed.

Test #3: testIdentifyPotentialCustomer_Two_Purchases_OK

Tests the Rule ‘Identify potential customers’ of the DRL file ‘potentialCustomer02.drl’. It will insert the following Purchases into the Working Memory in order to evaluate if they are correctly evaluated by the rules:

  • Customer ‘john’ spends $350 in Cash
  • Customer ‘mary’ spends $250 in Cash
  • Customer ‘john’ spends $400 in Cash
  • Customer ‘george’ spends $500 in Credit Card

Note that ‘john’ has two associated Purchases that should be taken into account in order to consider John a Potential Customer. This version of the Rule (potentialCustomer02.drl) works OK since it validates if there is a “PotentialCustomer” object in memory before inserting a new one.

 At the beginning, the session is created and the Global Variables are set in order to access from the Business Rules to a Logger and an Email Service.

// Create the Stateful Session
 KieSession session = TestUtil.createKieSession(DRL02_PATH);
// Add SLF4j Logger as a Global Variable
session.setGlobal("logger", logger);
// Add the Email Service as a Global Variable
session.setGlobal("emailService", EmailService.getInstance());

After the session is created, we instantiate the objects that are going to be inserted into the Working Memory:

 Purchase cashPurchasePotentialCustomer1 = new Purchase("john", 350, PaymentMethod.CASH);
 Purchase cashPurchaseLowAmount = new Purchase("mary", 250, PaymentMethod.CASH);
 Purchase cashPurchasePotentialCustomer2 = new Purchase("john", 400, PaymentMethod.CASH);
 Purchase creditPurchase = new Purchase("george", 500, PaymentMethod.CREDIT); 

 After creating the objects, we insert them into the Working Memory:

// Insert objects into the working memory
session.insert(cashPurchasePotentialCustomer1);
session.insert(cashPurchaseLowAmount);
session.insert(cashPurchasePotentialCustomer2);
session.insert(creditPurchase); 

As we did in the previous test case, we validate that the objects are inserted only after the rules were fired.

After firing the rules, we validate that there is only one Potential Customer object in memory and it is associated with John:

factHandles = session.getFactHandles(new ClassObjectFilter(PotentialCustomer.class));
 Assert.assertEquals(1, factHandles.size());
for (FactHandle fh : factHandles) {
 PotentialCustomer pc = (PotentialCustomer) ((DefaultFactHandle) fh).getObject();
 Assert.assertEquals("john", pc.getCustomerName());
 }

 Finally, don’t forget to dispose the session in order to release resources:

session.dispose(); 

Next post

I hope that after reading this post you have a better understanding of the rules execution in Drools.

In the next post we will cover more Drools Syntax so we can write more powerful rules.

Stay tuned! 🙂

References

Creating a Drools Project

This is the second post of a blog post series that are aimed to teach different functionalities of Drools/jBPM by providing clear explanations and simple examples. These posts will cover the following topics:

  • Drools Basic Concepts
  • Drools Project Configuration (This post!)
  • Drools Project Example
  • jBPM Basic Concepts
  • jBPM Project Configuration
  • jBPM Project Example
    • Script Task
    • Service Task
    • Human Task
    • Subprocesses
  • Drools + jBPM Project Example
    • Business Rule Task

This post shows different approaches for creating a new Drools Project. [ Leer en español ]

Creating a Drools Project

Requirements

Software and Knowledge requirements:

Optional requirements

Project configuration

We will review the most common approaches for creating a new Drools Project:

  1. Eclipse Plugin: Create a new Drools Project.
  2. Maven: Setup the required maven dependencies in your pom file.
  3. Manual configuration: Add desired .jars to your Classpath.

Examples of this guide are going to be created using Maven for their dependency management, since it is a very practical and easy approach. In addition, it is good to know that building and deploying activities of Drools/jBPM 6 are aligned with Maven and Maven repositories. Drools documentation: http://docs.jboss.org/drools/release/6.1.0.Final/drools-docs/html/Welcome.html#installationAndSetup

Eclipse Plugin

After installing the Eclipse plugin, you can create a new Drools Project by using the New Project Wizard. In order to include in your Classpath the required Drools libraries, you must configure in Eclipse a Drools Runtime. For doing this, please download and unzip the file drools-distribution-6.1.0.Final.zip from the Drools download site. Note that the Drools Runtime path must point to the “/binaries” directory instead of “/”.

Maven

This is the required configuration for setting up your pom.xml file:

  <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>org.drools</groupId>
       <artifactId>drools-bom</artifactId>
       <type>pom</type>
       <version>6.1.0.Final</version>
       <scope>import</scope>
     </dependency>
     ...
   </dependencies>
  </dependencyManagement>

 <dependencies>
   <dependency>
    <groupId>org.kie</groupId>
     <artifactId>kie-api</artifactId>
   </dependency>
   <dependency>
     <groupId>org.drools</groupId>
     <artifactId>drools-compiler</artifactId>
     <scope>runtime</scope>
   </dependency>
   ...
  </dependencies>

Since Drools 6.0.0.CR2 all Drools, jBPM and Optaplanner projects have a BOM project (Bills of Materials) in order to improve versions definition. In the previous code snippet this is done with the <dependencyManagement> tag. Learn more about it here: http://blog.athico.com/2013/08/maven-boms-bill-of-materials.html

Manual configuration

These are the important libraries that you might want to include in your project, as described in the Drools Documentation:

  • kie-api.jar – this provides the interfaces and factories. It also helps clearly show what is intended as a user API and what is just an engine API.
  • kie-internal-api.jar – this provides internal interfaces and factories.
  • drools-core.jar – this is the core engine, runtime component. This is the only runtime dependency if you are pre-compiling rules (and deploying via Package or RuleBase objects).
  • drools-compiler.jar – this contains the compiler/builder components to take rule source, and build executable rule bases. This is often a runtime dependency of your application, but it need not be if you are pre-compiling your rules. This depends on drools-core.
  • drools-decisiontables.jar – this is the decision tables ‘compiler’ component, which uses the drools-compiler component. This supports both excel and CSV input formats.

Maven configuration example

Here is an example of a Maven pom.xml for a Drools 6 project, using the Maven Bill of Materials (BOM). This Maven project only uses the dependencies:

  • org.kie:kie-api:6.1.0.Final
  • org.drools:drools-compiler:6.1.0.Final

Note that the version number is configured in the dependecyManagement section of the pom.xml, so it is not necessary to include a version number in the dependencies definition.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.plugtree.training</groupId>
 <artifactId>droolsjbpm-quickstart-guide</artifactId>
 <version>1.0</version>

 <name>Drools/jBPM Quickstart Guide</name>

 <!-- Drools Maven BOM (Bill of Materials) -->
 <dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>org.drools</groupId>
        <artifactId>drools-bom</artifactId>
        <type>pom</type>
        <version>6.1.0.Final</version>
        <scope>import</scope>
     </dependency>
   </dependencies>
 </dependencyManagement>

 <!-- Required dependencies -->
 <dependencies>
   <dependency>
       <groupId>org.kie</groupId>
       <artifactId>kie-api</artifactId>
     </dependency>
     <dependency>
       <groupId>org.drools</groupId>
       <artifactId>drools-compiler</artifactId>
       <scope>runtime</scope>
     </dependency>
 </dependencies>
</project>

Drools Project Example

Next post will include an example of a Drools Project, which will have some basic rules in order to understand how the Rules are processed. [Updated 20-nov-2014] Next post is ready! Read it here.

References

Drools ~ Basic concepts

Introduction

This is the first post of a blog post series that are aimed to teach different functionalities of Drools/jBPM by providing clear explanations and simple examples. These posts will cover the following topics:

  • Drools Basic Concepts (This post!)
  • Drools Project Configuration
  • Drools Project Example
  • jBPM Basic Concepts
  • jBPM Project Configuration
  • jBPM Project Example
    • Script Task
    • Service Task
    • Human Task
    • Subprocesses
  • Drools + jBPM Project Example
    • Business Rule Task

This post reviews the different concepts that we need to know before starting to develop rules.

Leer versión en español ]

Resources

Examples will be specifically thought for each specific topic, so the reader can focus on the functionality to be learnt without adding unnecessary complexity. Most of them will be a simple JUnit Test Cases.

This guide is based on version 6.1.0.Final of Drools/jBPM,  which is quite new (Aug-2014) but it seems to be more stable than the version 6.0.1.Final.

Most of the graphics used in this guide were created with Lucidchart and they include public domain cliparts from Openclipart.

KIE

Version 6 introduces KIE (Knowledge is Everything), which is the new group name for the following subprojects of Drools/jBPM:

Training_KIE_Groupname - New Page

Business Rules

Mariano De Maio has written a really good article which explains Business Rules, you can read it here.

Basically, a Rule has two sections:

  • Condition: When the condition is met, the rule is available for execution
  • Consequence: Actions to be taken when the rule is executed

Conditions can check information of objects stored in the Working Memory of the Rule Engine. These objects in memory are also called facts and they represent the state of our Rule Engine.

Training_Facts_Drools_Actions - New Page (1)

Condition constraints

When the data of the Working Memory is updated (insertions, modifications, deletions), it is evaluated against the conditions of the rules in order to activate/deactivate them.

There are two kind of constraints that can be applied to rules conditions:

  • Object Type constraint: Objects in Working Memory will match this constraint when they are instances of the specified Object Type (Java Class). For example, the constraint:

Order()

 matches for each Order instance found in the Working Memory.

  • Field constraint: Objects will match the constraint if their fields has certain values. For example, the constraint:

Order(status==”New”)

matches for each Order instance found in the Working Memory, having their attribute “status” equals to the String “New”.

 

Pattern Matching

An Object Type Constraint plus its zero or more Field Constraints is referred to as a pattern.

When an object in memory satisfies both constraints, it is said to be matched. When all conditions of a rule are matched, a Rule Match is created. The rule and the matched facts will be referenced and placed into the Agenda.

The process of matching patterns against the inserted data is referred to as Pattern Matching.

Training_Drools_PatternMatching - New Page (1)

The following graphic displays the Pattern Matching process for a rule that will be fired for each new Order that it is found in the Working Memory.

training_PatternMatching - New Page (2)

Agenda

The Agenda is a collection of activated rules (all conditions are met, so its consequence can be executed) and it is responsible of managing the execution order of rules.

When the rules are being fired, Drools picks one rule from the Agenda and executes its consequence, which may or may not cause further activations or deactivations (if data in the Working Memory is changed by the consequence, other rules can be activated or deactivated). This continues until the Drools Agenda is empty, that is there are no more rules to be fired. The order in which Drools fires the rules can be tweaked by configuring the rules with special attributes, which will be discussed in the ‘Conflict Resolution’ section.

The engine cycles through these two phases:

  1. Rule Runtime Actions: Executes rule consequences or main Java App process.
  2. Agenda Evaluation: Selects a rule to be fired.

Training_Drools_Execution_Lifecycle - New Page (1)

Conflict resolution

Rule Matches on the Agenda are referred to as a conflict set and their execution is determine by a Conflict Resolution Strategy. Notice that the order of execution so far is considered arbitrary.

The execution order will be determined by a Conflict Resolution Strategy, based on the priority of each rule ready to be fired. Priority can be set by adding to the rules the attribute “salience” (integer value, the higher the value, the higher the priority). By default, all rules have the same priority (0).

Rules should be independent from each other and they should not be “aware” of other possible rules or execution flows. So, it is not a good idea to write rules as a sequence of executions ordered by priorities. Although, we often need to define priority to certain rules. For example, we need to assure that initializing rules are executed at first.

There are other ways of setting a sequence of execution to rules, such as Agenda Groups, Ruleflow Groups and Activation Groups. These topics will be discussed later.

Inference

As it is described in the Merriam-Webster Dictionary, inference is the process of “reaching a conclusion about something from known facts or evidence”.

In other words, inference is the way of generating new knowledge from the evaluation of our current knowledge.

From the point of view of a Rule Engine, reasoning is done thanks to the business rules. Conclusions can include a modification of current facts or evidence. These modification of facts can lead to new reasoning + new conclusions. This new reasoning triggered by the modification of facts in a conclusion is also called inference.

Knowledge Base

The Knowledge Base is a repository of all the application’s knowledge definitions (rules, processes, functions, type models). It does not contain runtime data.

Session

Runtime data is stored in a Session, which can be created from a Knowledge Base that has the application’s knowledge definitions.

Stateful Session

A Stateful Session is the most common way to interact with the engine. It allows the application to establish an iterative conversation with the engine, where the state of the session is kept across invocations. When a rule consequence changes the state of the session, it might trigger new rules activations or deactivations.

Stateless Session

Stateless sessions do not support iterative invocations, so the engine will not be able to provide inference features. Each invocation has its own isolated data/state, which is disposed at the end of the invocation. Actually, a Stateless session is a wrapped Stateful Session with this lifecycle:

    • Stateful session is created
    • Data is inserted into the session
    • All rules are fired
    • Results are returned
    • Session is disposed

Drools Project Configuration

Next post will include information on how to create a new Drools Project.

[Updated 22-ago-2014] Next post is ready! Read it here.

References