Repository navigation
Home
This adapter is what lets the VanillaBP Business Cockpit show the user tasks and the business cases of an application whose workflows run through the BPMS-agnostic Process-Engine-API. It is a cockpit adapter, not a BPMS adapter: the engine belongs to the VanillaBP Process-Engine-API adapter, and what this artifact adds is a pair of ears. It runs inside an application on VanillaBP Version 2. It listens to what the engine delivers, asks your application what a task or a case is about, and hands the result to the cockpit server.
Everything which is the same whichever engine you run on belongs to the cockpit itself: the transports to
the cockpit server, the @UserTaskDetailsProvider and @WorkflowDetailsProvider methods, the templates
the titles are rendered from. It is documented in the
Business Cockpit wiki: the two transports under
Connecting a workflow module,
the two provider methods under
Reporting workflows and user tasks,
and the templates under
Templates. Read those first. This wiki
adds what a user of the Process-Engine-API half has to know:
-
Getting started
The one dependency per platform, and what has to be there already. -
Configuration
Every key the cockpit reads, and the one this half adds. -
How a user task reaches the cockpit
Why nothing is written into your BPMN, and what has to hand this adapter a task. -
What the cockpit shows, and what it cannot
The answers this BPMS can give, and the ones it cannot.
Your application already runs on VanillaBP 2 with the Process-Engine-API adapter and already has an outbox store. Everything the cockpit observes is written into that store first and sent afterwards, so nothing is lost when the cockpit server is down.
One dependency; it pulls in the platform-neutral half of the cockpit adapter:
<dependency>
<groupId>io.vanillabp.businesscockpit</groupId>
<artifactId>businesscockpit-process-engine-api-adapter-spring-boot</artifactId>
<version>0.9.0-SNAPSHOT</version>
</dependency>The same one dependency; it is a Quarkus extension and it brings the neutral half, which is one too:
<dependency>
<groupId>io.vanillabp.businesscockpit</groupId>
<artifactId>businesscockpit-process-engine-api-adapter-quarkus</artifactId>
<version>0.9.0-SNAPSHOT</version>
</dependency>Where the cockpit server is, and what the cockpit has to know about your workflow module:
vanillabp:
cockpit:
rest:
base-url: http://localhost:8080
workflow-modules:
taxi-ride:
cockpit:
workflow-module-uri: http://localhost:8081/taxi-ride
ui-uri-type: WEBPACK_MF_REACT
ui-uri-path: /remoteEntry.js
i18n-languages:
- en
bpmn-description-language: en
initiator-source: by-applicationinitiator-source has no default, and a reporting module without it does not start. Write
by-application where your details providers set the initiator themselves, which is the usual case,
and system where this module knows no action a user causes. Nobody can work out later who caused a
case, so a case which ran without an initiator stays without one.
A workflow module which configures none of these keys is left out of everything the cockpit does, and a module which configures some of them is told at startup which one is still missing.
Nothing of this adapter appears in your BPMN files. On Camunda 7 the cockpit installs task listeners in the engine and on Camunda 8 it relies on the engine's own task listener jobs; the Process-Engine-API has neither, and it needs neither: a workflow application learns about a user task because it subscribed for one, and the task arrives with the payload the subscription asked for. So there is no listener type to look for in a diagram, and a model your author wrote is deployed the way it was written.
What the API does not have is a second seam at that subscription. A delivered task goes to exactly one subscriber - the engine picks the first subscription matching a task and remembers it as the one active for that task - so this adapter must not subscribe next to the workflow application: it would take the task away from the very code which is supposed to work on it.
The seam is therefore inside the adapter's own subscription. The VanillaBP Process-Engine-API adapter
calls io.vanillabp.pea.observation.PeaUserTaskObserver for every task it is handed and every task the
engine withdraws, and this adapter contributes a bean of that type. Your application does nothing
for this. Add the artifact, configure the cockpit for your workflow module, and the user tasks appear.
Every delivery of a process you claim is passed on, even one which no @WorkflowTask method of yours
claims. A task list shows a user task whether or not your code reacts to it.
Such a task needs one line in your configuration, though. VanillaBP ends the start when a user task of
a process you claim has no @WorkflowTask method, unless you say that something else works on it. A
@UserTaskDetailsProvider method does not count here. It only says what the cockpit shows. So mark
each task which only a task list works on, by its BPMN element id:
vanillabp:
workflow-modules:
my-module:
workflows:
MyProcess:
tasks:
ReviewTheClaim:
implemented-externally: trueThe message which ends the start names the line for every task it is missing for. The other places this key may be written are listed under A task something else serves in the VanillaBP wiki.
Only a BPMN process which one of your @WorkflowService classes claims is reported. That is the process
named in bpmnProcess, and every process named in secondaryBpmnProcesses, such as one a call activity
starts. A BPMN file may carry a further process which none of your classes claims. VanillaBP ends the
start for it unless you mark the whole process as served by somebody else:
vanillabp:
workflow-modules:
my-module:
workflows:
NightlyReview:
implemented-externally: trueSuch a process is deployed with its file and left alone. The VanillaBP adapter does not subscribe for its tasks, and the cockpit reports none of them. The same holds for a process somebody else deployed to the same engine. A subscription matches a task by its form reference. So a task of such a process can still reach your application where it shows the same form as one of your tasks. The cockpit skips that task as well.
A user task of a process which a call activity starts arrives with the variables of that process
instance. It is reported only where they carry the id of your workflow aggregate, under the name of
its @Id attribute. Neither this adapter nor the Process-Engine-API adapter passes that variable on to
a called process. The engine behind the API decides. Camunda 8 copies the caller's variables into a
called process unless the call activity says propagateAllParentVariables="false". Camunda 7 copies
nothing unless the model says so, with camunda:in variables="all" or with a camunda:in for that
variable. A task delivered without the id is dropped, and only a DEBUG line says so.
Which business case such a task belongs to depends on the workflow aggregate of the called process:
- It shares the aggregate of the process which called it. The task belongs to the caller's case. The Process-Engine-API names only the instance the task sits in and nothing above it. So the cockpit takes the case from what VanillaBP wrote down when it started the workflow of that aggregate. Where the task's instance is another one, the task sits in a called process, and the case is the one VanillaBP started. Where the first task of a case sits in the called process, the case appears with it, under the process of the caller.
- It has a workflow aggregate of its own. It is a case of its own, under the instance it sits in.
VanillaBP decides which processes share an aggregate, by the @WorkflowService classes which declare
them. The engine may name no process instance in the delivery, or VanillaBP may have written down no
start of the workflow. Then nothing can tell the two apart, and the task stays under the instance it
sits in.
The Process-Engine-API makes a promise: when an engine starts a workflow, it answers with an id, and that id is the process instance which the tasks of that workflow name. The cockpit relies on it. Where a user task names another instance than the one VanillaBP started, and the task is not part of a called process, the log says so in a WARN line, once per instance. The case then appears under the instance the task names. A change you report while the case has no open user task goes out under the id of the start, and the cockpit does not know that id. Tell the people who maintain the engine adapter. An engine which names no instance in its tasks at all gets no warning. Its cases appear under the id of your workflow aggregate, and a change reaches them while a user task is open.
What happens to a delivery: the adapter turns the task into the identifiers the cockpit addresses it
by. Your @UserTaskDetailsProvider method runs on the workflow aggregate and decides what the cockpit
shows. The finished report goes into one outbox entry, and the dispatcher later sends what that entry
holds.
sequenceDiagram
participant E as Process-Engine-API
participant S as The adapter's subscription
participant X as The cockpit's observer
participant A as Your details provider
participant O as Outbox
participant D as Outbox dispatcher
participant C as Cockpit server
E->>S: user task delivered, with its payload
S->>X: userTaskDelivered
X->>A: enrich it
A-->>X: details, aggregate saved
X->>O: one entry carrying the report, in a transaction of its own
D->>C: what the entry holds
So the report says what the task looked like when the engine handed it over. A restart before the entry is sent takes nothing from it. What arrived with the delivery is kept in memory on the node which got it, and it is read right there. Every other BPMS can be asked again later. This one cannot be asked at all, so a node which starts fresh can say nothing about a task it did not see delivered.
Your details provider runs on the engine's delivery thread, and an error in it is not ignored. The
adapter writes the error to its log and reports the delivery to the engine as failed. Your application
still hears about the task: the adapter calls your @WorkflowTask method before it lets the failure
out. VanillaBP knows a repeated delivery by its task id, so that method runs once per task.
What the engine does with a failed delivery is up to the engine. The Process-Engine-API says nothing about it. The reference implementation for an embedded Camunda 7 leaves the user task where it is, raises no incident, and offers the task to the adapter again on its next pull. So the report arrives as soon as the provider works, and until then the error keeps coming back into your log.
The end of a task is two ends, and they cost a broken provider different things. A completion is a task
the engine finished because your application asked it to with ProcessService#completeUserTask. What
that meant for the business may still be a rejection which a BPMN error event picks up. A cancelation is
the engine taking the task away without finishing it, say through a boundary event, and the task becomes
pointless.
A worker of VanillaBP's outbox asks for a completion after your transaction committed. So your call returns before the provider runs, and a failure never reaches your code. The outbox brings the entry back. An embedded engine takes part in the transaction that worker opens, so the completion rolls back with the failed report and the next attempt reports the end once the provider works. An engine which keeps the completion answers the next attempt with "this task is gone". VanillaBP then writes one warning and drops the entry, and the report of that end is lost.
A cancelation is reported once. The engine has forgotten the task by the time your provider runs, so a failure there costs the report for good and the cockpit keeps showing the task as open. Write a details provider so that it survives being called twice for one task, and so that it does not need a second try to work.
A user task the adapter is given appears in the cockpit with what the engine said about it - the assignee, the candidate users and groups, the due and follow-up dates, the variables the subscription asked for - plus the name the modeller gave the task and the process, plus whatever your details provider returns. When the engine delivers the task again because something about it changed, the cockpit is told it changed. When the task is gone, the cockpit is told it was completed, or cancelled where the engine says the task was taken away.
What it cannot do follows from what the Process-Engine-API is: a delivery API, not a query API.
| What you may expect | What happens |
|---|---|
| A business case appears when it starts | It appears with the first user task delivered for it; a workflow without user tasks never appears |
| A finished case leaves the cockpit's list of open cases | It does not: nothing reports the end of a workflow |
| A cancelation is distinguishable from a completion | Only where the engine names a reason, and the reason names the API which noticed the end. The reference engine adapter says delete for a task somebody completed in a task list, so the cockpit shows that task as cancelled |
| A broken details provider is noticed | The delivery fails, the adapter logs the error, and the engine offers the task again until the provider works. A completion comes back with its outbox entry. A cancelation is reported once, so that report is gone |
| A restart does not lose anything | Reports written before the restart are complete. After it, the node can say nothing about those tasks, so their end is never reported and getUserTask answers nothing |
| A business case is created in the cockpit once | Once, until so many newer cases passed through the node that this one was pushed out of its memory |
aggregateChanged updates every case of the aggregate |
It updates a case while one of its user tasks is open, and a case whose workflow VanillaBP started, also between two user tasks. A task this node was given is enough. For a task only VanillaBP's delivery log knows, and for a case between two tasks, the version has to be known: VanillaBP wrote it down with the task or with the start of the workflow, or this node still holds a delivery of that workflow. Otherwise the log says why, once per case |
getUserTask reads a task from the engine |
It reads what the delivery said, and answers nothing once the task is gone |
A @TaskParam of a details provider is filled |
It is, whether or not a @WorkflowTask method reads the same variable. The engine adapter asks the engine for what your details providers read as well (what a subscription asks for) |
| Two Process-Engine-API engines can be migrated between | Not yet: the adapter allows one adapter id of this type |
| A business case shows the same version as its user tasks | While this node still holds a delivery of that case, or VanillaBP wrote down the version with an open user task of it or with the start of its workflow. Otherwise the case shows what this release of your application deployed instead of the engine's version tag |
| A details provider named for a version runs for that version | Only where the engine fills a version tag and your method names that tag; every other version specification matches nothing, and nothing says so |
A @UserTaskDetailsProvider or a @WorkflowDetailsProvider method may name the versions of the BPMN
process it serves, and VanillaBP then runs it only for the workflows on those versions. What a version
is depends on the BPMS, and on this one there is very little of it.
The Process-Engine-API has no repository. There are no process definitions to count and no version
numbers. The only version which reaches the cockpit is the processDefinitionVersionTag an engine
writes into the meta map of a delivered task, and an engine writes it where it keeps one. So a user task
carries the tag of its own process, or it carries nothing, and the business case it appears with carries
the same.
A task of a called process carries the tag of the called process, because the engine tags the delivery with the model the task belongs to.
On Camunda 7 and Camunda 8 the start ends where a user task has providers and none of them covers the version the BPMS gave the deployed model. That check does not run here. This adapter reports no deployed version, so there is nothing to hold the providers against while your application starts.
That leaves one way of writing a version which works here, the tag itself:
@UserTaskDetailsProvider(taskDefinition = "pay-the-fare", version = "ride-2026-09")
public UserTaskDetails pay(
final TaxiRide ride,
final PrefilledUserTaskDetails prefilled) { ... }The tag is compared as text, so the method runs for the deliveries whose tag reads exactly like that.
Every other way of writing a version matches nothing here. version = "3", version = "1-3" and
version = ">2" need a version number, and this BPMS reports none. version = "v1.0..v2.0" needs to
know where the two tags stand in the deployment order, and that needs the list of deployed versions,
which no adapter of this BPMS can ask for.
A method which matches nothing is skipped, not reported. Having no matching provider is a normal answer for the cockpit, which then sends the details it had prefilled. The task still appears, with the names out of your BPMN and with what the engine said about it, and without whatever your method would have added. Your log is little help. A delivery which carries no version at all passes without a word, and the line VanillaBP writes at startup about a version tag no BPMS knows says the method serves no workflow, which is the one case where it does run. So the sign to watch for is a cockpit which shows less than you wrote.
So cut your providers by version only where the engine fills a version tag and your method names that same tag. Everywhere else keep one method without a version, which serves every workflow. The same application may also run on Camunda 7 or Camunda 8, where a version is a counted version and every range works, so a method written for those two is right there and dead here.
Each of these has an entry in GAPS.md of
this adapter's repository,
with what the API offers today and what would close it. That file is the basis of the conversation with
the Process-Engine-API team, and it is the honest answer to "why does my cockpit not show this": the
cockpit asks a question this engine API cannot answer yet.
