FEEL expression and breaking changes

Hey guys, first off, great work with the new. 0.23.1 release. Looking forward to. experimenting with the. new features.

With regards to the new support for FEEL expressions, was there any considerations towards breaking code which is already referencing. variables?

For example, code like this no longer works:

`	String orderItem = variables.get("orderItem").toString();`

If this was a variable defined in the workflow. I have to now change this to:

	String orderItem = variables.get("=orderItem").toString();

It appears as though this is not the case for variables defined programmatically resulting to two different access patterns. Also, just out of curiosity, what were the primary motivations for adopting FEEL?

TIA>

Hi @klaus.nji,

thank you for reporting!

No. I’m not aware of this behavior. Please provide more information to analyze the behavior.

You can read more about our motivation here: Allow to specify every technical attribute as expression · Issue #3417 · camunda/zeebe · GitHub

Best regards,
Philipp

1 Like

Hi @philipp.ossler, thanks for the quick response. I have attached a workflow which worked with the previous release. It deployed and ran just fine.

I have made modifications in the flow to use FEEL expressions and have also have highlighted changes in the code which were necessary to get the workflow executing correctly. I wonder if the code changes could have been avoided. A feel variable defined as =variableName defined in the workflow should not imply the code expect or attempt to resolve a variable named =variableName from the variable stack. Instead code should be expecting variable variableName.

The working code is here:

[import io.zeebe.client.ZeebeClient;
import io.zeebe.client.api.response.DeploymentEvent;
import io.zeebe.client.api.response.WorkflowInstanceEvent;
import io.zeebe.client.api.worker.JobWorker;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class SimpleOrderProcessWithMultiSubprocessApp {

	public static void main(String[] args) {

		final String contactPoint = args.length >= 1 ? args[0] : "127.0.0.1:26500";

		System.out.println("Connecting to broker: " + contactPoint);

		final ZeebeClient client = ZeebeClient.newClientBuilder().brokerContactPoint(contactPoint).usePlaintext().build();

		System.out.println("Connected to broker: " + contactPoint);

		final DeploymentEvent deployment =
				client.newDeployCommand().addResourceFromClasspath("order-process-simple-with-multi-subprocess.bpmn").send().join();

		final int version = deployment.getWorkflows().get(0).getVersion();
		System.out.println("Workflow deployed. Version: " + version);

		final Map<String, Object> data = new HashMap<>();
		data.put("orderId", 31243);
		data.put("orderItems", Arrays.asList(435, 182, 376));

		final WorkflowInstanceEvent wfInstance =
				client
						.newCreateInstanceCommand()
						.bpmnProcessId("order-process-with-simple-multi-subprocess")
						.latestVersion()
						.variables(data)
						.send()
						.join();

		final long workflowInstanceKey = wfInstance.getWorkflowInstanceKey();

		System.out.println("Workflow instance created. Key: " + workflowInstanceKey);

		final JobWorker jobWorker =
				client
						.newWorker()
						.jobType("payment-service")
						.handler(
								(jobClient, job) -> {
									final Map<String, Object> variables = job.getVariablesAsMap();

									System.out.println("Process order: " + variables.get("orderId"));
									System.out.println("Collect money");

									final Map<String, Object> result = new HashMap<>();
									result.put("totalPrice", 46.50);

									jobClient.newCompleteCommand(job.getKey()).variables(result).send().join();
								})
						.fetchVariables("orderId")
						.open();

		final JobWorker emailWorker =
				client
						.newWorker()
						.jobType("customer-service")
						.handler(
								(jobClient, job) -> {
									final Map<String, Object> variables = job.getVariablesAsMap();

									System.out.println("Competing order: " + variables.get("orderId"));

									String itemsShipped = variables.get("=shipments").toString();   // v0.23.1 had to use == here

									System.out.println("Order completed with these items shipped:  " + itemsShipped);

									jobClient.newCompleteCommand(job.getKey()).send().join();
								})
						.open();



		// subprocess workers
		final JobWorker packagingWorker =
				client
						.newWorker()
						.jobType("packaging-service")
						.handler(
								(jobClient, job) -> {
									final Map<String, Object> variables = job.getVariablesAsMap();
									System.out.println("Packaging item for order: " + variables.get("orderId"));

									String orderItem = variables.get("=orderItem").toString();  // v0.23.1: had to use = here

									System.out.println("\t Preparing packaging for item: " + orderItem);

									final Map<String, Object> result = new HashMap<>();
									result.put("packageItem", orderItem + ".packaging");

									jobClient.newCompleteCommand(job.getKey()).variables(result).send().join();
								})
						.open();

		final JobWorker shippingWorker =
				client
						.newWorker()
						.jobType("shipping-service")
						.handler(
								(jobClient, job) -> {
									final Map<String, Object> variables = job.getVariablesAsMap();
									System.out.println("Processing order: " + variables.get("orderId"));

									String packageItem = variables.get("packageItem").toString();

									System.out.println("\t Preparing shipment for package item: " + packageItem);

									final Map<String, Object> result = new HashMap<>();
									result.put("shipment", packageItem + ".shipment");

									jobClient.newCompleteCommand(job.getKey()).variables(result).send().join();
								})
						.open();



		waitUntilClose();

		jobWorker.close();
		emailWorker.close();
		packagingWorker.close();

		client.close();
		System.out.println("Closed.");
	}

	public static void waitUntilClose() {
		try (Scanner scanner = new Scanner(System.in)) {
			while (scanner.hasNextLine()) {
				final String nextLine = scanner.nextLine();
				if (nextLine.contains("close")) {
					return;
				}
			}
		}
	}
}]

