Flow Studio
WorkflowEvent Schema
The abstract base class all workflow events inherit from. Every event carries a common set of fields for correlation, tenant isolation, and event type identification.
Base Class
namespace BizFirst.Ai.ProcessEngine.EventBus;
/// <summary>
/// Base class for all workflow events — both built-in and custom.
/// Provides correlation, tenant, and event metadata.
/// </summary>
public abstract class WorkflowEvent
{
// Set by the engine at publish time
public string EventId { get; init; } = Guid.NewGuid().ToString();
public string EventType { get; init; } = string.Empty; // auto-set by dispatcher
public DateTimeOffset OccurredAt { get; init; } = DateTimeOffset.UtcNow;
// Correlation context — always required
public string ExecutionId { get; init; }
public string ProcessId { get; init; }
public string TenantId { get; init; }
}
Base Fields Reference
| Field | Type | Description |
|---|---|---|
EventId | string (GUID) | Unique identifier for this event instance — use for deduplication in idempotent handlers |
EventType | string | Auto-set by dispatcher to the class name — e.g., "WorkflowCompleted" |
OccurredAt | DateTimeOffset | UTC timestamp when PublishAsync was called |
ExecutionId | string | The execution instance this event relates to |
ProcessId | string | The workflow definition (process) this execution is running |
TenantId | string | Tenant that owns the workflow — always include in handler DB writes for isolation |
Idempotent Handler Pattern
Use EventId to prevent double-processing in handlers that write to external systems:
public async Task HandleAsync(WorkflowCompleted @event, CancellationToken ct)
{
// Skip if already processed (e.g., after a retry)
if (await _processedEvents.ExistsAsync(@event.EventId, ct))
return;
await _billingService.ChargeExecutionAsync(@event.TenantId, @event.ExecutionId, ct);
await _processedEvents.MarkAsync(@event.EventId, ct);
}
Serialisation for External Delivery
When pushing events to an external message broker from a handler, serialise the full event object to preserve all base fields:
public async Task HandleAsync(WorkflowCompleted @event, CancellationToken ct)
{
var message = new ServiceBusMessage(JsonSerializer.Serialize(@event))
{
MessageId = @event.EventId, // dedup key
CorrelationId = @event.ExecutionId,
Subject = @event.EventType
};
await _sender.SendMessageAsync(message, ct);
}