Flow Studio
Server Node Contract
The IServerNode interface — lifecycle methods StartAsync, StopAsync, and HandleRequestAsync — and the ServerNodeRequest / ServerNodeResponse envelope types.
IServerNode Interface
public interface IServerNode : IHostedService
{
string NodeId { get; }
string DisplayName { get; }
Task<ServerNodeResponse> HandleRequestAsync(
ServerNodeRequest request,
CancellationToken ct);
ServerNodeHealth GetHealth();
}
public record ServerNodeRequest
{
public string ExecutionId { get; init; } = default!;
public string TenantId { get; init; } = default!;
public string NodeId { get; init; } = default!;
public JsonDocument Payload { get; init; } = default!;
}
public record ServerNodeResponse
{
public bool Success { get; init; }
public JsonDocument? Data { get; init; }
public string? ErrorMessage { get; init; }
}
public record ServerNodeHealth
{
public bool IsReady { get; init; }
public string Status { get; init; } = "unknown";
public DateTimeOffset CheckedAt { get; init; }
public Dictionary<string, object> Diagnostics { get; init; } = [];
}
Lifecycle Methods
| Method | Called By | Purpose |
|---|---|---|
StartAsync(ct) | ASP.NET Core host at startup | Load model, open connections, warm up resources, register with ServerNodeRegistry |
HandleRequestAsync(req, ct) | ServerNodeCallExecutor during workflow execution | Process one workflow node call — must be thread-safe |
StopAsync(ct) | ASP.NET Core host on shutdown | Drain in-flight requests, close connections, release resources |
GetHealth() | Health check endpoint GET /health/server-nodes/{nodeId} | Report readiness status and diagnostics |
Base Class Implementation
public abstract class BaseServerNode : IServerNode
{
private readonly IServerNodeRegistry _registry;
private readonly ILogger _logger;
protected BaseServerNode(IServerNodeRegistry registry, ILogger logger)
{
_registry = registry;
_logger = logger;
}
public abstract string NodeId { get; }
public abstract string DisplayName { get; }
public virtual async Task StartAsync(CancellationToken ct)
{
_logger.LogInformation("ServerNode {NodeId} starting...", NodeId);
await InitialiseAsync(ct);
_registry.Register(NodeId, this);
_logger.LogInformation("ServerNode {NodeId} ready.", NodeId);
}
public virtual async Task StopAsync(CancellationToken ct)
{
_logger.LogInformation("ServerNode {NodeId} stopping...", NodeId);
_registry.Deregister(NodeId);
await DisposeResourcesAsync(ct);
}
protected abstract Task InitialiseAsync(CancellationToken ct);
protected abstract Task DisposeResourcesAsync(CancellationToken ct);
public abstract Task<ServerNodeResponse> HandleRequestAsync(
ServerNodeRequest request, CancellationToken ct);
public abstract ServerNodeHealth GetHealth();
}
Register after initialisation, not before: Always call
_registry.Register(NodeId, this) at the end of StartAsync, after all resources are loaded. If the node registers before it is ready, workflow calls may arrive before the model is loaded or connections are established.