High level architecture of the PHM application. Figure 1: High level architecture of the PHM application.

If you read the first article in this series, then you already set up the example application you'll need for this article. If you have not set up the population health management application, you should do that before continuing. In this article, we'll run a few business processes through our event- and business-process-driven application to test it out.

The happy-path scenario

The most important thing you can learn from this article is how to wire the user interface (UI) together with the back end of a business application driven by a jBPM business process. Wiring these components together is not well-documented, and might take trial and error to get right.

If you are a business-process developer, you can easily validate a business-process scenario by running it through jBPM Business Central. Doing that won't help you much if you are a UI developer, however, and need to build or customize a user-facing business application.

In this case of the population health management application, the business process you developed drives one or possibly several applications where the end-user is a family doctor, pharmacist, or insurance agent. Each of them needs to access member records.

Your task is to wire the front-end application used by each of these parties to a single business process running in the jBPM process server, also known as the KieServer. This server uses a REST API to expose a variety of operations. For the purpose of this article, calls to the KieServer REST API will use basic authentication. In real life, it is more likely that you would use a stronger authentication mechanism, such as OAuth implemented in Keycloak.

For the remainder of this article, I will use the cURL command to demonstrate how to call a REST API from an example end-user application UI. The cURL command is available from both the Linux command-line interface (CLI) and a Windows PowerShell. The particular way that you call a REST API when you develop or customize the UI for end-user applications will vary.

You can quickly get some basic information about the server by running:

curl --user kieserver:secret \
--location --request GET 'http://localhost:8080/kie-server/services/rest/server?Accept=application/json' \
--header 'Accept: application/json'

The response will be, for example:

{
  "type": "SUCCESS",
  "msg": "Kie Server info",
  "result": {
    "kie-server-info": {
      "id": "sample-server",
      "version": "7.33.0.Final",
      "name": "sample-server",
      "location": "http://localhost:8080/kie-server/services/rest/server",
      "capabilities": [
        "KieServer",
        "BRM",
        "BPM",
        "CaseMgmt",
        "BPM-UI",
        "BRP",
        "DMN",
        "Swagger",
        "Prometheus"
      ],
      "messages": [
        {
          "severity": "INFO",
          "timestamp": {
            "java.util.Date": 1580476689750
          },
          "content": [
            "Server KieServerInfo{serverId='sample-server', version='7.33.0.Final', name='sample-server', location='http://localhost:8080/kie-server/services/rest/server', capabilities=[KieServer, BRM, BPM, CaseMgmt, BPM-UI, BRP, DMN, Swagger, Prometheus]', messages=null', mode=DEVELOPMENT}started successfully at Fri Jan 31 08:18:09 EST 2020"
          ]
        }
      ],
      "mode": "DEVELOPMENT"
    }
  }
}

Notice the list of the server's capabilities.

