Migration Guide

Migrate to Drools 8

This guide explains how to migrate Drools 7 projects to Drools 8 projects.

Firstly, Drools 8 supports Drools 7 APIs and DRL syntax, so basically you don’t need to change your Java codes and rule assets. However, there are some cautions in pom.xml.

Typical Drools 6 or early 7 projects have pom dependencies like this.

Traditional pom.xml
  </dependencied>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-core</artifactId>
      <version>${version.org.drools}</version>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-compiler</artifactId>
      <version>${version.org.drools}</version>
    </dependency>
  </dependencied>

But since Drools 7.45.0, drools-engine and drools-engine-classic have been introduced as aggregator dependencies. The dependencies drools-engine-classic, drools-mvel are deprecated starting with Drools 8. Hence, use drools-engine.

    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-engine</artifactId>
      <version>${version.org.drools}</version>
    </dependency>

Drools 8 made some module refactoring, so you may find some difficulty in collecting dependencies. This aggregator dependency would help.

Rule Unit

As introduced in First Rule Project, using Rule Units is a recommended style for implementing rules in Drools 8. It will require some modifications to your codebase, but if you are going to develop cloud-native applications, Rule Units are strongly recommended, because Kogito also works with Rule Units.

Rule Unit pom.xml
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-ruleunits-engine</artifactId>
      <version>${version.org.drools}</version>
    </dependency>

drools-ruleunits-engine is required for Rule Unit APIs.

Regarding related Java API usage, in Drools 7, you create a KieSession from rule assets. Then insert facts and fire rules.

Traditional Java code
        KieServices ks = KieServices.Factory.get();
        KieContainer kcontainer = ks.getKieClasspathContainer();
        KieBase kbase = kcontainer.getKieBase();
        KieSession ksession = kbase.newKieSession();
        try {
            ksession.insert(new Measurement("color", "red"));
            ksession.insert(new Measurement("color", "green"));

            ksession.fireAllRules();
        } finally {
            ksession.dispose();
        }

Using Rule Unit APIs, the resulting Java snippet would be like this:

Rule Unit Java code
        MeasurementUnit measurementUnit = new MeasurementUnit();
        RuleUnitInstance<MeasurementUnit> instance = RuleUnitProvider.get().createRuleUnitInstance(measurementUnit);
        try {
            measurementUnit.getMeasurements().add(new Measurement("color", "red"));
            measurementUnit.getMeasurements().add(new Measurement("color", "green"));

            instance.fire();
        } finally {
            instance.dispose();
        }

In Rule Unit, instantiate RuleUnitInstance instead of KieSession. Add facts to DataSource property instead of insert. Generic KIE API calls (e.g. KieServices, KieContainer, KieBase …​ ) are no longer needed. Instead, one more class Unit has to be defined.

Rule Unit Class
public class MeasurementUnit implements RuleUnitData {

    private final DataStore<Measurement> measurements;
    private final Set<String> controlSet = new HashSet<>();

    public MeasurementUnit() {
        this(DataSource.createStore());
    }

    public MeasurementUnit(DataStore<Measurement> measurements) {
        this.measurements = measurements;
    }

    public DataStore<Measurement> getMeasurements() {
        return measurements;
    }

    public Set<String> getControlSet() {
        return controlSet;
    }
}

This Unit class associates rules (DRL) and inserted facts (DataSource), so it helps you to manage the relationship.

DRL syntax style also changes.

Traditional DRL
global java.util.Set controlSet;

rule "will execute per each Measurement having ID color"
when
	Measurement( id == "color", $colorVal : val )
then
	controlSet.add($colorVal);
end
Rule Unit DRL
unit MeasurementUnit;

rule "will execute per each Measurement having ID color"
when
	/measurements[ id == "color", $colorVal : val ]
then
	controlSet.add($colorVal);
end

Firstly, declare unit. Don’t need to declare global, because controlSet is a field of MeasurementUnit. Traditional global usage would become the Unit’s field. In addition, the main difference is OOPath notation in LHS. It is described in detail at Rule conditions in DRL. You should change from the class name (Measurement) to the DataSource property name in the Unit class (measurements).

In addition, you need to take care of the insert in RHS. The insert method inserts a fact into a "DEFAULT" entry-point, but in Rule Unit use cases, you must specify a DataSource where you want to insert the fact. Hence, you must call DataStore.add or DataStream.append instead of insert.

