FlowSmith.Runtime 0.1.1

dotnet add package FlowSmith.Runtime --version 0.1.1
                    
NuGet\Install-Package FlowSmith.Runtime -Version 0.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FlowSmith.Runtime" Version="0.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FlowSmith.Runtime" Version="0.1.1" />
                    
Directory.Packages.props
<PackageReference Include="FlowSmith.Runtime" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FlowSmith.Runtime --version 0.1.1
                    
#r "nuget: FlowSmith.Runtime, 0.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FlowSmith.Runtime@0.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FlowSmith.Runtime&version=0.1.1
                    
Install as a Cake Addin
#tool nuget:?package=FlowSmith.Runtime&version=0.1.1
                    
Install as a Cake Tool

FlowSmith

CI Build NuGet Packages License: MIT

Lightweight workflow engine for .NET with SQL Server persistence.

Overview

FlowSmith v0.1 is a minimal workflow execution engine that supports:

  • Linear multi-step backend workflows
  • Retry with exponential backoff
  • Compensation (reverse execution on failure)
  • SQL Server persistence (ADO.NET, no ORM)
  • Leasing for distributed execution
  • Read-only monitoring UI (Blazor Server)

Architecture

Projects

  1. FlowSmith.Core - Shared contracts and domain models

    • Workflow DSL abstractions (Workflow, IFlowStep, IFlowBuilder)
    • Persistence contracts (IInstanceStore, ILeaseStore, IStepExecutionStore)
    • Application service contracts (IWorkflowCommandService, IWorkflowQueryService)
    • DTOs for queries and monitoring
    • No dependencies (pure contracts)
  2. FlowSmith.Runtime - Execution runtime and persistence

    • SQL Server persistence implementations (ADO.NET)
    • Start/Restart command services
    • Query services for monitoring
    • Extension methods for quick setup:
      • AddFlowSmithRuntime(connectionString) - register all services
      • MapFlowSmithRuntimeApi() - register HTTP API endpoints
    • Does NOT execute steps
    • Depends on: Core
  3. FlowSmith.Worker - Background worker library

    • Polling loop with leasing
    • Workflow execution engine
    • Retry and compensation logic
    • SQL Server persistence implementations (ADO.NET)
    • Uses DI for step instantiation
    • Extension methods for quick setup:
      • AddFlowSmithWorker(connectionString) - register all services
      • RegisterWorkflow<T>(name) - register workflow types
    • Depends on: Core
  4. FlowSmith.UI - Monitoring interface (Blazor Server)

    • Read-only dashboard
    • Instance list and details
    • Step execution timeline
    • Does NOT execute workflows
    • Depends on: Core

Examples Projects

Located in examples/ folder:

  1. FlowSmith.Examples - Sample workflows and worker host

    • OrderProcessingWorkflow (4-step with compensation)
    • Demonstrates retry and compensation patterns
    • Worker host implementation using AddFlowSmithWorker()
    • Depends on: Core, Worker
  2. FlowSmith.Api - REST API for commands and queries

    • Start/Restart workflow endpoints
    • Query endpoints for monitoring
    • Uses FlowSmith.Runtime services via AddFlowSmithRuntime()
    • Hosts FlowSmith.UI Blazor components
    • Depends on: Core, Runtime, UI, ServiceDefaults
  3. FlowSmith.AppHost - .NET Aspire orchestration

    • Orchestrates all services and dependencies
    • Manages SQL Server container
    • Provides monitoring dashboard
    • Depends on: Api (as "flowsmith-api"), Examples (as "flowsmith-worker")
  4. FlowSmith.ServiceDefaults - Aspire service defaults

    • OpenTelemetry configuration
    • Health checks
    • Service discovery
    • Shared by: Api, Examples

Key Principles

  • Deterministic execution: State is fully persisted before each step
  • Crash-safe: Can recover from crashes at any point
  • Explicit SQL: All queries are hand-written, no ORMs
  • Leasing via SQL Server: Uses UPDLOCK + READPAST + ROWLOCK
  • No reflection magic: Explicit workflow registry
  • Worker uses DI: Steps resolved via IServiceProvider

Getting Started

  1. Install Aspire workload:

    dotnet workload install aspire
    
  2. Ensure Docker Desktop is running (required for SQL Server)

  3. Run AppHost:

    dotnet run --project examples/FlowSmith.AppHost/FlowSmith.AppHost.csproj
    

Aspire will automatically:

  • Start SQL Server in a container
  • Create and initialize the database
  • Start FlowSmith.Api (runtime API + UI) and FlowSmith.Examples (worker)
  • Open the Aspire Dashboard for monitoring

Manual Setup (Without Aspire)

Prerequisites

  • .NET 10 SDK
  • SQL Server (LocalDB or full instance)

Database Setup

  1. Create database:

    CREATE DATABASE FlowSmith;
    
  2. Run schema script:

    sqlcmd -S localhost -d FlowSmith -i database/schema.sql
    
  3. Update connection strings in:

    • examples/FlowSmith.Examples/appsettings.json (Worker)
    • examples/FlowSmith.Api/appsettings.json (API + UI)

Build and Run

# Build solution
dotnet build FlowSmith.slnx

# Run tests
dotnet test FlowSmith.slnx

# Run API + UI (in one terminal)
cd examples/FlowSmith.Api
dotnet run

