Integration Events Module¶
The Integration Events module extends the Event Queue infrastructure with cross-service integration concerns: stable wire names via [IntegrationEvent], a compile-time EventTypeRegistry for polymorphic deserialization, and pluggable external transports (e.g., Azure Service Bus).
Under the hood, Integration Events shares the same runtime infrastructure — ChannelWorker<T>, EventQueueChannel<T>, IEventTransport<TEvent> — but its generator emits additional plumbing that plain [EventQueue] does not.
When to use which?
Use [EventQueue] for in-process domain or aggregate events that never leave the process boundary. Use [IntegrationEvents] when events cross service boundaries and need stable serialization identities.
Quick Start¶
Integration events follow a topic-based pattern. Events are defined as nested types inside a shared topic class, then published and subscribed independently.
1. Define events in a shared contracts project¶
// In contracts project (shared between services)
public sealed class OrderingEvents
{
[IntegrationEvent("ordering.order-placed")]
public sealed record OrderPlaced(string OrderId, string CustomerId);
[IntegrationEvent("ordering.order-cancelled")]
public sealed record OrderCancelled(string OrderId, string Reason);
}
2. Declare the publisher¶
// In the ordering bounded context
[IntegrationEvents(typeof(OrderingEvents))]
public static partial class OrderingIntegrationEvents;
3. Subscribe with handlers¶
// In a different bounded context that reacts to ordering events
[IntegrationEventHandler<NotificationRuntime>(typeof(OrderingEvents))]
public static partial class NotificationOrderingEventHandlers
{
public static Eff<NotificationRuntime, Unit> Handle(OrderingEvents.OrderPlaced evt) =>
from _ in NotificationEffects.Send<NotificationRuntime>(
$"Order {evt.OrderId} placed by {evt.CustomerId}")
select unit;
public static Eff<NotificationRuntime, Unit> Handle(OrderingEvents.OrderCancelled evt) =>
from _ in AuditEffects.Record<NotificationRuntime>(
"OrderCancelled", evt.OrderId)
select unit;
}
4. Declare the runtime¶
[Runtime]
public partial class OrderingRuntime;
// OrderingIntegrationEvents is auto-discovered — no [Uses] needed
The generator produces everything [EventQueue] does, plus:
EventTypeRegistry— aDictionary<string, Type>mapping wire names to CLR typesSetTransport()— method for plugging in external transports (e.g., Azure Service Bus)
Sub-Pages¶
| Page | Description |
|---|---|
| Attributes | [IntegrationEvents], [IntegrationEventHandler<TRuntime>], and [IntegrationEvent] reference |
| Generated Code | Event Type Registry, transport injection, and wire-name serialization |
| Effects | Generated effect methods and composition patterns |
Templating¶
Use Scriban templates for dead-letter and poison-message alert content. When an integration event fails processing, render a structured alert body with event context.
{{- # Templates/IntegrationEvents/PoisonMessageAlert.scriban-txt -}}
Failed to process {{ wire_name }} from {{ topic }}.
Event ID: {{ event_id }}
Failure count: {{ failure_count }}
Last error: {{ last_error }}
Received: {{ received_at | date.to_string "%Y-%m-%d %H:%M:%S UTC" }}
For syntax reference and best practices, see Scriban Templating.
Diagnostics¶
| ID | Severity | Description |
|---|---|---|
| DSEQ06 | Error | Event type missing [IntegrationEvent] attribute |
| DSEQ09 | Error | IntegrationEventHandler class must be static |
| DSEQ10 | Warning | IntegrationEventHandler has no handler methods |
DSEQ06 is unique to Integration Events — it enforces that every event type handled by [IntegrationEventHandler] carries an [IntegrationEvent] attribute with a stable wire name. DSEQ09 and DSEQ10 mirror the EventQueue handler diagnostics (DSEQ03, DSEQ04).