Creating a Data Object Model With an Expression-based Service Model

In this guide, we’ll look at creating a data object model, where the data for the data object comes from a Java API which are referenced through expressions.

The steps we’ll go through are

  1. (Optional) Create a Spring bean and expose it for writing expressions

  2. Create the data object model

  3. Create the service model based on the data object model

  4. Create an example case model and forms to show the usage of the data object

Creating a Spring Bean (optional)

This part can be skipped if you’re not interested in executing the example models in the next parts. This part assumes familiarity with Spring, exposing a custom Spring configuration in Flowable and using an IDE.

We’ll create a simple Spring bean that can be used in expressions later on to build the service model for the data object model. This Spring bean is by no means production-ready and only serves as purpose to demonstrate how an expression in a service model delegates to the actual Java class (e.g. no persistency, but keeping everythin in a HashMap).

The customer POJO looks quite simple:

public class Customer {

    private String id;
    private String name;
    private boolean isActive;
    private String country;
    private long yearlyFee;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public boolean getIsActive() {
        return isActive;
    }
    public void setActive(boolean active) {
        this.isActive = active;
    }
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public long getYearlyFee() {
        return yearlyFee;
    }
    public void setYearlyFee(long yearlyFee) {
        this.yearlyFee = yearlyFee;
    }

    @Override
    public String toString() {
        return "Customer{" +
            "id='" + id + '\'' +
            ", name='" + name + '\'' +
            ", isActive=" + isActive +
            ", country='" + country + '\'' +
            ", yearlyFee=" + yearlyFee +
            '}';
    }

}

The example class implementing the CRUD methods looks as follows:

import java.util.*;
import org.slf4j.*;
import com.fasterxml.jackson.databind.*;

public class CustomerService {

    private static Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);

    private ObjectMapper objectMapper;

    private Map<String, Customer> customers = new HashMap<>();

    public CustomerService(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    public JsonNode findCustomerById(String id) {
        Customer customer = customers.get(id);
        if (customer != null) {
            LOGGER.info("Retrieved customer " + customer);
            return objectMapper.valueToTree(customer);
        }
        return null;
    }

    public JsonNode createCustomer(String name, boolean isActive, String country, Long yearlyFee) {
        Customer customer = new Customer();
        customer.setId(UUID.randomUUID().toString());
        customer.setName(name);
        customer.setActive(isActive);
        customer.setCountry(country);
        customer.setYearlyFee(yearlyFee);

        customers.put(customer.getId(), customer);

        LOGGER.info("Created customer " + customer);

        return objectMapper.valueToTree(customer);
    }

    public JsonNode updateCustomer(String id, String name, boolean isActive, String country, Long yearlyFee) {
        Customer customer = customers.get(id);
        customer.setName(name);
        customer.setActive(isActive);
        customer.setCountry(country);
        customer.setYearlyFee(yearlyFee);

        LOGGER.info("Updated customer " + customer);

        return objectMapper.valueToTree(customer);
    }

    public void deleteCustomer(String id) {
        Customer customer = customers.remove(id);
        LOGGER.info("Deleted customer " + customer);
    }

}

One perhaps unexpected observation here is that all of the return types of the methods are JsonNode. Flowable mandates that any Spring bean used in an expression for a service model returns JsonNode. The reason for this is

  • to have a uniform way of handling service invocations

  • to avoid storing serializable Java objects and force storing the data as a JSON variable

This class needs to be exposed as a Spring bean so that it can be used in expressions. To do so, add the following lines to your custom Configuration (class annotated with @Configuration).

@Bean
public CustomerService customerService(ObjectMapper objectMapper) {
    return new CustomerService(objectMapper);
}

Creating the Data Object Model

In Flowable Design, go to the Others section, select Data object from the list on the left-hand side, and then click on the the Create button in the top-right corner.

In the dialog, give the data object model a name, set the key, and optionally add a meaningful description for it. Once all the information is entered then click on Create new model.

Create a Data Object

Once created, the Data Object Model editor opens, and we can start defining it.

Defining the Data Object Fields

A data object consists of one or more fields. New fields can be added by clicking the Create new field button.

The following form is shown:

New data object field

