A Minimal Spring Boot Drools Web Service
Posted on February 6, 2015 • 4 min read • 776 wordsA little while back, I knocked up Qzr to demonstrate using Spring Boot with the Drools rules engine. However, I also wanted to play around with a few more technologies (AngularJS and Spring HATEOAS), so it’s a bit large for just demonstrating exposing Drools rules as an HTTP web service.
A few folks found it difficult to pick out the essentials of running Drools in a Spring Boot application, so I thought I’d have a go at creating a simpler application, which does nothing more than that.
Hence, the Bus Pass Web Service
As might be guessed from the project name, for the rules, I took my cues from the Drools Bus Pass example in the Drools project. I cut the rules down a little bit and reduced the code by replacing some of the Java fact classes with DRL declared types. I prefer this for facts which are only referenced from within the DRL.
Assuming that you have a reasonably recent install of Maven and the JDK (I have tested with 8), you should be able to do the following from the command line.
Build the application:
mvn clean package
Run the application:
java -jar target/buspass-ws-1.0.0-SNAPSHOT.jar
Then send a request to the API using curl or your favourite web browser. The rules state that if you request a bus pass for a person with age less than 16, you should see a ChildBusPass. For someone 16 or over, you should see an AdultBusPass.
For example, opening http://127.0.0.1:8080/buspass?name=Steve&age=15 gives me:
{"person":{"name":"Steve","age":15},"busPassType":"ChildBusPass"}
… and opening http://127.0.0.1:8080/buspass?name=Steve&age=16 gives me:
{"person":{"name":"Steve","age":16},"busPassType":"AdultBusPass"}
The full source code is on GitHub, so that you can browse through it. I don’t intend to change it much now, other than to add a few comments. The following are some of the key features, that you should know about.
First of all, it’s a Maven project, so I hope you’re familiar with that. The following XML is extracted from the pom.xml. Note that to enable Spring Boot, I have imported the Spring platform Bill of Materials and defined spring-boot-starter-web as a dependency. By including the spring-boot-maven-plugin, the Maven build will generate an executable jar, which will run up an embedded Tomcat instance to host the web application. You don’t need to have a web server installed on your machine, to run this application.
The Drools functionality is enabled by defining kie-ci as a dependency. This brings in the Drools API, and sets up classpath scanning so that it can find the rules in your application.
<!-- Transitively bring in the Spring IO Platform Bill-of-Materials `pom.xml` --> | |
<dependencyManagement> | |
<dependencies> | |
<dependency> | |
<groupId>io.spring.platform</groupId> | |
<artifactId>platform-bom</artifactId> | |
<version>1.1.1.RELEASE</version> | |
<type>pom</type> | |
<scope>import</scope> | |
</dependency> | |
</dependencies> | |
</dependencyManagement> | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
</dependency> | |
<!-- ... --> | |
<dependency> | |
<groupId>org.kie</groupId> | |
<artifactId>kie-ci</artifactId> | |
<version>${kie.version}</version> | |
</dependency> | |
</dependencies> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-maven-plugin</artifactId> | |
<executions> | |
<execution> | |
<goals> | |
<goal>repackage</goal> | |
</goals> | |
</execution> | |
</executions> | |
</plugin> | |
</plugins> | |
</build> |
Having kie-ci in the project means that Drools will scan for rules based on certain conventions. It will look for a file called kmodule.xml in src/main/resources/META-INF/.
<?xml version="1.0" encoding="UTF-8"?> | |
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<kbase name="BusPassKbase" packages="com.sctrcd.buspassws.rules"> | |
<ksession name="BusPassSession" /> | |
</kbase> | |
</kmodule> |
The kmodule.xml
defines the package where the rules for your knowledge base can be found. Based on the definition above, it will scan for rules (.drl
files and others) in src/main/resources/com/sctrcd/buspassws/rules
. I won’t explain the rules. Feel free to go take a look at them yourself. As can be seen in the XML, this also defines a knowledge session called BusPassSession
. This means that you can now start a knowledge session like so:
KieContainer kieContainer = KieServices.Factory.get().getKieClasspathContainer(); | |
KieSession kieSession = kieContainer.newKieSession("BusPassSession"); |
The heart of a Spring Boot application is its main
class, which causes your application to be bootstrapped.
@SpringBootApplication | |
public class BusPassApp { | |
public static void main(String[] args) { | |
ApplicationContext ctx = SpringApplication.run(BusPassApp.class, args); | |
} | |
@Bean | |
public KieContainer kieContainer() { | |
return KieServices.Factory.get().getKieClasspathContainer(); | |
} | |
} |
This is standard Spring Boot stuff, but the addition we have here is to define a bean, which references the Drools KieClasspathContainer
. In doing this, we have a reference to the container, which we can inject into our application beans. This is exactly what we do with the BusPassService.
@Service | |
public class BusPassService { | |
private final KieContainer kieContainer; | |
@Autowired | |
public BusPassService(KieContainer kieContainer) { | |
log.info("Initialising a new bus pass session."); | |
this.kieContainer = kieContainer; | |
} | |
/** | |
* Create a new session, insert a person's details and fire rules to | |
* determine what kind of bus pass is to be issued. | |
*/ | |
public BusPass getBusPass(Person person) { | |
KieSession kieSession = kieContainer.newKieSession("BusPassSession"); | |
kieSession.insert(person); | |
kieSession.fireAllRules(); | |
BusPass busPass = findBusPass(kieSession); | |
kieSession.dispose(); | |
return busPass; | |
} | |
// ... | |
} |
As you can see, we are now exposing Drools functionality in our Spring Boot application. A service bean is injected with a reference to the Drools KieContainer
. Subsequently, whenever a call is made to the getBusPass
method, we instantiate a new KieSession
(note the session name, which matches that defined in kmodule.xml
), insert details about a person, fire rules, and see what kind of bus pass they should be given.
Finally, we need a controller.
@RestController | |
public class BusPassController { | |
private static Logger log = LoggerFactory.getLogger(BusPassController.class); | |
private final BusPassService busPassService; | |
@Autowired | |
public BusPassController(BusPassService busPassService) { | |
this.busPassService = busPassService; | |
} | |
@RequestMapping(value = "/buspass", | |
method = RequestMethod.GET, produces = "application/json") | |
public BusPass getBusPass( | |
@RequestParam(required = true) String name, | |
@RequestParam(required = true) int age) { | |
Person person = new Person(name, age); | |
log.debug("Bus pass request received for: " + person); | |
BusPass busPass = busPassService.getBusPass(person); | |
return busPass; | |
} | |
} |
By annotating the controller class as @RestController
, Spring will set it up as a bean and ensure that anything returned from a method is marshalled. As the getBusPass method has been defined as producing application/json
, Spring will automatically use Jackson to marshal the response to JSON.
The @RequestMapping
annotation indicates that you can reach the URL at /buspass
. For instance, if you run up the application as it is, this means that you can send GET requests to http://127.0.0.1:8080/buspass. The @RequestParam
annotations indicate that you need to send querystring arguments, providing values for “name” and “age”.
All that remains is to try it out. Please do let me know if you spot anything that you think could be improved.