Where did the execution-listeners go?
A very common requirement that I saw in all my projects over the last years, is the update of business data related to a process instance.
Example: A process for handling offer requests of customers. A DB-table for related business data holds information of the current 'state' that should be updated after an offer is sent to a customer by a sales representative.
How to solve with Camunda Platform 7 embedded
In this simple process the state change is modeled as a none intermediate throw event. The Element has a execution listener attached to it. The listener is defined with an expression, that calls a method of a StateService bean from the context and passes a reference and the target state. The reference is the primary key of the business entity that was set as a process-variable upon instance start.
When running Camunda 7 as an embedded engine we have access to the classpath of the embedding application. Implementing the application with spring boot even allows calls to the bean context from within expressions defined in the BPMN model. The expression from this example
${stateService.changeState(execution.getVariable('reference'), 'OFFERED')}
calls the method 'changeState' of a bean named stateService. The value of the instance-variable 'reference' is used as the first method argument, the value 'OFFERED' as the second.
But execution listeners are no more with Camunda Platform 8!
Due to the new architecture of Camunda Platform 8, the engine (Zebee) is decoupled from the business code. Business-logic has to be implemented via JobWorkers that get their work from the engine via gRPC-API and report back on success (or failure).
You might already know that pattern as "External task pattern" that was already support by Camunda 7.
How to solve with Camunda Platform 8
The process looks almost similar. Instead of none intermediate throw event, a service task is used. So that a job worker can pick up the job, the jobType has to be defined - in this case the type is defined as changeState. The target state is provided via a custom header.
In this example the worker is implemented as part of a spring boot application using spring-boot-starter-camunda. The worker is defined with help of the @JobWorker annotation, that links the implementation to the jobType specified in the BPMN model. By using the @JobWorker annotation, the communication to Zeebe for retrieving and completing the job is taken care of for us. Using the annotation the job will be auto-completed after the execution the method. Instance variables and job headers can be accessed via the ActivatedJob wrapper object.
An alternative to model the state change as a service task, we could also use an message intermediate throw event. By using the same jobType as in the first model variant, we can use the exact same worker implementation to do the work.
Of course we are not bound to the use of Java. Due to the architecture of Camunda Platform 8, the worker can be implemented in any desired language. In addition to the official Zeebe clients, the Camunda community already came up with a whole bunch of useful client implementations for different languages and frameworks.