Traditional DRL
rule "Insert another Measurement"
when
	Measurement( id == "color", $colorVal : val )
then
	insert(new Measurement("monochrome", "false"));
end
Rule Unit DRL
unit MeasurementUnit;

rule "Insert another Measurement"
when
	/measurements[ id == "color", $colorVal : val ]
then
	measurements.add(new Measurement("monochrome", "false"));
end

Note that you don’t need to re-write update, modify and delete, because they work for a fact in its DataSource expectedly.

These changes are not negligible, but hopefully, as this migration guide demonstrated, they are not so difficult to implement either.

Migration from non-executable model to executable model

The drools-engine-classic and drools-mvel execute rules with non executable model. drools-engine executes rules with executable model. For more details about the executable model, see Executable rule models.

As mentioned, drools-engine-classic and drools-mvel are deprecated, it is recommended to migrate from a non-executable model to an executable model by changing dependencies.

In general, from an external perspective, rule evaluation and rule consequence execution results can be expected to be the same using either the non-executable model or the executable model. You may however encounter a few edge cases during compile time or runtime. If you find any issues, please report them to the Drools community. We have already identified some small known differences, reported below.

Invalid cast

When you specify dialect "mvel" in a rule, the non-executable model is tolerant of invalid type cast, because of type coercion behavior in MVEL. For example,

global MyUtils myUtils;

rule Rule1
dialect "mvel"
when
  $p : Person()
then
  myUtils.doWork((String) $p.age )));
end

This rule can be built with non-executable model even if $p.age is int which is not acceptable in Java syntax.

With executable model, the rule fails with a build error.

Cannot cast from int to String

In this case, the rule can be easily fixed by using valid Java syntax. For example,

 ...
then
  myUtils.doWork(java.util.Objects.toString( $p.age ));
end

Generics support

non-executable model is tolerant of generics type. For example,

rule Rule1
  no-loop true
  when
    $fact : Fact( str == "ADD", $val : "100")
  then
    modify($fact) {
      bdList.add($val);
    }
end

non-executable model allows to add a String object to bdList even if Fact.bdList is List<BigDecimal> which is not acceptable in Java syntax.

With executable model, the rule fails with a build error.

The method add(BigDecimal) in the type List<BigDecimal> is not applicable for the arguments (String)

In this case, the rule can be easily fixed by using valid Java syntax. For example,

 ...
then
  modify($fact) {
    bdList.add(new BigDecimal($val));
  }
end

Invalid coercion

For example, Java allows coercion from int to long. However, Java doesn’t allow coercion from int to Long. If you have the rule to call the setWrapperLong method which accepts Long.

rule Rule1
dialect "mvel"
when
  $f : Fact()
then
  $f.setWrapperLong(10);
end

non-executable model coerces 10 to Long, so it doesn’t throw an Exception. However, the executable model throws a build error.

The method setWrapperLong(Long) in the type Fact is not applicable for the arguments (int)

In this case, the rule can be easily fixed by using valid Java syntax. For example,

 ...
then
  $f.setWrapperLong(10L);
end

Generics return type resolution

Assuming you have classes below:

parent class
public abstract class Vehicle<TEngine extends Engine> {
        // ...

	public abstract TEngine getEngine();

	public TEngine getMotor() {
		return getEngine();
	}
sub class
public class DieselCar extends Vehicle<DieselEngine> {
	private final DieselEngine engine;

	public DieselCar(String maker, String model, int kw, boolean adBlueRequired) {
		super(maker, model);
		this.engine = new DieselEngine(kw, adBlueRequired);
	}

	@Override
	public DieselEngine getEngine() {
		return engine;
	}
}
public class DieselEngine extends Engine {

	private final boolean adBlueRequired;
rule
rule Rule1
	when
		$v : DieselCar(motor.adBlueRequired == true)
	then
		// do something
end

non-executable model can dynamically resolve that motor is DieselEngine so the rule works. However, the executable model resolves motor to TEngine, so a build error is thrown.

Unknown field adBlueRequired on TEngine

In this case, the rule can be fixed by specifying the subtype with the # operator. For example,

