JBoss Drools

Drools Decision Table Example

In this article we will look into an example of Drools Decision Tables.

So what are Decision Tables?
We know rules are defined in a drl file but if you have lots of similar rules with different values you can make use of Drools Decision Tables.

Rules that share the same conditions with different parameters can be captured in a decision table. Decision tables are a precise yet compact way of representing conditional logic and are well suited to business level rules.
 
 

 
You can define the decision tables in an Excel spreadsheet (the .xls file) or a comma separated value (the .csv file) format.

Before we start with the example, let’s work on our setup.

If you want to more know about Drools Introduction or its setup, read here.

This example uses the following frameworks:

  1. Maven 3.2.3
  2. Java 8
  3. Drools 6.2
  4. Eclipse  as the IDE, version Luna 4.4.1.

In your pom.xml, you need to add the below dependencies:

  1. knowledge-api – this provides the interfaces and factories
  2. drools-core – this is the core engine, runtime component. This is the only runtime dependency if you are pre-compiling rules.
  3. drools-complier – this contains the compiler/builder components to take rule source, and build executable rule bases. You don’t need this during runtime, if your rules are pre-compiled.

pom.xml:

<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.javacodegeeks.drools</groupId>
	<artifactId>droolsHelloWorld</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>knowledge-api</artifactId>
			<version>${drools.version}</version>
		</dependency>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-core</artifactId>
			<version>${drools.version}</version>
		</dependency>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-compiler</artifactId>
			<version>${drools.version}</version>
		</dependency>
	</dependencies>
	<properties>
		<drools.version>6.2.0.Final</drools.version>
		<jbpm.version>6.2.0.Final</jbpm.version>
	</properties>
</project>

1. Shopping Cart Rules

As an Example to understand Drool’s Decision Tables, we will consider an example of shopping cart. Once the customer is done with the shopping we will have to calculate the cart’s total price based on Customer‘s attributes.

For example, if the customer has just registered to the site, there will be a 2% discount on the first purchase.

If the customer has a coupon, another 5% discount will be applied on the total price. The coupon code and the percentage amounts may vary. We may also want to add similar rules in future.

Before we start defining the rules in XLS, let’s go through the domain model.

Customer will add one or more products to the cart.

Product:

package com.javacodegeeks.drools.model;

public class Product {
	private int price;
	private String desc;
	
	public Product(String desc, int price) {
		this.desc = desc;
		this.price = price;
	}

	public int getPrice() {
		return price;
	}

	public String getDesc() {
		return desc;
	}
	
	public String toString() {
		return "product: " + desc + ", price: " + price;
	}
}

Here is our customer class. It has reference to Cart. The other important attributes are the coupon code and whether the customer is new.

Customer:

package com.javacodegeeks.drools.model;

public class Customer {
	private Cart cart;
	private String coupon;
	private boolean isNew;
	
	public static Customer newCustomer() {
		Customer customer = new Customer();
		customer.isNew = true;
		return customer;
	}
	
	public boolean getIsNew() {
		return isNew;
	}

	public void addItem(Product product, int qty) {
		if (cart == null) {
			cart = new Cart(this);			
		}
		cart.addItem(product, qty);
	}

	public String getCoupon() {
		return coupon;
	}

	public void setCoupon(String coupon) {
		this.coupon = coupon;
	}

	public Cart getCart() {
		return cart;
	}
	
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Customer new? ")
		   .append(isNew)
		   .append("\nCoupon: ")
		   .append(coupon)
		   .append("\n")
		   .append(cart);
		return sb.toString();
	}
}

Customer’s cart contain the cart items.

Cart:

package com.javacodegeeks.drools.model;

import java.util.ArrayList;
import java.util.List;

public class Cart {
	private Customer customer;
	private List cartItems = new ArrayList();
	private double discount;

	public Cart(Customer customer) {
		this.customer = customer;
	}

	public void addItem(Product p, int qty) {
		CartItem cartItem = new CartItem(this, p, qty);
		cartItems.add(cartItem);
	}

