Storage Backends
FlowOrchestrator's persistence layer is built on three core interfaces. The package you install provides an implementation; you can also swap in your own.
Core Interfaces
| Interface | Responsibility |
|---|---|
IFlowStore |
Flow definitions and enabled/disabled state |
IFlowRunStore |
Run records and step status tracking |
IOutputsRepository |
Step input/output blobs keyed by (RunId, StepKey) |
These three interfaces are the minimum required to replace the built-in backends.
SQL Server
dotnet add package FlowOrchestrator.SqlServer
builder.Services.AddFlowOrchestrator(options =>
{
options.UseSqlServer(connectionString);
options.UseHangfire();
});
FlowOrchestratorSqlMigrator runs on startup and auto-creates all required tables if they do not exist. No manual migration step is needed.
Tables created:
| Table | Purpose |
|---|---|
FlowDefinitions |
Registered flows, enable/disable state |
FlowRuns |
One row per run: status, timestamps, trigger key |
FlowSteps |
One row per step per run: status, attempt count |
FlowStepAttempts |
Detailed per-attempt records (start, end, error) |
FlowOutputs |
Step inputs and outputs serialized as JSON |
FlowStepDispatches |
Idempotent dispatch ledger — supports the Dispatch many, Execute once invariant |
FlowStepClaims |
Exclusive execution claim per step (the Execute once half) |
FlowRunControls |
Cancellation, timeout, and idempotency key records |
FlowIdempotencyKeys |
Trigger-time idempotency dedupe |
FlowEvents |
Event stream records (when EnableEventPersistence = true) |
FlowSignalWaiters |
Parked WaitForSignal step state (see WaitForSignal) |
FlowScheduleStates |
Cron override storage (when Scheduler.PersistOverrides = true) |
WebhookReplayNonces |
Replay-attack nonce ledger for the dashboard webhook hardening pipeline (v1.25; opt-in) |
WebhookRejections |
DLQ + recent-deliveries log for the webhook pipeline (v1.25; opt-in) |
Connection string format:
{
"ConnectionStrings": {
"FlowOrchestrator": "Server=.;Database=FlowOrchestrator;Trusted_Connection=True;"
}
}
PostgreSQL
dotnet add package FlowOrchestrator.PostgreSQL
builder.Services.AddFlowOrchestrator(options =>
{
options.UsePostgreSql(connectionString);
options.UseHangfire();
});
FlowOrchestratorPgMigrator creates the same table set in PostgreSQL on startup. Uses Npgsql — no EF Core dependency. PostgreSQL table names are snake_case (flow_runs, webhook_replay_nonces, webhook_rejections, …).
Webhook hardening backends (v1.25)
The replay-nonce + DLQ stores default to in-memory (single-replica only). For multi-replica deployments register the backend-specific implementations:
builder.Services.AddFlowOrchestrator(options =>
{
options.UseSqlServer(sqlConn);
options.AddSqlServerWebhookHardening(sqlConn); // SqlWebhookReplayStore + SqlWebhookRejectionStore
});
// or for PostgreSQL:
builder.Services.AddFlowOrchestrator(options =>
{
options.UsePostgreSql(pgConn);
options.AddPostgreSqlWebhookHardening(pgConn); // PostgreSqlWebhookReplayStore + PostgreSqlWebhookRejectionStore
});
Both replay-store implementations use atomic upserts (INSERT … WHERE NOT EXISTS on Sql Server, ON CONFLICT DO NOTHING on Postgres) so concurrent replicas race correctly without a serialisable transaction. Tables are created by the existing migrators with idempotent IF NOT EXISTS guards.
{
"ConnectionStrings": {
"FlowOrchestratorPg": "Host=localhost;Database=floworch;Username=app;Password=secret"
}
}
In-Memory
dotnet add package FlowOrchestrator.InMemory
builder.Services.AddFlowOrchestrator(options =>
{
options.UseInMemory();
options.UseHangfire();
});
Warning
All run data is lost when the process restarts. Use this for local development and unit tests only.
UseInMemory() must be called explicitly — there is no silent fallback. Calling AddFlowOrchestrator() without any storage backend throws InvalidOperationException on startup.
Comparing Backends
| Feature | SQL Server | PostgreSQL | In-Memory |
|---|---|---|---|
| Persistence across restarts | Yes | Yes | No |
| Run history and step timeline | Yes | Yes | Yes (current session) |
| Schedule override persistence | Yes | Yes | No |
| Cron expressions | Yes | Yes | Yes |
| Webhook deduplication | Yes | Yes | Session only |
| Testcontainers support | Yes | Yes | — |
| Production-ready | Yes | Yes | No |
Custom Backend
Implement the three core interfaces:
public sealed class RedisFlowStore : IFlowStore { ... }
public sealed class RedisFlowRunStore : IFlowRunStore { ... }
public sealed class RedisOutputsRepository : IOutputsRepository { ... }
Register them directly on options.Services:
builder.Services.AddFlowOrchestrator(options =>
{
options.Services.AddSingleton<IFlowStore, RedisFlowStore>();
options.Services.AddSingleton<IFlowRunStore, RedisFlowRunStore>();
options.Services.AddSingleton<IOutputsRepository, RedisOutputsRepository>();
options.UseHangfire();
});
Advanced Contracts
For full feature parity (schedule overrides, run control, event stream, retention, and concurrency safety), implement these additional interfaces:
| Interface | Feature |
|---|---|
IFlowScheduleStateStore |
Persistent cron overrides (Scheduler.PersistOverrides) |
IFlowRunControlStore |
Cancel, timeout, and idempotency key state. The engine checks this on every TriggerAsync to deduplicate runs and on every RunStepAsync to honour cancellation. |
IFlowEventReader |
Run event stream (GET /flows/api/runs/{runId}/events) |
IFlowRetentionStore |
Background retention sweep (deletes old run data) |
IFlowSignalStore |
Parked WaitForSignal waiter state. Required if you want to use the WaitForSignal built-in step on a custom backend. |
IFlowRunRuntimeStore |
Step claim/dispatch ledger per run. Implements TryRecordDispatchAsync (idempotent INSERT — prevents duplicate dispatch) and TryClaimStepAsync (claim exclusion — ensures a step is executed by at most one worker). Required for production use with any multi-worker runtime. |
Register these the same way — directly on options.Services.
Hangfire Storage vs FlowOrchestrator Storage
These are completely independent stores:
- Hangfire storage — holds job queue, server heartbeats, retry state. Configured on
AddHangfire(...). - FlowOrchestrator storage — holds flow definitions, run history, step outputs. Configured on
options.UseSqlServer(...)/UsePostgreSql()/UseInMemory().
You can mix them freely:
// Hangfire on SQL Server, FlowOrchestrator on PostgreSQL
builder.Services.AddHangfire(c => c.UseSqlServerStorage(sqlConnStr));
builder.Services.AddFlowOrchestrator(options =>
{
options.UsePostgreSql(pgConnStr);
options.UseHangfire();
});