Decision Tables are a compact way of representing conditional logic, and they can be used in Drools to define business rules. In this tutorial we will learn how to design and test them with an example.

In Drools, Decision Tables are a way to generate rules from the data entered into a spreadsheet. The spreadsheet can be a standard Excel (XLS) or a CSV File.

In a Decision table, each row is a rule and each column in that row is either a condition or action for that rule. Ideally, rules are authored without regard for the order of rows; this makes maintenance easier as rows will not need to be moved around all the time. As the rule engine processes the facts, any rules that match will fire. Here is an example of Excel using a Decision Table:

Drools decision tables example

In this spreadsheet a simple rule is included: if the Customer object's age parameter equals to "1" the Customer is allowed a discount of 15%. If the Customer's age is greater, a 25% discount is allowed.

Entries in a Rule Set area may define DRL constructs (except rules) and specify rule attributes. Entries must be given in a vertically stacked sequence of cell pairs. Here is a list of Entries that can be included in the SpreadSheet:

RuleSet: The package name for the generated DRL file. Must be First entry in the Decision Table.

Sequential: Can be "true" or "false". If "true", then salience is used to ensure that rules fire from the top down. Optional, at most once. If omitted, no firing order is imposed.

EscapeQuotes: Can be "true" or "false". If "true", then quotation marks are escaped so that they appear literally in the DRL. If omitted, quotation marks are escaped.

Import: A comma-separated list of Java classes to import. Optional, may be used repeatedly.

Variables: Declarations of DRL globals, i.e., a type followed by a variable name.

Functions: One or more function definitions, according to DRL syntax. Optional.

Queries: One or more query definitions, according to DRL syntax. Optional.

Declare: One or more declarative types, according to DRL syntax. Optional.

Building an example

In the SpreadSheet we have declared a Java Object named Customer, so we will add it into our Project:

package com.mastertheboss.model;


public class Customer {

	private int age;
	private int discount;
	private String name;

	public Customer(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getDiscount() {
		return discount;
	}

	public void setDiscount(int discount) {
		this.discount = discount;
	}

}

And now a simple Java main class to test the Rule contained in the Spreadsheet:

package com.mastertheboss;

import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;

import com.mastertheboss.model.Customer;

public class DroolsTest {

	public static final void main(String[] args) {
		try {
			KieServices ks = KieServices.Factory.get();
			KieContainer kContainer = ks.getKieClasspathContainer();
			KieSession kSession = kContainer.newKieSession("ksession-rule");

			Customer customer1 = new Customer("Frank");
			customer1.setAge(4);

			Customer customer2 = new Customer("John");
			customer2.setAge(1);

			FactHandle fact1 = kSession.insert(customer1);
			FactHandle fact2 = kSession.insert(customer2);
			
			kSession.fireAllRules();

			System.out.println("The discount for the Customer " + customer1.getName() + " is " + customer1.getDiscount());
			System.out.println("The discount for the Customer " + customer2.getName() + " is " + customer2.getDiscount());

		} catch (Throwable t) {
			t.printStackTrace();
		}
	}

}

Please notice that in this example it is assumed that a KieSession named "ksession-rule" is available in the classpath. Here is a view of the project structure which includes the kmodule.xml into the resources/META-INF folder and the spreadsheet into resources/rules:

src
├── main
│   ├── java
│   │   └── com
│   │       └── mastertheboss
│   │           ├── DroolsTest.java
│   │           └── model
│   │               └── Customer.java
│   └── resources
│       ├── Discount.xls
│       ├── META-INF
│       │   └── kmodule.xml
│       └── rules
│           └── rules.xls
└── test
    ├── java
    │   └── com
    │       └── mastertheboss
    │           └── RuleTest.java
    └── resources
        └── log4j.properties

When running the project, the expected output is:

The discount for the Customer Frank is 25
The discount for the Customer John is 15

Coding an Unit Test for our Decision Table

We can easily turn our sample project into a JUnit Test. The set up will be a bit more verbose as we will build the KieSession from an external Resource:

package com.mastertheboss;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
import org.kie.api.io.Resource;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mastertheboss.model.Customer;

public class RuleTest {
	static final Logger LOG = LoggerFactory.getLogger(RuleTest.class);
	private KieServices kieServices = KieServices.Factory.get();
	private KieSession kSession;

	@Before
	public void setup() {
		Resource dt = ResourceFactory.newClassPathResource("rules/rules.xls", getClass());
		kSession = getKieSession(dt);
	}

	public KieSession getKieSession(Resource dt) {
		KieFileSystem kieFileSystem = kieServices.newKieFileSystem().write(dt);
		KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem).buildAll();
		KieRepository kieRepository = kieServices.getRepository();
		ReleaseId krDefaultReleaseId = kieRepository.getDefaultReleaseId();
		KieContainer kieContainer = kieServices.newKieContainer(krDefaultReleaseId);
		KieSession ksession = kieContainer.newKieSession();

		return ksession;
	}

	@Test
	public void testCustomer1CorrectDiscount() throws Exception {

		Customer customer = new Customer("Frank");
		customer.setAge(4);

		kSession.insert(customer);
		kSession.fireAllRules();

		assertEquals(customer.getDiscount(), 25);
	}

	@Test
	public void testCustomer2CorrectDiscount() throws Exception {
		Customer customer = new Customer("John");
		customer.setAge(1);
		kSession.insert(customer);
		kSession.fireAllRules();

		assertEquals(customer.getDiscount(), 15);
	}

}
When running the example from the Command Line or the Ide, is it expected that both assertions pass:
Drools decision tables example

When to Use Decision Tables?

Consider using Decision Tables if your business rules exist can be expressed as rule templates and data: each row of a decision table provides data that is combined with a template to generate a rule. Many businesses already use spreadsheets for managing data. With Drools decision tables, you can also manage your business rules with spreadsheets. This assumes you are familiar to manage packages of rules in .xls or .csv files.

On the other hand, Decision tables are not recommended for rules that do not follow a set of templates or where there are a small number of rules.

You can find the source code for this tutorial on Github: https://github.com/fmarchioni/mastertheboss/tree/master/DroolsMaven/DecisionTable

0
0
0
s2smodern