Working with drools using excel sheet decision table — part-3 (High Performance StatelessKieSession)

Paras Bansal
3 min readNov 12, 2021

part-2 link— https://paras301.medium.com/working-with-drools-using-excel-sheet-decision-table-part-2-52936db848fb

This part I’ll use Springboot to further improve the performance of drools StatelessKieSession. If you go to the last part, the problem was that we were creating drools StatelessKieSession on each request. So even though we had compiled rules at the boot, creation of KieSession was taking long time.

I’ll be testing with 10,000 rules.

There are a few changes I made to the application for this:

  1. Instead of using generic type JSON input parsing, I am using springboot auto-mapping to object. So I got rid of JSONNode as per previous part example and now I am directly using Customer object. It is purely your choice how to architect your application in this case ( you can still continue to use the JSONNode type parsing of input)
  2. Instead of keeping spreadsheet outside the application jar, I am packaging it inside. Well the idea is to compile the rules during the boot time itself, so now if you want to change rules at the run time, it can’t happen. You can still keep the spreadsheets outside the application jar, but you’ll have to restart the application once if there are any changes to the rules.

So the new rules compiler now looks like this:

package com.company.service;import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.StatelessKieSession;
import org.kie.internal.utils.KieHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Service;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
@Getter
public class RulesCompiler {
ResourcePatternResolver resourcePatternResolver;
private StatelessKieSession kSession;
public RulesCompiler(@Autowired ResourcePatternResolver resourcePatternResolver) {
this.resourcePatternResolver = resourcePatternResolver;
List<String> rulesList;
try {
Resource[] resources = resourcePatternResolver.getResources("classpath:rules/*.xls");
rulesList = new ArrayList<>();
for(Resource r : resources) {
log.info("Compiling resource file --> " + r.getFilename());
InputStream is = getClass().getResourceAsStream("/rules/" + r.getFilename());
SpreadsheetCompiler sc = new SpreadsheetCompiler();
String rules=sc.compile(is, InputType.XLS);
log.info("Compiled drools file == " + rules);
rulesList.add(rules);
}
KieHelper kh = new KieHelper();
rulesList.forEach(r -> kh.addContent(r, ResourceType.DRL));
kSession = kh.build().newStatelessKieSession();} catch (Exception e) {
log.error("Error while reading the resource", e);
}
}
}

See here, we are trying to get all the spreadsheets in the resources folder, compile them and build the StatelessKieSession in the constructor itself. So now, if we autowire this RulesCompiler, Spring will ensure that the bean is created during the application boot (which means rules compilation and keeping the StatelessKieSession ready).

package com.company.service;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.command.Command;
import org.kie.api.runtime.ClassObjectFilter;
import org.kie.api.runtime.ExecutionResults;
import org.kie.api.runtime.StatelessKieSession;
import org.kie.internal.command.CommandFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.company.vo.Customer;@Slf4j
@Service
public class RulesProcessingService {

@Autowired
RulesCompiler rulesCompiler;
public Customer applyRules(Customer req) {

StatelessKieSession kSession = rulesCompiler.getKSession();
log.info("Executing rules ...");List<Command> cmds = new ArrayList<>();
cmds.add(CommandFactory.newInsert(req));
cmds.add(CommandFactory.newFireAllRules());
cmds.add(CommandFactory.newGetObjects(new ClassObjectFilter(Customer.class), "output"));
ExecutionResults results = kSession.execute(CommandFactory.newBatchExecution(cmds));List<Customer> data_out = (List<Customer>) (Collection<?>) results.getValue("output");log.info("rules output = " + data_out);return data_out.get(0);
}
}

This approach significantly reduced the response time. For a spreadsheet of 10,000 rules, from seconds, we are reduced to 10–12ms for each request. Here are the stats:

Boot time of application increased from 5secs to 23 seconds

2021-11-12 15:20:50.527  INFO 7560 --- [           main] com.company.application.Application      : Started Application in 22.555 seconds (JVM running for 23.347)

First request took around 450ms

Subsequent requests took 10–12 ms

The only disadvantage of this setup is that you can’t change your rules at the run the time of your application and you’ll need to plan for a downtime.

There are too many advantages though:

  1. Any compile time issues you can catch during the boot itself
  2. There is no overhead of compilation of rules with each request
  3. Its fast (even with 10K rules, on my local machine, the response is just 10ms)

That’s all folks. Please provide your feedback and leave comments.

--

--