Repository navigation
Deviations
Where this adapter does not deliver what VanillaBP's platform-wide contract promises. The list is longer than for
a BPMS-specific adapter, which is the point of this adapter: it is built mock-first to find out what the
Process-Engine-API would have to offer. Every entry has a full explanation in
GAPS.md, which also names what
would close it.
None of these gaps is a defect of an engine behind the API. They are properties of the API this adapter speaks. The first entry is not a gap at all. It says which parts of a BPMN file this adapter looks at, and several of the entries below rest on it.
- What this adapter reads of your model
- Workflow modules cannot be kept apart by the engine
- What the engine already holds cannot be checked
- Only one adapter id of this type is possible
- Workflow awareness cannot be probed
- An unreachable engine looks like an unknown task
- The engine never reports a workflow it started itself
- A timer is whatever the engine behind the API makes of it
- The end of a workflow is not reported
- A changed aggregate cannot be pushed
- Processes have no version, only a tag
- Cancellations are not delivered
- An error nobody catches is the engine's answer
- A transaction subprocess is handed over unread
- A delivered task does not name its BPMN process
- A renamed process is served where the task name says so
- Messages are not deduplicated by the engine
- Workflows have no observable timeline
- Older versions of a process cannot be checked
- An overdue open task is only reported
- Concurrent branches of a workflow are not pointed out
Every other VanillaBP adapter has a BPMN model type to ask, supplied by its BPMS. The Process-Engine-API has none, so this adapter reads the raw XML itself and it reads only what it needs. Everything else in a file goes to the engine byte for byte, exactly as you modelled it.
These are the parts it looks at:
- a
processmarkedisExecutable="true", for its id and its name; - the service-like tasks
serviceTask,sendTask,businessRuleTaskandscriptTask, for their id, the type of theirzeebe:taskDefinitionand a reference to a decision; - a
userTaskof the BPMN namespace, for its id, its name and the external reference of itszeebe:formDefinition; - the start events of a process: one the engine would fire on its own ends the deployment, see the engine never reports a workflow it started itself;
- the
message,signal,errorandescalationdeclarations of the file, whose names the modeuse-prefixrewrites and which VanillaBP compares against the other workflow modules of the application.
Under use-prefix the rewrite works on the same names, plus the process id and the processId of a call
activity's zeebe:calledElement.
Everything else is skipped by local name. A compensation, a standard or multi-instance loop, a data object, an event subprocess, a gateway, a transaction subprocess: the adapter walks past all of them. They reach the engine as you modelled them and run the way that engine runs them.
Which is why this wiki says nothing about them. The two Camunda adapters can list what a construct becomes, because each of them talks to one known engine. This adapter talks to whichever engine you wired behind the API, and the Process-Engine-API does not name it. A third such list would be guesswork, so there is none. If your model leans on one of these constructs, read that engine's own documentation and test against it.
The API has no tenants, no namespaces and no per-deployment scope, so VanillaBP's default name-clash-avoidance
mode by-adapter is rejected while deploying and you have to choose use-prefix or none, see
Configuration. use-prefix works without engine support, at the
price of the adapter rewriting the raw BPMN XML by element name, which covers the dialects it knows.
GAPS 15
What it costs when two applications end up sharing one task type anyway, and why a service task and a user task are answered differently there, is what happens when a task of another application reaches yours.
When an application starts, VanillaBP asks a BPMS which of the identifiers it is about to deploy somebody else already holds - a process id of another application, a message name of a module deployed years ago - and warns about what it finds. The Process-Engine-API cannot be asked that, for any kind of identifier: there is nothing to search and no model to read, so this adapter says nothing rather than half of something.
What you do get is the check which needs no engine. While a workflow module deploys, the adapter reads the message
names, signal names, error codes, escalation codes and task definitions out of its BPMN files, and VanillaBP warns
where another workflow module of the same application ends up under one of those names. That matters most under
the mode none, where nothing is prefixed, and most of all for a task definition: a task subscription of this API
matches a task type for the whole engine, so two modules sharing one task definition means the subscription of one
module takes the tasks of the other. The check reads the raw BPMN by element name, so it reaches the dialects the
adapter knows, and a decision id stays out of it because this adapter cannot prefix one either.
The BPMN process ids of what you deploy are checked too, and that check refuses where the others warn: two workflow modules whose process ids reach the engine under one id end the startup, see Configuration. A shared message name leaves both models in the engine, while two processes under one id mean the engine kept one model and lost the other, and no warning repairs that afterwards. GAPS 24
The engine is handed to the adapter as application-provided beans without any per-instance connection configuration, so two adapter ids of this type would talk to the very same engine and the boot fails with a guiding message. A migration between two engines behind the Process-Engine-API is therefore not expressible. GAPS 14
The correlation commands are final and cannot carry a PREFLIGHT_CHECK, so the adapter cannot ask whether a
workflow exists and answers optimistically with a one-time guiding WARN. In a
migration setup a wrong "yes"
would route the operation to the wrong BPMS, and VanillaBP does not let it come to that: this adapter reports that
it cannot locate workflows, so a workflow module which prioritizes it next to another adapter ends the boot with a
message naming the ways out. Use this adapter as the only configured BPMS until the gap is closed.
The same final classes are the reason message correlation has no phase one at all and travels with the default
execution mode.
GAPS 11 (why)
Command failures are untyped, so a failing task or user-task probe cannot be told apart from an engine which is down and maps to "unknown". This matters in migration setups only, where the answer decides which BPMS serves the operation. GAPS 10 (why)
Timer, signal and conditional start events cannot be served: nothing in the API says "the engine just started this
process", so VanillaBP could never ask your @WorkflowStartedByBpms method for the workflow aggregate everything
downstream is addressed by. A process carrying such a start event is rejected while deploying, with a message naming
the element and the alternatives.
A @WorkflowStartedByBpms method kept for a BPMN process id the application only declares
(secondaryBpmnProcesses) is not judged either: VanillaBP would compare it against the start events of a version
the engine still holds, and that model cannot be read here, so the check stays silent for such an id.
GAPS 16
This adapter never sees a timer fire. A timer start event is refused while deploying, which is the entry above,
and every other timer sits in the model the adapter hands the engine. The scoping rewrite this adapter does on
the raw BPMN touches the process id, a call activity's called element, message names, signal names, error codes,
escalation codes, the task definition type and the external form reference of a user task. A
timerEventDefinition is not in that list and is handed over exactly as you modelled it.
So the two questions a timer raises are the engine's to answer, and this adapter cannot answer either of them: in which time zone a value naming no zone is read, and how long after the due date the workflow actually moves on. On Camunda 7 both answers are written down, because the engine runs in the application's own JVM. Here they depend on the engine you wired behind the API.
A value carrying its own zone or offset is the one shape which does not depend on the answer, and a point in
time your application worked out and shared as an Instant is another, see
which time zone a timer counts in.
@WorkflowEnded methods are never called, because the API has no event, callback or subscription saying that an
instance ended. Deploying WARNs naming the BPMN process; the workflow itself runs normally, which is why this is
not a deployment failure.
GAPS 17
aggregateChanged throws in both phases, since the API modifies the payload of TASKS only and has no command whose
subject is a running process instance. Phase one already refuses, so the application sees the failure at its call
rather than in an outbox dispatch after the commit. Completing a task pushes the shared values, so a task the
workflow waits at is the way out.
GAPS 18
The API knows no version of a deployed process definition and no deployment order, so a
version
made of numbers, a range or a >/< boundary matches nothing and is reported once with a guiding message. An
exact tag (version = "release-2024") works where the engine supplies the task-meta key
processDefinitionVersionTag. Methods without the attribute serve every version, as everywhere else - and where the
engine supplies no tag either, they are the ONLY methods a delivery reaches: a task whose every method names
versions fails the delivery with a message saying so, rather than picking one of them.
GAPS 19
@TaskEvent CANCELED never arrives, for service tasks and user tasks alike: the subscription's termination
callback carries the task ID and no aggregate reference, so the adapter cannot tell which workflow to notify.
VanillaBP has a second way in for a BPMS which reports no cancellation. At every delivery the core asks the adapter whether the other tasks it believes are open in that workflow are still there, and reports the ones which are gone. That way stays closed here. The Process-Engine-API has no operation which asks about a task, and a failed command comes back untyped, so this adapter cannot tell "the engine does not have it" from "the engine did not answer" - and a probe which guessed would cancel the open work of every woken workflow whenever the engine hiccups. Model the cancellation instead: an event subprocess or a boundary event with a service task behind it turns the moment into a delivery, and a delivery is what this API reports reliably. Details GAPS 27 (why)
A TaskException from your handler, and a cancelTask from your code, both reach the API as a
complete-by-error command carrying the error code. What the engine then does with a code no boundary event
catches is left open by BPMN, and this adapter cannot say which answer you get, because the answer belongs to
the engine you wired behind the API.
What this adapter does is the same either way. It sends the command and reads the result only to find out whether the command itself got through; a failure there is logged as a failed completion and retried by the outbox. Nothing comes back about the error being caught or not, and nothing of VanillaBP reads your model to check that a code can be caught. So your application is not told, here as everywhere else, see where that error goes.
The two Camunda adapters answer this question on their own pages, and they answer it differently: Camunda 7 ends the workflow quietly, Camunda 8 raises an incident. Which of the two the engine behind this API resembles is worth finding out before a model relies on it.
BPMN's cancel event may be modelled inside a transaction subprocess and nowhere else. VanillaBP has no notion
of either, and transaction is one of the elements this adapter skips by name, see what this adapter reads of
your model.
So a model carrying a transaction subprocess reaches the engine as you wrote it, and what that engine makes of it is its own business. This adapter neither claims the construct nor refuses it, and it cannot say what happens.
The word cancel means something else everywhere else in these wikis, cancelTask and
WorkflowEnd.Kind.CANCELED above all, see
this cancel is not BPMN's cancel event.
The adapter expects the engine to supply the BPMN process ID as the task meta entry bpmnProcessId. Without it a
task definition used by several processes of one workflow module cannot be routed, which a guiding error explains,
and the task definition then has to be unique across the module's processes. The mode use-prefix sidesteps this,
since it scopes task definitions by their process anyway.
GAPS 6
Renaming a BPMN process leaves the old id behind in the engine, with workflows still running on it. You declare it
with secondaryBpmnProcesses so your methods keep serving those workflows, see
a renamed process.
Camunda 7 reads the old model out of its engine to find the tasks it will hand out. This adapter has no repository
to read it from, so it composes the names instead, from the task definitions your @WorkflowTask methods serve for
that id, which is what Camunda 8 does as well.
Under use-prefix with task definitions scoped per process, which is the default, that is enough: the tasks of the
old id carry the old id in their name, the adapter subscribes for those names, and every such delivery reaches the
methods of the old id. The start says which names it opened. Each name is subscribed twice, as an asynchronous task
and as a user task, because nothing outside the model says which of the two the task was.
Under none, and under use-prefix with prefix-task-definitions-per-process: false, the old and the new process
share one task name. The workflows are served, but a delivery is attributed to a deployed process, because telling
the two apart would need the bpmnProcessId task meta entry above and no engine behind this API fills it. The
start says so for every id it is true of.
One case is not served. A @WorkflowTask method wired to a BPMN element id (@WorkflowTask(id = ...)) names no
task definition, so no name can be composed for it, and those workflows stand still at their next task. The start
warns and names the two ways out: wire the method by task definition, or keep deploying the old model under its old
id until its workflows have ended.
What such an id does not get is what needs the model: no BPMN diagram and no history for a viewer, which the adapter now says instead of answering nothing, and none of the checks VanillaBP runs over a held version. The end of such a workflow is not reported either, like everywhere else here. GAPS 23 (why)
The API offers no message id, so a redelivered dispatch correlates again where the outbox retries after a crash. This concerns correlations carrying a correlation id, which VanillaBP deduplicates engine-side on other BPMS. Model catch events idempotently where a duplicate correlation would matter. GAPS 11
The API has neither a repository nor a query/history API, so the viewer is answered from what THIS application
version deployed: getProcessDefinitions reports the deployed process versioned by its deployment key,
getBpmnXml the deployed resource byte for byte, and getWorkflowHistory the definition plus a null element
history, the SPI's documented "not supported by the underlying BPMS". Three consequences: call activities cannot be
drilled into, usedByElements is never filled, and a workflow running on a definition deployed by a PREVIOUS
application version is served with the currently deployed model.
GAPS 12
and GAPS 13
VanillaBP looks at the versions a BPMS still holds when an application starts and reports the
task definitions of older versions no method serves any more. This adapter reports no version
at all (the Process-Engine-API has no version notion, see Deviations above and GAPS.md
no. 19), so the check is switched off here and outfaded-versions has nothing to name. A
dropped task definition shows up the way it did before: when a workflow reaches it. Everything else VanillaBP asks
about a held version goes the same way, since no catalog is registered: its start events, the elements which can
fork one of its workflows, and how many workflows still run on it.
Details
VanillaBP measures how long a task left open by a @TaskId handler has been waiting and says so once it passed
vanillabp.delivery.max-task-age, see
an open task and how old it is.
The API hands out a task subscription rather than a lock this adapter renews, so there is nothing to stop and no
incident to raise instead: the report is all that happens here.
A BPMN process holding more than one token has two branches writing the same workflow aggregate, and without a version attribute one of the two writes is lost without an error. Other adapters read their model while wiring and let VanillaBP warn about it. This adapter never holds a model, so it reports nothing and the hint stays silent here, for the version being deployed and for every older one the engine still runs workflows on. Everything else works: a version conflict in the transaction VanillaBP owns is recognized, named by a guiding message and passed on to the engine as a failed task, and the four ways to avoid the collision are the same, see Two writers on one aggregate. GAPS 21