	when
		$v : DieselCar(motor#DieselEngine.adBlueRequired == true)

KIE Server

KIE Server is retired and no longer a component of Drools 8. If your system is working on KIE Server with Drools 7, consider migration to Kogito.

While KIE Server hosts multiple kjar containers, One Kogito instance hosts one domain service. Hence, you would create Kogito project per kjar container, which would be microservice style.

You can find the detailed migration steps in the next sections, Overview of migration from Drools v7 to Kogito microservices.

Business Central

Business Central is retired and no longer a component of Drools 8. For asset management, the usual version control system is recommended, for example, git. For editors, VS Code Kogito extension is recommended.

BPMN integration / Rule flow

BPMN integration and Rule flow can be achieved by Kogito.

Overview of migration from Drools v7 to Kogito microservices

You can migrate the decision service artifacts that you developed in Drools v7 to Kogito microservices. Kogito currently supports migration for the following types of decision services:

  • Decision Model and Notation (DMN) models: You migrate DMN-based decision services by moving the DMN resources from KJAR artifacts to the respective Kogito archetype.

  • Predictive Model Markup Language (PMML) models: You migrate PMML-based prediction and prediction services by moving the PMML resources from KJAR artifacts to the respective Kogito archetype.

  • Drools Rule Language (DRL) rules: You migrate the DRL-based decision services by enclosing them in a Quarkus REST endpoint. This approach of migration enables you to use major Quarkus features, such as hot reload and native compilation. The Quarkus features and the programming model of Kogito enable the automatic generation of the Quarkus REST endpoints for implementation in your applications and services.

Migration of a DMN service from Drools v7 to a Kogito microservice

You can migrate DMN-based decision services from Drools v7 to Kogito microservices by moving the DMN resources from KJAR artifacts to the respective Kogito project. In the Kogito microservices, some of the KIE v7 features are no longer required.

Major changes and migration considerations

The following table describes the major changes and features that affect migration from the KIE Server API and KJAR to Kogito deployments:

Table 1. DMN migration considerations
Feature In KIE Server API In Kogito artifact

DMN models

stored in src/main/resources of KJAR.

copy as is to src/main/resources.

Object models (POJOs) required for KIE Server generic marshalling

managed using Data Model object editor in Business Central.

object model editing is no longer required.

DMNRuntimeListener

configured using a system property or kmodule.xml file.

must be configured using CDI, by annotating the DMNRuntimeEventListener with CDI’s @ApplicationScope annotation.

Other configuration options

configured using system property or kmodule.xml file.

except DMNRuntimeEventListener, only default values are considered and no override of configuration is supported.

KIE Server Client API

used in conjunction with object models to interact with a KJAR that is deployed on the KIE Server.

for object models, this feature is no longer required.

You can select your own choice of REST library.

REST API

when a KJAR is deployed on KIE Server, the applications interacting with specific DMN model endpoint, use the same API on Kogito deployment.

advanced support for specific DMN model generation.

Test scenarios

run with a JUnit activator.

analogous JUnit activator is available on Kogito.

The features that are not mentioned in the previous table are either not supported or not required in a cloud-native Kogito deployment.

Migration strategy

When migrating a DMN project to a Kogito project, first you can migrate external applications that are interacting with decision services on the KIE Server. You can use the REST endpoints that are specific to DMN models. After using the REST endpoints, you can migrate the external applications from the KIE Server to a Kogito deployment. For additional information about specific REST endpoints to DMN models, you can reference the REST endpoints for specific DMN models blog post.

The migration strategy includes the following steps:

  1. Migrate existing external applications from the generic KIE Server API to a specific DMN REST endpoint using the KIE Server.

  2. Migrate a KJAR that is deployed on the KIE Server to a Kogito microservice.

  3. Deploy the Kogito microservice using OpenShift.

  4. Reconnect the external application and migrate the REST API consumption from the specific DMN REST endpoint to the Kogito deployment.

Migrating external applications to REST endpoints specific to DMN models

To migrate a DMN project to a Kogito deployment, first you can migrate external applications that use specific DMN REST endpoints to interact with decision services on the KIE Server.

Procedure
  1. If you are using the REST endpoints in your external application, retrieve the Swagger or OAS specification file of the KJAR using GET /server/containers/{containerId}/dmn/openapi.json (|.yaml) endpoint.

    For additional information about REST endpoints for specific DMN models, you can reference REST endpoints for specific DMN models blog post.

  2. In your external application, select the Java or JDK library to interact with the decision services. You can interact with the decision services using the REST endpoint for the specific KJAR.

The KIE Server Client Java API is not supported in the migration to a Kogito deployment.

Migrating a DMN model KJAR to a Kogito microservice

After migrating your external application, you need to migrate a KJAR that is specific to a DMN model to a Kogito microservice.

Procedure
  1. Create a Maven project for your Kogito microservice.

    For additional information about the procedure about creating a Maven project, you can reference Modeling and development of decision services: DMN with Kogito blog post.

    The Maven project creates Kogito artifacts.

  2. Copy the DMN models from the src/main/resources folder of the KJAR to the src/main/resources folder of the Kogito artifact.

  3. Copy the test scenarios from the src/test/resources folder of the KJAR to the src/test/resources folder of the Kogito artifact.

    You need to import the Kogito dependency of test scenarios in the pom.xml file of your project and create a JUnit activator using the KIE Server REST API.
  4. Run the following command and ensure that the test scenario is running for the specified non-regression tests.

    mvn clean install

    After running the Kogito application, you can retrieve the Swagger or OAS specification file. The Swagger or OAS specifications provide the same information as the REST endpoint along with the following implementation details:

    • Base URL of the server where the API is available

    • References Schemas names

    You can use the provided implementation details when your external application is re-routed to the new URL.

After migrating a DMN model KJAR to a Kogito microservice, you can deploy the microservice using OpenShift.

Example of migrating a DMN model KJAR to a Kogito microservice

The following is an example of migrating a DMN model KJAR to a Kogito microservice:

Example of a DMN decision service
Figure 1. Example decision service implemented using DMN model
Example of a DMN decision service using ItemDefinition structure
Figure 2. Example DMN model using specific ItemDefinition structure

You need to define the object model (POJO) as a DTO in an existing KJAR that is developed in Business Central.

Example of an object model defined as DTO in a KJAR
package com.myspace.demo20210321;

/**
 * This class was automatically generated by the data modeler tool.
 */

public class Reservation implements java.io.Serializable {

	static final long serialVersionUID = 1L;
	@com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer.class)
	private java.time.LocalDate checkin;
	@com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer.class)
	private java.time.LocalDate checkout;

