Flow Studio
Delivery Guarantees
The workflow event bus is an in-process, synchronous fan-out. It provides at-most-once delivery within a single process lifetime. For durable delivery, combine it with an external message broker from inside a handler.
Guarantee Model
| Property | Behaviour |
|---|---|
| Delivery model | At-most-once — events are not replayed after process restart |
| Ordering | Events are delivered to handlers in publish order within a single call chain |
| Concurrency | Concurrent executions publish independently — no cross-execution ordering guarantee |
| Handler failure isolation | If handler A throws, handler B still runs; publisher is not affected |
| Retry | No automatic retry — design handlers to be idempotent and self-recovering |
| Persistence | None — in-memory only, events lost on process restart |
Achieving Durable Delivery
If you need guaranteed delivery (e.g., webhook that must fire even if the app restarts), implement a handler that writes the event to a durable outbox table, then dispatch from the outbox using a background job:
// Handler: write to outbox DB table — this is transactional
public class WorkflowEventOutboxHandler : IWorkflowEventHandler<WorkflowCompleted>
{
private readonly IOutboxRepository _outbox;
public WorkflowEventOutboxHandler(IOutboxRepository outbox) => _outbox = outbox;
public async Task HandleAsync(WorkflowCompleted @event, CancellationToken ct)
{
await _outbox.InsertAsync(new OutboxEntry
{
Id = @event.EventId,
EventType = "WorkflowCompleted",
Payload = JsonSerializer.Serialize(@event),
CreatedAt = DateTimeOffset.UtcNow,
Status = OutboxStatus.Pending
}, ct);
}
}
// Separate background job reads from outbox and delivers to external systems
// — this job survives restarts and can retry failed deliveries
Handler Exception Handling
By default, the dispatcher catches and logs handler exceptions without rethrowing. This means a handler that crashes does not fail the workflow. If you need a handler failure to fail the workflow, throw from the handler and configure the dispatcher to propagate.
// In appsettings.json (or options)
{
"WorkflowEventBus": {
"PropagateHandlerExceptions": false, // default — isolated
"LogHandlerExceptions": true
}
}
Process Restart Scenarios
Lost events on restart: If the process crashes between PublishAsync being called and the handlers completing, those events are lost. This is an inherent limitation of the in-process bus. Design your system so that the workflow execution result (written to the database by the engine) is the source of truth, and event handlers are supplementary notification mechanisms.