Following properties can be set:

  • Label (optional): a human-readable version of the (technical) name of the field.

  • Name (required): the (technical) name of the field. This is the name that is used for example in expressions in process or case models (e.g. ${customer.id === '123}).

  • Type (required): the type of the field. Currently supported are String/Date/Boolean/Integer/Double/Long fields.

  • Default value (optional): a value for the field that is set in case when a value would not have been set.

  • Description (optional): a human-readable explanation of the purpose of this field.

Let’s add following fields to the data object:

  • ID: a unique identifier for the customer.

  • Name: the name of the customer

  • Active: a flag indicating whether this is an active customer or not

  • Country: the country where the customer resides.

  • Yearly fee: a monetary value indicating some yearly fee the customer pays.

This should result in something that looks like this:

Data object fields

If a description is set, an information icon is shown next to the label of that field (e.g. for the ID field here.)

The actions column of the table shows the following actions (from left to right):

  • Edit: this will allows changing the field settings.

  • Delete: this deletes the field. A confirmation will be asked before deleting.

  • Move up: moves the field one row up.

  • Move down: moves the field one row down.

Advanced Settings

At the bottom of the page there is a check box available that, when selected, displays optional advanced settings:

Advanced settings

All these fields are (technical) metadata settings:

  • Type: A custom type for the data object.

  • Sub type: A custom sub type for the data object.

  • External Id: An external id to identify the data object.

The main use case for these is to categorise or easily query and retrieve data object definitions via the API’s.

Include in App

Data object models can be added and are deployed as part of a regular app. To do that we need to go to the Apps section of Flowable Design and create an app. After the app is created in the App Editor, we need to

  • select the + button

  • select now Include an existing model into the app

  • go to the Others models tab and then select the data object model

Add data object to app

Creating the Service Model

In this part, we’ll create the service model needed to retrieve and manipulate the data for a data object model. The point about doing this, is that all of this is hidden to the business modeler. The actual details how this done are hidden in the service models and don’t leak into the models where the data object gets used.

In Flowable Design, go to the Others section, select Service from the list on the left-hand side, and then click on the the Create button in the top-right corner.

In the dialog, give the model a name, set the key, and optionally add a meaningful description for it. Once all the information is entered then click on Create new model.

Expression Service Definition Creation

Two dropdowns are available: one to select the type and one to select the service type. he type is either standard or based on a data object model.

  • A standard service model allows defining operations and parameters in a free-form way. This is for service models that e.g. get used by the service registry task (See The Service Registry Developer Guide) in process or case models.

  • A service model based on a data object model is used to connect a data object model to an implementation of how to retrieve and manipulate the data at runtime. When this type is selected, the structure of the data model will define a set of required operations and define which parameters are available (i.e. the fields of the data object model).

Select now based on a data object model. A second dropdown appears that allows selecting the data object model. Choose from that list the Customer data object model which we’ve created above.

Expression Service Definition Type Selection

With the third dropdown, the type of service is chosen. In this case, we’ll use the expression option.

When switching to this type a few things happen, as shown on the screenshot below.

  • The Data Object Configuration section appears

  • Four operations are automatically generated

Expression Service Definition Data Object Model Selected

For expression-based service models, only the Lookup ID needs to be configured. This Lookup ID is used to uniquely identify and retrieve data object instances. As such, it is mandatory to mark one of the data object fields as the 'lookup identifier'. In the example here, this is the ID field.

Operations

After selecting a data object model from the dropdown, four operations have automatically been generated. These operations are needed by any service definitions that handles the data of a data object (this is why these operations, unlike any other operations, cannot be deleted). Of course, other operations for the same service model are still permitted.

The default generated operations are

  • Lookup : This operation will be used whenever the data object instance is needed. For example, when a form is displaying information about the data object, the data will be retrieved using this operation behind the scene.

  • Create : This operation is be used when a data object is created for the first time.

  • Update : This operation is used when the data of an existing data object instance is changed.

  • Delete : This operations allow deleting the data object instance.

Expression Service Definition Operations

Let’s have a look at the concrete configuration of the four operations for our customer data object.

Lookup Operation

The responsability of the Lookup operation is to retrieve the data of a data object instance at runtime, based on a data object model.

Note that the name can be changed, but the key is not editable. This is because this opertation is a 'fixed' operation which every data object based service model needs to have.

In the example Spring bean, this data is retrieved by calling public JsonNode findCustomerById(String id), where id is the actual unique identifier of the customer.

The Lookup operation does not have input parameters, as the only value available is the lookupId.

Any (backend) expression in Flowable is put between ${}, the Expression field thus needs to be configured to ${customerService.findCustomerById(lookupId)}.

The Lookup REST API produces a JSON response and this JSON needs to be mapped to the structure of the data object. Unlike a standard service model, the fact we’ve got the data object structure available, helps us in defining this mapping.

As our example Spring bean returns all the properties on the root level, the mapping is a simple one-to-one mapping. The left-hand side shows the JsonNode response side, which needs to be mapped to the data object model structure on the right-hand side:

Expression Service Definition - Lookup Operation Output Mapping

Since the JSON response contains the data on the root level, the Path column is empty. However, if this wouldn’t have been the case the Path can be used to define where the value can be found (using JSON-Pointer syntax).

Create and Update Operation

The Create and Update operations are quite similar: they both start from a representation of the data object, typically through forms, and need to either create or change the corresponding data.

Configuring the create operation is easy: the Expression needs to be set to ${customerService.createCustomer(name, isActive, country, yearlyFee)}

Expression Service Definition - Create Operation

The Update operation looks similar, except using ${customerService.updateCustomer(id, name, isActive, country, yearlyFee)} for the Expression

The Create and Update method calls typically return a JSON response. This response needs to be mapped to the data object instance in a way similar to the Lookup (one-to-one).

Delete Operation

The delete operation is the simplest of all operations. Beyond changing the Expression to ${customerService.deleteCustomer(lookupId)}, nothing else is needed.

Expression Service Definition - Delete Operation

A delete operation has neither input nor output parameters, as only the lookupId can be used and nothing needs to be mapped back as the data object instance gets deleted when invoked.

Example: How To Use the Data Object Model in a Case Model

Let’s use the models from above to create a simple case model. The goal is to create a CMMN case model that can be used to create and manage customer data.

The end result will be a CMMN model that looks like:

CMMN Model

Of course, this is but a minimal model highlighting specifically using a data object model (backed by a service model). A realistic customer case model would have way more steps and details than this example.

Creating the CMMN Model

In Flowable Design, click on Apps in the header bar. Create a new App and name it Customer app. Include the data object and service model we’ve created above by clicking the large + button and selecting Include an existing model into the app.

Now, click the + button again and select Create a new model for the app and Case in the next step. Give the model a name (e.g. Customer Case) and a unique key (e.g. customerCase).

Create CMMN model

Click Create new model. The view will now change to the CMMN editor.

The Start Form

When a case instance is started for this model, we want to show a form that asks for customer data.

Click on the plan model (the only thing shown on the screen right now) and click in the property panel on the Start form property. The New tab is open by default, so fill in a name for the start form and click on Create:

Case Start Form

An empty form model is now shown. Before we model the form, we have to configure it to be bound to a data object model. To do this, click on the Data model property in the property panel:

Case Start Form Data Model Configuration

A popup is now shown that allows us to fill in the data object model we want to reference. Here, we fill in the following:

  • variable name: customer

    • This is how we’ll reference the data object in the form value bindings.

  • Data object definition key: customer

    • This is the key of the data object model.

  • Mode: Create

    • When handling the form, we need to know whether this form will create the data object or only view or modify it.

Case Start Form Data Object Model Popup

This effectively creates a sort of 'scope', similar to the root or parent scope where variables can be stored on. It is also possible to bind the data object instance to a root or parent variable (e.g. using root.customer).

Let’s add some fields to the model:

Case Start Form Fields

The fields here are

  • Name, bound to customer.name and of type text.

  • Active?, bound to customer.active and of type checkbox.

  • Country, bound to customer.country and of type text.

  • Fee, bound to customer.yearlyFee and of type number.

Note that each fields is bound to customer., which is the variable configured for the data object in the popup above.

Publishing the App

Save the CMMN model and go to our app model (it is the model right at the top when going to the Apps tab). The three models (data object, service and CMMN model) are part of the app:

App view

Now click the Publish button (this will only work when you have a running Flowable Work or Engage where you can publish against).

Go to your Flowable Work or Engage installation, log in, Click the New button and select Work. Type customer in the search field. The customer case is now be selectable:

Definitions View

Select it. The start form is now shown:

Start Form

Filling in the form and clicking Submit will start a case instance and immediately end it, as the case has no tasks to do.

The Work Form

Simular to the start form, we can add a Work Form to the case model. This form can be seen from the case instance view and the goal is to display the customer information, but in a read-only mode. The actual editing will be implemented with a user task later.

Close to the Start form property, there is the Work form property. Create a new form:

Case Work Form Property

Similar to the start form, click the Data model property of the form. Give it the same customer variable and key, but change the mode to View this time:

Case Work Form Data Model Configuration

The fields look exactly the same as for the start form, except that all fields have the Enabled checkbox set to false.

The Customer Update Task

Let’s add a few things to the case model:

  1. Add a stage with name Active

  2. Add a user event listener Inactivate in this stage

  3. Add an exit sentry to this stage and link it to the event listener

  4. Add user task named Change customer details to this stage

  5. Make this task manual activated (select the manual activation checkbox in the property panel)

  6. Make this task (infinite) repeatable (select the repetition checkbox in the property panel)

  7. Add a stage with name Inactive below the other stage

  8. Add an entry sentry to the second stage. Link it to the first stage and select the Exit transition. We want to move to this stage when the first one is terminated

Select the user task again and create a new form:

Update Task Form

Similar to above, configure the data object model for this form. Set the mode to Modify.

Note: Modify actually also allows for viewing data, as will become clear in the next step.

Update Task Form Data Model

Let’s create a simple form, only adding a name and fee field. Set the Enabled checkbox of the Name field to false.

Update Task Form Fields

Let’s deploy the app: either go to the Apps tab, select the app model and click Publish or click the Quick Deploy button in the editor toolbar (the cloud icon with the triangle).

Go to Flowable Work or Engage and, like before, start a new case instance and fill in the start form. After starting the case instance, the view is now more interesting. The two stages are shown in the header and when clicking the Work form tab, the read-only form from above is displayed:

Case View

To start the (manual) user task, we need to click the Change customer details button in the header. This will start the user task, which can be found by clicking the Open tasks tab. The form to update the customer data is shown:

Update Task View

Change the fee to another value. If you’re running the example applications, you’ll see in those applications the output

Update customer Customer{id='8db19819-62df-4baa-b746-12fae1376941', name='John Doe', active=false, country='Belgium', yearlyFee=123456}

Note that the Change customer details tasks can be started an infinite number of times. In reality, you’d probably want to limit this to one. The Max instance count property on the user can be used to make sure there is only one user task open at any given time.

Using the Service Registry Task

This feature is only available from Flowable 3.5.1

Let’s finish up the CMMN model:

Cmmn Model Second Stage

  1. Add an event listener to the Inactive stage with name Remove customer data. The idea is that when the Inactivate event listener is selected, the first stage gets terminated, which brings the customer in an inactive but not yet deleted state. To actually delete the data, the Remove customer data event listener needs to be triggered.

  2. Add a service registry task Delete customer data to the same stage and link it to the event listener with an entry sentry.

  3. Add an exit sentry to the whole case (this is fully optional, as the behavior would be the same without here, but it’s visually clearer).

Above, we’ve configured the service model to be based on a data object model. However, this service model is still available as a regular service model and thus can be referenced from the service registry task.

Select the Delete customer data task and select the Delete operation from the model and operation. In the Input parameters configuration, bind the value to ${customer.id}.

Service Registry Task Configuration

Now

  1. Deploy the app again and start a new case instance.

  2. Click the Inactivate button in the header. This brings the case instance to the Inactive stage.

  3. Click the Remove customer data button in the header.

The customer data is now deleted. When running with the example appliaction, in the logging you can now see something like

Deleted customer Customer{id='3465f3ae-e8e9-43e2-a10e-27887270e764', name='John Doe', active=false, country='Belgium', yearlyFee=123456}