A 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.
Before we continue, I should probably mention that you can grab the source code here:
https://github.com/gratiartis/buspass-ws
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, but I think 7 should be okay), 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!– 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/.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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.
Thanks
LikeLike
Many thanks for this example, it is very easy to understand and a great help to us.
I have a question and maybe you know the answer:
– When I tried to configure this project with decision tables, I received and exception with the message: “No RuleTable cells in spreadsheet.” Decision tables need the configuration from the XLS and the DRL file, and here I found a problem:
I am very newbie with this matter, but debugging I found at AbstractKieModule (v6.5.0.Final) at line 335 “if (template.getDtable().equals( fileName )) {” where it is comparing the configured path against the fileName recovered from classpath. The problem here is with the Decision tables where one is loaded from the classpath resolution but the other is loaded from configuration: the first one has “BOOT-INF/classes/” and the other haven’t causing it cannot load rules because fileName and template.getDtable() are not the same.
Do you know if there is any extra configuration to fix this? (I resolved this with a trick: adding twice the template configuration once with the “BOOT-INF/classes” and another one without this part, because I need it works when run from an IDE (normal classpath loaded) or from Spring Boot (jar packaged with BOOT-INF/classes)
Thanks a lot again by the example!
LikeLike
Hi there Victor. Apologies for not replying yet – I’ve been away skiing for the past week. 🙂
If you put all the .drl, .xls, .gdst, etc. under src/main/resources/com/mycompany/mypackage the packages can be defined in the kbase XML definition. This enables the kmodule to be created for you by convention.
If you wish to load in DRL/Excel that isn’t stored in locations which meet the convention, then I think you will need to look at creating your ksession programmatically instead. See “4.2.2.4. Defining a KieModule programmatically” in the Drools user guide: https://docs.jboss.org/drools/release/6.5.0.Final/drools-docs/html_single/index.html#BuildDeployUtilizeAndRunSection
LikeLike
What did you do exactly? Did you place the kmodules.xml in the BOOT-INF/classes with maven-resources-plugin?
I have the same issue although with data tables (XLS) and templates (DRT) so I need to define them in the kmodules.xml
LikeLike
I put those files in src/main/resources. The source code is all there in GitHub for you to take a look.
LikeLike
A quickly (and a little dirty) work around is to duplicate the configuration one for the packed application (Sprint Boot) and one for local development:
With this trick the rule engine loads the configuration and can execute it.
Another way is what @sctrcdr said before (this is the cleanest way), make an implementation or look for already implemented KieModule “loader”. I think I checked one but it doesn’t working properly with excel tables (See “4.2.2.4. Defining a KieModule programmatically” in the Drools user guide: https://docs.jboss.org/drools/release/6.5.0.Final/drools-docs/html_single/index.html#BuildDeployUtilizeAndRunSection)
Best regards!
LikeLiked by 1 person
Ups! Source code was removed sorry. I’m trying again removing special characters (let me know if you can understand):
— Definition of business rules–
kbase name=”DefaultKB” packages=”core.rules.businessRules”
— Normal load —
ruleTemplate
dtable=”/core/rules/businessRules/GenericPolicy.xls”
template=”/core/rules/businessRules/GenericBusinessRules.drt”
row=”3″ col=”2″
— Trick to load from a Spring Boot packaged —
ruleTemplate
dtable=”BOOT-INF/classes/core/rules/businessRules/GenericPolicy.xls”
template=”BOOT-INF/classes/core/rules/businessRules/GenericBusinessRules.drt”
row=”3″ col=”2″
ksession name=”DefaultKS”
kbase
LikeLiked by 1 person
Thanks again! Then a good week skiing 😉
I resolved it: all excels are loaded although they are not defined into the kbase XML, so I removed all not used (it is not my expected behavior.
Your instructions are very helpfully and I think I can take advantage of them in the future.
Thanks a lot!
LikeLike
Should we install KIE execution server to make use of REST call ?
LikeLike
Doing it this way with Spring, means that you do not need the execution server. That’s a different way to set up a REST API.
LikeLike
This example is by far one the comprehensive examples I have seen on implementation of executing drools rules to make REST calls.Thanks a lot..
I am trying a similar project with jBPM process engine by creating a workflow with BPMN2 containing 3 REST tasks to be called at runtime.. First REST task is an “Authentication service” and then second REST task retrieves customer info when the web services is called. The third performs a “Funds Transfer service”…
I would like to know if you have some knowledge in integrating all these REST service with the process engine for execution??
LikeLike
Hi Frederick. So far, I haven’t made use of the process engine, so I’m not going to be able to help you with that one. If you haven’t tried it already, I would recommend sending a message to the jBPM Usage Google Group, where you might have more luck: https://groups.google.com/forum/#!forum/jbpm-usage
LikeLike