	public double getDiscount() {
		return discount;
	}

	public void addDiscount(double discount) {
		this.discount += discount;
	}

	public int getTotalPrice() {
		int total = 0;
		for (CartItem item : cartItems) {
			total += item.getProduct().getPrice() * item.getQty();
		}
		return total;
	}

	public Customer getCustomer() {
		return customer;
	}

	public List getCartItems() {
		return cartItems;
	}

	public void setCustomer(Customer customer) {
		this.customer = customer;
	}

	public int getFinalPrice() {
		return getTotalPrice() - (int) getDiscount();
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (CartItem cartItem : cartItems) {
			sb.append(cartItem)
			  .append("\n");
		}
		sb.append("Discount: ")
		  .append(getDiscount())
		  .append("\nTotal: ")
		  .append(getTotalPrice())
		  .append("\nTotal After Discount: ")
		  .append(getFinalPrice());
		return sb.toString();
	}
}

Each cart item contains product and quantity ordered.

CartItem:

package com.javacodegeeks.drools.model;

public class CartItem {
	private Cart cart;
	private Product product;
	private int qty;
	
	public CartItem(Cart cart, Product product, int qty) {
		this.cart = cart;
		this.product = product;
		this.qty = qty;
	}

	public Product getProduct() {
		return product;
	}

	public int getQty() {
		return qty;
	}
	
	public String toString() {
		return product + ", qty: " + qty;
	}
}

The rule that we want to write in XLS is:

package com.javacodegeeks.drools;
import com.javacodegeeks.drools.model.Customer;
import com.javacodegeeks.drools.model.Product;

rule "If Coupon==DISC01, apply 5% discount"
when
$customer:Customer(coupon == "DISC01" )
then
$customer.getCart().addDiscount(((double)$customer.getCart().getTotalPrice())*0.05d);
end

rule "If consumer is new, apply 2% discount"
when
$customer:Customer($customer.isNew())
then
$customer.getCart().addDiscount(((double)$customer.getCart().getTotalPrice())*0.02d);
end

We will now write the above in XLS.

2. Decision Tables in XLS

The decision table is grouped in several sections. You can think of the first section as the header section where we declare the package, import classes, functions, variables, enter some notes.

  1. RuleSet defines the package
  2. Import specifies the used classes, including static imported functions
  3. Notes can be any text

The second section starts with ‘RuleTable’.

  1. ‘RuleTable’ denotes the start of the decision table.
  2. It groups rules that operate on the same domain object and conditions.
  3. The next line defines column types.
  4. The column types are: NAME, CONDITION and ACTION
  5. You will use NAME to specify a rule name. If you don’t specify a name, it gets auto-generated.
  6. CONDITION defines the rule condition
  7. ACTION is a rule action.

In the next section, we declare the shared objects.

  1. Our shared object here is $customer:Customer.
  2. It exposes the Customer object as a $customer variable.
  3. Note that he first two columns are merged into one column because they share the same type.

In the next line we define the individual conditions or code blocks (in case of actions).

  1. coupon gets converted to customer.getCoupon()
  2. isNew gets converted to customer.getIsNew()
  3. Action contains the code that applies when the condition is satisfied.
    $customer.getCart().addDiscount(((double)$customer.getCart().getTotalPrice())*$param);
  4. $param is substituted by the value provided in the last section

Next, we provide some meaningful description of the column/action.

In the final section, we provide the actual values where each line represents one rule. If a cell doesn’t have a value, that condition/action is ignored.

Drools Decision Tables XLS
Drools Decision Tables XLS

3. Run the Decision Tables Example

To run the example, we need to create the Customer object, add some product items to the cart.

Next we have to load the decision table in XLS format and build the KnowledgeBase. Using the KnowledgeBase, we’ll create a stateless knowledge session.

