kogitoLogo

Welcome

Kogito is currently for Developer Preview only. Developer Preview releases contain features that might not be fully tested and are not in production-ready state. Users are advised to avoid running the software for production or business-critical workloads. Some features in this release might not be available in future releases.

1. Kogito business automation

Kogito is a cloud-native business automation technology for building cloud-ready business applications. The name Kogito derives from the Latin "Cogito", as in "Cogito, ergo sum" ("I think, therefore I am"), and is pronounced [ˈkoː.d͡ʒi.to] (KO-jee-to). The letter K has reference to Kubernetes, the base for OpenShift as the target cloud platform for Kogito, and to the Knowledge Is Everything (KIE) open source business automation project from which Kogito originates.

Kogito is designed specifically to excel in a hybrid cloud environment and to be adaptable to your domain and tooling needs. The core objective of Kogito is to help you mold a set of business processes and decisions into your own domain-specific cloud-native set of services.

Image of business assets moving to cloud services
Figure 1. Business processes and decisions to cloud services

When you are using Kogito, you are building a cloud-native application as a set of independent domain-specific services, collaborating to achieve some business value. The processes and rules that you use to describe the target behavior are executed as part of the services that you create. The resulting services are highly distributed and scalable with no centralized orchestration service, and the runtime that your service uses is optimized for what your service needs.

Kogito includes components that are based on well-known business automation KIE projects, specifically Drools, jBPM, and OptaPlanner, to offer dependable, open source solutions for business rules, business processes, and constraint solving.

1.1. Cloud-first priority

Kogito is designed to run and scale on a cloud infrastructure. You can use Kogito with the latest cloud-based technologies, such as Quarkus, Knative, and Apache Kafka, to get fast boot times and instant scaling on container application platforms, such as OpenShift.

Image of cloud-based technologies
Figure 2. Technologies used with Kogito

For example, Kogito is compatible with the following technologies:

  • OpenShift, based on Kubernetes, is the target platform for building and managing containerized applications.

  • Quarkus is the new native Java stack for Kubernetes that you can use when you build applications with Kogito services.

  • Spring Boot is also supported with Kogito if you need to use the Spring Framework with Kogito.

  • GraalVM with Quarkus enables you to use native compilation with Kogito, resulting in fast start-up times and minimal footprint. For example, a native Kogito service starts in about 0.003ms, about 100 times faster than a non-native start-up. Fast start-up is almost a necessity in a cloud ecosystem, especially if you need small serverless applications.

  • Knative enables you to build serverless applications with Kogito that you can scale up or down (to zero) as needed.

  • Prometheus and Grafana are compatible with Kogito services for monitoring and analytics with optional extensions.

  • Kafka, Infinispan, and Keycloak are also some of the middleware technologies that Kogito supports.

1.2. Domain-specific flexibility

Kogito adapts to your business domain instead of forcing you to modify your domain to work with Kogito. You can expose your Kogito services with domain-specific APIs, based on the processes and rules that you have defined. Domain-specific APIs for Kogito services do not require third-party or internal APIs.

For example, a process for onboarding employees could generate remote REST API endpoints that you can use to onboard new employees or get information on their status, all using domain-specific JSON data.

Image of REST API endpoints in Swagger UI
Figure 3. Example custom API endpoints in Swagger

You can also expose domain-specific data through events or in a data index so that the data can be consumed and queried by other services.

1.3. Developer-centered experience

Another focus of Kogito is optimal developer experience. You can use much or all of your existing tooling and workflow to develop, build, and deploy Kogito services, whether locally for testing or into the cloud. Quarkus offers development mode features to help with local testing, such as live reload of your processes and rules in your running applications for advanced debugging.

Kogito tooling is embeddable so that you can continue using the worklfow you already use for cloud-native services. For example, the Kogito VSCode extension enables you to edit your BPMN 2.0 processes and DMN decisions from within VSCode, next to your other application code.

Demo of Kogito BPMN2 extension in VSCode
Figure 4. Building a Kogito process service in VSCode

To deploy your services into the cloud, you can use the Kogito Operator, which guides you through every step. The Kogito Operator uses the Operator Framework to automate and manage many of the deployment steps for you. For example, when you provide the operator a link to the Git repository that contains your application, the operator can automatically configure the components required to build your project from source and deploy the resulting services. Kogito also offers a Command Line Interface (CLI) to simplify some of these deployment tasks.

1.4. Kogito on Quarkus and Spring Boot

The primary Java frameworks that Kogito supports are Quarkus (recommended) and Spring Boot.

Quarkus is a Kubernetes-native Java framework with a container-first approach to building Java applications, especially for Java virtual machines (JVMs) such as GraalVM and HotSpot. Quarkus optimizes Java specifically for Kubernetes by reducing the size of both the Java application and container image footprint, eliminating some of the Java programming workload from previous generations, and reducing the amount of memory required to run those images.

For Kogito services, Quarkus is the preferred framework for optimal Kubernetes compatibility and enhanced developer features, such as live reload in development mode for advanced debugging.

Spring Boot is a Java-based framework for building standalone production-ready Spring applications. Spring Boot enables you to develop Spring applications with minimal configurations and without an entire Spring configuration setup.

For Kogito services, Spring Boot is supported for developers who need to use Kogito in an existing Spring Framework environment.

2. Creating and running your first Kogito services

As a developer of business processes and rules, you can use Kogito business automation to build cloud-native applications that adapt to your business domain and tooling.

Prerequisites
  • JDK 11 or later is installed.

  • Apache Maven 3.5.3 or later is installed.

  • Docker is installed.

2.1. Enabling the Kogito VSCode extension for BPMN and DMN modeling

Visual Studio Code (VSCode) is the preferred integrated development environment (IDE) for developing Kogito services. Kogito provides a VSCode extension that enables you to design Business Process Model and Notation (BPMN) 2.0 business processes and Decision Model and Notation (DMN) decision models directly in VSCode.

Prerequisites
Procedure
  1. In the kogito-tooling page in GitHub, download the latest version of the vscode_extension_kogito_kie_editors_VERSION.vsix file.

  2. In your VSCode IDE, go to ExtensionsMore actionsInstall from VSIX and select the downloaded extension file.

  3. When the Kogito extension appears in the extension list in VSCode, select it and click Enable, if needed.

    After you enable this extension, any .bpmn2 and .dmn files that you open in VSCode are automatically displayed as graphical models.

    If the Kogito BPMN or DMN designers open only the XML source of a BPMN or DMN file and displays an error message, review the reported errors and the model file to ensure all BPMN or DMN elements are correctly defined.

2.2. Creating a Maven project for a Kogito service

Before you can begin developing Kogito services, you need to create a Maven project where you can build your Kogito assets and any other related resources for your application.

Procedure
  1. In a command terminal, navigate to a local folder where you want to store the new Kogito project.

  2. Run the following command to generate a project within a defined folder:

    On Quarkus
    mvn archetype:generate -DarchetypeGroupId=org.kie.kogito -DarchetypeArtifactId=kogito-quarkus-archetype -DgroupId=org.acme -DartifactId=sample-kogito
    On Spring Boot
    mvn archetype:generate -DarchetypeGroupId=org.kie.kogito -DarchetypeArtifactId=kogito-springboot-archetype -DgroupId=org.acme -DartifactId=sample-kogito

    This command generates a sample-kogito Maven project and imports the Kogito extension for all required dependencies and configurations to prepare your application for business automation.

  3. Open or import the project in your IDE to view the contents.

2.3. Designing the application logic for a Kogito service using DMN and BPMN

After you create your Kogito project, you can create or import Business Process Model and Notation (BPMN) business processes, Decision Model and Notation (DMN) decision models, Drools Rule Language (DRL) business rules, XLS or XLSX decision tables, Java services, and other assets in the src/main/resources folder of your project.

The example for this procedure is a basic Kogito service that offers one REST endpoint: /persons. This endpoint is automatically generated based on an example PersonProcess.bpmn2 business process that employs an example PersonDecisions.dmn DMN model to make decisions based on the data being processed.

The business process contains the business logic of a Kogito service. The process provides the complete set of steps to achieve the business goal. The process is also the entry point to the service that can be consumed by other services.

The business rule enables you to define decision logic in reusable pieces that can be used in a declarative way. You can define business rules and decisions in several ways, such as with DMN models, DRL rules, or XLS or XLSX decision tables. The example for this procedure uses a DMN model.

Procedure
  1. In the Maven project that you generated for your Kogito service, create a src/main/java/org/acme/kogito/model folder and add the following Person.java object inside the new folder:

    Example person Java object
    package org.acme.kogito.model;
    
    import java.io.Serializable;
    
    public class Person {
    
    	private String name;
    	private int age;
    	private boolean adult;
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	public boolean isAdult() {
    		return adult;
    	}
    
    	public void setAdult(boolean adult) {
    		this.adult = adult;
    	}
    
    	@Override
    	public String toString() {
    		return "Person [name=" + name + ", age=" + age + ", adult=" + adult + "]";
    	}
    
    }

    This example Java object sets and retrieves a person’s name, age, and adult status.

  2. Create a src/main/resources/org/acme/kogito folder and add the following PersonDecisions.dmn DMN model inside the new folder:

    Image of person DMN decision diagram
    Figure 5. Example person DMN decision requirements diagram (DRD)
    Image of person DMN decision table
    Figure 6. Example DMN boxed expression for Is Adult decision
    Image of person DMN data types
    Figure 7. Example DMN data types

    This example DMN model consists of a basic DMN input node and a decision node defined by a DMN decision table with a custom structured data type.

    In VSCode (recommended), you can add the Kogito VSCode extension to design the DRD, boxed expression, and data types with the Kogito DMN designer.

    To create this example DMN model quickly, you can copy the following PersonDecisions.dmn file content:

    Example DMN file
    <dmn:definitions xmlns:dmn="http://www.omg.org/spec/DMN/20180521/MODEL/" xmlns="https://kiegroup.org/dmn/_52CEF9FD-9943-4A89-96D5-6F66810CA4C1" xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/" xmlns:kie="http://www.drools.org/kie/dmn/1.2" xmlns:dmndi="http://www.omg.org/spec/DMN/20180521/DMNDI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/" xmlns:feel="http://www.omg.org/spec/DMN/20180521/FEEL/" id="_84B432F5-87E7-43B1-9101-1BAFE3D18FC5" name="PersonDecisions" typeLanguage="http://www.omg.org/spec/DMN/20180521/FEEL/" namespace="https://kiegroup.org/dmn/_52CEF9FD-9943-4A89-96D5-6F66810CA4C1">
      <dmn:extensionElements/>
      <dmn:itemDefinition id="_DEF2C3A7-F3A9-4ABA-8D0A-C823E4EB43AB" name="tPerson" isCollection="false">
        <dmn:itemComponent id="_DB46DB27-0752-433F-ABE3-FC9E3BDECC97" name="Age" isCollection="false">
          <dmn:typeRef>number</dmn:typeRef>
        </dmn:itemComponent>
        <dmn:itemComponent id="_8C6D865F-E9C8-43B0-AB4D-3F2075A4ECA6" name="Name" isCollection="false">
          <dmn:typeRef>string</dmn:typeRef>
        </dmn:itemComponent>
        <dmn:itemComponent id="_9033704B-4E1C-42D3-AC5E-0D94107303A1" name="Adult" isCollection="false">
          <dmn:typeRef>boolean</dmn:typeRef>
        </dmn:itemComponent>
      </dmn:itemDefinition>
      <dmn:inputData id="_F9685B74-0C69-4982-B3B6-B04A14D79EDB" name="Person">
        <dmn:extensionElements/>
        <dmn:variable id="_0E345A3C-BB1F-4FB2-B00F-C5691FD1D36C" name="Person" typeRef="tPerson"/>
      </dmn:inputData>
      <dmn:decision id="_0D2BD7A9-ACA1-49BE-97AD-19699E0C9852" name="Is Adult">
        <dmn:extensionElements/>
        <dmn:variable id="_54CD509F-452F-40E5-941C-AFB2667D4D45" name="Is Adult" typeRef="boolean"/>
        <dmn:informationRequirement id="_2F819B03-36B7-4DEB-AED6-2B46AE3ADB75">
          <dmn:requiredInput href="#_F9685B74-0C69-4982-B3B6-B04A14D79EDB"/>
        </dmn:informationRequirement>
        <dmn:decisionTable id="_58370567-05DE-4EC0-AC2D-A23803C1EAAE" hitPolicy="UNIQUE" preferredOrientation="Rule-as-Row">
          <dmn:input id="_ADEF36CD-286A-454A-ABD8-9CF96014021B">
            <dmn:inputExpression id="_4930C2E5-7401-46DD-8329-EAC523BFA492" typeRef="number">
              <dmn:text>Person.Age</dmn:text>
            </dmn:inputExpression>
          </dmn:input>
          <dmn:output id="_9867E9A3-CBF6-4D66-9804-D2206F6B4F86" typeRef="boolean"/>
          <dmn:rule id="_59D6BFF0-35B4-4B7E-8D7B-E31CB0DB8242">
            <dmn:inputEntry id="_7DC55D63-234F-497B-A12A-93DA358C0136">
              <dmn:text>&gt; 18</dmn:text>
            </dmn:inputEntry>
            <dmn:outputEntry id="_B3BB5B97-05B9-464A-AB39-58A33A9C7C00">
              <dmn:text>true</dmn:text>
            </dmn:outputEntry>
          </dmn:rule>
          <dmn:rule id="_8FCD63FE-8AD8-4F56-AD12-923E87AFD1B1">
            <dmn:inputEntry id="_B4EF7F13-E486-46CB-B14E-1D21647258D9">
              <dmn:text>&lt;= 18</dmn:text>
            </dmn:inputEntry>
            <dmn:outputEntry id="_F3A9EC8E-A96B-42A0-BF87-9FB1F2FDB15A">
              <dmn:text>false</dmn:text>
            </dmn:outputEntry>
          </dmn:rule>
        </dmn:decisionTable>
      </dmn:decision>
      <dmndi:DMNDI>
        <dmndi:DMNDiagram>
          <di:extension>
            <kie:ComponentsWidthsExtension>
              <kie:ComponentWidths dmnElementRef="_58370567-05DE-4EC0-AC2D-A23803C1EAAE">
                <kie:width>50</kie:width>
                <kie:width>100</kie:width>
                <kie:width>100</kie:width>
                <kie:width>100</kie:width>
              </kie:ComponentWidths>
            </kie:ComponentsWidthsExtension>
          </di:extension>
          <dmndi:DMNShape id="dmnshape-_F9685B74-0C69-4982-B3B6-B04A14D79EDB" dmnElementRef="_F9685B74-0C69-4982-B3B6-B04A14D79EDB" isCollapsed="false">
            <dmndi:DMNStyle>
              <dmndi:FillColor red="255" green="255" blue="255"/>
              <dmndi:StrokeColor red="0" green="0" blue="0"/>
              <dmndi:FontColor red="0" green="0" blue="0"/>
            </dmndi:DMNStyle>
            <dc:Bounds x="404" y="464" width="100" height="50"/>
            <dmndi:DMNLabel/>
          </dmndi:DMNShape>
          <dmndi:DMNShape id="dmnshape-_0D2BD7A9-ACA1-49BE-97AD-19699E0C9852" dmnElementRef="_0D2BD7A9-ACA1-49BE-97AD-19699E0C9852" isCollapsed="false">
            <dmndi:DMNStyle>
              <dmndi:FillColor red="255" green="255" blue="255"/>
              <dmndi:StrokeColor red="0" green="0" blue="0"/>
              <dmndi:FontColor red="0" green="0" blue="0"/>
            </dmndi:DMNStyle>
            <dc:Bounds x="404" y="311" width="100" height="50"/>
            <dmndi:DMNLabel/>
          </dmndi:DMNShape>
          <dmndi:DMNEdge id="dmnedge-_2F819B03-36B7-4DEB-AED6-2B46AE3ADB75" dmnElementRef="_2F819B03-36B7-4DEB-AED6-2B46AE3ADB75">
            <di:waypoint x="504" y="489"/>
            <di:waypoint x="404" y="336"/>
          </dmndi:DMNEdge>
        </dmndi:DMNDiagram>
      </dmndi:DMNDI>
    </dmn:definitions>

    To create this example DMN model in VSCode using the Kogito DMN designer, follow these steps:

    1. In the left toolbar, select DMN Input Data, drag the node to the canvas, and double-click the node to name it Person.

    2. In the left toolbar, select DMN decision, drag the node to the canvas, double-click the node to name it Is Adult, and link to it from the input node.

    3. Select the decision node to display the node options and click the Edit icon to open the DMN boxed expression designer to define the decision logic for the node.

    4. Click the undefined expression field and select Decision Table.

    5. Click the upper-left corner of the decision table to set the hit policy to Unique.

    6. Set the input and output columns so that the input source Person.Age with type number determines the age limit and the output target Is Adult with type boolean determines adult status:

      Image of person DMN decision table
      Figure 8. Example DMN decision table for Is Adult decision
    7. In the upper tab options, select the Data Types tab and add the following tPerson structured data type and nested data types:

      Image of person DMN data types
      Figure 9. Example DMN data types
    8. Save the DMN decision file.

  3. In the src/main/resources/org/acme/kogito folder, use a BPMN 2.0 process modeler to create the following PersonProcess.bpmn2 process:

    Image of person process diagram
    Figure 10. Example person BPMN process

    This example process consists of the following basic BPMN components:

    • Start event

    • Business rule task

    • Exclusive gateway

    • User task

    • End events

    In VSCode (recommended), you can add the Kogito VSCode extension to model the business process with the Kogito process designer.

    To create this example process quickly, you can copy the following PersonProcess.bpmn2 file content:

    Example BPMN file
    <bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:bpsim="http://www.bpsim.org/schemas/1.0" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:drools="http://www.jboss.org/drools" id="_xAcbQD7LEDiUWpRQWNPiuQ" exporter="jBPM Process Modeler" exporterVersion="2.0" targetNamespace="http://www.omg.org/bpmn20">
      <bpmn2:itemDefinition id="_personItem" structureRef="org.acme.kogito.model.Person"/>
      <bpmn2:itemDefinition id="_isAdultItem" structureRef="Boolean"/>
      <bpmn2:itemDefinition id="_UserTask_1_SkippableInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_PriorityInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_CommentInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_DescriptionInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_CreatedByInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_TaskNameInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_GroupIdInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_ContentInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_NotStartedReassignInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_NotCompletedReassignInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_NotStartedNotifyInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_NotCompletedNotifyInputXItem" structureRef="Object"/>
      <bpmn2:itemDefinition id="_UserTask_1_personInputXItem" structureRef="org.acme.kogito.model.Person"/>
      <bpmn2:itemDefinition id="_BusinessRuleTask_1_namespaceInputXItem" structureRef="java.lang.String"/>
      <bpmn2:itemDefinition id="_BusinessRuleTask_1_modelInputXItem" structureRef="java.lang.String"/>
      <bpmn2:itemDefinition id="_BusinessRuleTask_1_decisionInputXItem" structureRef="java.lang.String"/>
      <bpmn2:itemDefinition id="_BusinessRuleTask_1_PersonInputXItem" structureRef="org.acme.kogito.model.Person"/>
      <bpmn2:itemDefinition id="_BusinessRuleTask_1_Is-AdultOutputXItem" structureRef="Boolean"/>
      <bpmn2:process id="persons" drools:packageName="org.acme.kogito" drools:version="1.0" drools:adHoc="false" name="Person Process" isExecutable="true" processType="Public">
        <bpmn2:property id="person" itemSubjectRef="_personItem" name="person"/>
        <bpmn2:property id="isAdult" itemSubjectRef="_isAdultItem" name="isAdult"/>
        <bpmn2:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="BusinessRuleTask_1"/>
        <bpmn2:sequenceFlow id="SequenceFlow_2" sourceRef="BusinessRuleTask_1" targetRef="ExclusiveGateway_1"/>
        <bpmn2:sequenceFlow id="SequenceFlow_3" sourceRef="ExclusiveGateway_1" targetRef="UserTask_1">
          <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" id="_xAoBcD7LEDiUWpRQWNPiuQ" language="http://www.java.com/java">return isAdult == false;</bpmn2:conditionExpression>
        </bpmn2:sequenceFlow>
        <bpmn2:sequenceFlow id="SequenceFlow_4" sourceRef="UserTask_1" targetRef="EndEvent_1"/>
        <bpmn2:sequenceFlow id="SequenceFlow_5" sourceRef="ExclusiveGateway_1" targetRef="EndEvent_2">
          <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" id="_xAoogD7LEDiUWpRQWNPiuQ" language="http://www.java.com/java">return isAdult == true;</bpmn2:conditionExpression>
        </bpmn2:sequenceFlow>
        <bpmn2:startEvent id="StartEvent_1" name="StartProcess">
          <bpmn2:extensionElements>
            <drools:metaData name="elementname">
              <drools:metaValue>StartProcess</drools:metaValue>
            </drools:metaData>
          </bpmn2:extensionElements>
          <bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing>
        </bpmn2:startEvent>
        <bpmn2:businessRuleTask id="BusinessRuleTask_1" name="Evaluate person" implementation="http://www.jboss.org/drools/dmn">
          <bpmn2:extensionElements>
            <drools:metaData name="elementname">
              <drools:metaValue>Evaluate person</drools:metaValue>
            </drools:metaData>
          </bpmn2:extensionElements>
          <bpmn2:incoming>SequenceFlow_1</bpmn2:incoming>
          <bpmn2:outgoing>SequenceFlow_2</bpmn2:outgoing>
          <bpmn2:ioSpecification id="_xAqdsD7LEDiUWpRQWNPiuQ">
            <bpmn2:dataInput id="BusinessRuleTask_1_namespaceInputX" drools:dtype="java.lang.String" itemSubjectRef="_BusinessRuleTask_1_namespaceInputXItem" name="namespace"/>
            <bpmn2:dataInput id="BusinessRuleTask_1_decisionInputX" drools:dtype="java.lang.String" itemSubjectRef="_BusinessRuleTask_1_decisionInputXItem" name="decision"/>
            <bpmn2:dataInput id="BusinessRuleTask_1_modelInputX" drools:dtype="java.lang.String" itemSubjectRef="_BusinessRuleTask_1_modelInputXItem" name="model"/>
            <bpmn2:dataInput id="BusinessRuleTask_1_PersonInputX" drools:dtype="org.acme.kogito.model.Person" itemSubjectRef="_BusinessRuleTask_1_PersonInputXItem" name="Person"/>
            <bpmn2:dataOutput id="BusinessRuleTask_1_Is-AdultOutputX" drools:dtype="Boolean" itemSubjectRef="_BusinessRuleTask_1_Is-AdultOutputXItem" name="Is Adult"/>
            <bpmn2:inputSet id="_xArr0D7LEDiUWpRQWNPiuQ">
              <bpmn2:dataInputRefs>BusinessRuleTask_1_namespaceInputX</bpmn2:dataInputRefs>
              <bpmn2:dataInputRefs>BusinessRuleTask_1_decisionInputX</bpmn2:dataInputRefs>
              <bpmn2:dataInputRefs>BusinessRuleTask_1_modelInputX</bpmn2:dataInputRefs>
              <bpmn2:dataInputRefs>BusinessRuleTask_1_PersonInputX</bpmn2:dataInputRefs>
            </bpmn2:inputSet>
            <bpmn2:outputSet id="_xArr0T7LEDiUWpRQWNPiuQ">
              <bpmn2:dataOutputRefs>BusinessRuleTask_1_Is-AdultOutputX</bpmn2:dataOutputRefs>
            </bpmn2:outputSet>
          </bpmn2:ioSpecification>
          <bpmn2:dataInputAssociation id="_xArr0j7LEDiUWpRQWNPiuQ">
            <bpmn2:targetRef>BusinessRuleTask_1_namespaceInputX</bpmn2:targetRef>
            <bpmn2:assignment id="_xAsS4D7LEDiUWpRQWNPiuQ">
              <bpmn2:from xsi:type="bpmn2:tFormalExpression" id="_xAthAD7LEDiUWpRQWNPiuQ">https://kiegroup.org/dmn/_52CEF9FD-9943-4A89-96D5-6F66810CA4C1</bpmn2:from>
              <bpmn2:to xsi:type="bpmn2:tFormalExpression" id="_xAuIED7LEDiUWpRQWNPiuQ">BusinessRuleTask_1_namespaceInputX</bpmn2:to>
            </bpmn2:assignment>
          </bpmn2:dataInputAssociation>
          <bpmn2:dataInputAssociation id="_xAuIET7LEDiUWpRQWNPiuQ">
            <bpmn2:targetRef>BusinessRuleTask_1_decisionInputX</bpmn2:targetRef>
            <bpmn2:assignment id="_xAuIEj7LEDiUWpRQWNPiuQ">
              <bpmn2:from xsi:type="bpmn2:tFormalExpression" id="_xAuvID7LEDiUWpRQWNPiuQ">Is Adult</bpmn2:from>
              <bpmn2:to xsi:type="bpmn2:tFormalExpression" id="_xAuvIT7LEDiUWpRQWNPiuQ">BusinessRuleTask_1_decisionInputX</bpmn2:to>
            </bpmn2:assignment>
          </bpmn2:dataInputAssociation>
          <bpmn2:dataInputAssociation id="_xAuvIj7LEDiUWpRQWNPiuQ">
            <bpmn2:targetRef>BusinessRuleTask_1_modelInputX</bpmn2:targetRef>
            <bpmn2:assignment id="_xAuvIz7LEDiUWpRQWNPiuQ">
              <bpmn2:from xsi:type="bpmn2:tFormalExpression" id="_xAuvJD7LEDiUWpRQWNPiuQ">PersonDecisions</bpmn2:from>
              <bpmn2:to xsi:type="bpmn2:tFormalExpression" id="_xAuvJT7LEDiUWpRQWNPiuQ">BusinessRuleTask_1_modelInputX</bpmn2:to>
            </bpmn2:assignment>
          </bpmn2:dataInputAssociation>
          <bpmn2:dataInputAssociation id="_xAuvJj7LEDiUWpRQWNPiuQ">
            <bpmn2:sourceRef>person</bpmn2:sourceRef>
            <bpmn2:targetRef>BusinessRuleTask_1_PersonInputX</bpmn2:targetRef>
          </bpmn2:dataInputAssociation>
          <bpmn2:dataOutputAssociation id="_xAuvJz7LEDiUWpRQWNPiuQ">
            <bpmn2:sourceRef>BusinessRuleTask_1_Is-AdultOutputX</bpmn2:sourceRef>
            <bpmn2:targetRef>isAdult</bpmn2:targetRef>
          </bpmn2:dataOutputAssociation>
        </bpmn2:businessRuleTask>
        <bpmn2:exclusiveGateway id="ExclusiveGateway_1" name="Exclusive Gateway 1" gatewayDirection="Diverging">
          <bpmn2:extensionElements>
            <drools:metaData name="elementname">
              <drools:metaValue>Exclusive Gateway 1</drools:metaValue>
            </drools:metaData>
          </bpmn2:extensionElements>
          <bpmn2:incoming>SequenceFlow_2</bpmn2:incoming>
          <bpmn2:outgoing>SequenceFlow_5</bpmn2:outgoing>
          <bpmn2:outgoing>SequenceFlow_3</bpmn2:outgoing>
        </bpmn2:exclusiveGateway>
        <bpmn2:userTask id="UserTask_1" name="Special handling for children">
          <bpmn2:extensionElements>
            <drools:metaData name="elementname">
              <drools:metaValue>Special handling for children</drools:metaValue>
            </drools:metaData>
          </bpmn2:extensionElements>
          <bpmn2:incoming>SequenceFlow_3</bpmn2:incoming>
          <bpmn2:outgoing>SequenceFlow_4</bpmn2:outgoing>
          <bpmn2:ioSpecification id="_xAv9QD7LEDiUWpRQWNPiuQ">
            <bpmn2:dataInput id="UserTask_1_TaskNameInputX" drools:dtype="Object" itemSubjectRef="_UserTask_1_TaskNameInputXItem" name="TaskName"/>
            <bpmn2:dataInput id="UserTask_1_personInputX" drools:dtype="org.acme.kogito.model.Person" itemSubjectRef="_UserTask_1_personInputXItem" name="person"/>
            <bpmn2:dataInput id="UserTask_1_SkippableInputX" drools:dtype="Object" itemSubjectRef="_UserTask_1_SkippableInputXItem" name="Skippable"/>
            <bpmn2:dataInput id="UserTask_1_PriorityInputX" drools:dtype="Object" itemSubjectRef="_UserTask_1_PriorityInputXItem" name="Priority"/>
            <bpmn2:inputSet id="_xAv9QT7LEDiUWpRQWNPiuQ">
              <bpmn2:dataInputRefs>UserTask_1_TaskNameInputX</bpmn2:dataInputRefs>
              <bpmn2:dataInputRefs>UserTask_1_personInputX</bpmn2:dataInputRefs>
              <bpmn2:dataInputRefs>UserTask_1_SkippableInputX</bpmn2:dataInputRefs>
              <bpmn2:dataInputRefs>UserTask_1_PriorityInputX</bpmn2:dataInputRefs>
            </bpmn2:inputSet>
          </bpmn2:ioSpecification>
          <bpmn2:dataInputAssociation id="_xAv9Qj7LEDiUWpRQWNPiuQ">
            <bpmn2:targetRef>UserTask_1_TaskNameInputX</bpmn2:targetRef>
            <bpmn2:assignment id="_xAv9Qz7LEDiUWpRQWNPiuQ">
              <bpmn2:from xsi:type="bpmn2:tFormalExpression" id="_xAwkUD7LEDiUWpRQWNPiuQ">ChildrenHandling</bpmn2:from>
              <bpmn2:to xsi:type="bpmn2:tFormalExpression" id="_xAwkUT7LEDiUWpRQWNPiuQ">UserTask_1_TaskNameInputX</bpmn2:to>
            </bpmn2:assignment>
          </bpmn2:dataInputAssociation>
          <bpmn2:dataInputAssociation id="_xAwkUj7LEDiUWpRQWNPiuQ">
            <bpmn2:sourceRef>person</bpmn2:sourceRef>
            <bpmn2:targetRef>UserTask_1_personInputX</bpmn2:targetRef>
          </bpmn2:dataInputAssociation>
          <bpmn2:dataInputAssociation id="_xAwkUz7LEDiUWpRQWNPiuQ">
            <bpmn2:targetRef>UserTask_1_SkippableInputX</bpmn2:targetRef>
            <bpmn2:assignment id="_xAwkVD7LEDiUWpRQWNPiuQ">
              <bpmn2:from xsi:type="bpmn2:tFormalExpression" id="_xAwkVT7LEDiUWpRQWNPiuQ">true</bpmn2:from>
              <bpmn2:to xsi:type="bpmn2:tFormalExpression" id="_xAwkVj7LEDiUWpRQWNPiuQ">UserTask_1_SkippableInputX</bpmn2:to>
            </bpmn2:assignment>
          </bpmn2:dataInputAssociation>
          <bpmn2:dataInputAssociation id="_xAwkVz7LEDiUWpRQWNPiuQ">
            <bpmn2:targetRef>UserTask_1_PriorityInputX</bpmn2:targetRef>
            <bpmn2:assignment id="_xAwkWD7LEDiUWpRQWNPiuQ">
              <bpmn2:from xsi:type="bpmn2:tFormalExpression" id="_xAwkWT7LEDiUWpRQWNPiuQ">1</bpmn2:from>
              <bpmn2:to xsi:type="bpmn2:tFormalExpression" id="_xAwkWj7LEDiUWpRQWNPiuQ">UserTask_1_PriorityInputX</bpmn2:to>
            </bpmn2:assignment>
          </bpmn2:dataInputAssociation>
        </bpmn2:userTask>
        <bpmn2:endEvent id="EndEvent_1" name="End Event 1">
          <bpmn2:extensionElements>
            <drools:metaData name="elementname">
              <drools:metaValue>End Event 1</drools:metaValue>
            </drools:metaData>
          </bpmn2:extensionElements>
          <bpmn2:incoming>SequenceFlow_4</bpmn2:incoming>
        </bpmn2:endEvent>
        <bpmn2:endEvent id="EndEvent_2" name="End Event 2">
          <bpmn2:extensionElements>
            <drools:metaData name="elementname">
              <drools:metaValue>End Event 2</drools:metaValue>
            </drools:metaData>
          </bpmn2:extensionElements>
          <bpmn2:incoming>SequenceFlow_5</bpmn2:incoming>
        </bpmn2:endEvent>
      </bpmn2:process>
      <bpmndi:BPMNDiagram>
        <bpmndi:BPMNPlane bpmnElement="persons">
          <bpmndi:BPMNShape id="shape_EndEvent_2" bpmnElement="EndEvent_2">
            <dc:Bounds height="56" width="56" x="622" y="212"/>
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="shape_EndEvent_1" bpmnElement="EndEvent_1">
            <dc:Bounds height="56" width="56" x="622" y="112"/>
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="shape_UserTask_1" bpmnElement="UserTask_1">
            <dc:Bounds height="50" width="110" x="465" y="105"/>
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="shape_ExclusiveGateway_1" bpmnElement="ExclusiveGateway_1">
            <dc:Bounds height="56" width="56" x="365" y="103"/>
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="shape_BusinessRuleTask_1" bpmnElement="BusinessRuleTask_1">
            <dc:Bounds height="50" width="110" x="180" y="103"/>
          </bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="shape_StartEvent_1" bpmnElement="StartEvent_1">
            <dc:Bounds height="56" width="56" x="97" y="110"/>
          </bpmndi:BPMNShape>
          <bpmndi:BPMNEdge id="edge_shape_ExclusiveGateway_1_to_shape_EndEvent_2" bpmnElement="SequenceFlow_5">
            <di:waypoint x="390" y="153"/>
            <di:waypoint x="390" y="230"/>
            <di:waypoint x="622" y="230"/>
          </bpmndi:BPMNEdge>
          <bpmndi:BPMNEdge id="edge_shape_UserTask_1_to_shape_EndEvent_1" bpmnElement="SequenceFlow_4">
            <di:waypoint x="575" y="130"/>
            <di:waypoint x="598" y="130"/>
            <di:waypoint x="622" y="130"/>
          </bpmndi:BPMNEdge>
          <bpmndi:BPMNEdge id="edge_shape_ExclusiveGateway_1_to_shape_UserTask_1" bpmnElement="SequenceFlow_3">
            <di:waypoint x="415" y="128"/>
            <di:waypoint x="440" y="128"/>
            <di:waypoint x="440" y="130"/>
            <di:waypoint x="465" y="130"/>
          </bpmndi:BPMNEdge>
          <bpmndi:BPMNEdge id="edge_shape_BusinessRuleTask_1_to_shape_ExclusiveGateway_1" bpmnElement="SequenceFlow_2">
            <di:waypoint x="290" y="128"/>
            <di:waypoint x="327" y="128"/>
            <di:waypoint x="365" y="128"/>
          </bpmndi:BPMNEdge>
          <bpmndi:BPMNEdge id="edge_shape_StartEvent_1_to_shape_BusinessRuleTask_1" bpmnElement="SequenceFlow_1">
            <di:waypoint x="133" y="128"/>
            <di:waypoint x="156" y="128"/>
            <di:waypoint x="180" y="128"/>
          </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
      </bpmndi:BPMNDiagram>
      <bpmn2:relationship id="_xAznoD7LEDiUWpRQWNPiuQ" type="BPSimData">
        <bpmn2:extensionElements>
          <bpsim:BPSimData>
            <bpsim:Scenario id="default" name="Simulationscenario">
              <bpsim:ScenarioParameters/>
              <bpsim:ElementParameters elementRef="UserTask_1">
                <bpsim:TimeParameters>
                  <bpsim:ProcessingTime>
                    <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
                  </bpsim:ProcessingTime>
                </bpsim:TimeParameters>
                <bpsim:ResourceParameters>
                  <bpsim:Availability>
                    <bpsim:FloatingParameter value="0"/>
                  </bpsim:Availability>
                  <bpsim:Quantity>
                    <bpsim:FloatingParameter value="0"/>
                  </bpsim:Quantity>
                </bpsim:ResourceParameters>
                <bpsim:CostParameters>
                  <bpsim:UnitCost>
                    <bpsim:FloatingParameter value="0"/>
                  </bpsim:UnitCost>
                </bpsim:CostParameters>
              </bpsim:ElementParameters>
              <bpsim:ElementParameters elementRef="BusinessRuleTask_1">
                <bpsim:TimeParameters>
                  <bpsim:ProcessingTime>
                    <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
                  </bpsim:ProcessingTime>
                </bpsim:TimeParameters>
                <bpsim:ResourceParameters>
                  <bpsim:Availability>
                    <bpsim:FloatingParameter value="0"/>
                  </bpsim:Availability>
                  <bpsim:Quantity>
                    <bpsim:FloatingParameter value="0"/>
                  </bpsim:Quantity>
                </bpsim:ResourceParameters>
                <bpsim:CostParameters>
                  <bpsim:UnitCost>
                    <bpsim:FloatingParameter value="0"/>
                  </bpsim:UnitCost>
                </bpsim:CostParameters>
              </bpsim:ElementParameters>
              <bpsim:ElementParameters elementRef="StartEvent_1">
                <bpsim:TimeParameters>
                  <bpsim:ProcessingTime>
                    <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
                  </bpsim:ProcessingTime>
                </bpsim:TimeParameters>
              </bpsim:ElementParameters>
            </bpsim:Scenario>
          </bpsim:BPSimData>
        </bpmn2:extensionElements>
        <bpmn2:source>_xAcbQD7LEDiUWpRQWNPiuQ</bpmn2:source>
        <bpmn2:target>_xAcbQD7LEDiUWpRQWNPiuQ</bpmn2:target>
      </bpmn2:relationship>
    </bpmn2:definitions>

    To create this example BPMN process in VSCode using the Kogito process designer, follow these steps:

    1. In the upper-right corner of the process designer, click the Properties icon and under Process Data, add a process variable with the name person and the type org.acme.kogito.model.Person.

    2. In the left toolbar, select ActivitiesBusiness Rule, drag the task to the canvas, and link to it from the start event.

    3. Select the business rule task and define the following properties:

      • General: Name the rule task Evaluate person.

      • Implementation/Execution: Set the following values:

        • Rule Language: DMN

        • Namespace: https://kiegroup.org/dmn/_52CEF9FD-9943-4A89-96D5-6F66810CA4C1

        • Decision Name: Is Adult

        • DMN Model Name: PersonDecisions

      • Data Assignments: Add a data input and a data output, both with the name person, with the type org.acme.kogito.model.Person, and with the source and target person.

    4. In the left toolbar, select GatewaysExclusive, drag the gateway to the canvas, and link to it from the rule task.

    5. In the left toolbar, select ActivitiesUser, drag the user task to the canvas, and link to it from the gateway.

    6. Select the user task and define the following properties:

      • General: Name the user task Special handling for children.

      • Implementation/Execution: Set the task name to ChildrenHandling, and add a data input with the name person, the type org.acme.kogito.model.Person, and the source person.

    7. In the left toolbar, select End EventsEnd, drag two end events to the canvas, and link to one end event from the user task and to the other end event from the gateway.

    8. Save the BPMN process file.

2.3.1. Using DRL rule units as an alternative decision service

As an alternative to using Decision Model and Notation (DMN) to define this example decision service, you can also use a Drools Rule Language (DRL) file implemented as a rule unit.

Rule units are groups of data sources, global variables, and DRL rules that function together for a specific purpose. You can use rule units to partition a rule set into smaller units, bind different data sources to those units, and then execute the individual unit. Rule units are an enhanced alternative to rule-grouping DRL attributes such as rule flow groups, rule agenda groups, or activation groups for execution control. For more information about rule units, see Rule units in DRL.

Procedure
  1. In the src/main/resources/org/acme/kogito folder that you created for this example, instead of using a DMN file, add the following PersonRules.drl file:

    Example person DRL file
    package org.acme.kogito.model
    unit PersonRules;
    
    import org.acme.kogito.model.Person;
    
    rule isAdult
    	when
    		$person: /person[ age > 18 ]
    	then
        modify($person) {
        	setAdult(true)
        };
    end

    This example rule determines that any person who is older than 18 is classified as an adult. The rule file also declares that the rule belongs to the rule unit PersonRules. This is the rule unit that you define as part of the business rule task in the example BPMN process. When you build the project, the rule unit is generated and associated with the DRL file.

    The rule also defines the condition using OOPath notation. OOPath is an object-oriented syntax extension of XPath that is designed for browsing graphs of objects in DRL rule condition constraints. OOPath uses the compact notation from XPath for navigating through related elements while handling collections and filtering constraints, and is specifically useful for graphs of objects.

    You can also rewrite the same rule condition in a more explicit form using the traditional rule pattern syntax, as shown in the following example:

    Example person DRL file using traditional notation
    package org.acme.kogito.model
    unit PersonRules;
    
    import org.acme.kogito.model.Person;
    
    rule isAdult
    	when
    		$person: Person(age > 18) from person
    	then
        modify($person) {
        	setAdult(true)
        };
    end
  2. In the src/main/resources/org/acme/kogito folder, use a BPMN 2.0 process modeler to open the PersonProcess.bpmn2 process diagram that you created.

  3. Select the Evaluate person business rule task and for the Implementation/Execution property, set the rule language to DRL (instead of DMN) and the rule flow group to unit:org.acme.kogito.PersonRules.

    This rule unit syntax in the Rule Flow Group field specifies that you are using the org.acme.kogito.PersonRules rule unit instead of a traditional rule flow group. This is the rule unit that you referenced in the example DRL file. When you build the project, the business process implicitly declares the rule unit as part of the business rule task to execute the DRL file.

  4. Save the process file to update the model.

2.4. Running a Kogito service

After you design the business decisions and processes for your Kogito service, you can run your Quarkus or Spring Boot application in one of the following modes:

  • Development mode: For local testing. On Quarkus, development mode also offers live reload of your processes and rules in your running applications for advanced debugging.

  • JVM mode: For compatibility with a Java Virtual Machine (JVM).

  • Native mode: (Quarkus only, requires GraalVM) For direct binary execution as native code.

Procedure

In a command terminal, navigate to the project with your Kogito service and run one of the following commands, depending on your preferred run mode and application environment:

  • For development mode:

    On Quarkus
    mvn clean compile quarkus:dev
    On Sprint Boot
    mvn clean compile spring-boot:run
  • For JVM mode:

    On Quarkus and Spring Boot
    mvn clean package
    java -jar target/sample-kogito-1.0-SNAPSHOT-runner.jar
  • For native mode (requires GraalVM):

    On Quarkus only
    mvn clean package -Dnative
    ./target/sample-kogito-1.0-SNAPSHOT-runner

2.5. Testing a Kogito service

After your Kogito service is running, you can test the service by sending REST API requests to execute your services according to how you set up the application.

This example tests the /persons REST API endpoint that is automatically generated based on the PersonProcess.bpmn2 business process, according to the decisions in the PersonDecisions.dmn file (or the rules in the PersonRules.drl file if you used a DRL rule unit).

For this example, can use a REST client or curl utility to send requests with the following components:

  • URL: http://localhost:8080/persons

  • HTTP headers:

    • accept: application/json

    • content-type: application/json

  • HTTP methods: GET, POST, PUT, or DELETE

Example POST request body to add an adult
{
  "person": {
    "name": "John Quark",
    "age": 20
  }
}
Example curl command to add an adult
curl -X POST http://localhost:8080/persons -H 'content-type: application/json' -H 'accept: application/json' -d '{"person": {"name":"John Quark", "age": 20}}'
Example response
{
  "id": "b7d993ac-272d-43c7-8245-d4df96d9ab6c",
  "person": {
    "name": "John Quark",
    "age": 20,
    "adult": true
  }
}

This example procedure uses curl commands for convenience.

Procedure

In a command terminal window that is separate from your running application, navigate to the project with your Kogito service and use any of the following curl commands with JSON requests to interact with your running service:

On Spring Boot, you might need to modify how your application exposes API endpoints in order for these example requests to function. For more information, see the README file included in the example Spring Boot project that you created.
  • Add an adult person:

    Example request
    curl -X POST http://localhost:8080/persons -H 'content-type: application/json' -H 'accept: application/json' -d '{"person": {"name":"John Quark", "age": 20}}'
    Example response
    {"id":"b7d993ac-272d-43c7-8245-d4df96d9ab6c","person":{"name":"John Quark","age":20,"adult":true}}
  • Add an underage person:

    Example request
    curl -X POST http://localhost:8080/persons -H 'content-type: application/json' -H 'accept: application/json' -d '{"person": {"name":"Jenny Quark", "age": 15}}'
    Example response
    {"id":"d3d06fa6-3826-48ca-8bdf-6ff424ea55ab","person":{"name":"Jenny Quark","age":15,"adult":false}}
  • View active process instances:

    Example request
    curl -X GET http://localhost:8080/persons -H 'content-type: application/json' -H 'accept: application/json'
    Example response
    [{"id":"c2579bd4-a192-4cae-a6b8-f28151e9c992","person":{"name":"Jenny Quark","age":15,"adult":false}}]
  • View process instance details using the returned process UUID:

    Example request
    curl -X GET http://localhost:8080/persons/d3d06fa6-3826-48ca-8bdf-6ff424ea55ab/tasks -H 'content-type: application/json' -H 'accept: application/json'
    Example response (JSON)
    {"59c48f97-4640-4147-9d65-3f889c79bc33":"ChildrenHandling"}
  • View task instance details using the returned process and task UUIDs:

    Example request
    curl -X GET http://localhost:8080/persons/d3d06fa6-3826-48ca-8bdf-6ff424ea55ab/ChildrenHandling/59c48f97-4640-4147-9d65-3f889c79bc33 -H 'content-type: application/json' -H 'accept: application/json'
    Example response
    {"person":{"name":"Jenny Quark","age":15,"adult":false},"id":"59c48f97-4640-4147-9d65-3f889c79bc33","name":"ChildrenHandling"}
  • Complete the evaluation using the returned UUIDs:

    Example request
    curl -X POST http://localhost:8080/persons/d3d06fa6-3826-48ca-8bdf-6ff424ea55ab/ChildrenHandling/59c48f97-4640-4147-9d65-3f889c79bc33 -H 'content-type: application/json' -H 'accept: application/json' -d '{}'
    Example response
    {"id":"d3d06fa6-3826-48ca-8bdf-6ff424ea55ab","person":{"name":"Jenny Quark","age":15,"adult":false}}

2.6. Example applications with Kogito services

Kogito includes several example applications with Kogito services to help you develop your own applications. The following list describes some of the examples provided with Kogito. For the full list of examples and instructions for using them, see the kogito-examples page in GitHub.

  • dmn-quarkus-example and dmn-springboot-example: A decision service (on Quarkus or Spring Boot) that uses DMN to determine driver penalty and suspension based on traffic violations

  • drools-quarkus-example: A set of decision services that use DRL and XLS spreadsheet decision tables to determine a person’s adult status, for deployment locally or on OpenShift

  • drools-quarkus-unit-example: A set of decision services that use DRL with rule units to determine a person’s adult status, for deployment locally or on OpenShift

  • drools-polyglot-example: A scenario that uses plain Java or JavaScript to define a business rule as a demonstration of GraalVM capabilities with Drools

  • ruleunit-quarkus-example and ruleunit-springboot-example: A decision service (on Quarkus or Spring Boot) that uses DRL with rule units to validate a loan application and that exposes REST operations to view application status

  • jbpm-quarkus-helloworld: A Hello World process service on Quarkus

  • jbpm-quarkus-example and jbpm-springboot-example: A process service (on Quarkus or Spring Boot) for ordering items and that exposes REST operations to create new orders or to list and delete active orders

  • onboarding-example: A combination of a process service and two decision services that use DMN and DRL for onboarding new employees

3. Deploying Kogito services on OpenShift

As a developer of business processes and rules, you can deploy Kogito services on OpenShift for cloud implementation. The Kogito Operator automates many of the deployment steps for you or guides you through the deployment process. You can use the Kogito command-line interface (CLI) to interact with the Kogito Operator for deployment tasks.

Prerequisites
  • OpenShift 4.2 or later is installed.

  • The OpenShift project for the deployment is created.

3.1. Kogito on OpenShift

You can deploy Kogito services on OpenShift for cloud implementation. In this architecture, Kogito services are deployed as OpenShift pods that you can scale up and down individually to provide as few or as many containers as required for a particular service. You can use standard OpenShift methods to manage the pods and balance the load.

To help you deploy your services on OpenShift, Kogito provides the following resources:

  • Kogito Operator: An operator that guides you through the deployment process. The Kogito Operator uses the Operator Framework and automates many of the deployment steps for you. For example, you can provide the Operator a link to the Git repository that contains your application, and the operator can pull the project, build it (including native compilation, if applicable), and deploy the resulting services.

  • Kogito command-line interface (CLI): A CLI tool that enables you to interact with the Kogito Operator for deployment tasks. The Kogito CLI also enables you to deploy Kogito services from source instead of relying on custom resources and YAML files. You can use the Kogito CLI as a command-line alternative for deploying Kogito services without the OpenShift web console.

3.2. Deploying Kogito services on OpenShift using the OpenShift web console

After you create your Kogito services as part of a business application, you can use the OpenShift web console to deploy your services. As an aid to deploy your services, you can also use the Kogito Operator, which guides you through the deployment process. The Kogito Operator uses the Operator Framework and automates many of the deployment steps for you. For example, you can provide the Operator a link to the Git repository that contains your application, and the operator can pull the project, build it (including native compilation, if applicable), and deploy the resulting services.

Prerequisites
  • The application with your Kogito services is in a Git repository.

  • You have access to the OpenShift web console with cluster-admin permissions.

Procedure
  1. In the OpenShift web console, go to OperatorsOperatorHub in the left menu, search for and select Kogito Operator, and follow the on-screen instructions to install the latest operator version.

  1. After you install the Kogito Operator, in the OpenShift web console, go to OperatorsInstalled Operators and select Kogito Operator.

  2. In the operator page, select the Kogito Service tab and click Create Kogito App to create the Kogito service definition.

    Image of Kogito service page in web console
    Figure 11. Create a Kogito service definition
  3. In the application window, drag and drop a YAML or JSON file that contains your service definition, or manually define the service data in the application window.

    At a minimum, define the application configurations shown in the following example YAML file:

    Example YAML definition for an application with Kogito services
    apiVersion: app.kiegroup.org/v1alpha1 # Kogito API for this service
    kind: KogitoApp  # Application type
    metadata:
      name: example-quarkus  # Application name
      namespace: kogito  # OpenShift project namespace
    spec:
      build:
        gitSource:
          uri: 'https://github.com/kiegroup/kogito-examples'  # Git repository containing application
          contextDir: jbpm-quarkus-example  # Git folder location of application
        imageVersion: 0.8.0-rc1  # Kogito Operator version

    If you have an internal Maven repository, you can use it as a Maven mirror service and specify the Maven mirror URL in your Kogito service definition to substantially shorten build time:

    mavenMirrorURL: http://nexus3-nexus.apps-crc.testing/repository/maven-public/
  4. After you define your application data, click Create to generate the Kogito service.

    Your application is listed in the Kogito service page:

    Image of Kogito service listed in web console
    Figure 12. New Kogito service instance

    You can select the application name to view or modify application settings and YAML details:

    Image of Kogito service details page in web console
    Figure 13. View Kogito service details
  5. In the left menu of the web console, go to BuildsBuilds to view the status of your application build.

    You can select a specific build to view build details:

    Image of Kogito service build details page in web console
    Figure 14. View Kogito service build details
  6. After the application build is complete, go to WorkloadsDeployment Configs to view the application deployment configurations, pod status, and other details.

    You can select the application name to increase or decrease the pod count or modify deployment settings:

    Image of Kogito service deployment details page in web console
    Figure 15. View Kogito service deployment details
  7. After your Kogito service is deployed, in the left menu of the web console, go to NetworkingRoutes to view the access link to the deployed application.

    You can select the application name to view or modify route settings:

    Image of Kogito service route details page in web console
    Figure 16. View Kogito service route details

    With the application route, you can integrate your Kogito services with your business automation solutions as needed.

3.3. Deploying Kogito services on OpenShift using the Kogito CLI

The Kogito command-line interface (CLI) enables you to interact with the Kogito Operator for deployment tasks. The Kogito CLI also enables you to deploy Kogito services from source instead of relying on custom resources and YAML files. You can use the Kogito CLI as a command-line alternative for deploying Kogito services without the OpenShift web console.

Prerequisites
  • The oc OpenShift CLI is installed. For oc installation instructions, see the OpenShift documentation.

  • You have OpenShift permissions to create resources in a given namespace.

Procedure
  1. Download the latest version of the Kogito CLI distribution from GitHub.

  2. Extract the kogito-RELEASE binary file:

    • On Linux: In a command terminal, navigate to the directory where you downloaded the kogito-RELEASE binary file and run the following command to extract the contents:

      Extract the Kogito CLI distribution
      tar -xvf kogito-RELEASE.tar.gz
    • On Windows: In your file browser, navigate to the directory where you downloaded the kogito-RELEASE binary file and extract the ZIP file.

    The kogito executable file appears.

  3. Move the extracted kogito file to an existing directory in your PATH variable, such as /usr/local/bin:

    • On Linux: In a command terminal, run the following command:

      Move the kogito file
      cp /PATH_TO_KOGITO /usr/local/bin
    • On Windows: In your file browser, move the extracted directory to the new location.

  4. With the Kogito CLI now installed, run the following commands to deploy your Kogito services on OpenShift from source:

    Example Kogito service deployment from existing namespace
    # Uses the provisioned namespace in your OpenShift cluster
    $ kogito use-project PROJECT_NAME
    
    # Deploys a new Kogito service from source
    $ kogito deploy-service example-quarkus https://github.com/kiegroup/kogito-examples --context-dir jbpm-quarkus-example
    The first time that you use the Kogito CLI to interact with a project or service, the Kogito Operator is automatically installed and used to execute the relevant tasks.

    Alternatively, you can generate a new namespace in your cluster during deployment:

    Example Kogito service deployment from new namespace
    # Creates a new namespace in your cluster
    $ kogito new-project NEW_PROJECT_NAME
    
    # Deploys a new Kogito service from source
    $ kogito deploy-service example-quarkus https://github.com/kiegroup/kogito-examples --context-dir jbpm-quarkus-example

    You can also use the following abbreviated syntax for service deployment:

    Abbreviated command for Kogito service deployment
    $ kogito deploy-service example-quarkus https://github.com/kiegroup/kogito-examples --context-dir jbpm-quarkus-example --project PROJECT_NAME

3.4. Travel agency tutorial for Kogito services on OpenShift

The Kogito travel agency tutorial in GitHub contains a sequence of example applications with Kogito services, each of them increasing in complexity. These travel agency applications illustrate many of the advanced configuration options you can use whether you are deploying services locally or on OpenShift, such as process persistence with Infinispan, messaging with Kafka, and application data indexing with the Data Index Service.

For more information about each example application, see the README file in the relevant application folder.

This tutorial demonstrates the following two related applications:

  • 06-kogito-travel-agency: Application for determining travel plans

  • 06-kogito-visas: Application for managing travel visas, if required

These two applications communicate through events. The travel agency service sends visa applications for travelers that require visas to visit a given country, and based on a visa application evaluation, the visa service responds with the visa approval or rejection.

The applications expose REST API endpoints that are generated from the Business Process Model and Notation 2.0 (BPMN2) business process definitions in the services. Internally, the applications communicate using Apache Kafka messaging. The logic to interact with Kafka to produce and consume messages is also generated from the BPMN2 process definitions.

Tutorial objectives
  • Deploy an application with advanced Kogito services, including supporting services and infrastructure.

  • Deploy Kogito infrastructure (Infinispan, Kafka, and Data Index Service) using the Kogito Operator and Kogito CLI.

  • Deploy Kogito application and service definitions using the Kogito CLI.

  • Use binary builds to deploy Kogito services on OpenShift.

Prerequisites
  • VSCode IDE is installed.

  • The Kogito VSCode extension is installed and enabled in your VSCode IDE.

  • OpenShift 4.2 or later is installed.

  • The oc OpenShift CLI is installed. For oc installation instructions, see the OpenShift documentation.

  • You have access to the OpenShift web console with cluster-admin permissions.

  • The Kogito command-line interface (CLI) is installed from the latest Kogito CLI distribution.

  • Git is installed.

  • JDK 11 or later is installed.

  • Apache Maven 3.5.3 or later is installed.

  • GraalVM is installed (recommended).

3.4.1. Cloning the travel agency tutorial Git repository

For this travel agency tutorial, you need local access to the example applications, so you must first clone the travel agency tutorial Git repository to your local system.

Procedure

In a command terminal, navigate to a directory where you want to store the Kogito travel agency applications and enter the following command to clone the repository:

Cloning the travel agency tutorial repository
git clone https://github.com/kiegroup/kogito-travel-agency-tutorial.git

The cloned Kogito-travel-agency-tutorial repository contains a sequence of example applications with Kogito services, each of them increasing in complexity.

The following applications are the examples you need for this tutorial:

  • 06-kogito-travel-agency: Application for determining travel plans

  • 06-kogito-visas: Application for managing travel visas, if required

3.4.2. Configuring access to your OpenShift environment

To complete the travel agency tutorial, you must ensure that you have proper access to both the OpenShift web console and to the oc CLI.

You can use different types of OpenShift 4.x environments, such as a full OpenShift cluster or a small CodeReady Containers environment. However, the OpenShift environment must have access to the public Internet in order to be able to pull in the required container images and build artifacts.
Procedure
  1. Log in to the OpenShift web console and in the upper-right corner of the screen, select your profile and click Copy Login Command.

  2. In the new window that appears, log in again to re-authenticate your user and then click Display Token.

  3. Copy the oc login command and enter it in a command terminal:

    Example oc CLI login token
    oc login --token=OPENSHIFT_TOKEN --server=https://WEB_CONSOLE_SERVER

    If your authentication fails or you do not have cluster-admin permissions, contact your OpenShift administrator.

3.4.3. Creating an OpenShift project and installing the Kogito Operator using the Kogito CLI

To set up an example Kogito application for deployment on OpenShift, you must create a project (namespace) in OpenShift in which you can install the application and the Kogito Operator. The Kogito Operator uses the Operator Framework and automates many of the deployment steps for you. The first time that you use the Kogito CLI to interact with a project or service, the Kogito Operator is automatically installed and used to execute the relevant tasks.

You can create the project and install the Kogito Operator using the OpenShift web console or using the Kogito CLI. This example uses the Kogito CLI.

Procedure
  1. In a command terminal, with the oc CLI connected to your OpenShift environment, enter the following command to create the OpenShift project:

    Creating the OpenShift project
    $ oc new-project kogito-travel-agency --display-name="Kogito Travel Agency" --description="Kogito Travel Agency application."
  2. Enter the following command to connect the new project to the Kogito CLI tooling:

    Connecting to the new project
    $ kogito use-project
    Project set to 'kogito-travel-agency'

    If you do not have cluster-admin permissions and another user created the kogito-travel-agency project for you, enter the following command to connect to the existing project:

    kogito use-project kogito-travel-agency

    The kogito use-project command also automatically installs the following components:

    • Kogito Operator: Provides automation for deployment on OpenShift

    • Infinispan Operator: Provides persistence infrastructure for Kogito services

    • Strimzi Operator: Provides messaging infrastructure for Kogito services

    • Keycloak Operator: Provides security and single sign-on infrastructure for Kogito services

    After you create the OpenShift project and install the Kogito Operator using the Kogito CLI, the operator is also listed in the OpenShift web console in OperatorsInstalled Operators.

    Image of installed operators in web console
    Figure 17. Installed operators in web console

3.4.4. Enabling Infinispan persistence for Kogito services

Kogito supports runtime persistence for process data in your services. Kogito persistence is based on Infinispan and enables you to configure key-value storage definitions to persist data, such as active nodes and process instance variables, so that the data is preserved across application restarts.

The Kogito Operator uses the Infinispan Operator to deploy and manage the Infinispan infrastructure in a Kogito project. For optimal Kogito deployment on OpenShift, enable Inifinispan for your Kogito services. You can install the Infinispan infrastructure using the Kogito Operator UI in the OpenShift web console or using the Kogito CLI. This example uses the Kogito CLI to install Infinispan infrastructure and the Kogito Operator UI in the web console to verify that the infrastructure is enabled.

Instead of explicitly enabling Infinispan persistence, you can install the Kogito Data Index Service to automatically generate the required Infinispan infrastructure for the Kogito services. However, for the first Kogito services that you deploy, consider following this procedure to create your infrastructure manually and to better understand Kogito deployment features.
Procedure
  1. In a command terminal, enter the following command to install the Infinispan infrastructure for the Kogito services:

    Installing Infinispan infrastructure
    kogito install infinispan
  2. In the OpenShift web console, use the left menu to navigate to the following windows to verify the installed Infinispan infrastructure:

    • OperatorsInstalled OperatorsKogito OperatorKogito Infra: A new kogito-infra custom resource is listed.

      Image of Kogito Infras page in web console
      Figure 18. Kogito infrastructure resource for Infinispan
    • OperatorsInstalled OperatorsInfinispanInfinispan Cluster: A new kogito-infinispan custom resource is listed.

      Image of Infinispan Cluster page in web console
      Figure 19. Infinispan cluster resource
    • WorkloadsStateful Sets: A new kogito-infinispan stateful set is deployed.

      Image of Stateful Sets page in web console
      Figure 20. Stateful set for Infinispan

3.4.5. Enabling Kafka messaging for Kogito services

Kogito supports the MicroProfile Reactive Messaging specification for messaging in your services. Kogito messaging is based on Apache Kafka and enables you to configure messages as either input or output of business process execution.

The Kogito Operator uses the Strimzi Operator to deploy and manage the Kafka infrastructure in a Kogito project. For optimal Kogito deployment on OpenShift, enable Kafka messaging for your Kogito services. You can install the Kafka infrastructure using the Kogito Operator UI in the OpenShift web console or using the Kogito CLI. This example uses the Kogito CLI to install the Kafka infrastructure and the Kogito Operator UI in the web console to verify that the infrastructure is enabled.

Instead of explicitly enabling Kafka messaging, you can install the Kogito Data Index Service to automatically generate the required Kafka infrastructure for the Kogito Operator. However, for the first Kogito services that you deploy, consider following this procedure to create your infrastructure manually and to better understand Kogito deployment features.
Procedure
  1. In a command terminal, enter the following command to install the Kafka infrastructure for the Kogito services:

    Installing Kafka infrastructure
    kogito install kafka
  2. In the OpenShift web console, use the left menu to navigate to the following windows to verify the installed Kafka infrastructure:

    • OperatorsInstalled OperatorsKogito OperatorKogito Infra: Select the kogito-infra custom resource and note that the Install Kafka option is enabled.

      Image of Kogito infra details in web console
      Figure 21. Kafka enabled
    • OperatorsInstalled OperatorsStrimziKafka: A new kogito-kafka custom resource is listed.

      Image of Kafkas page in web console
      Figure 22. Kafka custom resource
    • WorkloadsStateful Sets: New kogito-kafka-kafka and kogito-kafka-zookeeper stateful sets are deployed.

      Image of Stateful Sets page in web console
      Figure 23. Stateful sets for Kafka

3.4.6. Enabling the Data Index Service for Kogito services

Kogito supports a Data Index Service that stores all Kogito events related to processes, tasks, and domain data. The Data Index Service uses Kafka messaging to consume CloudEvents messages from Kogito services, indexes the returned data for future GraphQL queries, and stores the data in the Infinispan persistence store. The Data Index Service is at the core of all Kogito search, insight, and management capabilities.

The Kogito Operator uses the Data Index Service for data management in a Kogito project. For optimal Kogito deployment on OpenShift, enable the Data Index Service for your Kogito services. You can install the Data Index Service using the Kogito Operator UI in the OpenShift web console or using the Kogito CLI. This example uses the Kogito CLI to install the Data Index Service and the Kogito Operator UI in the web console to verify that the service is enabled.

Procedure
  1. In a command terminal, enter the following command to install the Kogito Data Index Service for the Kogito services:

    Installing Data Index Service
    kogito install data-index

    When you enter this example command, the Kogito Operator verifies that the required Infinispan and Kafka infrastructures exist and provisions the Data Index Service to connect to your existing infrastructures. If the infrastructures do not exist, the Kogito Operator automatically deploys new infrastructures and connects to them.

    Due to this automated infrastructure setup with the Data Index Service, you do not need to explicitly create Infinispan and Kafka infrastructures for every deployment. However, for the first Kogito services that you deploy, consider creating your infrastructure manually to better understand Kogito deployment features.
  2. In the OpenShift web console, use the left menu to navigate to the following windows to verify the installed Data Index Service:

    • OperatorsInstalled OperatorsKogito OperatorKogito Data Index: A new kogito-data-index custom resource is listed.

      Image of Kogito Data Index page in web console
      Figure 24. Data Index Service resource
    • WorkloadsDeployments: A new kogito-data-index deployment is listed.

      Image of Data Index deployment page in web console
      Figure 25. Data Index Service deployment
    • NetworkingRoutes: A new kogito-data-index route is listed.

      Image of Data Index route page in web console
      Figure 26. Data Index Service route

      You can click the Location URL to view the Kogito Data Index Service GraphQL interface (GraphiQL).

      Image of Data Index GraphiQL interface
      Figure 27. GraphQL interface for Data Index Service

3.4.7. Creating the travel agency services using the Kogito CLI

After you set up the required infrastructures for your travel agency applications, you can create the travel agency services and provision the OpenShift resources required for deployment with the binary build used in this example. You can create the services using the OpenShift web console or using the Kogito CLI. This example uses the Kogito CLI to start the services and the web console to verify that the services are running.

The travel agency example application includes the following key OpenShift resources:

  • BuildConfig: Configures the application to support a binary build in addition to a traditional OpenShift build for deployment. In a binary build, you build the application locally and push the built application to the OpenShift build to be packaged into the runtime container image. A binary build enables services to be deployed faster than a traditional OpenShift build and deployment.

  • ImageStream: Defines the set of container images identified by tags.

  • DeploymentConfig: Describes the desired state of the Kogito application as a pod template.

  • Service: Functions as a Kubernetes-internal load balancer to serve the application pods.

  • Route: Exposes the Service at a host name.

Procedure
  1. In a command terminal, enter the following commands to create Kogito services for the kogito-travel-agency and kogito-visas applications:

    Creating the travel agency service
    $ kogito create-service kogito-travel-agency
    Creating the visas service
    $ kogito create-service kogito-visas
    You can specify a Git repository location to create your services remotely instead of from a local source. However, this example uses the local applications to demonstrate how to prepare the Kogito project on a development machine for a direct push to the cloud.
  2. In the OpenShift web console, use the left menu to go to OperatorsInstalled OperatorsKogito OperatorKogito Service and verify the new kogito-travel-agency and kogito-visas services:

    Image of travel agency and visas services listed in web console
    Figure 28. New travel agency and visas services listed

    The new services are available but not yet deployed on OpenShift until you deploy the service projects from source using a binary build.

3.4.8. Deploying the travel agency services on OpenShift using a binary build

OpenShift builds can require extensive amounts of time. As a faster alternative for building and deploying your Kogito services on OpenShift, you can use a binary build. In a binary build, you build the application locally and push the built application to the an OpenShift BuildConfig configuration to be packaged into the runtime container image.

The travel agency example application includes a BuildConfig configuration to support a binary build in addition to traditional building for deployment.

Kogito also supports Source-to-Image (S2I) builds, which build the application in an OpenShift build and then pass the built application to the next OpenShift build to be packaged into the runtime container image. The Kogito S2I build configuration also enables you to build the project directly from a Git repository on the OpenShift platform.

However, this example uses the local applications to demonstrate how to prepare the Kogito project on a development machine for a direct push to the cloud.

Procedure
  1. In a command terminal, navigate to the 06-kogito-travel-agency directory in your local travel agency tutorial Git repository and build the project using Maven:

    Building the local travel agency project
    $ cd 06-kogito-travel-agency
    $ mvn clean package

    This command builds the project in standard JDK mode to package the application as a runner JAR file and include any dependencies in a libs folder.

    Alternatively, you can also build the project in native mode (requires GraalVM and SubstrateVM) to build and compile the application into a native executable for your system.

  2. Navigate to the generated target folder and verify the following resources:

    • kogito-travel-agency-1.0-SNAPSHOT.jar: Standard JAR file with only the classes and resources of the project.

    • kogito-travel-agency-1.0-SNAPSHOT-runner.jar: Executable JAR file for the project. Note that this is not an uber-JAR file because the dependencies are copied into the target/lib directory.

    • lib: Directory with project dependencies.

  3. In the generated target folder, enter the following command to deploy the travel agency application to OpenShift using a binary build:

    Deploying to OpenShift using binary build
    $ oc start-build kogito-travel-agency --from-dir=target
    Output
    Uploading directory "/target" as binary input for the build ...
    ....
    Uploading finished
    build.build.openshift.io/kogito-travel-agency-1 started

    You can use the following command to check the logs of the builder pod if needed:

    Checking logs of builder pod
    $ oc logs -f build/kogito-travel-agency-1

    After the binary build is complete, the result is pushed to the kogito-travel-agency Image Stream that was created by the Kogito Operator and triggers a new deployment.

  4. In the OpenShift web console, use the left menu to navigate to the following windows to verify the deployed service:

    • WorkloadsDeployment Configs: Select the kogito-travel-agency deployment to view the application deployment configurations, pod status, and other details.

      Image of travel agency service deployment details page in web console
      Figure 29. Travel agency deployment details
    • NetworkingRoutes: Select the Location URL for the kogito-travel-agency route to view the main page of the Kogito travel agency application:

      Image of Travel Agency application main page
      Figure 30. Travel agency application interface

    After you verify that the travel agency application is deployed, you repeat the same steps to deploy the visas application.

  5. In a command terminal, navigate to the 06-kogito-visas directory in your local travel agency tutorial Git repository and build the project using Maven:

    Building the local visas project
    $ cd 06-kogito-visas
    $ mvn clean package
  6. Navigate to the generated target folder and enter the following command to deploy the visas application to OpenShift using a binary build:

    Deploying to OpenShift using binary build
    $ oc start-build kogito-visas --from-dir=target
    Output
    Uploading directory "target" as binary input for the build ...
    ....
    Uploading finished
    build.build.openshift.io/kogito-visas-1 started

    You can use the following command to check the logs of the builder pod if needed:

    Checking logs of builder pod
    $ oc logs -f build/kogito-visas-1

    After the binary build is complete, the result is pushed to the kogito-visas Image Stream that was created by the Kogito Operator and triggers a new deployment.

  7. In the OpenShift web console, use the left menu to navigate to the following windows to verify the deployed service:

    • WorkloadsDeployment Configs: Select the kogito-visas deployment to view the application deployment configurations, pod status, and other details.

      Image of visas service deployment details page in web console
      Figure 31. Visas deployment details
    • NetworkingRoutes: Select the Location URL for the kogito-visas route to view the main page of the Kogito visas application:

      Image of Visas application main page
      Figure 32. Visas application interface

    To test the deployed applications, interact with the application interfaces to create a new travel plan, or use a REST client or curl utility to send a REST request, such as the following example request body:

    Example REST request body to add a traveler and trip
    {
      "traveller": {
        "firstName": "Jan",
        "lastName": "Kowalski",
        "email": "jan@email.com",
        "nationality": "Polish",
        "address": {
          "street": "Polna",
          "city": "Krakow",
          "zipCode": "32-000",
          "country": "Poland"
        }
      },
      "trip": {
        "country": "US",
        "city": "New York",
        "begin": "2019-11-04T00:00:00.000+02:00",
        "end": "2019-11-07T00:00:00.000+02:00"
      }
    }

4. Developing decision services with Kogito

As a developer of business decisions, you can use Kogito business automation to develop decision services using Decision Model and Notation (DMN) models, Drools Rule Language (DRL) rules, XLS or XLSX spreadsheet decision tables, or a combination of all three methods.

4.1. Decision-authoring assets in Kogito

Kogito supports several assets that you can use to define business decisions for your decision service. Each decision-authoring asset has different advantages, and you might prefer to use one or a combination of multiple assets depending on your goals and needs.

The following table highlights the main decision-authoring assets supported in Kogito projects to help you decide or confirm the best method for defining decisions in your decision service.

Table 1. Decision-authoring assets supported in Kogito
Asset Highlights Authoring tools Documentation

Decision Model and Notation (DMN) models

  • Are decision models based on a notation standard defined by the Object Management Group (OMG)

  • Use graphical decision requirements diagrams (DRDs) with one or more decision requirements graphs (DRGs) to trace business decision flows

  • Use an XML schema that allows the DMN models to be shared between DMN-compliant platforms

  • Support Friendly Enough Expression Language (FEEL) to define decision logic in DMN decision tables and other DMN boxed expressions

  • Can be integrated efficiently with Business Process Model and Notation (BPMN) process models

  • Are optimal for creating comprehensive, illustrative, and stable decision flows

Kogito DMN designer in VSCode or other DMN-compliant editor

Decision Model and Notation (DMN)

DRL rules

  • Are individual rules that you define directly in .drl text files

  • Provide the most flexibility for defining rules and other technicalities of rule behavior

  • Are optimal for creating rules that require advanced DRL options, such as rule units

  • Have strict syntax requirements for rules to be compiled properly

Any integrated development environment (IDE)

Drools Rule Language (DRL)

Spreadsheet decision tables

  • Are tabular sets of rules that you define in .xls or .xlsx spreadsheet files

  • Support template keys and values for creating rule templates

  • Are optimal for business environments that already rely on decision tables for rules

  • Have strict syntax requirements for rules to be compiled properly when used in Kogito

Spreadsheet editor

Spreadsheet decision tables

4.2. Example applications with Kogito services

Kogito includes several example applications with Kogito services to help you develop your own applications. The following list describes some of the examples provided with Kogito. For the full list of examples and instructions for using them, see the kogito-examples page in GitHub.

  • dmn-quarkus-example and dmn-springboot-example: A decision service (on Quarkus or Spring Boot) that uses DMN to determine driver penalty and suspension based on traffic violations

  • drools-quarkus-example: A set of decision services that use DRL and XLS spreadsheet decision tables to determine a person’s adult status, for deployment locally or on OpenShift

  • drools-quarkus-unit-example: A set of decision services that use DRL with rule units to determine a person’s adult status, for deployment locally or on OpenShift

  • drools-polyglot-example: A scenario that uses plain Java or JavaScript to define a business rule as a demonstration of GraalVM capabilities with Drools

  • ruleunit-quarkus-example and ruleunit-springboot-example: A decision service (on Quarkus or Spring Boot) that uses DRL with rule units to validate a loan application and that exposes REST operations to view application status

  • jbpm-quarkus-helloworld: A Hello World process service on Quarkus

  • jbpm-quarkus-example and jbpm-springboot-example: A process service (on Quarkus or Spring Boot) for ordering items and that exposes REST operations to create new orders or to list and delete active orders

  • onboarding-example: A combination of a process service and two decision services that use DMN and DRL for onboarding new employees

5. Using DMN models in Kogito services

As a business analyst or business rules developer, you can use Decision Model and Notation (DMN) to model a decision service graphically in a decision requirements diagram (DRD). This diagram consists of one or more decision requirements graphs (DRGs) that trace business decisions from start to finish, with each decision node using logic defined in DMN boxed expressions such as decision tables.

Kogito provides design and runtime support for DMN 1.2 models at conformance level 3, and runtime-only support for DMN 1.1 and 1.3 models at conformance level 3. You can design your DMN models with the Kogito DMN designer in VSCode or import existing DMN models into your Kogito projects for deployment and execution. Any DMN 1.1 models that you import into your Kogito project, open in the DMN designer, and save are converted to DMN 1.2 models. DMN 1.3 models are not supported in the Kogito DMN designer.

For more information about DMN, see the Object Management Group (OMG) Decision Model and Notation specification.

5.1. Decision Model and Notation (DMN)

Decision Model and Notation (DMN) is a standard established by the Object Management Group (OMG) for describing and modeling operational decisions. DMN defines an XML schema that enables DMN models to be shared between DMN-compliant platforms and across organizations so that business analysts and business rules developers can collaborate in designing and implementing DMN decision services. The DMN standard is similar to and can be used together with the Business Process Model and Notation (BPMN) standard for designing and modeling business processes.

For more information about the background and applications of DMN, see the OMG Decision Model and Notation specification.

5.1.1. DMN conformance levels

The DMN specification defines three incremental levels of conformance in a software implementation. A product that claims compliance at one level must also be compliant with any preceding levels. For example, a conformance level 3 implementation must also include the supported components in conformance levels 1 and 2. For the formal definitions of each conformance level, see the OMG Decision Model and Notation specification.

The following list summarizes the three DMN conformance levels:

Conformance level 1

A DMN conformance level 1 implementation supports decision requirement diagrams (DRDs), decision logic, and decision tables, but decision models are not executable. Any language can be used to define the expressions, including natural, unstructured languages.

Conformance level 2

A DMN conformance level 2 implementation includes the requirements in conformance level 1, and supports Simplified Friendly Enough Expression Language (S-FEEL) expressions and fully executable decision models.

Conformance level 3

A DMN conformance level 3 implementation includes the requirements in conformance levels 1 and 2, and supports Friendly Enough Expression Language (FEEL) expressions, the full set of boxed expressions, and fully executable decision models.

Kogito provides design and runtime support for DMN 1.2 models at conformance level 3, and runtime-only support for DMN 1.1 and 1.3 models at conformance level 3. You can design your DMN models with the Kogito DMN designer in VSCode or import existing DMN models into your Kogito projects for deployment and execution. Any DMN 1.1 models that you import into your Kogito project, open in the DMN designer, and save are converted to DMN 1.2 models. DMN 1.3 models are not supported in the Kogito DMN designer.

5.1.2. DMN decision requirements diagram (DRD) components

A decision requirements diagram (DRD) is a visual representation of your DMN model. This diagram consists of one or more decision requirements graphs (DRGs) that represent a particular domain of an overall DRD. The DRGs trace business decisions using decision nodes, business knowledge models, sources of business knowledge, input data, and decision services.

The following table summarizes the components in a DRD:

Table 2. DRD components
Component Description Notation

Elements

Decision

Node where one or more input elements determine an output based on defined decision logic.

dmn decision node

Business knowledge model

Reusable function with one or more decision elements. Decisions that have the same logic but depend on different sub-input data or sub-decisions use business knowledge models to determine which procedure to follow.

dmn bkm node

Knowledge source

External authorities, documents, committees, or policies that regulate a decision or business knowledge model. Knowledge sources are references to real-world factors rather than executable business rules.

dmn knowledge source node

Input data

Information used in a decision node or a business knowledge model. Input data usually includes business-level concepts or objects relevant to the business, such as loan applicant data used in a lending strategy.

dmn input data node

Decision service

Top-level decision containing a set of reusable decisions published as a service for invocation. A decision service can be invoked from an external application or a BPMN business process.

dmn decision service node

Requirement connectors

Information requirement

Connection from an input data node or decision node to another decision node that requires the information.

dmn info connector

Knowledge requirement

Connection from a business knowledge model to a decision node or to another business knowledge model that invokes the decision logic.

dmn knowledge connector

Authority requirement

Connection from an input data node or a decision node to a dependent knowledge source or from a knowledge source to a decision node, business knowledge model, or another knowledge source.

dmn authority connector

Artifacts

Text annotation

Explanatory note associated with an input data node, decision node, business knowledge model, or knowledge source.

dmn annotation node

Association

Connection from an input data node, decision node, business knowledge model, or knowledge source to a text annotation.

dmn association connector

The following table summarizes the permitted connectors between DRD elements:

Table 3. DRD connector rules
Starts from Connects to Connection type Example

Decision

Decision

Information requirement

dmn decision to decision

Business knowledge model

Decision

Knowledge requirement

dmn bkm to decision

Business knowledge model

dmn bkm to bkm

Decision service

Decision

Knowledge requirement

dmn decision service to decision

Business knowledge model

dmn decision service to bkm

Input data

Decision

Information requirement

dmn input to decision

Knowledge source

Authority requirement

dmn input to knowledge source

Knowledge source

Decision

Authority requirement

dmn knowledge source to decision

Business knowledge model

dmn knowledge source to bkm

Knowledge source

dmn knowledge source to knowledge source

Decision

Text annotation

Association

dmn decision to annotation

Business knowledge model

dmn bkm to annotation

Knowledge source

dmn knowledge source to annotation

Input data

dmn input to annotation

The following example DRD illustrates some of these DMN components in practice:

dmn example drd
Figure 33. Example DRD: Loan prequalification

The following example DRD illustrates DMN components that are part of a reusable decision service:

dmn example drd3
Figure 34. Example DRD: Phone call handling as a decision service

In a DMN decision service node, the decision nodes in the bottom segment incorporate input data from outside of the decision service to arrive at a final decision in the top segment of the decision service node. The resulting top-level decisions from the decision service are then implemented in any subsequent decisions or business knowledge requirements of the DMN model. You can reuse DMN decision services in other DMN models to apply the same decision logic with different input data and different outgoing connections.

5.1.3. Rule expressions in FEEL

Friendly Enough Expression Language (FEEL) is an expression language defined by the Object Management Group (OMG) DMN specification. FEEL expressions define the logic of a decision in a DMN model. FEEL is designed to facilitate both decision modeling and execution by assigning semantics to the decision model constructs. FEEL expressions in decision requirements diagrams (DRDs) occupy table cells in boxed expressions for decision nodes and business knowledge models.

For more information about FEEL in DMN, see the OMG Decision Model and Notation specification.

5.1.3.1. Variable and function names in FEEL

Unlike many traditional expression languages, Friendly Enough Expression Language (FEEL) supports spaces and a few special characters as part of variable and function names. A FEEL name must start with a letter, ?, or _ element. The unicode letter characters are also allowed. Variable names cannot start with a language keyword, such as and, true, or every. The remaining characters in a variable name can be any of the starting characters, as well as digits, white spaces, and special characters such as +, -, /, *, ', and ..

For example, the following names are all valid FEEL names:

  • Age

  • Birth Date

  • Flight 234 pre-check procedure

Several limitations apply to variable and function names in FEEL:

Ambiguity

The use of spaces, keywords, and other special characters as part of names can make FEEL ambiguous. The ambiguities are resolved in the context of the expression, matching names from left to right. The parser resolves the variable name as the longest name matched in scope. You can use ( ) to disambiguate names if necessary.

Spaces in names

The DMN specification limits the use of spaces in FEEL names. According to the DMN specification, names can contain multiple spaces but not two consecutive spaces.

In order to make the language easier to use and avoid common errors due to spaces, Kogito removes the limitation on the use of consecutive spaces. Kogito supports variable names with any number of consecutive spaces, but normalizes them into a single space. For example, the variable references First Name with one space and First Name with two spaces are both acceptable in Kogito.

Kogito also normalizes the use of other white spaces, like the non-breakable white space that is common in web pages, tabs, and line breaks. From a Kogito FEEL engine perspective, all of these characters are normalized into a single white space before processing.

The keyword in

The keyword in is the only keyword in the language that cannot be used as part of a variable name. Although the specifications allow the use of keywords in the middle of variable names, the use of in in variable names conflicts with the grammar definition of for, every and some expression constructs.

5.1.3.2. Data types in FEEL

Friendly Enough Expression Language (FEEL) supports the following data types:

  • Numbers

  • Strings

  • Boolean values

  • Dates

  • Time

  • Date and time

  • Days and time duration

  • Years and months duration

  • Functions

  • Contexts

  • Ranges (or intervals)

  • Lists

The DMN specification currently does not provide an explicit way of declaring a variable as a function, context, range, or list, but Kogito extends the DMN built-in types to support variables of these types.

The following list describes each data type:

Numbers

Numbers in FEEL are based on the IEEE 754-2008 Decimal 128 format, with 34 digits of precision. Internally, numbers are represented in Java as BigDecimals with MathContext DECIMAL128. FEEL supports only one number data type, so the same type is used to represent both integers and floating point numbers.

FEEL numbers use a dot (.) as a decimal separator. FEEL does not support -INF, +INF, or NaN. FEEL uses null to represent invalid numbers.

Kogito extends the DMN specification and supports additional number notations:

  • Scientific: You can use scientific notation with the suffix e<exp> or E<exp>. For example, 1.2e3 is the same as writing the expression 1.2*10**3, but is a literal instead of an expression.

  • Hexadecimal: You can use hexadecimal numbers with the prefix 0x. For example, 0xff is the same as the decimal number 255. Both uppercase and lowercase letters are supported. For example, 0XFF is the same as 0xff.

  • Type suffixes: You can use the type suffixes f, F, d, D, l, and L. These suffixes are ignored.

Strings

Strings in FEEL are any sequence of characters delimited by double quotation marks.

Example:

"John Doe"
Boolean values

FEEL uses three-valued boolean logic, so a boolean logic expression may have values true, false, or null.

Dates

Date literals are not supported in FEEL, but you can use the built-in date() function to construct date values. Date strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document. The format is "YYYY-MM-DD" where YYYY is the year with four digits, MM is the number of the month with two digits, and DD is the number of the day.

Example:

date( "2017-06-23" )

Date objects have time equal to "00:00:00", which is midnight. The dates are considered to be local, without a timezone.

Time

Time literals are not supported in FEEL, but you can use the built-in time() function to construct time values. Time strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document. The format is "hh:mm:ss[.uuu][(+-)hh:mm]" where hh is the hour of the day (from 00 to 23), mm is the minutes in the hour, and ss is the number of seconds in the minute. Optionally, the string may define the number of milliseconds (uuu) within the second and contain a positive (+) or negative (-) offset from UTC time to define its timezone. Instead of using an offset, you can use the letter z to represent the UTC time, which is the same as an offset of -00:00. If no offset is defined, the time is considered to be local.

Examples:

time( "04:25:12" )
time( "14:10:00+02:00" )
time( "22:35:40.345-05:00" )
time( "15:00:30z" )

Time values that define an offset or a timezone cannot be compared to local times that do not define an offset or a timezone.

Date and time

Date and time literals are not supported in FEEL, but you can use the built-in date and time() function to construct date and time values. Date and time strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document. The format is "<date>T<time>", where <date> and <time> follow the prescribed XML schema formatting, conjoined by T.

Examples:

date and time( "2017-10-22T23:59:00" )
date and time( "2017-06-13T14:10:00+02:00" )
date and time( "2017-02-05T22:35:40.345-05:00" )
date and time( "2017-06-13T15:00:30z" )

Date and time values that define an offset or a timezone cannot be compared to local date and time values that do not define an offset or a timezone.

If your implementation of the DMN specification does not support spaces in the XML schema, use the keyword dateTime as a synonym of date and time.
Days and time duration

Days and time duration literals are not supported in FEEL, but you can use the built-in duration() function to construct days and time duration values. Days and time duration strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document, but are restricted to only days, hours, minutes and seconds. Months and years are not supported.

Examples:

duration( "P1DT23H12M30S" )
duration( "P23D" )
duration( "PT12H" )
duration( "PT35M" )
If your implementation of the DMN specification does not support spaces in the XML schema, use the keyword dayTimeDuration as a synonym of days and time duration.
Years and months duration

Years and months duration literals are not supported in FEEL, but you can use the built-in duration() function to construct days and time duration values. Years and months duration strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document, but are restricted to only years and months. Days, hours, minutes, or seconds are not supported.

Examples:

duration( "P3Y5M" )
duration( "P2Y" )
duration( "P10M" )
duration( "P25M" )
If your implementation of the DMN specification does not support spaces in the XML schema, use the keyword yearMonthDuration as a synonym of years and months duration.
Functions

FEEL has function literals (or anonymous functions) that you can use to create functions. The DMN specification currently does not provide an explicit way of declaring a variable as a function, but Kogito extends the DMN built-in types to support variables of functions.

Example:

function(a, b) a + b

In this example, the FEEL expression creates a function that adds the parameters a and b and returns the result.

Contexts

FEEL has context literals that you can use to create contexts. A context in FEEL is a list of key and value pairs, similar to maps in languages like Java. The DMN specification currently does not provide an explicit way of declaring a variable as a context, but Kogito extends the DMN built-in types to support variables of contexts.

Example:

{ x : 5, y : 3 }

In this example, the expression creates a context with two entries, x and y, representing a coordinate in a chart.

In DMN 1.2, another way to create contexts is to create an item definition that contains the list of keys as attributes, and then declare the variable as having that item definition type.

The Kogito DMN API supports DMN ItemDefinition structural types in a DMNContext represented in two ways:

  • User-defined Java type: Must be a valid JavaBeans object defining properties and getters for each of the components in the DMN ItemDefinition. If necessary, you can also use the @FEELProperty annotation for those getters representing a component name which would result in an invalid Java identifier.

  • java.util.Map interface: The map needs to define the appropriate entries, with the keys corresponding to the component name in the DMN ItemDefinition.

Ranges (or intervals)

FEEL has range literals that you can use to create ranges or intervals. A range in FEEL is a value that defines a lower and an upper bound, where either can be open or closed. The DMN specification currently does not provide an explicit way of declaring a variable as a range, but Kogito extends the DMN built-in types to support variables of ranges.

The syntax of a range is defined in the following formats:

range          := interval_start endpoint '..' endpoint interval_end
interval_start := open_start | closed_start
open_start     := '(' | ']'
closed_start   := '['
interval_end   := open_end | closed_end
open_end       := ')' | '['
closed_end     := ']'
endpoint       := expression

The expression for the endpoint must return a comparable value, and the lower bound endpoint must be lower than the upper bound endpoint.

For example, the following literal expression defines an interval between 1 and 10, including the boundaries (a closed interval on both endpoints):

[ 1 .. 10 ]

The following literal expression defines an interval between 1 hour and 12 hours, including the lower boundary (a closed interval), but excluding the upper boundary (an open interval):

[ duration("PT1H") .. duration("PT12H") ]

You can use ranges in decision tables to test for ranges of values, or use ranges in simple literal expressions. For example, the following literal expression returns true if the value of a variable x is between 0 and 100:

x in [ 1 .. 100 ]
Lists

FEEL has list literals that you can use to create lists of items. A list in FEEL is represented by a comma-separated list of values enclosed in square brackets. The DMN specification currently does not provide an explicit way of declaring a variable as a list, but Kogito extends the DMN built-in types to support variables of lists.

Example:

[ 2, 3, 4, 5 ]

All lists in FEEL contain elements of the same type and are immutable. Elements in a list can be accessed by index, where the first element is 1. Negative indexes can access elements starting from the end of the list so that -1 is the last element.

For example, the following expression returns the second element of a list x:

x[2]

The following expression returns the second-to-last element of a list x:

x[-2]

Elements in a list can also be counted by the function count, which uses the list of elements as the parameter.

For example, the following expression returns 4:

count([ 2, 3, 4, 5 ])

5.1.4. DMN decision logic in boxed expressions

Boxed expressions in DMN are tables that you use to define the underlying logic of decision nodes and business knowledge models in a decision requirements diagram (DRD) or decision requirements graph (DRG). Some boxed expressions can contain other boxed expressions, but the top-level boxed expression corresponds to the decision logic of a single DRD artifact. While DRDs with one or more DRGs represent the flow of a DMN decision model, boxed expressions define the actual decision logic of individual nodes. DRDs and boxed expressions together form a complete and functional DMN decision model.

The following are the types of DMN boxed expressions:

  • Decision tables

  • Literal expressions

  • Contexts

  • Relations

  • Functions

  • Invocations

  • Lists

The Kogito DMN designer does not provide boxed list expressions, but supports a FEEL list data type that you can use in boxed literal expressions. For more information about the list data type and other FEEL data types in Kogito, see Data types in FEEL.

All Friendly Enough Expression Language (FEEL) expressions that you use in your boxed expressions must conform to the FEEL syntax requirements in the OMG Decision Model and Notation specification.

5.1.4.1. DMN decision tables

A decision table in DMN is a visual representation of one or more business rules in a tabular format. You use decision tables to define rules for a decision node that applies those rules at a given point in the decision model. Each rule consists of a single row in the table, and includes columns that define the conditions (input) and outcome (output) for that particular row. The definition of each row is precise enough to derive the outcome using the values of the conditions. Input and output values can be FEEL expressions or defined data type values.

For example, the following decision table determines credit score ratings based on a defined range of a loan applicant’s credit score:

dmn decision table example
Figure 35. Decision table for credit score rating

The following decision table determines the next step in a lending strategy for applicants depending on applicant loan eligibility and the bureau call type:

dmn decision table example2
Figure 36. Decision table for lending strategy

The following decision table determines applicant qualification for a loan as the concluding decision node in a loan prequalification decision model:

dmn decision table example3
Figure 37. Decision table for loan prequalification

Decision tables are a popular way of modeling rules and decision logic, and are used in many methodologies (such as DMN) and implementation frameworks (such as Drools).

Kogito supports both DMN decision tables and Drools-native decision tables, but they are different types of assets with different syntax requirements and are not interchangeable. For more information about Drools-native decision tables in Kogito, see Spreadsheet decision tables.
Hit policies in DMN decision tables

Hit policies determine how to reach an outcome when multiple rules in a decision table match the provided input values. For example, if one rule in a decision table applies a sales discount to military personnel and another rule applies a discount to students, then when a customer is both a student and in the military, the decision table hit policy must indicate whether to apply one discount or the other (Unique, First) or both discounts (Collect Sum). You specify the single character of the hit policy (U, F, C+) in the upper-left corner of the decision table.

The following decision table hit policies are supported in DMN:

  • Unique (U): Permits only one rule to match. Any overlap raises an error.

  • Any (A): Permits multiple rules to match, but they must all have the same output. If multiple matching rules do not have the same output, an error is raised.

  • Priority (P): Permits multiple rules to match, with different outputs. The output that comes first in the output values list is selected.

  • First (F): Uses the first match in rule order.

  • Collect (C+, C>, C<, C#): Aggregates output from multiple rules based on an aggregation function.

    • Collect ( C ): Aggregates values in an arbitrary list.

    • Collect Sum (C+): Outputs the sum of all collected values. Values must be numeric.

    • Collect Min (C<): Outputs the minimum value among the matches. The resulting values must be comparable, such as numbers, dates, or text (lexicographic order).

    • Collect Max (C>): Outputs the maximum value among the matches. The resulting values must be comparable, such as numbers, dates or text (lexicographic order).

    • Collect Count (C#): Outputs the number of matching rules.

5.1.4.2. Boxed literal expressions

A boxed literal expression in DMN is a literal FEEL expression as text in a table cell, typically with a labeled column and an assigned data type. You use boxed literal expressions to define simple or complex node logic or decision data directly in FEEL for a particular node in a decision. Literal FEEL expressions must conform to FEEL syntax requirements in the OMG Decision Model and Notation specification.

For example, the following boxed literal expression defines the minimum acceptable PITI calculation (principal, interest, taxes, and insurance) in a lending decision, where acceptable rate is a variable defined in the DMN model:

dmn literal expression example2
Figure 38. Boxed literal expression for minimum PITI value

The following boxed literal expression sorts a list of possible dating candidates (soul mates) in an online dating application based on their score on criteria such as age, location, and interests:

dmn literal expression example3b
Figure 39. Boxed literal expression for matching online dating candidates
5.1.4.3. Boxed context expressions

A boxed context expression in DMN is a set of variable names and values with a result value. Each name-value pair is a context entry. You use context expressions to represent data definitions in decision logic and set a value for a desired decision element within the DMN decision model. A value in a boxed context expression can be a data type value or FEEL expression, or can contain a nested sub-expression of any type, such as a decision table, a literal expression, or another context expression.

For example, the following boxed context expression defines the factors for sorting delayed passengers in a flight-rebooking decision model, based on defined data types (tPassengerTable, tFlightNumberList):

dmn context expression example
Figure 40. Boxed context expression for flight passenger waiting list

The following boxed context expression defines the factors that determine whether a loan applicant can meet minimum mortgage payments based on principal, interest, taxes, and insurance (PITI), represented as a front-end ratio calculation with a sub-context expression:

dmn context expression example2
Figure 41. Boxed context expression for front-end client PITI ratio
5.1.4.4. Boxed relation expressions

A boxed relation expression in DMN is a traditional data table with information about given entities, listed as rows. You use boxed relation tables to define decision data for relevant entities in a decision at a particular node. Boxed relation expressions are similar to context expressions in that they set variable names and values, but relation expressions contain no result value and list all variable values based on a single defined variable in each column.

For example, the following boxed relation expression provides information about employees in an employee rostering decision:

dmn relation expression example
Figure 42. Boxed relation expression with employee information
5.1.4.5. Boxed function expressions

A boxed function expression in DMN is a parameterized boxed expression containing a literal FEEL expression, a nested context expression of an external JAVA or PMML function, or a nested boxed expression of any type. By default, all business knowledge models are defined as boxed function expressions. You use boxed function expressions to call functions on your decision logic and to define all business knowledge models.

For example, the following boxed function expression determines airline flight capacity in a flight-rebooking decision model:

dmn function expression example
Figure 43. Boxed function expression for flight capacity

The following boxed function expression contains a basic Java function as a context expression for determining absolute value in a decision model calculation:

dmn function expression example2
Figure 44. Boxed function expression for absolute value

The following boxed function expression determines a monthly mortgage installment as a business knowledge model in a lending decision, with the function value defined as a nested context expression:

dmn function expression example3
Figure 45. Boxed function expression for installment calculation in business knowledge model

The following boxed function expression uses a PMML model included in the DMN file to define the minimum acceptable PITI calculation (principal, interest, taxes, and insurance) in a lending decision:

5.1.4.6. Boxed invocation expressions

A boxed invocation expression in DMN is a boxed expression that invokes a business knowledge model. A boxed invocation expression contains the name of the business knowledge model to be invoked and a list of parameter bindings. Each binding is represented by two boxed expressions on a row: The box on the left contains the name of a parameter and the box on the right contains the binding expression whose value is assigned to the parameter to evaluate the invoked business knowledge model. You use boxed invocations to invoke at a particular decision node a business knowledge model defined in the decision model.

For example, the following boxed invocation expression invokes a Reassign Next Passenger business knowledge model as the concluding decision node in a flight-rebooking decision model:

dmn invocation example
Figure 46. Boxed invocation expression to reassign flight passengers

The following boxed invocation expression invokes an InstallmentCalculation business knowledge model to calculate a monthly installment amount for a loan before proceeding to affordability decisions:

dmn invocation example2
Figure 47. Boxed invocation expression for required monthly installment

5.1.5. DMN model example

The following is a real-world DMN model example that demonstrates how you can use decision modeling to reach a decision based on input data, circumstances, and company guidelines. In this scenario, a flight from San Diego to New York is canceled, requiring the affected airline to find alternate arrangements for its inconvenienced passengers.

First, the airline collects the information necessary to determine how best to get the travelers to their destinations:

Input data
  • List of flights

  • List of passengers

Decisions
  • Prioritize the passengers who will get seats on a new flight

  • Determine which flights those passengers will be offered

Business knowledge models
  • The company process for determining passenger priority

  • Any flights that have space available

  • Company rules for determining how best to reassign inconvenienced passengers

The airline then uses the DMN standard to model its decision process in the following decision requirements diagram (DRD) for determining the best rebooking solution:

dmn passenger rebooking drd
Figure 48. DRD for flight rebooking

Similar to flowcharts, DRDs use shapes to represent the different elements in a process. Ovals contain the two necessary input data, rectangles contain the decision points in the model, and rectangles with clipped corners (business knowledge models) contain reusable logic that can be repeatedly invoked.

The DRD draws logic for each element from boxed expressions that provide variable definitions using FEEL expressions or data type values.

Some boxed expressions are basic, such as the following decision for establishing a prioritized waiting list:

dmn context expression example
Figure 49. Boxed context expression example for prioritized wait list

Some boxed expressions are more complex with greater detail and calculation, such as the following business knowledge model for reassigning the next delayed passenger:

dmn reassign passenger
Figure 50. Boxed function expression for passenger reassignment

The following is the DMN source file for this decision model:

<dmn:definitions xmlns="https://www.drools.org/kie-dmn/Flight-rebooking" xmlns:dmn="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:feel="http://www.omg.org/spec/FEEL/20140401" id="_0019_flight_rebooking" name="0019-flight-rebooking" namespace="https://www.drools.org/kie-dmn/Flight-rebooking">
  <dmn:itemDefinition id="_tFlight" name="tFlight">
    <dmn:itemComponent id="_tFlight_Flight" name="Flight Number">
      <dmn:typeRef>feel:string</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tFlight_From" name="From">
      <dmn:typeRef>feel:string</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tFlight_To" name="To">
      <dmn:typeRef>feel:string</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tFlight_Dep" name="Departure">
      <dmn:typeRef>feel:dateTime</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tFlight_Arr" name="Arrival">
      <dmn:typeRef>feel:dateTime</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tFlight_Capacity" name="Capacity">
      <dmn:typeRef>feel:number</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tFlight_Status" name="Status">
      <dmn:typeRef>feel:string</dmn:typeRef>
    </dmn:itemComponent>
  </dmn:itemDefinition>
  <dmn:itemDefinition id="_tFlightTable" isCollection="true" name="tFlightTable">
    <dmn:typeRef>tFlight</dmn:typeRef>
  </dmn:itemDefinition>
  <dmn:itemDefinition id="_tPassenger" name="tPassenger">
    <dmn:itemComponent id="_tPassenger_Name" name="Name">
      <dmn:typeRef>feel:string</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tPassenger_Status" name="Status">
      <dmn:typeRef>feel:string</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tPassenger_Miles" name="Miles">
      <dmn:typeRef>feel:number</dmn:typeRef>
    </dmn:itemComponent>
    <dmn:itemComponent id="_tPassenger_Flight" name="Flight Number">
      <dmn:typeRef>feel:string</dmn:typeRef>
    </dmn:itemComponent>
  </dmn:itemDefinition>
  <dmn:itemDefinition id="_tPassengerTable" isCollection="true" name="tPassengerTable">
    <dmn:typeRef>tPassenger</dmn:typeRef>
  </dmn:itemDefinition>
  <dmn:itemDefinition id="_tFlightNumberList" isCollection="true" name="tFlightNumberList">
    <dmn:typeRef>feel:string</dmn:typeRef>
  </dmn:itemDefinition>
  <dmn:inputData id="i_Flight_List" name="Flight List">
    <dmn:variable name="Flight List" typeRef="tFlightTable"/>
  </dmn:inputData>
  <dmn:inputData id="i_Passenger_List" name="Passenger List">
    <dmn:variable name="Passenger List" typeRef="tPassengerTable"/>
  </dmn:inputData>
  <dmn:decision name="Prioritized Waiting List" id="d_PrioritizedWaitingList">
    <dmn:variable name="Prioritized Waiting List" typeRef="tPassengerTable"/>
    <dmn:informationRequirement>
      <dmn:requiredInput href="#i_Passenger_List"/>
    </dmn:informationRequirement>
    <dmn:informationRequirement>
      <dmn:requiredInput href="#i_Flight_List"/>
    </dmn:informationRequirement>
    <dmn:knowledgeRequirement>
      <dmn:requiredKnowledge href="#b_PassengerPriority"/>
    </dmn:knowledgeRequirement>
    <dmn:context>
      <dmn:contextEntry>
        <dmn:variable name="Cancelled Flights" typeRef="tFlightNumberList"/>
        <dmn:literalExpression>
          <dmn:text>Flight List[ Status = "cancelled" ].Flight Number</dmn:text>
        </dmn:literalExpression>
      </dmn:contextEntry>
      <dmn:contextEntry>
        <dmn:variable name="Waiting List" typeRef="tPassengerTable"/>
        <dmn:literalExpression>
          <dmn:text>Passenger List[ list contains( Cancelled Flights, Flight Number ) ]</dmn:text>
        </dmn:literalExpression>
      </dmn:contextEntry>
      <dmn:contextEntry>
        <dmn:literalExpression>
          <dmn:text>sort( Waiting List, passenger priority )</dmn:text>
        </dmn:literalExpression>
      </dmn:contextEntry>
    </dmn:context>
  </dmn:decision>
  <dmn:decision name="Rebooked Passengers" id="d_RebookedPassengers">
    <dmn:variable name="Rebooked Passengers" typeRef="tPassengerTable"/>
    <dmn:informationRequirement>
      <dmn:requiredDecision href="#d_PrioritizedWaitingList"/>
    </dmn:informationRequirement>
    <dmn:informationRequirement>
      <dmn:requiredInput href="#i_Flight_List"/>
    </dmn:informationRequirement>
    <dmn:knowledgeRequirement>
      <dmn:requiredKnowledge href="#b_ReassignNextPassenger"/>
    </dmn:knowledgeRequirement>
    <dmn:invocation>
      <dmn:literalExpression>
        <dmn:text>reassign next passenger</dmn:text>
      </dmn:literalExpression>
      <dmn:binding>
        <dmn:parameter name="Waiting List"/>
        <dmn:literalExpression>
          <dmn:text>Prioritized Waiting List</dmn:text>
        </dmn:literalExpression>
      </dmn:binding>
      <dmn:binding>
        <dmn:parameter name="Reassigned Passengers List"/>
        <dmn:literalExpression>
          <dmn:text>[]</dmn:text>
        </dmn:literalExpression>
      </dmn:binding>
      <dmn:binding>
        <dmn:parameter name="Flights"/>
        <dmn:literalExpression>
          <dmn:text>Flight List</dmn:text>
        </dmn:literalExpression>
      </dmn:binding>
    </dmn:invocation>
  </dmn:decision>
  <dmn:businessKnowledgeModel id="b_PassengerPriority" name="passenger priority">
    <dmn:encapsulatedLogic>
      <dmn:formalParameter name="Passenger1" typeRef="tPassenger"/>
      <dmn:formalParameter name="Passenger2" typeRef="tPassenger"/>
      <dmn:decisionTable hitPolicy="UNIQUE">
        <dmn:input id="b_Passenger_Priority_dt_i_P1_Status" label="Passenger1.Status">
          <dmn:inputExpression typeRef="feel:string">
            <dmn:text>Passenger1.Status</dmn:text>
          </dmn:inputExpression>
          <dmn:inputValues>
            <dmn:text>"gold", "silver", "bronze"</dmn:text>
          </dmn:inputValues>
        </dmn:input>
        <dmn:input id="b_Passenger_Priority_dt_i_P2_Status" label="Passenger2.Status">
          <dmn:inputExpression typeRef="feel:string">
            <dmn:text>Passenger2.Status</dmn:text>
          </dmn:inputExpression>
          <dmn:inputValues>
            <dmn:text>"gold", "silver", "bronze"</dmn:text>
          </dmn:inputValues>
        </dmn:input>
        <dmn:input id="b_Passenger_Priority_dt_i_P1_Miles" label="Passenger1.Miles">
          <dmn:inputExpression typeRef="feel:string">
            <dmn:text>Passenger1.Miles</dmn:text>
          </dmn:inputExpression>
        </dmn:input>
        <dmn:output id="b_Status_Priority_dt_o" label="Passenger1 has priority">
          <dmn:outputValues>
            <dmn:text>true, false</dmn:text>
          </dmn:outputValues>
          <dmn:defaultOutputEntry>
            <dmn:text>false</dmn:text>
          </dmn:defaultOutputEntry>
        </dmn:output>
        <dmn:rule id="b_Passenger_Priority_dt_r1">
          <dmn:inputEntry id="b_Passenger_Priority_dt_r1_i1">
            <dmn:text>"gold"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r1_i2">
            <dmn:text>"gold"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r1_i3">
            <dmn:text>>= Passenger2.Miles</dmn:text>
          </dmn:inputEntry>
          <dmn:outputEntry id="b_Passenger_Priority_dt_r1_o1">
            <dmn:text>true</dmn:text>
          </dmn:outputEntry>
        </dmn:rule>
        <dmn:rule id="b_Passenger_Priority_dt_r2">
          <dmn:inputEntry id="b_Passenger_Priority_dt_r2_i1">
            <dmn:text>"gold"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r2_i2">
            <dmn:text>"silver","bronze"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r2_i3">
            <dmn:text>-</dmn:text>
          </dmn:inputEntry>
          <dmn:outputEntry id="b_Passenger_Priority_dt_r2_o1">
            <dmn:text>true</dmn:text>
          </dmn:outputEntry>
        </dmn:rule>
        <dmn:rule id="b_Passenger_Priority_dt_r3">
          <dmn:inputEntry id="b_Passenger_Priority_dt_r3_i1">
            <dmn:text>"silver"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r3_i2">
            <dmn:text>"silver"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r3_i3">
            <dmn:text>>= Passenger2.Miles</dmn:text>
          </dmn:inputEntry>
          <dmn:outputEntry id="b_Passenger_Priority_dt_r3_o1">
            <dmn:text>true</dmn:text>
          </dmn:outputEntry>
        </dmn:rule>
        <dmn:rule id="b_Passenger_Priority_dt_r4">
          <dmn:inputEntry id="b_Passenger_Priority_dt_r4_i1">
            <dmn:text>"silver"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r4_i2">
            <dmn:text>"bronze"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r4_i3">
            <dmn:text>-</dmn:text>
          </dmn:inputEntry>
          <dmn:outputEntry id="b_Passenger_Priority_dt_r4_o1">
            <dmn:text>true</dmn:text>
          </dmn:outputEntry>
        </dmn:rule>
        <dmn:rule id="b_Passenger_Priority_dt_r5">
          <dmn:inputEntry id="b_Passenger_Priority_dt_r5_i1">
            <dmn:text>"bronze"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r5_i2">
            <dmn:text>"bronze"</dmn:text>
          </dmn:inputEntry>
          <dmn:inputEntry id="b_Passenger_Priority_dt_r5_i3">
            <dmn:text>>= Passenger2.Miles</dmn:text>
          </dmn:inputEntry>
          <dmn:outputEntry id="b_Passenger_Priority_dt_r5_o1">
            <dmn:text>true</dmn:text>
          </dmn:outputEntry>
        </dmn:rule>
      </dmn:decisionTable>
    </dmn:encapsulatedLogic>
    <dmn:variable name="passenger priority" typeRef="feel:boolean"/>
  </dmn:businessKnowledgeModel>
  <dmn:businessKnowledgeModel id="b_ReassignNextPassenger" name="reassign next passenger">
    <dmn:encapsulatedLogic>
      <dmn:formalParameter name="Waiting List" typeRef="tPassengerTable"/>
      <dmn:formalParameter name="Reassigned Passengers List" typeRef="tPassengerTable"/>
      <dmn:formalParameter name="Flights" typeRef="tFlightTable"/>
      <dmn:context>
        <dmn:contextEntry>
          <dmn:variable name="Next Passenger" typeRef="tPassenger"/>
          <dmn:literalExpression>
            <dmn:text>Waiting List[1]</dmn:text>
          </dmn:literalExpression>
        </dmn:contextEntry>
        <dmn:contextEntry>
          <dmn:variable name="Original Flight" typeRef="tFlight"/>
          <dmn:literalExpression>
            <dmn:text>Flights[ Flight Number = Next Passenger.Flight Number ][1]</dmn:text>
          </dmn:literalExpression>
        </dmn:contextEntry>
        <dmn:contextEntry>
          <dmn:variable name="Best Alternate Flight" typeRef="tFlight"/>
          <dmn:literalExpression>
            <dmn:text>Flights[ From = Original Flight.From and To = Original Flight.To and Departure > Original Flight.Departure and Status = "scheduled" and has capacity( item, Reassigned Passengers List ) ][1]</dmn:text>
          </dmn:literalExpression>
        </dmn:contextEntry>
        <dmn:contextEntry>
          <dmn:variable name="Reassigned Passenger" typeRef="tPassenger"/>
          <dmn:context>
            <dmn:contextEntry>
              <dmn:variable name="Name" typeRef="feel:string"/>
              <dmn:literalExpression>
                <dmn:text>Next Passenger.Name</dmn:text>
              </dmn:literalExpression>
            </dmn:contextEntry>
            <dmn:contextEntry>
              <dmn:variable name="Status" typeRef="feel:string"/>
              <dmn:literalExpression>
                <dmn:text>Next Passenger.Status</dmn:text>
              </dmn:literalExpression>
            </dmn:contextEntry>
            <dmn:contextEntry>
              <dmn:variable name="Miles" typeRef="feel:number"/>
              <dmn:literalExpression>
                <dmn:text>Next Passenger.Miles</dmn:text>
              </dmn:literalExpression>
            </dmn:contextEntry>
            <dmn:contextEntry>
              <dmn:variable name="Flight Number" typeRef="feel:string"/>
              <dmn:literalExpression>
                <dmn:text>Best Alternate Flight.Flight Number</dmn:text>
              </dmn:literalExpression>
            </dmn:contextEntry>
          </dmn:context>
        </dmn:contextEntry>
        <dmn:contextEntry>
          <dmn:variable name="Remaining Waiting List" typeRef="tPassengerTable"/>
          <dmn:literalExpression>
            <dmn:text>remove( Waiting List, 1 )</dmn:text>
          </dmn:literalExpression>
        </dmn:contextEntry>
        <dmn:contextEntry>
          <dmn:variable name="Updated Reassigned Passengers List" typeRef="tPassengerTable"/>
          <dmn:literalExpression>
            <dmn:text>append( Reassigned Passengers List, Reassigned Passenger )</dmn:text>
          </dmn:literalExpression>
        </dmn:contextEntry>
        <dmn:contextEntry>
          <dmn:literalExpression>
            <dmn:text>if count( Remaining Waiting List ) > 0 then reassign next passenger( Remaining Waiting List, Updated Reassigned Passengers List, Flights ) else Updated Reassigned Passengers List</dmn:text>
          </dmn:literalExpression>
        </dmn:contextEntry>
      </dmn:context>
    </dmn:encapsulatedLogic>
    <dmn:variable name="reassign next passenger" typeRef="tPassengerTable"/>
    <dmn:knowledgeRequirement>
      <dmn:requiredKnowledge href="#b_HasCapacity"/>
    </dmn:knowledgeRequirement>
  </dmn:businessKnowledgeModel>
  <dmn:businessKnowledgeModel id="b_HasCapacity" name="has capacity">
    <dmn:encapsulatedLogic>
      <dmn:formalParameter name="flight" typeRef="tFlight"/>
      <dmn:formalParameter name="rebooked list" typeRef="tPassengerTable"/>
      <dmn:literalExpression>
        <dmn:text>flight.Capacity > count( rebooked list[ Flight Number = flight.Flight Number ] )</dmn:text>
      </dmn:literalExpression>
    </dmn:encapsulatedLogic>
    <dmn:variable name="has capacity" typeRef="feel:boolean"/>
  </dmn:businessKnowledgeModel>
</dmn:definitions>

5.2. DMN support in Kogito

Kogito provides design and runtime support for DMN 1.2 models at conformance level 3, and runtime-only support for DMN 1.1 and 1.3 models at conformance level 3. You can design your DMN models with the Kogito DMN designer in VSCode or import existing DMN models into your Kogito projects for deployment and execution. Any DMN 1.1 models that you import into your Kogito project, open in the DMN designer, and save are converted to DMN 1.2 models. DMN 1.3 models are not supported in the Kogito DMN designer.

In addition to all DMN conformance level 3 requirements, Kogito also includes enhancements and fixes to FEEL and DMN model components to optimize the experience of implementing DMN decision services with Kogito. From a platform perspective, DMN models are like any other business asset in Kogito, such as DRL files or spreadsheet decision tables, that you can include in your Kogito project and execute to start your DMN decision services.

5.2.1. FEEL enhancements in Kogito

Kogito includes the following enhancements and other changes to FEEL in the current DMN implementation:

  • Space Sensitivity: This DMN implementation of the FEEL language is space insensitive. The goal is to avoid non-deterministic behavior based on the context and differences in behavior based on invisible characters, such as white spaces. This means that for this implementation, a variable named first name with one space is exactly the same as first name with two spaces in it.

  • List functions or() and and() : The specification defines two list functions named or() and and(). However, according to the FEEL grammar, these are not valid function names, as and and or are reserved keywords. This implementation renames these functions to any() and all() respectively, in anticipation for DMN 1.2.

  • Keyword in cannot be used in variable names: The specification defines that any keyword can be reused as part of a variable name, but the ambiguities caused with the for …​ in …​ return loop prevent the reuse of the in keyword. All other keywords are supported as part of variable names.

  • Keywords are not supported in attributes of anonymous types: FEEL is not a strongly typed language and the parser must resolve ambiguity in name parts of an attribute of an anonymous type. The parser supports reusable keywords as part of a variable name defined in the scope, but the parser does not support keywords in attributes of an anonymous type. For example, for item in Order.items return Federal Tax for Item( item ) is a valid and supported FEEL expression, where a function named Federal Tax for Item(…​) can be defined and invoked correctly in the scope. However, the expression for i in [ {x and y : true, n : 1}, {x and y : false, n: 2} ] return i.x and y is not supported because anonymous types are defined in the iteration context of the for expression and the parser cannot resolve the ambiguity.

  • Support for date and time literals on ranges: According to the grammar rules #8, #18, #19, #34 and #62, date and time literals are supported in ranges (pages 110-111). Chapter 10.3.2.7 on page 114, on the other hand, contradicts the grammar and says they are not supported. This implementation chose to follow the grammar and support date and time literals on ranges, as well as extend the specification to support any arbitrary expression (see extensions below).

  • Invalid time syntax: Chapter 10.3.2.3.4 on page 112 and bullet point about time on page 131 both state that the time string lexical representation follows the XML Schema Datatypes specification as well as ISO 8601. According to the XML Schema specification (https://www.w3.org/TR/xmlschema-2/#time), the lexical representation of a time follows the pattern hh:mm:ss.sss without any leading character. The DMN specification uses a leading "T" in several examples, that we understand is a typo and not in accordance with the standard.

  • Support for scientific and hexadecimal notations: This implementation supports scientific and hexadecimal notation for numbers. For example, 1.2e5 (scientific notation), 0xD5 (hexadecimal notation).

  • Support for expressions as end points in ranges: This implementation supports expressions as endpoints for ranges. For example, [date("2016-11-24")..date("2016-11-27")]

  • Support for additional types: The specification only defines the following as basic types of the language:

    • number

    • string

    • boolean

    • days and time duration

    • years and month duration

    • time

    • date and time

      For completeness and orthogonality, this implementation also supports the following types:

    • context

    • list

    • range

    • function

    • unary test

  • Support for unary tests: For completeness and orthogonality, unary tests are supported as first class citizens in the language. They are functions with an implicit single parameter and can be invoked in the same way as functions. For example,

    UnaryTestAsFunction.feel
      {
          is minor : < 18,
          Bob is minor : is minor( bob.age )
      }
  • Support for additional built-in functions: The following additional functions are supported:

    • now() : Returns the current local date and time.

    • today() : Returns the current local date.

    • decision table() : Returns a decision table function, although the specification mentions a decision table. The function on page 114 is not implementable as defined.

    • string( mask, p…​ ) : Returns a string formatted as per the mask. See Java String.format() for details on the mask syntax. For example, string( "%4.2f", 7.1298 ) returns the string "7.12".

  • Support for additional date and time arithmetics: Subtracting two dates returns a day and time duration with the number of days between the two dates, ignoring daylight savings. For example,

    DateArithmetic.feel
    date( "2017-05-12" ) - date( "2017-04-25" ) = duration( "P17D" )

5.2.2. DMN model enhancements in Kogito

Kogito includes the following enhancements to DMN model support in the current DMN implementation:

  • Support for types with spaces on names: The DMN XML schema defines type refs such as QNames. The QNames do not allow spaces. Therefore, it is not possible to use types like FEEL date and time, days and time duration or years and months duration. This implementation does parse such typerefs as strings and allows type names with spaces. However, in order to comply with the XML schema, it also adds the following aliases to such types that can be used instead:

    • date and time = dateTime

    • days and time duration = duration or dayTimeDuration

    • years and months duration = duration or yearMonthDuration

      Note that, for the "duration" types, the user can simply use duration and the Drools engine will infer the proper duration, either days and time duration or years and months duration.

  • Lists support heterogeneous element types: Currently this implementation supports lists with heterogeneous element types. This is an experimental extension and does limit the functionality of some functions and filters. This decision will be re-evaluated in the future.

  • TypeRef link between Decision Tables and Item Definitions: On decision tables/input clause, if no values list is defined, the Drools engine automatically checks the type reference and applies the allowed values check if it is defined.

5.3. Creating and editing DMN models in the Kogito DMN designer

You can use the Kogito DMN designer in VSCode to design DMN decision requirements diagrams (DRDs) and define decision logic for a complete and functional DMN decision model. Kogito provides design and runtime support for DMN 1.2 models at conformance level 3, and includes enhancements and fixes to FEEL and DMN model components to optimize the experience of implementing DMN decision services with Kogito. Kogito also provides runtime-only support for DMN 1.1 and 1.3 models at conformance level 3, but any DMN 1.1 models that you import into your Kogito project, open in the DMN designer, and save are converted to DMN 1.2 models. DMN 1.3 models are not supported in the Kogito DMN designer.

Prerequisite
  • VSCode IDE is installed.

  • The Kogito VSCode extension is installed and enabled in your VSCode IDE. (To install the extension, download the latest version of the vscode_extension_kogito_kie_editors_VERSION.vsix file from the kogito-tooling page in GitHub, and in your VSCode IDE, go to ExtensionsMore actionsInstall from VSIX and select the downloaded extension file. When the extension appears in the extension list in VSCode, select it and click Enable, if needed.)

  • You have created a Kogito project and have included any Java objects required for your Kogito service. For information about creating a project, see Creating and running your first Kogito services.

Procedure
  1. In your VSCode IDE, create or import a DMN file in the relevant folder of your Kogito project, typically in src/main/resources.

  2. Open the new or imported DMN file to view the decision requirements diagram (DRD) in the Kogito DMN designer.

    If the DRD does not open in the Kogito DMN designer, ensure that you have installed and enabled the Kogito VSCode extension.

    If the Kogito DMN designer opens only the XML source of the DMN file and displays an error message, review the reported errors and the DMN model file to ensure all DMN elements are correctly defined.

  3. Begin adding components to your new or imported DRD by clicking and dragging one of the DMN nodes from the left toolbar:

    dmn drag decision node
    Figure 51. Adding DRD components

    The following DRD components are available:

    • Decision: Use this node for a DMN decision, where one or more input elements determine an output based on defined decision logic.

    • Business knowledge model: Use this node for reusable functions with one or more decision elements. Decisions that have the same logic but depend on different sub-input data or sub-decisions use business knowledge models to determine which procedure to follow.

    • Knowledge source: Use this node for external authorities, documents, committees, or policies that regulate a decision or business knowledge model. Knowledge sources are references to real-world factors rather than executable business rules.

    • Input data: Use this node for information used in a decision node or a business knowledge model. Input data usually includes business-level concepts or objects relevant to the business, such as loan applicant data used in a lending strategy.

    • Text annotation: Use this node for explanatory notes associated with an input data node, decision node, business knowledge model, or knowledge source.

    • Decision service: Use this node to enclose a set of reusable decisions implemented as a decision service for invocation. A decision service can be used in other DMN models and can be invoked from an external application or a BPMN business process.

  4. In the DMN designer canvas, double-click the new DRD node to enter an informative node name.

  5. If the node is a decision or business knowledge model, select the node to display the node options and click the Edit icon to open the DMN boxed expression designer to define the decision logic for the node:

    dmn decision edit
    Figure 52. Opening a new decision node boxed expression
    dmn bkm edit
    Figure 53. Opening a new business knowledge model boxed expression

    By default, all business knowledge models are defined as boxed function expressions containing a literal FEEL expression, a nested context expression of an external JAVA or PMML function, or a nested boxed expression of any type.

    For decision nodes, you click the undefined table to select the type of boxed expression you want to use, such as a boxed literal expression, boxed context expression, decision table, or other DMN boxed expression.

    dmn decision boxed expression options
    Figure 54. Selecting the logic type for a decision node

    For business knowledge models, you click the top-left function cell to select the function type, or right-click the function value cell, select Clear, and select a boxed expression of another type.

    dmn bkm define
    Figure 55. Selecting the function or other logic type for a business knowledge model
  6. In the selected boxed expression designer for either a decision node (any expression type) or business knowledge model (function expression), click the applicable table cells to define the table name, variable data types, variable names and values, function parameters and bindings, or FEEL expressions to include in the decision logic.

    You can right-click cells for additional actions where applicable, such as inserting or removing table rows and columns or clearing table contents.

    The following is an example decision table for a decision node that determines credit score ratings based on a defined range of a loan applicant’s credit score:

    dmn decision table example1a
    Figure 56. Decision node decision table for credit score rating

    The following is an example boxed function expression for a business knowledge model that calculates mortgage payments based on principal, interest, taxes, and insurance (PITI) as a literal expression:

    dmn function expression example4
    Figure 57. Business knowledge model function for PITI calculation
  7. After you define the decision logic for the selected node, click Back to MODEL_NAME to return to the DRD view.

  8. For the selected DRD node, use the available connection options to create and connect to the next node in the DRD, or click and drag a new node onto the DRD canvas from the left toolbar.

    The node type determines which connection options are supported. For example, an Input data node can connect to a decision node, knowledge source, or text annotation using the applicable connection type, whereas a Knowledge source node can connect to any DRD element. A Decision node can connect only to another decision or a text annotation.

    The following connection types are available, depending on the node type:

    • Information requirement: Use this connection from an input data node or decision node to another decision node that requires the information.

    • Knowledge requirement: Use this connection from a business knowledge model to a decision node or to another business knowledge model that invokes the decision logic.

    • Authority requirement: Use this connection from an input data node or a decision node to a dependent knowledge source or from a knowledge source to a decision node, business knowledge model, or another knowledge source.

    • Association: Use this connection from an input data node, decision node, business knowledge model, or knowledge source to a text annotation.

    dmn input connection example
    Figure 58. Connecting credit score input to the credit score rating decision
    dmn input connection example2
  9. Continue adding and defining the remaining DRD components of your decision model and save the completed DRD.

    The following is an example DRD for a loan prequalification decision model:

    dmn example drd
    Figure 59. Completed DRD for loan prequalification

    The following is an example DRD for a phone call handling decision model using a reusable decision service:

    dmn example drd3
    Figure 60. Completed DRD for phone call handling with a decision service

    In a DMN decision service node, the decision nodes in the bottom segment incorporate input data from outside of the decision service to arrive at a final decision in the top segment of the decision service node. The resulting top-level decisions from the decision service are then implemented in any subsequent decisions or business knowledge requirements of the DMN model. You can reuse DMN decision services in other DMN models to apply the same decision logic with different input data and different outgoing connections.

5.3.1. Defining DMN decision logic in boxed expressions in the Kogito DMN designer

Boxed expressions in DMN are tables that you use to define the underlying logic of decision nodes and business knowledge models in a decision requirements diagram (DRD) or decision requirements graph (DRG). Some boxed expressions can contain other boxed expressions, but the top-level boxed expression corresponds to the decision logic of a single DRD artifact. While DRDs with one or more DRGs represent the flow of a DMN decision model, boxed expressions define the actual decision logic of individual nodes. DRDs and boxed expressions together form a complete and functional DMN decision model.

You can use the Kogito DMN designer in VSCode to define decision logic for your DRD components using built-in boxed expressions.

Prerequisites
  • A DMN file is created or imported in your Kogito project in VSCode.

Procedure
  1. In your VSCode IDE, open the DMN file to view the decision requirements diagram (DRD) in the Kogito DMN designer.

    If the DRD does not open in the Kogito DMN designer, ensure that you have installed and enabled the Kogito VSCode extension.

    If the Kogito DMN designer opens only the XML source of the DMN file and displays an error message, review the reported errors and the DMN model file to ensure all DMN elements are correctly defined.

  2. In the DMN designer canvas, select a decision node or business knowledge model node that you want to define and click the Edit icon to open the DMN boxed expression designer:

    dmn decision edit
    Figure 61. Opening a new decision node boxed expression
    dmn bkm edit
    Figure 62. Opening a new business knowledge model boxed expression

    By default, all business knowledge models are defined as boxed function expressions containing a literal FEEL expression, a nested context expression of an external JAVA or PMML function, or a nested boxed expression of any type.

    For decision nodes, you click the undefined table to select the type of boxed expression you want to use, such as a boxed literal expression, boxed context expression, decision table, or other DMN boxed expression.

    dmn decision boxed expression options
    Figure 63. Selecting the logic type for a decision node

    For business knowledge model nodes, you click the top-left function cell to select the function type, or right-click the function value cell, select Clear, and select a boxed expression of another type.

    dmn bkm define
    Figure 64. Selecting the function or other logic type for a business knowledge model
  3. For this example, use a decision node and select Decision Table as the boxed expression type.

    A decision table in DMN is a visual representation of one or more rules in a tabular format. Each rule consists of a single row in the table, and includes columns that define the conditions (input) and outcome (output) for that particular row.

  4. Click the input column header to define the name and data type for the input condition. For example, name the input column Credit Score.FICO with a number data type. This column specifies numeric credit score values or ranges of loan applicants.

  5. Click the output column header to define the name and data type for the output values. For example, name the output column Credit Score Rating and next to the Data Type option, click Manage to go to the Data Types page where you can create a custom data type with score ratings as constraints.

    dmn manage data types
    Figure 65. Managing data types for a column header value
  6. On the Data Types page, click New Data Type to add a new data type.

    For this example, click New Data Type and create a Credit_Score_Rating data type as a string:

    dmn custom data type add
    Figure 66. Adding a new data type
  7. Click Add Constraints, select Enumeration from the drop-down options, and add the following constraints:

    • "Excellent"

    • "Good"

    • "Fair"

    • "Poor"

    • "Bad"

    dmn custom data type constraints
    Figure 67. Adding constraints to the new data type

    To change the order of data type constraints, you can click the left end of the constraint row and drag the row as needed:

    dmn custom data type constraints drag
    Figure 68. Dragging constraints to change constraint order

    For information about constraint types and syntax requirements for the specified data type, see the Decision Model and Notation specification.

  8. Click OK to save the constraints and click the check mark to the right of the data type to save the data type.

  9. Return to the Credit Score Rating decision table, click the Credit Score Rating column header, and set the data type to this new custom data type.

  10. Use the Credit Score.FICO input column to define credit score values or ranges of values, and use the Credit Score Rating column to specify one of the corresponding ratings you defined in the Credit_Score_Rating data type.

    Right-click any value cell to insert or delete rows (rules) or columns (clauses).

    dmn decision table example1a
    Figure 69. Decision node decision table for credit score rating
  11. After you define all rules, click the top-left corner of the decision table to define the rule Hit Policy and Builtin Aggregator (for COLLECT hit policy only).

    The hit policy determines how to reach an outcome when multiple rules in a decision table match the provided input values. The built-in aggregator determines how to aggregate rule values when you use the COLLECT hit policy.

    dmn hit policies
    Figure 70. Defining the decision table hit policy

    The following example is a more complex decision table that determines applicant qualification for a loan as the concluding decision node in the same loan prequalification decision model:

    dmn decision table example3
    Figure 71. Decision table for loan prequalification

For boxed expression types other than decision tables, you follow these guidelines similarly to navigate the boxed expression tables and define variables and parameters for decision logic, but according to the requirements of the boxed expression type. Some boxed expressions, such as boxed literal expressions, can be single-column tables, while other boxed expressions, such as function, context, and invocation expressions, can be multi-column tables with nested boxed expressions of other types.

For example, the following boxed context expression defines the parameters that determine whether a loan applicant can meet minimum mortgage payments based on principal, interest, taxes, and insurance (PITI), represented as a front-end ratio calculation with a sub-context expression:

dmn context expression example2
Figure 72. Boxed context expression for front-end client PITI ratio

The following boxed function expression determines a monthly mortgage installment as a business knowledge model in a lending decision, with the function value defined as a nested context expression:

dmn function expression example3
Figure 73. Boxed function expression for installment calculation in business knowledge model

For more information and examples of each boxed expression type, see DMN decision logic in boxed expressions.

5.3.2. Creating custom data types for DMN boxed expressions in the Kogito DMN designer

In DMN boxed expressions in the Kogito DMN designer, data types determine the structure of the data that you use within an associated table, column, or field in the boxed expression. You can use default DMN data types (such as String, Number, Boolean) or you can create custom data types to specify additional fields and constraints that you want to implement for the boxed expression values.

Custom data types that you create for a boxed expression can be simple or structured:

  • Simple data types have only a name and a type assignment. Example: Age (number).

  • Structured data types contain multiple fields associated with a parent data type. Example: A single type Person containing the fields Name (string), Age (number), Email (string).

Prerequisites
  • A DMN file is created or imported in your Kogito project in VSCode.

Procedure
  1. In your VSCode IDE, open the DMN file to view the decision requirements diagram (DRD) in the Kogito DMN designer.

    If the DRD does not open in the Kogito DMN designer, ensure that you have installed and enabled the Kogito VSCode extension.

    If the Kogito DMN designer opens only the XML source of the DMN file and displays an error message, review the reported errors and the DMN model file to ensure all DMN elements are correctly defined.

  2. In the DMN designer canvas, select a decision node or business knowledge model for which you want to define the data types and click the Edit icon to open the DMN boxed expression designer.

  3. If the boxed expression is for a decision node that is not yet defined, click the undefined table to select the type of boxed expression you want to use, such as a boxed literal expression, boxed context expression, decision table, or other DMN boxed expression.

    dmn decision boxed expression options
    Figure 74. Selecting the logic type for a decision node
  4. Click the cell for the table header, column header, or parameter field (depending on the boxed expression type) for which you want to define the data type and click Manage to go to the Data Types page where you can create a custom data type.

    dmn manage data types
    Figure 75. Managing data types for a column header value

    You can also set and manage custom data types for a specified decision node or business knowledge model node by selecting the Properties icon in the upper-right corner of the DMN designer:

    dmn manage data types1a
    Figure 76. Managing data types in decision requirements diagram (DRD) properties

    The data type that you define for a specified cell in a boxed expression determines the structure of the data that you use within that associated table, column, or field in the boxed expression.

    In this example, an output column Credit Score Rating for a DMN decision table defines a set of custom credit score ratings based on an applicant’s credit score.

  5. On the Data Types page, click New Data Type to add a new data type.

    For this example, click New Data Type and create a Credit_Score_Rating data type as a string:

    dmn custom data type add
    Figure 77. Adding a new data type

    If the data type requires a list of items, enable the List setting.

  6. Click Add Constraints, select Enumeration from the drop-down options, and add the following constraints:

    • "Excellent"

    • "Good"

    • "Fair"

    • "Poor"

    • "Bad"

    dmn custom data type constraints
    Figure 78. Adding constraints to the new data type

    To change the order of data type constraints, you can click the left end of the constraint row and drag the row as needed:

    dmn custom data type constraints drag
    Figure 79. Dragging constraints to change constraint order

    For information about constraint types and syntax requirements for the specified data type, see the Decision Model and Notation specification.

  7. Click OK to save the constraints and click the check mark to the right of the data type to save the data type.

  8. Return to the Credit Score Rating decision table, click the Credit Score Rating column header, set the data type to this new custom data type, and define the rule values for that column with the rating constraints that you specified.

    dmn decision table example1a
    Figure 80. Decision table for credit score rating

    In the DMN decision model for this scenario, the Credit Score Rating decision flows into the following Loan Prequalification decision that also requires custom data types:

    dmn manage data types blank
    Figure 81. Decision table for loan prequalification
  9. Continuing with this example, return to the Data Types window, click New Data Type, and create a Loan_Qualification data type as a Structure with no constraints.

    When you save the new structured data type, the first sub-field appears so that you can begin defining nested data fields in this parent data type. You can use these sub-fields in association with the parent structured data type in boxed expressions, such as nested column headers in decision tables or nested table parameters in context or function expressions.

    For additional sub-fields, select the addition icon next to the Loan_Qualification data type:

    dmn manage data types structured
    Figure 82. Adding a new structured data type with nested fields
  10. For this example, under the structured Loan_Qualification data type, add a Qualification field with "Qualified" and "Not Qualified" enumeration constraints, and a Reason field with no constraints. Add also a simple Back_End_Ratio and a Front_End_Ratio data type, both with "Sufficient" and "Insufficient" enumeration constraints.

    Click the check mark to the right of each data type that you create to save your changes.

    dmn manage data types structured2
    Figure 83. Adding nested data types with constraints

    To change the order or nesting of data types, you can click the left end of the data type row and drag the row as needed:

    dmn manage data types structured2 drag
    Figure 84. Dragging data types to change data type order or nesting
  11. Return to the decision table and, for each column, click the column header cell, set the data type to the new corresponding custom data type, and define the rule values as needed for the column with the constraints that you specified, if applicable.

    dmn decision table example3
    Figure 85. Decision table for loan prequalification

For boxed expression types other than decision tables, you follow these guidelines similarly to navigate the boxed expression tables and define custom data types as needed.

For example, the following boxed function expression uses custom tCandidate and tProfile structured data types to associate data for online dating compatibility:

dmn manage data types structured3
Figure 86. Boxed function expression for online dating compatibility
dmn manage data types structured3a
Figure 87. Custom data type definitions for online dating compatibility
dmn manage data types structured3b
Figure 88. Parameter definitions with custom data types for online dating compatibility

5.3.3. DMN model documentation in the Kogito DMN designer

In the Kogito DMN designer, you can use the Documentation tab to generate a report of your DMN model. The DMN model report contains all decision requirements diagrams (DRDs), data types, and boxed expressions in your DMN model. You can use this report to share your DMN model details or as part of your internal reporting workflow.

dmn documentation
Figure 89. Example DMN model report

5.3.4. Kogito DMN designer navigation and properties

The Kogito DMN designer provides the following additional features to help you navigate through the components and properties of decision requirements diagrams (DRDs).

DMN decision and diagram views

In the upper-right corner of the DMN designer, select the Decision Navigator view to navigate between the decision components, graphs, and boxed expressions of a selected DRD:

dmn designer nav view
Figure 90. Decision Navigator view

In the upper-right corner of the DMN designer, select the Preview icon to view an elevated preview of the DRD:

dmn designer preview
Figure 91. Diagram preview
DRD properties and design

In the upper-right corner of the DMN designer, select the Properties icon to modify the identifying information, data types, and appearance of a selected DRD, DRD node, or boxed expression cell:

dmn designer properties
Figure 92. DRD node properties

To view the properties of the entire DRD, click the DRD canvas background instead of a specific node.

DRD search

In the upper-right corner of the DMN designer, use the search bar to search for text that appears in your DRD. The search feature is especially helpful in complex DRDs with many nodes:

dmn designer search
Figure 93. DRD search

5.4. Kogito service execution

After you design your Kogito service, you can build and run your application and then send REST API requests to the application to execute your services. The exact REST API requests that you can use depend on how you set up the application.

For example, consider a Kogito service that is set up to generate a /persons REST API endpoint and determines whether a given customer is an adult or is underage. In this example, you can send the following POST request using a REST client or curl utility to add an adult and execute the service:

Example POST request body to add an adult
{
  "person": {
    "name": "John Quark",
    "age": 20
  }
}
Example curl command to add an adult
curl -X POST http://localhost:8080/persons -H 'content-type: application/json' -H 'accept: application/json' -d '{"person": {"name":"John Quark", "age": 20}}'
Example response
{
  "id": "b7d993ac-272d-43c7-8245-d4df96d9ab6c",
  "person": {
    "name": "John Quark",
    "age": 20,
    "adult": true
  }
}

For information about creating, running, and testing an example application with Kogito services, see Creating and running your first Kogito services.

For information about deploying your Kogito service to OpenShift, see Deploying Kogito services on OpenShift.

6. Using DRL rules in Kogito services

As a business rules developer, you can define business rules using Drools Rule Language (DRL) directly in free-form .drl text files. A DRL file can contain one or more rules that define at a minimum the rule conditions (when) and actions (then).

6.1. Drools Rule Language (DRL)

Drools Rule Language (DRL) is a notation established by the Drools open source business automation project for defining and describing business rules. You define DRL rules in .drl text files. A DRL file can contain one or more rules that define at a minimum the rule conditions (when) and actions (then).

DRL files consist of the following components:

Components in a DRL file
package
unit  // Recommended

import

function  // Optional

query  // Optional

declare   // Optional

global   // Optional

rule "rule name"
    // Attributes
    when
        // Conditions
    then
        // Actions
end

rule "rule2 name"

...

The following example DRL rule determines the age limit in a loan application decision service:

Example rule for loan application age limit
rule "Underage"
  salience 15
  agenda-group "applicationGroup"
  when
    $application : LoanApplication()
    Applicant( age < 21 )
  then
    $application.setApproved( false );
    $application.setExplanation( "Underage" );
end

A DRL file can contain single or multiple rules, queries, and functions, and can define resource declarations such as imports, globals, and attributes that are assigned and used by your rules and queries. The DRL package must be listed at the top of a DRL file and the rules are typically listed last. All other DRL components can follow any order.

Each rule must have a unique name within the rule package. If you use the same rule name more than once in any DRL file in the package, the rules fail to compile. Always enclose rule names with double quotation marks (rule "rule name") to prevent possible compilation errors, especially if you use spaces in rule names.

6.1.1. OOPath syntax in DRL rule conditions

OOPath is an object-oriented syntax extension of XPath that optimizes the capabilities of Drools Rule Language (DRL) rule conditions. OOPath uses the compact notation from XPath for navigating through related elements while handling collections and filtering constraints, and is specifically useful for browsing graphs of objects in DRL rule condition constraints.

For optimal rule constructs and capabilities, OOPath is the preferred syntax with DRL rules and is the syntax used in most DRL example rules and in DRL documentation.

The following example rules demonstrate basic OOPath syntax compared to traditional DRL rule syntax:

Example person DRL file using OOPath
package org.acme.kogito.model
unit EvaluatePerson;

import org.acme.kogito.model.Person;

rule isAdult
	when
		$person: /person[ age > 18 ]
	then
    modify($person) {
    	setAdult(true)
    };
end
Example person DRL file using traditional notation
package org.acme.kogito.model
unit EvaluatePerson;

import org.acme.kogito.model.Person;

rule isAdult
	when
		$person: Person(age > 18) from person
	then
    modify($person) {
    	setAdult(true)
    };
end

Formally, the core grammar of an OOPath expression is defined in extended Backus-Naur form (EBNF) notation in the following way:

EBNF notation for OOPath expressions
OOPExpr = [ID ( ":" | ":=" )] ( "/" | "?/" ) OOPSegment { ( "/" | "?/" | "." ) OOPSegment } ;
OOPSegment = ID ["#" ID] ["[" ( Number | Constraints ) "]"]

In practice, an OOPath expression has the following features and capabilities:

  • Starts with a forward slash / or with a question mark and forward slash ?/ if it is a non-reactive OOPath expression (described later in this section).

  • Can dereference a single property of an object with the period . operator.

  • Can dereference multiple properties of an object with the forward slash / operator. If a collection is returned, the expression iterates over the values in the collection.

  • Can filter out traversed objects that do not satisfy one or more constraints. The constraints are written as predicate expressions between square brackets, as shown in the following example:

    Constraints as a predicate expression
    Student( $grade: /plan/exams[ course == "Big Data" ]/grades )
  • Can downcast a traversed object to a subclass of the class declared in the generic collection. Subsequent constraints can also safely access the properties declared only in that subclass, as shown in the following example. Objects that are not instances of the class specified in this inline cast are automatically filtered out.

    Constraints with downcast objects
    Student( $grade: /plan/exams#AdvancedExam[ course == "Big Data", level > 3 ]/grades )
  • Can backreference an object of the graph that was traversed before the currently iterated graph. For example, the following OOPath expression matches only the grades that are above the average for the passed exam:

    Constraints with backreferenced object
    Student( $grade: /plan/exams/grades[ result > ../averageResult ] )
  • Can recursively be another OOPath expression, as shown in the following example:

    Recursive constraint expression
    Student( $exam: /plan/exams[ /grades[ result > 20 ] ] )
  • Can access objects by their index between square brackets [], as shown in the following example. To adhere to Java convention, OOPath indexes are 0-based, while XPath indexes are 1-based.

    Constraints with access to objects by index
    Student( $grade: /plan/exams[0]/grades )
6.1.1.1. OOPath syntax with graphs of objects in DRL rule conditions

OOPath is specifically useful for reasoning over collections and browsing graphs of objects in DRL rule conditions.

When the field of a fact is a collection, you can use the from condition element (keyword) in traditional DRL notation to bind and reason over all the items in that collection one by one. If you need to browse a graph of objects in the rule condition constraints, the extensive use of the from condition element results in a verbose and repetitive syntax, as shown in the following example:

Example rule that browses a graph of objects with from in traditional notation
rule "Find all grades for Big Data exam"
  when
    $student: Student( $plan: plan )
    $exam: Exam( course == "Big Data" ) from $plan.exams
    $grade: Grade() from $exam.grades
  then
    // Actions
end

In this example, the domain model contains a Student object with a Plan of study. The Plan can have zero or more Exam instances and an Exam can have zero or more Grade instances. Only the root object of the graph, the Student in this case, needs to be in the working memory of the Drools engine for this rule setup to function.

As a more efficient alternative to using extensive from statements, you can use the abbreviated OOPath syntax, as shown in the following example:

Example rule that browses a graph of objects with OOPath syntax
rule "Find all grades for Big Data exam"
  when
    Student( $grade: /plan/exams[course == "Big Data"]/grades )
  then
    // Actions
end
6.1.1.2. Object reactivity in OOPath expressions

OOPath expressions can be reactive or non-reactive. The Drools engine does not react to updates involving a deeply nested object that is traversed during the evaluation of an OOPath expression.

To make these objects reactive to changes, modify the objects to extend the class org.drools.core.phreak.ReactiveObject. After you modify an object to extend the ReactiveObject class, the domain object invokes the inherited method notifyModification to notify the Drools engine when one of the fields has been updated, as shown in the following example:

Example object method to notify the Drools engine that an exam has been moved to a different course
public void setCourse(String course) {
        this.course = course;
        notifyModification(this);
        }

With the following corresponding OOPath expression, when an exam is moved to a different course, the rule is re-executed and the list of grades matching the rule is recomputed:

Example OOPath expression from "Big Data" rule
Student( $grade: /plan/exams[ course == "Big Data" ]/grades )

You can also use the ?/ separator instead of the / separator to disable reactivity in only one sub-portion of an OOPath expression, as shown in the following example:

Example OOPath expression that is partially non-reactive
Student( $grade: /plan/exams[ course == "Big Data" ]?/grades )

With this example, the Drools engine reacts to a change made to an exam or if an exam is added to the plan, but not if a new grade is added to an existing exam.

If an OOPath portion is non-reactive, all remaining portions of the OOPath expression also become non-reactive. For example, the following OOPath expression is completely non-reactive:

Example OOPath expression that is completely non-reactive
Student( $grade: ?/plan/exams[ course == "Big Data" ]/grades )

For this reason, you cannot use the ?/ separator more than once in the same OOPath expression. For example, the following expression causes a compilation error:

Example OOPath expression with duplicate non-reactivity markers
Student( $grade: /plan?/exams[ course == "Big Data" ]?/grades )

Another alternative for enabling OOPath expression reactivity is to use the dedicated implementations for List and Set interfaces in Kogito. These implementations are the ReactiveList and ReactiveSet classes. A ReactiveCollection class is also available. The implementations also provide reactive support for performing mutable operations through the Iterator and ListIterator classes.

The following example class uses these classes to configure OOPath expression reactivity:

Example Java class to configure OOPath expression reactivity
public class School extends AbstractReactiveObject {
    private String name;
    private final List<Child> children = new ReactiveList<Child>(); (1)

    public void setName(String name) {
        this.name = name;
        notifyModification(); (2)
    }

    public void addChild(Child child) {
        children.add(child); (3)
        // No need to call `notifyModification()` here
    }
  }
1 Uses the ReactiveList instance for reactive support over the standard Java List instance.
2 Uses the required notifyModification() method for when a field is changed in reactive support.
3 The children field is a ReactiveList instance, so the notifyModification() method call is not required. The notification is handled automatically, like all other mutating operations performed over the children field.

6.1.2. Packages in DRL

A package is a folder of related assets in Kogito, such as data objects, DRL files, decision tables, and other asset types. A package also serves as a unique namespace for each group of rules. A single rule base can contain multiple packages. You typically store all the rules for a package in the same file as the package declaration so that the package is self-contained. However, you can import objects from other packages that you want to use in the rules.

The following example is a package name and namespace for a DRL file in a mortgage application decision service:

Example package definition in a DRL file
package org.mortgages;

The following railroad diagram shows all the components that may make up a package:

package
Figure 94. Package

Note that a package must have a namespace and be declared using standard Java conventions for package names; i.e., no spaces, unlike rule names which allow spaces. In terms of the order of elements, they can appear in any order in the rule file, with the exception of the package statement, which must be at the top of the file. In all cases, the semicolons are optional.

Notice that any rule attribute (as described in the section [rules-attributes-ref_drl-rules]) may also be written at package level, superseding the attribute’s default value. The modified default may still be replaced by an attribute setting within a rule.

6.1.3. Rule units in DRL

Rule units are groups of data sources, global variables, and DRL rules that function together for a specific purpose. You can use rule units to partition a rule set into smaller units, bind different data sources to those units, and then execute the individual unit. Rule units are an enhanced alternative to rule-grouping DRL attributes such as rule agenda groups or activation groups for execution control.

The following example is a rule unit designated in a DRL file in a mortgage application decision service:

Example package definition and rule unit designation in a DRL file
package org.mortgages;
unit MortgageRules;

Rule units are helpful when you want to coordinate rule execution so that the complete execution of one rule unit triggers the start of another rule unit and so on. For example, assume that you have a set of rules for data enrichment, another set of rules that processes that data, and another set of rules that extract the output from the processed data. If you add these rule sets into three distinct rule units, you can coordinate those rule units so that complete execution of the first unit triggers the start of the second unit and the complete execution of the second unit triggers the start of third unit.

To define a rule unit, implement the RuleUnit interface as shown in the following example:

Example rule unit class
package org.mypackage.myunit;

public static class AdultUnit implements RuleUnit {
    private int adultAge;
    private DataSource<Person> persons;

    public AdultUnit( ) { }

    public AdultUnit( DataSource<Person> persons, int age ) {
        this.persons = persons;
        this.age = age;
    }

    // A data source of `Persons` in this rule unit:
    public DataSource<Person> getPersons() {
        return persons;
    }

    // A global variable in this rule unit:
    public int getAdultAge() {
        return adultAge;
    }

    // Life-cycle methods:
    @Override
    public void onStart() {
        System.out.println("AdultUnit started.");
    }

    @Override
    public void onEnd() {
        System.out.println("AdultUnit ended.");
    }
}

In this example, persons is a source of facts of type Person. A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. The adultAge global variable is accessible from all the rules belonging to this rule unit. The last two methods are part of the rule unit life cycle and are invoked by the Drools engine.

The Drools engine supports the following optional life-cycle methods for rule units:

Table 4. Rule unit life-cycle methods
Method Invoked when

onStart()

Rule unit execution starts

onEnd()

Rule unit execution ends

onSuspend()

Rule unit execution is suspended (used only with runUntilHalt())

onResume()

Rule unit execution is resumed (used only with runUntilHalt())

onYield(RuleUnit other)

The consequence of a rule in the rule unit triggers the execution of a different rule unit

You can add one or more rules to a rule unit. By default, all the rules in a DRL file are automatically associated with a rule unit that follows the naming convention of the DRL file name. If the DRL file is in the same package and has the same name as a class that implements the RuleUnit interface, then all of the rules in that DRL file implicitly belong to that rule unit. For example, all the rules in the AdultUnit.drl file in the org.mypackage.myunit package are automatically part of the rule unit org.mypackage.myunit.AdultUnit.

To override this naming convention and explicitly declare the rule unit that the rules in a DRL file belong to, use the unit keyword in the DRL file. The unit declaration must immediately follow the package declaration and contain the name of the class in that package that the rules in the DRL file are part of.

Example rule unit declaration in a DRL file
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    $p : /persons[age >= adultAge]
  then
    System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
Do not mix rules with and without a rule unit in the same KIE base. Mixing two rule paradigms in a KIE base results in a compilation error.

You can also rewrite the same rule condition in a more explicit form using the traditional rule pattern syntax instead of OOPath syntax, as shown in the following example:

Example rule unit declaration in a DRL file using traditional notation
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    $p : Person(age >= adultAge) from persons
  then
    System.out.println($p.getName() + " is adult and greater than " + adultAge);
end

In this example, any matching facts in the rule conditions are retrieved from the persons data source defined in the DataSource definition in the rule unit class. The rule condition and action use the adultAge variable in the same way that a global variable is defined at the DRL file level.

To execute one or more rule units defined in a KIE base, create a new RuleUnitExecutor class bound to the KIE base, create the rule unit from the relevant data source, and run the rule unit executer:

Example rule unit execution
// Create a `RuleUnitExecutor` class and bind it to the KIE base:
KieBase kbase = kieContainer.getKieBase();
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );

// Create the `AdultUnit` rule unit using the `persons` data source and run the executor:
RuleUnit adultUnit = new AdultUnit(persons, 18);
executor.run( adultUnit );

Rules are executed by the RuleUnitExecutor class. The RuleUnitExecutor class creates KIE sessions and adds the required DataSource objects to those sessions, and then executes the rules based on the RuleUnit that is passed as a parameter to the run() method.

The example execution code produces the following output when the relevant Person facts are inserted in the persons data source:

Example rule unit execution output
org.mypackage.myunit.AdultUnit started.
Jane is adult and greater than 18
John is adult and greater than 18
org.mypackage.myunit.AdultUnit ended.

Instead of explicitly creating the rule unit instance, you can register the rule unit variables in the executor and pass to the executor the rule unit class that you want to run, and then the executor creates an instance of the rule unit. You can then set the DataSource definition and other variables as needed before running the rule unit.

Alternate rule unit execution option with registered variables
executor.bindVariable( "persons", persons );
        .bindVariable( "adultAge", 18 );
executor.run( AdultUnit.class );

The name that you pass to the RuleUnitExecutor.bindVariable() method is used at run time to bind the variable to the field of the rule unit class with the same name. In the previous example, the RuleUnitExecutor inserts into the new rule unit the data source bound to the "persons" name and inserts the value 18 bound to the String "adultAge" into the fields with the corresponding names inside the AdultUnit class.

To override this default variable-binding behavior, use the @UnitVar annotation to explicitly define a logical binding name for each field of the rule unit class. For example, the field bindings in the following class are redefined with alternative names:

Example code to modify variable binding names with @UnitVar
package org.mypackage.myunit;

public static class AdultUnit implements RuleUnit {
    @UnitVar("minAge")
    private int adultAge = 18;

    @UnitVar("data")
    private DataSource<Person> persons;
}

You can then bind the variables to the executor using those alternative names and run the rule unit:

Example rule unit execution with modified variable names
executor.bindVariable( "data", persons );
        .bindVariable( "minAge", 18 );
executor.run( AdultUnit.class );

You can execute a rule unit in passive mode by using the run() method (equivalent to invoking fireAllRules() on a KIE session) or in active mode using the runUntilHalt() method (equivalent to invoking fireUntilHalt() on a KIE session). By default, the Drools engine runs in passive mode and evaluates rule units only when a user or an application explicitly calls run() (or fireAllRules() for standard rules). If a user or application calls runUntilHalt() for rule units (or fireUntilHalt() for standard rules), the Drools engine starts in active mode and evaluates rule units continually until the user or application explicitly calls halt().

If you use the runUntilHalt() method, invoke the method on a separate execution thread to avoid blocking the main thread:

Example rule unit execution with runUntilHalt() on a separate thread
new Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
6.1.3.1. Data sources for rule units

A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. A rule unit can have zero or more data sources and each DataSource definition declared inside a rule unit can correspond to a different entry point into the rule unit executor. Multiple rule units can share a single data source, but each rule unit must use different entry points through which the same objects are inserted.

You can create a DataSource definition with a fixed set of data in a rule unit class, as shown in the following example:

Example data source definition
DataSource<Person> persons = DataSource.create( new Person( "John", 42 ),
                                                new Person( "Jane", 44 ),
                                                new Person( "Sally", 4 ) );

Because a data source represents the entry point of the rule unit, you can insert, update, or delete facts in a rule unit:

Example code to insert, modify, and delete a fact in a rule unit
// Insert a fact:
Person john = new Person( "John", 42 );
FactHandle johnFh = persons.insert( john );

// Modify the fact and optionally specify modified properties (for property reactivity):
john.setAge( 43 );
persons.update( johnFh, john, "age" );

// Delete the fact:
persons.delete( johnFh );
6.1.3.2. Rule unit execution control

Rule units are helpful when you want to coordinate rule execution so that the execution of one rule unit triggers the start of another rule unit and so on.

To facilitate rule unit execution control, the Drools engine supports the following rule unit methods that you can use in DRL rule actions to coordinate the execution of rule units:

  • drools.run(): Triggers the execution of a specified rule unit class. This method imperatively interrupts the execution of the rule unit and activates the other specified rule unit.

  • drools.guard(): Prevents (guards) a specified rule unit class from being executed until the associated rule condition is met. This method declaratively schedules the execution of the other specified rule unit. When the Drools engine produces at least one match for the condition in the guarding rule, the guarded rule unit is considered active. A rule unit can contain multiple guarding rules.

As an example of the drools.run() method, consider the following DRL rules that each belong to a specified rule unit. The NotAdult rule uses the drools.run( AdultUnit.class ) method to trigger the execution of the AdultUnit rule unit:

Example DRL rules with controlled execution using drools.run()
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    Person(age >= 18, $name : name) from persons
  then
    System.out.println($name + " is adult");
end
package org.mypackage.myunit
unit NotAdultUnit

rule NotAdult
  when
    $p : Person(age < 18, $name : name) from persons
  then
    System.out.println($name + " is NOT adult");
    modify($p) { setAge(18); }
    drools.run( AdultUnit.class );
end

The example also uses a RuleUnitExecutor class created from the KIE base that was built from these rules and a DataSource definition of persons bound to it:

Example rule executor and data source definitions
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Person> persons = executor.newDataSource( "persons",
                                                     new Person( "John", 42 ),
                                                     new Person( "Jane", 44 ),
                                                     new Person( "Sally", 4 ) );

In this case, the example creates the DataSource definition directly from the RuleUnitExecutor class and binds it to the "persons" variable in a single statement.

The example execution code produces the following output when the relevant Person facts are inserted in the persons data source:

Example rule unit execution output
Sally is NOT adult
John is adult
Jane is adult
Sally is adult

The NotAdult rule detects a match when evaluating the person "Sally", who is under 18 years old. The rule then modifies her age to 18 and uses the drools.run( AdultUnit.class ) method to trigger the execution of the AdultUnit rule unit. The AdultUnit rule unit contains a rule that can now be executed for all of the 3 persons in the DataSource definition.

As an example of the drools.guard() method, consider the following BoxOffice class and BoxOfficeUnit rule unit class:

Example BoxOffice class
public class BoxOffice {
    private boolean open;

    public BoxOffice( boolean open ) {
        this.open = open;
    }

    public boolean isOpen() {
        return open;
    }

    public void setOpen( boolean open ) {
        this.open = open;
    }
}
Example BoxOfficeUnit rule unit class
public class BoxOfficeUnit implements RuleUnit {
    private DataSource<BoxOffice> boxOffices;

    public DataSource<BoxOffice> getBoxOffices() {
        return boxOffices;
    }
}

The example also uses the following TicketIssuerUnit rule unit class to keep selling box office tickets for the event as long as at least one box office is open. This rule unit uses DataSource definitions of persons and tickets:

Example TicketIssuerUnit rule unit class
public class TicketIssuerUnit implements RuleUnit {
    private DataSource<Person> persons;
    private DataSource<AdultTicket> tickets;

    private List<String> results;

    public TicketIssuerUnit() { }

    public TicketIssuerUnit( DataSource<Person> persons, DataSource<AdultTicket> tickets ) {
        this.persons = persons;
        this.tickets = tickets;
    }

    public DataSource<Person> getPersons() {
        return persons;
    }

    public DataSource<AdultTicket> getTickets() {
        return tickets;
    }

    public List<String> getResults() {
        return results;
    }
}

The BoxOfficeUnit rule unit contains a BoxOfficeIsOpen DRL rule that uses the drools.guard( TicketIssuerUnit.class ) method to guard the execution of the TicketIssuerUnit rule unit that distributes the event tickets, as shown in the following DRL rule examples:

Example DRL rules with controlled execution using drools.guard()
package org.mypackage.myunit;
unit TicketIssuerUnit;

rule IssueAdultTicket when
    $p: /persons[ age >= 18 ]
then
    tickets.insert(new AdultTicket($p));
end
rule RegisterAdultTicket when
    $t: /tickets
then
    results.add( $t.getPerson().getName() );
end
package org.mypackage.myunit;
unit BoxOfficeUnit;

rule BoxOfficeIsOpen
  when
    $box: /boxOffices[ open ]
  then
    drools.guard( TicketIssuerUnit.class );
end

In this example, so long as at least one box office is open, the guarded TicketIssuerUnit rule unit is active and distributes event tickets. When no more box offices are in open state, the guarded TicketIssuerUnit rule unit is prevented from being executed.

The following example class illustrates a more complete box office scenario:

Example class for the box office scenario
DataSource<Person> persons = executor.newDataSource( "persons" );
DataSource<BoxOffice> boxOffices = executor.newDataSource( "boxOffices" );
DataSource<AdultTicket> tickets = executor.newDataSource( "tickets" );

List<String> list = new ArrayList<>();
executor.bindVariable( "results", list );

// Two box offices are open:
BoxOffice office1 = new BoxOffice(true);
FactHandle officeFH1 = boxOffices.insert( office1 );
BoxOffice office2 = new BoxOffice(true);
FactHandle officeFH2 = boxOffices.insert( office2 );

persons.insert(new Person("John", 40));

// Execute `BoxOfficeIsOpen` rule, run `TicketIssuerUnit` rule unit, and execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);

assertEquals( 1, list.size() );
assertEquals( "John", list.get(0) );
list.clear();

persons.insert(new Person("Matteo", 30));

// Execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);

assertEquals( 1, list.size() );
assertEquals( "Matteo", list.get(0) );
list.clear();

// One box office is closed, the other is open:
office1.setOpen(false);
boxOffices.update(officeFH1, office1);
persons.insert(new Person("Mark", 35));
executor.run(BoxOfficeUnit.class);

assertEquals( 1, list.size() );
assertEquals( "Mark", list.get(0) );
list.clear();

// All box offices are closed:
office2.setOpen(false);
boxOffices.update(officeFH2, office2); // Guarding rule is no longer true.
persons.insert(new Person("Edson", 35));
executor.run(BoxOfficeUnit.class); // No execution

assertEquals( 0, list.size() );
6.1.3.3. Rule unit identity conflicts

In rule unit execution scenarios with guarded rule units, a rule can guard multiple rule units and at the same time a rule unit can be guarded and then activated by multiple rules. For these two-way guarding scenarios, rule units must have a clearly defined identity to avoid identity conflicts.

By default, the identity of a rule unit is the rule unit class name and is treated as a singleton class by the RuleUnitExecutor. This identification behavior is encoded in the getUnitIdentity() default method of the RuleUnit interface:

Default identity method in the RuleUnit interface
default Identity getUnitIdentity() {
    return new Identity( getClass() );
}

In some cases, you may need to override this default identification behavior to avoid conflicting identities between rule units.

For example, the following RuleUnit class contains a DataSource definition that accepts any kind of object:

Example Unit0 rule unit class
public class Unit0 implements RuleUnit {
    private DataSource<Object> input;

    public DataSource<Object> getInput() {
        return input;
    }
}

This rule unit contains the following DRL rule that guards another rule unit based on two conditions (in OOPath notation):

Example GuardAgeCheck DRL rule in the rule unit
package org.mypackage.myunit
unit Unit0

rule GuardAgeCheck
  when
    $i: /input#Integer
    $s: /input#String
  then
    drools.guard( new AgeCheckUnit($i) );
    drools.guard( new AgeCheckUnit($s.length()) );
end

The guarded AgeCheckUnit rule unit verifies the age of a set of persons. The AgeCheckUnit contains a DataSource definition of the persons to check, a minAge variable that it verifies against, and a List for gathering the results:

Example AgeCheckUnit rule unit
public class AgeCheckUnit implements RuleUnit {
    private final int minAge;
    private DataSource<Person> persons;
    private List<String> results;

    public AgeCheckUnit( int minAge ) {
        this.minAge = minAge;
    }

    public DataSource<Person> getPersons() {
        return persons;
    }

    public int getMinAge() {
        return minAge;
    }

    public List<String> getResults() {
        return results;
    }
}

The AgeCheckUnit rule unit contains the following DRL rule that performs the verification of the persons in the data source:

Example CheckAge DRL rule in the rule unit
package org.mypackage.myunit
unit AgeCheckUnit

rule CheckAge
  when
    $p : /persons{ age > minAge }
  then
    results.add($p.getName() + ">" + minAge);
end

This example creates a RuleUnitExecutor class, binds the class to the KIE base that contains these two rule units, and creates the two DataSource definitions for the same rule units:

Example executor and data source definitions
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );

DataSource<Object> input = executor.newDataSource( "input" );
DataSource<Person> persons = executor.newDataSource( "persons",
                                                     new Person( "John", 42 ),
                                                     new Person( "Sally", 4 ) );

List<String> results = new ArrayList<>();
executor.bindVariable( "results", results );

You can now insert some objects into the input data source and execute the Unit0 rule unit:

Example rule unit execution with inserted objects
ds.insert("test");
ds.insert(3);
ds.insert(4);
executor.run(Unit0.class);
Example results list from the execution
[Sally>3, John>3]

In this example, the rule unit named AgeCheckUnit is considered a singleton class and then executed only once, with the minAge variable set to 3. Both the String "test" and the Integer 4 inserted into the input data source can also trigger a second execution with the minAge variable set to 4. However, the second execution does not occur because another rule unit with the same identity has already been evaluated.

To resolve this rule unit identity conflict, override the getUnitIdentity() method in the AgeCheckUnit class to include also the minAge variable in the rule unit identity:

Modified AgeCheckUnit rule unit to override the getUnitIdentity() method
public class AgeCheckUnit implements RuleUnit {

    ...

    @Override
    public Identity getUnitIdentity() {
        return new Identity(getClass(), minAge);
    }
}

With this override in place, the previous example rule unit execution produces the following output:

Example results list from executing the modified rule unit
[John>4, Sally>3, John>3]

The rule units with minAge set to 3 and 4 are now considered two different rule units and both are executed.

6.1.3.4. Rule units used with BPMN processes

If you use a DRL rule unit as part of a business rule task in a Business Process Model and Notation (BPMN) process in your Kogito project, you do not need to create an explicit rule unit class that implements the RuleUnit interface. Instead, you designate the rule unit in the DRL file as usual and specify the rule unit in the format unit:PACKAGE_NAME.UNIT_NAME in the implementation details for the business rule task in the BPMN process. When you build the project, the business process implicitly declares the rule unit as part of the business rule task to execute the DRL file.

For example, the following is a DRL file with a rule unit designation:

Example rule unit declaration in a DRL file
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    $p : /persons[age >= adultAge]
  then
    System.out.println($p.getName() + " is adult and greater than " + adultAge);
end

In the relevant business process in a BPMN 2.0 process modeler, you select the business rule task and for the Implementation/Execution property, you set the rule language to DRL and the rule flow group to unit:org.mypackage.AdultUnit.

This rule unit syntax in the Rule Flow Group field specifies that you are using the org.mypackage.AdultUnit rule unit instead of a traditional rule flow group. This is the rule unit that you referenced in the example DRL file. When you build the project, the business process implicitly declares the rule unit as part of the business rule task to execute the DRL file.

6.1.4. Import statements in DRL

import
Figure 95. Import

Similar to import statements in Java, imports in DRL files identify the fully qualified paths and type names for any objects that you want to use in the rules. You specify the package and data object in the format packageName.objectName, with multiple imports on separate lines. The Drools engine automatically imports classes from the Java package with the same name as the DRL package and from the package java.lang.

The following example is an import statement for a loan application object in a mortgage application decision service:

Example import statement in a DRL file
import org.mortgages.LoanApplication;

6.1.5. Functions in DRL

function
Figure 96. Function

Functions in DRL files put semantic code in your rule source file instead of in Java classes. Functions are especially useful if an action (then) part of a rule is used repeatedly and only the parameters differ for each rule. Above the rules in the DRL file, you can declare the function or import a static method from a helper class as a function, and then use the function by name in an action (then) part of the rule.

The following examples illustrate a function that is either declared or imported in a DRL file:

Example function declaration with a rule (option 1)
function String hello(String applicantName) {
    return "Hello " + applicantName + "!";
}

rule "Using a function"
  when
    // Empty
  then
    System.out.println( hello( "James" ) );
end
Example function import with a rule (option 2)
import function my.package.applicant.hello;

rule "Using a function"
  when
    // Empty
  then
    System.out.println( hello( "James" ) );
end

6.1.6. Queries in DRL

query
Figure 97. Query

<@Edoardo, see this section.>

Queries in DRL files search the working memory of the Drools engine for facts related to the rules in the DRL file. You add the query definitions in DRL files and then obtain the matching results in your application code. Queries search for a set of defined conditions and do not require when or then specifications. Query names are global to the KIE base and therefore must be unique among all other rule queries in the project. To return the results of a query, you construct a QueryResults definition using ksession.getQueryResults("name"), where "name" is the query name. This returns a list of query results, which enable you to retrieve the objects that matched the query. You define the query and query results parameters above the rules in the DRL file.

The following example is a query definition in a DRL file for underage applicants in a mortgage application decision service, with the accompanying application code:

Example query definition in a DRL file
query "people under the age of 21"
    $person : Person( age < 21 )
end
Example application code to obtain query results
QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age  of 21" );

You can also iterate over the returned QueryResults using a standard for loop. Each element is a QueryResultsRow that you can use to access each of the columns in the tuple.

Example application code to obtain and iterate over query results
QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );

System.out.println( "These people are under the age of 21:" );

for ( QueryResultsRow row : results ) {
    Person person = ( Person ) row.get( "person" );
    System.out.println( person.getName() + "\n" );
}

Support for positional syntax has been added for more compact code. By default the declared type order in the type declaration matches the argument position. But it possible to override these using the @position annotation. This allows patterns to be used with positional arguments, instead of the more verbose named arguments.

declare Cheese
    name : String @position(1)
    shop : String @position(2)
    price : int @position(0)
end

The @position annotation, in the org.drools.definition.type package, can be used to annotate original objects on the classpath. Currently only fields on classes can be annotated. Inheritance of classes is supported, but not interfaces or methods. The isContainedIn query below demonstrates the use of positional arguments in a pattern; Location(x, y;) instead of Location( thing == x, location == y).

Queries can now call other queries, this combined with optional query arguments provides derivation query style backward chaining. Positional and named syntax is supported for arguments. It is also possible to mix both positional and named, but positional must come first, separated by a semi colon. Literal expressions can be passed as query arguments, but at this stage you cannot mix expressions with variables. Here is an example of a query that calls another query. Note that z here will always be an out variable. The ? symbol means the query is pull only, once the results are returned you will not receive further results as the underlying data changes.

declare Location
    thing : String
    location : String
end

query isContainedIn( String x, String y )
    Location(x, y;)
    or
    ( Location(z, y;) and ?isContainedIn(x, z;) )
end

As previously mentioned you can use live "open" queries to reactively receive changes over time from the query results, as the underlying data it queries against changes. Notice the "look" rule calls the query without using ?.

query isContainedIn( String x, String y )
    Location(x, y;)
    or
    ( Location(z, y;) and isContainedIn(x, z;) )
end

rule look when
    Person( $l : likes )
    isContainedIn( $l, 'office'; )
then
   insertLogical( $l 'is in the office' );
end

Kogito supports unification for derivation queries, in short this means that arguments are optional. It is possible to call queries from Java leaving arguments unspecified using the static field org.drools.core.runtime.rule.Variable.v - note you must use v and not an alternative instance of Variable. These are referred to as out arguments. Note that the query itself does not declare at compile time whether an argument is in or an out, this can be defined purely at runtime on each use. The following example will return all objects contained in the office.

results = ksession.getQueryResults( "isContainedIn", new Object[] {  Variable.v, "office" } );
l = new ArrayList<List<String>>();
for ( QueryResultsRow r : results ) {
    l.add( Arrays.asList( new String[] { (String) r.get( "x" ), (String) r.get( "y" ) } ) );
}

The algorithm uses stacks to handle recursion, so the method stack will not blow up.

It is also possible to use as input argument for a query both the field of a fact as in:

query contains(String $s, String $c)
    $s := String( this.contains( $c ) )
end

rule PersonNamesWithA when
    $p : Person()
    contains( $p.name, "a"; )
then
end

and more in general any kind of valid expression like in:

query checkLength(String $s, int $l)
    $s := String( length == $l )
end

rule CheckPersonNameLength when
    $i : Integer()
    $p : Person()
    checkLength( $p.name, 1 + $i + $p.age; )
then
end

The following is not yet supported:

  • List and Map unification

  • Expression unification - pred( X, X + 1, X * Y / 7 )

6.1.7. Type declarations and metadata in DRL

type declaration
Figure 98. Type declaration
meta data
Figure 99. Metadata

Declarations in DRL files define new fact types or metadata for fact types to be used by rules in the DRL file:

  • New fact types: The default fact type in the java.lang package of Kogito is Object, but you can declare other types in DRL files as needed. Declaring fact types in DRL files enables you to define a new fact model directly in the Drools engine, without creating models in a lower-level language like Java. You can also declare a new type when a domain model is already built and you want to complement this model with additional entities that are used mainly during the reasoning process.

  • Metadata for fact types: You can associate metadata in the format @KEY( VALUE ) with new or existing facts. Metadata can be any kind of data that is not represented by the fact attributes and is consistent among all instances of that fact type. The metadata can be queried at run time by the Drools engine and used in the reasoning process.

6.1.7.1. Type declarations without metadata in DRL

A declaration of a new fact does not require any metadata, but must include a list of attributes or fields. If a type declaration does not include identifying attributes, the Drools engine searches for an existing fact class in the classpath and raises an error if the class is missing.

The following example is a declaration of a new fact type Person with no metadata in a DRL file:

Example declaration of a new fact type with a rule
declare Person
  name : String
  dateOfBirth : java.util.Date
  address : Address
end

rule "Using a declared type"
  when
    $p : Person( name == "James" )
  then   // Insert Mark, who is a customer of James.
    Person mark = new Person();
    mark.setName( "Mark" );
    insert( mark );
end

In this example, the new fact type Person has the three attributes name, dateOfBirth, and address. Each attribute has a type that can be any valid Java type, including another class that you create or a fact type that you previously declared. The dateOfBirth attribute has the type java.util.Date, from the Java API, and the address attribute has the previously defined fact type Address.

To avoid writing the fully qualified name of a class every time you declare it, you can define the full class name as part of the import clause:

Example type declaration with the fully qualified class name in the import
import java.util.Date

declare Person
    name : String
    dateOfBirth : Date
    address : Address
end

When you declare a new fact type, the Drools engine generates at compile time a Java class representing the fact type. The generated Java class is a one-to-one JavaBeans mapping of the type definition.

For example, the following Java class is generated from the example Person type declaration:

Generated Java class for the Person fact type declaration
public class Person implements Serializable {
    private String name;
    private java.util.Date dateOfBirth;
    private Address address;

    // Empty constructor
    public Person() {...}

    // Constructor with all fields
    public Person( String name, Date dateOfBirth, Address address ) {...}

    // If keys are defined, constructor with keys
    public Person( ...keys... ) {...}

    // Getters and setters
    // `equals` and `hashCode`
    // `toString`
}

You can then use the generated class in your rules like any other fact, as illustrated in the previous rule example with the Person type declaration:

Example rule that uses the declared Person fact type
rule "Using a declared type"
  when
    $p : Person( name == "James" )
  then   // Insert Mark, who is a customer of James.
    Person mark = new Person();
    mark.setName( "Mark" );
    insert( mark );
end
6.1.7.2. Enumerative type declarations in DRL

DRL supports the declaration of enumerative types in the format declare enum FACT_TYPE, followed by a comma-separated list of values ending with a semicolon. You can then use the enumerative list in the rules in the DRL file.

For example, the following enumerative type declaration defines days of the week for an employee scheduling rule:

Example enumerative type declaration with a scheduling rule
declare enum DaysOfWeek
   SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday");

   fullName : String
end

rule "Using a declared Enum"
when
   $emp : Employee( dayOff == DaysOfWeek.MONDAY )
then
   ...
end
6.1.7.3. Extended type declarations in DRL

DRL supports type declaration inheritance in the format declare FACT_TYPE_1 extends FACT_TYPE_2. To extend a type declared in Java by a subtype declared in DRL, you repeat the parent type in a declaration statement without any fields.

For example, the following type declarations extend a Student type from a top-level Person type, and a LongTermStudent type from the Student subtype:

Example extended type declarations
import org.people.Person

declare Person end

declare Student extends Person
    school : String
end

declare LongTermStudent extends Student
    years : int
    course : String
end
6.1.7.4. Type declarations with metadata in DRL

You can associate metadata in the format @KEY( VALUE ) (the value is optional) with fact types or fact attributes. Metadata can be any kind of data that is not represented by the fact attributes and is consistent among all instances of that fact type. The metadata can be queried at run time by the Drools engine and used in the reasoning process. Any metadata that you declare before the attributes of a fact type are assigned to the fact type, while metadata that you declare after an attribute are assigned to that particular attribute.

In the following example, the two metadata attributes @author and @dateOfCreation are declared for the Person fact type, and the two metadata items @key (literal) and @maxLength are declared for the name attribute. The @key literal metadata attribute has no required value, so the parentheses and the value are omitted.

Example metadata declaration for fact types and attributes
import java.util.Date

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )

    name : String @key @maxLength( 30 )
    dateOfBirth : Date
    address : Address
end

For declarations of metadata attributes for existing types, you can identify the fully qualified class name as part of the import clause for all declarations or as part of the individual declare clause:

Example metadata declaration for an imported type
import org.drools.examples.Person

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end
Example metadata declaration for a declared type
declare org.drools.examples.Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end
6.1.7.5. Metadata tags for fact type and attribute declarations in DRL

Although you can define custom metadata attributes in DRL declarations, the Drools engine also supports the following predefined metadata tags for declarations of fact types or fact type attributes.

The examples in this section that refer to the VoiceCall class assume that the sample application domain model includes the following class details:

VoiceCall fact class in an example Telecom domain model
public class VoiceCall {
  private String  originNumber;
  private String  destinationNumber;
  private Date    callDateTime;
  private long    callDuration;  // in milliseconds

  // Constructors, getters, and setters
}
@role

This tag determines whether a given fact type is handled as a regular fact or an event in the Drools engine during complex event processing.

Default parameter: fact

Supported parameters: fact, event

@role( fact | event )
Example: Declare VoiceCall as event type
declare VoiceCall
  @role( event )
end
@timestamp

This tag is automatically assigned to every event in the Drools engine. By default, the time is provided by the session clock and assigned to the event when it is inserted into the working memory of the Drools engine. You can specify a custom time stamp attribute instead of the default time stamp added by the session clock.

Default parameter: The time added by the Drools engine session clock

Supported parameters: Session clock time or custom time stamp attribute

@timestamp( ATTRIBUTE_NAME )
Example: Declare VoiceCall timestamp attribute
declare VoiceCall
  @role( event )
  @timestamp( callDateTime )
end
@duration

This tag determines the duration time for events in the Drools engine. Events can be interval-based events or point-in-time events. Interval-based events have a duration time and persist in the working memory of the Drools engine until their duration time has lapsed. Point-in-time events have no duration and are essentially interval-based events with a duration of zero. By default, every event in the Drools engine has a duration of zero. You can specify a custom duration attribute instead of the default.

Default parameter: Null (zero)

Supported parameters: Custom duration attribute

@duration( ATTRIBUTE_NAME )
Example: Declare VoiceCall duration attribute
declare VoiceCall
  @role( event )
  @timestamp( callDateTime )
  @duration( callDuration )
end
@expires

This tag determines the time duration before an event expires in the working memory of the Drools engine. By default, an event expires when the event can no longer match and activate any of the current rules. You can define an amount of time after which an event should expire. This tag definition also overrides the implicit expiration offset calculated from temporal constraints and sliding windows in the KIE base. This tag is available only when the Drools engine is running in stream mode.

Default parameter: Null (event expires after event can no longer match and activate rules)

Supported parameters: Custom timeOffset attribute in the format [#d][#h][#m][#s][[ms]]

@expires( TIME_OFFSET )
Example: Declare expiration offset for VoiceCall events
declare VoiceCall
  @role( event )
  @timestamp( callDateTime )
  @duration( callDuration )
  @expires( 1h35m )
end
@typesafe

This tab determines whether a given fact type is compiled with or without type safety. By default, all type declarations are compiled with type safety enabled. You can override this behavior to type-unsafe evaluation, where all constraints are generated as MVEL constraints and executed dynamically. This is useful when dealing with collections that do not have any generics or mixed type collections.

Default parameter: true

Supported parameters: true, false

@typesafe( BOOLEAN )
Example: Declare VoiceCall for type-unsafe evaluation
declare VoiceCall
  @role( fact )
  @typesafe( false )
end
@serialVersionUID

This tag defines an identifying serialVersionUID value for a serializable class in a fact declaration. If a serializable class does not explicitly declare a serialVersionUID, the serialization run time calculates a default serialVersionUID value for that class based on various aspects of the class, as described in the Java Object Serialization Specification. However, for optimal deserialization results and for greater compatibility with serialized KIE sessions, set the serialVersionUID as needed in the relevant class or in your DRL declarations.

Default parameter: Null

Supported parameters: Custom serialVersionUID integer

@serialVersionUID( INTEGER )
Example: Declare serialVersionUID for a VoiceCall class
declare VoiceCall
  @serialVersionUID( 42 )
end
@key

This tag enables a fact type attribute to be used as a key identifier for the fact type. The generated class can then implement the equals() and hashCode() methods to determine if two instances of the type are equal to each other. The Drools engine can also generate a constructor using all the key attributes as parameters.

Default parameter: None

Supported parameters: None

ATTRIBUTE_DEFINITION @key
Example: Declare Person type attributes as keys
declare Person
    firstName : String @key
    lastName : String @key
    age : int
end

For this example, the Drools engine checks the firstName and lastName attributes to determine if two instances of Person are equal to each other, but it does not check the age attribute. The Drools engine also implicitly generates three constructors: one without parameters, one with the @key fields, and one with all fields:

Example constructors from the key declarations
Person() // Empty constructor

Person( String firstName, String lastName )

Person( String firstName, String lastName, int age )

You can then create instances of the type based on the key constructors, as shown in the following example:

Example instance using the key constructor
Person person = new Person( "John", "Doe" );
@position

This tag determines the position of a declared fact type attribute or field in a positional argument, overriding the default declared order of attributes. You can use this tag to modify positional constraints in patterns while maintaining a consistent format in your type declarations and positional arguments. You can use this tag only for fields in classes on the classpath. If some fields in a single class use this tag and some do not, the attributes without this tag are positioned last, in the declared order. Inheritance of classes is supported, but not interfaces of methods.

Default parameter: None

Supported parameters: Any integer

ATTRIBUTE_DEFINITION @position ( INTEGER )
Example: Declare a fact type and override declared order
declare Person
    firstName : String @position( 1 )
    lastName : String @position( 0 )
    age : int @position( 2 )
    occupation: String
end

In this example, the attributes are prioritized in positional arguments in the following order:

  1. lastName

  2. firstName

  3. age

  4. occupation

In positional arguments, you do not need to specify the field name because the position maps to a known named field. For example, the argument Person( lastName == "Doe" ) is the same as Person( "Doe"; ), where the lastName field has the highest position annotation in the DRL declaration. The semicolon ; indicates that everything before it is a positional argument. You can mix positional and named arguments on a pattern by using the semicolon to separate them. Any variables in a positional argument that have not yet been bound are bound to the field that maps to that position.

The following example patterns illustrate different ways of constructing positional and named arguments. The patterns have two constraints and a binding, and the semicolon differentiates the positional section from the named argument section. Variables and literals and expressions using only literals are supported in positional arguments, but not variables alone.

Example patterns with positional and named arguments
Person( "Doe", "John", $a; )

Person( "Doe", "John"; $a : age )

Person( "Doe"; firstName == "John", $a : age )

Person( lastName == "Doe"; firstName == "John", $a : age )

Positional arguments can be classified as input arguments or output arguments. Input arguments contain a previously declared binding and constrain against that binding using unification. Output arguments generate the declaration and bind it to the field represented by the positional argument when the binding does not yet exist.

In extended type declarations, use caution when defining @position annotations because the attribute positions are inherited in subtypes. This inheritance can result in a mixed attribute order that can be confusing in some cases. Two fields can have the same @position value and consecutive values do not need to be declared. If a position is repeated, the conflict is solved using inheritance, where position values in the parent type have precedence, and then using the declaration order from the first to last declaration.

For example, the following extended type declarations result in mixed positional priorities:

Example extended fact type with mixed position annotations
declare Person
    firstName : String @position( 1 )
    lastName : String @position( 0 )
    age : int @position( 2 )
    occupation: String
end

declare Student extends Person
    degree : String @position( 1 )
    school : String @position( 0 )
    graduationDate : Date
end

In this example, the attributes are prioritized in positional arguments in the following order:

  1. lastName (position 0 in the parent type)

  2. school (position 0 in the subtype)

  3. firstName (position 1 in the parent type)

  4. degree (position 1 in the subtype)

  5. age (position 2 in the parent type)

  6. occupation (first field with no position annotation)

  7. graduationDate (second field with no position annotation)

6.1.7.6. Property-change settings and listeners for fact types

<@Edoardo, see this section. Another hot topic in 7.x and one we link to from the DRL content.>

By default, the Drools engine does not re-evaluate all fact patterns for fact types each time a rule is triggered, but instead reacts only to modified properties that are constrained or bound inside a given pattern. For example, if a rule calls modify() as part of the rule actions but the action does not generate new data in the KIE base, the Drools engine does not automatically re-evaluate all fact patterns because no data was modified. This property reactivity behavior prevents unwanted recursions in the KIE base and results in more efficient rule evaluation. This behavior also means that you do not always need to use the no-loop rule attribute to avoid infinite recursion.

You can modify or disable this property reactivity behavior with the following KnowledgeBuilderConfiguration options, and then use a property-change setting in your Java class or DRL files to fine-tune property reactivity as needed:

  • ALWAYS: (Default) All types are property reactive, but you can disable property reactivity for a specific type by using the @classReactive property-change setting.

  • ALLOWED: No types are property reactive, but you can enable property reactivity for a specific type by using the @propertyReactive property-change setting.

  • DISABLED: No types are property reactive. All property-change listeners are ignored.

Example property reactivity setting in KnowledgeBuilderConfiguration
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
config.setOption(PropertySpecificOption.ALLOWED);
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);

Alternatively, you can update the drools.propertySpecific system property in the standalone.xml file of your Kogito distribution:

Example property reactivity setting in system properties
<system-properties>
  ...
  <property name="drools.propertySpecific" value="ALLOWED"/>
  ...
</system-properties>

The Drools engine supports the following property-change settings and listeners for fact classes or declared DRL fact types:

@classReactive

If property reactivity is set to ALWAYS in the Drools engine (all types are property reactive), this tag disables the default property reactivity behavior for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to re-evaluate all fact patterns for the specified fact type each time the rule is triggered, instead of reacting only to modified properties that are constrained or bound inside a given pattern.

Example: Disable default property reactivity in a DRL type declaration
declare Person
  @classReactive
    firstName : String
    lastName : String
end
Example: Disable default property reactivity in a Java class
@classReactive
public static class Person {
    private String firstName;
    private String lastName;
}
@propertyReactive

If property reactivity is set to ALLOWED in the Drools engine (no types are property reactive unless specified), this tag enables property reactivity for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to react only to modified properties that are constrained or bound inside a given pattern for the specified fact type, instead of re-evaluating all fact patterns for the fact each time the rule is triggered.

Example: Enable property reactivity in a DRL type declaration (when reactivity is disabled globally)
declare Person
  @propertyReactive
    firstName : String
    lastName : String
end
Example: Enable property reactivity in a Java class (when reactivity is disabled globally)
@propertyReactive
public static class Person {
    private String firstName;
    private String lastName;
}
@watch

This tag enables property reactivity for additional properties that you specify in-line in fact patterns in DRL rules. This tag is supported only if property reactivity is set to ALWAYS in the Drools engine, or if property reactivity is set to ALLOWED and the relevant fact type uses the @propertyReactive tag. You can use this tag in DRL rules to add or exclude specific properties in fact property reactivity logic.

Default parameter: None

Supported parameters: Property name, * (all), ! (not), !* (no properties)

<factPattern> @watch ( <property> )
Example: Enable or disable property reactivity in fact patterns
// Listens for changes in both `firstName` (inferred) and `lastName`:
Person(firstName == $expectedFirstName) @watch( lastName )

// Listens for changes in all properties of the `Person` fact:
Person(firstName == $expectedFirstName) @watch( * )

// Listens for changes in `lastName` and explicitly excludes changes in `firstName`:
Person(firstName == $expectedFirstName) @watch( lastName, !firstName )

// Listens for changes in all properties of the `Person` fact except `age`:
Person(firstName == $expectedFirstName) @watch( *, !age )

// Excludes changes in all properties of the `Person` fact (equivalent to using `@classReactivity` tag):
Person(firstName == $expectedFirstName) @watch( !* )

The Drools engine generates a compilation error if you use the @watch tag for properties in a fact type that uses the @classReactive tag (disables property reactivity) or when property reactivity is set to ALLOWED in the Drools engine and the relevant fact type does not use the @propertyReactive tag. Compilation errors also arise if you duplicate properties in listener annotations, such as @watch( firstName, ! firstName ).

@propertyChangeSupport

For facts that implement support for property changes as defined in the JavaBeans Specification, this tag enables the Drools engine to monitor changes in the fact properties.

Example: Declare property change support in JavaBeans object
declare Person
    @propertyChangeSupport
end
6.1.7.7. Access to DRL declared types in application code

<@Edoardo, see this section>

Declared types in DRL are typically used within the DRL files while Java models are typically used when the model is shared between rules and applications. Because declared types are generated at KIE base compile time, an application cannot access them until application run time. In some cases, an application needs to access and handle facts directly from the declared types, especially when the application wraps the Drools engine and provides higher-level, domain-specific user interfaces for rules management.

To handle declared types directly from the application code, you can use the org.drools.definition.type.FactType API in Kogito. Through this API, you can instantiate, read, and write fields in the declared fact types.

The following example code modifies a Person fact type directly from an application:

Example application code to handle a declared fact type through the FactType API
import java.util.Date;

import org.kie.api.definition.type.FactType;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;

...

// Get a reference to a KIE base with the declared type:
KieBase kbase = ...

// Get the declared fact type:
FactType personType = kbase.getFactType("org.drools.examples", "Person");

// Create instances:
Object bob = personType.newInstance();

// Set attribute values:
personType.set(bob, "name", "Bob" );
personType.set(bob, "dateOfBirth", new Date());
personType.set(bob, "address", new Address("King's Road","London","404"));

// Insert the fact into a KIE session:
KieSession ksession = ...
ksession.insert(bob);
ksession.fireAllRules();

// Read attributes:
String name = (String) personType.get(bob, "name");
Date date = (Date) personType.get(bob, "dateOfBirth");

The API also includes other helpful methods, such as setting all the attributes at once, reading values from a Map collection, or reading all attributes at once into a Map collection.

Although the API behavior is similar to Java reflection, the API does not use reflection and relies on more performant accessors that are implemented with generated bytecode.

6.1.8. Global variables in DRL

global
Figure 100. Global

<@Edoardo, see this section>

Global variables in DRL files typically provide data or services for the rules, such as application services used in rule consequences, and return data from rules, such as logs or values added in rule consequences. You set the global value in the working memory of the Drools engine through a KIE session configuration or REST operation, declare the global variable above the rules in the DRL file, and then use it in an action (then) part of the rule. For multiple global variables, use separate lines in the DRL file.

The following example illustrates a global variable list configuration for the Drools engine and the corresponding global variable definition in the DRL file:

Example global list configuration for the Drools engine
List<String> list = new ArrayList<>();
KieSession kieSession = kiebase.newKieSession();
kieSession.setGlobal( "myGlobalList", list );
Example global variable definition with a rule
global java.util.List myGlobalList;

rule "Using a global"
  when
    // Empty
  then
    myGlobalList.add( "My global list" );
end

Do not use global variables to establish conditions in rules unless a global variable has a constant immutable value. Global variables are not inserted into the working memory of the Drools engine, so the Drools engine cannot track value changes of variables.

Do not use global variables to share data between rules. Rules always reason and react to the working memory state, so if you want to pass data from rule to rule, assert the data as facts into the working memory of the Drools engine.

A use case for a global variable might be an instance of an email service. In your integration code that is calling the Drools engine, you obtain your emailService object and then set it in the working memory of the Drools engine. In the DRL file, you declare that you have a global of type emailService and give it the name "email", and then in your rule consequences, you can use actions such as email.sendSMS(number, message).

If you declare global variables with the same identifier in multiple packages, then you must set all the packages with the same type so that they all reference the same global value.

6.1.9. Rule attributes in DRL

rule attributes
Figure 101. Rule attributes

Rule attributes are additional specifications that you can add to business rules to modify rule behavior. In DRL files, you typically define rule attributes above the rule conditions and actions, with multiple attributes on separate lines, in the following format:

rule "rule_name"
    // Attribute
    // Attribute
    when
        // Conditions
    then
        // Actions
end

The following table lists the names and supported values of the attributes that you can assign to rules:

Table 5. Rule attributes
Attribute Value

salience

An integer defining the priority of the rule. Rules with a higher salience value are given higher priority when ordered in the activation queue.

Example: salience 10

enabled

A Boolean value. When the option is selected, the rule is enabled. When the option is not selected, the rule is disabled.

Example: enabled true

date-effective

A string containing a date and time definition. The rule can be activated only if the current date and time is after a date-effective attribute.

Example: date-effective "4-Sep-2018"

date-expires

A string containing a date and time definition. The rule cannot be activated if the current date and time is after the date-expires attribute.

Example: date-expires "4-Oct-2018"

no-loop

A Boolean value. When the option is selected, the rule cannot be reactivated (looped) if a consequence of the rule re-triggers a previously met condition. When the condition is not selected, the rule can be looped in these circumstances.

Example: no-loop true

<@Edoardo, you said get rid of these three rule groups, or just agenda-group? If we ditch all three, it will impact many example rules and other DRL ref descriptions in the content that use one or more of these rules extensively. So will be a widespread update. That’s fine, just making sure we’re aware of that. Should we not just keep them and explain that rule units are better? This is what we already do.>

agenda-group

A string identifying an agenda group to which you want to assign the rule. Agenda groups allow you to partition the agenda to provide more execution control over groups of rules. Only rules in an agenda group that has acquired a focus are able to be activated.

Example: agenda-group "GroupName"

activation-group

A string identifying an activation (or XOR) group to which you want to assign the rule. In activation groups, only one rule can be activated. The first rule to fire will cancel all pending activations of all rules in the activation group.

Example: activation-group "GroupName"

ruleflow-group

A string identifying a rule flow group. In rule flow groups, rules can fire only when the group is activated by the associated rule flow.

Example: ruleflow-group "GroupName"

duration

A long integer value defining the duration of time in milliseconds after which the rule can be activated, if the rule conditions are still met.

Example: duration 10000

timer

A string identifying either int (interval) or cron timer definitions for scheduling the rule.

Example: timer ( cron:* 0/15 * * * ? ) (every 15 minutes)

calendar

A Quartz calendar definition for scheduling the rule.

Example: calendars "* * 0-7,18-23 ? * *" (exclude non-business hours)

auto-focus

A Boolean value, applicable only to rules within agenda groups. When the option is selected, the next time the rule is activated, a focus is automatically given to the agenda group to which the rule is assigned.

Example: auto-focus true

lock-on-active

A Boolean value, applicable only to rules within rule flow groups or agenda groups. When the option is selected, the next time the ruleflow group for the rule becomes active or the agenda group for the rule receives a focus, the rule cannot be activated again until the ruleflow group is no longer active or the agenda group loses the focus. This is a stronger version of the no-loop attribute, because the activation of a matching rule is discarded regardless of the origin of the update (not only by the rule itself). This attribute is ideal for calculation rules where you have a number of rules that modify a fact and you do not want any rule re-matching and firing again.

Example: lock-on-active true

dialect

A string identifying either JAVA or MVEL as the language to be used for code expressions in the rule. By default, the rule uses the dialect specified at the package level. Any dialect specified here overrides the package dialect setting for the rule.

Example: dialect "JAVA"

6.1.9.1. Timer and calendar rule attributes in DRL

Timers and calendars are DRL rule attributes that enable you to apply scheduling and timing constraints to your DRL rules. These attributes require additional configurations depending on the use case.

The timer attribute in DRL rules is a string identifying either int (interval) or cron timer definitions for scheduling a rule and supports the following formats:

Timer attribute formats
timer ( int: __INITIAL_DELAY__ __REPEAT_INTERVAL__ )

timer ( cron: __CRON_EXPRESSION__ )
Example interval timer attributes
// Run after a 30-second delay
timer ( int: 30s )

// Run every 5 minutes after a 30-second delay each time
timer ( int: 30s 5m )
Example cron timer attribute
// Run every 15 minutes
timer ( cron:* 0/15 * * * ? )

Interval timers follow the semantics of java.util.Timer objects, with an initial delay and an optional repeat interval. Cron timers follow standard Unix cron expressions.

The following example DRL rule uses a cron timer to send an SMS text message every 15 minutes:

Example DRL rule with a cron timer
rule "Send SMS message every 15 minutes"
  timer ( cron:* 0/15 * * * ? )
  when
    $a : Alarm( on == true )
  then
    channels[ "sms" ].insert( new Sms( $a.mobileNumber, "The alarm is still on." );
end

<@Edoardo, see these paragraphs about active vs. passive modes (fireAllRules vs fireUntilHalt) and then configuring KIE session. Several other places in the DRL/engine content discusses active vs passive, fireAllRules, etc., so need some direction.>

Generally, a rule that is controlled by a timer becomes active when the rule is triggered and the rule consequence is executed repeatedly, according to the timer settings. The execution stops when the rule condition no longer matches incoming facts. However, the way the Drools engine handles rules with timers depends on whether the Drools engine is in active mode or in passive mode.

By default, the Drools engine runs in passive mode and evaluates rules, according to the defined timer settings, when a user or an application explicitly calls fireAllRules(). Conversely, if a user or application calls fireUntilHalt(), the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly calls halt().

When the Drools engine is in active mode, rule consequences are executed even after control returns from a call to fireUntilHalt() and the Drools engine remains reactive to any changes made to the working memory. For example, removing a fact that was involved in triggering the timer rule execution causes the repeated execution to terminate, and inserting a fact so that some rule matches causes that rule to be executed. However, the Drools engine is not continually active, but is active only after a rule is executed. Therefore, the Drools engine does not react to asynchronous fact insertions until the next execution of a timer-controlled rule. Disposing a KIE session terminates all timer activity.

When the Drools engine is in passive mode, rule consequences of timed rules are evaluated only when fireAllRules() is invoked again. However, you can change the default timer-execution behavior in passive mode by configuring the KIE session with a TimedRuleExecutionOption option, as shown in the following example:

KIE session configuration to automatically execute timed rules in passive mode
KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration();
ksconf.setOption( TimedRuleExecutionOption.YES );
KSession ksession = kbase.newKieSession(ksconf, null);

You can additionally set a FILTERED specification on the TimedRuleExecutionOption option that enables you to define a callback to filter those rules, as shown in the following example:

KIE session configuration to filter which timed rules are automatically executed
KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration();
conf.setOption( new TimedRuleExecutionOption.FILTERED(new TimedRuleExecutionFilter() {
    public boolean accept(Rule[] rules) {
        return rules[0].getName().equals("MyRule");
    }
}) );

For interval timers, you can also use an expression timer with expr instead of int to define both the delay and interval as an expression instead of a fixed value.

The following example DRL file declares a fact type with a delay and period that are then used in the subsequent rule with an expression timer:

Example rule with an expression timer
declare Bean
  delay   : String = "30s"
  period  : long = 60000
end

rule "Expression timer"
  timer ( expr: $d, $p )
  when
    Bean( $d : delay, $p : period )
  then
    // Actions
end

The expressions, such as $d and $p in this example, can use any variable defined in the pattern-matching part of the rule. The variable can be any String value that can be parsed into a time duration or any numeric value that is internally converted in a long value for a duration in milliseconds.

Both interval and expression timers can use the following optional parameters:

  • start and end: A Date or a String representing a Date or a long value. The value can also be a Number that is transformed into a Java Date in the format new Date( ((Number) n).longValue() ).

  • repeat-limit: An integer that defines the maximum number of repetitions allowed by the timer. If both the end and the repeat-limit parameters are set, the timer stops when the first of the two is reached.

Example timer attribute with optional start, end, and repeat-limit parameters
timer (int: 30s 1h; start=3-JAN-2020, end=4-JAN-2020, repeat-limit=50)

In this example, the rule is scheduled for every hour, after a delay of 30 seconds each hour, beginning on 3 January 2020 and ending either on 4 January 2020 or when the cycle repeats 50 times.

If the system is paused (for example, the session is serialized and then later deserialized), the rule is scheduled only one time to recover from missing activations regardless of how many activations were missed during the pause, and then the rule is subsequently scheduled again to continue in sync with the timer setting.

The calendar attribute in DRL rules is a Quartz calendar definition for scheduling a rule and supports the following format:

Calendar attribute format
calendars "DEFINITION_OR_REGISTERED_NAME"
Example calendar attributes
// Exclude non-business hours
calendars "* * 0-7,18-23 ? * *"

// Weekdays only, as registered in the KIE session
calendars "weekday"

You can adapt a Quartz calendar based on the Quartz calendar API and then register the calendar in the KIE session, as shown in the following example:

Adapting a Quartz Calendar
Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
Registering the calendar in the KIE session
ksession.getCalendars().set( "weekday", weekDayCal );

You can use calendars with standard rules and with rules that use timers. The calendar attribute can contain one or more comma-separated calendar names written as String literals.

The following example rules use both calendars and timers to schedule the rules:

Example rules with calendars and timers
rule "Weekdays are high priority"
  calendars "weekday"
  timer ( int:0 1h )
  when
    Alarm()
  then
    send( "priority high - we have an alarm" );
end

rule "Weekends are low priority"
  calendars "weekend"
  timer ( int:0 4h )
  when
    Alarm()
  then
    send( "priority low - we have an alarm" );
end

6.1.10. Rule conditions in DRL

rule
Figure 102. Rule
lhs
Figure 103. Conditional element in a rule

The when part of a DRL rule (also known as the Left Hand Side (LHS) of the rule) contains the conditions that must be met to execute an action. Conditions consist of a series of stated patterns and constraints, with optional bindings and supported rule condition elements (keywords), based on the available data objects in the package. For example, if a bank requires loan applicants to have over 21 years of age, then the when condition of an "Underage" rule would be Applicant( age < 21 ).

DRL uses when instead of if because if is typically part of a procedural execution flow during which a condition is checked at a specific point in time. In contrast, when indicates that the condition evaluation is not limited to a specific evaluation sequence or point in time, but instead occurs continually at any time. Whenever the condition is met, the actions are executed.

If the when section is empty, then the conditions are considered to be true and the actions in the then section are executed the first time a fireAllRules() call is made in the Drools engine. This is useful if you want to use rules to set up the Drools engine state.

The following example rule uses empty conditions to insert a fact every time the rule is executed:

Example rule without conditions
rule "Always insert applicant"
  when
    // Empty
  then   // Actions to be executed once
    insert( new Applicant() );
end

// The rule is internally rewritten in the following way:

rule "Always insert applicant"
  when
    eval( true )
  then
    insert( new Applicant() );
end

If rule conditions use multiple patterns with no defined keyword conjunctions (such as and, or, or not), the default conjunction is and:

Example rule without keyword conjunctions
rule "Underage"
  when
    application : LoanApplication()
    Applicant( age < 21 )
  then
    // Actions
end

// The rule is internally rewritten in the following way:

rule "Underage"
  when
    application : LoanApplication()
    and Applicant( age < 21 )
  then
    // Actions
end
6.1.10.1. Patterns and constraints

A pattern in a DRL rule condition is the segment to be matched by the Drools engine. A pattern can potentially match each fact that is inserted into the working memory of the Drools engine. A pattern can also contain constraints to further define the facts to be matched.

The railroad diagram below shows the syntax for this:

Pattern
Figure 104. Pattern

In the simplest form, with no constraints, a pattern matches a fact of the given type. In the following example, the type is Person, so the pattern will match against all Person objects in the working memory of the Drools engine:

Example pattern for a single fact type
Person()

The type does not need to be the actual class of some fact object. Patterns can refer to superclasses or even interfaces, potentially matching facts from many different classes. For example, the following pattern matches all objects in the working memory of the Drools engine:

Example pattern for all objects
Object() // Matches all objects in the working memory

The parentheses of a pattern enclose the constraints, such as the following constraint on the person’s age:

Example pattern with a constraint
Person( age == 50 )

A constraint is an expression that returns true or false. Pattern constraints in DRL are essentially Java expressions with some enhancements, such as property access, and some differences, such as equals() and !equals() semantics for == and != (instead of the usual same and not same semantics).

Any JavaBeans property can be accessed directly from pattern constraints. A bean property is exposed internally using a standard JavaBeans getter that takes no arguments and returns something. For example, the age property is written as age in DRL instead of the getter getAge():

DRL constraint syntax with JavaBeans properties
Person( age == 50 )

// This is the same as the following getter format:

Person( getAge() == 50 )

Kogito uses the standard JDK Introspector class to achieve this mapping, so it follows the standard JavaBeans specification. For optimal Drools engine performance, use the property access format, such as age, instead of using getters explicitly, such as getAge().

Do not use property accessors to change the state of the object in a way that might affect the rules because the Drools engine caches the results of the match between invocations for higher efficiency.

For example, do not use property accessors in the following ways:

public int getAge() {
    age++; // Do not do this.
    return age;
}
public int getAge() {
    Date now = DateUtil.now(); // Do not do this.
    return DateUtil.differenceInYears(now, birthday);
}

Instead of following the second example, insert a fact that wraps the current date in the working memory and update that fact between fireAllRules() as needed.

However, if the getter of a property cannot be found, the compiler uses the property name as a fallback method name, without arguments:

Fallback method if object is not found
Person( age == 50 )

// If `Person.getAge()` does not exist, the compiler uses the following syntax:

Person( age() == 50 )

You can also nest access properties in patterns, as shown in the following example. Nested properties are indexed by the Drools engine.

Example pattern with nested property access
Person( address.houseNumber == 50 )

// This is the same as the following format:

Person( getAddress().getHouseNumber() == 50 )
In stateful KIE sessions, use nested accessors carefully because the working memory of the Drools engine is not aware of any of the nested values and does not detect when they change. Either consider the nested values immutable while any of their parent references are inserted into the working memory, or, if you want to modify a nested value, mark all of the outer facts as updated. In the previous example, when the houseNumber property changes, any Person with that Address must be marked as updated.

You can use any Java expression that returns a boolean value as a constraint inside the parentheses of a pattern. Java expressions can be mixed with other expression enhancements, such as property access:

Example pattern with a constraint using property access and Java expression
Person( age == 50 )

You can change the evaluation priority by using parentheses, as in any logical or mathematical expression:

Example evaluation order of constraints
Person( age > 100 && ( age % 10 == 0 ) )

You can also reuse Java methods in constraints, as shown in the following example:

Example constraints with reused Java methods
Person( Math.round( weight / ( height * height ) ) < 25.0 )

Do not use constraints to change the state of the object in a way that might affect the rules because the Drools engine caches the results of the match between invocations for higher efficiency. Any method that is executed on a fact in the rule conditions must be a read-only method. Also, the state of a fact should not change between rule invocations unless those facts are marked as updated in the working memory on every change.

For example, do not use a pattern constraint in the following ways:

Person( incrementAndGetAge() == 10 ) // Do not do this.
Person( System.currentTimeMillis() % 1000 == 0 ) // Do not do this.

Standard Java operator precedence applies to constraint operators in DRL, and DRL operators follow standard Java semantics except for the == and != operators.

The == operator uses null-safe equals() semantics instead of the usual same semantics. For example, the pattern Person( firstName == "John" ) is similar to java.util.Objects.equals(person.getFirstName(), "John"), and because "John" is not null, the pattern is also similar to "John".equals(person.getFirstName()).

The != operator uses null-safe !equals() semantics instead of the usual not same semantics. For example, the pattern Person( firstName != "John" ) is similar to !java.util.Objects.equals(person.getFirstName(), "John").

If the field and the value of a constraint are of different types, the Drools engine uses type coercion to resolve the conflict and reduce compilation errors. For instance, if "ten" is provided as a string in a numeric evaluator, a compilation error occurs, whereas "10" is coerced to a numeric 10. In coercion, the field type always takes precedence over the value type:

Example constraint with a value that is coerced
Person( age == "10" ) // "10" is coerced to 10

For groups of constraints, you can use a delimiting comma , to use implicit and connective semantics:

Example patterns with multiple constraints
// Person is at least 50 years old and weighs at least 80 kilograms:
Person( age > 50, weight > 80 )

// Person is at least 50 years old, weighs at least 80 kilograms, and is taller than 2 meters:
Person( age > 50, weight > 80, height > 2 )
Although the && and , operators have the same semantics, they are resolved with different priorities. The && operator precedes the || operator, and both the && and || operators together precede the , operator. Use the comma operator at the top-level constraint for optimal Drools engine performance and human readability.

You cannot embed a comma operator in a composite constraint expression, such as in parentheses:

Example of misused comma in composite constraint expression
// Do not use the following format:
Person( ( age > 50, weight > 80 ) || height > 2 )

// Use the following format instead:
Person( ( age > 50 && weight > 80 ) || height > 2 )
6.1.10.2. Bound variables in patterns and constraints

You can bind variables to patterns and constraints to refer to matched objects in other portions of a rule. Bound variables can help you define rules more efficiently or more consistently with how you annotate facts in your data model. To differentiate more easily between variables and fields in a rule, use the standard format $VARIABLE for variables, especially in complex rules. This convention is helpful but not required in DRL.

For example, the following DRL rule uses the variable $p for a pattern with the Person fact:

Pattern with a bound variable
rule "simple rule"
  when
    $p : Person()
  then
    System.out.println( "Person " + $p );
end

Similarly, you can also bind variables to properties in pattern constraints, as shown in the following example:

// Two persons of the same age:
Person( $firstAge : age ) // Binding
Person( age == $firstAge ) // Constraint expression

Ensure that you separate constraint bindings and constraint expressions for clearer and more efficient rule definitions. Although mixed bindings and expressions are supported, they can complicate patterns and affect evaluation efficiency.

// Do not use the following format:
Person( $age : age * 2 < 100 )

// Use the following format instead:
Person( age * 2 < 100, $age : age )

The Drools engine does not support bindings to the same declaration, but does support unification of arguments across several properties. While positional arguments are always processed with unification, the unification symbol := exists for named arguments.

The following example patterns unify the age property across two Person facts:

Example pattern with unification
Person( $age := age )
Person( $age := age )

Unification declares a binding for the first occurrence and constrains to the same value of the bound field for sequence occurrences.

6.1.10.3. Nested constraints and inline casts

In some cases, you might need to access multiple properties of a nested object, as shown in the following example:

Example pattern to access multiple properties
Person( name == "mark", address.city == "london", address.country == "uk" )

You can group these property accessors to nested objects with the syntax .( <constraints> ) for more readable rules, as shown in the following example:

Example pattern with grouped constraints
Person( name == "mark", address.( city == "london", country == "uk") )
The period prefix . differentiates the nested object constraints from a method call.

When you work with nested objects in patterns, you can use the syntax TYPE#SUB_TYPE to cast to a subtype and make the getters from the parent type available to the subtype. You can use either the object name or fully qualified class name, and you can cast to one or multiple subtypes, as shown in the following examples:

Example patterns with inline casting to a subtype
// Inline casting with subtype name:
Person( name == "mark", address#LongAddress.country == "uk" )

// Inline casting with fully qualified class name:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )

// Multiple inline casts:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )

These example patterns cast Address to LongAddress, and additionally to DetailedCountry in the last example, making the parent getters available to the subtypes in each case.

You can use the instanceof operator to infer the results of the specified type in subsequent uses of that field with the pattern, as shown in the following example:

Person( name == "mark", address instanceof LongAddress, address.country == "uk" )

If an inline cast is not possible (for example, if instanceof returns false), the evaluation is considered false.

6.1.10.4. Date literal in constraints

By default, the Drools engine supports the date format dd-mmm-yyyy. You can customize the date format, including a time format mask if needed, by providing an alternative format mask with the system property drools.dateformat="dd-mmm-yyyy hh:mm". You can also customize the date format by changing the language locale with the drools.defaultlanguage and drools.defaultcountry system properties (for example, the locale of Thailand is set as drools.defaultlanguage=th and drools.defaultcountry=TH).

Example pattern with a date literal restriction
Person( bornBefore < "27-Oct-2009" )
6.1.10.5. Auto-boxing and primitive types

Drools attempts to preserve numbers in their primitive or object wrapper form, so a variable bound to an int primitive when used in a code block or expression will no longer need manual unboxing; unlike early Drools versions where all primitives were autoboxed, requiring manual unboxing. A variable bound to an object wrapper will remain as an object; the existing JDK 1.5 and JDK 5 rules to handle auto-boxing and unboxing apply in this case. When evaluating field constraints, the system attempts to coerce one of the values into a comparable format; so a primitive is comparable to an object wrapper.

6.1.10.6. Supported operators in DRL pattern constraints

DRL supports standard Java semantics for operators in pattern constraints, with some exceptions and with some additional operators that are unique in DRL. The following list summarizes the operators that are handled differently in DRL constraints than in standard Java semantics or that are unique in DRL constraints.

.(), #

Use the .() operator to group property accessors to nested objects, and use the # operator to cast to a subtype in nested objects. Casting to a subtype makes the getters from the parent type available to the subtype. You can use either the object name or fully qualified class name, and you can cast to one or multiple subtypes.

Example patterns with nested objects
// Ungrouped property accessors:
Person( name == "mark", address.city == "london", address.country == "uk" )

// Grouped property accessors:
Person( name == "mark", address.( city == "london", country == "uk") )
The period prefix . differentiates the nested object constraints from a method call.
Example patterns with inline casting to a subtype
// Inline casting with subtype name:
Person( name == "mark", address#LongAddress.country == "uk" )

// Inline casting with fully qualified class name:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )

// Multiple inline casts:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
!.

Use this operator to dereference a property in a null-safe way. The value to the left of the !. operator must be not null (interpreted as != null) in order to give a positive result for pattern matching.

Example constraint with null-safe dereferencing
Person( $streetName : address!.street )

// This is internally rewritten in the following way:

Person( address != null, $streetName : address.street )
[]

Use this operator to access a List value by index or a Map value by key.

Example constraints with List and Map access
// The following format is the same as `childList(0).getAge() == 18`:
Person(childList[0].age == 18)

// The following format is the same as `credentialMap.get("jdoe").isValid()`:
Person(credentialMap["jdoe"].valid)
<, <=, >, >=

Use these operators on properties with natural ordering. For example, for Date fields, the < operator means before, and for String fields, the operator means alphabetically before. These properties apply only to comparable properties.

Example constraints with before operator
Person( birthDate < $otherBirthDate )

Person( firstName < $otherFirstName )
==, !=

Use these operators as equals() and !equals() methods in constraints, instead of the usual same and not same semantics.

Example constraint with null-safe equality
Person( firstName == "John" )

// This is similar to the following formats:

java.util.Objects.equals(person.getFirstName(), "John")
"John".equals(person.getFirstName())
Example constraint with null-safe not equality
Person( firstName != "John" )

// This is similar to the following format:

!java.util.Objects.equals(person.getFirstName(), "John")
&&, ||

Use these operators to create an abbreviated combined relation condition that adds more than one restriction on a field. You can group constraints with parentheses () to create a recursive syntax pattern.

Example constraints with abbreviated combined relation
// Simple abbreviated combined relation condition using a single `&&`:
Person(age > 30 && < 40)

// Complex abbreviated combined relation using groupings:
Person(age ((> 30 && < 40) || (> 20 && < 25)))

// Mixing abbreviated combined relation with constraint connectives:
Person(age > 30 && < 40 || location == "london")
abbreviatedCombinedRelationCondition
Figure 105. Abbreviated combined relation condition
abbreviatedCombinedRelationConditionGroup
Figure 106. Abbreviated combined relation condition withparentheses
matches, not matches

Use these operators to indicate that a field matches or does not match a specified Java regular expression. Typically, the regular expression is a String literal, but variables that resolve to a valid regular expression are also supported. These operators apply only to String properties. If you use matches against a null value, the resulting evaluation is always false. If you use not matches against a null value, the resulting evaluation is always true. As in Java, regular expressions that you write as String literals must use a double backslash \\ to escape.

Example constraint to match or not match a regular expression
Person( country matches "(USA)?\\S*UK" )

Person( country not matches "(USA)?\\S*UK" )
contains, not contains

Use these operators to verify whether a field that is an Array or a Collection contains or does not contain a specified value. These operators apply to Array or Collection properties, but you can also use these operators in place of String.contains() and !String.contains() constraints checks.

Example constraints with contains and not contains for a Collection
// Collection with a specified field:
FamilyTree( countries contains "UK" )

FamilyTree( countries not contains "UK" )


// Collection with a variable:
FamilyTree( countries contains $var )

FamilyTree( countries not contains $var )
Example constraints with contains and not contains for a String literal
// Sting literal with a specified field:
Person( fullName contains "Jr" )

Person( fullName not contains "Jr" )


// String literal with a variable:
Person( fullName contains $var )

Person( fullName not contains $var )
For backward compatibility, the excludes operator is a supported synonym for not contains.
memberOf, not memberOf

Use these operators to verify whether a field is a member of or is not a member of an Array or a Collection that is defined as a variable. The Array or Collection must be a variable.

Example constraints with memberOf and not memberOf with a Collection
FamilyTree( person memberOf $europeanDescendants )

FamilyTree( person not memberOf $europeanDescendants )
soundslike

Use this operator to verify whether a word has almost the same sound, using English pronunciation, as the given value (similar to the matches operator). This operator uses the Soundex algorithm.

Example constraint with soundslike
// Match firstName "Jon" or "John":
Person( firstName soundslike "John" )
str

Use this operator to verify whether a field that is a String starts with or ends with a specified value. You can also use this operator to verify the length of the String.

Example constraints with str
// Verify what the String starts with:
Message( routingValue str[startsWith] "R1" )

// Verify what the String ends with:
Message( routingValue str[endsWith] "R2" )

// Verify the length of the String:
Message( routingValue str[length] 17 )
in, notin

Use these operators to specify more than one possible value to match in a constraint (compound value restriction). This functionality of compound value restriction is supported only in the in and not in operators. The second operand of these operators must be a comma-separated list of values enclosed in parentheses. You can provide values as variables, literals, return values, or qualified identifiers. These operators are internally rewritten as a list of multiple restrictions using the operators == or !=.

compoundValueRestriction
Figure 107. compoundValueRestriction
Example constraints with in and notin
Person( $color : favoriteColor )
Color( type in ( "red", "blue", $color ) )

Person( $color : favoriteColor )
Color( type notin ( "red", "blue", $color ) )
6.1.10.7. Operator precedence in DRL pattern constraints

DRL supports standard Java operator precedence for applicable constraint operators, with some exceptions and with some additional operators that are unique in DRL. The following table lists DRL operator precedence where applicable, from highest to lowest precedence:

Table 6. Operator precedence in DRL pattern constraints
Operator type Operators Notes

Nested or null-safe property access

.(), !.

Not standard Java semantics

List or Map access

[]

Not standard Java semantics

Constraint binding

:

Not standard Java semantics

Multiplicative

*, /%

Additive

+, -

Shift

>>, >>>, <<

Relational

<, <=, >, >=, instanceof

Equality

== !=

Uses equals() and !equals() semantics, not standard Java same and not same semantics

Non-short-circuiting AND

&

Non-short-circuiting exclusive OR

^

Non-short-circuiting inclusive OR

|

Logical AND

&&

Logical OR

||

Ternary

? :

Comma-separated AND

,

Not standard Java semantics

6.1.10.8. Supported rule condition elements in DRL (keywords)

DRL supports the following rule condition elements (keywords) that you can use with the patterns that you define in DRL rule conditions:

and

Use this to group conditional components into a logical conjunction. Infix and prefix and are supported. You can group patterns explicitly with parentheses (). By default, all listed patterns are combined with and when no conjunction is specified.

infixAnd
Figure 108. infixAnd
prefixAnd
Figure 109. prefixAnd
Example patterns with and
//Infix `and`:
Color( colorType : type ) and Person( favoriteColor == colorType )

//Infix `and` with grouping:
(Color( colorType : type ) and (Person( favoriteColor == colorType ) or Person( favoriteColor == colorType ))

// Prefix `and`:
(and Color( colorType : type ) Person( favoriteColor == colorType ))

// Default implicit `and`:
Color( colorType : type )
Person( favoriteColor == colorType )

Do not use a leading declaration binding with the and keyword (as you can with or, for example). A declaration can only reference a single fact at a time, and if you use a declaration binding with and, then when and is satisfied, it matches both facts and results in an error.

Example misuse of and
// Causes compile error:
$person : (Person( name == "Romeo" ) and Person( name == "Juliet"))
or

Use this to group conditional components into a logical disjunction. Infix and prefix or are supported. You can group patterns explicitly with parentheses (). You can also use pattern binding with or, but each pattern must be bound separately.

infixOr
Figure 110. infixOr
prefixOr
Figure 111. prefixOr
Example patterns with or
//Infix `or`:
Color( colorType : type ) or Person( favoriteColor == colorType )

//Infix `or` with grouping:
(Color( colorType : type ) or (Person( favoriteColor == colorType ) and Person( favoriteColor == colorType ))

// Prefix `or`:
(or Color( colorType : type ) Person( favoriteColor == colorType ))
Example patterns with or and pattern binding
pensioner : (Person( sex == "f", age > 60 ) or Person( sex == "m", age > 65 ))

(or pensioner : Person( sex == "f", age > 60 )
    pensioner : Person( sex == "m", age > 65 ))

The behavior of the or condition element is different from the connective || operator for constraints and restrictions in field constraints. The Drools engine does not directly interpret the or element but uses logical transformations to rewrite a rule with or as a number of sub-rules. This process ultimately results in a rule that has a single or as the root node and one sub-rule for each of its condition elements. Each sub-rule is activated and executed like any normal rule, with no special behavior or interaction between the sub-rules.

Therefore, consider the or condition element a shortcut for generating two or more similar rules that, in turn, can create multiple activations when two or more terms of the disjunction are true.

exists

Use this to specify facts and constraints that must exist. This option is triggered on only the first match, not subsequent matches. If you use this element with multiple patterns, enclose the patterns with parentheses ().

exists
Figure 112. Exists
Example patterns with exists
exists Person( firstName == "John")

exists (Person( firstName == "John", age == 42 ))

exists (Person( firstName == "John" ) and
        Person( lastName == "Doe" ))
not

Use this to specify facts and constraints that must not exist. If you use this element with multiple patterns, enclose the patterns with parentheses ().

not
Figure 113. Not
Example patterns with not
not Person( firstName == "John")

not (Person( firstName == "John", age == 42 ))

not (Person( firstName == "John" ) and
     Person( lastName == "Doe" ))
forall

Use this to verify whether all facts that match the first pattern match all the remaining patterns. When a forall construct is satisfied, the rule evaluates to true. This element is a scope delimiter, so it can use any previously bound variable, but no variable bound inside of it is available for use outside of it.

forall
Figure 114. Forall
Example rule with forall
rule "All full-time employees have red ID badges"
  when
    forall( $emp : Employee( type == "fulltime" )
                   Employee( this == $emp, badgeColor = "red" ) )
  then
    // True, all full-time employees have red ID badges.
end

In this example, the rule selects all Employee objects whose type is "fulltime". For each fact that matches this pattern, the rule evaluates the patterns that follow (badge color) and if they match, the rule evaluates to true.

To state that all facts of a given type in the working memory of the Drools engine must match a set of constraints, you can use forall with a single pattern for simplicity.

Example rule with forall and a single pattern
rule "All full-time employees have red ID badges"
  when
    forall( Employee( badgeColor = "red" ) )
  then
    // True, all full-time employees have red ID badges.
end

You can use forall constructs with multiple patterns or nest them with other condition elements, such as inside a not element construct.

Example rule with forall and multiple patterns
rule "All employees have health and dental care programs"
  when
    forall( $emp : Employee()
            HealthCare( employee == $emp )
            DentalCare( employee == $emp )
          )
  then
    // True, all employees have health and dental care.
end
Example rule with forall and not
rule "Not all employees have health and dental care"
  when
    not ( forall( $emp : Employee()
                  HealthCare( employee == $emp )
                  DentalCare( employee == $emp ) )
        )
  then
    // True, not all employees have health and dental care.
end
The format forall( p1 p2 p3 …​) is equivalent to not( p1 and not( and p2 p3 …​ ) ).
from

Use this to specify a data source for a pattern. This enables the Drools engine to reason over data that is not in the working memory. The data source can be a sub-field on a bound variable or the result of a method call. The expression used to define the object source is any expression that follows regular MVEL syntax. Therefore, the from element enables you to easily use object property navigation, execute method calls, and access maps and collection elements.

from
Figure 115. from
Example rule with from and pattern binding
rule "Validate zipcode"
  when
    Person( $personAddress : address )
    Address( zipcode == "23920W" ) from $personAddress
  then
    // Zip code is okay.
end
Example rule with from and a graph notation
rule "Validate zipcode"
  when
    $p : Person()
    $a : Address( zipcode == "23920W" ) from $p.address
  then
    // Zip code is okay.
end
Example rule with from to iterate over all objects
rule "Apply 10% discount to all items over US$ 100 in an order"
  when
    $order : Order()
    $item  : OrderItem( value > 100 ) from $order.items
  then
    // Apply discount to `$item`.
end

For large collections of objects, instead of adding an object with a large graph that the Drools engine must iterate over frequently, add the collection directly to the KIE session and then join the collection in the condition, as shown in the following example:

when
  $order : Order()
  OrderItem( value > 100, order == $order )
Example rule with from and lock-on-active rule attribute
rule "Assign people in North Carolina (NC) to sales region 1"
  ruleflow-group "test"
  lock-on-active true
  when
    $p : Person()
    $a : Address( state == "NC" ) from $p.address
  then
    modify ($p) {} // Assign the person to sales region 1.
end

rule "Apply a discount to people in the city of Raleigh"
  ruleflow-group "test"
  lock-on-active true
  when
    $p : Person()
    $a : Address( city == "Raleigh" ) from $p.address
  then
    modify ($p) {} // Apply discount to the person.
end

Using from with lock-on-active rule attribute can result in rules not being executed. You can address this issue in one of the following ways:

  • Avoid using the from element when you can insert all facts into the working memory of the Drools engine or use nested object references in your constraint expressions.

  • Place the variable used in the modify() block as the last sentence in your rule condition.

  • Avoid using the lock-on-active rule attribute when you can explicitly manage how rules within the same ruleflow group place activations on one another.

The pattern that contains a from clause cannot be followed by another pattern starting with a parenthesis. The reason for this restriction is that the DRL parser reads the from expression as "from $l (String() or Number())" and it cannot differentiate this expression from a function call. The simplest workaround to this is to wrap the from clause in parentheses, as shown in the following example:

Example rules with from used incorrectly and correctly
// Do not use `from` in this way:
rule R
  when
    $l : List()
    String() from $l
    (String() or Number())
  then
    // Actions
end

// Use `from` in this way instead:
rule R
  when
    $l : List()
    (String() from $l)
    (String() or Number())
  then
    // Actions
end
entry-point

Use this to define an entry point, or event stream, corresponding to a data source for the pattern. This element is typically used with the from condition element. You can declare an entry point for events so that the Drools engine uses data from only that entry point to evaluate the rules. You can declare an entry point either implicitly by referencing it in DRL rules or explicitly in your Java application.

Example rule with from entry-point
rule "Authorize withdrawal"
  when
    WithdrawRequest( $ai : accountId, $am : amount ) from entry-point "ATM Stream"
    CheckingAccount( accountId == $ai, balance > $am )
  then
    // Authorize withdrawal.
end

<@Edoardo, see this snippet and section.>

Example Java application code with EntryPoint object and inserted facts
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.EntryPoint;

// Create your KIE base and KIE session as usual:
KieSession session = ...

// Create a reference to the entry point:
EntryPoint atmStream = session.getEntryPoint("ATM Stream");

// Start inserting your facts into the entry point:
atmStream.insert(aWithdrawRequest);
collect

Use this to define a collection of objects that the rule can use as part of the condition. The rule obtains the collection either from a specified source or from the working memory of the Drools engine. The result pattern of the collect element can be any concrete class that implements the java.util.Collection interface and provides a default no-arg public constructor. You can use Java collections like List, LinkedList, and HashSet, or your own class. If variables are bound before the collect element in a condition, you can use the variables to constrain both your source and result patterns. However, any binding made inside the collect element is not available for use outside of it.

collect
Figure 116. Collect
Example rule with collect
import java.util.List

rule "Raise priority when system has more than three pending alarms"
  when
    $system : System()
    $alarms : List( size >= 3 )
              from collect( Alarm( system == $system, status == 'pending' ) )
  then
    // Raise priority because `$system` has three or more `$alarms` pending.
end

In this example, the rule assesses all pending alarms in the working memory of the Drools engine for each given system and groups them in a List. If three or more alarms are found for a given system, the rule is executed.

You can also use the collect element with nested from elements, as shown in the following example:

Example rule with collect and nested from
import java.util.LinkedList;

rule "Send a message to all parents"
  when
    $town : Town( name == 'Paris' )
    $mothers : LinkedList()
               from collect( Person( children > 0 )
                             from $town.getPeople()
                           )
  then
    // Send a message to all parents.
end
accumulate

Use this to iterate over a collection of objects, execute custom actions for each of the elements, and return one or more result objects (if the constraints evaluate to true). This element is a more flexible and powerful form of the collect condition element. You can use predefined functions in your accumulate conditions or implement custom functions as needed. You can also use the abbreviation acc for accumulate in rule conditions.

Use the following format to define accumulate conditions in rules:

Preferred format for accumulate
accumulate( SOURCE_PATTERN; FUNCTIONS [;CONSTRAINTS] )
accumulate
Figure 117. Accumulate
Although the Drools engine supports alternate formats for the accumulate element for backward compatibility, this format is preferred for optimal performance in rules and applications.

The Drools engine supports the following predefined accumulate functions. These functions accept any expression as input.

  • average

  • min

  • max

  • count

  • sum

  • collectList

  • collectSet

In the following example rule, min, max, and average are accumulate functions that calculate the minimum, maximum, and average temperature values over all the readings for each sensor:

Example rule with accumulate to calculate temperature values
rule "Raise alarm"
  when
    $s : Sensor()
    accumulate( Reading( sensor == $s, $temp : temperature );
                $min : min( $temp ),
                $max : max( $temp ),
                $avg : average( $temp );
                $min < 20, $avg > 70 )
  then
    // Raise the alarm.
end

The following example rule uses the average function with accumulate to calculate the average profit for all items in an order:

Example rule with accumulate to calculate average profit
rule "Average profit"
  when
    $order : Order()
    accumulate( OrderItem( order == $order, $cost : cost, $price : price );
                $avgProfit : average( 1 - $cost / $price ) )
  then
    // Average profit for `$order` is `$avgProfit`.
end

To use custom, domain-specific functions in accumulate conditions, create a Java class that implements the org.kie.api.runtime.rule.AccumulateFunction interface. For example, the following Java class defines a custom implementation of an AverageData function:

Example Java class with custom implementation of average function
// An implementation of an accumulator capable of calculating average values

public class AverageAccumulateFunction implements org.kie.api.runtime.rule.AccumulateFunction<AverageAccumulateFunction.AverageData> {

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    }

    public void writeExternal(ObjectOutput out) throws IOException {

    }

    public static class AverageData implements Externalizable {
        public int    count = 0;
        public double total = 0;

        public AverageData() {}

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            count   = in.readInt();
            total   = in.readDouble();
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt(count);
            out.writeDouble(total);
        }

    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.rule.AccumulateFunction#createContext()
     */
    public AverageData createContext() {
        return new AverageData();
    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.rule.AccumulateFunction#init(java.io.Serializable)
     */
    public void init(AverageData context) {
        context.count = 0;
        context.total = 0;
    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.rule.AccumulateFunction#accumulate(java.io.Serializable, java.lang.Object)
     */
    public void accumulate(AverageData context,
                           Object value) {
        context.count++;
        context.total += ((Number) value).doubleValue();
    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.rule.AccumulateFunction#reverse(java.io.Serializable, java.lang.Object)
     */
    public void reverse(AverageData context, Object value) {
        context.count--;
        context.total -= ((Number) value).doubleValue();
    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.rule.AccumulateFunction#getResult(java.io.Serializable)
     */
    public Object getResult(AverageData context) {
        return new Double( context.count == 0 ? 0 : context.total / context.count );
    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.rule.AccumulateFunction#supportsReverse()
     */
    public boolean supportsReverse() {
        return true;
    }

    /* (non-Javadoc)
     * @see org.kie.api.runtime.rule.AccumulateFunction#getResultType()
     */
    public Class< ? > getResultType() {
        return Number.class;
    }

}

To use the custom function in a DRL rule, import the function using the import accumulate statement:

Format to import a custom function
import accumulate CLASS_NAME FUNCTION_NAME
Example rule with the imported average function
import accumulate AverageAccumulateFunction.AverageData average

rule "Average profit"
  when
    $order : Order()
    accumulate( OrderItem( order == $order, $cost : cost, $price : price );
                $avgProfit : average( 1 - $cost / $price ) )
  then
    // Average profit for `$order` is `$avgProfit`.
end

For backward compatibility, the Drools engine also supports the configuration of accumulate functions through configuration files and system properties, but this is a deprecated method. To configure the average function from the previous example using the configuration file or system property, set a property as shown in the following example:

drools.accumulate.function.average = AverageAccumulateFunction.AverageData

Note that drools.accumulate.function is a required prefix, average is how the function is used in the DRL files, and AverageAccumulateFunction.AverageData is the fully qualified name of the class that implements the function behavior.

accumulate alternate syntax for a single function with return type

The accumulate syntax evolved over time with the goal of becoming more compact and expressive. Nevertheless, Kogito still supports previous syntaxes for backward compatibility purposes.

In case the rule is using a single accumulate function on a given accumulate, the author may add a pattern for the result object and use the "from" keyword to link it to the accumulate result.

Example: a rule to apply a 10% discount on orders over $100 could be written in the following way:

rule "Apply 10% discount to orders over US$ 100,00"
when
    $order : Order()
    $total : Number( doubleValue > 100 )
             from accumulate( OrderItem( order == $order, $value : value ),
                              sum( $value ) )
then
    // apply discount to $order
end

In the above example, the accumulate element is using only one function (sum), and so, the rules author opted to explicitly write a pattern for the result type of the accumulate function (Number) and write the constraints inside it. There are no problems in using this syntax over the compact syntax presented before, except that is is a bit more verbose. Also note that it is not allowed to use both the return type and the functions binding in the same accumulate statement.

Compile-time checks are performed in order to ensure the pattern used with the "from" keyword is assignable from the result of the accumulate function used.

With this syntax, the "from" binds to the single result returned by the accumulate function, and it does not iterate.

In the above example, "$total" is bound to the result returned by the accumulate sum() function.

As another example however, if the result of the accumulate function is a collection, "from" still binds to the single result and it does not iterate:

rule "Person names"
when
  $x : Object() from accumulate(MyPerson( $val : name );
                                collectList( $val ) )
then
  // $x is a List
end

The bound "$x : Object()" is the List itself, returned by the collectList accumulate function used.

This is an important distinction to highlight, as the "from" keyword can also be used separately of accumulate, to iterate over the elements of a collection:

rule "Iterate the numbers"
when
    $xs : List()
    $x : Integer() from $xs
then
  // $x matches and binds to each Integer in the collection
end

While this syntax is still supported for backward compatibility purposes, for this and other reasons we encourage rule authors to make use instead of the preferred accumulate syntax (described previously), to avoid any potential pitfalls.

accumulate with inline custom code

Another possible syntax for the accumulate is to define inline custom code, instead of using accumulate functions.

The use of accumulate with inline custom code is not a good practice for several reasons, including difficulties on maintaining and testing rules that use them, as well as the inability of reusing that code. Implementing your own accumulate functions is very simple and straightforward, they are easy to unit test and to use. This form of accumulate is supported for backward compatibility only.

Only limited support for inline accumulate is provided while using the executable model. For example, you cannot use an external binding in the code while using the MVEL dialect:

rule R
dialect "mvel"
when
    String( $l : length )
    $sum : Integer() from accumulate (
                           Person( age > 18, $age : age ),
                           init( int sum = 0 * $l; ),
                           action( sum += $age; ),
                           reverse( sum -= $age; ),
                           result( sum )
                     )

The general syntax of the accumulate CE with inline custom code is:

RESULT_PATTERN from accumulate( SOURCE_PATTERN,
                                  init( INIT_CODE ),
                                  action( ACTION_CODE ),
                                  reverse( REVERSE_CODE ),
                                  result( RESULT_EXPRESSION ) )

The meaning of each of the elements is the following:

  • SOURCE_PATTERN: the source pattern is a regular pattern that the Drools engine will try to match against each of the source objects.

  • INIT_CODE: this is a semantic block of code in the selected dialect that will be executed once for each tuple, before iterating over the source objects.

  • ACTION_CODE: this is a semantic block of code in the selected dialect that will be executed for each of the source objects.

  • REVERSE_CODE: this is an optional semantic block of code in the selected dialect that if present will be executed for each source object that no longer matches the source pattern. The objective of this code block is to undo any calculation done in the ACTION_CODE block, so that the Drools engine can do decremental calculation when a source object is modified or deleted, hugely improving performance of these operations.

  • RESULT_EXPRESSION: this is a semantic expression in the selected dialect that is executed after all source objects are iterated.

  • RESULT_PATTERN: this is a regular pattern that the Drools engine tries to match against the object returned from the RESULT_EXPRESSION. If it matches, the accumulate conditional element evaluates to true and the Drools engine proceeds with the evaluation of the next CE in the rule. If it does not matches, the accumulate CE evaluates to false and the Drools engine stops evaluating CEs for that rule.

It is easier to understand if we look at an example:

rule "Apply 10% discount to orders over US$ 100,00"
when
    $order : Order()
    $total : Number( doubleValue > 100 )
             from accumulate( OrderItem( order == $order, $value : value ),
                              init( double total = 0; ),
                              action( total += $value; ),
                              reverse( total -= $value; ),
                              result( total ) )
then
    // apply discount to $order
end

In the above example, for each Order in the Working Memory, the Drools engine will execute the INIT_CODE initializing the total variable to zero. Then it will iterate over all OrderItem objects for that order, executing the action for each one (in the example, it will sum the value of all items into the total variable). After iterating over all OrderItem objects, it will return the value corresponding to the result expression (in the above example, the value of variable total). Finally, the Drools engine will try to match the result with the Number pattern, and if the double value is greater than 100, the rule will fire.

The example used Java as the semantic dialect, and as such, note that the usage of the semicolon as statement delimiter is mandatory in the init, action and reverse code blocks. The result is an expression and, as such, it does not admit ';'. If the user uses any other dialect, he must comply to that dialect’s specific syntax.

As mentioned before, the REVERSE_CODE is optional, but it is strongly recommended that the user writes it in order to benefit from the improved performance on update and delete.

The accumulate CE can be used to execute any action on source objects. The following example instantiates and populates a custom object:

rule "Accumulate using custom objects"
when
    $person   : Person( $likes : likes )
    $cheesery : Cheesery( totalAmount > 100 )
                from accumulate( $cheese : Cheese( type == $likes ),
                                 init( Cheesery cheesery = new Cheesery(); ),
                                 action( cheesery.addCheese( $cheese ); ),
                                 reverse( cheesery.removeCheese( $cheese ); ),
                                 result( cheesery ) );
then
    // do something
end
eval

The conditional element eval is essentially a catch-all which allows any semantic code (that returns a primitive boolean) to be executed. This code can refer to variables that were bound in the conditions of the rule and functions in the rule package. Overuse of eval reduces the declarativeness of your rules and can result in a poorly performing Drools engine. While eval can be used anywhere in the patterns, it is typically added as the last conditional element in the conditions of a rule.

eval
Figure 118. Eval

Instances of eval cannot be indexed and thus are not as efficient as Field Constraints. However this makes them ideal for being used when functions return values that change over time, which is not allowed within Field Constraints.

For those who are familiar with Kogito 2.x lineage, the old Kogito parameter and condition tags are equivalent to binding a variable to an appropriate type, and then using it in an eval node.

p1 : Parameter()
p2 : Parameter()
eval( p1.getList().containsKey( p2.getItem() ) )

p1 : Parameter()
p2 : Parameter()
// call function isValid in the LHS
eval( isValid( p1, p2 ) )

6.1.11. Rule actions in DRL

The then part of the rule (also known as the Right Hand Side (RHS) of the rule) contains the actions to be performed when the conditional part of the rule has been met. Actions consist of one or more methods that execute consequences based on the rule conditions and on available data objects in the package. For example, if a bank requires loan applicants to have over 21 years of age (with a rule condition Applicant( age < 21 )) and a loan applicant is under 21 years old, the then action of an "Underage" rule would be setApproved( false ), declining the loan because the applicant is under age.

The main purpose of rule actions is to to insert, delete, or modify data in the working memory of the Drools engine. Effective rule actions are small, declarative, and readable. If you need to use imperative or conditional code in rule actions, then divide the rule into multiple smaller and more declarative rules.

Example rule for loan application age limit
rule "Underage"
  when
    application : LoanApplication()
    Applicant( age < 21 )
  then
    application.setApproved( false );
    application.setExplanation( "Underage" );
end
6.1.11.1. Supported rule action methods in DRL

DRL supports the following rule action methods that you can use in DRL rule actions. You can use these methods to modify the working memory of the Drools engine without having to first reference a working memory instance. These methods act as shortcuts to the methods provided by the KnowledgeHelper class in your Kogito distribution.

<@Edoardo, KnowledgeHelper still accurate, and what should the new link below be?>

For all rule action methods,

see the Kogito KnowledgeHelper.java page in GitHub.

set

Use this to set the value of a field.

setFIELD ( VALUE )
Example rule action to set the values of a loan application approval
$application.setApproved ( false );
$application.setExplanation( "has been bankrupt" );
modify

Use this to specify fields to be modified for a fact and to notify the Drools engine of the change. This method provides a structured approach to fact updates. It combines the update operation with setter calls to change object fields.

modify ( FACT_EXPRESSION ) {
    EXPRESSION,
    EXPRESSION,
    ...
}
Example rule action to modify a loan application amount and approval
modify( LoanApplication ) {
        setAmount( 100 ),
        setApproved ( true )
}
update

Use this to specify fields and the entire related fact to be updated and to notify the Drools engine of the change. After a fact has changed, you must call update before changing another fact that might be affected by the updated values. To avoid this added step, use the modify method instead.

update ( __OBJECT__, __HANDLE__ )  // Informs the Drools engine that an object has changed

update ( __OBJECT__ )  // Causes the Drools engine to search for a fact handle of the object
Example rule action to update a loan application amount and approval
LoanApplication.setAmount( 100 );
update( LoanApplication );
If you provide property-change listeners, you do not need to call this method when an object changes. For more information about property-change listeners, see Property-change settings and listeners for fact types.
insert

Use this to insert a new fact into the working memory of the Drools engine and to define resulting fields and values as needed for the fact.

insert( new __OBJECT__ );
Example rule action to insert a new loan applicant object
insert( new Applicant() );
insertLogical

Use this to insert a new fact logically into the Drools engine. The Drools engine is responsible for logical decisions on insertions and retractions of facts. After regular or stated insertions, facts must be retracted explicitly. After logical insertions, the facts that were inserted are automatically retracted when the conditions in the rules that inserted the facts are no longer true.

insertLogical( new OBJECT );
Example rule action to logically insert a new loan applicant object
insertLogical( new Applicant() );
delete

Use this to remove an object from the Drools engine. The keyword retract is also supported in DRL and executes the same action, but delete is typically preferred in DRL code for consistency with the keyword insert.

delete( OBJECT );
Example rule action to delete a loan applicant object
delete( Applicant );
6.1.11.2. Other rule action methods from drools and kcontext variables

<@Edoardo, see this section.>

In addition to the standard rule action methods, the Drools engine supports methods in conjunction with the predefined drools and kcontext variables that you can also use in rule actions.

You can use the drools variable to call methods from the KnowledgeHelper class in your Kogito distribution, which is also the class that the standard rule action methods are based on. For all drools rule action options,

see the Kogito KnowledgeHelper.java page in GitHub.

The following examples are common methods that you can use with the drools variable:

  • drools.halt(): Terminates rule execution if a user or application has previously called fireUntilHalt(). When a user or application calls fireUntilHalt(), the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly calls halt(). Otherwise, by default, the Drools engine runs in passive mode and evaluates rules only when a user or an application explicitly calls fireAllRules().

  • drools.getWorkingMemory(): Returns the WorkingMemory object.

  • drools.setFocus( "AGENDA_GROUP" ): Sets the focus to a specified agenda group to which the rule belongs.

  • drools.getRule().getName(): Returns the name of the rule.

  • drools.getTuple(), drools.getActivation(): Returns the Tuple that matches the currently executing rule and then delivers the corresponding Activation. These calls are useful for logging and debugging purposes.

You can use the kcontext variable with the getKieRuntime() method to call other methods from the KieContext class and, by extension, the RuleContext class in your Kogito distribution. The full Knowledge Runtime API is exposed through the kcontext variable and provides extensive rule action methods. For all kcontext rule action options,

see the Kogito RuleContext.java page in GitHub.

The following examples are common methods that you can use with the kcontext.getKieRuntime() variable-method combination:

  • kcontext.getKieRuntime().halt(): Terminates rule execution if a user or application has previously called fireUntilHalt(). This method is equivalent to the drools.halt() method. When a user or application calls fireUntilHalt(), the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly calls halt(). Otherwise, by default, the Drools engine runs in passive mode and evaluates rules only when a user or an application explicitly calls fireAllRules().

  • kcontext.getKieRuntime().getAgenda(): Returns a reference to the KIE session Agenda, and in turn provides access to rule activation groups, rule agenda groups, and ruleflow groups.

    Example call to access agenda group "CleanUp" and set the focus
    kcontext.getKieRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();

    This example is equivalent to drools.setFocus( "CleanUp" ).

  • kcontext.getKieRuntime().getQueryResults(<string> query): Runs a query and returns the results. This method is equivalent to drools.getKieRuntime().getQueryResults().

  • kcontext.getKieRuntime().getKieBase(): Returns the KieBase object. The KIE base is the source of all the knowledge in your rule system and the originator of the current KIE session.

  • kcontext.getKieRuntime().setGlobal(), ~.getGlobal(), ~.getGlobals(): Sets or retrieves global variables.

  • kcontext.getKieRuntime().getEnvironment(): Returns the runtime Environment, similar to your operating system environment.

6.1.11.3. Advanced rule actions with conditional and named consequences

In general, effective rule actions are small, declarative, and readable. However, in some cases, the limitation of having a single consequence for each rule can be challenging and lead to verbose and repetitive rule syntax, as shown in the following example rules:

Example rules with verbose and repetitive syntax
rule "Give 10% discount to customers older than 60"
  when
    $customer : Customer( age > 60 )
  then
    modify($customer) { setDiscount( 0.1 ) };
end

rule "Give free parking to customers older than 60"
  when
    $customer : Customer( age > 60 )
    $car : Car( owner == $customer )
  then
    modify($car) { setFreeParking( true ) };
end

A partial solution to the repetition is to make the second rule extend the first rule, as shown in the following modified example:

Partially enhanced example rules with an extended condition
rule "Give 10% discount to customers older than 60"
  when
    $customer : Customer( age > 60 )
  then
    modify($customer) { setDiscount( 0.1 ) };
end

rule "Give free parking to customers older than 60"
    extends "Give 10% discount to customers older than 60"
  when
    $car : Car( owner == $customer )
  then
    modify($car) { setFreeParking( true ) };
end

As a more efficient alternative, you can consolidate the two rules into a single rule with modified conditions and labelled corresponding rule actions, as shown in the following consolidated example:

Consolidated example rule with conditional and named consequences
rule "Give 10% discount and free parking to customers older than 60"
  when
    $customer : Customer( age > 60 )
    do[giveDiscount]
    $car : Car( owner == $customer )
  then
    modify($car) { setFreeParking( true ) };
  then[giveDiscount]
    modify($customer) { setDiscount( 0.1 ) };
end

This example rule uses two actions: the usual default action and another action named giveDiscount. The giveDiscount action is activated in the condition with the keyword do when a customer older than 60 years old is found in the KIE base, regardless of whether or not the customer owns a car.

You can configure the activation of a named consequence with an additional condition, such as the if statement in the following example. The condition in the if statement is always evaluated on the pattern that immediately precedes it.

Consolidated example rule with an additional condition
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them"
  when
    $customer : Customer( age > 60 )
    if ( type == "Golden" ) do[giveDiscount]
    $car : Car( owner == $customer )
  then
    modify($car) { setFreeParking( true ) };
  then[giveDiscount]
    modify($customer) { setDiscount( 0.1 ) };
end

You can also evaluate different rule conditions using a nested if and else if construct, as shown in the following more complex example:

Consolidated example rule with more complex conditions
rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones"
  when
    $customer : Customer( age > 60 )
    if ( type == "Golden" ) do[giveDiscount10]
    else if ( type == "Silver" ) break[giveDiscount5]
    $car : Car( owner == $customer )
  then
    modify($car) { setFreeParking( true ) };
  then[giveDiscount10]
    modify($customer) { setDiscount( 0.1 ) };
  then[giveDiscount5]
    modify($customer) { setDiscount( 0.05 ) };
end

This example rule gives a 10% discount and free parking to Golden customers over 60, but only a 5% discount without free parking to Silver customers. The rule activates the consequence named giveDiscount5 with the keyword break instead of do. The keyword do schedules a consequence in the Drools engine agenda, enabling the remaining part of the rule conditions to continue being evaluated, while break blocks any further condition evaluation. If a named consequence does not correspond to any condition with do but is activated with break, the rule fails to compile because the conditional part of the rule is never reached.

6.1.12. Comments in DRL files

DRL supports single-line comments prefixed with a double forward slash // and multi-line comments enclosed with a forward slash and asterisk /* …​ */. You can use DRL comments to annotate rules or any related components in DRL files. DRL comments are ignored by the Drools engine when the DRL file is processed.

Example rule with comments
rule "Underage"
  // This is a single-line comment.
  when
    $application : LoanApplication()  // This is an in-line comment.
    Applicant( age < 21 )
  then
    /* This is a multi-line comment
    in the rule actions. */
    $application.setApproved( false );
    $application.setExplanation( "Underage" );
end
multi line comment
Figure 119. Multi-line comment
The hash symbol # is not supported for DRL comments.

6.1.13. Error messages for DRL troubleshooting

Kogito provides standardized messages for DRL errors to help you troubleshoot and resolve problems in your DRL files. The error messages use the following format:

error message
Figure 120. Error message format for DRL file problems
  • 1st Block: Error code

  • 2nd Block: Line and column in the DRL source where the error occurred

  • 3rd Block: Description of the problem

  • 4th Block: Component in the DRL source (rule, function, query) where the error occurred

  • 5th Block: Pattern in the DRL source where the error occurred (if applicable)

Kogito supports the following standardized error messages:

101: no viable alternative

Indicates that the parser reached a decision point but could not identify an alternative.

Example rule with incorrect spelling
1: rule "simple rule"
2:   when
3:     exists Person()
4:     exits Student()  // Must be `exists`
5:   then
6: end
Error message
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule "simple rule"
Example rule without a rule name
1: package org.drools.examples;
2: rule    // Must be `rule "rule name"` (or `rule rule_name` if no spacing)
3:   when
4:     Object()
5:   then
6:     System.out.println("A RHS");
7: end
Error message
[ERR 101] Line 3:2 no viable alternative at input 'when'

In this example, the parser encountered the keyword when but expected the rule name, so it flags when as the incorrect expected token.

Example rule with incorrect syntax
1: rule "simple rule"
2:   when
3:     Student( name == "Andy )  // Must be `"Andy"`
4:   then
5: end
Error message
[ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule "simple rule" in pattern Student
A line and column value of 0:-1 means the parser reached the end of the source file (<eof>) but encountered incomplete constructs, usually due to missing quotation marks "…​", apostrophes '…​', or parentheses (…​).
102: mismatched input

Indicates that the parser expected a particular symbol that is missing at the current input position.

Example rule with an incomplete rule statement
1: rule simple_rule
2:   when
3:     $p : Person(
        // Must be a complete rule statement
Error message
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule "simple rule" in pattern Person
A line and column value of 0:-1 means the parser reached the end of the source file (<eof>) but encountered incomplete constructs, usually due to missing quotation marks "…​", apostrophes '…​', or parentheses (…​).
Example rule with incorrect syntax
1: package org.drools.examples;
2:
3: rule "Wrong syntax"
4:   when
5:     not( Car( ( type == "tesla", price == 10000 ) || ( type == "kia", price == 1000 ) ) from $carList )
       // Must use `&&` operators instead of commas `,`
6:   then
7:     System.out.println("OK");
8: end
Error messages
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule "Wrong syntax" in pattern Car
[ERR 101] Line 5:57 no viable alternative at input 'type' in rule "Wrong syntax"
[ERR 102] Line 5:106 mismatched input ')' expecting 'then' in rule "Wrong syntax"

In this example, the syntactic problem results in multiple error messages related to each other. The single solution of replacing the commas , with && operators resolves all errors. If you encounter multiple errors, resolve one at a time in case errors are consequences of previous errors.

103: failed predicate

Indicates that a validating semantic predicate evaluated to false. These semantic predicates are typically used to identify component keywords in DRL files, such as declare, rule, exists, not, and others.

Example rule with an invalid keyword
 1: package nesting;
 2:
 3: import org.drools.compiler.Person
 4: import org.drools.compiler.Address
 5:
 6: Some text  // Must be a valid DRL keyword
 7:
 8: rule "test something"
 9:   when
10:     $p: Person( name=="Michael" )
11:   then
12:     $p.name = "other";
13:     System.out.println(p.name);
14: end
Error message
[ERR 103] Line 6:0 rule 'rule_key' failed predicate: {(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rule

The Some text line is invalid because it does not begin with or is not a part of a DRL keyword construct, so the parser fails to validate the rest of the DRL file.

This error is similar to 102: mismatched input, but usually involves DRL keywords.
104: trailing semi-colon not allowed

Indicates that an eval() clause in a rule condition uses a semicolon ; but must not use one.

Example rule with eval() and trailing semicolon
1: rule "simple rule"
2:   when
3:     eval( abc(); )  // Must not use semicolon `;`
4:   then
5: end
Error message
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule "simple rule"
105: did not match anything

Indicates that the parser reached a sub-rule in the grammar that must match an alternative at least once, but the sub-rule did not match anything. The parser has entered a branch with no way out.

Example rule with invalid text in an empty condition
1: rule "empty condition"
2:   when
3:     None  // Must remove `None` if condition is empty
4:   then
5:      insert( new Person() );
6: end
Error message
[ERR 105] Line 2:2 required (...)+ loop did not match anything at input 'WHEN' in rule "empty condition"

In this example, the condition is intended to be empty but the word None is used. This error is resolved by removing None, which is not a valid DRL keyword, data type, or pattern construct.

6.2. Creating DRL rules for your Kogito project

You can create and manage DRL rules for your Kogito project in your IDE (VSCode is recommended). In each DRL rule file, you define rule conditions, actions, and other components related to the rule, based on the data objects you create or import in the package.

Prerequisite
Procedure
  1. In your VSCode IDE, create or import a DRL file in the relevant folder of your Kogito project, typically in src/main/resources.

  2. Define the DRL file with any of the following components.

    At a minimum, each DRL file must specify the package, import, and rule components. All other components are optional.

    Components in a DRL file
    package
    unit  // Recommended
    
    import
    
    function  // Optional
    
    query  // Optional
    
    declare   // Optional
    
    global   // Optional
    
    rule "rule name"
        // Attributes
        when
            // Conditions
        then
            // Actions
    end
    
    rule "rule2 name"
    
    ...
    • package: Use this to specify the location of the Java data objects in your project, typically the sub-directory path in src/main/java.

      Specifying a package
      package org.mortgages
    • unit: Use this to associate this rule file with a rule unit. Rule units are groups of data sources, global variables, and DRL rules that function together for a specific purpose. Rule units are an enhanced alternative to rule-grouping DRL attributes such as rule flow groups, rule agenda groups, or activation groups for execution control. When you build the project, the rule unit is generated and associated with the DRL file.

      Declaring a rule unit
      unit MortgageRules
    • import: Use this to identify specific data objects from the package that you want to use in the DRL file. Specify the package and data object in the format packageName.objectName, with multiple imports on separate lines.

      Importing data objects
      import org.mortgages.LoanApplication;
    • function: (optional) Use this to include a function to be used by rules in the DRL file. Functions in DRL files put semantic code in your rule source file instead of in Java classes. Functions are especially useful if an action (then) part of a rule is used repeatedly and only the parameters differ for each rule. Above the rules in the DRL file, you can declare the function or import a static method from a helper class as a function, and then use the function by name in an action (then) part of the rule.

      Declaring and using a function with a rule (option 1)
      function String hello(String applicantName) {
          return "Hello " + applicantName + "!";
      }
      
      rule "Using a function"
        when
          // Empty
        then
          System.out.println( hello( "James" ) );
      end
      Importing and using the function with a rule (option 2)
      import function my.package.applicant.hello;
      
      rule "Using a function"
        when
          // Empty
        then
          System.out.println( hello( "James" ) );
      end

      <@Edoardo, see query below>

    • query: (optional) Use this to search the Drools engine for facts related to the rules in the DRL file. You add the query definitions in DRL files and then obtain the matching results in your application code. Queries search for a set of defined conditions and do not require when or then specifications. Query names are global to the KIE base and therefore must be unique among all other rule queries in the project. To return the results of a query, construct a traditional QueryResults definition using ksession.getQueryResults("name"), where "name" is the query name. This returns a list of query results, which enable you to retrieve the objects that matched the query. Define the query and query results parameters above the rules in the DRL file.

      Example query definition in a DRL file
      query "people under the age of 21"
          $person : Person( age < 21 )
      end
      Example application code to obtain query results
      QueryResults results = ksession.getQueryResults( "people under the age of 21" );
      System.out.println( "we have " + results.size() + " people under the age  of 21" );
    • declare: (optional) Use this to declare a new fact type to be used by rules in the DRL file. The default fact type in the java.lang package of Kogito is Object, but you can declare other types in DRL files as needed. Declaring fact types in DRL files enables you to define a new fact model directly in the Drools engine, without creating models in a lower-level language like Java.

      Declaring and using a new fact type
      declare Person
        name : String
        dateOfBirth : java.util.Date
        address : Address
      end
      
      rule "Using a declared type"
        when
          $p : Person( name == "James" )
        then   // Insert Mark, who is a customer of James.
          Person mark = new Person();
          mark.setName( "Mark" );
          insert( mark );
      end

      <@Edoardo, see global below>

    • global: (optional) Use this to include a global variable to be used by rules in the DRL file. Global variables typically provide data or services for the rules, such as application services used in rule consequences, and return data from rules, such as logs or values added in rule consequences. Set the global value in the working memory of the Drools engine through a KIE session configuration or REST operation, declare the global variable above the rules in the DRL file, and then use it in an action (then) part of the rule. For multiple global variables, use separate lines in the DRL file.

      Setting the global list configuration for the Drools engine
      List<String> list = new ArrayList<>();
      KieSession kieSession = kiebase.newKieSession();
      kieSession.setGlobal( "myGlobalList", list );
      Defining the global list in a rule
      global java.util.List myGlobalList;
      
      rule "Using a global"
        when
          // Empty
        then
          myGlobalList.add( "My global list" );
      end

      Do not use global variables to establish conditions in rules unless a global variable has a constant immutable value. Global variables are not inserted into the working memory of the Drools engine, so the Drools engine cannot track value changes of variables.

      Do not use global variables to share data between rules. Rules always reason and react to the working memory state, so if you want to pass data from rule to rule, assert the data as facts into the working memory of the Drools engine.

    • rule: Use this to define each rule in the DRL file. Rules consist of a rule name in the format rule "name", followed by optional attributes that define rule behavior (such as salience or no-loop), followed by when and then definitions. Each rule must have a unique name within the rule package. The when part of the rule contains the conditions that must be met to execute an action. For example, if a bank requires loan applicants to have over 21 years of age, then the when condition for an "Underage" rule would be Applicant( age < 21 ). The then part of the rule contains the actions to be performed when the conditional part of the rule has been met. For example, when the loan applicant is under 21 years old, the then action would be setApproved( false ), declining the loan because the applicant is under age.

      Rule for loan application age limit
      rule "Underage"
        salience 15
        when
          $application : LoanApplication()
          Applicant( age < 21 )
        then
          $application.setApproved( false );
          $application.setExplanation( "Underage" );
      end

      The following example is a DRL file in a loan application decision service:

      Example DRL file for a loan application
      package org.mortgages;
      unit MortgageRules
      
      import org.mortgages.LoanApplication;
      import org.mortgages.Bankruptcy;
      import org.mortgages.Applicant;
      
      rule "Bankruptcy history"
      	salience 10
      	when
      		$a : LoanApplication()
      		exists (Bankruptcy( yearOfOccurrence > 1990 || amountOwed > 10000 ))
      	then
      		$a.setApproved( false );
      		$a.setExplanation( "has been bankrupt" );
      		delete( $a );
      end
      
      rule "Underage"
      	salience 15
      	when
      		$application : LoanApplication()
      		Applicant( age < 21 )
      	then
      		$application.setApproved( false );
      		$application.setExplanation( "Underage" );
      		delete( $application );
      end
  3. After you define all components of the rule, save the file in .drl format.

6.2.1. Adding conditions in DRL rules

The when part of a DRL rule contains the conditions that must be met to execute an action. For example, if a bank requires loan applicants to have over 21 years of age, then the when condition of an "Underage" rule would be Applicant( age < 21 ). Conditions consist of a series of stated patterns and constraints, with optional bindings and other supported DRL elements, based on the available data objects in the package.

Prerequisites
  • The package location and the import list of data objects required in the rule are defined at the top of the DRL file.

  • The rule name is defined in the format rule "name" below the package, import, and other lines that apply to the entire DRL file. The same rule name cannot be used more than once in the same package. Optional rule attributes (such as salience or no-loop) that define rule behavior are below the rule name, before the when section.

Procedure
  1. In the DRL file, enter when within the rule to begin adding condition statements. The when section consists of zero or more fact patterns that define conditions for the rule.

    <@Edoardo, see following paragraph about empty when and fireAllRules>

    If the when section is empty, then the conditions are considered to be true and the actions in the then section are executed the first time a fireAllRules() call is made in the Drools engine. This is useful if you want to use rules to set up the Drools engine state.

    Example rule without conditions
    rule "Always insert applicant"
      when
        // Empty
      then   // Actions to be executed once
        insert( new Applicant() );
    end
    
    // The rule is internally rewritten in the following way:
    
    rule "Always insert applicant"
      when
        eval( true )
      then
        insert( new Applicant() );
    end
  2. Enter a pattern for the first condition to be met, with optional constraints, bindings, and other supported DRL elements. A basic pattern format is <patternBinding> : <patternType> ( <constraints> ). Patterns are based on the available data objects in the package and define the conditions to be met in order to trigger actions in the then section.

    • Simple pattern: A simple pattern with no constraints matches against a fact of the given type. For example, the following condition is only that the applicant exists.

      when
        Applicant()
    • Pattern with constraints: A pattern with constraints matches against a fact of the given type and the additional restrictions in parentheses that are true or false. For example, the following condition is that the applicant is under the age of 21.

      when
        Applicant( age < 21 )
    • Pattern with binding: A binding on a pattern is a shorthand reference that other components of the rule can use to refer back to the defined pattern. For example, the following binding a on LoanApplication is used in a related action for underage applicants.

      when
        $a : LoanApplication()
        Applicant( age < 21 )
      then
        $a.setApproved( false );
        $a.setExplanation( "Underage" )
  3. Continue defining all condition patterns that apply to this rule. The following are some of the keyword options for defining DRL conditions:

    • and: Use this to group conditional components into a logical conjunction. Infix and prefix and are supported. By default, all listed patterns are combined with and when no conjunction is specified.

      // All of the following examples are interpreted the same way:
      $a : LoanApplication() and Applicant( age < 21 )
      
      $a : LoanApplication()
      and Applicant( age < 21 )
      
      $a : LoanApplication()
      Applicant( age < 21 )
      
      (and $a : LoanApplication() Applicant( age < 21 ))
    • or: Use this to group conditional components into a logical disjunction. Infix and prefix or are supported.

      // All of the following examples are interpreted the same way:
      Bankruptcy( amountOwed == 100000 ) or IncomeSource( amount == 20000 )
      
      Bankruptcy( amountOwed == 100000 )
      or IncomeSource( amount == 20000 )
      
      (or Bankruptcy( amountOwed == 100000 ) IncomeSource( amount == 20000 ))
    • exists: Use this to specify facts and constraints that must exist. This option is triggered on only the first match, not subsequent matches. If you use this element with multiple patterns, enclose the patterns with parentheses ().

      exists ( Bankruptcy( yearOfOccurrence > 1990 || amountOwed > 10000 ) )
    • not: Use this to specify facts and constraints that must not exist.

      not ( Applicant( age < 21 ) )
    • forall: Use this to verify whether all facts that match the first pattern match all the remaining patterns. When a forall construct is satisfied, the rule evaluates to true.

      forall( $app : Applicant( age < 21 )
                    Applicant( this == $app, status = 'underage' ) )
    • from: Use this to specify a data source for a pattern.

      Applicant( ApplicantAddress : address )
      Address( zipcode == "23920W" ) from ApplicantAddress
    • entry-point: Use this to define an Entry Point corresponding to a data source for the pattern. Typically used with from.

      Applicant() from entry-point "LoanApplication"
    • collect: Use this to define a collection of objects that the rule can use as part of the condition. In the example, all pending applications in the Drools engine for each given mortgage are grouped in a List. If three or more pending applications are found, the rule is executed.

      $m : Mortgage()
      $a : List( size >= 3 )
          from collect( LoanApplication( Mortgage == $m, status == 'pending' ) )
    • accumulate: Use this to iterate over a collection of objects, execute custom actions for each of the elements, and return one or more result objects (if the constraints evaluate to true). This option is a more flexible and powerful form of collect. Use the format accumulate( <source pattern>; <functions> [;<constraints>] ). In the example, min, max, and average are accumulate functions that calculate the minimum, maximum, and average temperature values over all the readings for each sensor. Other supported functions include count, sum, variance, standardDeviation, collectList, and collectSet.

      $s : Sensor()
      accumulate( Reading( sensor == $s, $temp : temperature );
                  $min : min( $temp ),
                  $max : max( $temp ),
                  $avg : average( $temp );
                  $min < 20, $avg > 70 )
    For more information about DRL rule conditions, see Rule conditions in DRL.
  4. After you define all condition components of the rule, save the DRL file.

6.2.2. Adding actions in DRL rules

The then part of the rule contains the actions to be performed when the conditional part of the rule has been met. For example, when a loan applicant is under 21 years old, the then action of an "Underage" rule would be setApproved( false ), declining the loan because the applicant is under age. Actions consist of one or more methods that execute consequences based on the rule conditions and on available data objects in the package. The main purpose of rule actions is to to insert, delete, or modify data in the working memory of the Drools engine.

Prerequisites
  • The package location and the import list of data objects required in the rule are defined at the top of the DRL file.

  • The rule name is defined in the format rule "name" below the package, import, and other lines that apply to the entire DRL file. The same rule name cannot be used more than once in the same package. Optional rule attributes (such as salience or no-loop) that define rule behavior are below the rule name, before the when section.

Procedure
  1. In the DRL file, enter then after the when section of the rule to begin adding action statements.

  2. Enter one or more actions to be executed on fact patterns based on the conditions for the rule.

    The following are some of the keyword options for defining DRL actions:

    • set: Use this to set the value of a field.

      $application.setApproved ( false );
      $application.setExplanation( "has been bankrupt" );
    • modify: Use this to specify fields to be modified for a fact and to notify the Drools engine of the change. This method provides a structured approach to fact updates. It combines the update operation with setter calls to change object fields.

      modify( LoanApplication ) {
              setAmount( 100 ),
              setApproved ( true )
      }
    • update: Use this to specify fields and the entire related fact to be updated and to notify the Drools engine of the change. After a fact has changed, you must call update before changing another fact that might be affected by the updated values. To avoid this added step, use the modify method instead.

      LoanApplication.setAmount( 100 );
      update( LoanApplication );
    • insert: Use this to insert a new fact into the Drools engine.

      insert( new Applicant() );
    • insertLogical: Use this to insert a new fact logically into the Drools engine. The Drools engine is responsible for logical decisions on insertions and retractions of facts. After regular or stated insertions, facts must be retracted explicitly. After logical insertions, the facts that were inserted are automatically retracted when the conditions in the rules that inserted the facts are no longer true.

      insertLogical( new Applicant() );
    • delete: Use this to remove an object from the Drools engine. The keyword retract is also supported in DRL and executes the same action, but delete is typically preferred in DRL code for consistency with the keyword insert.

      delete( Applicant );
    For more information about DRL rule actions, see Rule actions in DRL.
  3. After you define all action components of the rule, save the DRL file.

6.3. Kogito service execution

After you design your Kogito service, you can build and run your application and then send REST API requests to the application to execute your services. The exact REST API requests that you can use depend on how you set up the application.

For example, consider a Kogito service that is set up to generate a /persons REST API endpoint and determines whether a given customer is an adult or is underage. In this example, you can send the following POST request using a REST client or curl utility to add an adult and execute the service:

Example POST request body to add an adult
{
  "person": {
    "name": "John Quark",
    "age": 20
  }
}
Example curl command to add an adult
curl -X POST http://localhost:8080/persons -H 'content-type: application/json' -H 'accept: application/json' -d '{"person": {"name":"John Quark", "age": 20}}'
Example response
{
  "id": "b7d993ac-272d-43c7-8245-d4df96d9ab6c",
  "person": {
    "name": "John Quark",
    "age": 20,
    "adult": true
  }
}

For information about creating, running, and testing an example application with Kogito services, see Creating and running your first Kogito services.

For information about deploying your Kogito service to OpenShift, see Deploying Kogito services on OpenShift.

7. Using spreadsheet decision tables in Kogito services

As a business rules developer, you can define business rules in a tabular format in spreadsheet decision tables and then include the spreadsheet file in your Kogito project. These rules are compiled into Drools Rule Language (DRL) for the decision service in your project.

7.1. Spreadsheet decision tables

Spreadsheet decision tables are XLS or XLSX spreadsheet files that contain business rules defined in a tabular format. You can include spreadsheet decision tables as part of your Kogito project. Each row in a decision table is a rule, and each column is a condition, an action, or another rule attribute. After you create and include your spreadsheet decision tables in your project, the rules you defined are compiled into Drools Rule Language (DRL) rules for the decision service.

7.2. Decision table use case

An online shopping site lists the shipping charges for ordered items. The site provides free shipping under the following conditions:

  • The number of items ordered is 4 or more and the checkout total is $300 or more.

  • Standard shipping is selected (4 or 5 business days from the date of purchase).

The following are the shipping rates under these conditions:

Table 7. For orders less than $300
Number of items Delivery day Shipping charge in USD, N = Number of items

3 or fewer

Next day

2nd day

Standard

35

15

10

4 or more

Next day

2nd day

Standard

N*7.50

N*3.50

N*2.50

Table 8. For orders more than $300
Number of items Delivery day Shipping charge in USD, N = Number of items

3 or fewer

Next day

2nd day

Standard

25

10

N*1.50

4 or more

Next day

2nd day

Standard

N*5

N*2

FREE

These conditions and rates are shown in the following example spreadsheet decision table:

Decision table example
Figure 121. Decision table for shipping charges

In order for a decision table to be compiled in your Kogito project, the table must comply with certain structure and syntax requirements, within an XLS or XLSX spreadsheet, as shown in this example. For more information, see Creating spreadsheet decision tables for your Kogito project.

7.3. Creating spreadsheet decision tables for your Kogito project

Spreadsheet decision tables (XLS or XLSX) require two key areas that define rule data: a RuleSet area and a RuleTable area. The RuleSet area of the spreadsheet defines elements that you want to apply globally to all rules in the same package (not only the spreadsheet), such as a rule set name or universal rule attributes. The RuleTable area defines the actual rules (rows) and the conditions, actions, and other rule attributes (columns) that constitute that rule table within the specified rule set. A spreadsheet of decision tables can contain multiple RuleTable areas, but only one RuleSet area.

For each Kogito project, try to include only one spreadsheet of decision tables, containing all necessary RuleTable definitions. Although you can include separate decision table spreadsheets, including multiple spreadsheets in the same project package can cause compilation errors from conflicting RuleSet or RuleTable attributes and is therefore not recommended.

Refer to the following sample spreadsheet as you define your decision table:

Decision table example
Figure 122. Sample spreadsheet decision table for shipping charges
Procedure
  1. In a new XLS or XLSX spreadsheet, go to the second or third column and label a cell RuleSet (row 1 in example). Reserve the column or columns to the left for descriptive metadata (optional).

  2. In the next cell to the right, enter a name for the RuleSet. This named rule set will contain all RuleTable rules defined in the rule package.

  3. Under the RuleSet cell, define any rule attributes (one per cell) that you want to apply globally to all rule tables in the package. Specify attribute values in the cells to the right. For example, you can enter an Import label and in the cell to the right, specify relevant data objects from other packages that you want to import into the package for the decision table (in the format package.name.object.name). For supported cell labels and values, see RuleSet definitions.

  4. Below the RuleSet area and in the same column as the RuleSet cell, skip a row and label a new cell RuleTable (row 7 in example) and enter a table name in the same cell. The name is used as the initial part of the name for all rules derived from this rule table, with the row number appended for distinction. You can override this automatic naming by inserting a NAME attribute column.

  5. Use the next four rows to define the following elements as needed (rows 8-11 in example):

    • Rule attributes: Conditions, actions, or other attributes. For supported cell labels and values, see RuleTable definitions.

    • Object types: The data objects to which the rule attributes apply. If the same object type applies to multiple columns, merge the object cells into one cell across multiple columns (as shown in the sample decision table), instead of repeating the object type in multiple cells. When an object type is merged, all columns below the merged range will be combined into one set of constraints within a single pattern for matching a single fact at a time. When an object is repeated in separate columns, the separate columns can create different patterns, potentially matching different or identical facts.

    • Constraints: Constraints on the object types.

    • Column label: (Optional) Any descriptive label for the column, as a visual aid. Leave blank if unused.

      As an alternative to populating both the object type and constraint cells, you can leave the object type cell or cells empty and enter the full expression in the corresponding constraint cell or cells. For example, instead of Order as the object type and itemsCount > $1 as a constraint (separate cells), you can leave the object type cell empty and enter Order( itemsCount > $1 ) in the constraint cell, and then do the same for other constraint cells.
  6. After you have defined all necessary rule attributes (columns), enter values for each column as needed, row by row, to generate rules (rows 12-17 in example). Cells with no data are ignored (such as when a condition or action does not apply).

    If you need to add more rule tables to this decision table spreadsheet, skip a row after the last rule in the previous table, label another RuleTable cell in the same column as the previous RuleTable and RuleSet cells, and create the new table following the same steps in this section (rows 19-29 in example).

  7. Save your XLS or XLSX spreadsheet to finish.

  8. In your VSCode IDE, import the XLS or XLSX spreadsheet file in the relevant folder of your Kogito project, typically in src/main/resources.

Only the first worksheet in a spreadsheet workbook will be processed as a decision table when you included the spreadsheet in your Kogito project. Each RuleSet name combined with the RuleTable name must be unique across all decision table files in the same package.

After you include the decision table in your Kogito project, the rules are rendered as DRL rules like the following example, from the sample spreadsheet:

//row 12
rule "Basic_12"
salience 10
  when
    $order : Order( itemsCount > 0, itemsCount <= 3, deliverInDays == 1 )
  then
    insert( new Charge( 35 ) );
end

<@Edoardo, what’s the Kogito equivalent to the following note?>

Enabling white space used in cell values

By default, any white space before or after values in decision table cells is removed before the decision table is processed by the Drools engine. To retain white space that you use intentionally before or after values in cells, set the drools.trimCellsInDTable system property to false in your Kogito distribution.

7.3.1. RuleSet definitions

Entries in the RuleSet area of a decision table define DRL constructs and rule attributes that you want to apply to all rules in a package (not only in the spreadsheet). Entries must be in a vertically stacked sequence of cell pairs, where the first cell contains a label and the cell to the right contains the value. A decision table spreadsheet can have only one RuleSet area.

The following table lists the supported labels and values for RuleSet definitions:

Table 9. Supported RuleSet definitions
Label Value Usage

RuleSet

The package name for the generated DRL file. Optional, the default is rule_table.

Must be the first entry.

Sequential

true or false. If true, then salience is used to ensure that rules fire from the top down.

Optional, at most once. If omitted, no firing order is imposed.

SequentialMaxPriority

Integer numeric value

Optional, at most once. In sequential mode, this option is used to set the start value of the salience. If omitted, the default value is 65535.

SequentialMinPriority

Integer numeric value

Optional, at most once. In sequential mode, this option is used to check if this minimum salience value is not violated. If omitted, the default value is 0.

EscapeQuotes

true or false. If true, then quotation marks are escaped so that they appear literally in the DRL.

Optional, at most once. If omitted, quotation marks are escaped.

Import

A comma-separated list of Java classes to import from another package.

Optional, may be used repeatedly.

Variables

Declarations of DRL globals (a type followed by a variable name). Multiple global definitions must be separated by commas.

Optional, may be used repeatedly.

Functions

One or more function definitions, according to DRL syntax.

Optional, may be used repeatedly.

Queries

One or more query definitions, according to DRL syntax.

Optional, may be used repeatedly.

Declare

One or more declarative types, according to DRL syntax.

Optional, may be used repeatedly.

In some cases, Microsoft Office, LibreOffice, and OpenOffice might encode a double quotation mark differently, causing a compilation error. For example, “A” fails, but "A" succeeds.

7.3.2. RuleTable definitions

Entries in the RuleTable area of a decision table define conditions, actions, and other rule attributes for the rules in that rule table. A spreadsheet of decision tables can contain multiple RuleTable areas.

The following table lists the supported labels (column headers) and values for RuleTable definitions. For column headers, you can use either the given labels or any custom labels that begin with the letters listed in the table.

Table 10. Supported RuleTable definitions
Label Or custom label that begins with Value Usage

NAME

N

Provides the name for the rule generated from that row. The default is constructed from the text following the RuleTable tag and the row number.

At most one column.

DESCRIPTION

I

Results in a comment within the generated rule.

At most one column.

CONDITION

C

Code snippet and interpolated values for constructing a constraint within a pattern in a condition.

At least one per rule table.

ACTION

A

Code snippet and interpolated values for constructing an action for the consequence of the rule.

At least one per rule table.

METADATA

@

Code snippet and interpolated values for constructing a metadata entry for the rule.

Optional, any number of columns.

The following sections provide more details about how condition, action, and metadata columns use cell data:

Conditions

For columns headed CONDITION, the cells in consecutive lines result in a conditional element:

  • First cell: Text in the first cell below CONDITION develops into a pattern for the rule condition, and uses the snippet in the next line as a constraint. If the cell is merged with one or more neighboring cells, a single pattern with multiple constraints is formed. All constraints are combined into a parenthesized list and appended to the text in this cell.

    If this cell is empty, the code snippet in the cell below it must result in a valid conditional element on its own. For example, instead of Order as the object type and itemsCount > $1 as a constraint (separate cells), you can leave the object type cell empty and enter Order( itemsCount > $1 ) in the constraint cell, and then do the same for any other constraint cells.

    To include a pattern without constraints, you can write the pattern in front of the text of another pattern, with or without an empty pair of parentheses. You can also append a from clause to the pattern.

    If the pattern ends with eval, code snippets produce boolean expressions for inclusion into a pair of parentheses after eval.

  • Second cell: Text in the second cell below CONDITION is processed as a constraint on the object reference in the first cell. The code snippet in this cell is modified by interpolating values from cells farther down in the column. If you want to create a constraint consisting of a comparison using == with the value from the cells below, then the field selector alone is sufficient. Any other comparison operator must be specified as the last item within the snippet, and the value from the cells below is appended. For all other constraint forms, you must mark the position for including the contents of a cell with the symbol $param. Multiple insertions are possible if you use the symbols $1, $2, and so on, and a comma-separated list of values in the cells below. However, do not separate $1, $2, and so on, by commas, or the table will fail to process.

    To expand a text according to the pattern forall($delimiter){$snippet}, repeat the $snippet once for each of the values of the comma-separated list in each of the cells below, insert the value in place of the symbol $, and join these expansions by the given $delimiter. Note that the forall construct may be surrounded by other text.

    If the first cell contains an object, the completed code snippet is added to the conditional element from that cell. A pair of parentheses is provided automatically, as well as a separating comma if multiple constraints are added to a pattern in a merged cell. If the first cell is empty, the code snippet in this cell must result in a valid conditional element on its own. For example, instead of Order as the object type and itemsCount > $1 as a constraint (separate cells), you can leave the object type cell empty and enter Order( itemsCount > $1 ) in the constraint cell, and then do the same for any other constraint cells.

  • Third cell: Text in the third cell below CONDITION is a descriptive label that you define for the column, as a visual aid.

  • Fourth cell: From the fourth row on, non-blank entries provide data for interpolation. A blank cell omits the condition or constraint for this rule.

Actions

For columns headed ACTION, the cells in consecutive lines result in an action statement:

  • First cell: Text in the first cell below ACTION is optional. If present, the text is interpreted as an object reference.

  • Second cell: Text in the second cell below ACTION is a code snippet that is modified by interpolating values from cells farther down in the column. For a singular insertion, mark the position for including the contents of a cell with the symbol $param. Multiple insertions are possible if you use the symbols $1, $2, and so on, and a comma-separated list of values in the cells below. However, do not separate $1, $2, and so on, by commas, or the table will fail to process.

    A text without any marker symbols can execute a method call without interpolation. In this case, use any non-blank entry in a row below the cell to include the statement. The forall construct is supported.

    If the first cell contains an object, then the cell text (followed by a period), the text in the second cell, and a terminating semicolon are strung together, resulting in a method call that is added as an action statement for the consequence. If the first cell is empty, the code snippet in this cell must result in a valid action element on its own.

  • Third cell: Text in the third cell below ACTION is a descriptive label that you define for the column, as a visual aid.

  • Fourth cell: From the fourth row on, non-blank entries provide data for interpolation. A blank cell omits the condition or constraint for this rule.

Metadata

For columns headed METADATA, the cells in consecutive lines result in a metadata annotation for the generated rules:

  • First cell: Text in the first cell below METADATA is ignored.

  • Second cell: Text in the second cell below METADATA is subject to interpolation, using values from the cells in the rule rows. The metadata marker character @ is prefixed automatically, so you do not need to include that character in the text for this cell.

  • Third cell: Text in the third cell below METADATA is a descriptive label that you define for the column, as a visual aid.

  • Fourth cell: From the fourth row on, non-blank entries provide data for interpolation. A blank cell results in the omission of the metadata annotation for this rule.

7.3.3. Additional rule attributes for RuleSet or RuleTable definitions

The RuleSet and RuleTable areas also support labels and values for other rule attributes, such as PRIORITY or NO-LOOP. Rule attributes specified in a RuleSet area will affect all rule assets in the same package (not only in the spreadsheet). Rule attributes specified in a RuleTable area will affect only the rules in that rule table. You can use each rule attribute only once in a RuleSet area and once in a RuleTable area. If the same attribute is used in both RuleSet and RuleTable areas within the spreadsheet, then RuleTable takes priority and the attribute in the RuleSet area is overridden.

The following table lists the supported labels (column headers) and values for additional RuleSet or RuleTable definitions. For column headers, you can use either the given labels or any custom labels that begin with the letters listed in the table.

<@Edoardo, so nix just AGENDA-GROUP from table below, but leave other groups?>

Table 11. Additional rule attributes for RuleSet or RuleTable definitions
Label Or custom label that begins with Value

PRIORITY

P

An integer defining the salience value of the rule. Rules with a higher salience value are given higher priority when ordered in the activation queue. Overridden by the Sequential flag.

Example: PRIORITY 10

DATE-EFFECTIVE

V

A string containing a date and time definition. The rule can be activated only if the current date and time is after a DATE-EFFECTIVE attribute.

Example: DATE-EFFECTIVE "4-Sep-2018"

DATE-EXPIRES

Z

A string containing a date and time definition. The rule cannot be activated if the current date and time is after the DATE-EXPIRES attribute.

Example: DATE-EXPIRES "4-Oct-2018"

NO-LOOP

U

A Boolean value. When this option is set to true, the rule cannot be reactivated (looped) if a consequence of the rule re-triggers a previously met condition.

Example: NO-LOOP true

AGENDA-GROUP

G

A string identifying an agenda group to which you want to assign the rule. Agenda groups allow you to partition the agenda to provide more execution control over groups of rules. Only rules in an agenda group that has acquired a focus are able to be activated.

Example: AGENDA-GROUP "GroupName"

ACTIVATION-GROUP

X

A string identifying an activation (or XOR) group to which you want to assign the rule. In activation groups, only one rule can be activated. The first rule to fire will cancel all pending activations of all rules in the activation group.

Example: ACTIVATION-GROUP "GroupName"

DURATION

D

A long integer value defining the duration of time in milliseconds after which the rule can be activated, if the rule conditions are still met.

Example: DURATION 10000

TIMER

T

A string identifying either int (interval) or cron timer definitions for scheduling the rule.

Example: TIMER "*/5 * * * *" (every 5 minutes)

CALENDAR

E

A Quartz calendar definition for scheduling the rule.

Example: CALENDAR "* * 0-7,18-23 ? * *" (exclude non-business hours)

AUTO-FOCUS

F

A Boolean value, applicable only to rules within agenda groups. When this option is set to true, the next time the rule is activated, a focus is automatically given to the agenda group to which the rule is assigned.

Example: AUTO-FOCUS true

LOCK-ON-ACTIVE

L

A Boolean value, applicable only to rules within rule flow groups or agenda groups. When this option is set to true, the next time the ruleflow group for the rule becomes active or the agenda group for the rule receives a focus, the rule cannot be activated again until the ruleflow group is no longer active or the agenda group loses the focus. This is a stronger version of the no-loop attribute, because the activation of a matching rule is discarded regardless of the origin of the update (not only by the rule itself). This attribute is ideal for calculation rules where you have a number of rules that modify a fact and you do not want any rule re-matching and firing again.

Example: LOCK-ON-ACTIVE true

RULEFLOW-GROUP

R

A string identifying a rule flow group. In rule flow groups, rules can fire only when the group is activated by the associated rule flow.

Example: RULEFLOW-GROUP "GroupName"

Example decision table with definitions used
Figure 123. Sample decision table spreadsheet with attribute columns

7.4. Kogito service execution

After you design your Kogito service, you can build and run your application and then send REST API requests to the application to execute your services. The exact REST API requests that you can use depend on how you set up the application.

For example, consider a Kogito service that is set up to generate a /persons REST API endpoint and determines whether a given customer is an adult or is underage. In this example, you can send the following POST request using a REST client or curl utility to add an adult and execute the service:

Example POST request body to add an adult
{
  "person": {
    "name": "John Quark",
    "age": 20
  }
}
Example curl command to add an adult
curl -X POST http://localhost:8080/persons -H 'content-type: application/json' -H 'accept: application/json' -d '{"person": {"name":"John Quark", "age": 20}}'
Example response
{
  "id": "b7d993ac-272d-43c7-8245-d4df96d9ab6c",
  "person": {
    "name": "John Quark",
    "age": 20,
    "adult": true
  }
}

For information about creating, running, and testing an example application with Kogito services, see Creating and running your first Kogito services.

For information about deploying your Kogito service to OpenShift, see Deploying Kogito services on OpenShift.

8. Drools engine in Kogito

As a business rules developer, your understanding of the Drools engine in Kogito can help you design more effective business assets and a more scalable decision management architecture. The Drools engine is the Kogito component that stores, processes, and evaluates data to execute business rules and to reach the decisions that you define. This document describes basic concepts and functions of the Drools engine to consider as you create your business rule system and decision services in Kogito.

8.1. Drools engine in Kogito

The Drools engine is the rules engine in Kogito. The Drools engine stores, processes, and evaluates data to execute the business rules or decision models that you define. The basic function of the Drools engine is to match incoming data, or facts, to the conditions of rules and determine whether and how to execute the rules.

The Drools engine operates using the following basic components:

  • Rules: Business rules or DMN decisions that you define. All rules must contain at a minimum the conditions that trigger the rule and the actions that the rule dictates.

  • Facts: Data that enters or changes in the Drools engine that the Drools engine matches to rule conditions to execute applicable rules.

  • Production memory: Location where rules are stored in the Drools engine.

  • Working memory: Location where facts are stored in the Drools engine.

  • Agenda: Location where activated rules are registered and sorted (if applicable) in preparation for execution.

When a business user or an automated system adds or updates rule-related information in Kogito, that information is inserted into the working memory of the Drools engine in the form of one or more facts. The Drools engine matches those facts to the conditions of the rules that are stored in the production memory to determine eligible rule executions. (This process of matching facts to rules is often referred to as pattern matching.) When rule conditions are met, the Drools engine activates and registers rules in the agenda, where the Drools engine then sorts prioritized or conflicting rules in preparation for execution.

The following diagram illustrates these basic components of the Drools engine:

rule engine inkscape enterprise
Figure 124. Overview of basic Drools engine components

These core concepts can help you to better understand other more advanced components, processes, and sub-processes of the Drools engine, and as a result, to design more effective business assets in Kogito.

8.2. Inference and truth maintenance in the Drools engine

The basic function of the Drools engine is to match data to business rules and determine whether and how to execute rules. To ensure that relevant data is applied to the appropriate rules, the Drools engine makes inferences based on existing knowledge and performs the actions based on the inferred information.

For example, the following DRL rule determines the age requirements for adults, such as in a bus pass policy:

Rule to define age requirement
rule "Infer Adult"
when
  $p : Person(age >= 18)
then
  insert(new IsAdult($p))
end

Based on this rule, the Drools engine infers whether a person is an adult or a child and performs the specified action (the then consequence). Every person who is 18 years old or older has an instance of IsAdult inserted for them in the working memory. This inferred relation of age and bus pass can then be invoked in any rule, such as in the following rule segment:

$p : Person()
IsAdult(person == $p)

In many cases, new data in a rule system is the result of other rule executions, and this new data can affect the execution of other rules. If the Drools engine asserts data as a result of executing a rule, the Drools engine uses truth maintenance to justify the assertion and enforce truthfulness when applying inferred information to other rules. Truth maintenance also helps to identify inconsistencies and to handle contradictions. For example, if two rules are executed and result in a contradictory action, the Drools engine chooses the action based on assumptions from previously calculated conclusions.

The Drools engine inserts facts using either stated or logical insertions:

  • Stated insertions: Defined with insert(). After stated insertions, facts are generally retracted explicitly. (The term insertion, when used generically, refers to stated insertion.)

  • Logical insertions: Defined with insertLogical(). After logical insertions, the facts that were inserted are automatically retracted when the conditions in the rules that inserted the facts are no longer true. The facts are retracted when no condition supports the logical insertion. A fact that is logically inserted is considered to be justified by the Drools engine.

For example, the following sample DRL rules use stated fact insertion to determine the age requirements for issuing a child bus pass or an adult bus pass:

Rules to issue bus pass, stated insertion
rule "Issue Child Bus Pass"
when
  $p : Person(age < 18)
then
  insert(new ChildBusPass($p));
end

rule "Issue Adult Bus Pass"
when
  $p : Person(age >= 18)
then
  insert(new AdultBusPass($p));
end

These rules are not easily maintained in the Drools engine as bus riders increase in age and move from child to adult bus pass. As an alternative, these rules can be separated into rules for bus rider age and rules for bus pass type using logical fact insertion. The logical insertion of the fact makes the fact dependent on the truth of the when clause.

The following DRL rules use logical insertion to determine the age requirements for children and adults:

Children and adult age requirements, logical insertion
rule "Infer Child"
when
  $p : Person(age < 18)
then
  insertLogical(new IsChild($p))
end

rule "Infer Adult"
when
  $p : Person(age >= 18)
then
  insertLogical(new IsAdult($p))
end
For logical insertions, your fact objects must override the equals and hashCode methods from the java.lang.Object object according to the Java standard. Two objects are equal if their equals methods return true for each other and if their hashCode methods return the same values. For more information, see the Java API documentation for your Java version.

When the condition in the rule is false, the fact is automatically retracted. This behavior is helpful in this example because the two rules are mutually exclusive. In this example, if the person is younger than 18 years old, the rule logically inserts an IsChild fact. After the person is 18 years old or older, the IsChild fact is automatically retracted and the IsAdult fact is inserted.

The following DRL rules then determine whether to issue a child bus pass or an adult bus pass and logically insert the ChildBusPass and AdultBusPass facts. This rule configuration is possible because the truth maintenance system in the Drools engine supports chaining of logical insertions for a cascading set of retracts.

Rules to issue bus pass, logical insertion
rule "Issue Child Bus Pass"
when
  $p : Person()
    IsChild(person == $p)
then
  insertLogical(new ChildBusPass($p));
end

rule "Issue Adult Bus Pass"
when
  $p : Person()
    IsAdult(person =$p)
then
  insertLogical(new AdultBusPass($p));
end

When a person turns 18 years old, the IsChild fact and the person’s ChildBusPass fact is retracted. To these set of conditions, you can relate another rule that states that a person must return the child pass after turning 18 years old. When the Drools engine automatically retracts the ChildBusPass object, the following rule is executed to send a request to the person:

Rule to notify bus pass holder of new pass
rule "Return ChildBusPass Request"
when
  $p : Person()
    not(ChildBusPass(person == $p))
then
  requestChildBusPass($p);
end

The following flowcharts illustrate the life cycle of stated and logical insertions:

Stated Assertion
Figure 125. Stated insertion
Logical Assertion
Figure 126. Logical insertion

When the Drools engine logically inserts an object during a rule execution, the Drools engine justifies the object by executing the rule. For each logical insertion, only one equal object can exist, and each subsequent equal logical insertion increases the justification counter for that logical insertion. A justification is removed when the conditions of the rule become untrue. When no more justifications exist, the logical object is automatically retracted.

8.2.1. Government ID example

So now we know what inference is, and have a basic example, how does this facilitate good rule design and maintenance?

Consider a government ID department that is responsible for issuing ID cards when children become adults. They might have a decision table that includes logic like this, which says when an adult living in London is 18 or over, issue the card:

RuleTable ID Card

CONDITION

CONDITION

ACTION

p : Person

location

age >= $1

issueIdCard($1)

Select Person

Select Adults

Issue ID Card

Issue ID Card to Adults

London

18

p

However the ID department does not set the policy on who an adult is. That’s done at a central government level. If the central government were to change that age to 21, this would initiate a change management process. Someone would have to liaise with the ID department and make sure their systems are updated, in time for the law going live.

This change management process and communication between departments is not ideal for an agile environment, and change becomes costly and error prone. Also the card department is managing more information than it needs to be aware of with its "monolithic" approach to rules management which is "leaking" information better placed elsewhere. By this we mean that it doesn’t care what explicit "age >= 18" information determines whether someone is an adult, only that they are an adult.

In contrast to this, let’s pursue an approach where we split (de-couple) the authoring responsibilities, so that both the central government and the ID department maintain their own rules.

It’s the central government’s job to determine who is an adult. If they change the law they just update their central repository with the new rules, which others use:

RuleTable Age Policy

CONDITION

ACTION

p : Person

age >= $1

insert($1)

Adult Age Policy

Add Adult Relation

Infer Adult

18

new IsAdult( p )

The IsAdult fact, as discussed previously, is inferred from the policy rules. It encapsulates the seemingly arbitrary piece of logic "age >= 18" and provides semantic abstractions for its meaning. Now if anyone uses the above rules, they no longer need to be aware of explicit information that determines whether someone is an adult or not. They can just use the inferred fact:

RuleTable ID Card

CONDITION

CONDITION

ACTION

p : Person

isAdult

location

person == $1

issueIdCard($1)

Select Person

Select Adults

Issue ID Card

Issue ID Card to Adults

London

p

p

While the example is very minimal and trivial it illustrates some important points. We started with a monolithic and leaky approach to our knowledge engineering. We created a single decision table that had all possible information in it and that leaks information from central government that the ID department did not care about and did not want to manage.

We first de-coupled the knowledge process so each department was responsible for only what it needed to know. We then encapsulated this leaky knowledge using an inferred fact IsAdult. The use of the term IsAdult also gave a semantic abstraction to the previously arbitrary logic "age >= 18".

So a general rule of thumb when doing your knowledge engineering is:

  • Bad

    • Monolithic

    • Leaky

  • Good

    • De-couple knowledge responsibilities

    • Encapsulate knowledge

    • Provide semantic abstractions for those encapsulations

8.2.2. Fact equality modes in the Drools engine

<@Edoardo, see this section.>

The Drools engine supports the following fact equality modes that determine how the Drools engine stores and compares inserted facts:

  • identity: (Default) The Drools engine uses an IdentityHashMap to store all inserted facts. For every new fact insertion, the Drools engine returns a new FactHandle object. If a fact is inserted again, the Drools engine returns the original FactHandle object, ignoring repeated insertions for the same fact. In this mode, two facts are the same for the Drools engine only if they are the very same object with the same identity.

  • equality: The Drools engine uses a HashMap to store all inserted facts. The Drools engine returns a new FactHandle object only if the inserted fact is not equal to an existing fact, according to the equals() method of the inserted fact. In this mode, two facts are the same for the Drools engine if they are composed the same way, regardless of identity. Use this mode when you want objects to be assessed based on feature equality instead of explicit identity.

As an illustration of fact equality modes, consider the following example facts:

Example facts
Person p1 = new Person("John", 45);
Person p2 = new Person("John", 45);

In identity mode, facts p1 and p2 are different instances of a Person class and are treated as separate objects because they have separate identities. In equality mode, facts p1 and p2 are treated as the same object because they are composed the same way. This difference in behavior affects how you can interact with fact handles.

For example, assume that you insert facts p1 and p2 into the Drools engine and later you want to retrieve the fact handle for p1. In identity mode, you must specify p1 to return the fact handle for that exact object, whereas in equality mode, you can specify p1, p2, or new Person("John", 45) to return the fact handle.

Example code to insert a fact and return the fact handle in identity mode
ksession.insert(p1);

ksession.getFactHandle(p1);
Example code to insert a fact and return the fact handle in equality mode
ksession.insert(p1);

ksession.getFactHandle(p1);

// Alternate option:
ksession.getFactHandle(new Person("John", 45));

To set the fact equality mode, use one of the following options:

  • Set the system property drools.equalityBehavior to identity (default) or equality.

  • Set the equality mode while creating the KIE base programmatically:

    KieServices ks = KieServices.get();
    KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration();
    kieBaseConf.setOption(EqualityBehaviorOption.EQUALITY);
    KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
  • Set the equality mode in the KIE module descriptor file (kmodule.xml) for a specific Kogito project:

    <kmodule>
      ...
      <kbase name="KBase2" default="false" equalsBehavior="equality" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1">
        ...
      </kbase>
      ...
    </kmodule>

8.3. Execution control in the Drools engine

<@Edoardo, see this section. I’ve removed agenda groups, but not sure how to address fireAllRules, etc.>

When new rule data enters the working memory of the Drools engine, rules may become fully matched and eligible for execution. A single working memory action can result in multiple eligible rule executions. When a rule is fully matched, the Drools engine creates an activation instance, referencing the rule and the matched facts, and adds the activation onto the Drools engine agenda. The agenda controls the execution order of these rule activations using a conflict resolution strategy.

After the first call of fireAllRules() in the Java application, the Drools engine cycles repeatedly through two phases:

  • Agenda evaluation. In this phase, the Drools engine selects all rules that can be executed. If no executable rules exist, the execution cycle ends. If an executable rule is found, the Drools engine registers the activation in the agenda and then moves on to the working memory actions phase to perform rule consequence actions.

  • Working memory actions. In this phase, the Drools engine performs the rule consequence actions (the then portion of each rule) for all activated rules previously registered in the agenda. After all the consequence actions are complete or the main Java application process calls fireAllRules() again, the Drools engine returns to the agenda evaluation phase to reassess rules.

Two Phase
Figure 127. Two-phase execution process in the Drools engine

When multiple rules exist on the agenda, the execution of one rule may cause another rule to be removed from the agenda. To avoid this, you can define how and when rules are executed in the Drools engine. Some common methods for defining rule execution order are by using rule salience, agenda groups, activation groups, or rule units for DRL rule sets.

8.3.1. Salience for rules

Each rule has an integer salience attribute that determines the order of execution. Rules with a higher salience value are given higher priority when ordered in the activation queue. The default salience value for rules is zero, but the salience can be negative or positive.

For example, the following sample DRL rules are listed in the Drools engine stack in the order shown:

rule "RuleA"
salience 95
when
    $fact : MyFact( field1 == true )
then
    System.out.println("Rule2 : " + $fact);
    update($fact);
end

rule "RuleB"
salience 100
when
   $fact : MyFact( field1 == false )
then
   System.out.println("Rule1 : " + $fact);
   $fact.setField1(true);
   update($fact);
end

The RuleB rule is listed second, but it has a higher salience value than the RuleA rule and is therefore executed first.

8.3.2. Activation groups for rules

An activation group is a set of rules bound together by the same activation-group rule attribute. In this group, only one rule can be executed. After conditions are met for a rule in that group to be executed, all other pending rule executions from that activation group are removed from the agenda.

For example, the following sample DRL rules belong to the specified activation group and are listed in the Drools engine stack in the order shown:

Sample DRL rules for banking
rule "Print balance for AccountPeriod1"
  activation-group "report"
when
  ap : AccountPeriod1()
  acc : Account()
then
  System.out.println( acc.accountNo +
                      " : " + acc.balance );
end
rule "Print balance for AccountPeriod2"
  activation-group "report"
when
  ap : AccountPeriod2()
  acc : Account()
then
  System.out.println( acc.accountNo +
                      " : " + acc.balance );
end

For this example, if the first rule in the "report" activation group is executed, the second rule in the group and all other executable rules on the agenda are removed from the agenda.

8.3.3. Rule execution modes and thread safety in the Drools engine

<@Edoardo, see this section. Not sure how we want to communicate this now. This came up a lot in 7.x so is/was a hot topic.>

The Drools engine supports the following rule execution modes that determine how and when the Drools engine executes rules:

  • Passive mode: (Default) The Drools engine evaluates rules when a user or an application explicitly calls fireAllRules(). Passive mode in the Drools engine is best for applications that require direct control over rule evaluation and execution, or for complex event processing (CEP) applications that use the pseudo clock implementation in the Drools engine.

    Example CEP application code with the Drools engine in passive mode
    KieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration();
    config.setOption( ClockTypeOption.get("pseudo") );
    KieSession session = kbase.newKieSession( conf, null );
    SessionPseudoClock clock = session.getSessionClock();
    
    session.insert( tick1 );
    session.fireAllRules();
    
    clock.advanceTime(1, TimeUnit.SECONDS);
    session.insert( tick2 );
    session.fireAllRules();
    
    clock.advanceTime(1, TimeUnit.SECONDS);
    session.insert( tick3 );
    session.fireAllRules();
    
    session.dispose();
  • Active mode: If a user or application calls fireUntilHalt(), the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly calls halt(). Active mode in the Drools engine is best for applications that delegate control of rule evaluation and execution to the Drools engine, or for complex event processing (CEP) applications that use the real-time clock implementation in the Drools engine. Active mode is also optimal for CEP applications that use active queries.

    Example CEP application code with the Drools engine in active mode
    KieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration();
    config.setOption( ClockTypeOption.get("realtime") );
    KieSession session = kbase.newKieSession( conf, null );
    
    new Thread( new Runnable() {
      @Override
      public void run() {
          session.fireUntilHalt();
      }
    } ).start();
    
    session.insert( tick1 );
    
    ... Thread.sleep( 1000L ); ...
    
    session.insert( tick2 );
    
    ... Thread.sleep( 1000L ); ...
    
    session.insert( tick3 );
    
    session.halt();
    session.dispose();

    This example calls fireUntilHalt() from a dedicated execution thread to prevent the current thread from being blocked indefinitely while the Drools engine continues evaluating rules. The dedicated thread also enables you to call halt() at a later stage in the application code.

Although you should avoid using both fireAllRules() and fireUntilHalt() calls, especially from different threads, the Drools engine can handle such situations safely using thread-safety logic and an internal state machine. If a fireAllRules() call is in progress and you call fireUntilHalt(), the Drools engine continues to run in passive mode until the fireAllRules() operation is complete and then starts in active mode in response to the fireUntilHalt() call. However, if the Drools engine is running in active mode following a fireUntilHalt() call and you call fireAllRules(), the fireAllRules() call is ignored and the Drools engine continues to run in active mode until you call halt().

For added thread safety in active mode, the Drools engine supports a submit() method that you can use to group and perform operations on a KIE session in a thread-safe, atomic action:

Example application code with submit() method to perform atomic operations in active mode
KieSession session = ...;

new Thread( new Runnable() {
  @Override
  public void run() {
      session.fireUntilHalt();
  }
} ).start();

final FactHandle fh = session.insert( fact_a );

... Thread.sleep( 1000L ); ...

session.submit( new KieSession.AtomicAction() {
  @Override
  public void execute( KieSession kieSession ) {
    fact_a.setField("value");
    kieSession.update( fh, fact_a );
    kieSession.insert( fact_1 );
    kieSession.insert( fact_2 );
    kieSession.insert( fact_3 );
  }
} );

... Thread.sleep( 1000L ); ...

session.insert( fact_z );

session.halt();
session.dispose();

Thread safety and atomic operations are also helpful from a client-side perspective. For example, you might need to insert more than one fact at a given time, but require the Drools engine to consider the insertions as an atomic operation and to wait until all the insertions are complete before evaluating the rules again.

8.3.4. Fact propagation modes in the Drools engine

<@Edoardo, also this section. This is coupled with rule execution modes and also came up a lot in 7.x and is/was a hot topic.>

The Drools engine supports the following fact propagation modes that determine how the Drools engine progresses inserted facts through the engine network in preparation for rule execution:

  • Lazy: (Default) Facts are propagated in batch collections at rule execution, not in real time as the facts are individually inserted by a user or application. As a result, the order in which the facts are ultimately propagated through the Drools engine may be different from the order in which the facts were individually inserted.

  • Immediate: Facts are propagated immediately in the order that they are inserted by a user or application.

  • Eager: Facts are propagated lazily (in batch collections), but before rule execution. The Drools engine uses this propagation behavior for rules that have the no-loop or lock-on-active attribute.

By default, the Phreak rule algorithm in the Drools engine uses lazy fact propagation for improved rule evaluation overall. However, in few cases, this lazy propagation behavior can alter the expected result of certain rule executions that may require immediate or eager propagation.

For example, the following rule uses a specified query with a ? prefix to invoke the query in pull-only or passive fashion:

Example rule with a passive query
query Q (Integer i)
    String( this == i.toString() )
end

rule "Rule"
  when
    $i : Integer()
    ?Q( $i; )
  then
    System.out.println( $i );
end

For this example, the rule should be executed only when a String that satisfies the query is inserted before the Integer, such as in the following example commands:

Example commands that should trigger the rule execution
KieSession ksession = ...
ksession.insert("1");
ksession.insert(1);
ksession.fireAllRules();

However, due to the default lazy propagation behavior in Phreak, the Drools engine does not detect the insertion sequence of the two facts in this case, so this rule is executed regardless of String and Integer insertion order. For this example, immediate propagation is required for the expected rule evaluation.

To alter the Drools engine propagation mode to achieve the expected rule evaluation in this case, you can add the @Propagation(TYPE) tag to your rule and set TYPE to LAZY, IMMEDIATE, or EAGER.

In the same example rule, the immediate propagation annotation enables the rule to be evaluated only when a String that satisfies the query is inserted before the Integer, as expected:

Example rule with a passive query and specified propagation mode
query Q (Integer i)
    String( this == i.toString() )
end

rule "Rule" @Propagation(IMMEDIATE)
  when
    $i : Integer()
    ?Q( $i; )
  then
    System.out.println( $i );
end

8.3.5. Agenda evaluation filters

<@Edoardo, see this section.>

AgendaFilter
Figure 128. AgendaFilters

The Drools engine supports an AgendaFilter object in the filter interface that you can use to allow or deny the evaluation of specified rules during agenda evaluation. You can specify an agenda filter as part of a fireAllRules() call.

The following example code permits only rules ending with the string "Test" to be evaluated and executed. All other rules are filtered out of the Drools engine agenda.

Example agenda filter definition
ksession.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );

8.3.6. Rule units in DRL

Rule units are groups of data sources, global variables, and DRL rules that function together for a specific purpose. You can use rule units to partition a rule set into smaller units, bind different data sources to those units, and then execute the individual unit. Rule units are an enhanced alternative to rule-grouping DRL attributes such as rule agenda groups or activation groups for execution control.

The following example is a rule unit designated in a DRL file in a mortgage application decision service:

Example package definition and rule unit designation in a DRL file
package org.mortgages;
unit MortgageRules;

Rule units are helpful when you want to coordinate rule execution so that the complete execution of one rule unit triggers the start of another rule unit and so on. For example, assume that you have a set of rules for data enrichment, another set of rules that processes that data, and another set of rules that extract the output from the processed data. If you add these rule sets into three distinct rule units, you can coordinate those rule units so that complete execution of the first unit triggers the start of the second unit and the complete execution of the second unit triggers the start of third unit.

To define a rule unit, implement the RuleUnit interface as shown in the following example:

Example rule unit class
package org.mypackage.myunit;

public static class AdultUnit implements RuleUnit {
    private int adultAge;
    private DataSource<Person> persons;

    public AdultUnit( ) { }

    public AdultUnit( DataSource<Person> persons, int age ) {
        this.persons = persons;
        this.age = age;
    }

    // A data source of `Persons` in this rule unit:
    public DataSource<Person> getPersons() {
        return persons;
    }

    // A global variable in this rule unit:
    public int getAdultAge() {
        return adultAge;
    }

    // Life-cycle methods:
    @Override
    public void onStart() {
        System.out.println("AdultUnit started.");
    }

    @Override
    public void onEnd() {
        System.out.println("AdultUnit ended.");
    }
}

In this example, persons is a source of facts of type Person. A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. The adultAge global variable is accessible from all the rules belonging to this rule unit. The last two methods are part of the rule unit life cycle and are invoked by the Drools engine.

The Drools engine supports the following optional life-cycle methods for rule units:

Table 12. Rule unit life-cycle methods
Method Invoked when

onStart()

Rule unit execution starts

onEnd()

Rule unit execution ends

onSuspend()

Rule unit execution is suspended (used only with runUntilHalt())

onResume()

Rule unit execution is resumed (used only with runUntilHalt())

onYield(RuleUnit other)

The consequence of a rule in the rule unit triggers the execution of a different rule unit

You can add one or more rules to a rule unit. By default, all the rules in a DRL file are automatically associated with a rule unit that follows the naming convention of the DRL file name. If the DRL file is in the same package and has the same name as a class that implements the RuleUnit interface, then all of the rules in that DRL file implicitly belong to that rule unit. For example, all the rules in the AdultUnit.drl file in the org.mypackage.myunit package are automatically part of the rule unit org.mypackage.myunit.AdultUnit.

To override this naming convention and explicitly declare the rule unit that the rules in a DRL file belong to, use the unit keyword in the DRL file. The unit declaration must immediately follow the package declaration and contain the name of the class in that package that the rules in the DRL file are part of.

Example rule unit declaration in a DRL file
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    $p : /persons[age >= adultAge]
  then
    System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
Do not mix rules with and without a rule unit in the same KIE base. Mixing two rule paradigms in a KIE base results in a compilation error.

You can also rewrite the same rule condition in a more explicit form using the traditional rule pattern syntax instead of OOPath syntax, as shown in the following example:

Example rule unit declaration in a DRL file using traditional notation
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    $p : Person(age >= adultAge) from persons
  then
    System.out.println($p.getName() + " is adult and greater than " + adultAge);
end

In this example, any matching facts in the rule conditions are retrieved from the persons data source defined in the DataSource definition in the rule unit class. The rule condition and action use the adultAge variable in the same way that a global variable is defined at the DRL file level.

To execute one or more rule units defined in a KIE base, create a new RuleUnitExecutor class bound to the KIE base, create the rule unit from the relevant data source, and run the rule unit executer:

Example rule unit execution
// Create a `RuleUnitExecutor` class and bind it to the KIE base:
KieBase kbase = kieContainer.getKieBase();
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );

// Create the `AdultUnit` rule unit using the `persons` data source and run the executor:
RuleUnit adultUnit = new AdultUnit(persons, 18);
executor.run( adultUnit );

Rules are executed by the RuleUnitExecutor class. The RuleUnitExecutor class creates KIE sessions and adds the required DataSource objects to those sessions, and then executes the rules based on the RuleUnit that is passed as a parameter to the run() method.

The example execution code produces the following output when the relevant Person facts are inserted in the persons data source:

Example rule unit execution output
org.mypackage.myunit.AdultUnit started.
Jane is adult and greater than 18
John is adult and greater than 18
org.mypackage.myunit.AdultUnit ended.

Instead of explicitly creating the rule unit instance, you can register the rule unit variables in the executor and pass to the executor the rule unit class that you want to run, and then the executor creates an instance of the rule unit. You can then set the DataSource definition and other variables as needed before running the rule unit.

Alternate rule unit execution option with registered variables
executor.bindVariable( "persons", persons );
        .bindVariable( "adultAge", 18 );
executor.run( AdultUnit.class );

The name that you pass to the RuleUnitExecutor.bindVariable() method is used at run time to bind the variable to the field of the rule unit class with the same name. In the previous example, the RuleUnitExecutor inserts into the new rule unit the data source bound to the "persons" name and inserts the value 18 bound to the String "adultAge" into the fields with the corresponding names inside the AdultUnit class.

To override this default variable-binding behavior, use the @UnitVar annotation to explicitly define a logical binding name for each field of the rule unit class. For example, the field bindings in the following class are redefined with alternative names:

Example code to modify variable binding names with @UnitVar
package org.mypackage.myunit;

public static class AdultUnit implements RuleUnit {
    @UnitVar("minAge")
    private int adultAge = 18;

    @UnitVar("data")
    private DataSource<Person> persons;
}

You can then bind the variables to the executor using those alternative names and run the rule unit:

Example rule unit execution with modified variable names
executor.bindVariable( "data", persons );
        .bindVariable( "minAge", 18 );
executor.run( AdultUnit.class );

You can execute a rule unit in passive mode by using the run() method (equivalent to invoking fireAllRules() on a KIE session) or in active mode using the runUntilHalt() method (equivalent to invoking fireUntilHalt() on a KIE session). By default, the Drools engine runs in passive mode and evaluates rule units only when a user or an application explicitly calls run() (or fireAllRules() for standard rules). If a user or application calls runUntilHalt() for rule units (or fireUntilHalt() for standard rules), the Drools engine starts in active mode and evaluates rule units continually until the user or application explicitly calls halt().

If you use the runUntilHalt() method, invoke the method on a separate execution thread to avoid blocking the main thread:

Example rule unit execution with runUntilHalt() on a separate thread
new Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
8.3.6.1. Data sources for rule units

A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. A rule unit can have zero or more data sources and each DataSource definition declared inside a rule unit can correspond to a different entry point into the rule unit executor. Multiple rule units can share a single data source, but each rule unit must use different entry points through which the same objects are inserted.

You can create a DataSource definition with a fixed set of data in a rule unit class, as shown in the following example:

Example data source definition
DataSource<Person> persons = DataSource.create( new Person( "John", 42 ),
                                                new Person( "Jane", 44 ),
                                                new Person( "Sally", 4 ) );

Because a data source represents the entry point of the rule unit, you can insert, update, or delete facts in a rule unit:

Example code to insert, modify, and delete a fact in a rule unit
// Insert a fact:
Person john = new Person( "John", 42 );
FactHandle johnFh = persons.insert( john );

// Modify the fact and optionally specify modified properties (for property reactivity):
john.setAge( 43 );
persons.update( johnFh, john, "age" );

// Delete the fact:
persons.delete( johnFh );
8.3.6.2. Rule unit execution control

Rule units are helpful when you want to coordinate rule execution so that the execution of one rule unit triggers the start of another rule unit and so on.

To facilitate rule unit execution control, the Drools engine supports the following rule unit methods that you can use in DRL rule actions to coordinate the execution of rule units:

  • drools.run(): Triggers the execution of a specified rule unit class. This method imperatively interrupts the execution of the rule unit and activates the other specified rule unit.

  • drools.guard(): Prevents (guards) a specified rule unit class from being executed until the associated rule condition is met. This method declaratively schedules the execution of the other specified rule unit. When the Drools engine produces at least one match for the condition in the guarding rule, the guarded rule unit is considered active. A rule unit can contain multiple guarding rules.

As an example of the drools.run() method, consider the following DRL rules that each belong to a specified rule unit. The NotAdult rule uses the drools.run( AdultUnit.class ) method to trigger the execution of the AdultUnit rule unit:

Example DRL rules with controlled execution using drools.run()
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    Person(age >= 18, $name : name) from persons
  then
    System.out.println($name + " is adult");
end
package org.mypackage.myunit
unit NotAdultUnit

rule NotAdult
  when
    $p : Person(age < 18, $name : name) from persons
  then
    System.out.println($name + " is NOT adult");
    modify($p) { setAge(18); }
    drools.run( AdultUnit.class );
end

The example also uses a RuleUnitExecutor class created from the KIE base that was built from these rules and a DataSource definition of persons bound to it:

Example rule executor and data source definitions
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Person> persons = executor.newDataSource( "persons",
                                                     new Person( "John", 42 ),
                                                     new Person( "Jane", 44 ),
                                                     new Person( "Sally", 4 ) );

In this case, the example creates the DataSource definition directly from the RuleUnitExecutor class and binds it to the "persons" variable in a single statement.

The example execution code produces the following output when the relevant Person facts are inserted in the persons data source:

Example rule unit execution output
Sally is NOT adult
John is adult
Jane is adult
Sally is adult

The NotAdult rule detects a match when evaluating the person "Sally", who is under 18 years old. The rule then modifies her age to 18 and uses the drools.run( AdultUnit.class ) method to trigger the execution of the AdultUnit rule unit. The AdultUnit rule unit contains a rule that can now be executed for all of the 3 persons in the DataSource definition.

As an example of the drools.guard() method, consider the following BoxOffice class and BoxOfficeUnit rule unit class:

Example BoxOffice class
public class BoxOffice {
    private boolean open;

    public BoxOffice( boolean open ) {
        this.open = open;
    }

    public boolean isOpen() {
        return open;
    }

    public void setOpen( boolean open ) {
        this.open = open;
    }
}
Example BoxOfficeUnit rule unit class
public class BoxOfficeUnit implements RuleUnit {
    private DataSource<BoxOffice> boxOffices;

    public DataSource<BoxOffice> getBoxOffices() {
        return boxOffices;
    }
}

The example also uses the following TicketIssuerUnit rule unit class to keep selling box office tickets for the event as long as at least one box office is open. This rule unit uses DataSource definitions of persons and tickets:

Example TicketIssuerUnit rule unit class
public class TicketIssuerUnit implements RuleUnit {
    private DataSource<Person> persons;
    private DataSource<AdultTicket> tickets;

    private List<String> results;

    public TicketIssuerUnit() { }

    public TicketIssuerUnit( DataSource<Person> persons, DataSource<AdultTicket> tickets ) {
        this.persons = persons;
        this.tickets = tickets;
    }

    public DataSource<Person> getPersons() {
        return persons;
    }

    public DataSource<AdultTicket> getTickets() {
        return tickets;
    }

    public List<String> getResults() {
        return results;
    }
}

The BoxOfficeUnit rule unit contains a BoxOfficeIsOpen DRL rule that uses the drools.guard( TicketIssuerUnit.class ) method to guard the execution of the TicketIssuerUnit rule unit that distributes the event tickets, as shown in the following DRL rule examples:

Example DRL rules with controlled execution using drools.guard()
package org.mypackage.myunit;
unit TicketIssuerUnit;

rule IssueAdultTicket when
    $p: /persons[ age >= 18 ]
then
    tickets.insert(new AdultTicket($p));
end
rule RegisterAdultTicket when
    $t: /tickets
then
    results.add( $t.getPerson().getName() );
end
package org.mypackage.myunit;
unit BoxOfficeUnit;

rule BoxOfficeIsOpen
  when
    $box: /boxOffices[ open ]
  then
    drools.guard( TicketIssuerUnit.class );
end

In this example, so long as at least one box office is open, the guarded TicketIssuerUnit rule unit is active and distributes event tickets. When no more box offices are in open state, the guarded TicketIssuerUnit rule unit is prevented from being executed.

The following example class illustrates a more complete box office scenario:

Example class for the box office scenario
DataSource<Person> persons = executor.newDataSource( "persons" );
DataSource<BoxOffice> boxOffices = executor.newDataSource( "boxOffices" );
DataSource<AdultTicket> tickets = executor.newDataSource( "tickets" );

List<String> list = new ArrayList<>();
executor.bindVariable( "results", list );

// Two box offices are open:
BoxOffice office1 = new BoxOffice(true);
FactHandle officeFH1 = boxOffices.insert( office1 );
BoxOffice office2 = new BoxOffice(true);
FactHandle officeFH2 = boxOffices.insert( office2 );

persons.insert(new Person("John", 40));

// Execute `BoxOfficeIsOpen` rule, run `TicketIssuerUnit` rule unit, and execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);

assertEquals( 1, list.size() );
assertEquals( "John", list.get(0) );
list.clear();

persons.insert(new Person("Matteo", 30));

// Execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);

assertEquals( 1, list.size() );
assertEquals( "Matteo", list.get(0) );
list.clear();

// One box office is closed, the other is open:
office1.setOpen(false);
boxOffices.update(officeFH1, office1);
persons.insert(new Person("Mark", 35));
executor.run(BoxOfficeUnit.class);

assertEquals( 1, list.size() );
assertEquals( "Mark", list.get(0) );
list.clear();

// All box offices are closed:
office2.setOpen(false);
boxOffices.update(officeFH2, office2); // Guarding rule is no longer true.
persons.insert(new Person("Edson", 35));
executor.run(BoxOfficeUnit.class); // No execution

assertEquals( 0, list.size() );
8.3.6.3. Rule unit identity conflicts

In rule unit execution scenarios with guarded rule units, a rule can guard multiple rule units and at the same time a rule unit can be guarded and then activated by multiple rules. For these two-way guarding scenarios, rule units must have a clearly defined identity to avoid identity conflicts.

By default, the identity of a rule unit is the rule unit class name and is treated as a singleton class by the RuleUnitExecutor. This identification behavior is encoded in the getUnitIdentity() default method of the RuleUnit interface:

Default identity method in the RuleUnit interface
default Identity getUnitIdentity() {
    return new Identity( getClass() );
}

In some cases, you may need to override this default identification behavior to avoid conflicting identities between rule units.

For example, the following RuleUnit class contains a DataSource definition that accepts any kind of object:

Example Unit0 rule unit class
public class Unit0 implements RuleUnit {
    private DataSource<Object> input;

    public DataSource<Object> getInput() {
        return input;
    }
}

This rule unit contains the following DRL rule that guards another rule unit based on two conditions (in OOPath notation):

Example GuardAgeCheck DRL rule in the rule unit
package org.mypackage.myunit
unit Unit0

rule GuardAgeCheck
  when
    $i: /input#Integer
    $s: /input#String
  then
    drools.guard( new AgeCheckUnit($i) );
    drools.guard( new AgeCheckUnit($s.length()) );
end

The guarded AgeCheckUnit rule unit verifies the age of a set of persons. The AgeCheckUnit contains a DataSource definition of the persons to check, a minAge variable that it verifies against, and a List for gathering the results:

Example AgeCheckUnit rule unit
public class AgeCheckUnit implements RuleUnit {
    private final int minAge;
    private DataSource<Person> persons;
    private List<String> results;

    public AgeCheckUnit( int minAge ) {
        this.minAge = minAge;
    }

    public DataSource<Person> getPersons() {
        return persons;
    }

    public int getMinAge() {
        return minAge;
    }

    public List<String> getResults() {
        return results;
    }
}

The AgeCheckUnit rule unit contains the following DRL rule that performs the verification of the persons in the data source:

Example CheckAge DRL rule in the rule unit
package org.mypackage.myunit
unit AgeCheckUnit

rule CheckAge
  when
    $p : /persons{ age > minAge }
  then
    results.add($p.getName() + ">" + minAge);
end

This example creates a RuleUnitExecutor class, binds the class to the KIE base that contains these two rule units, and creates the two DataSource definitions for the same rule units:

Example executor and data source definitions
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );

DataSource<Object> input = executor.newDataSource( "input" );
DataSource<Person> persons = executor.newDataSource( "persons",
                                                     new Person( "John", 42 ),
                                                     new Person( "Sally", 4 ) );

List<String> results = new ArrayList<>();
executor.bindVariable( "results", results );

You can now insert some objects into the input data source and execute the Unit0 rule unit:

Example rule unit execution with inserted objects
ds.insert("test");
ds.insert(3);
ds.insert(4);
executor.run(Unit0.class);
Example results list from the execution
[Sally>3, John>3]

In this example, the rule unit named AgeCheckUnit is considered a singleton class and then executed only once, with the minAge variable set to 3. Both the String "test" and the Integer 4 inserted into the input data source can also trigger a second execution with the minAge variable set to 4. However, the second execution does not occur because another rule unit with the same identity has already been evaluated.

To resolve this rule unit identity conflict, override the getUnitIdentity() method in the AgeCheckUnit class to include also the minAge variable in the rule unit identity:

Modified AgeCheckUnit rule unit to override the getUnitIdentity() method
public class AgeCheckUnit implements RuleUnit {

    ...

    @Override
    public Identity getUnitIdentity() {
        return new Identity(getClass(), minAge);
    }
}

With this override in place, the previous example rule unit execution produces the following output:

Example results list from executing the modified rule unit
[John>4, Sally>3, John>3]

The rule units with minAge set to 3 and 4 are now considered two different rule units and both are executed.

8.3.6.4. Rule units used with BPMN processes

If you use a DRL rule unit as part of a business rule task in a Business Process Model and Notation (BPMN) process in your Kogito project, you do not need to create an explicit rule unit class that implements the RuleUnit interface. Instead, you designate the rule unit in the DRL file as usual and specify the rule unit in the format unit:PACKAGE_NAME.UNIT_NAME in the implementation details for the business rule task in the BPMN process. When you build the project, the business process implicitly declares the rule unit as part of the business rule task to execute the DRL file.

For example, the following is a DRL file with a rule unit designation:

Example rule unit declaration in a DRL file
package org.mypackage.myunit
unit AdultUnit

rule Adult
  when
    $p : /persons[age >= adultAge]
  then
    System.out.println($p.getName() + " is adult and greater than " + adultAge);
end

In the relevant business process in a BPMN 2.0 process modeler, you select the business rule task and for the Implementation/Execution property, you set the rule language to DRL and the rule flow group to unit:org.mypackage.AdultUnit.

This rule unit syntax in the Rule Flow Group field specifies that you are using the org.mypackage.AdultUnit rule unit instead of a traditional rule flow group. This is the rule unit that you referenced in the example DRL file. When you build the project, the business process implicitly declares the rule unit as part of the business rule task to execute the DRL file.

8.4. Phreak rule algorithm in the Drools engine

The Drools engine in Kogito uses the Phreak algorithm for rule evaluation. Phreak evolved from the Rete algorithm, including the enhanced Rete algorithm ReteOO that was introduced in previous versions of Drools for object-oriented systems. Overall, Phreak is more scalable than Rete and ReteOO, and is faster in large systems.

While Rete is considered eager (immediate rule evaluation) and data oriented, Phreak is considered lazy (delayed rule evaluation) and goal oriented. The Rete algorithm performs many actions during the insert, update, and delete actions in order to find partial matches for all rules. This eagerness of the Rete algorithm during rule matching requires a lot of time before eventually executing rules, especially in large systems. With Phreak, this partial matching of rules is delayed deliberately to handle large amounts of data more efficiently.

The Phreak algorithm adds the following set of enhancements to previous Rete algorithms:

  • Three layers of contextual memory: Node, segment, and rule memory types

  • Rule-based, segment-based, and node-based linking

  • Lazy (delayed) rule evaluation

  • Stack-based evaluations with pause and resume

  • Isolated rule evaluation

  • Set-oriented propagations

8.4.1. Rule evaluation in Phreak

When the Drools engine starts, all rules are considered to be unlinked from pattern-matching data that can trigger the rules. At this stage, the Phreak algorithm in the Drools engine does not evaluate the rules. The insert, update, and delete actions are queued, and Phreak uses a heuristic, based on the rule most likely to result in execution, to calculate and select the next rule for evaluation. When all the required input values are populated for a rule, the rule is considered to be linked to the relevant pattern-matching data. Phreak then creates a goal that represents this rule and places the goal into a priority queue that is ordered by rule salience. Only the rule for which the goal was created is evaluated, and other potential rule evaluations are delayed. While individual rules are evaluated, node sharing is still achieved through the process of segmentation.

Unlike the tuple-oriented Rete, the Phreak propagation is collection oriented. For the rule that is being evaluated, the Drools engine accesses the first node and processes all queued insert, update, and delete actions. The results are added to a set, and the set is propagated to the child node. In the child node, all queued insert, update, and delete actions are processed, adding the results to the same set. The set is then propagated to the next child node and the same process repeats until it reaches the terminal node. This cycle creates a batch process effect that can provide performance advantages for certain rule constructs.

The linking and unlinking of rules happens through a layered bit-mask system, based on network segmentation. When the rule network is built, segments are created for rule network nodes that are shared by the same set of rules. A rule is composed of a path of segments. In case a rule does not share any node with any other rule, it becomes a single segment.

A bit-mask offset is assigned to each node in the segment. Another bit mask is assigned to each segment in the path of the rule according to these requirements:

  • If at least one input for a node exists, the node bit is set to the on state.

  • If each node in a segment has the bit set to the on state, the segment bit is also set to the on state.

  • If any node bit is set to the off state, the segment is also set to the off state.

  • If each segment in the path of the rule is set to the on state, the rule is considered linked, and a goal is created to schedule the rule for evaluation.

The same bit-mask technique is used to track modified nodes, segments, and rules. This tracking ability enables an already linked rule to be unscheduled from evaluation if it has been modified since the evaluation goal for it was created. As a result, no rules can ever evaluate partial matches.

This process of rule evaluation is possible in Phreak because, as opposed to a single unit of memory in Rete, Phreak has three layers of contextual memory with node, segment, and rule memory types. This layering enables much more contextual understanding during the evaluation of a rule.

LayeredMemory
Figure 129. Phreak three-layered memory system

The following examples illustrate how rules are organized and evaluated in this three-layered memory system in Phreak.

Example 1: A single rule (R1) with three patterns: A, B and C. The rule forms a single segment, with bits 1, 2, and 4 for the nodes. The single segment has a bit offset of 1.

segment1
Figure 130. Example 1: Single rule

Example 2: Rule R2 is added and shares pattern A.

segment2
Figure 131. Example 2: Two rules with pattern sharing

Pattern A is placed in its own segment, resulting in two segments for each rule. Those two segments form a path for their respective rules. The first segment is shared by both paths. When pattern A is linked, the segment becomes linked. The segment then iterates over each path that the segment is shared by, setting the bit 1 to on. If patterns B and C are later turned on, the second segment for path R1 is linked, and this causes bit 2 to be turned on for R1. With bit 1 and bit 2 turned on for R1, the rule is now linked and a goal is created to schedule the rule for later evaluation and execution.

When a rule is evaluated, the segments enable the results of the matching to be shared. Each segment has a staging memory to queue all inserts, updates, and deletes for that segment. When R1 is evaluated, the rule processes pattern A, and this results in a set of tuples. The algorithm detects a segmentation split, creates peered tuples for each insert, update, and delete in the set, and adds them to the R2 staging memory. Those tuples are then merged with any existing staged tuples and are executed when R2 is eventually evaluated.

Example 3: Rules R3 and R4 are added and share patterns A and B.

segment3
Figure 132. Example 3: Three rules with pattern sharing

Rules R3 and R4 have three segments and R1 has two segments. Patterns A and B are shared by R1, R3, and R4, while pattern D is shared by R3 and R4.

Example 4: A single rule (R1) with a subnetwork and no pattern sharing.

segment4
Figure 133. Example 4: Single rule with a subnetwork and no pattern sharing

Subnetworks are formed when a Not, Exists, or Accumulate node contains more than one element. In this example, the element B not( C ) forms the subnetwork. The element not( C ) is a single element that does not require a subnetwork and is therefore merged inside of the Not node. The subnetwork uses a dedicated segment. Rule R1 still has a path of two segments and the subnetwork forms another inner path. When the subnetwork is linked, it is also linked in the outer segment.

Example 5: Rule R1 with a subnetwork that is shared by rule R2.

segment5
Figure 134. Example 5: Two rules, one with a subnetwork and pattern sharing

The subnetwork nodes in a rule can be shared by another rule that does not have a subnetwork. This sharing causes the subnetwork segment to be split into two segments.

Constrained Not nodes and Accumulate nodes can never unlink a segment, and are always considered to have their bits turned on.

The Phreak evaluation algorithm is stack based instead of method-recursion based. Rule evaluation can be paused and resumed at any time when a StackEntry is used to represent the node currently being evaluated.

When a rule evaluation reaches a subnetwork, a StackEntry object is created for the outer path segment and the subnetwork segment. The subnetwork segment is evaluated first, and when the set reaches the end of the subnetwork path, the segment is merged into a staging list for the outer node that the segment feeds into. The previous StackEntry object is then resumed and can now process the results of the subnetwork. This process has the added benefit, especially for Accumulate nodes, that all work is completed in a batch, before propagating to the child node.

The same stack system is used for efficient backward chaining. When a rule evaluation reaches a query node, the evaluation is paused and the query is added to the stack. The query is then evaluated to produce a result set, which is saved in a memory location for the resumed StackEntry object to pick up and propagate to the child node. If the query itself called other queries, the process repeats, while the current query is paused and a new evaluation is set up for the current query node.

8.4.1.1. Rule evaluation with forward and backward chaining

The Drools engine in Kogito is a hybrid reasoning system that uses both forward chaining and backward chaining to evaluate rules. A forward-chaining rule system is a data-driven system that starts with a fact in the working memory of the Drools engine and reacts to changes to that fact. When objects are inserted into working memory, any rule conditions that become true as a result of the change are scheduled for execution by the agenda.

In contrast, a backward-chaining rule system is a goal-driven system that starts with a conclusion that the Drools engine attempts to satisfy, often using recursion. If the system cannot reach the conclusion or goal, it searches for subgoals, which are conclusions that complete part of the current goal. The system continues this process until either the initial conclusion is satisfied or all subgoals are satisfied.

The following diagram illustrates how the Drools engine evaluates rules using forward chaining overall with a backward-chaining segment in the logic flow:

RuleEvaluation
Figure 135. Rule evaluation logic using forward and backward chaining

8.4.2. Rule base configuration

<@Edoardo, see this section.>

Kogito contains a RuleBaseConfiguration.java object that you can use to configure exception handler settings, multithreaded execution, and sequential mode in the Drools engine.

For the rule base configuration options,

see the Kogito RuleBaseConfiguration.java page in GitHub.

The following rule base configuration options are available for the Drools engine:

drools.consequenceExceptionHandler

When configured, this system property defines the class that manages the exceptions thrown by rule consequences. You can use this property to specify a custom exception handler for rule evaluation in the Drools engine.

Default value: org.drools.core.runtime.rule.impl.DefaultConsequenceExceptionHandler

You can specify the custom exception handler using one of the following options:

  • Specify the exception handler in a system property:

    drools.consequenceExceptionHandler=org.drools.core.runtime.rule.impl.MyCustomConsequenceExceptionHandler
  • Specify the exception handler while creating the KIE base programmatically:

    KieServices ks = KieServices.Factory.get();
    KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration(); kieBaseConf.setOption(ConsequenceExceptionHandlerOption.get(MyCustomConsequenceExceptionHandler.class));
    KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
drools.multithreadEvaluation

When enabled, this system property enables the Drools engine to evaluate rules in parallel by dividing the Phreak rule network into independent partitions. You can use this property to increase the speed of rule evaluation for specific rule bases.

Default value: false

You can enable multithreaded evaluation using one of the following options:

  • Enable the multithreaded evaluation system property:

    drools.multithreadEvaluation=true
  • Enable multithreaded evaluation while creating the KIE base programmatically:

    KieServices ks = KieServices.Factory.get();
    KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration();
    kieBaseConf.setOption(MultithreadEvaluationOption.YES);
    KieBase kieBase = kieContainer.newKieBase(kieBaseConf);

Rules that use queries, salience, or agenda groups are currently not supported by the parallel Drools engine. If these rule elements are present in the KIE base, the compiler emits a warning and automatically switches back to single-threaded evaluation. However, in some cases, the Drools engine might not detect the unsupported rule elements and rules might be evaluated incorrectly. For example, the Drools engine might not detect when rules rely on implicit salience given by rule ordering inside the DRL file, resulting in incorrect evaluation due to the unsupported salience attribute.

drools.sequential

When enabled, this system property enables sequential mode in the Drools engine. In sequential mode, the Drools engine evaluates rules one time in the order that they are listed in the Drools engine agenda without regard to changes in the working memory. This means that the Drools engine ignores any insert, modify, or update statements in rules and executes rules in a single sequence. As a result, rule execution may be faster in sequential mode, but important updates may not be applied to your rules. You can use this property if you use stateless KIE sessions and you do not want the execution of rules to influence subsequent rules in the agenda. Sequential mode applies to stateless KIE sessions only.

Default value: false

You can enable sequential mode using one of the following options:

  • Enable the sequential mode system property:

    drools.sequential=true
  • Enable sequential mode while creating the KIE base programmatically:

    KieServices ks = KieServices.Factory.get();
    KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration();
    kieBaseConf.setOption(SequentialOption.YES);
    KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
  • Enable sequential mode in the KIE module descriptor file (kmodule.xml) for a specific Kogito project:

    <kmodule>
      ...
      <kbase name="KBase2" default="false" sequential="true" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1">
        ...
      </kbase>
      ...
    </kmodule>

8.4.3. Sequential mode in Phreak

<@Edoardo, see this section. This was another hot topic in 7.x.>

Sequential mode is an advanced rule base configuration in the Drools engine, supported by Phreak, that enables the Drools engine to evaluate rules one time in the order that they are listed in the Drools engine agenda without regard to changes in the working memory. In sequential mode, the Drools engine ignores any insert, modify, or update statements in rules and executes rules in a single sequence. As a result, rule execution may be faster in sequential mode, but important updates may not be applied to your rules.

Sequential mode applies to only stateless KIE sessions because stateful KIE sessions inherently use data from previously invoked KIE sessions. If you use a stateless KIE session and you want the execution of rules to influence subsequent rules in the agenda, then do not enable sequential mode. Sequential mode is disabled by default in the Drools engine.

To enable sequential mode, use one of the following options:

  • Set the system property drools.sequential to true.

  • Enable sequential mode while creating the KIE base programmatically:

    KieServices ks = KieServices.Factory.get();
    KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration();
    kieBaseConf.setOption(SequentialOption.YES);
    KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
  • Enable sequential mode in the KIE module descriptor file (kmodule.xml) for a specific Kogito project:

    <kmodule>
      ...
      <kbase name="KBase2" default="false" sequential="true" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1">
        ...
      </kbase>
      ...
    </kmodule>

To configure sequential mode to use a dynamic agenda, use one of the following options:

  • Set the system property drools.sequential.agenda to dynamic.

  • Set the sequential agenda option while creating the KIE base programmatically:

    KieServices ks = KieServices.Factory.get();
    KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration();
    kieBaseConf.setOption(SequentialAgendaOption.DYNAMIC);
    KieBase kieBase = kieContainer.newKieBase(kieBaseConf);

When you enable sequential mode, the Drools engine evaluates rules in the following way:

  1. Rules are ordered by salience and position in the rule set.

  2. An element for each possible rule match is created. The element position indicates the execution order.

  3. Node memory is disabled, with the exception of the right-input object memory.

  4. The left-input adapter node propagation is disconnected and the object with the node is referenced in a Command object. The Command object is added to a list in the working memory for later execution.

  5. All objects are asserted, and then the list of Command objects is checked and executed.

  6. All matches that result from executing the list are added to elements based on the sequence number of the rule.

  7. The elements that contain matches are executed in a sequence. If you set a maximum number of rule executions, the Drools engine activates no more than that number of rules in the agenda for execution.

In sequential mode, the LeftInputAdapterNode node creates a Command object and adds it to a list in the working memory of the Drools engine. This Command object contains references to the LeftInputAdapterNode node and the propagated object. These references stop any left-input propagations at insertion time so that the right-input propagation never needs to attempt to join the left inputs. The references also avoid the need for the left-input memory.

All nodes have their memory turned off, including the left-input tuple memory, but excluding the right-input object memory. After all the assertions are finished and the right-input memory of all the objects is populated, the Drools engine iterates over the list of LeftInputAdatperNode Command objects. The objects propagate down the network, attempting to join the right-input objects, but they are not retained in the left input.

The agenda with a priority queue to schedule the tuples is replaced by an element for each rule. The sequence number of the RuleTerminalNode node indicates the element where to place the match. After all Command objects have finished, the elements are checked and existing matches are executed. To improve performance, the first and the last populated cell in the elements are retained.

When the network is constructed, each RuleTerminalNode node receives a sequence number based on its salience number and the order in which it was added to the network.

The right-input node memories are typically hash maps for fast object deletion. Because object deletions are not supported, Phreak uses an object list when the values of the object are not indexed. For a large number of objects, indexed hash maps provide a performance increase. If an object has only a few instances, Phreak uses an object list instead of an index.

8.4.4. Property-change settings and listeners for fact types

<@Edoardo, see this section. Another hot topic in 7.x and one we link to from the DRL content.>

By default, the Drools engine does not re-evaluate all fact patterns for fact types each time a rule is triggered, but instead reacts only to modified properties that are constrained or bound inside a given pattern. For example, if a rule calls modify() as part of the rule actions but the action does not generate new data in the KIE base, the Drools engine does not automatically re-evaluate all fact patterns because no data was modified. This property reactivity behavior prevents unwanted recursions in the KIE base and results in more efficient rule evaluation. This behavior also means that you do not always need to use the no-loop rule attribute to avoid infinite recursion.

You can modify or disable this property reactivity behavior with the following KnowledgeBuilderConfiguration options, and then use a property-change setting in your Java class or DRL files to fine-tune property reactivity as needed:

  • ALWAYS: (Default) All types are property reactive, but you can disable property reactivity for a specific type by using the @classReactive property-change setting.

  • ALLOWED: No types are property reactive, but you can enable property reactivity for a specific type by using the @propertyReactive property-change setting.

  • DISABLED: No types are property reactive. All property-change listeners are ignored.

Example property reactivity setting in KnowledgeBuilderConfiguration
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
config.setOption(PropertySpecificOption.ALLOWED);
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);

Alternatively, you can update the drools.propertySpecific system property in the standalone.xml file of your Kogito distribution:

Example property reactivity setting in system properties
<system-properties>
  ...
  <property name="drools.propertySpecific" value="ALLOWED"/>
  ...
</system-properties>

The Drools engine supports the following property-change settings and listeners for fact classes or declared DRL fact types:

@classReactive

If property reactivity is set to ALWAYS in the Drools engine (all types are property reactive), this tag disables the default property reactivity behavior for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to re-evaluate all fact patterns for the specified fact type each time the rule is triggered, instead of reacting only to modified properties that are constrained or bound inside a given pattern.

Example: Disable default property reactivity in a DRL type declaration
declare Person
  @classReactive
    firstName : String
    lastName : String
end
Example: Disable default property reactivity in a Java class
@classReactive
public static class Person {
    private String firstName;
    private String lastName;
}
@propertyReactive

If property reactivity is set to ALLOWED in the Drools engine (no types are property reactive unless specified), this tag enables property reactivity for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to react only to modified properties that are constrained or bound inside a given pattern for the specified fact type, instead of re-evaluating all fact patterns for the fact each time the rule is triggered.

Example: Enable property reactivity in a DRL type declaration (when reactivity is disabled globally)
declare Person
  @propertyReactive
    firstName : String
    lastName : String
end
Example: Enable property reactivity in a Java class (when reactivity is disabled globally)
@propertyReactive
public static class Person {
    private String firstName;
    private String lastName;
}
@watch

This tag enables property reactivity for additional properties that you specify in-line in fact patterns in DRL rules. This tag is supported only if property reactivity is set to ALWAYS in the Drools engine, or if property reactivity is set to ALLOWED and the relevant fact type uses the @propertyReactive tag. You can use this tag in DRL rules to add or exclude specific properties in fact property reactivity logic.

Default parameter: None

Supported parameters: Property name, * (all), ! (not), !* (no properties)

<factPattern> @watch ( <property> )
Example: Enable or disable property reactivity in fact patterns
// Listens for changes in both `firstName` (inferred) and `lastName`:
Person(firstName == $expectedFirstName) @watch( lastName )

// Listens for changes in all properties of the `Person` fact:
Person(firstName == $expectedFirstName) @watch( * )

// Listens for changes in `lastName` and explicitly excludes changes in `firstName`:
Person(firstName == $expectedFirstName) @watch( lastName, !firstName )

// Listens for changes in all properties of the `Person` fact except `age`:
Person(firstName == $expectedFirstName) @watch( *, !age )

// Excludes changes in all properties of the `Person` fact (equivalent to using `@classReactivity` tag):
Person(firstName == $expectedFirstName) @watch( !* )

The Drools engine generates a compilation error if you use the @watch tag for properties in a fact type that uses the @classReactive tag (disables property reactivity) or when property reactivity is set to ALLOWED in the Drools engine and the relevant fact type does not use the @propertyReactive tag. Compilation errors also arise if you duplicate properties in listener annotations, such as @watch( firstName, ! firstName ).

@propertyChangeSupport

For facts that implement support for property changes as defined in the JavaBeans Specification, this tag enables the Drools engine to monitor changes in the fact properties.

Example: Declare property change support in JavaBeans object
declare Person
    @propertyChangeSupport
end

8.5. Drools engine queries and live queries

<@Edoardo, so verdict on this?>

You can use queries with the Drools engine to retrieve fact sets based on fact patterns as they are used in rules. The patterns might also use optional parameters.

To use queries with the Drools engine, you add the query definitions in DRL files and then obtain the matching results in your application code. While a query iterates over a result collection, you can use any identifier that is bound to the query to access the corresponding fact or fact field by calling the get() method with the binding variable name as the argument. If the binding refers to a fact object, you can retrieve the fact handle by calling getFactHandle() with the variable name as the parameter.

QueryResults
Figure 136. QueryResults
QueryResultsRow
Figure 137. QueryResultsRow
Example query definition in a DRL file
query "people under the age of 21"
    $person : Person( age < 21 )
end
Example application code to obtain and iterate over query results
QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );

System.out.println( "These people are under the age of 21:" );

for ( QueryResultsRow row : results ) {
    Person person = ( Person ) row.get( "person" );
    System.out.println( person.getName() + "\n" );
}

Invoking queries and processing the results by iterating over the returned set can be difficult when you are monitoring changes over time. To alleviate this difficulty with ongoing queries, Kogito provides live queries, which use an attached listener for change events instead of returning an iterable result set. Live queries remain open by creating a view and publishing change events for the contents of this view.

To activate a live query, start your query with parameters and monitor changes in the resulting view. You can use the dispose() method to terminate the query and discontinue this reactive scenario.

Example query definition in a DRL file
query colors(String $color1, String $color2)
    TShirt(mainColor = $color1, secondColor = $color2, $price: manufactureCost)
end
Example application code with an event listener and a live query
final List updated = new ArrayList();
final List removed = new ArrayList();
final List added = new ArrayList();

ViewChangedEventListener listener = new ViewChangedEventListener() {
 public void rowUpdated(Row row) {
  updated.add( row.get( "$price" ) );
 }

 public void rowRemoved(Row row) {
  removed.add( row.get( "$price" ) );
 }

 public void rowAdded(Row row) {
  added.add( row.get( "$price" ) );
 }
};

// Open the live query:
LiveQuery query = ksession.openLiveQuery( "colors",
                                          new Object[] { "red", "blue" },
                                          listener );
...
...

// Terminate the live query:
query.dispose()

For more live query examples, see Glazed Lists examples for Drools Live Queries.

8.6. Drools engine event listeners and debug logging

<@Edoardo, see this section.>

In Kogito, you can add or remove listeners for Drools engine events, such as fact insertions and rule executions. With Drools engine event listeners, you can be notified of Drools engine activity and separate your logging and auditing work from the core of your application.

The Drools engine supports the following default event listeners for the agenda and working memory:

  • AgendaEventListener

  • WorkingMemoryEventListener

WorkingMemoryEventManager
Figure 138. WorkingMemoryEventManager

For each event listener, the Drools engine also supports the following specific events that you can specify to be monitored:

  • MatchCreatedEvent

  • MatchCancelledEvent

  • BeforeMatchFiredEvent

  • AfterMatchFiredEvent

  • AgendaGroupPushedEvent

  • AgendaGroupPoppedEvent

  • ObjectInsertEvent

  • ObjectDeletedEvent

  • ObjectUpdatedEvent

  • ProcessCompletedEvent

  • ProcessNodeLeftEvent

  • ProcessNodeTriggeredEvent

  • ProcessStartEvent

For example, the following code uses a DefaultAgendaEventListener listener attached to a KIE session and specifies the AfterMatchFiredEvent event to be monitored. The code prints pattern matches after the rules are executed (fired):

Example code to monitor and print AfterMatchFiredEvent events in the agenda
ksession.addEventListener( new DefaultAgendaEventListener() {
   public void afterMatchFired(AfterMatchFiredEvent event) {
       super.afterMatchFired( event );
       System.out.println( event );
   }
});

The Drools engine also supports the following agenda and working memory event listeners for debug logging:

  • DebugAgendaEventListener

  • DebugRuleRuntimeEventListener

These event listeners implement the same supported event-listener methods and include a debug print statement by default. You can add a specific supported event to be monitored and documented, or monitor all agenda or working memory activity.

For example, the following code uses the DebugRuleRuntimeEventListener event listener to monitor and print all working memory events:

Example code to monitor and print all working memory events
ksession.addEventListener( new DebugRuleRuntimeEventListener() );

8.6.1. Configuring a logging utility in the Drools engine

<@Edoardo, see this section.>

The Drools engine uses the Java logging API SLF4J for system logging. You can use one of the following logging utilities with the Drools engine to investigate Drools engine activity, such as for troubleshooting or data gathering:

  • Logback

  • Apache Commons Logging

  • Apache Log4j

  • java.util.logging package

Procedure

For the logging utility that you want to use, add the relevant dependency to your Maven project or save the relevant XML configuration file in the org.drools package of your Kogito distribution:

Example Maven dependency for Logback
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>${logback.version}</version>
</dependency>
Example logback.xml configuration file in org.drools package
<configuration>
  <logger name="org.drools" level="debug"/>
  ...
<configuration>
Example log4j.xml configuration file in org.drools package
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  <category name="org.drools">
    <priority value="debug" />
  </category>
  ...
</log4j:configuration>
If you are developing for an ultra light environment, use the slf4j-nop or slf4j-simple logger.

Kogito Release Notes

Coming soon