Skip to content

Integration SPI

Stephan Pelikan edited this page Oct 12, 2026 · 5 revisions

VanillaBP brings its own answer to a handful of questions. Where is a workflow aggregate stored? In which transaction does a handler run? Where does a planned call to the BPMS (business process management system) wait for your commit? These answers fit the usual application. The integration SPI (service provider interface) lets an application which is not the usual one replace one of them with its own. You rarely need it. If your aggregates live in a persistence VanillaBP already supports, you need nothing from this page.

Technically it is the counterpart of the SPI for Java. The SPI for Java is the API your code calls and is annotated with, for example @WorkflowTask and ProcessService. Here you find interfaces which your code implements instead. VanillaBP then calls your answer where it needs something only your application can know.

  1. Why it is an artifact of its own
    Why these interfaces are not part of the SPI for Java, although business code implements them.
  2. What you can implement
    The five contracts, and the page which explains each of them.
  3. How it reaches your build
    The support modules, and why a workflow module depends on one of them.

Why it is an artifact of its own

The SPI for Java is the API a business application is written against. Every entry of its decision log is a promise to that application. It has a release line of its own, and version 2 of the platform did not change it. A workflow module compiles against the same artifact as before.

The integration SPI describes contracts of the runtime which calls them. An outbox and a log of processed deliveries exist because of how VanillaBP works inside, and they grow with it. So they are released with the platform integration, as io.vanillabp:vanillabp-integration-spi in the package io.vanillabp.integration.spi. If they were part of the SPI for Java, every addition to the runtime would change the API which business code is written against. And a workflow module would see interfaces it has no reason to know about.

What you can implement

Interface What you take over Explained on
AggregatePersistenceAware<A> loading and saving an aggregate in a persistence VanillaBP does not know, for example an event store or a ledger Workflow aggregates
TransactionRunner, TransactionRunnerAware<A> the unit of work VanillaBP puts around everything it does with one aggregate, for stores which the platform's transaction does not reach Workflow aggregates
PhaseTwoOutbox, PhaseTwoOutboxAware<A> the store which holds work planned for after your commit, both calls to a BPMS and an extension's own operations; useful if you already have outbox infrastructure Spring Boot, Quarkus
TaskDeliveryLog, TaskDeliveryLogAware<A> the record which stops a task delivered again from running your handler twice Workflow tasks
WorkflowAdapterCache remembering which BPMS holds a workflow, in a cache your cluster shares BPMS migration

The *Aware<A> variants answer the same question per aggregate, not for the whole application. An application with more than one kind of persistence needs that. The most specific implementation wins. The aggregate class an implementation names may be a superclass or an interface, so one bean can serve a whole workflow module.

The election cache is part of the integration SPI on purpose and not something internal. It is the recommended answer when asking the BPMS becomes the bottleneck of a cluster. And that answer has to be infrastructure which the application already runs (why).

A few more types are in the same artifact: Election, PhaseOperation, PhaseTwoCall, StoredKey. An outbox store of your own has to use them. They are documented where they are needed. A workflow service never touches them.

How it reaches your build

You add no dependency for it. Both platform support modules bring the artifact as a transitive dependency:

  • io.vanillabp:vanillabp-spring-boot-support on Spring Boot
  • io.vanillabp:vanillabp-quarkus-support on Quarkus

A pure workflow module depends on a support module. The support module provides what the workflow module needs to compile: the VanillaBP APIs. On purpose it does not provide the API of any BPMS, so you cannot break the hexagonal architecture by accident. On both platforms it is a plain library without platform infrastructure. This is why the application module needs more on top of it: the adapter on Spring Boot, and the core extension plus the adapter on Quarkus.

In the module which builds the application the artifact arrives anyway. On Spring Boot it comes through the adapter, on Quarkus through vanillabp-quarkus-integration. In both cases io.vanillabp:vanillabp-bom manages its version, so you never write a <version>.

Clone this wiki locally