drools

Dynamic drools rules

In my case it turned out that drools workbench was too much for non-tech folks. They were from operations and they felt that guided rule, guided decision tables, etc are something that won’t be comfortable with and cumbersome to work with.

Their job was to add rules and the main problem is that the number of rules was pretty high(~70 rules per project) with 30 projects. These rules should be dynamic, meaning they could change often.

Obviously I don’t need KIE server and drools workbench anymore. Furthermore since the ops were familiar with excel we’ve decided to write these dynamic rules into an excel custom format(not to be confused with drools Spreadsheet Decision Tables).

Guided Decision Table Example

Let’s try to introduce a simplified example here. We want to be able to delete some items which we specify in that excel format. Now this particular example it’s not sufficient for introducing a feature like this, since it can be easily achieved with Guided Decision Tables or Spreadsheet Decision Tables, but it will prove the point. And my particular situation was much different and more complex than this(it involved merging and average calculations). The custom excel format that we’re gonna use is.

Delete product excel model

Again this is an oversimplification. The source can be anything(csv, text) and kept anywhere(system, s3, ftp) as long we can read it.

 @Override
  public void process(Exchange exchange) throws Exception {
    RulesAndFacts rulesAndFacts = exchange.getIn().getBody(RulesAndFacts.class);
    final InputStream dynamicRulesStream = rulesAndFacts.getDynamicRulesStream();
    if (Objects.nonNull(dynamicRulesStream)) {
      Workbook workbook = new XSSFWorkbook(dynamicRulesStream);
      rulesAndFacts.setDeleteRuleData(buildDeleteData(workbook));
    }
  }

  private List<Double> buildDeleteData(Workbook workbook) {
    List<Double> deleteRuleData = new ArrayList<>();
    Sheet deleteRules = workbook.getSheetAt(0);

    deleteRules.iterator().forEachRemaining(row -> {
      if (row.getRowNum() > DATA_START_INDEX && rowHasCells(row)) {
        double productId = row.getCell(0).getNumericCellValue();
        deleteRuleData.add(productId);
      }
    });
    return deleteRuleData;
  }

The above code basically parses the excel into a list of doubles. Now in real life, you would use a structure that maps to the excel and has multiple fields. From these with the help of freemarker

package com.sergiuoltean.drools;

import java.lang.Number;

rule "Delete_${idx}"
	dialect "mvel"
	salience 18
	when
      del : ImportProduct( id == "${productId?c}")
	then
    retract( del );
end

I build the metacode that will be in the end pure drools code.

public void process(Exchange exchange) throws Exception {
    RulesAndFacts<ImportProduct> rulesAndFacts = exchange.getIn().getBody(RulesAndFacts.class);
    List<Double> deleteData = rulesAndFacts.getDeleteRuleData();
    List<String> deleteRules = deleteData.stream().map(del ->
            freemarkerEngine.process(DELETE_TEMPLATE, Map.of("productId", del, "idx", deleteData.indexOf(del)))
    ).collect(Collectors.toList());
    rulesAndFacts.setRules(deleteRules);
  }

These rules must be dynamic loaded into the rules engine. In order to achieve this I pass the list of generated drl files to the KIE container

 public KieContainer kieContainer(List<String> dynamicDrls) {
    KieServices kieServices = KieServices.Factory.get();

    KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
    kieFileSystem.write(ResourceFactory.newClassPathResource("rules/initial.drl", ENCODING));
    dynamicDrls.forEach(drl -> kieFileSystem.write("src/main/resources/" + UUID.randomUUID().toString() + ".drl",
            ResourceFactory.newReaderResource(new StringReader(drl), ENCODING)));
    KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
    kieBuilder.buildAll();
    KieModule kieModule = kieBuilder.getKieModule();
    return kieServices.newKieContainer(kieModule.getReleaseId());
  }

That’s it. The code can be found here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.