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:
In your pom.xml
, you need to add the below dependencies:
knowledge-api
– this provides the interfaces and factoriesdrools-core
– this is the core engine, runtime component. This is the only runtime dependency if you are pre-compiling rules.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.
- RuleSet defines the package
- Import specifies the used classes, including static imported functions
- Notes can be any text
The second section starts with ‘RuleTable’.
- ‘RuleTable’ denotes the start of the decision table.
- It groups rules that operate on the same domain object and conditions.
- The next line defines column types.
- The column types are: NAME, CONDITION and ACTION
- You will use NAME to specify a rule name. If you don’t specify a name, it gets auto-generated.
- CONDITION defines the rule condition
- ACTION is a rule action.
In the next section, we declare the shared objects.
- Our shared object here is
$customer:Customer
. - It exposes the
Customer
object as a$customer
variable. - 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).
coupon
gets converted tocustomer.getCoupon()
isNew
gets converted tocustomer.getIsNew()
- Action contains the code that applies when the condition is satisfied.
$customer.getCart().addDiscount(((double)$customer.getCart().getTotalPrice())*$param);
$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.
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.
You can download the full source code of this example here: droolsDecisionTables.zip
KnowledgeBase is deprecated.
Above same example how it works with substitute of KnowledgeBase?
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)
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 »
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
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