Getting started with Business Rules (Drools Rule Language)
In this exercise you will build a decision microservice which determines whether a person is an adult. To implement this service you will use Kogito and Quarkus, and develop these rules using DRL (Drools Rule Language), the rule unit API and the OOPath rule syntax.
Let’s get started.
Pre-requisites
-
Visual Studio Code
-
JDK 11+
-
Maven 3.6.3+
-
cURL (or another client/tool with which RESTful requests can be sent to the Kogito application)
Create the service project
In this step, you will create a Kogito application skeleton with Quarkus as the execution runtime.
The easiest way to create a new Kogito service is to use Quarkus maven plugin, as you can see in the command below:
mvn io.quarkus.platform:quarkus-maven-plugin:2.8.2.Final:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=rules-lab01 \
-Dextensions="kogito-quarkus-rules,quarkus-smallrye-openapi,quarkus-smallrye-health,quarkus-resteasy-jackson" \
-DnoCode
This command should generate a basic Maven project for you in the rules-lab01
subdirectory. The project consists of:
-
The Maven structure.
-
An OpenAPI Swagger-UI at
http://localhost:8080/q/swagger-ui
.
Once the project is generated, open the project in Visual Studio Code:
$ cd rules-lab01
$ code .
Running the Application
We will now run the rules service using Quarkus development mode. This allows us to keep the application running while implementing our application logic. Kogito and Quarkus will hot reload the application when it is accessed and changes have been detected.
Go back to your terminal (or open the integrated terminal in Visual Studio Code).
Make sure that you’re in the root directory of the adult-service
project (the directory containing the pom.xml
file).
We are ready to run our application. Run the following command to start the application in Quarkus development mode:
mvn clean compile quarkus:dev
Once the application is up, you can access the Swagger UI
You should be able to see the following page:
It’s working!
You can now stop the application with CTRL-C
.
Congratulations!
You’ve seen how to create the skeleton of basic rules service using Kogito and Quarkus, and how to start the application in Quarkus dev-mode.
Creating the Domain Model
In the previous step we’ve created a skeleton Kogito application with Quarkus and started the application in Quarkus dev-mode. In this step we create the domain model of our application.
Facts
A (business) rules and/or decision service operates on entities called facts. Facts is data over which a rules engine reasons and to which it applies its constraints. In Kogito, facts are implemented as POJOs (Plain Old Java Objects).
Our adult service determines if a person is an adult based on his age.
From this description of our application, we can infer the fact:
-
Person: which has a name, an age, and a boolean that states whether he/she is an adult.
Person
We first implement the Person
class. To do this, we first need to create a new package in our project.
In your Visual Studio Code IDE, create the package src/main/java/org/acme/domain
In the package src/main/java/org/acme/domain
create a new Person.java
file.
Implement this class as follows:
package org.acme.domain;
public class Person {
private String name;
private int age;
private boolean adult;
public Person() {
}
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;
}
}
image::drl/vscode-create-person-java.png[Create Person.java]
Congratulations!
You’ve implemented the domain model of your Kogito business rules project. In the next step, we will implement the Rule Unit of our application.
Developing rules with Rule Units
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.
Create a PersonUnit
We first implement the skeleton of our PersonUnit
class.
To do this, we first create a new PersonUnit.java
file in the org.acme
package in src/main/java
Implement this new PersonUnit
class as follows:
package org.acme;
import org.acme.domain.Person;
import org.kie.kogito.rules.DataSource;
import org.kie.kogito.rules.DataStore;
import org.kie.kogito.rules.RuleUnitData;
public class PersonUnit implements RuleUnitData {
//Add Person DataStore here
//Add adultAge variable here
public PersonUnit() {
}
//Add DataStore Getters and Setters here
//Add adultAge Getters and Setters here
}
Notice that the class is not fully implemented yet. We will add the additional logic now.
We now need to create our DataSource
for our Person
facts. A DataSource
provides us with a typed API to add facts to our unit.
Kogito provides a number of different DataSources
types, for example a DataStore
, which allows users to insert, update and remove facts, and a DataStream
that only allows to append facts to a stream.
In this example we will be using the DataStore
implementation for our Person
facts.
We therefore add a private DataStore
variable to our rule unit. We use the DataSource
factory class to create a new DataStore
instance and assign it to the variable.
Add the following code snippet to the PersonUnit.java
class, at the place of the //Add Person DataStore here
comment:
private DataStore<Person> persons = DataSource.createStore();
We also create the getters and setters for our store.
Add the following code snippet to the PersonUnit.java
class, at the place of the //Add DataStore Getters and Setters here
comment:
public DataStore<Person> getPersons() {
return persons;
}
public void setPersons(DataStore<Person> persons) {
this.persons = persons;
}
That’s it for now. We will implement some additional functionality to this unit later in this lab.
Congratulations!
In this step you’ve implemented your first Rule Unit. Well done! In the next step we will implement the rules and queries of our rule unit.
Enhancing the rules
The rules of our rule unit will be implemented in DRL, the Drools Rule Language. DRL is a declarative language in which advanced rules can be defined and implemented, using constructs like rules, functions and queries.
PersonUnit DRL
We first implement the skeleton of our PersonUnit.drl
file in the src/main/resources/org/acme
directory of our project.
Implement this DRL file as follows:
package org.acme;
//Unit definition
import org.acme.domain.Person;
rule "Is Adult"
when
//Person OOPath
then
//Set adult
end
query "adult"
//Adult query
end
We first need to define that this PersonUnit.drl
is connected to our PersonUnit
. We do this through unit
definition under the package
definition at the top of the DRL file:
Add the following DRL snippet to the PersonUnit.drl
file, at the place of the //Unit definition
comment:
unit PersonUnit;
Next, we implement the constraint, or left-hand-side of our rule. We will do this in the OOPath syntax. OOPath allows us to write constraints in an XPath-like syntax, allowing users to more easily navigate object hierarchies when writing rules.
Also, it allows us to easily define constraints using the rule unit DataSource
paradigm.
The following constraint matches Person
facts from the persons
datastore of our unit, who’s age is equal to, or greater than 18.
Add this snippet to the DRL file, at the place of the //Person OOPath
comment.
$p: /persons[age >= 18];
We can now implement the consequence of our rule, or the right-hand-side (RHS).
This the action that will be executed when the rule fires. In our case we want to set the person’s adult
field to true when the rule fires.
Add this snippet to the DRL file, at the place of the //Set adult
comment.
$p.setAdult(true);
The next thing we need to do for our Kogito application is a query. The query in a unit’s DRL, in combination with the rule unit definition, is used by the Kogito code generator to automatically generate the RESTful endpoint for our application.
In this query, we simply want to return all the facts from our persons
datastore.
Add this snippet to the DRL file, at the place of the //Adult query
comment.
$p: /persons;
This completes the initial implementation of our DRL.
Running the application
With our domain model, rule unit and rules implemented, we can now start our application. In a terminal, execute the following Maven command.
$ mvn clean compile quarkus:dev
We can inspect the generated RESTful endpoint in the Swagger-UI of the application.
We can now send a request to our generated RESTful endpoint using cURL:
curl -X POST "http://localhost:8080/adult" -H \
"accept: application/json" -H "Content-Type: application/json" \
-d "{\"persons\":[{\"age\":18,\"name\":\"Jason\"}]}"
You should see the following result, showing that Jason is an adult:
[{"name":"Jason","age":18,"adult":true}]
Stop the application in the first terminal using CTRL-C
.
Congratulations
In this step you’ve implemented your first Kogito rules and queries. You’ve seen how Kogito automatically generates the RESTful microservice for you using your business assets, like your rule unit and rules definitions. Finally, we’ve started our application in Quarkus dev-mode, and fired a request.
Working with variables in Rule Units
Apart from using DataSources
in our rule units to insert, update and delete facts, we can also define variables in our unit that can be used in our rules.
In this use-case we will add an adultAge
variable to our unit, which allows us to send the age at which a person is considered an adult in our request, and using that age in our rules.
PersonUnit DRL
First, we add a new adultAge
variable to our PersonUnit
class. Open the PersonUnit.java
file and add the following code snippet at the //Add adultAge variable here
comment.
private int adultAge;
We also add the getters and setters. Add these at the //Add adultAge Getters and Setters here
comment in the PersonUnit.java
class.
public int getAdultAge() {
return adultAge;
}
public void setAdultAge(int adultAge) {
this.adultAge = adultAge;
}
With our variable implemented, we can now use this variable in our rules. Open the PersonUnit.drl
file,
and replace the constaint of the rule ($p: /persons[age >= 18];
) with the following constraint, which replaces the hardcoded age 18
with our variable:
$p: /persons[age >= adultAge];
We’ve now added the functionality we want, so we can start our application again. Execute the following Maven command in a terminal:
mvn clean compile quarkus:dev
We can now hit the application with a request that contains our new adultAge
variable:
$ curl -X POST "http://localhost:8080/adult" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"adultAge\": 21, \"persons\":[{\"age\":18,\"name\":\"Jason\"}]}"
This will give you the following result:
[{"name":"Jason","age":18,"adult":false}]
Notice that, because we have defined the adultAge
to be 21, Jason is no longer considered an adult.
OpenAPI Specification
A Kogito Quarkus application running in Quarkus dev-mode automatically exposes an OpenAPI specification of its RESTful resources through a Swagger-UI. You can open this Swagger-UI using this link.
Open the POST /adult RESTful endpoint. Note that a fully typed API is generated for you, based on your business assets like your rule units and rules.
Congratulations
You have added a variable to your rule unit and used it in your rules. You’ve also experienced the power of live/hot reload of Kogito, providing extremely fast roundtrip times.
In this lab, you’ve learned how to implement business rules using Rule Units in Kogito. We’ve seen how Kogito generates a RESTful microservice from business assets, like your rule unit and rules. We’ve experienced the power of Kogito hot-reload when changing rules.