3 min read

Micronaut ft. Flowable - Rule engine validation

Micronaut ft. Flowable - Rule engine validation
#micronaut #flowable #gradle
Flowing through business rules validation. (Example made using Micronaut, Gradle and Flowable engine using H2 local database)

Brief

We need to create an application that will run an input payload by a DMN decision table and return the decision result.

The DMN standard provides the industry with a modeling notation for decisions that will support decision management and business rules. The notation is designed to be readable by business and IT users alike.

More info about DMN features can be found here: https://camunda.com/dmn/

Enter Flowable.

Finding

Flowable DMN is an embeddable Java engine that can run input payload through decision tables and return its result.

Moreover, it has a well documented REST API that can be used to get more in-depth details, that the Java engine may not supply: https://wwv.flowable.com/open-source/docs/dmn/ch07-REST/

Flowable also has a web interface that you can use running the Docker image provided here: https://hub.docker.com/r/flowable/all-in-one. You can create and update decision tables using the Modeler service: http://localhost:8080/flowable-modeler. The created decision tables can also be exported to dmn files.

Setup

  • Bring in Flowable engine and H2 database
dependencies {
    implementation "org.flowable:flowable-engine:6.6.0"
    implementation "org.flowable:flowable-dmn-engine:6.6.0"
    implementation group: 'com.h2database', name: 'h2'
}
build.gradle
  • Create a simple DMN table.

(This example has a single rule that includes two inputs and one output).

<definitionsid="definition_1" name="Rules" namespace="http://www.flowable.org/dmn">
  <decision id="RULES" name="Rules">
    <decisionTable id="decisionTable_1" hitPolicy="FIRST">
      <input name="input1" >
        <inputExpression id="inputExpression_1" typeRef="string">
          <text>input1</text>
        </inputExpression>
      </input>
      <input name="input2" >
        <inputExpression id="inputExpression_2" typeRef="string">
          <text>input2</text>
        </inputExpression>
      </input>
      <output id="outputExpression" name="output1" typeRef="string"></output>
      <rule>
        <inputEntry id="inputEntry_1_1">
          <text><![CDATA[== "INPUT1"]]></text>
        </inputEntry>
         <inputEntry id="inputEntry_2_1">
          <text><![CDATA[== "INPUT2"]]></text>
        </inputEntry>
        <outputEntry id="outputEntry_1_1">
          <text><![CDATA["OUTPUT1"]]></text>
        </outputEntry>
      </rule>
    </decisionTable>
  </decision>
  <dmndi:DMNDI></dmndi:DMNDI>
</definitions>
resources/rules.dmn
  • Add to the application.yml the database connection and the rule file location so it can be easily changed when moving to a managed database or changing the decision table.
flowable:
  datasource:
    url: jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000
  rules: rules.dmn
application.yml
  • Define a bean of DmnDecisionService that can be then injected in a controller or a service.
@Factory
@RequiredArgsConstructor
public class FlowableConfiguration {

    @Value("${flowable.rules}")
    private String ruleFile;

    @Value("${flowable.datasource.url}")
    private String datasourceUrl;

    @Bean
    public DmnDecisionService dmnDecisionService() {
        return dmnEngine().getDmnDecisionService();
    }

    private DmnEngine dmnEngine() {
        DmnEngine dmnEngine = new StandaloneDmnEngineConfiguration()
                .setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_TRUE)
                .setJdbcUrl(datasourceUrl) // provide connection to database
                .setStrictMode(false)
                .buildDmnEngine();

        dmnEngine.getDmnRepositoryService()
                .createDeployment()
                .addClasspathResource(ruleFile) // provide decision table
                .deploy();
        return dmnEngine;
    } 
}
FlowableConfig.java

Usage

The most straightforward explanation of how it works is: we have a set of rules defined as rows in a decision table. When querying the table, if any rule matches the data supplied, Flowable will mark the rule as true and return the rule's output.

Depending on the hitPolicy property defined in the decision table, we can decide if we want to return only the first rule hit, or all of those marked true. More info here: https://wwv.flowable.com/open-source/docs/dmn/ch06-DMN-Introduction/

The Flowable engine requires as an input:

  • decision table key ( <decision id="RULES" name="Rules">)
  • variables ( map of key and value inputs )
@Singleton
@RequiredArgsConstructor
public class FlowableService {

    private final DmnDecisionService dmnDecisionService;  //inject defined bean

    public List<Map<String, Object>> queryFlowable(Map<String, Object> flowableQuery, String decisionKey) {
        return dmnDecisionService
                .createExecuteDecisionBuilder()
                .decisionKey(decisionKey) // decision table key
                .variables(flowableQuery) // map of column name and value
                .executeDecision();
    }
}

The result of the decision will be returned in the form of a list of maps of key and value outputs.

In our case:

  • decisionKey: 'RULES'
  • input variables: <<input1, INPUT1>, <input2, INPUT2>>
  • output: [<output1, OUTPUT1>]

💡
Don't miss out on more posts like this! Susbcribe to our free newsletter!
💡
Currently I am working on a Java Interview e-book designed to successfully get you through any Java technical interview you may take.
Stay tuned! 🚀