Skip to content
Β 
Β 

Repository files navigation

CIB seven Bike-Leasing Blueprint

Note

🚧 Work in progress. This is a solution template β€” a reference to fork and build on, for our consultants and anyone else β€” not a product that ships. It's still being fleshed out, so parts may be incomplete and it may not yet fully demonstrate what it's meant to. Treat it as a living example, and expect it to keep evolving.

A ready-to-fork starting point for automating a business process on CIB seven (the community fork of Camunda 7) with an embedded engine, Spring Boot and Kotlin β€” one complete, runnable, production-shaped BPMN service you can clone and make your own.

This variant talks to the engine through the process-engine-api (bpm-crafters) abstraction instead of the plain CIB seven APIs: the BPMN service tasks are external tasks consumed by @ProcessEngineWorker beans, and starting instances / correlating messages / completing user tasks all go through the process-engine-api. The engine is still embedded, so Cockpit/Tasklist and the /engine-rest API remain available.

The scenario

Meet MiraVelo β€” a (fictional) lifestyle bike brand for the quarter-life-crisis crowd: gravel bikes for the weekends that count, road bikes for everyone who just wants to feel the asphalt. MiraVelo sells its bikes on a leasing model for private and corporate customers, and this project automates that leasing application from the first request to an active lease.

It's a made-up company, so nobody gets hurt when the DMN politely declines a 15-year-old's application for a carbon road bike.

What's inside

Most engine examples stop at a happy-path service task. This one deliberately walks through the broad palette of BPMN elements you actually meet in real processes β€” and the engineering scaffolding around them β€” so a new project starts from something complete instead of a blank page:

The bike-leasing process

  • a message start event, service tasks (process-engine-api workers) and a DMN business-rule task;
  • an embedded sub-process with an event-based gateway (sign vs. a 14-day deadline) and a non-interrupting 7-day reminder timer;
  • a parallel fork/join, and a user task with a Camunda Form β€” completable in the Tasklist or via a REST endpoint;
  • compensation / SAGA handlers guarded by error and escalation boundary events;
  • a call activity into a second process, a message event sub-process (application withdrawal), and a terminate end event.

How it's built

service/
  common-architecture-tests/   reusable ArchUnit + Konsist rule suite (src/main)
  app/                         the CIB seven bike-leasing service (hexagonal)
    adapter/inbound/rest        domain REST controllers
    adapter/inbound/cibseven    process-engine-api workers for the BPMN service tasks
    adapter/outbound/cibseven   drives the engine through the process-engine-api
    adapter/outbound/db         JPA persistence (leasing applications + bike portfolio)
    adapter/outbound/dealer     simulated bike dealer (stock check + order)
    adapter/process             generated *ProcessApi (bpmn-to-code) + engine config
    application/{port,service}  use-case ports and their services
    domain/{leasing,bike}       pure domain model
    resources/{bpmn,dmn,forms}  the process models and Camunda Forms
    resources/db/migration      Flyway forward-only migrations
openapi/openapi.json           generated by a test, committed, drift-gated in CI
bruno/                         REST scenarios (happy-path / escalation / abort / not-solvent / …)
package.json                   BPMN linting (bpmnlint) β€” tooling at the repo root
stack/                         Postgres dev stack (docker compose)
docs/{README.md, adr/, assets/} ADR index + records + the process diagram
.github/                       pre-merge pipeline + Dependabot
  • Stack: Kotlin 2.4 Β· Spring Boot 4 Β· CIB seven 2.2 (embedded) Β· process-engine-api (bpm-crafters) Β· PostgreSQL Β· Gradle with a libs.versions.toml version catalog.
  • Generated process API: the bpmn-to-code Gradle plugin turns each .bpmn into a typed *ProcessApi object, so element ids, messages, timers and variables are compile-checked constants used by both delegates and tests.
  • Forms: Camunda Forms (.form) are deployed with the process and render in the CIB seven Tasklist/Cockpit for the user tasks.
  • BPMN linting: bpmnlint (bpmnlint:recommended plus the camunda-platform-7 and @miragon/rules plugins) gates the .bpmn models. The tooling lives at the repo root β€” npm ci && npm run lint:bpmn β€” and runs in CI before the Gradle build.
  • API contract: springdoc generates openapi/openapi.json from the controllers; it is committed and drift-gated in CI, so any REST change that isn't regenerated fails the build.
  • Database: schema is owned by Flyway forward-only migrations (resources/db/migration); Hibernate only validates.
  • Observability: Spring Boot Actuator exposes health (with liveness/readiness probes) and Prometheus metrics under /actuator.