Workflow:

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Zeebe Modeler" exporterVersion="0.9.0">
  <bpmn:process id="order-process-with-simple-multi-subprocess" name="Order Process with Simple Multi Subprocess" isExecutable="true">
    <bpmn:startEvent id="order-placed" name="Order Placed">
      <bpmn:outgoing>SequenceFlow_18tqka5</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:endEvent id="order-delivered" name="Order Delivered">
      <bpmn:incoming>SequenceFlow_0lcod6u</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="SequenceFlow_18tqka5" sourceRef="order-placed" targetRef="fetch-items" />
    <bpmn:serviceTask id="fetch-items" name="Collect Payment">
      <bpmn:extensionElements>
        <zeebe:taskDefinition type="payment-service" />
      </bpmn:extensionElements>
      <bpmn:incoming>SequenceFlow_18tqka5</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_0a732fj</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:subProcess id="shipping-process" name="Shipping Subprocess">
      <bpmn:incoming>SequenceFlow_0a732fj</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_1wpe6v4</bpmn:outgoing>
      <bpmn:multiInstanceLoopCharacteristics>
        <bpmn:extensionElements>
          <zeebe:loopCharacteristics inputCollection="=orderItems" inputElement="=orderItem" outputCollection="=shipments" outputElement="=shipment" />
        </bpmn:extensionElements>
      </bpmn:multiInstanceLoopCharacteristics>
      <bpmn:startEvent id="StartEvent_0d0qorr">
        <bpmn:outgoing>SequenceFlow_0b4f136</bpmn:outgoing>
      </bpmn:startEvent>
      <bpmn:endEvent id="EndEvent_0ghfmea">
        <bpmn:incoming>SequenceFlow_1kqy99t</bpmn:incoming>
      </bpmn:endEvent>
      <bpmn:serviceTask id="packaging" name="Package Item">
        <bpmn:extensionElements>
          <zeebe:taskDefinition type="packaging-service" />
        </bpmn:extensionElements>
        <bpmn:incoming>SequenceFlow_0b4f136</bpmn:incoming>
        <bpmn:outgoing>SequenceFlow_0etjxtv</bpmn:outgoing>
      </bpmn:serviceTask>
      <bpmn:sequenceFlow id="SequenceFlow_0b4f136" sourceRef="StartEvent_0d0qorr" targetRef="packaging" />
      <bpmn:serviceTask id="shipping" name="Ship Item">
        <bpmn:extensionElements>
          <zeebe:taskDefinition type="shipping-service" />
        </bpmn:extensionElements>
        <bpmn:incoming>SequenceFlow_0etjxtv</bpmn:incoming>
        <bpmn:outgoing>SequenceFlow_1kqy99t</bpmn:outgoing>
      </bpmn:serviceTask>
      <bpmn:sequenceFlow id="SequenceFlow_0etjxtv" sourceRef="packaging" targetRef="shipping" />
      <bpmn:sequenceFlow id="SequenceFlow_1kqy99t" sourceRef="shipping" targetRef="EndEvent_0ghfmea" />
    </bpmn:subProcess>
    <bpmn:sequenceFlow id="SequenceFlow_0a732fj" sourceRef="fetch-items" targetRef="shipping-process" />
    <bpmn:serviceTask id="sending-notification" name="Send Email">
      <bpmn:extensionElements>
        <zeebe:taskDefinition type="customer-service" />
      </bpmn:extensionElements>
      <bpmn:incoming>SequenceFlow_1wpe6v4</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_0lcod6u</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sequenceFlow id="SequenceFlow_1wpe6v4" sourceRef="shipping-process" targetRef="sending-notification" />
    <bpmn:sequenceFlow id="SequenceFlow_0lcod6u" sourceRef="sending-notification" targetRef="order-delivered" />
  </bpmn:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="order-process-with-simple-multi-subprocess">
      <bpmndi:BPMNEdge id="SequenceFlow_0lcod6u_di" bpmnElement="SequenceFlow_0lcod6u">
        <di:waypoint x="1220" y="180" />
        <di:waypoint x="1312" y="180" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_1wpe6v4_di" bpmnElement="SequenceFlow_1wpe6v4">
        <di:waypoint x="1020" y="180" />
        <di:waypoint x="1120" y="180" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_0a732fj_di" bpmnElement="SequenceFlow_0a732fj">
        <di:waypoint x="410" y="170" />
        <di:waypoint x="480" y="170" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_18tqka5_di" bpmnElement="SequenceFlow_18tqka5">
        <di:waypoint x="209" y="170" />
        <di:waypoint x="310" y="170" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="251.5" y="98.5" width="0" height="13" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="order-placed">
        <dc:Bounds x="173" y="152" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="159" y="188" width="65" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="EndEvent_1253stq_di" bpmnElement="order-delivered">
        <dc:Bounds x="1312" y="162" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="1291" y="201" width="78" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ServiceTask_1kymioi_di" bpmnElement="fetch-items">
        <dc:Bounds x="310" y="130" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="SubProcess_1h2827p_di" bpmnElement="shipping-process" isExpanded="true">
        <dc:Bounds x="480" y="80" width="540" height="200" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_1kqy99t_di" bpmnElement="SequenceFlow_1kqy99t">
        <di:waypoint x="850" y="170" />
        <di:waypoint x="942" y="170" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_0etjxtv_di" bpmnElement="SequenceFlow_0etjxtv">
        <di:waypoint x="690" y="170" />
        <di:waypoint x="750" y="170" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_0b4f136_di" bpmnElement="SequenceFlow_0b4f136">
        <di:waypoint x="538" y="170" />
        <di:waypoint x="590" y="170" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="StartEvent_0d0qorr_di" bpmnElement="StartEvent_0d0qorr">
        <dc:Bounds x="502" y="152" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="EndEvent_0ghfmea_di" bpmnElement="EndEvent_0ghfmea">
        <dc:Bounds x="942" y="152" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ServiceTask_1x01ud8_di" bpmnElement="packaging">
        <dc:Bounds x="590" y="130" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ServiceTask_05r51yy_di" bpmnElement="shipping">
        <dc:Bounds x="750" y="130" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ServiceTask_1n6nlst_di" bpmnElement="sending-notification">
        <dc:Bounds x="1120" y="140" width="100" height="80" />
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