	private java.util.List<java.lang.String> guests;

	public Reservation() {
	}

	public java.time.LocalDate getCheckin() {
		return this.checkin;
	}

	public void setCheckin(java.time.LocalDate checkin) {
		this.checkin = checkin;
	}

	public java.time.LocalDate getCheckout() {
		return this.checkout;
	}

	public void setCheckout(java.time.LocalDate checkout) {
		this.checkout = checkout;
	}

	public java.util.List<java.lang.String> getGuests() {
		return this.guests;
	}

	public void setGuests(java.util.List<java.lang.String> guests) {
		this.guests = guests;
	}

	public Reservation(java.time.LocalDate checkin,
			java.time.LocalDate checkout,
			java.util.List<java.lang.String> guests) {
		this.checkin = checkin;
		this.checkout = checkout;
		this.guests = guests;
	}

}

In the previous example, the defined DTO is used in conjunction with the KIE Server client Java API. Alternatively, you can specify the DTO in the payload, when a non-Java external application is interacting with the KJAR that is deployed on the KIE Server.

Example of using KIE Server client Java API
DMNServicesClient dmnClient = kieServicesClient.getServicesClient(DMNServicesClient.class);
 DMNContext dmnContext = dmnClient.newContext();
 dmnContext.set("reservation", new com.myspace.demo20210321.Reservation(LocalDate.of(2021, 3, 1),
                                                                         LocalDate.of(2021, 3, 8),
                                                                         Arrays.asList("John", "Alice")));
 run(dmnClient, dmnContext);
Example of KJAR deployed manually
Figure 3. Example of manually specifying DTO in the payload
In the previous example, the FQCN of the object model in the REST API is used for the generic KIE Server marshalling.

Example of binding an external application to a Kogito deployment

After deploying the Kogito microservice, you need to bind your external application to the Kogito microservice deployment.

Binding your external application includes re-routing the external application and binding the application to a new base URL of the server that is associated with the Kogito application. For more information, see the following example:

Example of a DMN model specific REST endpoint of KJAR on KIE Server
Figure 4. Example /discount REST endpoint of KJAR on KIE Server
Example of a DMN model specific REST endpoint running on local Kogito application
Figure 5. Example /discount REST endpoint on local Kogito
Example of a DMN model specific REST endpoint with new base URL
Figure 6. Example /discount REST endpoint bound to new base URL of Kogito

Migration of a DRL service from Drools v7 to a Kogito microservice

You can build and deploy a sample project in Kogito to expose a stateless rules evaluation of the Drools rule engine in a Quarkus REST endpoint, and migrate the REST endpoint to Kogito.

The stateless rule evaluation is a single execution of a rule set in Drools and can be identified as a function invocation. In the invoked function, the output values are determined using the input values. Also, the invoked function uses the Drools rule engine to perform the jobs. Therefore, in such cases, a function is exposed using a REST endpoint and converted into a microservice. After converting into a microservice, a function is deployed into a Function as a Service environment to eliminate the cost of JVM startup time.

Major changes and migration considerations

The following table describes the major changes and features that affect migration from the KIE Server API and KJAR to Kogito deployments:

Table 2. DRL migration considerations
Feature In KIE Server API In Kogito with legacy API support In Kogito artifact

DRL files

stored in src/main/resources folder of KJAR.

copy as is to src/main/resources folder.

rewrite using the rule units and OOPath.

KieContainer

configured using a system property or kmodule.xml file.

replaced by KieRuntimeBuilder.

not required.

KieBase or KieSession

configured using a system property or kmodule.xml file.

configured using a system property or kmodule.xml file.

replaced by rule units.

Migration strategy

In Drools, you can migrate a rule evaluation to a Kogito deployment in the following two ways:

Using legacy API in Kogito

In Kogito, the kogito-legacy-api module makes the legacy API of Drools available; therefore, the DRL files remain unchanged. This approach of migrating rule evaluation requires minimal changes and enables you to use major Quarkus features, such as hot reload and native image creation.

Migrating to Kogito rule units

Migrating to Kogito rule units include the programming model of Kogito, which is based on the concept of rule units.

A rule unit in Kogito includes both a set of rules and the facts, against which the rules are matched. Rule units in Kogito also come with data sources. A rule unit data source is a source of the data processed by a given rule unit and represents the entry point, which is used to evaluate the rule unit. Rule units use two types of data sources:

