Skip to content

Latest commit

 

History

43 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

TraTech.BpmnInterpreter

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.

Table of Contents

What this project provides

This library is designed around three responsibilities:

  1. Read BPMN XML into generic BPMN elements.
  2. Build sequence graph (Sequence) with previous/next relationships.
  3. 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.

Target framework

  • .NET 8.0 (net8.0)

Quick start

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());
	}
}

Execution model

At runtime:

  1. A BPMN reader parses XML into BpmnElement records.
  2. Sequence materializes these as BpmnSequenceElement nodes.
  3. SequenceProcessor starts from all start events and processes reachable nodes.
  4. 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)

Supported BPMN elements

Known sequence element types currently materialized as concrete types:

  • startEvent
  • endEvent
  • task
  • scriptTask
  • exclusiveGateway
  • parallelGateway
  • intermediateCatchEvent
  • subProcess
  • boundaryEvent (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.

Routing decisions

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.

Data sharing with IDataMap

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 behavior

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.

Error handling and runtime guarantees

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.
  • StartAsync supports cancellation via CancellationToken.
  • Stop() requests graceful stop between element executions.
  • Processor state is reset on each StartAsync call.

Testing

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.sln

Test project and conventions:

  • src/BpmnInterpreter/TraTech.BpmnInterpreter.Tests
  • See TESTING_GUIDELINES.md for naming and structure.

Project structure

  • src/BpmnInterpreter/TraTech.BpmnInterpreter - main library
    • Abstractions/ - public contracts and base types
    • Core/ - readers, sequence graph, processor, data map
    • Extensions/ - helper extensions
  • src/BpmnInterpreter/TraTech.BpmnInterpreter.Tests - unit tests and fixtures
  • src/BpmnInterpreter/Playground - sample executable usage

Contributing

Contributions are welcome.

Recommended flow:

  1. Add or update tests for behavioral changes.
  2. Keep handler and processor behavior explicit and deterministic.
  3. Verify with dotnet test before submitting.

For behavior expectations and test style, start from:

  • src/BpmnInterpreter/TraTech.BpmnInterpreter.Tests/TESTING_GUIDELINES.md

About

BPMN Interpreter for .NET

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages