Skip to content

Configuration

Stephan Pelikan edited this page Oct 7, 2026 · 24 revisions

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.

  1. The adapter's properties
    Every key of vanillabp.adapters.<id>.*, and what each of them decides.
  2. The BPMN model for this adapter
    What your model has to carry so this adapter wires it, which is close to nothing.
  3. 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 is a page of its own, What becomes of a shared value.
  4. Which engine does it talk to?
    The in-memory fake by default, and how to put a real engine behind the API.
  5. Keeping workflow modules apart
    Which name-clash-avoidance modes work here, and which one this API cannot express.
  6. What happens when a task of another application reaches yours
    The refusal, and why a service task and a user task are answered differently.
  7. A task delivered twice runs your handler once
    What identifies a delivery here, so a redelivery is answered from the record.
  8. What a subscription asks for
    What the adapter subscribes to, and what it polls for.
  9. What the adapter remembers about your models
    The names and the user tasks it read at deployment, and who can ask for them.

The adapter's properties

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 BPMN model for this adapter

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 user task of a claimed process needs a method or implemented-externally: true (a task something else serves), and one without a form reference can only be marked, since 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)

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.

What reaches the engine

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.

Nobody but 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. Looking a workflow up there therefore means looking for that payload entry, under the name of your aggregate's ID attribute and with the id your application knows the workflow by. What VanillaBP makes of such a name is the platform's business and is written down there: 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 past VanillaBP shows itself at its first task instead: that delivery fails, naming the id it looked for and the aggregate class it found nothing under.

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 What becomes of a shared value.

Which engine does it talk to?

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.

Keeping workflow modules apart

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 hands VanillaBP the workflow module and the BPMN process id of everything it is about to deploy, and 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! followed by both sides, the mode which produced them and the fix. Rename one of the two process ids, or let the modules use use-prefix, which puts the module id in front of every process id so the ids differ by themselves. Mind where the startup stops: the second of the two modules is the one which fails, so the first one is already in the engine. The refusal cannot be switched off, because 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.

What happens when a task of another application reaches yours

A task subscription of the Process-Engine-API matches a task type across the whole engine, and there is no tenant behind it. So where two applications deploy a BPMN process of the same name, each of them is served the other's tasks, and the mode use-prefix is what keeps that from happening.

If it does happen, the application which got the task reads the workflow aggregate id out of the delivery and looks it up in its own database. It finds nothing, and VanillaBP refuses the delivery. The message names the workflow aggregate, the adapter, the task, the BPMN process and the workflow module, so you can look the workflow up in the other application, and then it 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. The first of the two comes with 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, and the difference is the API's:

Kind What happens
service task The task is failed 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 it and raises an incident if it has them; an engine which ignores the count applies its own default, which is what would have happened anyway
user task Nothing at the engine. UserTaskCompletionApi has completion and completion by error and no way to refuse, and both of those would move somebody else's workflow on. The adapter writes one warning naming the workflow and leaves the task untouched

The refusal is loud on purpose. Handing the task back quietly, so that the delivery costs the owning application nothing, means nobody ever learns that two applications share this engine and take each other's work. How often it happens is a number rather than 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, because that record would sit in the application which wrongly got the task while whoever investigates reads the records of the application which owns the workflow.

A task delivered twice runs your handler once

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).

What a subscription asks for

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 @TaskParam of 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.

What the adapter remembers about your models

The engine behind this API cannot be asked what was deployed to it, not even right after the deployment (Deviations and GAPS 12). So the adapter keeps what VanillaBP's deployment pipeline read at boot, and that record is what serves the viewer API and anything else which has to say more about a task than the engine does.

What it can be asked about your models, all out of the bytes it read once:

  • a process, by its workflow module and its BPMN process id, with the name a modeller wrote on it;
  • a user task, by the BPMN element id;
  • a user task, by the external zeebe:formDefinition reference - the key this adapter subscribes it under.

A form reference is not unique inside a workflow module: two processes may show the same form. That lookup therefore answers a collection, and so does the one by element id, because nothing keeps two processes of one module from reusing an id either.

You get the plain identifiers you wrote, never the prefixed ones the deployed bytes carry under a name-clash avoidance mode, and a module's processes are in the record from the moment the adapter deployed that module - which is before any subscription of your application opens.

What a delivered task says about itself is the other half, and the Process-Engine-API leaves that to the engine: it defines the keys a subscription may be RESTRICTED by and none of the keys a delivery carries. The adapter spells out the set 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 and the follow-up date - and 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 which is worth showing anyway.

Clone this wiki locally