FUEL Script Task

Hi, I’ve done a LOT of modeling in Zeebe, but this is my first attempt at getting something running in the BPM Platform. Things are … similar, but different enough that I’m stuck.

I’ve created a model that checks for required input parameters using a gateway with a default flow and a flow with a JUEL script condition:

I’ve then got a FUEL Script task that mocks getting the id of a piece of content. I plan to replace this with a Service task once I get this simplified model running:

Finally, I’m using another FUEL Script task to build a JSON response to be returned to the caller:

When I successfully deploy and call the workflow from Postman, it appears to succeed, but my response (ResponseBody) isn’t returned:

And in Cockpit, there is no record of the instance that I can use to trace the execution for clues about what might have gone wrong:

Here is the process definition:

GetContent.bpmn (5.8 KB)

I suspect that there may be issues with my FUEL scripts and/or with Input/Output parameter mapping, which I’m not doing any of currently since the Modeler appears to place the result of the evaluated expression into a Result Variable, which I can name.

Any help with understanding what’s failing and why would be most appreciated.

Thanks!

Doug

Hi @Doug_Wilson,

I don’t think anything in your example is failing, merely you won’t find the data in the places you’re looking. To start with, there are no transaction boundaries in your model. This simple process will get executed start to finish without anything to see in Cockpit (history in Cockpit is an enterprise feature).

In order to see the data you’re looking for, you’ll want to query one of the history APIs. (e.g. GET /history/variable-instance?processDefinitionKey=Test__Content__GetContent). Alternatively, for the sake of example, if you were to put in a human task before your end event, the execution would pause on that task, allowing you to get a feel for what it would look like in Cockpit.

2 Likes

It’s nice to see two people from Austin, TX helping each other out :slight_smile:

2 Likes

Thanks so much, Justin!

I am able to see the history data for my process at http://localhost:8080/engine-rest/history/variable-instance?processDefinitionKey=Test__Content__GetContent. Thanks for the link!

How can I get this data (UserId and ContentId) to be returned to the caller in the response JSON, e.g.

{ "ResponseBody": {
    "UserId": 2345,
    "ContentId": 1234
    }
}

I’m currently trying to build this using JUEL script in the “Build Response for Success” Script Task.

Thanks!

Doug

In terms of formatting the data in the response, you’re going to be limited to the format provided by the API (you can always create your own custom endpoints using the Java API, if you needed to).

In the meantime, working with what you have now… your ResponseBody object may be redundant here as the REST API will give you access to UserId and ContentId individually without having to build that third object. For example, if you take the id of a process instance that has executed:

GET /history/variable-instance?processInstanceId=66562109-19f3-11eb-b1a7-0242ac150102

[
    {
        "type": "String",
        "value": "admin",
        "valueInfo": {},
        "id": "6656210a-19f3-11eb-b1a7-0242ac150102",
        "name": "UserId",
        "processDefinitionKey": "Test__Content__GetContent",
        "processDefinitionId": "Test__Content__GetContent:1:4fc7bed5-19f3-11eb-b1a7-0242ac150102",
        "processInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "executionId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "activityInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "caseDefinitionKey": null,
        "caseDefinitionId": null,
        "caseInstanceId": null,
        "caseExecutionId": null,
        "taskId": null,
        "errorMessage": null,
        "tenantId": null,
        "state": "CREATED",
        "createTime": "2020-10-29T14:31:14.960+0000",
        "removalTime": null,
        "rootProcessInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102"
    },
    {
        "type": "Long",
        "value": 1234,
        "valueInfo": {},
        "id": "66573281-19f3-11eb-b1a7-0242ac150102",
        "name": "ContentId",
        "processDefinitionKey": "Test__Content__GetContent",
        "processDefinitionId": "Test__Content__GetContent:1:4fc7bed5-19f3-11eb-b1a7-0242ac150102",
        "processInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "executionId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "activityInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "caseDefinitionKey": null,
        "caseDefinitionId": null,
        "caseInstanceId": null,
        "caseExecutionId": null,
        "taskId": null,
        "errorMessage": null,
        "tenantId": null,
        "state": "CREATED",
        "createTime": "2020-10-29T14:31:14.967+0000",
        "removalTime": null,
        "rootProcessInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102"
    }
]

It’s hard to say for sure without knowing more about your requirements, but if the json data suits your use case better, you could grab that variable by name:

GET /history/variable-instance?processInstanceId=66562109-19f3-11eb-b1a7-0242ac150102&variableName=ResponseBody

[
    {
        "type": "String",
        "value": "\"{ \\\"UserId\\\": admin, \\\"ContentId\\\": 1234 }\"",
        "valueInfo": {},
        "id": "665780a4-19f3-11eb-b1a7-0242ac150102",
        "name": "ResponseBody",
        "processDefinitionKey": "Test__Content__GetContent",
        "processDefinitionId": "Test__Content__GetContent:1:4fc7bed5-19f3-11eb-b1a7-0242ac150102",
        "processInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "executionId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "activityInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102",
        "caseDefinitionKey": null,
        "caseDefinitionId": null,
        "caseInstanceId": null,
        "caseExecutionId": null,
        "taskId": null,
        "errorMessage": null,
        "tenantId": null,
        "state": "CREATED",
        "createTime": "2020-10-29T14:31:14.969+0000",
        "removalTime": null,
        "rootProcessInstanceId": "66562109-19f3-11eb-b1a7-0242ac150102"
    }
]

