Repository navigation
Configuration
Two things have to fit for this adapter: its properties and your BPMN model. Both are short here. Of all three adapters, this one asks the least of a model. If the in-memory fake and prefixed identifiers are fine for your application, you need nothing from this page beyond the minimal configuration.
-
The adapter's properties
Every key ofvanillabp.adapters.<id>.*, and what each of them decides. -
The BPMN model for this adapter
What your model has to carry so this adapter can wire it. It is close to nothing. -
What reaches the engine
The shared values as a payload, when it is sent, and where the name of a workflow is kept. What an engine behind the API makes of such a value has a page of its own, What becomes of a shared value. -
Which engine does it talk to?
The in-memory fake by default, and how to put a real engine behind the API. -
Keeping workflow modules apart
Which name-clash-avoidance modes work here, and which one this API cannot express. -
What happens when a task of another application reaches yours
The refusal, and why a service task and a user task get different answers. -
A task delivered twice runs your handler once
What identifies a delivery here, so a second delivery is answered from the record. -
What a subscription asks for
What the adapter subscribes to, and what it polls for. -
What the adapter remembers about your models
The names and the user tasks it read at deployment, and who can ask for them.
You configure the adapter where VanillaBP keeps the settings of each adapter, at vanillabp.adapters.<id>.* (see
Configure the adapters):
vanillabp:
prioritized-adapters:
- pea
adapters:
pea:
type: process-engine-api
name-clash-avoidance: use-prefix # or 'none' - this adapter has no default| Key | Default | Description |
|---|---|---|
type |
the adapter id | must be process-engine-api; you may leave it out if the adapter id already IS process-engine-api
|
name-clash-avoidance |
— |
use-prefix or none. This adapter cannot serve VanillaBP's default by-adapter, so you have to choose a mode, see below
|
How to reach a BPMS is the job of the Process-Engine-API implementation you plug in. You configure it the way that implementation expects. Timeouts and retries are settings of the engine underneath as well. This includes what becomes of a task whose transaction failed. One reason for that is another branch of the workflow which changed the workflow aggregate in between (two writers on one aggregate). VanillaBP owns that transaction. It logs a message which explains what to do and passes the exception on. Whether the engine then retries the task or leaves it to somebody is up to that engine.
You can configure only ONE adapter id of this type, see Deviations.
The Process-Engine-API does not define how a BPMN task names its implementation. So this adapter follows the
Camunda 8 convention (GAPS.md,
entry 7). Model with the Camunda Modeler (Camunda 8 / Cloud diagrams).
Which elements are actually executed depends on the engine behind the API.
| Element | What names the task definition | Notes |
|---|---|---|
| service, send, business-rule and script task | the zeebe:taskDefinition type
|
defaults to the name of the @WorkflowTask method |
| user task | the EXTERNAL zeebe:formDefinition reference
|
subscribed with TaskType.USER. A user task of a claimed process needs a method or implemented-externally: true (a task something else serves). A user task without a form reference can only be marked, because nothing is ever delivered for it |
| message catch event, message boundary event, receive task | the message name | correlated by the aggregate's ID, or by the correlation id where one is passed (see below) |
This is about a process which one of your @WorkflowService classes claims. Some processes only sit in your file,
and you marked them with vanillabp.workflow-modules.<module>.workflows.<process>.implemented-externally: true.
Such a process is deployed with the file. It gets no subscription and no check
(a process nobody claims).
Correlating without an id names only the aggregate. So every place in the workflow which waits for that message name is a candidate, and the engine behind the API decides which one receives the message. If more than one can wait at the same time, pass a correlation id. Do not rely on the engine's choice.
Version boundaries of @WorkflowTask(version = ...)
work with a version TAG only, see Deviations. The adapter
rejects start events which the engine fires on its own while it deploys, see
Deviations.
The adapter treats the Process-Engine-API as a remote BPMS. The engine can only evaluate what the payload
carries. So by default this adapter shares everything, unless you exclude it with @NoSyncWithBPMS
(see workflow aggregates). (why)
The shared attributes travel as the payload of every command the adapter sends for a workflow:
- the start commands,
-
the completion of a task at the end of a
@WorkflowTaskmethod, includingcompleteTaskByErrorfor aTaskException, - completing or canceling an asynchronous task,
- completing a user task,
- correlating a message.
A gateway directly after a @WorkflowTask works because of the payload sent with that task's completion.
Whatever the annotations say, the ID of the workflow aggregate is always part of the payload. Its name is the name of the aggregate's ID attribute. The API has no slot for a business key, so this value is how VanillaBP finds the workflow again. Message correlation follows the same rule as everywhere: no message content travels, only the state of the aggregate.
Only your application picks that id. It is the id of the workflow aggregate, your code assigns it, and the engine behind the API only keeps it. So to look up a workflow there, look for that payload entry. Its name is the name of your aggregate's ID attribute, and its value is the id your application knows the workflow by. What VanillaBP makes of such a name is the platform's business, and the platform wiki describes it: Starting workflows.
A name VanillaBP did not give cannot reach a start here, because this API reports no start of its own. See The engine never reports a workflow it started itself. A workflow somebody started without VanillaBP shows up at its first task instead. That delivery fails. The message names the id it looked for and the aggregate class under which it found nothing.
The payload is a Map<String, Object>, and this adapter converts nothing on the way. What the engine behind the API
then stores, and what that costs a decimal or a large whole number, is on
What becomes of a shared value.
By default the adapter runs against the in-memory mock engine io.vanillabp.pea.mock.InMemoryProcessEngine
which comes with it. The mock is useful for tests and for trying the adapter without any infrastructure. To replace
it, provide your own beans of the Process-Engine-API interfaces:
- Spring Boot: any bean of the interface replaces the mock.
-
Quarkus: any CDI bean of the interface which is not a default bean replaces the mock (the mock is a
@DefaultBean).
At startup the adapter parses the BPMN resources of each workflow module, one entry per
<bpmn:process isExecutable="true">. It deploys them through the DeploymentApi of the Process-Engine-API.
The Process-Engine-API has no way of its own to keep things apart. So of VanillaBP's name-clash-avoidance modes two remain, and you have to configure one of them (why):
| Mode | Supported? |
|---|---|
by-adapter |
no. It is VanillaBP's default, so this adapter stops the boot. The message names the workflow module and the two other modes. You have to choose one yourself |
use-prefix |
yes. The adapter rewrites the deployed BPMN itself: process ids, call-activity references, message and signal names, escalation and error codes, task definitions and the form references of user tasks |
none |
yes. Nothing is scoped, so your identifiers have to be unique across all workflow modules |
This adapter refuses one thing: two workflow modules which deploy the same BPMN process id into the engine. There is
no tenant and no namespace behind this API. So the engine would keep one of the two models and lose the other
without a word. The adapter tells VanillaBP the workflow module and the BPMN process id of everything it is about to
deploy. VanillaBP remembers which module took which id, so it sees the clash across modules. The startup ends with
Two BPMN processes deployed to adapter '<your adapter id>' reach the BPMS under the SAME identifier!. The message
goes on with both sides, the mode which produced them and the fix. Rename one of the two process ids. Or let the
modules use use-prefix: it puts the module id in front of every process id, so the ids differ by themselves. Note
where the startup stops. The second of the two modules is the one which fails, so the first one is already in the
engine. You cannot switch the refusal off. A start which fails costs less than an engine which runs one of the two
modules and says nothing about the other.
Whichever mode you choose, the adapter reads some names from a module's BPMN files while it deploys them: message
names, signal names, error codes, escalation codes and task definitions. VanillaBP warns where two workflow modules
of your application end up under one of those names. The warning names both modules and what to change. It never
stops the boot, because two modules may share a name on purpose. Under use-prefix there is nothing to find, so
this check is what keeps none usable. One thing nobody can tell you here: whether the engine already holds one of
your identifiers from somewhere else, see
Deviations.
You do not notice the prefixes. Your BPMN files, business code and configuration keep the plain identifiers. Only the
engine behind the Process-Engine-API sees the prefixed ones, and the adapter translates them back when a task is
delivered. Prefixed task definitions carry their BPMN process as well
(<workflow module>__<bpmn process>__<task definition>). That is how a task can be delivered although the engine
does not name its process, see Deviations.
A task subscription of the Process-Engine-API matches a task type across the whole engine, and there is no tenant
behind it. So if two applications deploy a BPMN process of the same name, each of them gets the other's tasks. The
mode use-prefix prevents that.
If it does happen, the application which got the task reads the id of the workflow aggregate from the delivery. It looks the id up in its own database and finds nothing, so VanillaBP refuses the delivery. The message names the workflow aggregate, the adapter, the task, the BPMN process and the workflow module. With these you can look the workflow up in the other application. Then the message names both situations which end here, because nothing in the code can tell them apart:
- the workflow belongs to another application, or
- it belonged to this one, and its workflow aggregate was deleted while the workflow was still running.
For the first one, the message gives the property key which ends it, spelled for that workflow module and that adapter.
What the adapter does next depends on the kind of task. The difference comes from the API:
| Kind | What happens |
|---|---|
| service task | The task fails with a retry count of zero. Your business code never ran and nothing was completed, so the engine still holds the task. An engine which reads the count stops offering the task, and raises an incident if it has incidents. An engine which ignores the count uses its own default, which is what would have happened anyway |
| user task | Nothing happens at the engine. UserTaskCompletionApi has completion and completion by error, but no way to refuse. Both of those would move somebody else's workflow on. The adapter writes one warning which names the workflow, and leaves the task alone |
The refusal is loud on purpose. If the task were handed back quietly, the delivery would cost the owning application
nothing. But then nobody would ever learn that two applications share this engine and take each other's work. You
see how often it happens as a number, not as a pile of log lines: the counter
vanillabp.task.deliveries.unknown.workflow, tagged with the adapter, the workflow module, the BPMN process and the
task definition. Nothing is written into the delivery store for it. That record would sit in the application which
got the task by mistake. But whoever investigates reads the records of the application which owns the workflow.
The Process-Engine-API is a remote BPMS. The adapter reports a task as completed AFTER the local transaction was
committed. So an engine which never learned that result hands out the task again. VanillaBP recognizes such a
delivery by the task ID the engine reports. It is the same ID on every new delivery of that task, and a
different one for the next task. VanillaBP answers a repeated delivery with the outcome it recorded when the handler
ran. It does not call the @WorkflowTask method a second time. The platform wiki describes what this covers and
what stays your business, together with the feature itself: a repeated
delivery.
The records are in your application's database, in VANILLABP_TASK_DELIVERY next to the workflow aggregates. You
can replace the engine behind the API while your adapter id stays the same. Task IDs then start over. So a record
written before the replacement can answer a delivery made after it. Every rebuilt backend needs one step to prevent
that. The platform wiki describes it once (the same link as above).
Now the other direction. A SubscribeForTaskCmd names the payload variables the subscriber wants. If it names none,
the engine hands over everything the process instance holds. This adapter did that until VanillaBP 2.0. It is the
same waste Camunda 8 warns about. Your @WorkflowTask method gets the workflow aggregate from your own database. So
a delivery which carries a copy of that data pays for something nobody reads. (why)
Instead, the adapter names the set for each subscription:
- the variable which holds the id of the workflow aggregate, named after your aggregate's id attribute. The delivery finds your aggregate by it;
- every variable which a
@TaskParamof the methods serving that task asks for. VanillaBP reads those names from your annotations while it wires your application. That is the only place they exist, because this adapter never sees a BPMN model of the deployed process. So the set follows your code. Add a parameter to a method, and its variable travels with the delivery; - every variable which a
@TaskParamof an extension's method reads for that task, for example a details provider of the Business Cockpit. The engine hands a task to this one subscription, so an extension reads what this subscription delivers. The methods of your extension get their variables whether or not a@WorkflowTaskmethod reads the same ones.
One subscription serves a task definition across the BPMN processes of a workflow module. So its set is the union of everything it serves. Two processes whose aggregates have differently named id attributes both work. The set is sorted, so it stays the same across restarts of one application version.
No key asks for more. A @TaskParam does not need one, because the name it spells out is part of the set. What is
left is a variable which reaches a handler under a name no annotation carries. Such a read does not silently get a
null. It fails the delivery with a message which names the variable and the set, and the engine behind the API
retries the way it always does. Read such a value from the workflow aggregate your method already receives.
Snapshots of version 2.0 had a key fetch-variables: all. It made a subscription ask for everything. The key is
gone. If your configuration still sets it, at any level, the start ends with a message which names every key that
sets it. Remove them. (why)
A BPMN process which no workflow service claims gets no subscription at all, so there is no set to name for it.
Every subscription logs what it asks for when it opens. When somebody reports that their handler no longer sees a variable, that line answers the first question.
You cannot ask the engine behind this API what was deployed to it, not even right after the deployment (Deviations and GAPS 12). So the adapter keeps what VanillaBP's deployment read at boot. This record serves the viewer API. It also serves anything else which has to say more about a task than the engine does.
This is what you can ask the adapter about your models. All of it comes from the bytes it read once:
- a process, by its workflow module and its BPMN process id, with the
namea modeller wrote on it; - a user task, by the BPMN element id;
- a user task, by the external
zeebe:formDefinitionreference. This is the key this adapter subscribes the user task under.
A form reference is not unique inside a workflow module, because two processes may show the same form. So that lookup answers a collection. The lookup by element id does the same, because nothing stops two processes of one module from reusing an id either.
You get the plain identifiers you wrote. You never get the prefixed ones which the deployed bytes carry under a name-clash-avoidance mode. A module's processes are in the record from the moment the adapter deployed that module. That is before any subscription of your application opens.
The other half is what a delivered task says about itself, and the Process-Engine-API leaves that to the engine. It defines the keys by which a subscription may be RESTRICTED. It defines none of the keys a delivery carries. So the adapter spells out the set which the API's own reference adapter for an embedded Camunda 7 fills: the element id, the process instance, the version tag, the task name, the assignee, the candidate users and groups, the due date and the follow-up date. The adapter reads its own two keys from that same list. A key your engine leaves out is never an error. It is one detail less about a task, and the task is still worth showing.
