Service Registry Engine Modeling Guide

In this guide, using the Flowable Service Registry Engine from a modeling perspective is described.

To fully explain the way the Service Registry Engine works, the examples in this guide include a high-level explanation of the underlying service definition (see the developer guide for complete details). If you are only interested in using the services, these sections can be skipped.

Service Definitions and Service Operations

After a service definition is created (see the Service Registry Engine Developer Guide), it can be used in process or case models. To do so, drag the Service registry task to the canvas from the Flowable design palette:

01 palette

In the attribute panel, the service and one of its operations can be selected (one service can expose multiple operations):

02 model and operation

At runtime, the process or case invokes the service configured in the referenced service definition.

Parameter Mappings

A service operation has zero or more input parameters and zero or more output parameters as part of its definition. These parameters define what kind of data the service expects when invoked and how the result looks like. However, it does not define how this data gets mapped into the process or case. As service definitions get reused between different models, this means that the same service can get and produce data in a different way depending on the parameter mapping.

The options for the parameter mapping are defined by the service definition and can be configured through the input parameters and output parameters attributes. This popup is automatically displaying the parameters defined in the service definition.

03 parameters

Suppose that the service operation has one input parameter, the popup would be as follows:

04 input parameters

Similarly, for the output parameters:

05 output parameters

When no parameters are defined, no mapping is needed.

Input parameters define what kind of data the service expects when it is invoked. Depending on the type of the parameter, the following values are supported in the value column of the input parameter popup:

In the list below, when an expression is mentioned, this expression has access to all variables of the current process instance or case instance. Also the uses of ${parent.var} and ${root.var} are supported.
  • String

    • A literal value that is passed as-is.

    • An expression. This could be simply to concatenate variables (e.g., ${var1}-${var2}) or to call a bean (e.g., myBean.doSomething(someVariable)).

  • Integer | Long | Double

    • A literal value that is passed as-is (e.g., 123, 12.3, etc.).

    • An expression that resolves to a numerical value.

  • Boolean

    • A literal 'true' or 'false'.

    • An expression that resolves to a Boolean value.

  • Date

    • A literal ISO8601 formatted text string.

    • An expression that resolves to a date (java.util.Date or a Joda-time LocalDate or LocalDatetime).

  • JSON

    • A literal JSON text string (e.g., { 'field': 'value' })

    • An expression that resolves to a JsonNode instance.

  • Array

    • A literal JSON array string or a comma-separated list of text values.

    • An expression that resolves to an ArrayNode instance.

In all cases, when a default value is provided as part of the service definition, the default value is used if no data is passed for that input parameter.

The input parameter mapping defines how process or case instance data is mapped to the input parameters of the service. In such a mapping, variable values (typically expressions) are referenced as the value for the parameter. The output parameter mapping, on the other hand, defines how the data produced by calling the service maps back into the process or case instance.

For an output mapping:

  • If the Target Expression is a literal value, this is the name of the variable into which the output parameter value is stored.

  • If the Target Expression is an expression, a writable variable is expected. For example, ${root.var} is once such variable.

In either case, type coercion (the same as described above for the input mapping) based on the type of the output parameter is applied.

Advanced Attributes

The service registry task has two advanced attributes:

  • Save output variables as transient variable.

  • Output variable name.

06 advanced attributes

When the 'Save output variables as transient variable' is checked, all output variables are stored as transient (i.e., non-persisted) variables. This is useful when a service returns data that is not needed to be stored in the process or case instance (e.g., the data is only used in a calculation or as input for another service call).

When the 'Output variable name' is filled, no output parameter mapping is applied. The result of the service call is to be stored as-is under the given variable name.

Examples

To clarify the descriptions above, let us look at some examples:

Getting a Simple JSON Value

In this example, we are using a REST service that fetches information that we want to store in a process or case instance.

Suppose we have a REST service that fetches client information using the URL /clients/{id}. The service returns client information as follows:

{
    "id": 123,
    "firstName": "John",
    "lastName": "Doe",
    "address": "Somelane 3, 1234 City",
    "birthDate": "1970-01-01T01:02:03.456Z",
    "creditScore": 123
}

The service definition looks as follows:

07 example 01 01

Note how the input parameter clientId is used as part of the URL for this operation.

Also, note that we are only returning a handful of the available properties. Not having the other properties (e.g., creditScore) in the output parameter list filters those out.

We can now create a simple process that uses that service:

08 example 01 02

Assuming the process model has a start form with clientId, the parameter mapping looks as follows:

09 example 01 03

Here we are mapping the value of the clientId from the start form to the single input parameter and the first name, last name, and birthDate are mapped to specific output variables. If this process was part of a case, we could push the variables upwards using ${root.clientFirstName} or ${parent.clientFirstName}.

If we run this process say in Flowable Work, the user task after starting the process instance now shows something like this, which validates that the service was invoked and variables were passed back and forth correctly.

10 example 01 04

Using Paths to get a Nested Value

Let us adapt the example in the previous section to include nested fields in the response. For example, the firstName and lastName are under the info field and the birthDate is further nested under the dateInfo property:

{
    "id": 123,
    "info": {
        "firstName": "John",
        "lastName": "Doe",
        "address": "Somelane 3, 1234 City",
        "dateInfo": {
            "birthDate": "1970-01-01T01:02:03.456Z"
        },
        "creditScore": 123
    }
}

To make the example in the previous section work, the output path is set to info (as everything is nested under that property), and the path of the birthDate is set to dateInfo (the path is relative to the output path of the operation):

11 example 02 01

Getting a List of Values

In this example, we have a REST endpoint that returns an array of clients:

[
    {
        "id": 123,
        "info": {
            "firstName": "John",
            "lastName": "Doe",
            "address": "Somelane 3, 1234 City",
            "dateInfo": {
                "birthDate": "1960-01-01T01:02:03.456Z"
            },
            "creditScore": 123
        }
    },
    {
        "id": 456,
        "info": {
            "firstName": "Jane",
            "lastName": "Doe",
            "address": "Somelane 3, 1234 City",
            "dateInfo": {
                "birthDate": "1963-01-04T01:02:03.456Z"
            },
            "creditScore": 5
        }
    },
    {
        "id": 789,
        "info": {
            "firstName": "Jimmy",
            "lastName": "Doe",
            "address": "Somelane 3, 1234 City",
            "dateInfo": {
                "birthDate": "1990-01-01T01:02:03.456Z"
            },
            "creditScore": 123
        }
    }
]

Again reusing the service definition from the previous example, we now add a new operation. This operation is a simple one, as the base URL is already set and we only need to map the output (a JSON array) to a list.

12 example 03 01

This service can now be used in a process and since the clientList is internally stored as an ArrayNode, it can be used, for example, to configure a user task for multi instance (when executed, it creates three user tasks):

13 example 03 02

Posting to a Rest Service

Suppose there exists another REST endpoint that allows creating a client object. Adding such an operation is similar to the steps above. First, add a new 'create' operation to the example from above and now:

  • Configure a set of input parameters that populate the JSON body when doing the POST.

  • For more complex use cases, a custom Freemarker template can be used (see the Service Registry Engine Developer Guide.

If the REST endpoint accepts a simple flat JSON structure for creating a client, the operation configuration could, for example, look like:

14 example 04 01