@klaus.nji, thanks for sharing!

In the BPMN, I can see two modifications that were not necessary:

  • inputElement="=orderItem"
  • outputCollection="=shipments"

These two attributes are no FEEL expressions (Zeebe | Camunda 8 Docs). So, you should not add a “=” before.

In order to keep the worker code as I was before 0.23.0, the multi-instance subprocess should have the attributes:

<zeebe:loopCharacteristics inputCollection="=orderItems" inputElement="orderItem" outputCollection="shipments" outputElement="=shipment" />

Does this work for you?

@philipp.ossler, if I do not include an “=” infront of any of the properties of the multi-instance subprocess, I get the following deployment errors:

Connected to broker: 127.0.0.1:26500
Exception in thread "main" io.zeebe.client.api.command.ClientStatusException: Command rejected with code 'CREATE': Expected to deploy new resources, but encountered the following errors:
'order-process-with-multiple-invoices.bpmn': - Element: SubProcess_03najjz > multiInstanceLoopCharacteristics > extensionElements > loopCharacteristics
    - ERROR: Expected expression but found static value 'shipment'. An expression must start with '=' (e.g. '=shipment').

	at io.zeebe.client.impl.ZeebeClientFutureImpl.transformExecutionException(ZeebeClientFutureImpl.java:93)
	at io.zeebe.client.impl.ZeebeClientFutureImpl.join(ZeebeClientFutureImpl.java:50)
	at OrderProcessWithMultipleInvoicesApp.main(OrderProcessWithMultipleInvoicesApp.java:104)
