dmn-flowable

Business Rules in Java met DMN en Flowable

Ontdek hoe DMN (Decision Model and Notation) toelaat om business rules in beslissingstabellen te modeleren, en deze ook direct uit te voeren, zonder alles om te zetten in Java code.  Op deze manier blijft je software inzichtelijk voor iedereen - ook zonder IT-technische achtergrond - én win je een hoop tijd!  We brengen het in de praktijk in een Spring Boot applicatie met Flowable als open source BPM tool.

toc-banner

Over Business Rules In Software
    Een voorbeeld
    Implementatie in Java Code
    Implementatie in een DMN Beslissingstabel
Een DMN demo met Spring Boot en Flowable
    Flowable Modeler
    Flowable DMN integratie in Spring Boot
DMN modellen en je DTAP ontwikkelstraat
DMN verder verkennen
Conclusie

Over business rules in software

Elke applicatie bevat business rules: regels die bepalen wat er moet gebeuren in bepaalde omstandigheden of bij bepaalde invoer.  Vaak zijn deze regels op de meest onvoorspelbare momenten aan wijzigingen onderhevig door nieuwe beslissingen, wetgeving, nieuwe inzichten, ...
Uiteraard wil je hier zo rationeel mee omgaan in het wijzigingsbeheer van je software.  Als je je codebase ietwat handig structureert dan voorzie je één plaats waar je die business rules implementeert.  Feit blijft dat je een omzetting van uitgeschreven regels naar code moet doen, die ook de nodige testing laten ondergaan, enz... wat zowel een zekere workload, doorlooptijd én risico introduceert.

Een handigere manier om hier mee om te gaan is om gebruik te maken van Decision Tables volgens de DMN (Decision Model and Notation) standaard.  Deze laat toe om op een niet-IT-technische manier business rules te specifiëren, en deze direct uit te voeren.
Helemaal in lijn met de low-code en citizen developer trends.

Een voorbeeld

In dit eenvoudige voorbeeld gaan we aan de slag met regels rond BTW percentages.  Onze imaginaire business verkoopt vanuit België allerlei producten aan particulieren en bedrijven in binnen- en buitenland.  Afhankelijk van het product dat verkocht wordt en de klant wordt een BTW percentage vastgesteld dat op de facturen moet gehanteerd worden.

Het huidige zicht op de business rules is als volgt:

  • Op alle producten moeten we in principe 21% BTW rekenen
  • Een uitzondering daarop zijn de boeken die we verkopen, daarop moet 6% aangerekend worden
  • Voor bedrijven buiten België maar binnen de EU werken we met intracommunautaire leveringen, met 0% BTW tot gevolg - let wel: dit geldt dus niet voor particulieren!

Te verwachten is dat deze regels ooit uitgebreid of aangepast zullen worden in de toekomst.  De vraag is nu hoe we deze best in onze software verwerken.

Implementatie in Java code

De meest voor de hand liggende en ook meest gebruikte techniek om business rules in software te verwerken, is om deze op dezelfde manier te beschouwen als elke andere requirement, en de rules te implementeren in code.

Specificaties van business rules kunnen variëren van excel-sheets tot stukjes proza of een bullet-lijst (zoals in ons voorbeeld), van effectieve regels tot een oplijsting van voorbeelden.  Als ontwikkelaar ga je hiermee dan aan de slag om de nodige beslissingen te automatiseren, en als tester maak je een zo volledig mogelijk testplan op dat alle nodige variaties afdekt.

Ons eenvoudig voorbeeld zou dan ook in code vertaald kunnen worden als volgt:

[src/main/java/com/xti/demo/dmn/dmndemo/JavaDemoApplication.java]package com.xti.demo.dmn.dmndemo;

public class JavaDemoApplication {

    public static void main ( String args[] ) {
        new JavaDemoApplication().demo();
    }

    public void demo ( ) {
        decide ("boek", "bedrijf", "non-EU");
        decide ("boek", "bedrijf", "EU");
        decide ("boek", "particulier", "EU");
        decide ("boek", "bedrijf", "BE");
        decide ("boek", "particulier", "BE");
        decide ("laptop", "particulier", "BE");
        decide ("laptop", "bedrijf", "BE");
        decide ("laptop", "bedrijf", "EU");
        decide ("laptop", "bedrijf", "non-EU");
    }

    public void decide ( String product, String klant, String land ) {
        Double btw = null;

        if ( "EU".equals(land) && "bedrijf".equals(klant)) {
            btw = 0d;
        } else {
            switch ( product ) {
                case "boek":
                    btw = 6d;
                    break;
                default:
                    btw = 21d;
            }
        }

        System.err.printf("BTW voor (%s, %s, %s) is: %.0f%%\n", product, klant, land, btw);
    }
}

De uitvoering geeft dan een verwacht resultaat per beslissing:

BTW voor (boek, bedrijf, non-EU) is: 6%
BTW voor (boek, bedrijf, EU) is: 0%
BTW voor (boek, particulier, EU) is: 6%
BTW voor (boek, bedrijf, BE) is: 6%
BTW voor (boek, particulier, BE) is: 6%
BTW voor (laptop, particulier, BE) is: 21%
BTW voor (laptop, bedrijf, BE) is: 21%
BTW voor (laptop, bedrijf, EU) is: 0%
BTW voor (laptop, bedrijf, non-EU) is: 21%


De nadelen van deze aanpak zijn ook vrij duidelijk:

  • de formulering van business rules is niet gestandaardiseerd en laat ruimte voor hiaten of foute interpretatie
  • de vertaalslag naar Java code vergt een zekere workload en doorlooptijd én introduceert een risico op fouten
  • het opstellen en automatiseren van test-scenarios die een correcte implementatie moeten verifiëren kost opnieuw tijd en moeite
  • het resultaat is niet transparant: de (Java of andere) code die de regels implementeert is niet leesbaar of minstens niet echt verifiëerbaar voor de business specialist die de regels formuleerde
  • Deze werkmethode (specificatie, vertaalslag naar code, development, test) te zwaar om efficiënt wijzigingen aan regels toe te laten 

Implementatie in DMN Beslissingtabel

Een handigere aanpak is om de business rules in DMN tabellen te modeleren.  DMN is een standaard van Object Management Group (OMG), die o.a. ook de UML en BPMN standaarden in beheer heeft.
Het formaat van een DMN tabel komt voor de meeste mensen zeer vertrouwd over aangezien er net als in Excel met een tabulaire structuur wordt gewerkt.  DMN Tabellen kunnen dan ook meestal door – of minstens samen met -- niet-IT-technische profielen (business, Subject Matter Expert) opgesteld worden.

Dit formaat werkt zowel voor de specificatie tijdens modelering als voor de uitvoering at runtime : de DMN standaard beschrijft een XML formaat dat elke DMN-engine kan consumeren.

dmn-onderdelen

De grote onderdelen van een DMN tabel zijn de volgende:

  • Naam en ID van de beslissingstabel
    Deze laten toe om de beslissingstabel eenduidig te identificeren, alsook om ernaar te refereren tijdens automatisatie
  • Inputs
  • Dit is de data die nodig is om tot een beslissing te komen.  Dit kan er eentje zijn, of een hele reeks.  Elk van de input heeft een naam en datatype, en optioneel een lijst van toegelaten waarden.
  • Outputs
    De outputs van een beslissing.  Dit kan opnieuw één element zijn, of een hele reeks.  Net als de input heeft ook een output een naam en datatype
  • Business Rules
    Elke horizontale lijn in de tabel staat voor één regel, die op basis van match met de input al dan niet relevant kan zijn voor de geëvalueerde data, en die dan de bijhorende output data bepaalt
    De kolommen met inputs samen vormen een voorwaarde ("condition") onder dewelke de business rule relevant is.
    De kolommen met outputs samen vormen de conclusie ("conclusion") die bij die condition hoort
  • Een hit policy 
    die aangeeft op welke manier de business rules zich tot elkaar verhouden: worden ze één voor één van boven naar onder afgelopen tot er een match is, wordt er maar één match verwacht, worden alle relevante rules in rekening gebracht, e.d.
    De eenvoudigste hit policy ("first match") wordt aangeduid als "F" - dit betekent dat de regels van boven naar beneden geëvalueerd worden, en dat de eerste match geldt.  Of er verderop nog regels matchen of niet wordt niet nader bekeken. 

 

