Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[KIELive#39] Migrating Drools to the cloud with...

[KIELive#39] Migrating Drools to the cloud with Kogito: a step by step path

Let's walk through all the phases of the migration path from a traditional Drools application to a cloud native Kogito-based application.

About this session

One of the most typical usage of Drools in the serverless era is exposing the evaluation of a rule set in a stateless way through a REST endpoint. Doing this is pretty straightforward, for instance integrating Drools with Quarkus.

Once you have done this however you may feel that you're missing some important Quarkus features like the hot reload and the possibility of creating a native image of your application. To leverage these capabilities you will have to migrate from Drools to Kogito.

To make this migration as quick as possible, Kogito offers a thin adapter layer allowing to use the same Drools API, with which you're already familiar, also in Kogito. Moreover in this way the migration can be performed without changing anything in your rules files. Finally you can fully embrace the Kogito philosophy and further migrate your project to also use rule units. Performing this last step will also make Kogito automatically generate the REST endpoints for you starting from an opportunely written query in your drl.

The purpose of this talk is to guide you through all the phases of this migration path from a traditional Drools application to a cloud native Kogito one.

Link to this live streaming: https://red.ht/kielive39

About the invited speaker:

Mario is a principal software engineer at Red Hat working as Drools project lead. Among his interests there are also functional programming and Domain Specific Languages. He is also a Java Champion, the JUG Milano coordinator, a frequent speaker and the co-author of "Modern Java in Action" published by Manning.

KIE Community

July 27, 2021
Tweet

More Decks by KIE Community

Other Decks in Technology

Transcript

  1. 1 Migrating to the cloud with : a step by

    step path Mario Fusco @mariofusco Principal Software Engineer Drools Project Lead July, 2021
  2. Quarkus - REST endpoint From Drools to Kogito - Agenda

    Drools Application Rules evaluation in microservice
  3. Quarkus - REST endpoint From Drools to Kogito - Agenda

    Drools Application Kogito Quarkus extension + legacy Drools API Rules evaluation in microservice Hot reload + Native image
  4. Quarkus - REST endpoint From Drools to Kogito - Agenda

    Drools Application Kogito Quarkus extension + legacy Drools API Kogito Quarkus extension + Rule units API Rules evaluation in microservice Hot reload + Native image Automatic REST endpoint generation
  5. Quarkus - REST endpoint From Drools to Kogito - Agenda

    Drools Application Kogito Quarkus extension + legacy Drools API Kogito Quarkus extension + Rule units API Rules evaluation in microservice Hot reload + Native image Automatic REST endpoint generation https://github.com/kiegroup/kogito-examples/ tree/master/rules-legacy-quarkus-example https://github.com/kiegroup/kogito-examples/ tree/master/ruleunit-quarkus-example
  6. A simple Drools project (domain model) public class Applicant {

    private String name; private int age; public Applicant(String name, int age) { this.name = name; this.age = age; } } 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; } }
  7. A simple Drools project (rules) 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
  8. Rules evaluation as a Function as a Service • Stateless

    rules evaluation can be seen as a pure function invocation ◦ You invoke the function with a few arguments and it produces a result ◦ The arguments are actually the facts inserted into the sessions and the result is the outcome of your rules set applied on those facts ◦ But for the users of your function this is an implementation detail • This function invocation can be exposed through a REST endpoint, making it a perfect microservice • It can be eventually deployed in a Function as a Service environment ◦ Maybe for this purpose a Java application is not a good fit and it will be necessary to create a native image (more on this later…) Stateless rules evaluation ... … as a pure function invocation ... … exposed as a REST endpoint ... … packaged into a native image
  9. Integrating with Quarkus <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> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> <plugin> <groupId>io.quarkus</groupId> <artifactId>quarkus-maven-plugin</artifactId> <executions> <execution> <goals> <goal>build</goal> </goals> </execution> </executions> </plugin> </plugins> </build> The kie project containing the business rules
  10. Exposing rules evaluation with a REST endpoint @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; } }
  11. The DTO 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; } }
  12. Giving it a try mario@mario-pc:~/workspace/drools/drools-quarkus$ java -jar quarkus-app/target/quarkus-app/quarkus-run.jar __ ____

    __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-07-05 18:24:48,269 INFO [org.dro.com.kie.bui.imp.ClasspathKieProject] (main) Found kmodule: jar:file:/home/mario/workspace/drools/drools-quarkus/quarkus-app/t arget/quarkus-app/lib/main/org.example.drools-project-1.0-SNAPSHOT .jar!/META-INF/kmodule.xml 2021-07-05 18:24:49,391 INFO [org.dro.com.kie.bui.imp.InternalKieModuleProvider] (main) Creating KieModule for artifact org.example:drools-project:1.0-SNAPSHOT 2021-07-05 18:24:49,439 INFO [org.dro.mod.CanonicalKieModuleProvider] (main) Artifact org.example:drools-project:1.0-SNAPSHOT has executable model 2021-07-05 18:24:50,995 INFO [io.quarkus] (main) quarkus-app 1.0-SNAPSHOT on JVM (powered by Quarkus 1.13.6.Final) started in 4.728s. Listening on: http://0.0.0.0:8080 2021-07-05 18:24:50,998 INFO [io.quarkus] (main) Profile prod activated. 2021-07-05 18:24:50,998 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jackson]
  13. Giving it a try mario@mario-pc:~/workspace/drools/drools-quarkus$ java -jar quarkus-app/target/quarkus-app/quarkus-run.jar __ ____

    __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-07-05 18:24:48,269 INFO [org.dro.com.kie.bui.imp.ClasspathKieProject] (main) Found kmodule: jar:file:/home/mario/workspace/drools/drools-quarkus/quarkus-app/t arget/quarkus-app/lib/main/org.example.drools-project-1.0-SNAPSHOT .jar!/META-INF/kmodule.xml 2021-07-05 18:24:49,391 INFO [org.dro.com.kie.bui.imp.InternalKieModuleProvider] (main) Creating KieModule for artifact org.example:drools-project:1.0-SNAPSHOT 2021-07-05 18:24:49,439 INFO [org.dro.mod.CanonicalKieModuleProvider] (main) Artifact org.example:drools-project:1.0-SNAPSHOT has executable model 2021-07-05 18:24:50,995 INFO [io.quarkus] (main) quarkus-app 1.0-SNAPSHOT on JVM (powered by Quarkus 1.13.6.Final) started in 4.728s. Listening on: http://0.0.0.0:8080 2021-07-05 18:24:50,998 INFO [io.quarkus] (main) Profile prod activated. 2021-07-05 18:24:50,998 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jackson] Quite long startup time since the KieBase creation is done at runtime
  14. Drools on Quarkus is cool, we are all done right?

    NOPE! We cannot leverage a big part of Quarkus features at the moment, and in particular we are missing • Hot reload • Native image compilation
  15. From Drools to Kogito (with the 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> <build> <plugins> <plugin> <groupId>io.quarkus</groupId> <artifactId>quarkus-maven-plugin</artifactId> <executions> <execution> <goals> <goal>build</goal> </goals> </execution> </executions> </plugin> </plugins> </build> The Quarkus Kogito extension for rules This module allows you to use the legacy API (since we want to make minimal changes to our project) The use of the Kogito extension also makes possible to consolidate the former 2 modules into a single one
  16. Exposing rules evaluation with a REST endpoint @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; } } The KieRuntimeBuilder is generated by the Kogito extension and automatically injected by Quarkus You create KieSessions out of it exactly as you did for the KieContainer No other change is necessary
  17. Quarkus hot reload mario@mario-pc:~/workspace/kogito/kogito-examples/rules-legacy-quarkus-example$ mvn quarkus:dev [INFO] --- quarkus-maven-plugin:2.0.0.Final:dev (default-cli)

    @ rules-legacy-quarkus-example --- Listening for transport dt_socket at address: 5005 __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-07-06 16:44:38,165 INFO [io.quarkus] (Quarkus Main Thread) rules-legacy-quarkus-example 2.0.0-SNAPSHOT on JVM (powered by Quarkus 2.0.0.Final) started in 2.510s. Listening on: http://localhost:8080 2021-07-06 16:44:38,167 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
  18. Quarkus hot reload mario@mario-pc:~/workspace/kogito/kogito-examples/rules-legacy-quarkus-example$ mvn quarkus:dev [INFO] --- quarkus-maven-plugin:2.0.0.Final:dev (default-cli)

    @ rules-legacy-quarkus-example --- Listening for transport dt_socket at address: 5005 __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-07-06 16:44:38,165 INFO [io.quarkus] (Quarkus Main Thread) rules-legacy-quarkus-example 2.0.0-SNAPSHOT on JVM (powered by Quarkus 2.0.0.Final) started in 2.510s. Listening on: http://localhost:8080 2021-07-06 16:44:38,167 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2021-07-06 16:45:44,974 INFO [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-1) Live reload total time: 0.627s 2021-07-06 16:46:01,489 WARN [io.qua.dep.dev.JavaCompilationProvider] (Quarkus Test Watcher - 0) bootstrap class path not set in conjunction with -source 8, line -1 in [unknown source] 2021-07-06 16:46:04,255 INFO [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-0) Restarting quarkus due to changes in HotReloadSupportClass.class. 2021-07-06 16:46:04,267 INFO [io.quarkus] (Quarkus Main Thread) rules-legacy-quarkus-example stopped in 0.010s
  19. Generating a native image mario@mario-pc:~/workspace/kogito/kogito-examples/rules-legacy-quarkus-example$ mvn clean package -Pnative [INFO]

    [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Running Quarkus native-image plugin on GraalVM 21.1.0 Java 11 CE (Java Version 11.0.11+8-jvmci-21.1-b05) [rules-legacy-quarkus-example-runner:663694] (clinit): 790.13 ms, 2.30 GB [rules-legacy-quarkus-example-runner:663694] (typeflow): 26,623.51 ms, 2.30 GB [rules-legacy-quarkus-example-runner:663694] (objects): 35,075.43 ms, 2.30 GB [rules-legacy-quarkus-example-runner:663694] (features): 1,498.52 ms, 2.30 GB [rules-legacy-quarkus-example-runner:663694] analysis: 68,379.29 ms, 2.30 GB [rules-legacy-quarkus-example-runner:663694] universe: 2,678.90 ms, 2.30 GB [rules-legacy-quarkus-example-runner:663694] (parse): 12,574.37 ms, 2.80 GB [rules-legacy-quarkus-example-runner:663694] (inline): 11,008.55 ms, 3.55 GB [rules-legacy-quarkus-example-runner:663694] (compile): 55,101.27 ms, 3.78 GB [rules-legacy-quarkus-example-runner:663694] compile: 81,843.69 ms, 3.78 GB [rules-legacy-quarkus-example-runner:663694] image: 8,130.90 ms, 3.67 GB [rules-legacy-quarkus-example-runner:663694] write: 899.46 ms, 3.67 GB # Printing build artifacts to: rules-legacy-quarkus-example-runner.build_artifacts.txt [rules-legacy-quarkus-example-runner:663694] [total]: 170,321.21 ms, 3.67 GB [INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildRunner] objcopy --strip-debug rules-legacy-quarkus-example-runner [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 176013ms [INFO] BUILD SUCCESS
  20. Running the native image mario@mario-pc:~/workspace/kogito/kogito-examples/rules-legacy-quarkus-example$ ./target/rules-legacy-quarkus-example-runner __ ____ __ _____

    ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-07-06 17:51:49,502 INFO [io.quarkus] (main) rules-legacy-quarkus-example 2.0.0-SNAPSHOT native (powered by Quarkus 2.0.0.Final) started in 0.014s. Listening on: http://0.0.0.0:8080 2021-07-06 17:51:49,503 INFO [io.quarkus] (main) Profile prod activated. 2021-07-06 17:51:49,503 INFO [io.quarkus] (main) Installed features: [cdi, kogito-rules, resteasy, resteasy-jackson, servlet, smallrye-context-propagation, smallrye-health, smallrye-openapi, swagger-ui] Negligible start time of the native image makes it a perfect fit for a function as a service deployment
  21. Much better than before right? SURE! But what if I

    could better define the boundaries of my business domain so that Kogito could generate the biggest part of the code for me?
  22. Introducing rule units • Rule Unit: a unit of computation

    for a rule evaluation including fact to be matched and rules
  23. Introducing rule units • DataStream: append-only ◦ subscribers only receive

    new (and possibly past) messages ◦ cannot update/remove ◦ stream may also be hot/cold in “reactive streams” terminology • DataStore: add/update/remove ◦ subscribers may act upon the data store, by acting upon the fact handle • Rule Unit: a unit of computation for a rule evaluation including fact to be matched and rules
  24. Why rule units Data Defs Rule Defs Multiple cross-cutting features:

    • supersede kiebase definition (modularity/grouping of rules) • supersede agenda groups / rule-flow group (modularity of unit of execution+rules, on a per-rule basis) • supersede entry point definition (modularity of the working memory) Rule Units: • introduce a unified, top-down module concept • introduce an abstraction over unit of execution • introduce a data source abstraction
  25. Defining a rule unit modeling the Loan application 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; } } Just a marker interface to flag this class as a container of rule unit datas
  26. Introducing oopath ▸ Similar to XPath / JSONPath $street: /persons/address/street

    [ number == 18 ] ▸ roughly equivalent to: Person( address.street.number == 18 ) from entry-point persons ▸ or: $person: Person() from entry-point persons $address: Address() from $person.address $street: Street ( number == 18 ) from $address.street private final DataStream<Person> persons = ....
  27. Rewriting rules in the rule unit context with oopath 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
  28. Rule Unit • Automated serialization ◦ inferred from the RuleUnitData

    definition • native JSON ◦ not a custom serialization format • Clear definition of input and output ◦ RuleUnitData for the shape of the input ◦ query for the shape of the output clear definition of computation boundaries automatic REST endpoint generation
  29. Automatically generated query executor 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"); } }
  30. Automatically generated REST 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 unitDTO) { RuleUnitInstance<org.kie.kogito.queries.LoanUnit> instance = ruleUnit.createInstance(unitDTO); return instance.executeQuery(LoanUnitQueryFindApproved.class); } }