FlowSmith.Core
0.1.1
dotnet add package FlowSmith.Core --version 0.1.1
NuGet\Install-Package FlowSmith.Core -Version 0.1.1
<PackageReference Include="FlowSmith.Core" Version="0.1.1" />
<PackageVersion Include="FlowSmith.Core" Version="0.1.1" />
<PackageReference Include="FlowSmith.Core" />
paket add FlowSmith.Core --version 0.1.1
#r "nuget: FlowSmith.Core, 0.1.1"
#:package FlowSmith.Core@0.1.1
#addin nuget:?package=FlowSmith.Core&version=0.1.1
#tool nuget:?package=FlowSmith.Core&version=0.1.1
FlowSmith
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
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)
- Workflow DSL abstractions (
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 servicesMapFlowSmithRuntimeApi()- register HTTP API endpoints
- Does NOT execute steps
- Depends on: Core
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 servicesRegisterWorkflow<T>(name)- register workflow types
- Depends on: Core
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:
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
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
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")
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
Quick Start with .NET Aspire (Recommended)
Install Aspire workload:
dotnet workload install aspireEnsure Docker Desktop is running (required for SQL Server)
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
Create database:
CREATE DATABASE FlowSmith;Run schema script:
sqlcmd -S localhost -d FlowSmith -i database/schema.sqlUpdate 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
Pending→Running→SucceededRunning→Compensating→CompensatedCompensating→CompensationFailed(terminal)
Step States
Pending→Running→SucceededRunning→Failed(with retry)Succeeded→Compensated(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 | Versions 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. |
-
net10.0
- No dependencies.
NuGet packages (3)
Showing the top 3 NuGet packages that depend on FlowSmith.Core:
| Package | Downloads |
|---|---|
|
FlowSmith.UI
FlowSmith UI - Blazor Server monitoring interface. Read-only workflow instance views and timeline visualization. |
|
|
FlowSmith.Runtime
FlowSmith Runtime - SQL Server persistence implementation, Start/Restart logic, and Query layer. Does not execute steps. |
|
|
FlowSmith.Worker
FlowSmith Worker - Polling loop, leasing, and step execution engine. Uses DI for workflow instantiation. |
GitHub repositories
This package is not used by any popular GitHub repositories.