The decision table needs a special configuration that is encapsulated in the DecisionTableConfiguration class. This configuration specifies the type of decision table and it is then passed to the knowledge builder. Since the rules are in XLS format, we have to use the DecisionTableInputType.XLS object as the input type.

DecisionTableConfiguration dtconf = KnowledgeBuilderFactory
				.newDecisionTableConfiguration();
		dtconf.setInputType(DecisionTableInputType.XLS);

		KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory
				.newKnowledgeBuilder();
		knowledgeBuilder.add(ResourceFactory
				.newClassPathResource("shopping_cart_customer.xls"),
				ResourceType.DTABLE, dtconf);

Here is the complete code.

DroolsDecisionTableExample:

package com.javacodegeeks.drools;

import org.kie.api.io.ResourceType;
import org.kie.internal.KnowledgeBase;
import org.kie.internal.KnowledgeBaseFactory;
import org.kie.internal.builder.DecisionTableConfiguration;
import org.kie.internal.builder.DecisionTableInputType;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
import org.kie.internal.runtime.StatelessKnowledgeSession;

import com.javacodegeeks.drools.model.Customer;
import com.javacodegeeks.drools.model.Product;

public class DroolsDecisionTableExample {
	private static StatelessKnowledgeSession session;

	public static void main(String[] args) throws Exception {
		KnowledgeBase knowledgeBase = createKnowledgeBaseFromSpreadsheet();
		session = knowledgeBase.newStatelessKnowledgeSession();

		Customer customer = new Customer();
		Product p1 = new Product("Laptop", 15000);
		Product p2 = new Product("Mobile", 5000);
		Product p3 = new Product("Books", 2000);
		customer.addItem(p1, 1);
		customer.addItem(p2, 2);
		customer.addItem(p3, 5);
		customer.setCoupon("DISC01");

		session.execute(customer);

		System.out.println("First Customer\n" + customer);

		Customer newCustomer = Customer.newCustomer();
		newCustomer.addItem(p1, 1);
		newCustomer.addItem(p2, 2);
		
		session.execute(newCustomer);

		System.out.println("*********************************");
		System.out.println("Second Customer\n" + newCustomer);
	}

	private static KnowledgeBase createKnowledgeBaseFromSpreadsheet()
			throws Exception {
		DecisionTableConfiguration dtconf = KnowledgeBuilderFactory
				.newDecisionTableConfiguration();
		dtconf.setInputType(DecisionTableInputType.XLS);

		KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory
				.newKnowledgeBuilder();
		knowledgeBuilder.add(ResourceFactory
				.newClassPathResource("shopping_cart_customer.xls"),
				ResourceType.DTABLE, dtconf);

		if (knowledgeBuilder.hasErrors()) {
			throw new RuntimeException(knowledgeBuilder.getErrors().toString());
		}		

		KnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
		knowledgeBase.addKnowledgePackages(knowledgeBuilder
				.getKnowledgePackages());
		return knowledgeBase;
	}
}

Output:

First Customer
Customer new? false
Coupon: DISC01
product: Laptop, price: 15000, qty: 1
product: Mobile, price: 5000, qty: 2
product: Books, price: 2000, qty: 5
Discount: 1750.0
Total: 35000
Total After Discount: 33250
*********************************
Second Customer
Customer new? true
Coupon: null
product: Laptop, price: 15000, qty: 1
product: Mobile, price: 5000, qty: 2
Discount: 5000.0
Total: 25000
Total After Discount: 20000

4. Download the Eclipse Project

This was an example about Drools Decision Tables.

Download
You can download the full source code of this example here: droolsDecisionTables.zip

Ram Mokkapaty

Ram holds a master's degree in Machine Design from IT B.H.U. His expertise lies in test driven development and re-factoring. He is passionate about open source technologies and actively blogs on various java and open-source technologies like spring. He works as a principal Engineer in the logistics domain.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Nishant
Nishant
6 years ago

KnowledgeBase is deprecated.

Above same example how it works with substitute of KnowledgeBase?

Tihamer
Tihamer
6 years ago

