Repository navigation
Configuration
Two things have to fit for this adapter: its properties and your BPMN model. Both are short here, because this adapter asks the least of a model of all three: an application which is happy with the in-memory fake and with prefixed identifiers needs 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 wires it, which is close to nothing. -
What reaches the engine
The shared values as a payload, and what the engine does with them. -
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. -
A task delivered twice runs your handler once
What identifies a delivery here, so a redelivery is answered from the record. -
What a subscription asks for
What the adapter subscribes to, and what it polls for.
The adapter is configured at VanillaBP's canonical per-adapter location 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; may be omitted if the adapter id already IS process-engine-api
|
name-clash-avoidance |
— |
use-prefix or none. VanillaBP's default by-adapter cannot be served here, so the mode has to be chosen, see below
|
fetch-variables |
derived |
which payload variables a task subscription asks the engine for. Resolvable per workflow module, workflow and task, most specific wins. derived is the variable holding the workflow aggregate's id plus every variable a @TaskParam of the served tasks names; all is the complete payload of the process instance. Same key and same two values as on Camunda 8, see below
|
How to reach a BPMS is the concern of the Process-Engine-API implementation you plug in, and it is configured by that implementation's own means. Timeouts and retry behavior are properties of the underlying engine as well - including what becomes of a task whose transaction failed, for instance because another branch of the workflow changed the workflow aggregate in between (two writers on one aggregate): VanillaBP owns that transaction, logs the guiding message and passes the exception on, and whether the engine retries the task or leaves it to somebody is that engine's answer.
Only ONE adapter id of this type can be configured, see Deviations.
The Process-Engine-API does not define how a BPMN task names its implementation, so this adapter adopts the
Camunda 8 convention (GAPS.md,
entry 7). Model with the Camunda Modeler (Camunda 8 / Cloud diagrams);
which elements are actually executed is defined by 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 @WorkflowTask method's name |
| user task | the EXTERNAL zeebe:formDefinition reference
|
subscribed with TaskType.USER; a matching method is an optional notification handler |
| 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) |
Correlating without an id names the aggregate alone, so every occurrence of that message name waiting in the workflow is a candidate and the engine behind the API decides which of them receives the message. Where more than one can wait at once, pass a correlation id rather than relying on that decision.
Version boundaries of @WorkflowTask(version = ...)
work with a version TAG only, see Deviations. Start events the
engine fires on its own are rejected while deploying, see
Deviations.
The Process-Engine-API is treated as a remote BPMS: the engine can only evaluate what the payload carries. The
default of this adapter is therefore that everything is shared, unless you exclude it with @NoSyncWithBPMS
(see workflow aggregates). (why)
The shared attributes travel as the payload of every command the adapter sends on behalf of a workflow: the start
commands, the completion of a task at the end of a @WorkflowTask method (completeTaskByError for a
TaskException included), completing or canceling an asynchronous task, completing a user task and correlating a
message. The payload of the completion at the end of a @WorkflowTask is what makes a gateway directly behind that
task work.
Independent of the annotations the workflow aggregate's ID is always part of the payload, named after the aggregate's ID attribute: the API has no business-key slot, so that value is how VanillaBP finds the workflow again. Message correlation follows the same rule as everywhere, no message content travels, only the aggregate state.
By default the adapter runs against the in-memory mock engine io.vanillabp.pea.mock.InMemoryProcessEngine
shipped with it, which is useful for tests and for exploring the adapter without any infrastructure. Provide your
own beans of the Process-Engine-API interfaces to replace it:
- Spring Boot: any bean of the respective interface replaces the mock.
-
Quarkus: any non-default CDI bean of the respective interface replaces the mock (the mock is a
@DefaultBean).
On startup each workflow module's BPMN resources are parsed (one entry per <bpmn:process isExecutable="true">)
and deployed via the Process-Engine-API's DeploymentApi.
The Process-Engine-API has no isolation mechanism of its own, so of VanillaBP's name-clash-avoidance modes two remain and one has to be configured (why):
| Mode | Supported? |
|---|---|
by-adapter |
no, and it is VanillaBP's default, so this adapter fails the boot naming the workflow module and the two alternatives. You have to choose actively |
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 user-task form references |
none |
yes. Nothing is scoped, so your identifiers have to be unique across all workflow modules |
One thing this adapter refuses: 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 therefore hands VanillaBP the workflow module and the BPMN process id of everything it deploys, over
all modules of the startup, and a collision ends the startup with Different BPMN processes deployed to adapter '<your adapter id>' end up under the SAME identifier! followed by both sides and the fix. Rename one of the two, or let the
modules use use-prefix, which puts the module id in front of every process id so the ids differ by themselves. The
refusal cannot be switched off: a start which fails is cheaper than an engine which runs one of the two modules and
says nothing about the other.
Whichever mode you choose, the adapter reads the message names, signal names, error codes, escalation codes and
task definitions of a module's BPMN files while it deploys them, and 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
fails the boot, because two modules may share a name on purpose. Under use-prefix there is nothing to find, so
this is the check which keeps none usable. What nobody can tell you here is whether the engine already holds one
of your identifiers from somewhere else, see
Deviations.
Prefixing is transparent: 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>), which is what makes a task deliverable without the
engine naming its process, see Deviations.
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 the task out again. VanillaBP identifies such a
delivery by the task ID the engine reports - the same ID on every redelivery of that task, a different one for
the next task - and answers a repeated delivery with the outcome it recorded when the handler ran, instead of
invoking the @WorkflowTask method a second time. What that covers, and what stays your business, is described
with the feature itself: a repeated
delivery.
The records live in your application's database, in VANILLABP_TASK_DELIVERY next to the workflow aggregates, and
the engine behind the API can be replaced while your adapter id stays. Task IDs start over then, so a record
written before the replacement can answer a delivery made after it, and the step which prevents that is the one
every rebuilt backend needs, written once on the platform wiki (the same link as above).
The other direction. A SubscribeForTaskCmd names the payload variables the subscriber wants, and naming none
means the engine hands over everything the process instance holds. That is what this adapter did until VanillaBP
2.0, and it is the same waste Camunda 8 warns about: your @WorkflowTask method is served the workflow aggregate
out of your own database, so a delivery carrying a copy of that data pays for something nobody reads. (why)
The adapter names the set instead, per subscription:
- the variable holding the workflow aggregate's id, named after your aggregate's id attribute - the delivery finds your aggregate by it;
- every variable a
@TaskParamof the methods serving that task asks for. VanillaBP reads those names off your annotations while it wires your application, which is the only place they exist: this adapter never sees a BPMN model of the deployed process. So the set follows your code - adding a parameter to a method is all it takes for its variable to travel with the delivery.
One subscription serves a task definition across the BPMN processes of a workflow module, so its set is the union over 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.
fetch-variables: all brings the old behaviour back, and a @TaskParam does not need it: the name it spells out
is part of the set. What is left for it is a variable reaching a handler under a name no annotation carries. Such
a read is not silently answered with a null - it fails the delivery with a message naming the variable, the set
and the property, and the engine behind the API applies its retry semantics.
Where no workflow service serves a BPMN process, the id variable cannot be named, and the subscription asks for everything rather than for a set which may be missing what its handler needs.
Every subscription logs what it asks for when it opens. When somebody reports a variable their handler no longer sees, that line answers the first question.
