Camunda and APEX — Combining Low-Code Application Development with BPM Workflows

Jasmin Fluri
codeburst

--

Camunda BPM workflows are useful to model processes — APEX is excellent for rapid application development — Combining both in a project showed what the challenges and possibilities are in process automation.

📃 Most applications in companies are form-based. They require the user to add, edit or delete data in a database. And they all have in common, that they follow some sort of workflow with business rules and events.

🔩 Since APEX is a great way to create form-based applications, we tried to combine it in a small project with Camunda BPM for process automation. The goal was to have a visual overview of the process without coupling it to the frontend application or embed it somwhere in the code. This separation was achieved with Oracle APEX as a User interface and a separately deployed Camunda BPM for the process automation.

Architecture

🏯The APEX frontend application used the Camunda REST Endpoints for the communication with the Camunda process. Camunda — on the other side — used the ORDS REST Endpoints for the database calls.

Architecture of the two applications — APEX and Camunda BPM

The REST Endpoint layers provide a clear separation and precise interfaces for the communication between the two parts. The APEX Frontend is the only component that directly accesses the database to update, insert and delete data.

Process Overview

The task at hand was to automate a proposal process. A detailed explanation and our solution are described in the following video.

Video about Process Automation with Camunda BPM and Oracle Apex

👨‍🏫 The proposal process allows each member of a company to create proposals that have a veto period of 10 days. If no other member vetos the proposal is accepted. If a member vetos, the proposal is declined. So pretty straight forward.

Camunda Process

👨‍💻 The automated process was designed in Camunda BPM as shown in the following picture.
🔩 All the service tasks in the process are calls to the ORDS REST Endpoints.

Camunda Process for the Proposal Process

📧 The message events were used to catch messages that were triggered by APEX to inform the process about new or changed data in the GUI. Based on that data, the process continues. The information that was handed over through the message events was either used for making decisions at gateways, hand over variables for the decision table or to end the process before the time limit was expired.

Call Camunda from APEX

Camunda REST API

🚧 Camunda has out-of-the-box a REST API that can be used as soon as a process model is deployed. In our project, we used it for the external start of a process instance and for sending message events to the process instance for exchanging information between the frontend and the process instance. The complete documentation to the Camunda REST API can be found here🔽

Start process instance

🚀 To start a Camunda process instance from APEX the Start Process Instance Endpoint was used. The URL of this POST Request is built as follows:

https://your.domain.com/rest/process-definition/process-definition-id/start

To use the URL in a POST Request two elements of the above URL have to be replaced:

  • your.domain.com —The URL of the domain where your application is deployed
  • process-definition-id — The generated ID of your deployed process as shown in the picture below (in the Camunda cockpit)
Process Definition ID of the Camunda Process

📝 The body of the Start Process Instance POST Request can be defined individually based on which variables should be passed to Camunda. The business key identifies the process instance uniquely within the process definition.

{
"variables": {
"email" : {
"value" : "my-email@gmail.com",
"type": "String"
},
"Name" : {
"value" : "Theodor",
"type": "String"
},
"budget" : {
"value" : 5000,
"type": "Integer"
},
"category" : {
"value" : "Marketing",
"type": "String"
}
},
"businessKey" : "myProcess1"
}

💫 Once the request has been made, a new process instance can be found in the Camunda Cockpit. It lists the delivered variables and the business key that was submitted in the request body.

Receiving Message Events

📩 The second type of calls from APEX to Camunda was sent over Message Events. Message Events make it possible to interact with the individual process instances during their execution.

The definition of a Message Event constists of an Event Id and a unique Message Name that is later used to map the incoming messages to the correct active Message Event.

Configuration of a Camunda Message Event

🔍 The request URL to send a HTTP POST message event to your process looks like this:

https://your.domain.com/rest/process-definition/message

Setting up an APEX Application

Setting up an APEX application is very easy. https://apex.oracle.com/en/ provides free APEX instances in the cloud that can be created with the click of a button.

A big advantage of an APEX application besides having a low-code frontend is, that the complete functionality of the Oracle database can be used as well. So we automatically have the possibility to write PL/SQL code as part of our application and use the ORDS REST Endpoints as a service layer.

Call REST APIs inside a PL/SQL procedure

📣 PL/SQL procedures can also be used to call external REST APIs. The APEX_WEB_SERVICE package makes it possible to call external web services inside of PL/SQL procedures. APEX also has its own utilities to parse and generate JSON — the APEX_JSON package.

APEX_JSON was used in this example project to generate the payload of the HTTP request — the body of the REST call that was sent to the Camunda process to start a new process instance. APEX_WEB_SERVICE then called the REST Endpoint and submitted the created payload as shown in the code examples below 🔽.