  • DataStream: This is an append-only data source and the facts added into the DataStream cannot be updated or removed.

  • DataStore: This data source is for modifiable data. You can update or remove an object using the FactHandle that is returned when the object is added into the DataStore.

Overall, a rule unit contains two parts: The definition of the fact to be evaluated and the set of rules evaluating the facts.

Example loan application project

In the following sections, a loan application project is used as an example to migrate a DRL project to Kogito deployments. The domain model of the loan application project is made of two classes, the LoanApplication class and the Applicant class:

Example LoanApplication class
public class LoanApplication {

   private String id;
   private Applicant applicant;
   private int amount;
   private int deposit;
   private boolean approved = false;

   public LoanApplication(String id, Applicant applicant,
                      	int amount, int deposit) {
   	this.id = id;
   	this.applicant = applicant;
   	this.amount = amount;
   	this.deposit = deposit;
   }
}
Example Applicant class
public class Applicant {

   private String name;
   private int age;

   public Applicant(String name, int age) {
   	this.name = name;
   	this.age = age;
   }
}

The rule set is created using business decisions to approve or reject an application, along with the last rule of collecting all the approved applications in a list.

Example rule set in loan application
global Integer maxAmount;
global java.util.List approvedApplications;

rule LargeDepositApprove when
   $l: LoanApplication( applicant.age >= 20, deposit >= 1000, amount <= maxAmount )
then
   modify($l) { setApproved(true) }; // loan is approved
end

rule LargeDepositReject when
   $l: LoanApplication( applicant.age >= 20, deposit >= 1000, amount > maxAmount )
then
   modify($l) { setApproved(false) }; // loan is rejected
end

// ... more loans approval/rejections business rules ...

rule CollectApprovedApplication when
   $l: LoanApplication( approved )
then
   approvedApplications.add($l); // collect all approved loan applications
end

Exposing rule evaluation with a REST endpoint using Quarkus

You can expose the rule evaluation that is developed in Business Central with a REST endpoint using Quarkus.

Procedure
  1. Create a new module based on the module that contains the rules and Quarkus libraries, providing the REST support:

