Spring Boot integrated rule engine Drools

A rules engine serves as a pluggable software component that implements a method of business rules that have been externalized or separated from the application code. This externalization or separation allows business users to modify the rules without modifying the application programs. In the case of large distributed applications dealing with complex logic, a rules engine can be used to isolate the application code from common rule logic that can be commonly applied across distributed platforms. This greatly improves the flexibility and stability of the system.

In this article, we’ll build a Spring Boot sample application to demonstrate the use of the Drools rules engine to determine the outcome of various inputs. The application simulates a recruitment scenario. The rule engine decides whether to hire or not based on data such as the age, work experience, and skill level of the candidate. If hired, a salary data will be generated.

Drools rule engine (https://www.drools.org/) is JBoss’ open source rule engine implementation.

The KIE project (https://www.kie.org/about/) allows integrating the Drools Rules engine with Spring Boot.

App structure:

├─src
│ ├─main
│ │ ├─java
│ │ │ └─demo
│ │ │ └─drools
│ │ │ │ SpringbootDroolsApplication.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ DroolsConfig.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ DroolsController.java
│ │ │ │
│ │ │ ├─model
│ │ │ │ Candidate.java
│ │ │ │ Enroll.java
│ │ │ │
│ │ │ └─service
│ │ │DroolsService.java
│ │ │
│ │ └─resources
│ │ │ application.properties
│ │ │
│ │ └─rules
│ │ recruitment_salary.drl

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>demo</groupId>
<artifactId>springboot-drools</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-drools</name>
<description>Demo project for Spring Boot with Drools</description>
<properties>
<java.version>17</java.version>
<drools.version>8.41.0.Final</drools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
<version>${drools.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

SpringbootDroolsApplication.java

package demo.drools;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootDroolsApplication {

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

}

DroolsConfig.java

package demo.drools.config;

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.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DroolsConfig {
 
 private static final KieServices kieServices = KieServices.Factory.get();
 private static final String RULES_CUSTOMER_RULES_DRL = "rules/recruitment_salary.drl";
 
   @Bean
     public KieContainer kieContainer() {
         KieFileSystem kieFileSystem = kieServices. newKieFileSystem();
         kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
         
         KieBuilder kieBuilder = kieServices. newKieBuilder(kieFileSystem);
         kieBuilder.buildAll();
         
         KieModule kieModule = kieBuilder. getKieModule();
         KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
         
         return kieContainer;
     }
}

We define Drools rules as files and place them in the /src/main/resources/rules directory.

recruitment_salary.drl

import demo.drools.model.Candidate;
global demo.drools.model.Enroll enroll;

dialect "mvel"

rule "Checking Work Experience"
when
Candidate(workExperience < 2)
then
enroll.setEnrollStatus("Rejected - Not enough working experience");
enroll.setEnrollSalary(-1);
end

rule "Checking age less than 65"
when
Candidate(age >= 65)
then
enroll.setEnrollStatus("Rejected - Over the age limit for this job");
enroll.setEnrollSalary(-1);
end

rule "Checking Skill Level less than 4"
when
Candidate((skillLevel < 4 & amp; & amp; skillLevel >= 1), workExperience >= 2, age < 65)
then
enroll.setEnrollStatus("Approved");
enroll.setEnrollSalary(30000);
end

rule "Checking Skill Level less than 8"
when
Candidate((skillLevel < 8 & amp; & amp; skillLevel >= 4), workExperience >= 2, age < 65)
then
enroll.setEnrollStatus("Approved");
enroll.setEnrollSalary(50000);
end

rule "Checking Skill Level great than 8"
when
Candidate(skillLevel >= 8, workExperience >= 2, age < 65)
then
enroll.setEnrollStatus("Approved");
enroll.setEnrollSalary(100000);
end

The following rules are defined in the rules file:

  • Candidates with less than 2 years of work experience will not be hired
  • If the candidate’s age is greater than or equal to 65 years old, he will not be hired
  • If the candidate’s skill level is between 1 (inclusive) and 4, and the age is less than 65, he will be hired, and the salary will be set at 30000
  • If the candidate’s skill level is between 4 (inclusive) and 8, and the age is less than 65, he will be hired, and the salary will be set at 50,000
  • If the candidate’s skill level is greater than 8 (inclusive) and his age is less than 65, he will be hired and his salary will be set at 100,000

Candidate.java

package demo.drools.model;

import lombok.Data;

@Data
public class Candidate {
private String name;
private int age;
private int workExperience;
private long skillLevel;
}

Enroll.java

package demo.drools.model;

import lombok.Data;

@Data
public class Enroll {
private String enrollStatus;
private int enrollSalary;
}

DroolsService.java

package demo.drools.service;

import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import demo.drools.model.Candidate;
import demo.drools.model.Enroll;

@Service
public class DroolsService {
@Autowired
private KieContainer kieContainer;

public Enroll getEnroll(Candidate applicantRequest) {
Enroll enroll = new Enroll();
KieSession kieSession = kieContainer. newKieSession();
\t\t
kieSession.setGlobal("enroll", enroll);
kieSession.insert(applicantRequest);
kieSession.fireAllRules();
kieSession.dispose();
\t\t
return enroll;
}

}

DroolsController.java

package demo.drools.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import demo.drools.model.Candidate;
import demo.drools.model.Enroll;
import demo.drools.service.DroolsService;

@RestController
@RequestMapping("/recruitservice")
public class DroolsController {
@Autowired
private DroolsService recruitService;

@PostMapping("/enroll")
public ResponseEntity<Enroll> getEnroll(@RequestBody Candidate request) {
Enroll enroll = recruitService. getEnroll(request);
\t\t
return new ResponseEntity<>(enroll, HttpStatus.OK);
}
}

Controller receives a Candidate object data, then triggers the rules defined in recruitment_salary.drl through DroolsService, and finally returns an Enroll object data.

Work process:

Test:

Start the SpringBoot application, execute the test, and use Postman to send a post request to the URL: http://localhost:8080/recruitservice/enroll.

1. Work experience less than 2 years

2. Age greater than 65

3. Age less than 65 years old, work experience greater than 2 years, skill level greater than 1 but less than 4

4. Age less than 65 years old, work experience greater than 2 years, skill level greater than 4 and less than 8

5. Age less than 65 years old, work experience greater than 2 years, skill level greater than 8