-
Notifications
You must be signed in to change notification settings - Fork 0
Spindle.Hosting Setup
This page describes the initial setup of Spindle in a .NET application. It covers both kinds of service commonly used with Spindle:
- A worker service that advances durable flow instances in the background.
- A non-worker service, such as an API or message-ingress service, that starts flows while a separate worker advances them.
EF Core provider setup and migration management are covered in Spindle EF Core persistence.
Install Spindle.Hosting and one persistence implementation. For an EF Core application, install the provider package for the database as described in the persistence page.
dotnet add package Spindle.Hosting
dotnet add package Spindle.Persistence.EFCore.PostgreSQLSpindle targets .NET 8 and .NET 10 in this repository. Keep the Spindle package version and the EF Core provider major version aligned with the target framework used by the application.
The application should provide a connection string and, for PostgreSQL or SQL Server, may provide a schema:
{
"ConnectionStrings": {
"Spindle": "Host=localhost;Database=spindle;Username=spindle;Password=change-me"
},
"Spindle": {
"Schema": "spindle"
}
}Do not commit production credentials. Use the application's normal secret or environment configuration. The environment-variable form of the schema setting is Spindle__Schema.
SQLite and MySQL do not support the schema registration parameter. Leave Spindle:Schema unset when using those providers.
A worker service registers the persistence provider, flow definitions, optional step handlers, and AddSpindleWorker. AddSpindleWorker includes the runtime and starts the hosted background pump.
using Spindle.Abstractions.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Spindle.Hosting;
using Spindle.Persistence.EFCore.PostgreSQL;
var builder = Host.CreateApplicationBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("Spindle")
?? throw new InvalidOperationException("The Spindle connection string is required.");
var schema = builder.Configuration["Spindle:Schema"];
builder.Services.AddSpindlePostgreSql(
connectionString,
schema: schema);
builder.Services.AddSpindleFlow<MyFlow, MyRequest, MyResult>(
new FlowName("my-flow"));
builder.Services.AddSpindleStepHandler<
MyStepHandler,
MyStepRequest,
MyStepResult>(
new StepHandlerId("my-step"));
builder.Services.AddSpindleWorker(options =>
{
options.WorkerId = "orders-worker";
options.PollInterval = TimeSpan.FromMilliseconds(250);
options.MaxConcurrentFlowInstances = 4;
});
var app = builder.Build();
await app.RunAsync();The worker reads runnable flow instances from the configured store, executes ready local steps, fires due timers, and records progress. Multiple worker processes can use the same store; configure migrations separately so that only one controlled process applies database changes at a time.
AddSpindleWorker accepts an optional Action<SpindleHostOptions>:
| Option | Default | Purpose |
|---|---|---|
PollInterval |
250ms |
Delay when a worker tick makes no progress. |
MaxFlowInstancesPerTick |
100 |
Maximum runnable instances read per tick. |
MaxStepsPerFlowPerTick |
1 |
Maximum ready local steps run for one flow per tick. |
MaxConcurrentFlowInstances |
CPU count | Maximum flow instances advanced concurrently. |
LeaseDuration |
30s |
Duration of local step leases. |
WorkerId |
Machine-based | Worker identity recorded on attempts and leases. |
A non-worker service registers AddSpindleRuntime instead of AddSpindleWorker. This is useful for an API, job producer, or message-ingress process that starts flows but should not run the background worker loop.
using Spindle.Abstractions.Core;
using Spindle.Abstractions.Flows;
using Microsoft.Extensions.DependencyInjection;
using Spindle.Hosting;
using Spindle.Persistence.EFCore.PostgreSQL;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("Spindle")
?? throw new InvalidOperationException("The Spindle connection string is required.");
builder.Services.AddSpindlePostgreSql(
connectionString,
schema: builder.Configuration["Spindle:Schema"]);
builder.Services.AddSpindleFlow<MyFlow, MyRequest, MyResult>(
new FlowName("my-flow"));
builder.Services.AddSpindleRuntime();
var app = builder.Build();
app.MapPost("/flows", async (
StartFlowRequest request,
ISpindleRuntime runtime,
CancellationToken cancellationToken) =>
{
await runtime.StartAsync<MyRequest, MyResult>(
new FlowName("my-flow"),
new MyRequest(request.Value),
new StartFlowOptions { IdempotencyKey = request.IdempotencyKey },
cancellationToken);
return Results.Accepted();
});
await app.RunAsync();AddSpindleRuntime registers ISpindleRuntime and the flow/handler registries, but it does not start SpindleWorkerHostedService. Durable work that needs background advancement requires a worker service connected to the same persistence store.
Do not register both AddSpindleRuntime and AddSpindleWorker; AddSpindleWorker already registers the runtime.
Register every flow that the process may start or execute:
builder.Services.AddSpindleFlow<MyFlow, MyRequest, MyResult>(
new FlowName("my-flow"));Register handlers used by ctx.StepHandler(...):
builder.Services.AddSpindleStepHandler<
MyStepHandler,
MyStepRequest,
MyStepResult>(
new StepHandlerId("my-step"));Flow names and step-handler IDs are part of the persisted flow contract. Keep them stable when deploying a new application version.
Choose one of the migration workflows described in Spindle EF Core persistence. In development and test environments, applying migrations during startup can be convenient. In production, prefer a separately controlled migration job or reviewed SQL script.