Een DMN demo met Spring Boot en Flowable

DMN Modeleren in Flowable Modeler

De eerste stap is uiteraard om de DMN tabel te gaan modeleren, zodat we deze nadien in een Java applicatie kunnen uitvoeren.

De modeler-applicatie is één van de tools binnen Flowable BPM (www.flowable.org) waarin je modellen kan tekenen.  Deze toepassing is web-based, dus je hebt een draaiende Flowable installatie nodig om deze te gebruiken.  Je kan Flowable downloaden van de website, maar de makkelijkste manier om snel even kennis te maken met Flowable is de all-in-one Docker container (https://hub.docker.com/r/flowable/all-in-one) te starten: 

docker run -p 8080:8080 flowable/all-in-one


Eens de applicatie is opgestart kan je ze bereiken met je browser op http://localhost:8080/flowable-modeler, en inloggen met de standaard user admin en paswoord test.

In de Flowable modeler applicatie vind je een menu "Decision Tables" en de mogelijkheid tot "Create Decision Table"

dmn-1

Je kiest een naam en een key voor je beslissingstabel.  Voor de naam kies je uiteraard best iets wat goed de beslissing omschrijft, de key is de technische identificatie die je zal gebruiken vanuit je Java code om de beslissingstabel uit te voeren.

dmn-2


Tenslotte kom je in de editor terecht die toelaat om je tabel te modeleren.   Eens je daarmee klaar bent kan je de DMN tabel bewaren.

dmn-3

Na het bewaren kom je terug in het overzicht binnen "Decision Tables" verschijnt je nieuwe tabel.

dmn-4

Als je hierop doorklikt zie je ook de mogelijkheid tot een "Export Decision Table" en zo de XML te downloaden die we straks in onze codebase zullen opnemen.

dmn-5

De inhoud van de .dmn file zou er ongeveer als volgt moeten uitzien:
(als je ineens met het code-voorbeeld aan de slag wil kan je deze XML ook copiëren en zo de modelering overslaan.  Importeren van de XML file in Flowable Modeler laat dan toe om er verder aan te werken)

<definitions xmlns="http://www.omg.org/spec/DMN/20180521/MODEL/" id="definition_2bcb6839-5712-11ea-bb12-0242ac110002" name="BTW Percentage" namespace="http://www.flowable.org/dmn">
  <decision id="btwDecision" name="BTW Percentage">
    <description>just a little demo</description>
    <decisionTable id="decisionTable_2bcb6839-5712-11ea-bb12-0242ac110002" hitPolicy="FIRST">
      <input label="Type Klant">
        <inputExpression id="inputExpression_1" typeRef="string">
          <text>klant</text>
        </inputExpression>
        <inputValues>
          <text>"particulier","bedrijf"</text>
        </inputValues>
      </input>
      <input label="Product">
        <inputExpression id="inputExpression_4" typeRef="string">
          <text>product</text>
        </inputExpression>
      </input>
      <input label="Land">
        <inputExpression id="inputExpression_3" typeRef="string">
          <text>land</text>
        </inputExpression>
        <inputValues>
          <text>"BE","EU","non-EU"</text>
        </inputValues>
      </input>
      <output id="outputExpression_2" label="BTW Percentage" name="btw" typeRef="number">
        <outputValues>
          <text>"0","6","21"</text>
        </outputValues>
      </output>
      <rule>
        <inputEntry id="inputEntry_1_1">
          <text><![CDATA[== "bedrijf"]]></text>
        </inputEntry>
        <inputEntry id="inputEntry_4_1">
          <text><![CDATA[-]]></text>
        </inputEntry>
        <inputEntry id="inputEntry_3_1">
          <text><![CDATA[== "EU"]]></text>
        </inputEntry>
        <outputEntry id="outputEntry_2_1">
          <text><![CDATA[0]]></text>
        </outputEntry>
      </rule>
      <rule>
        <inputEntry id="inputEntry_1_2">
          <text><![CDATA[-]]></text>
        </inputEntry>
        <inputEntry id="inputEntry_4_2">
          <text><![CDATA[== "boek"]]></text>
        </inputEntry>
        <inputEntry id="inputEntry_3_2">
          <text><![CDATA[-]]></text>
        </inputEntry>
        <outputEntry id="outputEntry_2_2">
          <text><![CDATA[6]]></text>
        </outputEntry>
      </rule>
      <rule>
        <inputEntry id="inputEntry_1_3">
          <text><![CDATA[-]]></text>
        </inputEntry>
        <inputEntry id="inputEntry_4_3">
          <text><![CDATA[-]]></text>
        </inputEntry>
        <inputEntry id="inputEntry_3_3">
          <text><![CDATA[-]]></text>
        </inputEntry>
        <outputEntry id="outputEntry_2_3">
          <text><![CDATA[21]]></text>
        </outputEntry>
      </rule>
    </decisionTable>
  </decision>
</definitions>

 

Flowable DMN integratie in een Spring Boot Applicatie

Nu we onze DMN tabel hebben gemodeleerd en gedownload is de volgende stap om deze in een Java applicatie te gaan gebruiken.  We werken hier op basis van het Spring Boot framework.  De makkelijkste start is om met de Spring Initializr (https://start.spring.io/) aan de slag te gaan.  Deze web applicatie laat je toe om een basisproject te downloaden voor je applicatie.

Ga naar https://start.spring.io/ en geef een groupId en artifactId op, voor deze demo vertrekken we van een Spring Boot applicatie zonder extra dependencies.
Klik onderaan op "Generate".  De resulterende ZIP file bevat een werkende codebase voor je Spring Boot applicatie.

spring-initializr


Maak een folder "dmn" onder "src/main/resources" aan - hier zal Flowable je DMN tabellen gaan zoeken bij opstart.
Download je decision table XML (.dmn file) vanuit Flowable Modeler en plaats die in deze folder.

 

Om Flowable toe te voegen aan je project vul je volgende twee dependencies aan in je pom.xml file:

<dependency>
  <groupId>org.flowable</groupId>
  <artifactId>flowable-spring-boot-starter</artifactId>
  <version>6.5.0</version>
</dependency>

<!-- enkel voor demo -->
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>


Vervolledig dan de voorgegenereerde Spring Boot applicatie om de DMN Engine van Flowable tot het juiste resultaat te laten komen.  Flowable is volledig Spring Boot enabled, dus door het autowiren van de DmnEngine levert een werkende setup met default configuratie op:

[src/main/java/com/xti/demo/dmn/dmndemo/DmnDemoApplication.java]package com.xti.demo.dmn.dmndemo;

import java.util.Map;

import org.flowable.dmn.engine.DmnEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DmnDemoApplication implements CommandLineRunner {

    @Autowired
    private DmnEngine dmnEngine;

    public static void main(String[] args) {
        SpringApplication.run(DmnDemoApplication.class, args);
    }

    @Override
    public void run(String...args) throws Exception {
        decide ("boek", "bedrijf", "non-EU");
        decide ("boek", "bedrijf", "EU");
        decide ("boek", "particulier", "EU");
        decide ("boek", "bedrijf", "BE");
        decide ("boek", "particulier", "BE");
        decide ("laptop", "particulier", "BE");
        decide ("laptop", "bedrijf", "BE");
        decide ("laptop", "bedrijf", "EU");
        decide ("laptop", "bedrijf", "non-EU");

        System.exit(0);
    }

    public void decide ( String product, String klant, String land ) {

        Map<String,Object> result = dmnEngine
            .getDmnRuleService()
            .createExecuteDecisionBuilder()
            .decisionKey("btwDecision")
                .variable("product", product)
                .variable("klant", klant)
                .variable("land", land)
            .executeWithSingleResult();

        Double btw = (Double)result.get("btw");

        System.err.printf("BTW voor (%s, %s, %s) is: %.0f%%\n", product, klant, land, btw);
    }
}

 

Bij het runnen van deze applicatie krijg je uiteindelijk hetzelfde resultaat als in de klassieke Java implementatie:

BTW voor (boek, bedrijf, non-EU) is: 6%
BTW voor (boek, bedrijf, EU) is: 0%
BTW voor (boek, particulier, EU) is: 6%
BTW voor (boek, bedrijf, BE) is: 6%
BTW voor (boek, particulier, BE) is: 6%
BTW voor (laptop, particulier, BE) is: 21%
BTW voor (laptop, bedrijf, BE) is: 21%
BTW voor (laptop, bedrijf, EU) is: 0%
BTW voor (laptop, bedrijf, non-EU) is: 21%


De DMN Engine brengt bij opstart een kleine delay met zich mee, maar nadien worden beslissingen snel uitgevoerd.

Bij elke update van de DMN tabel zal je uiteraard de nieuwe XML file moeten downloaden en de vorige laten overschrijven.  Voor een vlotte ontwikkelcyclus kan je dit dan ook best automatiseren.

 

DMN modellen en je DTAP ontwikkelstraat

Flowable ondersteunt ook het "hot-deployen" van DMN Tabellen, wat je dan voor de keuze stelt: zie je zo'n DMN Tabel als een onderdeel van de codebase (een deliverable van het projectteam) of louter als configuratie die in productie kan gebeuren?
Daarbij aansluitend bepaalt het antwoord op deze vraag dus ook of je de wijzigingen door de hele ontwikkelstraat laat lopen (van development over testing en acceptatie tot productie) of gewoon als invoer naar een productie-omgeving laat gebeuren.

Vaak zit de toegevoegde waarde van het gebruik van Decision Tables vooral in de transparantie van hoe de beslissing gevormd wordt, én de garantie dat ze eenduidig te vinden en aan te passen is.
In de meeste scenario's stuur je de DMN modellen best mee door de ontwikkelstraat, en pas je geautomatiseerde testen aan de de juiste toepassing van de vernieuwde regels aftoetsen.

Enkel als er ambities zijn om wijzigingen door te voeren zonder deze te koppelen aan een software-release is het andere scenario het overwegen waard.

DMN verder verkennen

Het zal duidelijk zijn dat we in deze blogpost enkel maar de absolute basis van DMN én Flowable aangeraakt hebben.
Enkele interessant topics om verder uit te diepen zijn alvast:

  • De uitgebreidere mogelijkheden van DMN specificatie, met onder andere alle mogelijke hit policies
  • Het gebruik van een relationele database in plaats van de inmemory H2 database
  • Het opzoeken en visualiseren van historische beslissingen die Flowable bijhoudt, aan de hand van de History Service
  • Het versioneren van beslissingstabellen en het in parallel uitvoeren van de juiste versie
  • Het inpassen van DMN Decision Tables in een geuatomatiseerd Business Proces (BPMN)

In de Flowable documentatie vind je de nodige aanknopingspunten.

Conclusie

Met een minimale impact laat Flowable toe om business rules te modeleren én uit te voeren als een DMN Decision Table.
Het modeleren van je business rules als DMN beslissingstabellen standaardiseert de specificatie ervan.
Het effectief uitvoeren van deze modellen vermijdt een development-en-test cyclus bovenop de modelering, maakt de werking van de software inzichtelijk, én laat op eenvoudige wijzigingen toe. 

 

 

Op zoek naar een Flowable Partner?

Onderwerpen: Java / Open Source / BPM / Flowable

Geïnteresseerd in BPM Engines en Software Development?  Lees onze gids "Business Process Management en BPM Engines onder de loep"

Lees het e-book
BPM e-book

Recente artikels

Artikels per onderwerp