Knowledgebase is not just deprecated. The above code breaks at line 52:

44 private static KnowledgeBase createKnowledgeBaseFromSpreadsheet()
45 throws Exception {
46 DecisionTableConfiguration dtconf = KnowledgeBuilderFactory
47 .newDecisionTableConfiguration();
48 dtconf.setInputType(DecisionTableInputType.XLS);
49
50 KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory
51 .newKnowledgeBuilder();
52 knowledgeBuilder.add(ResourceFactory
.newClassPathResource(“shopping_cart_customer.xls”),
ResourceType.DTABLE, dtconf);

The exception is:
Exception in thread “main” java.lang.IllegalArgumentException: Unable to instantiate service for Class ‘org.drools.compiler.compiler.DecisionTableProvider’
at org.kie.internal.utils.ServiceRegistryImpl.get(ServiceRegistryImpl.java:169)
at org.drools.compiler.compiler.DecisionTableFactory.loadProvider(DecisionTableFactory.java:57)
at org.drools.compiler.compiler.DecisionTableFactory.getDecisionTableProvider(DecisionTableFactory.java:50)
at org.drools.compiler.compiler.DecisionTableFactory.loadFromResource(DecisionTableFactory.java:37)
at org.drools.compiler.builder.impl.KnowledgeBuilderImpl.decisionTableToPackageDescr(KnowledgeBuilderImpl.java:404)
at org.drools.compiler.builder.impl.KnowledgeBuilderImpl.addPackageFromDecisionTable(KnowledgeBuilderImpl.java:374)
at org.drools.compiler.builder.impl.KnowledgeBuilderImpl.addKnowledgeResource(KnowledgeBuilderImpl.java:766)
at org.drools.compiler.builder.impl.KnowledgeBuilderImpl.add(KnowledgeBuilderImpl.java:2249)
at com.javacodegeeks.drools.DecisionTableExample.createKnowledgeBaseFromSpreadsheet(DecisionTableExample.java:52)
at com.javacodegeeks.drools.DecisionTableExample.main(DecisionTableExample.java:20)

Tihamer
Tihamer
6 years ago

OK, I figured out how to make this with Drools 6.4.0. I also added a check to make sure that the rules got loaded (the function kbString). Here is the new class: public class DecisionTableExample { public static void main(String[] args) throws Exception { KieServices kServices = KieServices.Factory.get(); KieContainer kieContainer = kServices.getKieClasspathContainer(); KieSession kieSession = kieContainer.newKieSession(“ksession-dtables”); KieBase kieBase = kieSession.getKieBase(); System.out.println(“kb:” + kbString(kieBase)); Customer customer = new Customer(); Product p1 = new Product(“Laptop”, 15000); Product p2 = new Product(“Mobile”, 5000); Product p3 = new Product(“Books”, 2000); customer.addItem(p1, 1); customer.addItem(p2, 2); customer.addItem(p3, 5); customer.setCoupon(“DISC01”); FactHandle fact1 = kieSession.insert(customer); kieSession.fireAllRules(); System.out.println(“First Customer\n”… Read more »

Bruce E Tuschhoff
Bruce E Tuschhoff
5 years ago

Explain a little more
Action contains the code that applies when the condition is satisfied.
Is the following two distinct operations? If so should a semi colon separate them?
$customer.getCart().addDiscount(((double)$customer.getCart().getTotalPrice())*$param);

Also addDiscount((double) what does double denote?

Thanks

Sivakumar Ganti
Sivakumar Ganti
4 years ago

InOrder to make the droolsDecisionTables.zip work with drools 6.5.0.Final , you need to do the following changes
1. In pom.xml add these following dependencies,

org.kie
kie-api
${drools.version}

org.drools
drools-decisiontables
${drools.version}

2. Change output of the sysout to newCustomer
System.out.println(“Second Customer\n” + newCustomer);
3. shopping_cart_customer.xlsx , 2%, should be .02, change .2 to .02

Back to top button