    Example dependencies for creating a new module
    <dependencies>
    
     <dependency>
       <groupId>io.quarkus</groupId>
       <artifactId>quarkus-resteasy</artifactId>
     </dependency>
     <dependency>
       <groupId>io.quarkus</groupId>
       <artifactId>quarkus-resteasy-jackson</artifactId>
     </dependency>
    
     <dependency>
       <groupId>org.example</groupId>
       <artifactId>drools-project</artifactId>
       <version>1.0-SNAPSHOT</version>
     </dependency>
    
    <dependencies>
  2. Create a REST endpoint.

    The following is an example setup for creating a REST endpoint:

    Example FindApprovedLoansEndpoint endpoint setup
    @Path("/find-approved")
    public class FindApprovedLoansEndpoint {
    
       private static final KieContainer kContainer = KieServices.Factory.get().newKieClasspathContainer();
    
       @POST()
       @Produces(MediaType.APPLICATION_JSON)
       @Consumes(MediaType.APPLICATION_JSON)
       public List<LoanApplication> executeQuery(LoanAppDto loanAppDto) {
       	KieSession session = kContainer.newKieSession();
    
       	List<LoanApplication> approvedApplications = new ArrayList<>();
       	session.setGlobal("approvedApplications", approvedApplications);
       	session.setGlobal("maxAmount", loanAppDto.getMaxAmount());
    
       	loanAppDto.getLoanApplications().forEach(session::insert);
       	session.fireAllRules();
       	session.dispose();
    
       	return approvedApplications;
       }
    }

    In the previous example, a KieContainer containing the rules is created and added into a static field. The rules in the KieContainer are obtained from the other module in the class path. Using this approach, you can reuse the same KieContainer for subsequent invocations related to the FindApprovedLoansEndpoint endpoint without recompiling the rules.

    The two modules are consolidated in the next process of migrating rule units to a Kogito microservice using legacy API.

    When the FindApprovedLoansEndpoint endpoint is invoked, a new KieSession is created from the KieContainer. The KieSession is populated with the objects from LoanAppDto resulting from the unmarshalling of a JSON request.

    Example LoanAppDto class
    public class LoanAppDto {
    
       private int maxAmount;
    
       private List<LoanApplication> loanApplications;
    
       public int getMaxAmount() {
       	return maxAmount;
       }
    
       public void setMaxAmount(int maxAmount) {
       	this.maxAmount = maxAmount;
       }
    
       public List<LoanApplication> getLoanApplications() {
       	return loanApplications;
       }
    
       public void setLoanApplications(List<LoanApplication> loanApplications) {
       	this.loanApplications = loanApplications;
       }
    }

    When the fireAllRules() method is called, KieSession is fired and the business logic is evaluated against the input data. After business logic evaluation, the last rule collects all the approved applications in a list and the same list is returned as an output.

  3. Start the Quarkus application.

  4. Invoke the FindApprovedLoansEndpoint endpoint with a JSON request that contains the loan applications to be checked.

    The value of the maxAmount is used in the rules as shown in the following example:

    Example curl request
    curl -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' -d '{"maxAmount":5000,
    "loanApplications":[
    {"id":"ABC10001","amount":2000,"deposit":1000,"applicant":{"age":45,"name":"John"}}, {"id":"ABC10002","amount":5000,"deposit":100,"applicant":{"age":25,"name":"Paul"}}, {"id":"ABC10015","amount":1000,"deposit":100,"applicant":{"age":12,"name":"George"}}
    ]}' http://localhost:8080/find-approved
    Example JSON response
    [
      {
        "id": "ABC10001",
        "applicant": {
          "name": "John",
          "age": 45
        },
        "amount": 2000,
        "deposit": 1000,
        "approved": true
      }
    ]
Using this approach, you cannot use the hot reload feature and cannot create a native image of the project. In the next steps, the missing Quarkus features are provided by the Kogito extension that enables Quarkus aware of the DRL files and implement the hot reload feature in a similar way.

Migrating a rule evaluation to a Kogito microservice using legacy API

After exposing a rule evaluation with a REST endpoint, you can migrate the rule evaluation to a Kogito microservice using legacy API.

Procedure
  1. Add the following dependencies to the project pom.xml file to enable the use of Quarkus and legacy API:

