In this tutorial we will learn how to use Drools Rules (coded in a Decision Table) from a Spring Boot application.

Decision Tables are a compact way of representing conditional logic, and they can be used in Drools to define business rules. 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.

Creating the Spring Boot project

Start by creating a minimal Spring Boot project:

$ spring init -dweb drools-demo-springboot

Next, we will be adding drools dependencies to the project. Here is the full pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<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>
  <artifactId>drools-demo-springboot</artifactId>
  <name>drools</name>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>
  <properties>
    <drools-version>7.15.0.Final</drools-version>
    <apache-poi-version>3.13</apache-poi-version>

  </properties>
  <dependencies>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
 
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-decisiontables</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>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>


  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Great. The rule that we will use is based on a simple Decision Table:

 

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.

In order to make available the Drools object in Spring Boot, we will create a DroolsConfiguration class that will provide the needed KieSession, KieContainer and KieFileSystem objects:

package com.mastertheboss.drools.config;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;

public class DroolsConfiguration {

    private static final String RULES_PATH = "com/mastertheboss/drools/rules/";
    private KieServices kieServices=KieServices.Factory.get();
 
    private  KieFileSystem getKieFileSystem() throws IOException{
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        List<String> rules=Arrays.asList("rules.xls");
        for(String rule:rules){
            kieFileSystem.write(ResourceFactory.newClassPathResource(rule));
        }
        return kieFileSystem;

    }
 
    public KieContainer getKieContainer() throws IOException {
        getKieRepository();

        KieBuilder kb = kieServices.newKieBuilder(getKieFileSystem());
        kb.buildAll();

        KieModule kieModule = kb.getKieModule();
        KieContainer kContainer = kieServices.newKieContainer(kieModule.getReleaseId());

        return kContainer;

    }
 
    private void getKieRepository() {
        final KieRepository kieRepository = kieServices.getRepository();
        kieRepository.addKieModule(new KieModule() {
                        public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });
    }
 
    public KieSession getKieSession(){
        getKieRepository();
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        kieFileSystem.write(ResourceFactory.newClassPathResource("com/mastertheboss/drools/rules/rules.xls"));
        
        
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();
        KieModule kieModule = kb.getKieModule();

        KieContainer kContainer = kieServices.newKieContainer(kieModule.getReleaseId());

        return kContainer.newKieSession();

    }

}

To insert our Facts into the Kie Session, we will create a Service class that uses our DroolsConfiguration:

package com.mastertheboss.drools.service;

import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;

import com.mastertheboss.drools.config.DroolsConfiguration;
import com.mastertheboss.model.Customer;

@Service
public class CustomerService {

    private KieSession kieSession=new DroolsConfiguration().getKieSession();

    public Customer insertCustomer(Customer customer){
        kieSession.insert(customer);
        kieSession.fireAllRules();
        return  customer;

    }

}

The object Customer is merely a POJO:

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;
  }

}

That's all. Now we can autowire our CustomerService class in the main application and use it to insert some Customer objects:

package com.mastertheboss.drools;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.mastertheboss.drools.service.CustomerService;
import com.mastertheboss.model.Customer;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

@Autowired
CustomerService service;

public void run(String... args) {   
    Customer customer1 = new Customer("Frank");
    customer1.setAge(4);
    
    Customer customer2 = new Customer("John");
    customer2.setAge(1);
      
    service.insertCustomer(customer1);
    service.insertCustomer(customer2);
      
    System.out.println("Allowed discount John: " +customer1.getDiscount());
    System.out.println("Allowed discount Frank: " +customer2.getDiscount());
  }

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);

  }
}

If you run your Spring Boot application, the expected output is:

Allowed discount John: 25
Allowed discount Frank: 15

Adding a Test class for your Drool rules

Instead of using the Application class, as simple @SpringBootTest can be used instead:

package com.mastertheboss.drools.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.mastertheboss.drools.service.CustomerService;
import com.mastertheboss.model.Customer;
import org.springframework.test.context.junit4.SpringRunner;

import static junit.framework.TestCase.assertEquals;
@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomerTest {

 
   @Autowired
   CustomerService service;

   @Test
    public void testDiscount() {
    	Customer customer1 = new Customer("Frank");
    	customer1.setAge(4);
    	
    	Customer customer2 = new Customer("John");
    	customer2.setAge(1);
        
      service.insertCustomer(customer1);
      service.insertCustomer(customer2);
        
      assertEquals(25, customer1.getDiscount());
      assertEquals(15, customer2.getDiscount());
    }

 
}

You can obviously run it with:

mvn clean test

Here is the full view of our Project, which includes the main class, the configuration class, the service and the test class:

├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── mastertheboss
    │   │           ├── drools
    │   │           │   ├── config
    │   │           │   │   └── DroolsConfiguration.java
    │   │           │   ├── DemoApplication.java
    │   │           │   └── service
    │   │           │       └── CustomerService.java
    │   │           └── model
    │   │               └── Customer.java
    │   └── resources
    │       ├── com
    │       │   └── mastertheboss
    │       │       └── drools
    │       │           └── rules
    │       │               └── rules.xls
    │       └── logback.xml
    └── test
        └── java
            └── com
                └── mastertheboss
                    └── drools
                        └── service
                            └── CustomerTest.java