Drools With Spring Boot 3
Drools with Spring
Here I am going to show you an example how to use Drools with Spring Boot 3 framework.
If you want to learn what is Drool then you can read the tutorial how to integrate drools in Spring application.You can also check an example on how to integrate Drools with Spring Boot 2 framework.
Prerequisites
Java 19, Drools, 7.74.1, maven 3.9.8
Project Setup
Create maven based project in your favorite IDE or tool. The following pom.xml file can be used for your maven based project:
<?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>
<groupId>com.roytuts</groupId>
<artifactId>spring-boot-3-drools</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.74.1.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.74.1.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> Drools Configuration
I want to use Java based configurations so create the following Java class for the required configurations. I will put the rule file (ends with .drl) under src/main/resources/rules directory.
I have defined few beans for the Spring and Drools integration.
I have created KieFileSystem bean that finds all the rule files put under the classpath file system. These rule files define the rules, which would be applied on the object fields for validation.
I have defined KieContainer bean which is used to create different components, such as, KieSession that is required to create before fire rules on the drools file.
package com.roytuts.springboot3.drools.config;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
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.Message;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.Results;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
@Configuration
public class DroolsConfig {
private static final String RULES_PATH = "rules/";
@Bean
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
for (Resource file : getRuleFiles()) {
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(),
StandardCharsets.UTF_8.name()));
}
return kieFileSystem;
}
private Resource[] getRuleFiles() throws IOException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
}
@Bean
public KieContainer kieContainer() throws IOException {
final KieRepository kieRepository = getKieServices().getRepository();
kieRepository.addKieModule(new KieModule() {
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem()).buildAll();
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
System.out.println(results.getMessages());
throw new IllegalStateException("### errors ###");
}
return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
}
private KieServices getKieServices() {
return KieServices.Factory.get();
}
} Exception Class
I will create custom exception class that will throw exception when the validation fails. I will create ValidationException class to throw validation exception for null or empty or invalid value.
package com.roytuts.springboot3.drools.exception;
public class ValidationException extends RuntimeException {
private static final long serialVersionUID = 8263652792230400320L;
public ValidationException(String msg) {
super(msg);
}
public ValidationException(Throwable t) {
super(t);
}
public ValidationException(String msg, Throwable t) {
super(msg, t);
}
} Rule File
Create a rule file EmployeeValidation.drl under src/main/resources/rules.
First line declares the package for the rule file. Then I have imported the required Java classes.
Next I have defined various rules and throw Exception accordingly.
You will find Employee in “when” clause is the object and fields such as name, email, phone etc. are accessed directly by their property names in the Employee class.
package rules
//list any import classes here
import com.roytuts.springboot3.drools.dto.Employee;
import com.roytuts.springboot3.drools.exception.ValidationException;
rule "EmployeeValidation"
when
Employee(id == 0 || name == null || email == null || phone == 0 || name.trim().length == 0 || email.trim().length == 0
|| address.street == null || address.city == null || address.state == null || address.zip == 0 || address.country == null
|| address.street.trim().length == 0 || address.city.trim().length == 0 || address.state.trim().length == 0 || address.country.trim().length == 0)
then
throw new ValidationException("id, name, email, phone, street, city, state, zip and country are required fields");
end
rule "EmployeeNameValidation"
when
Employee(name != null && name not matches "(\\b[A-Z]{1}[a-z]+)( )([A-Z]{1}[a-z]+\\b)")
then
throw new ValidationException("name should contain only letter and space");
end
rule "EmployeeEmailValidation"
when
Employee(email != null && email not matches "^[A-Za-z0-9!#%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$")
then
throw new ValidationException("Invalid email address");
end
rule "EmployeePhoneValidation"
when
Employee(phone != 0 && phone not matches "^[0-9]{10}$")
then
throw new ValidationException("Invalid phone number");
end
rule "EmployeeZipValidation"
when
Employee(address.zip != 0 && address.zip not matches "^[0-9]{6}$")
then
throw new ValidationException("Invalid zip number");
end Service Class
The following service class process the business logic for the application.
package com.roytuts.springboot3.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 com.roytuts.springboot3.drools.dto.Employee;
@Service
public class EmployeeService {
@Autowired
private KieContainer kieContainer;
public void saveEmployee(Employee employee) {
KieSession kieSession = kieContainer.newKieSession();
kieSession.insert(employee); // which object to validate
kieSession.fireAllRules(); // fire all rules defined into drool file (EmployeeValidation.drl)
kieSession.dispose();
// once validation passed, save employee object to database
}
} REST Controller
The Spring REST controller that handles end users requests/responses is given below.
If the employee object validation fails then you will see exception in the console and Internal Server Error (500) on the web page or REST client.
If the validation passes then the response with status code 200 will be returned.
package com.roytuts.springboot3.drools.rest.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.RestController;
import com.roytuts.springboot3.drools.dto.Employee;
import com.roytuts.springboot3.drools.service.EmployeeService;
@RestController
public class EmployeeRestController {
@Autowired
private EmployeeService employeeService;
@PostMapping("/employee/save")
public ResponseEntity<String> storeEmployeeInfo(@RequestBody Employee employee) {
employeeService.saveEmployee(employee);
return new ResponseEntity<>(HttpStatus.OK);
}
} Spring Boot Main Class
A class is having a main method with @SpringBootApplication is enough to deploy the Spring Boot application into embedded Tomcat server.
package com.roytuts.springboot3.drools;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
} Testing Drools with Spring Boot 3
Run the above main class to deploy the application into the embedded Tomcat server and start the application.
Once the application deployed, it starts on port number 8080 as it is a default port of Tomcat. You may also override this port number in application.properties or application.yml file using key server.port.
You can use any REST client tool to test the REST endpoint. I am using a tool called Postman to test the application.
URL: http://localhost:8080/employee/save
Method: POST
Request Body:
{
"id" : 0,
"name" : "",
"email" : "",
"phone" : 0,
"address" : {
"flatNo" : "",
"street" : "",
"city" : "",
"state" : "",
"zip" : 0,
"country" : ""
}
} Response:
{
"timestamp": "2024-09-26T08:32:17.572+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/employee/save"
} The actual error message you will find in the console as follows:
[dispatcherServlet] in context with path [] threw exception [Request processing failed: Exception executing consequence for rule "EmployeeValidation" in rules: com.roytuts.springboot3.drools.exception.ValidationException: id, name, email, phone, street, city, state, zip and country are required fields] with root cause
com.roytuts.springboot3.drools.exception.ValidationException: id, name, email, phone, street, city, state, zip and country are required fields If you change the value for the Request Body as below in the above request:
{
"id" : 1,
"name" : "Soumitra Roy",
"email" : "soumitra.sarkar@email.com",
"phone" : 1234567890,
"address" : {
"flatNo" : "",
"street" : "Street",
"city" : "City",
"state" : "State",
"zip" : 111111,
"country" : "India"
}
} You will see the response as 200 OK.
No comments
Leave a comment