    Example dependencies for using Quarkus and legacy API
    <dependencies>
     <dependency>
      <groupId>org.kie.kogito</groupId>
      <artifactId>kogito-quarkus-rules</artifactId>
     </dependency>
     <dependency>
      <groupId>org.kie.kogito</groupId>
      <artifactId>kogito-legacy-api</artifactId>
     </dependency>
    </dependencies>
  2. Rewrite the REST endpoint implementation:

    Example REST endpoint implementation
    @Path("/find-approved")
    public class FindApprovedLoansEndpoint {
    
       @Inject
       KieRuntimeBuilder kieRuntimeBuilder;
    
       @POST()
       @Produces(MediaType.APPLICATION_JSON)
       @Consumes(MediaType.APPLICATION_JSON)
       public List<LoanApplication> executeQuery(LoanAppDto loanAppDto) {
       	KieSession session = kieRuntimeBuilder.newKieSession();
    
       	List<LoanApplication> approvedApplications = new ArrayList<>();
       	session.setGlobal("approvedApplications", approvedApplications);
       	session.setGlobal("maxAmount", loanAppDto.getMaxAmount());
    
       	loanAppDto.getLoanApplications().forEach(session::insert);
       	session.fireAllRules();
       	session.dispose();
    
       	return approvedApplications;
       }
    }

    In the rewritten REST endpoint implementation, instead of creating the KieSession from the KieContainer, the KieSession is created automatically using an integrated KieRuntimeBuilder.

    The KieRuntimeBuilder is an interface provided by the kogito-legacy-api module that replaces the KieContainer. Using KieRuntimeBuilder, you can create KieBases and KieSessions in a similar way you create in KieContainer. Kogito automatically generates an implementation of KieRuntimeBuilder interface at compile time and integrates the KieRuntimeBuilder into a class, which implements the FindApprovedLoansEndpoint REST endpoint.

  3. Start your Quarkus application in development mode.

    You can also use the hot reload to make the changes to the rules files that are applied to the running application. Also, you can create a native image of your rule based application.

Implementing rule units and automatic REST endpoint generation

After migrating rule units to a Kogito microservice, you can implement the rule units and automatic generation of the REST endpoint.

In Kogito, a rule unit contains a set of rules and the facts, against which the rules are matched. Rule units in Kogito also come with data sources. A rule unit data source is a source of the data processed by a given rule unit and represents the entry point, which is used to evaluate the rule unit. Rule units use two types of data sources:

  • DataStream: This is an append-only data source. In DataStream, subscribers receive new and past messages, stream can be hot or cold in the reactive streams. Also, the facts added into the DataStream cannot be updated or removed.

  • DataStore: This data source is for modifiable data. You can update or remove an object using the FactHandle that is returned when the object is added into the DataStore.

Overall, a rule unit contains two parts: the definition of the fact to be evaluated and the set of rules evaluating the facts.

Procedure
  1. Implement a fact definition using POJO:

    Example implementation of a fact definition using POJO
    package org.kie.kogito.queries;
    
    import org.kie.kogito.rules.DataSource;
    import org.kie.kogito.rules.DataStore;
    import org.kie.kogito.rules.RuleUnitData;
    
    public class LoanUnit implements RuleUnitData {
    
       private int maxAmount;
       private DataStore<LoanApplication> loanApplications;
    
       public LoanUnit() {
       	this(DataSource.createStore(), 0);
       }
    
       public LoanUnit(DataStore<LoanApplication> loanApplications, int maxAmount) {
       	this.loanApplications = loanApplications;
       	this.maxAmount = maxAmount;
       }
    
       public DataStore<LoanApplication> getLoanApplications() { return loanApplications; }
    
       public void setLoanApplications(DataStore<LoanApplication> loanApplications) {
       	this.loanApplications = loanApplications;
       }
    
       public int getMaxAmount() { return maxAmount; }
       public void setMaxAmount(int maxAmount) { this.maxAmount = maxAmount; }
    }

    In the previous example, instead of using LoanAppDto the LoanUnit class is bound directly. LoanAppDto is used to marshall or unmarshall JSON requests. Also, the previous example implements the org.kie.kogito.rules.RuleUnitData interface and uses a DataStore to contain the loan applications to be approved.

