Skip to content

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 — a Dictionary<string, Type> mapping wire names to CLR types
  • SetTransport() — 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).