A lightweight BPMN execution library for .NET.
TraTech.BpmnInterpreter lets you load BPMN XML, build an in-memory sequence graph, and execute it through pluggable handlers for each BPMN element type.
- What this project provides
- Target framework
- Quick start
- Execution model
- Supported BPMN elements
- Routing decisions
- Data sharing with IDataMap
- Boundary event behavior
- Error handling and runtime guarantees
- Testing
- Project structure
- Contributing
This library is designed around three responsibilities:
- Read BPMN XML into generic BPMN elements.
- Build sequence graph (
Sequence) with previous/next relationships. - Process sequence (
SequenceProcessor) with custom handlers.
It is a good fit when you want to drive workflow behavior from BPMN definitions while keeping domain logic in .NET code.
- .NET 8.0 (
net8.0)
The following example shows the end-to-end flow:
using System.Text;
using System.Xml.Linq;
using TraTech.BpmnInterpreter.Abstractions;
using TraTech.BpmnInterpreter.Core;
using TraTech.BpmnInterpreter.Core.Elements;
using TraTech.BpmnInterpreter.Core.SequenceElements;
// 1) Load BPMN XML (use your own BPMN definition)
var xml = """
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="Process_1">
<startEvent id="Start_1">
<outgoing>Flow_1</outgoing>
</startEvent>
<task id="Task_1" name="Do work">
<incoming>Flow_1</incoming>
<outgoing>Flow_2</outgoing>
</task>
<endEvent id="End_1">
<incoming>Flow_2</incoming>
</endEvent>
<sequenceFlow id="Flow_1" sourceRef="Start_1" targetRef="Task_1"/>
<sequenceFlow id="Flow_2" sourceRef="Task_1" targetRef="End_1"/>
</process>
</definitions>
""";
IEnumerable<BpmnElement> bpmnElements;
var reader = new BpmnProcessReader("http://www.omg.org/spec/BPMN/20100524/MODEL");
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
var doc = XDocument.Load(ms);
bpmnElements = reader.Read(doc);
}
// 2) Build executable sequence graph
var sequence = new Sequence(bpmnElements);
// 3) Register handlers and execute
var processor = ISequenceProcessorBuilder
.Create<SequenceProcessorBuilder>()
.UsingElementHandler(StartEvent.ElementTypeName, new StartHandler())
.UsingElementHandler(Task.ElementTypeName, new TaskHandler())
.UsingElementHandler(EndEvent.ElementTypeName, new EndHandler())
.WithDefaultElementHandler(new DefaultHandler())
.WithBpmnSequence(sequence)
.Build<SequenceProcessor>();
await processor.StartAsync();
// ---- Example handlers ----
public sealed class StartHandler : ISequenceElementHandler
{
public Task<SequenceNextDecision> ProcessAsync(
BpmnSequenceElement currentElement,
ISequenceElementHandlerContext context,
CancellationToken cancellationToken = default)
{
context.DataMap.Set("counter", 1);
return Task.FromResult(SequenceNextDecision.UseDefault());
}
}
public sealed class TaskHandler : ISequenceElementHandler
{
public Task<SequenceNextDecision> ProcessAsync(
BpmnSequenceElement currentElement,
ISequenceElementHandlerContext context,
CancellationToken cancellationToken = default)
{
var counter = context.DataMap.Get<int>("counter");
context.DataMap.Set("counter", counter + 1);
return Task.FromResult(SequenceNextDecision.UseDefault());
}
}
public sealed class EndHandler : ISequenceElementHandler
{
public Task<SequenceNextDecision> ProcessAsync(
BpmnSequenceElement currentElement,
ISequenceElementHandlerContext context,
CancellationToken cancellationToken = default)
{
return Task.FromResult(SequenceNextDecision.None());
}
}
public sealed class DefaultHandler : ISequenceElementHandler
{
public Task<SequenceNextDecision> ProcessAsync(
BpmnSequenceElement currentElement,
ISequenceElementHandlerContext context,
CancellationToken cancellationToken = default)
{
return Task.FromResult(SequenceNextDecision.UseDefault());
}
}At runtime:
- A BPMN reader parses XML into
BpmnElementrecords. Sequencematerializes these asBpmnSequenceElementnodes.SequenceProcessorstarts from all start events and processes reachable nodes.- Handlers can control next scheduling using
SequenceNextDecision.
The handler context (ISequenceElementHandlerContext) provides access to:
DataMap(execution-scoped key/value store)Sequence(graph currently executed)SequenceProcessor(processor instance)
Known sequence element types currently materialized as concrete types:
startEventendEventtaskscriptTaskexclusiveGatewayparallelGatewayintermediateCatchEventsubProcessboundaryEvent(attached as boundary to owning element)
If an element type is part of sequence flow but has no dedicated concrete type, it falls back to base BpmnSequenceElement.
ISequenceElementHandler.ProcessAsync returns SequenceNextDecision:
SequenceNextDecision.UseDefault()- Schedule the element's default outgoing nodes.
SequenceNextDecision.WithNext(...)- Override outgoing nodes and schedule only the provided elements.
SequenceNextDecision.None()- Schedule no next element.
This is especially useful for gateway logic.
Use IDataMap to share state across handlers:
Set(key, value)overwrites existing value.TrySet(key, value)writes only if key does not exist.Get<T>(key)retrieves typed value (throws on missing/incompatible type).TryGet<T>(key, out value)safely attempts retrieval.
Boundary event handlers (IBoundaryEventHandler) are executed for side effects.
Important behavior:
- Boundary handlers do not decide routing.
- Main routing decisions are controlled by the main element handler's
SequenceNextDecision.
Current runtime contracts include:
- Starting execution requires at least one start event and one end event.
- Missing element handler and no default handler => throws
KeyNotFoundException. - Missing boundary handler and no default boundary handler => throws
KeyNotFoundException. StartAsyncsupports cancellation viaCancellationToken.Stop()requests graceful stop between element executions.- Processor state is reset on each
StartAsynccall.
The repository includes unit tests for readers, sequence construction, routing behavior, cancellation, and handler contracts.
Run tests from repository root:
dotnet test src/BpmnInterpreter/TraTech.BpmnInterpreter.slnTest project and conventions:
src/BpmnInterpreter/TraTech.BpmnInterpreter.Tests- See
TESTING_GUIDELINES.mdfor naming and structure.
src/BpmnInterpreter/TraTech.BpmnInterpreter- main libraryAbstractions/- public contracts and base typesCore/- readers, sequence graph, processor, data mapExtensions/- helper extensions
src/BpmnInterpreter/TraTech.BpmnInterpreter.Tests- unit tests and fixturessrc/BpmnInterpreter/Playground- sample executable usage
Contributions are welcome.
Recommended flow:
- Add or update tests for behavioral changes.
- Keep handler and processor behavior explicit and deterministic.
- Verify with
dotnet testbefore submitting.
For behavior expectations and test style, start from:
src/BpmnInterpreter/TraTech.BpmnInterpreter.Tests/TESTING_GUIDELINES.md