At this point, you could take the string in response.data[0].value and parse it into a json object that looks more like what you were looking for:

{
   "ResponseBody": {
       "UserId": "admin",
       "ContentId": 1234
    }
}

Are you saying that the response from any RESTful request to the Camunda BPMN engine will only contain the following structure without implementing a new or modified API using Java?

{
    "links": [
        {
            "method": "GET",
            "href": "http://localhost:8080/engine-rest/process-instance/a5c6677b-19aa-11eb-98fb-0242ac110002",
            "rel": "self"
        }
    ],
    "id": "a5c6677b-19aa-11eb-98fb-0242ac110002",
    "definitionId": "Test__Content__GetContent:1:a1df905a-19aa-11eb-98fb-0242ac110002",
    "businessKey": null,
    "caseInstanceId": null,
    "ended": true,
    "suspended": false,
    "tenantId": null
}

Using FEEL in a Script Task in Zeebe, I can evaluate a custom literal expression like this:

{ "ResponseStatusCode": 200, "ResponseStatusMessage": "Success", "ResponseBody": { "UserId": 2345, "ContentId": 1234 } }

If I’ve previously created UserId and ContentId variables, I can use them in the expression like this:

{ "ResponseStatusCode": 200, "ResponseStatusMessage": "Success", "ResponseBody": { "UserId": UserId, "ContentId": ContentId } }

The resulting value is always assigned to an automatic variable called “result”, which can be renamed to an arbitrary value (e.g. “WorkflowResult”) using Output Parameter mapping like this:

=result : WorkflowResult

And the contents of the “WorkflowResult” (and any other mapped Output Parameters) are added to the standard output, resulting in response output like this:

{
    "ResponseStatusCode": 200,
    "ResponseStatusMessage": "Success",
    "ResponseBody": {
        "UserId": 2345,
        "ContentId": 1234
    },
    "links": [
        {
            "method": "GET",
            "href": "http://localhost:8080/engine-rest/process-instance/a5c6677b-19aa-11eb-98fb-0242ac110002",
            "rel": "self"
        }
    ],
    "id": "a5c6677b-19aa-11eb-98fb-0242ac110002",
    "definitionId": "Test__Content__GetContent:1:a1df905a-19aa-11eb-98fb-0242ac110002",
    "businessKey": null,
    "caseInstanceId": null,
    "ended": true,
    "suspended": false,
    "tenantId": null
}

This is advantageous because it doesn’t require two (2) server roundtrips – the first to POST the request data and another to GET the results.

Is there really no way to do this in the BPM Platform without altering or extending the BPMN Engine REST interface using Java? Isn’t there a way to “hook into” or override the default output using something in the variable processing or “object model”? If not, then what is the purpose/value of Output Parameter mapping?

By the way, doing this in Zeebe currently takes at least one full second for each task evaluation, making even a braindead simple model like this one a 2 - 3 second proposition, which is the reason I’m here evaluating the older (and hopefully faster) BPM Platform.

I’m not aware of a way to achieve what you’re looking to do. Given that the Camunda process engine is so easily embeddable, as a Java developer, if I need something custom I have lightning-fast access via the Java API and can create a REST interface that suits whatever needs I have at the moment.

I think it’s important to point out that the REST API you’re interacting with is just a separate app that is shipped with Camunda. I see it more as a convenience as opposed to an interaction that Camunda was designed around (it can be left out of the deployment entirely if you so choose). Zeebe on the other hand, I believe, was designed from the ground up to be interfaced with via REST so I think you’ll find far more goodies built-in in that regard.

To answer your first question, each of the APIs will contain their own response structures as documented. The one you referenced, for example, is unique to /process-instance as documented here.

1 Like

Just to follow that up with a parallel to your example, it does look like the start process instance API will return the latest process instance variables to you (and in your case, since there’s no transaction boundaries, that will be their final value). It doesn’t meet your custom definition criteria (the variables will be in the form documented here), but would be done in a single call.

1 Like

Thanks for the documentation links and especially for the perspective on the REST API, Justin. That makes a lot of sense.

It looks like the endpoint I’m calling from Postman (POST /process-definition/key/{key}/start) will return the mapped Output Parameters by adding the “withVariablesInReturn” parameter to the request body:

"withVariablesInReturn": true

This is pretty close to what I want, but is an all-or-nothing option, which seems to result in all process variables being returned individually in addition to being returned in the JSON structure I’ve defined.

While creating a custom REST API isn’t something I was really planning to do, it would certainly allow me to simplify the available options and reduce the “attack surface” from a security standpoint.

I really appreciate all your help and sharing your experience.

Best regards,

Doug

1 Like