Design decisions

  • Hexagonal architecture keeps the engine and framework at the edges: the domain and use cases never depend on CIB seven, so business logic is testable and the engine is replaceable. The :service:common-architecture-tests module enforces this with ArchUnit (bytecode: layering, dependency direction, naming) and Konsist (source: one declaration per file, no wildcard imports) β€” one line wires it into a service: class ArchitectureTest : ServiceArchitectureTest(...).
  • Unit tests (JUnit 5 + MockK) cover every domain type, application service and adapter with given/when/then comments and shared testLeasingApplication(...) builders β€” controllers via @WebMvcTest, persistence via @DataJpaTest. The workers are covered by the process tests.
  • Mutation testing (pitest) gates PRs at a score of 80: coverage says a line ran, mutation says a test would have noticed. It runs diff-scoped on PRs and a full-module sweep nightly.
  • Process tests (cibseven-bpm-assert) drive the deployed model β€” timers and async continuations are fired and messages correlated by hand, while the real @ProcessEngineWorker beans consume the external service tasks β€” covering happy-path, escalation, abort, DMN rejection, and the bike-unavailable β†’ alternative-selection loop.
  • Model validation (bpmn-to-code-testing) checks the .bpmn models structurally at build time (BpmnRules.all() plus a custom rule requiring every service task to be an external task with a topic).
  • Bruno + CI proves the same scenarios against the running app: domain REST endpoints drive the business actions, and the CIB seven /engine-rest API completes user tasks and fires timer jobs so the whole flow runs in the pipeline without real 14-day waits.
  • Dependabot keeps Gradle, the Postgres image and GitHub Actions current.

Every non-obvious decision is recorded as an Architecture Decision Record β€” start at the docs index and its ADRs (0001–0011) to read the why before changing the what.

Run it

# 1. start Postgres
docker compose -f stack/docker-compose.yml up -d

# 2. run the app (CIB seven Cockpit/Tasklist at http://localhost:8080/camunda, admin/admin)
./gradlew :service:app:bootRun

# 3. lint the BPMN models (tooling lives at the repo root)
npm ci && npm run lint:bpmn

# 4. drive the scenarios (build + arch + process tests first, then the REST flows)
./gradlew build
cd bruno && npx --yes @usebruno/cli@4.0.0 run . --env local -r

Prefer containers? ./gradlew :service:app:bootBuildImage builds an OCI image (cibseven-process-engine-api-example/app:…) with Spring's buildpacks β€” no Dockerfile β€” to run against the Postgres dev stack. See CONTRIBUTING.md for the details.

Start a case with POST http://localhost:8080/api/bike-leasing ({ "customerName": …, "email": …, "age": 35, "monthlyNetIncome": 3500, "bikeId": "BIKE-900", "bikeModel": "Gravel Explorer 900" }).

The age and monthlyNetIncome feed the checkCreditRating DMN; the bikeId identifies the bike and is the only bike attribute the engine ever carries. The descriptive bikeModel lives in a separate bike portfolio aggregate (its own bike_portfolio table, keyed by bikeId) β€” never as a process variable β€” and GET /api/bike-leasing/{id} resolves it back from there. Alongside it a small set of read endpoints round out the API: GET /api/bike-leasing lists applications (paged), GET /api/bikes returns the bike portfolio, and GET /api/tasks/clarify-alternative lists the open clarification tasks. Every endpoint is described by the committed, drift-gated openapi/openapi.json.

If the requested bike is out of stock, the Clarify alternative with customer user task can be resolved two ways, a deliberate contrast:

  • the recommended path β€” a client calls POST …/api/bike-leasing/{id}/clarify-alternative, which routes through the domain (persisting the chosen alternative) before completing the task; versus
  • the form-only path on clarify-return in cancel-bike-order.bpmn, kept as a counter-example: completing it via the Camunda Form or /engine-rest never touches the domain, so its data lands only in process variables (see the bpmn:documentation on each task).

Bike availability itself is decided by a BikeDealerPort outbound adapter (checkAvailability / order) whose small out-of-stock deny-list drives the branch.

Incident demo

Want to teach transaction boundaries, retries and incidents? Submit a request for the poison bike BIKE-FAIL: the simulated dealer "outage" fails the Order bike from dealer external task, its retries count down to zero (3 attempts, per the adapter's retry policy), and an incident appears in the Cockpit to analyze and retry. A ready-to-run Bruno collection lives in bruno/06-incident-demo/.

Contributing

Contributions are welcome. Please open an issue to discuss substantial changes first, keep the architecture tests green (./gradlew build), and use Conventional Commits for commit messages and PR titles.

License

Licensed under the MIT License.

About

A ready-to-fork bike-leasing blueprint on Operaton with an embedded engine, wired through the bpm-crafters process-engine-api β€” one complete, production-shaped BPMN service in Spring Boot & Kotlin 🚲

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages