Flow Studio
Registering a Subscriber
Register subscribers in ASP.NET Core DI — typically as Transient or Scoped. Multiple subscribers are all called, in registration order, for every node event.
Basic Registration
// In Program.cs or a dedicated extension method
builder.Services.AddTransient<INodeEventSubscriber, AuditNodeSubscriber>();
builder.Services.AddTransient<INodeEventSubscriber, MetricsNodeSubscriber>();
builder.Services.AddTransient<INodeEventSubscriber, ErrorAlertSubscriber>();
Lifetime Considerations
| Lifetime | When to Use | Gotcha |
|---|---|---|
Transient | Stateless subscribers (write to repo, increment metric) | A new instance per node event — no shared state between events |
Scoped | Subscribers that need a scoped service (e.g., EF DbContext) | Scope is the execution scope, not the HTTP request scope |
Singleton | Subscribers with in-memory state across executions (e.g., circuit breaker) | Must be thread-safe; all executions share the instance |
Registration Order = Execution Order
Subscribers are called in the exact order they are registered. Register more critical subscribers first (e.g., audit logging before alerting) so that even if alerting fails, the audit record is still written.
// Recommended order: audit first, then metrics, then alerting
services.AddTransient<INodeEventSubscriber, AuditNodeSubscriber>();
services.AddTransient<INodeEventSubscriber, MetricsNodeSubscriber>();
services.AddTransient<INodeEventSubscriber, SlackAlertSubscriber>();
Extension Method Pattern (Recommended)
For subscribers shipped in a library or module, expose a registration extension:
// In your library's ServiceCollectionExtensions.cs
public static class NodeObservabilityExtensions
{
public static IServiceCollection AddNodeObservability(
this IServiceCollection services,
Action<NodeObservabilityOptions>? configure = null)
{
var options = new NodeObservabilityOptions();
configure?.Invoke(options);
services.AddSingleton(options);
services.AddTransient<INodeEventSubscriber, AuditNodeSubscriber>();
if (options.EnableMetrics)
services.AddTransient<INodeEventSubscriber, MetricsNodeSubscriber>();
if (options.EnableAlerts)
services.AddTransient<INodeEventSubscriber, ErrorAlertSubscriber>();
return services;
}
}
// Usage in Program.cs
builder.Services.AddNodeObservability(opts =>
{
opts.EnableMetrics = true;
opts.EnableAlerts = true;
});
Conditional Registration (Feature Flags)
var config = builder.Configuration;
if (config.GetValue<bool>("Features:NodeAuditLog"))
builder.Services.AddTransient<INodeEventSubscriber, AuditNodeSubscriber>();
if (config.GetValue<bool>("Features:NodeMetrics"))
builder.Services.AddTransient<INodeEventSubscriber, MetricsNodeSubscriber>();
Testing a Subscriber
[Fact]
public async Task AuditSubscriber_WritesRecord_OnBeforeExecute()
{
// Arrange
var repo = new FakeAuditRepository();
var subscriber = new AuditNodeSubscriber(repo);
var args = new NodeExecutionEventArgs
{
NodeId = "node-1",
NodeType = "HttpRequest",
ExecutionId = "exec-123",
ProcessId = "proc-456",
TenantId = "tenant-789",
StartedAt = DateTimeOffset.UtcNow,
Context = FakeNodeContext.Create()
};
// Act
await subscriber.OnBeforeExecuteAsync(args, CancellationToken.None);
// Assert
Assert.Single(repo.Entries);
Assert.Equal(AuditStatus.InProgress, repo.Entries[0].Status);
Assert.Equal("node-1", repo.Entries[0].NodeId);
}
No attribute scanning: Subscribers are not discovered by attribute — they must be explicitly registered in DI. This is intentional: it ensures you always know exactly which subscribers are active in a given deployment.