bpmn-dsl-kotlin
CompletedA Kotlin DSL that compiles BPMN 2.0 collaborations to an immutable AST, validates them, and emits renderable XML — with a Flowable deployment path.
What it does
bpmn-dsl-kotlin authors BPMN 2.0 collaborations as Kotlin code instead of
handwritten XML or a drawn diagram. A bpmnCollaboration { … } block compiles
to an immutable AST; BpmnValidator produces structured diagnostics with codes
and remediation hints; LayeredLayoutEngine computes BPMN DI coordinates
deterministically; and BpmnXmlWriter serializes semantic XML plus DI that
imports into bpmn-js-class viewers with zero warnings. A Flowable adapter adds
flowable: attributes through a serializer seam — the core stays
vendor-neutral — and the executable buyer process deploys to Flowable.
val c = enterpriseProcurementCollaboration()val xml = BpmnXmlWriter(FlowableExtensionSerializer) .writeToString(c, LayeredLayoutEngine().layout(c))The running example is a two-pool enterprise procurement collaboration between
a buyer organization and an external supplier: 68 + 25 flow elements across
8 lanes, 7 message flows, 3 boundary events, an embedded subprocess, and 21
process variables. ./gradlew :bpmn-example:run writes a 55 KB .bpmn file
that bpmn-moddle parses and headless bpmn-js renders (153 elements) — both
with zero warnings.
Why I built it
It is the supporting project for the tutorial series Building a Type-Safe Kotlin DSL for BPMN 2.0 — each chapter maps to one module. Beyond that, it exists because generated workflows need an authoring format that a compiler and a diff reviewer can both check: a 60-line Kotlin declaration is reviewable, refactorable, and testable in ways a 50 KB XML file or a modeler export is not.
Key features
- Type-safe internal DSL —
@DslMarkerscope control with separate node-container and flow-container scopes: a lane cannot own a sequence flow and a boundary event cannot appear inside a catch event; the compiler says so. - Immutable AST — sealed hierarchies of events, activities, and gateways; references are ids, not object graphs, so equality and serialization stay sane.
- First-class validation — a diagnostic-code registry covering ids, endpoints, reachability, gateway rules, boundary attachments, lane containment, message-flow contracts, and DI invariants; the example validates with zero errors and zero warnings.
- Deterministic layout — longest-path columns, lane-index rows, boundary events pinned to host corners, orthogonal routing with a per-pool return channel; a golden-file test guards the coordinates byte-for-byte.
- Verified against real tooling —
scripts/render-verify.mjsparses the output withbpmn-moddleand renders it in headless bpmn-js via Playwright. - Engine-neutral core, adapter seam —
EngineExtensionSerializeris the only vendor hook;bpmn-flowableis a separate module, and the library has no third-party runtime dependencies (JDK StAX only). - Seven Gradle modules —
bpmn-model,bpmn-dsl,bpmn-validation,bpmn-xml,bpmn-layout,bpmn-flowable,bpmn-example— so consumers can depend on exactly the layer they need.