Skip to content

Connecting a workflow module

Stephan Pelikan edited this page Oct 15, 2026 · 6 revisions

A workflow module reaches the cockpit through one dependency and a handful of configuration keys. This page is the setup. What your business code then writes is on Reporting workflows and user tasks.

  1. What has to be there already
    The three things the cockpit adapter builds on, and why it needs each of them.
  2. The one dependency
    Which artifact to add, per BPMS and per platform.
  3. The smallest configuration which works
    Where the cockpit server is, and what it has to know about your module.
  4. REST, Kafka or a way of your own
    The ways a report travels, and how to choose one.
  5. How a module registers itself
    What happens at startup, and what a cockpit which is down changes about it.
  6. What the cockpit asks back
    The endpoints your module answers once it is registered.

What has to be there already

Your application runs on VanillaBP Version 2, with the BPMS adapter for the engine your workflows run on. The cockpit adapter listens to that adapter's engine; it does not run anything itself.

Your application has an outbox store. Every report is written into it first and sent afterwards, in the same transaction as the workflow aggregate the report is about. That is what makes a rolled back workflow report nothing and a cockpit server which is down lose nothing. An application whose aggregates live in more than one store is fine: each report goes into the store of the case it belongs to.

Your workflow module has an id, which is VanillaBP's own, and it is the id the cockpit knows it by. An application with several workflow modules configures each of them.

The one dependency

One artifact per BPMS and platform. It brings the neutral half of the cockpit adapter with it, and that half brings io.vanillabp.businesscockpit:spi-for-java, so the annotations your business code uses are on the classpath without a second dependency.

BPMS Spring Boot Quarkus
Camunda 7 businesscockpit-camunda7-adapter-spring-boot businesscockpit-camunda7-adapter-quarkus
Camunda 8 businesscockpit-camunda8-adapter-spring-boot businesscockpit-camunda8-adapter-quarkus
Process-Engine-API businesscockpit-process-engine-api-adapter-spring-boot businesscockpit-process-engine-api-adapter-quarkus

All of them are published under the group io.vanillabp.businesscockpit. Which version to pick, and what the half for your engine does with your models, is in that adapter's own wiki: Camunda 7, Camunda 8, Process-Engine-API. The Camunda 8 adapter is published once per Camunda 8 minor, so its version carries the line it was built for.

<dependency>
  <groupId>io.vanillabp.businesscockpit</groupId>
  <artifactId>businesscockpit-camunda7-adapter-spring-boot</artifactId>
  <version>0.9.0-SNAPSHOT</version>
</dependency>

The smallest configuration which works

Two things: 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
          - de
        bpmn-description-language: en
        initiator-source: by-application

workflow-module-uri is the address the cockpit reaches your module at, both for the questions it asks and for the forms it loads. ui-uri-type and ui-uri-path say how the forms are served; User task forms and status sites is about those two. i18n-languages names the languages your titles are reported in, and bpmn-description-language says which language the names in your BPMN files are written in.

initiator-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. Reporting workflows and user tasks says where the value comes from and what happens when it is missing at runtime.

A module whose user interface the cockpit cannot load writes the whole address of one of its own pages as ui-uri-path instead, starting with http:// or https://. Such an address is opened as it is, and opening a task takes the user there in a window of its own. ui-uri-type then carries the name your cockpit user interface agreed on for such modules. Your details provider may hand out an address per task, so that the user lands on the very ticket the task is about.

A gap here ends the boot with a message naming the key which fills it, and every gap of the application is reported in one boot rather than one at a time. A workflow module which configures none of these keys reports nothing at all, and the log says so while the application starts. Every key, and the four levels a value may be written at, is on Configuration.

REST, Kafka or a way of your own

A report travels the way you choose. Two ways come with the cockpit adapter, and you configure one of them. Neither of them, or both, ends the boot with a message naming the keys of both. The third way is one you write yourself, and it needs no key at all.

REST is the straightforward one. vanillabp.cockpit.rest.base-url is the address of the cockpit server's BPMS API, and choosing that key chooses this way. The report is a POST, and the answer says whether the cockpit took it. A cockpit which is down means a failed call, the outbox entry stays, and it is repeated with a backoff until the server takes it. The connection is yours to describe: basic authentication or the client credentials flow, timeouts, a proxy, a truststore.

Kafka is the other one. vanillabp.cockpit.kafka.bootstrap-servers names the brokers and chooses this way, and three topics carry the three kinds of report. The adapter builds its own producer, so anything else the producer needs goes into vanillabp.cockpit.kafka.properties.* and is handed to the Kafka client unchanged. Kafka takes the cockpit out of the sending path: the broker accepts the report and the cockpit reads it when it is up again.

Which one to take is mostly an operations question. REST is one less thing to run, and it tells you straight away whether the cockpit understood the report. Kafka is the obvious choice where your other services already talk over it.

Your own way is a bean of type BusinessCockpitTransport. Where the application has one, the cockpit adapter reports through it and leaves both shipped transports alone. The interface has one method per kind of report, and each of them is called while an outbox entry is dispatched. Your transport is therefore handed a finished report, and it inherits what the dispatch does around it. A call which fails is repeated with a backoff. The call runs in the transaction of the workflow aggregate. Several reports about one user task have already collapsed into the one you are given.

On Spring Boot the bean is an ordinary one, and the shipped transport steps aside for it the way version 1's publishing beans did:

@Bean
public BusinessCockpitTransport ourOwnTransport() {
    return new OurOwnTransport();
}

On Quarkus it is a @Singleton class or a @Produces method. The adapter declares its own transport as ArC's @DefaultBean, so yours wins without any qualifier.

Such an application configures neither vanillabp.cockpit.rest.base-url nor vanillabp.cockpit.kafka.bootstrap-servers, and it starts all the same. The boot says that the reports go through the bean you brought. Where one of the two keys is set as well, the adapter does not read it and the boot says so, because a key nobody reads is worse than no key. Both keys next to your own bean end the boot, since nothing then says which of the two transports you meant.

To send what a shipped transport sends and do something more with it, wrap it. Ask for the bean of type BusinessCockpitConfiguration and build the shipped transport from it:

@Bean
public BusinessCockpitTransport ourOwnTransport(
        final BusinessCockpitConfiguration configuration) {
    return new OurOwnTransport(BusinessCockpitAssembly.transportOf(configuration));
}

BusinessCockpitTransport is a published interface of the cockpit adapter. It is not changed without an upgrade note, so a transport of your own is as safe to keep as the configuration keys are.

How a module registers itself

A workflow module tells the cockpit about itself once the application is up. The registration is an outbox entry like every other report, so a cockpit server which is down while your application starts does not stop it from booting. The entry waits and is repeated.

The registration carries the module id, the address the module answers at, and how its user interface is served. From that the cockpit opens a path of its own for the module and forwards everything below it, which is how the browser loads your forms without ever talking to your application directly.

It happens after the workflow modules are deployed rather than during the deployment. On Quarkus the outbox store creates its table in a startup step of its own, and an entry written any earlier would find no table. Both platforms do it at the same point, so the two behave alike.

What the cockpit asks back

A registered module is asked questions, and it answers them on workflow-module-uri. The endpoints are generated from an OpenAPI specification, so a module implements the parts it cares about and leaves the rest.

Two of them matter to most applications. One says which groups may see this workflow module at all, which is what the WorkflowModuleDetailsProvider bean answers, described on Reporting workflows and user tasks. The other serves the user interface of the module, which the cockpit's proxy forwards to.

Everything else the cockpit knows about your tasks and cases it knows because you reported it. That is the next page.

Clone this wiki locally