Create a simple rule engine with Spring Boot and Camunda DMN
Published On: 2019/04/30
In this blog post we will focus on setting up a simple rule engine using spring boot and camunda DMN engine.
Spring boot is a wrapper project on top of the Spring framework which adds the required dependencies and auto configure all the features you want to start the application. In the world of micro services Spring boot saves your time to setup a project.
DMN, Decision Model and Notation, is a standard published by OMG which provides modeling notation for decision that will support business decision management and business rules.
There are many provides of DMN engine. In this article we will use Camunda DMN engine to setup our cloud message charging rules project.
Maven Configuration
Create a project with Spring Initializr so that you will get a basic setup of the project. Add the camunda engine dependency to the project.
<dependency>
<groupId>org.camunda.bpm.dmn</groupId>
<artifactId>camunda-engine-dmn</artifactId>
</dependency>
Rule Engine API Objects
Create the model objects which holds the values of rule criteria and result
RuleCriteria.java
public abstract class RuleCriteria {
protected Map<String,Object> contextMap=new HashMap<>();
public abstract Map<String,Object> contextMap();
}
public class RuleResult<T> {
private String ruleCode;
private T result;
}
MessageChargingCriteria.java – View full code here
public class MessageChargingCriteria extends RuleCriteria{
private final String LICENSE_TYPE="licenseType";
private final String MESSAGE_COUNT="messageCount";
private final String licenseType;
private final long messageCount;
public MessageChargingCriteria(String licenseType, long messageCount) {
this.licenseType = licenseType;
this.messageCount = messageCount;
}
}
public class MessageChargeResult extends RuleResult<Double> {
public MessageChargeResult(String ruleCode, Double result) {
super(ruleCode, result);
}
}
public MessageChargeResult executeMessageChargingRule(String code, MessageChargingCriteria criteria);
Rule Engine Implementation
This dmn is loaded into a Rule Registry when the rule engine starts. This registry has a concurrent hash map to hold the dmn decision objects.
private InputStream ruleFileStream(String rulePath){
Path path = Paths.get(rulePath);
if(Files.isReadable(path)){
try{
return Files.newInputStream(path, StandardOpenOption.READ);
}catch (IOException ioe){
throw new RuleEngineException("Error while reading the DMN file definition from path: "+rulePath,ioe);
}
}
return this.getClass().getResourceAsStream(rulePath);
}
private DmnDecision createDmnDecision(String ruleCode){
String rulePath=new StringBuilder(this.rulesRootPath).append("/").append(ruleCode).append(".dmn").toString();
InputStream input = ruleFileStream(rulePath);
return this.dmnEngine.parseDecision(ruleCode,input);
}
private void register(String ruleCode){this.registry.put(ruleCode,createDmnDecision(ruleCode));}
MessageChargingRuleExecutor.java extends RuleExecutor
public class MessageChargingRuleExecutor extends RuleExecutor<MessageChargeResult>{
public MessageChargingRuleExecutor(final RuleRegistry registry,final DmnEngine dmnEngine) {
super(registry,dmnEngine);
}
@Override
public MessageChargeResult execute(DmnDecision decision, Map<String, Object> contextMap) {
try {
final DmnDecisionTableResult tableResult = this.getDmnEngine().evaluateDecisionTable(decision, contextMap);
final DmnDecisionRuleResult ruleResult = tableResult.getSingleResult();
final Double charge = (Double) ruleResult.get("charge");
return new MessageChargeResult( decision.getKey(),charge);
} catch (NullPointerException npe) {
return new MessageChargeResult(decision.getKey(),0d);
}
}
}
public class RuleEngineImpl implements RuleEngine {
private final MessageChargingRuleExecutor messageChargingRuleExecutor;
public RuleEngineImpl(MessageChargingRuleExecutor messageChargingRuleExecutor){
this.messageChargingRuleExecutor=messageChargingRuleExecutor;
}
@Override
public MessageChargeResult executeMessageChargingRule(String code, MessageChargingCriteria criteria) {
return this.messageChargingRuleExecutor.execute(code,criteria);
}
}
Conclusion
In this article, we looked at the setup of a simple rules projects using camunda dmn engine and spring boot. The code for this article can be found in the cloudmessage fabric Github repository.