This repository contains a basic Process Driven Applications (PDA) demo application created using Quarkus and the Kogito process engine. The purpose of this demo is to demonstrate key concepts of Process Driven Applications.
The application includes three services:
- Complaints Service (
de.thi.complaints
): A Quarkus service that handles restaurant complaint submissions. - Sentiment Analysis Service (
de.thi.sentiment
): A Quarkus service that assesses the sentiment of the complaint. It uses OpenAI (ChatGPT) and a User Task as a fallback. - Archive Service (
de.thi.archiv
, optional): A Quarkus service with a simple REST API to store complaints. This service can be integrated into the Complaints Service.
π£οΈ I have showcased this project at a Meetup of the Quarkus User Group Munich on Aug. 31st 2023. The accompanying slides are available here: meetup-kogito.pdf
To understand this walkthrough you should already be able to:
- Open and edit Kogito BPMN in VS Code and run a simple Kogito Quarkus app, as described at Installing the Kogito VSCode extension bundle without the Kogito Business Modeler Hub
- Understand the basics BPMN and the Process Driven Approach
- Start a Kogito process with the Swagger UI and perform User Tasks in the Dev UIs.
You must
- have Docker running
- have a working developer environment for Java 17 with Maven and an IDE of your choice (VS Code preferred)
π Open the file https://github.com/d135-1r43/restaurant-complaints/blob/master/de.thi.complaints/src/main/resources/complaints.bpmn in VS Code and analyse the properties of the process, especially the process variables under 'Process Data' to get a better understanding.
π Run the Complaints Service and start a complaint with the Swagger API. It should be available at http://localhost:8080/q/swagger-ui/. Remove the sentiment
value, just leave it out.
The Complaint Process uses several best-practice patterns. Let's have a look at themβ¦
βΉοΈ Sentiment Analysis is the process of analyzing digital text to determine if the emotional tone of the message is positive, negative, or neutral. In our case, we have a scale from 0 'very angry' to 10 'extremly happy'.
In 'Ask for Sentiment' the process will throw a message and will immediately wait for the returned message in 'Get Sentiment'. The process is agnostic of the actual implementation. The sentiment definition is done by OpenAI. There is an event listener on an error event, in that case a user task is started. As there is no API key for OpenAI defined on default, the User Task will always be triggered.
To achieve decoupling, we will have to define several things:
- In 'Ask for Sentiment' we will have to define a message. We do this in the BPMN editor UI and enter the free text name
text
in the field 'Message' in the dropdown 'Implementation/Execution'. - In 'Data Assignments' we will define the variable
complaintText
to be assigned to the message.
π Analyze the properties of 'Ask for Sentiment' now in VS Code.
As we are developing in a cloud world, the implementation of the sentiment analysis will run in a complete different service, in the Sentiment Analysis Service (de.thi.sentiment
). In order to make the two services communicate with each other, we will use Apache Kafka. Thankfully, Kogito and Quarkus abstract away all the nitty-gritty details. Yet we want to understand them.
In order for Kogito to use Quarkus, we have some dependencies in our pom.xml
:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-addons-quarkus-messaging</artifactId>
</dependency>
That is enough for the magic to happen: 'Ask for Sentiment' will create a Kafka event with the standard Cloudevents.io. This event will be sent to the Microprofile messaging channel text
, which is defined as a Kafka channel in our application.properties
. If you run Quarkus in Dev mode, it will start a Kafka automatically as a Dev Service. Also, if you start another Quarkus service in Dev mode, it will connect to the same Kafka. Learn more about Dev Services at Dev Services Overview.
π Analyze the application.properties
and understand on which topic the BPMN event will be sent to. In the Dev UI, look for the Kafka UI and try to find your cloud event. Understand, what has happenend. Also, try to add a random event with the plus button.
The Sentiment Analysis Service (de.thi.sentiment
) will implement the sentiment analysis. For the sake of simplicity, it is just a User Task to type in a value between 0 and 10.
π Open the BPMN https://github.com/d135-1r43/restaurant-complaints/blob/master/de.thi.sentiment/src/main/resources/sentiment.bpmn in VS Code. Understand the BPMN, analyze the variables and the properties of the start event, the user task and the end event.
π Start the Sentiment Analysis Service and understand that it connects to the same Kafka Dev Service and to the same Kogito Data Index Dev Service. Open its Dev UI at http://localhost:8081/q/dev.
We want the Sentiment process to start at the event. So we have to make sureβ¦
- that 'Get Text' is a Message Start event
- that it has the same message name under 'Implementation/Execution' as the corresponding Intermediate Throw Event
- that the mapping of the variables under 'Data Assignment' makes sense
- that we configure the topic correctly in the
application.properties
. The incoming channel has the keymp.messaging.incoming.kogito_incoming_stream
, the outgoing is calledmp.messaging.outgoing.kogito_outgoing_stream
.
π Make a complaint at the Complaints Service via Swagger UI. Switch to the Sentiment Analysis Service and run the user task to define the sentiment in the Dev UI. Understand why and how now the Complaints Service will catch the event at 'Get Sentiment'. Use the log files, the Kafka UI and the Kogito Management Console to deepen your understanding.
π Configure an OpenAI API key under the config openai.api.key
and rerun the process. Now the sentiment will be determined by OpenAI. Understand the Java Code that runs the Completion Request agains OpenAI.
The Complaints Service will ask for a response in a user task and, if the sentiment is bad and angry (>5), it will get the acknowledgement of the manager. After this, the next important pattern is implemented: A reuseable call activity. The call activity 'Archive Complaint Resolution' will call one of two processes:
- External: REST (https://github.com/d135-1r43/restaurant-complaints/blob/master/de.thi.complaints/src/main/resources/archiverest.bpmn): Archive the complaint at another service (Archive Service
de.thi.archiv
) via REST. In order for this to work, that third service has to run. - Internal: Database (https://github.com/d135-1r43/restaurant-complaints/blob/master/de.thi.complaints/src/main/resources/archivedb.bpmn)
π Open complaints.bpmn
and understand which call activity (REST or DB) is used. Run the complaint. Switch the call activity and experiment. Try to assert after the process, that the complaint has been saved either in the Archive Service or in the internal database. Understand, how the parameters are assigend from the process to the Java class.
βΉοΈ Under the hood, Kogito generates Java classes for each process. That is why the filename, the id and the name should not contain special characters.
Archive Service is a very simple REST service. It basically follows the guide at Simplified Hibernate ORM with Panache. It uses a Postgres to store the complaints. It starts at http://localhost:8082/q/swagger-ui/.
So how does the Complaints Service make the REST request? The magic happens in two classes.
The interface ComplaintRestClient
uses Microprofile REST Client to auto-implement a full feature REST client. There is no need to actually care about the HTTP requests, it is all abstracted away.
In order to know the URL of the service, it has to be configured in the application.properties
:
The class RestArchiver
serves as the Java implementation of the Service Task in the process.
We are using RESTAssured, a nice test framework, to write unit tests against the REST service. Have a look at ComplaintResourceTests.java
.
π Run the unit tests in your IDE. Write a third unit test to understand how testing REST APIs works.
The alternative call activity archivedb.bpmn
will not call an external service, yet it will save the complaint inside of the Complaints Service. It uses (like the Archive Service) Hibernate Panache, based on the JPA standard.
π Find out if we are using the Repository pattern or the Active Record pattern by analyzing the code starting from DbArchiver.java
.
βΉοΈ Testing with unit tests allows us to make sure that specific parts of the works and to identify the source of errors early on.
At the end of the day, the process itself must be tested as well. It is best practice to test each Service Task individually and only do a rough integration test against the process.
An example of such an integration test is ArchiveProcessTests.java
.
π Run the test. Understand how you can inject the process and how to start it in the test. Understand, how the start parameters are provided.