Now, get the list of deployment units on this server (also known as Kie containers, which are not to be confused with OCS containers or Docker containers:

curl --user kieserver:secret \
--location --request GET 'http://localhost:8080/kie-server/services/rest/server/containers' \
--header 'Accept: application/json'

The responses are the PHM-Model and PHM-Processes containers:

{
  "type": "SUCCESS",
  "msg": "List of created containers",
  "result": {
    "kie-containers": {
      "kie-container": [
        {
          "container-id": "PHM-Processes_1.0.0-SNAPSHOT",
          "release-id": {
            "group-id": "com.health-insurance",
            "artifact-id": "PHM-Processes",
            "version": "1.0.0-SNAPSHOT"
          },
          "resolved-release-id": {
            "group-id": "com.health-insurance",
            "artifact-id": "PHM-Processes",
            "version": "1.0.0-SNAPSHOT"
          },
          "status": "STARTED",
          "scanner": {
            "status": "DISPOSED",
            "poll-interval": null
          },
          "config-items": [
            {
              "itemName": "KBase",
              "itemValue": "",
              "itemType": "BPM"
            },
            {
              "itemName": "KSession",
              "itemValue": "",
              "itemType": "BPM"
            },
            {
              "itemName": "MergeMode",
              "itemValue": "MERGE_COLLECTIONS",
              "itemType": "BPM"
            },
            {
              "itemName": "RuntimeStrategy",
              "itemValue": "PER_PROCESS_INSTANCE",
              "itemType": "BPM"
            }
          ],
          "messages": [
            {
              "severity": "INFO",
              "timestamp": {
                "java.util.Date": 1580477082631
              },
              "content": [
                "Container PHM-Processes_1.0.0-SNAPSHOT successfully created with module com.health-insurance:PHM-Processes:1.0.0-SNAPSHOT."
              ]
            }
          ],
          "container-alias": "PHM-Processes"
        },
        {
          "container-id": "PHM-Model_1.0.0-SNAPSHOT",
          "release-id": {
            "group-id": "com.health-insurance",
            "artifact-id": "PHM-Model",
            "version": "1.0.0-SNAPSHOT"
          },
          "resolved-release-id": {
            "group-id": "com.health-insurance",
            "artifact-id": "PHM-Model",
            "version": "1.0.0-SNAPSHOT"
          },
          "status": "STARTED",
          "scanner": {
            "status": "DISPOSED",
            "poll-interval": null
          },
          "config-items": [
            {
              "itemName": "KBase",
              "itemValue": "",
              "itemType": "BPM"
            },
            {
              "itemName": "KSession",
              "itemValue": "",
              "itemType": "BPM"
            },
            {
              "itemName": "MergeMode",
              "itemValue": "MERGE_COLLECTIONS",
              "itemType": "BPM"
            },
            {
              "itemName": "RuntimeStrategy",
              "itemValue": "SINGLETON",
              "itemType": "BPM"
            }
          ],
          "messages": [
            {
              "severity": "INFO",
              "timestamp": {
                "java.util.Date": 1580476858851
              },
              "content": [
                "Container PHM-Model_1.0.0-SNAPSHOT successfully created with module com.health-insurance:PHM-Model:1.0.0-SNAPSHOT."
              ]
            }
          ],
          "container-alias": "PHM-Model"
        }
      ]
    }
  }
}

The response contains a lot of information for each Kie container but right now you only need the names. In the REST API calls to the PHM-Processes Kie container that follow, use the alias PHM-Processes. There is no need to use the full container id PHM-Processes_1.0.0-SNAPSHOT.

Assume that a certain trigger for a given member is received on a data stream, like Apache Kafka, by the PHM business application's data integration layer. (In this case, the integration component is Apache Camel.) As shown in Figure 1, a Camel event-driven consumer is subscribed to a channel in the Kafka stream. Whenever a trigger event is received, Camel starts a Trigger process instance in jBPM.

High level architecture of the PHM application.
Figure 1: High level architecture of the PHM application.
Figure 1: The PHM application's high-level architecture.">

In my view, a BPM should orchestrate business activities such as human tasks, decision tasks, and services that are immediately ancillary to such business tasks. Data integration should not be the job of the BPM and is best left to tools specifically designed for that purpose. Let Camel do the heavy lifting and let jBPM focus on what is real business logic as opposed to technical data operations. The goal is to have a scalable, robust, agile PHM solution not cramming every function into one swiss-knife tool.

Now, start the Trigger process. In real life, Camel will react to the trigger by starting the Trigger process in jBPM with the following REST API, where you only need to pass the member id and the trigger id:

curl --user kieserver:secret \
--location --request POST 'http://localhost:8080/kie-server/services/rest/server/containers/PHM-Processes/processes/PHM-Processes.Trigger/instances' \
--header 'Content-Type: application/json' \
--data-raw '{
     "pMemberId": "123",
     "pTriggerId": "R383"
}'

The response will be the process instance id.

Now put your business process developer hat back on. Go to Business Central, find the Manage section, and click process instances, as shown in Figure 2.

Business Central - Manage - process instances.
Figure 9: Business Central - Manage - process instances.
Figure 2: Go to Business Central to manage your process instances.">

Here, you will see three process instances. In addition to the Trigger process instance, you will see the two Task process instances that do not have a predecessor (Figure 3).

Business Central - Manage - Process instances
Figure 10: Business Central - Manage - Process instances.

Click on the Trigger process instance and look through the information available. If you are a business process developer, you will find the process diagram shown in Figure 4 useful when quickly debugging or testing. It shows where process execution is in a waiting state (red) and which activities or nodes have been completed (grey).

Trigger process instance diagram
Figure 12: Trigger process instance diagram.

However, the most powerful tools available to test and debug jBPM business processes and Drools business rules are the event-listener interfaces. No process should be deployed without them—check out this printout of events captured by the custom event listeners at the start of the Trigger process.

What is captured and how it is displayed is entirely up to you. Typically you would store this tracing data into a database so that you can easily extract reports from it when needed.

Now, put your user interface developer hat back on. Peter is the family physician for the insurance member whose trigger is being processed. Peter has logged into the application on his office PC or perhaps on his smartphone or tablet. You need to know the tasks Peter is a potential owner of through the REST API:

curl --user Peter:secret \
--location --request GET 'http://localhost:8080/kie-server/services/rest/server/containers/PHM-Processes/forms/tasks/1?lang=en&type=ANY&marshallContent=true' \
--header 'Accept: application/json'