CREATE OR REPLACE PROCEDURE start_camunda_process (
proposalid IN NUMBER,
budget IN NUMBER,
category IN VARCHAR2,
username IN VARCHAR2
) IS
l_body CLOB := '';
l_clob CLOB := '';
l_email VARCHAR2(240);
BEGIN
l_email := apex_util.get_email(p_username => username);
apex_web_service.g_request_headers.DELETE();
apex_web_service.g_request_headers(1).name := 'Content-Type';
apex_web_service.g_request_headers(1).value := 'application/json';
apex_json.initialize_clob_output;
apex_json.open_object;
apex_json.write('businessKey', 'proposalProcessAPEX');
apex_json.open_object('variables');
apex_json.open_object('requester');
apex_json.write('value', l_email);
apex_json.write('type', 'String');
apex_json.close_object();
apex_json.open_object('budget');
apex_json.write('value', budget);
apex_json.write('type', 'Integer');
apex_json.close_object();
apex_json.open_object('category');
apex_json.write('value', category);
apex_json.write('type', 'String');
apex_json.close_object();
apex_json.open_object('proposalId');
apex_json.write('value', proposalid);
apex_json.write('type', 'Integer');
apex_json.close_all; -- }}

l_body := apex_json.get_clob_output;
apex_json.free_output;

l_clob := apex_web_service.make_rest_request(
p_url => 'https://your.domain.com/rest/process-definition/process-definition-id/start',
p_http_method => 'POST',
p_body => l_body);
print_clob_to_output(l_clob);

INSERT INTO proposal_camunda(camunda_body, proposal_id)
VALUES (l_clob, proposalid);
UPDATE proposal_camunda
SET
instance_information = JSON_VALUE(
instance_information,
'$.id' RETURNING VARCHAR2)
WHERE
proposal_id = proposalid;
END start_camunda_process;

The procedure to start a Camunda process also stored the Camunda response — like instance-id — in a seperate table for future use.

The same thing was done to send message events to the Camunda process. The payload was created with APEX_JSON and the REST request was executed with APEX_WEB_SERVICE.

CREATE OR REPLACE PROCEDURE send_review_outcome (
proposalid IN NUMBER,
review_status IN VARCHAR2
) IS
l_body CLOB := '';
l_clob CLOB := '';
BEGIN
apex_web_service.g_request_headers.DELETE();
apex_web_service.g_request_headers(1).name := 'Content-Type';
apex_web_service.g_request_headers(1).value := 'application/json';
apex_json.initialize_clob_output;
apex_json.open_object;
apex_json.write('resultEnabled', true);
apex_json.write('messageName', 'ReceiveReviewOutcome');
apex_json.write('businessKey', 'proposalProcessAPEX');
apex_json.open_object('processVariables');
apex_json.open_object('complete');
apex_json.write('value', review_status);
apex_json.write('type', 'String');
apex_json.close_all;
l_body := apex_json.get_clob_output;
apex_json.free_output;
l_clob := apex_web_service.make_rest_request(
p_url => 'https://your.domain.com/rest/message',
p_http_method => 'POST',
p_body => l_body);
print_clob_to_output(l_clob);END send_review_outcome;

Call Camunda over a DB Trigger

🔱 To call the procedures in the APEX application, we used database triggers that were triggering the created stored procedures when certain critera was met.

To start the Camunda process we triggered the start_camunda_process procedure every time a new proposal was inserted into the proposal table.

CREATE OR REPLACE TRIGGER "TRI_PROPOSAL_INSERT_START_PROCESS" AFTER
INSERT ON "PROPOSAL"
FOR EACH ROW
BEGIN
start_camunda_process(
:new.id,
:new.budget,
:new.category,
:new.applicant
);
END;

💌 One kind of message event was sent when a proposal was updated and the proposal status changed. So we called send_review_outcome when the column review_status was updated.

CREATE OR REPLACE TRIGGER "TRI_PROPOSAL_UPDATE_SEND_REVIEW" AFTER
UPDATE OF review_status ON proposal
FOR EACH ROW
BEGIN
send_review_outcome(:old.id, :new.review_status);
END;

Creating REST Endpoints

REST Endpoints can be created directly in the ORDS module of apex.oracle.com.

RESTful Services inside apex.oracle.com

A new module has to be created to start with the implementation of RESTful Services. Afterwards, new endpoints can be added to the module as templates. A detailed description of how it works can be found here:

How to create a REST Endpoint that sends mails

