Chapter 19. Migration of a DRL service to a Red Hat build of Kogito microservice You can build and deploy a sample project in Red Hat build of Kogito to expose a stateless rules evaluation of the decision engine in a Red Hat build of Quarkus REST endpoint, and migrate the REST endpoint to Red Hat build of Kogito. The stateless rule evaluation is a single execution of a rule set in Red Hat Decision Manager 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 decision 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. 19.1. 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 Red Hat build of Kogito deployments: Table 19.1. DRL migration considerations Feature In KIE Server API In Red Hat build of Kogito with legacy API support In Red Hat build of 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. 19.2. Migration strategy In Red Hat Decision Manager, you can migrate a rule evaluation to a Red Hat build of Kogito deployment in the following two ways: Using legacy API in Red Hat build of Kogito In Red Hat build of Kogito, the kogito-legacy-api module makes the legacy API of Red Hat Decision Manager available; therefore, the DRL files remain unchanged. This approach of migrating rule evaluation requires minimal changes and enables you to use major Red Hat build of Quarkus features, such as hot reload and native image creation. Migrating to Red Hat build of Kogito rule units Migrating to Red Hat build of Kogito rule units include the programming model of Red Hat build of Kogito, which is based on the concept of rule units. A rule unit in Red Hat build of Kogito includes both a set of rules and the facts, against which the rules are matched. Rule units in Red Hat build of 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. 19.3. Example loan application project In the following sections, a loan application project is used as an example to migrate a DRL project to Red Hat build of 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 19.3.1. Exposing rule evaluation with a REST endpoint using Red Hat build of Quarkus You can expose the rule evaluation that is developed in Business Central with a REST endpoint using Red Hat build of Quarkus. Procedure 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 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 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. Note The two modules are consolidated in the process of migrating rule units to a Red Hat build of Kogito microservice using legacy API. For more information, see Migrating DRL rules units to Red Hat build of 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. Start the Red Hat build of Quarkus application. 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 Example JSON response [ { "id": "ABC10001", "applicant": { "name": "John", "age": 45 }, "amount": 2000, "deposit": 1000, "approved": true } ] Note Using this approach, you cannot use the hot reload feature and cannot create a native image of the project. In the 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. 19.3.2. Migrating a rule evaluation to a Red Hat build of Kogito microservice using legacy API After exposing a rule evaluation with a REST endpoint, you can migrate the rule evaluation to a Red Hat build of Kogito microservice using legacy API. Procedure Add the following dependencies to the project pom.xml file to enable the use of Red Hat build of Quarkus and legacy API: Example dependencies for using Quarkus and legacy API 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 . Red Hat build of Kogito automatically generates an implementation of KieRuntimeBuilder interface at compile time and integrates the KieRuntimeBuilder into a class, which implements the FindApprovedLoansEndpoint REST endpoint. Start your Red Hat build of 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. 19.3.3. Implementing rule units and automatic REST endpoint generation After migrating rule units to a Red Hat build of Kogito microservice, you can implement the rule units and automatic generation of the REST endpoint. In Red Hat build of Kogito, a rule unit contains a set of rules and the facts, against which the rules are matched. Rule units in Red Hat build of 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 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 example, instead of using LoanAppDto the LoanUnit class is bound directly. LoanAppDto is used to marshall or unmarshall JSON requests. Also, the 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 decision 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 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. Implement a DRL file: Example implementation of a DRL file 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 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: USDl: /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 decision engine can infer the type from the data source: USDl: LoanApplication( applicant.age >= 20, deposit >= 1000, amount ⇐ maxAmount ) from entry-point loanApplications In the 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, Red Hat build of 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("USDl"); } } 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); } } Note 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.

Commands:
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; } }
public class Applicant { private String name; private int age; public Applicant(String name, int age) { this.name = name; this.age = age; } }
global Integer maxAmount; global java.util.List approvedApplications; rule LargeDepositApprove when USDl: LoanApplication( applicant.age >= 20, deposit >= 1000, amount <= maxAmount ) then modify(USDl) { setApproved(true) }; // loan is approved end rule LargeDepositReject when USDl: LoanApplication( applicant.age >= 20, deposit >= 1000, amount > maxAmount ) then modify(USDl) { setApproved(false) }; // loan is rejected end // ... more loans approval/rejections business rules rule CollectApprovedApplication when USDl: LoanApplication( approved ) then approvedApplications.add(USDl); // collect all approved loan applications end
<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>
@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; } }
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; } }
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
[ { "id": "ABC10001", "applicant": { "name": "John", "age": 45 }, "amount": 2000, "deposit": 1000, "approved": true } ]
<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>
@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; } }
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; } }
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 USDl: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount <= maxAmount ] // oopath style then modify(USDl) { setApproved(true) }; end rule LargeDepositReject when USDl: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount > maxAmount ] then modify(USDl) { setApproved(false) }; end // ... more loans approval/rejections business rules // approved loan applications are now retrieved through a query query FindApproved USDl: /loanApplications[ approved ] end
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("USDl"); } }
@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); } }