    The org.kie.kogito.rules.RuleUnitData is a marker interface to notify the Drools rule engine that LoanUnit class is part of a rule unit definition. In addition, the DataStore is responsible to allow the rule engine to react on the changes by firing new rules and triggering other rules.

    Additionally, the consequences of the rules modify the approved property in the previous example. On the contrary, the maxAmount value is considered as a configuration parameter for the rule unit, which is not modified. The maxAmount is processed automatically during the rules evaluation and automatically set from the value passed in the JSON requests.

  2. Implement a DRL file:

    Example implementation of a DRL file
    package org.kie.kogito.queries;
    unit LoanUnit; // no need to using globals, all variables and facts are stored in the rule unit
    
    rule LargeDepositApprove when
       $l: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount <= maxAmount ] // oopath style
    then
       modify($l) { setApproved(true) };
    end
    
    rule LargeDepositReject when
       $l: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount > maxAmount ]
    then
       modify($l) { setApproved(false) };
    end
    
    // ... more loans approval/rejections business rules ...
    
    // approved loan applications are now retrieved through a query
    query FindApproved
       $l: /loanApplications[ approved ]
    end

    The DRL file that you create must declare the same package as fact definition implementation and a unit with the same name of the Java class. The Java class implements the RuleUnitData interface to state that the interface belongs to the same rule unit.

    Also, the DRL file in the previous example is rewritten using the OOPath expressions. In the DRL file, the data source acts as an entry point and the OOPath expression contains the data source name as root. However, the constraints are added in square brackets as follows:

    $l: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount ⇐ maxAmount ]

    Alternatively, you can use the standard DRL syntax, in which you can specify the data source name as an entry point. However, you need to specify the type of the matched object again as shown in the following example, even if the Drools rule engine can infer the type from the data source:

    $l: LoanApplication( applicant.age >= 20, deposit >= 1000, amount ⇐ maxAmount ) from entry-point loanApplications

    In the previous example, the last rule that collects all the approved loan applications is replaced by a query that retrieves the list. A rule unit defines the facts to be passed in input to evaluate the rules, and the query defines the expected output from the rule evaluation. Using this approach, Kogito can automatically generate a class that executes the query and returns the output as shown in the following example:

    Example LoanUnitQueryFindApproved class
    public class LoanUnitQueryFindApproved implements org.kie.kogito.rules.RuleUnitQuery<List<org.kie.kogito.queries.LoanApplication>> {
    
       private final RuleUnitInstance<org.kie.kogito.queries.LoanUnit> instance;
    
       public LoanUnitQueryFindApproved(RuleUnitInstance<org.kie.kogito.queries.LoanUnit> instance) {
       	this.instance = instance;
       }
    
       @Override
       public List<org.kie.kogito.queries.LoanApplication> execute() {
       	return instance.executeQuery("FindApproved").stream().map(this::toResult).collect(toList());
       }
    
       private org.kie.kogito.queries.LoanApplication toResult(Map<String, Object> tuple) {
       	return (org.kie.kogito.queries.LoanApplication) tuple.get("$l");
       }
    }

    The following is an example of a REST endpoint that takes a rule unit as input and passing the input to a query executor to return the output:

    Example LoanUnitQueryFindApprovedEndpoint endpoint
    @Path("/find-approved")
    public class LoanUnitQueryFindApprovedEndpoint {
    
       @javax.inject.Inject
       RuleUnit<org.kie.kogito.queries.LoanUnit> ruleUnit;
    
       public LoanUnitQueryFindApprovedEndpoint() {
       }
    
       public LoanUnitQueryFindApprovedEndpoint(RuleUnit<org.kie.kogito.queries.LoanUnit> ruleUnit) {
       	this.ruleUnit = ruleUnit;
       }
    
       @POST()
       @Produces(MediaType.APPLICATION_JSON)
       @Consumes(MediaType.APPLICATION_JSON)
       public List<org.kie.kogito.queries.LoanApplication> executeQuery(org.kie.kogito.queries.LoanUnit unit) {
       	RuleUnitInstance<org.kie.kogito.queries.LoanUnit> instance = ruleUnit.createInstance(unit);
       	return instance.executeQuery(LoanUnitQueryFindApproved.class);
       }
    }
    You can also add multiple queries and for each query, a different REST endpoint is generated. For example, the FindApproved REST endpoint is generated for find-approved.