APEX has its own API that can be used for sending emails called APEX_MAIL. It can be called from a PL/SQL procedure. We used it in our proposal application to send emails over a REST endpoint.

So we set up a REST endpoint in APEX for the email notifications:

Create ORDS-Handler-Definition for sending Mails

The source code of the ORDS-Handler needs to be written in PL/SQL. Bind variables can be defined with : followed by the name of the bind variable.

DECLARE
sent_status boolean := false;
responseStatus NUMBER;
BEGIN
APEX_MAIL.SEND(
p_to => :toEmail ,
p_from => :fromEmail ,
p_cc => :ccEmail ,
p_subj => :emailSubject ,
p_body => :emailBody ,
p_body_html => :emailBodyHTML );
sent_status := true;
responseStatus := 200;
COMMIT;
END;

The used bind variables in the PL/SQL code also have to be defined as parameters. They can either be input parameters IN or output parameters OUT.

IN-Parameter definition of ORDS Handler for mails

📞To call the mail REST Endpoint the request URL of the ORDS Handler can be used. The payload of the REST call contains the IN-Parameters that are listed above. It should then look somehow like this:

{
"toEmail": "my-email@gmail.com",
"fromEmail": "another-email@mail.com",
"ccEmail": "a-friend@hotmail.com",
"emailSubject": "Hello from APEX Rest Endpoint",
"emailBody": "This Email was sent using a Rest Endpoint",
"emailBodyHTML": "This is an <b>ORDS</b> Test"
}

Call APEX from Camunda

😰 The most challenging part of the process automation was to figure out how to configure Camunda to do the REST calls properly. Since there are no templates available that validate the data that is added, it is hard to find errors and to figure out what to include. The following steps document how to configure Camunda properly to do a REST call from a Service Task.

💌 Our first service task informed the Chief of Innovation via email that a new proposal was submitted that needs to be reviewed.

Service Task — Inform Chief of Innovation — General Configuration

🔁 The service task needs to be set up as a connector. If the type Connectoris choosen in the Implementation drop down menu, the Configure Connector option appears that will lead to the configuration of the connector.

Service Task — Inform Chief of Innovation — Connector Configuration

🔽 The connector Id is set to http-connector . The necessary input and output parameters are described below.

Input Parameters

Name            Type           Value
method Text POST
url Text http://apex.oracle.com/pls/apex/..
headers Map Content-Type application/json
payload Script out = JSON.stringify({ });
JavaScript
Inline Script

💬 The method contains the type of the REST call. Either POST, PUT or GET. The URL contains the URL of the REST Endpoint. The headers parameter contains the complete header of the REST call. The payload is shown in more detail below. It contains the body of the POST request.

Configuration of payload of a POST Call to send emails.

Output Parameters

Name            Type           Value
responseStatus Text ${response}

The responseStatus is saved in the response parameter that can then be read from other tasks as well inside the process instance.

Process modelling inside APEX

There is also another way on how to use Camunda BPM with APEX. The Flows for APEXextension that allows to include Camunda in your APEX application. With this approach, you don’t need a separately deployed Camunda instance. So if you want to reduce the overhead, even more, check out the following links.

🔽The Flows for APEX download page:

📑Blogpost from Niels de Bruin about Flows for APEX :

Summary

👍 With the clear separation of the process-logic and the user interface, an easy to manage application was built. The maintenance and enhancements are easy to handle, and the two parts run separately.

😕 A downside of the development was that debugging in Camunda is almost impossible. If you have a typo in a variable or a wrong parameter in the payload, it is tough to find out what is wrong and where to look. Fields are unfortunately not validated for their correct input. We lost a lot of time finding out what’s wrong with our data when sometimes it was just a typo. It would be great to have clear templates for the configuration.

😎 Luckily in APEX, there are clear error messages available and finding errors is relatively easy because error messages point you directly to the wrong object. Input fields in APEX are almost always validated, and errors are detected quickly. So not a lot of time was lost searching for mistakes in APEX.

🧐 Compared to a code-only frontend, we were faster with using APEXs low code approach. We still had a steep learning curve to learn all the configuration possibilities and configuration elements and how they should be used correctly inside APEX. I’m sure a trained APEX developer would have been much faster than we were. For the development of simple form-based applications, I would use APEX again; it was and still is the right choice.

💭 Don’t hesitate to ping me if you have any thoughts or questions on the subject! I’m on Twitter → @jasminfluri

Sources / References

--

--

👩‍💻Database & Automation Engineer @ schaltstelle.ch 💻 Oracle ACE Pro♠ — Writes about databases, automation and software engineering— 🐦@jasminfluri