Since you are building a custom UI (or integrating with an already existing UI) you don't need most of the information in the response. What you need is just the list of variables submitted by the form:

            "properties": [
                {
                    "name": "answer",
                    "typeInfo": {
                        "type": "BASE",
                        "className": "java.lang.String",
                        "multiple": false
                    },
                    "metaData": {
                        "entries": []
                    }
                },
                {
                    "name": "na",
                    "typeInfo": {
                        "type": "BASE",
                        "className": "java.lang.Boolean",
                        "multiple": false
                    },
                    "metaData": {
                        "entries": []
                    }
                },
                {
                    "name": "naText",
                    "typeInfo": {
                        "type": "BASE",
                        "className": "java.lang.String",
                        "multiple": false
                    },
                    "metaData": {
                        "entries": []
                    }
                }
            ]

Then, you can submit the variables' values by first defining the task's output with this REST API, where (as an example) you only submit the answer variable:

curl --user Peter:secret \
--location --request PUT 'http://localhost:8080/kie-server/services/rest/server/containers/PHM-Processes/tasks/1/contents/output' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data-raw '{
	"answer": "Hello."
}'

Then complete the task with the REST API:

curl --user Peter:secret \
--location --request PUT 'http://localhost:8080/kie-server/services/rest/server/containers/PHM-Processes/tasks/1/states/completed' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data-raw ''

Peter's task needs to be hard-closed. That is, according to our customer requirements, merely completing the task within the BPM system is not sufficient to consider the task closed. A signal from an external system must be received to actually close the task. The external system must be programmed to use the following REST API to send the appropriate signal:

curl --user externalsystem:secret \
--location --request POST 'http://localhost:8080/kie-server/services/rest/server/containers/PHM-Processes/processes/instances/4/signal/hard_close' \
--header 'Content-Type: application/json' \
--data-raw '{}'

Hard closing Peter's task will start two more task processes that have Peter's task as the predecessor. The other actors in the scenario are the insurance member Mary, the pharmacist Robert, and the insurance channel worker Charlie.

You can repeat the steps that you followed by playing Peter for each of the other actors. I will not describe these steps for you here. Bear in mind that according to the data provided by the Get the Data service, only Charlie's task requires hard-closing. The other two tasks can be soft-closed, they do not need a signal from an external system to be considered closed.

This completes the "Happy Path Scenario."

The reminder scenario

In this scenario, Charlie fails to take action within the period of time passed in the attribute Task. A reminderInitiation and a reminder email is sent as a result. In fact, an email should be sent with a frequency given by the attribute Task.reminderFrequency. The Task object with all its attributes is provided for each task by the Get the Data service. The email should stop after the task is completed.

The email is sent by a custom work item handler. In the PHM-Processes project's deployment descriptor, you can see that the parameters of EmailWorkItemHandler are system environment variables:

This is because you don't want the email server credentials to be written there for security reasons. You have to set the system environment variables DEMO_SMTP_SERVER, DEMO_SMTP_PORT, DEMO_SMTP_USER, and DEMO_SMTP_PWD to the corresponding values for the email server that you will be using. For example, you could use a mock server such as mailtrap.io.

Now, go to the REST API project that provides the mock Get the Data service, open the app.js file, find the data for Charlie's task, and change the value of the attributes reminderInitiation and reminderFrequency to the values below:

    {
      task: {
        id: 58,
        origId: 'B143',
        suppressed: false,
        suppressionPeriod: '',
        expirationDate: '2020-12-31T12:00:00.000Z',
        close: 'HARD',
        reminderInitiation: 'PT60S',
        reminderFrequency: 'R/PT60S',
        escalationTimer: 'P90D',
        description: 'Getting Community Info'
      },
      assignment: {
        actor: 'Charlie',
        channel: 'CCN',
        escalationActor: 'Marc',
        escalationChannel: 'CCN'
      },
      reminder: {
        address: 'charlie@healthinsurance.com',
        body: 'XYZ',
        from: 'PHM@healthinsurance.com',
        subject: 'Reminder'
      }
    }

The initial period of time is now 60 seconds instead of 15 days, and the frequency is 60 seconds instead of 15 days. This configuration will make it easier to test that the reminder is being sent. If you don't like these values just use whatever suits you.

After a few minutes, Charlie's email inbox will be full of reminder messages as shown in Figure 5.

Charlie's email inbox
Figure 5: Charlie's email inbox.
Figure 5: The results in Charlie's email inbox.

Now have Charlie complete the task and check that the email barrage has stopped. Of course, you might as well follow what happens in Business Central updating the diagrams of Charlie's Task and Reminder subprocesses.

The escalation scenario

By now, you should know how to run this scenario. Just change the escalation timer value to 60 seconds and see what happens. Bear in mind that this scenario depends on a task hard close and not on task completion, like the previous scenario.

Conclusions

By now, hopefully, you should have gained a solid understanding of how the example business process works, learned how to validate whether your implementation is correct or not, and learned how to wire the business application's user interface driven by the business process.

Last updated: June 29, 2020