# Run worker (in separate terminal)
cd examples/FlowSmith.Examples
dotnet run

Creating a Workflow

1. Define Steps

public class ReserveInventoryStep : IFlowStep
{
    private readonly ILogger<ReserveInventoryStep> _logger;

    public ReserveInventoryStep(ILogger<ReserveInventoryStep> logger)
    {
        _logger = logger;
    }

    public Task Execute(StepContext context)
    {
        var orderId = context.Data["orderId"];
        var items = context.Data["items"];

        _logger.LogInformation("Reserving inventory for order {OrderId}", orderId);

        // Business logic here
        var reservationId = Guid.NewGuid();
        context.Data["reservationId"] = reservationId;

        return Task.CompletedTask;
    }

    public Task Compensate(StepContext context)
    {
        var reservationId = context.Data.GetValueOrDefault("reservationId");
        _logger.LogWarning("Compensation: Releasing inventory reservation {ReservationId}", reservationId);

        // Reverse the reservation
        return Task.CompletedTask;
    }
}

2. Define Workflow

public class OrderProcessingWorkflow : Workflow
{
    public override void Build(IFlowBuilder builder)
    {
        builder
            .AddStep<ValidateOrderStep>("ValidateOrder")
            .WithRetry(maxAttempts: 3, backoffSeconds: 2)

            .AddStep<ReserveInventoryStep>("ReserveInventory")
            .WithRetry(maxAttempts: 3, backoffSeconds: 5)

            .AddStep<ChargePaymentStep>("ChargePayment")
            .WithRetry(maxAttempts: 2, backoffSeconds: 10)

            .AddStep<SendConfirmationEmailStep>("SendConfirmation")
            .WithRetry(maxAttempts: 2, backoffSeconds: 3);
    }
}

3. Register Workflow

In Worker Program.cs:

using FlowSmith.Worker.Extensions;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

// Register Worker
var connectionString = builder.Configuration.GetConnectionString("FlowSmithDb");
builder.Services.AddFlowSmithWorker(connectionString);

var host = builder.Build();

// Register workflow(s)
host.RegisterWorkflow<OrderProcessingWorkflow>("OrderProcessing");

// Or register multiple workflows:
host.RegisterWorkflows(registry =>
{
    registry.Register<OrderProcessingWorkflow>("OrderProcessing");
    registry.Register<UserRegistrationWorkflow>("UserRegistration");
});

host.Run();

4. Start Workflow

Option A: Via REST API (recommended)

The FlowSmith.Api project demonstrates how to set up the API in just a few lines:

using FlowSmith.Runtime.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Register FlowSmith Runtime
var connectionString = builder.Configuration.GetConnectionString("FlowSmithDb");
builder.Services.AddFlowSmithRuntime(connectionString);

var app = builder.Build();

// Register API endpoints (default prefix: /api/workflows)
app.MapFlowSmithRuntimeApi();

app.Run();

Start workflow via HTTP:

curl -X POST "http://localhost:5000/api/workflows/start?workflowName=OrderProcessing" \
  -H "Content-Type: application/json" \
  -d '{"orderId": 12345, "customerEmail": "user@example.com", "items": ["item1", "item2"]}'

Option B: Via code

using FlowSmith.Runtime.Extensions;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddFlowSmithRuntime(connectionString);
var serviceProvider = services.BuildServiceProvider();

var commandService = serviceProvider.GetRequiredService<IWorkflowCommandService>();

var instanceId = await commandService.StartAsync(
    "OrderProcessing",
    new Dictionary<string, object>
    {
        ["orderId"] = 12345,
        ["customerEmail"] = "user@example.com",
        ["items"] = new[] { "item1", "item2" }
    });

State Machine

Workflow States

  • PendingRunningSucceeded
  • RunningCompensatingCompensated
  • CompensatingCompensationFailed (terminal)

Step States

  • PendingRunningSucceeded
  • RunningFailed (with retry)
  • SucceededCompensated (during compensation)

Testing

Use NSubstitute for mocking (never Moq):

[Fact]
public async Task StartAsync_CreatesInstance()
{
    // Arrange
    var store = Substitute.For<IInstanceStore>();
    var service = new WorkflowCommandService(store, logger);

    // Act
    var instanceId = await service.StartAsync("TestWorkflow");

    // Assert
    await store.Received(1).CreateAsync(
        Arg.Is<WorkflowInstance>(i => i.WorkflowName == "TestWorkflow"),
        Arg.Any<CancellationToken>());
}

Configuration

Connection String

Update in appsettings.json:

{
  "ConnectionStrings": {
    "FlowSmithDb": "Server=localhost;Database=FlowSmith;Integrated Security=true;TrustServerCertificate=true;"
  }
}

Worker Settings

  • Poll interval: 1 second (hardcoded in v0.1)
  • Lease duration: 5 minutes (hardcoded in v0.1)
  • Retry backoff: Exponential (configurable per step)

Roadmap

v0.1 (Current)

  • ✅ Linear workflows
  • ✅ Retry + compensation
  • ✅ SQL Server persistence
  • ✅ Leasing
  • ✅ Basic monitoring

Future

  • Parallel steps
  • Timers and delays
  • Event-driven triggers
  • Distributed cluster support
  • Advanced monitoring and metrics

License

See LICENSE file.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.1 113 3/1/2026
0.1.0 111 3/1/2026