Caused by: java.util.concurrent.ExecutionException: io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Command rejected with code 'CREATE': Expected to deploy new resources, but encountered the following errors:
'order-process-with-multiple-invoices.bpmn': - Element: SubProcess_03najjz > multiInstanceLoopCharacteristics > extensionElements > loopCharacteristics
    - ERROR: Expected expression but found static value 'shipment'. An expression must start with '=' (e.g. '=shipment').

	at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
	at io.zeebe.client.impl.ZeebeClientFutureImpl.join(ZeebeClientFutureImpl.java:48)
	... 1 more
Caused by: io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Command rejected with code 'CREATE': Expected to deploy new resources, but encountered the following errors:
'order-process-with-multiple-invoices.bpmn': - Element: SubProcess_03najjz > multiInstanceLoopCharacteristics > extensionElements > loopCharacteristics
    - ERROR: Expected expression but found static value 'shipment'. An expression must start with '=' (e.g. '=shipment').

and if I do not access the inputElement with an equal sign like this =invoice, my process cannot proceed.

Also from your correction, why does the inputCollection have the = and yet the outputCollection does not?

Another issue I just ran across was with the Kafka Connector. I reran the sample ping-pong example against 0.23.1. First I had to modify the same process as follows:

 <bpmn:message id="Message_19mpeg2" name="pong">
    <bpmn:extensionElements>
      <zeebe:subscription correlationKey="= key" />
    </bpmn:extensionElements>
  </bpmn:message>

Otherwise I get a deployment error:

Command rejected with code 'CREATE': Expected to deploy new resources, but encountered the following errors:
'kafka-test-process': - Element: Message_19mpeg2 > extensionElements > subscription
    - ERROR: Expected expression but found static value 'key'. An expression must start with '=' (e.g. '=key').

Once I make the change, I can deploy the workflow but now the Kafka sink can no longer correlate to this message. Error is:

\n\t... 10 more\nCaused by: com.jayway.jsonpath.PathNotFoundException: No results for path: $['variablesAsMap']['key']\n\tat com.jayway.jsonpath.internal.path.EvaluationContextImpl.getValue(EvaluationContextImpl.java:133)\n\tat com.jayway.jsonpath.JsonPath.read(JsonPath.java:187)\n\tat com.jayway.jsonpath.internal.JsonContext.read(JsonContext.java:102)\n\tat

:neutral_face:adding a lot to this but there appears to be an issue.

Hi @klaus.nji,

regarding your comments:

You need to add = in front of the inputCollection and the outputElement. I provided the correct attributes in the previous comment. This is also described in the documentation.

What do you mean by this? What is the error?

The outputCollection and the inputElement does not access a variable. Instead, these attributes define the variable or the nested property where the value should be stored. It’s like a pointer or an assignment in a programming language.

inputElement (:variable) = inputCollection[loopCounter] (:expression)
outputCollection (:variable) = outputElement (:expression)

I don’t know about the Kafka Connector. Please create an issue in the GitHub repo.

Thanks @philipp.ossler for taking the time and patience in responding.

I certainly need to pay closer attention to the documentation. I made the changes as suggested and workflow runs to completion.

That’s nice to hear :tada:

I saw that you already created issues for Kafka connect. Perfect :+1: