Skip to content

Event Module

A. Shafie edited this page Sep 26, 2025 · 12 revisions

Event Module

The Event Module provides the infrastructure for implementing a publish-subscribe pattern within your application. Events are messages that notify other parts of the application that something significant has occurred.

Core Concepts

  • Notification: Events are facts about something that has already happened (e.g., ProductCreatedEvent, OrderShippedEvent).
  • Zero-to-Many Handlers: An event can have any number of handlers, including zero. If no handlers are registered for an event, LiteBus will silently ignore it by default.
  • Decoupling: Events are a powerful tool for decoupling components. The publisher of an event does not need to know about its subscribers.
  • Naming Convention: Events should be named with verbs in the past tense to reflect that they are historical facts.

Event Contracts

LiteBus is highly flexible in how events are defined.

IEvent Interface

You can explicitly mark a class as an event by implementing the IEvent marker interface.

/// <summary>
/// An event that is published after a new product has been successfully created.
/// </summary>
public sealed class ProductCreatedEvent : IEvent
{
    public required Guid ProductId { get; init; }
    public required string Name { get; init; }
}

POCO Events (Plain Old CLR Objects)

A key feature of LiteBus is its ability to use any class as an event, without requiring it to implement a library-specific interface. This is ideal for publishing domain events directly from your domain model, keeping it clean of infrastructure dependencies.

// This is a pure domain event from your domain layer.
// It has no dependency on LiteBus.
public sealed class OrderShipped
{
    public required Guid OrderId { get; init; }
    public required string TrackingNumber { get; init; }
    public DateTime ShippedAt { get; } = DateTime.UtcNow;
}

Event Handlers

Event handlers contain the logic that reacts to a published event. An event can have multiple handlers.

// Handler to send a confirmation email
public sealed class SendConfirmationEmailHandler : IEventHandler<OrderShipped>
{
    public async Task HandleAsync(OrderShipped @event, CancellationToken cancellationToken = default)
    {
        // Logic to send email...
    }
}

// Handler to update the inventory projection
public sealed class UpdateInventoryProjectionHandler : IEventHandler<OrderShipped>
{
    public async Task HandleAsync(OrderShipped @event, CancellationToken cancellationToken = default)
    {
        // Logic to update a read model or projection...
    }
}

The Event Mediator/Publisher

The IEventMediator (aliased as IEventPublisher) is used to broadcast events to all registered handlers.

public interface IEventMediator
{
    Task PublishAsync(IEvent @event, EventMediationSettings? settings = null, CancellationToken cancellationToken = default);
    Task PublishAsync<TEvent>(TEvent @event, EventMediationSettings? settings = null, CancellationToken cancellationToken = default) where TEvent : notnull;
}

// A semantic alias for IEventMediator
public interface IEventPublisher : IEventMediator { }

Usage

// In a command handler or service
public class ShipOrderCommandHandler : ICommandHandler<ShipOrderCommand>
{
    private readonly IEventPublisher _eventPublisher;

    public ShipOrderCommandHandler(IEventPublisher eventPublisher)
    {
        _eventPublisher = eventPublisher;
    }

    public async Task HandleAsync(ShipOrderCommand command, CancellationToken cancellationToken = default)
    {
        // ... shipping logic ...

        // Publish a domain event
        await _eventPublisher.PublishAsync(new OrderShipped
        {
            OrderId = command.OrderId,
            TrackingNumber = "..."
        });
    }
}

Advanced Event Mediation (v4.0)

Version 4.0 introduced a completely redesigned EventMediationSettings API, providing granular control over handler execution.

Execution Settings

The Execution property on EventMediationSettings controls the concurrency model. Handlers are grouped by their [HandlerPriority] value, and you can control how these groups and the handlers within them execute.

var settings = new EventMediationSettings
{
    Execution = new EventMediationExecutionSettings
    {
        // Controls how different priority groups run relative to each other.
        // - Sequential (default): Group 1 finishes before Group 2 starts.
        // - Parallel: All groups run concurrently.
        PriorityGroupsConcurrencyMode = ConcurrencyMode.Sequential,

        // Controls how handlers within the same priority group run.
        // - Sequential (default): Handlers run one by one.
        // - Parallel: All handlers in the group run concurrently.
        HandlersWithinSamePriorityConcurrencyMode = ConcurrencyMode.Parallel
    }
};

await _eventPublisher.PublishAsync(myEvent, settings);

Routing Settings

The Routing property controls which handlers are selected for execution.

var settings = new EventMediationSettings
{
    Routing = new EventMediationRoutingSettings
    {
        // Filter handlers by tags
        Tags = new[] { "Notifications" },

        // Advanced filtering with a predicate on the handler's descriptor
        HandlerPredicate = descriptor => descriptor.HandlerType.Namespace.StartsWith("MyProject.Core")
    }
};

The HandlerPredicate now receives a full IHandlerDescriptor, allowing for powerful filtering based on handler type, priority, tags, and more.

Shared Advanced Features

The Event Module utilizes several advanced features that are shared across all LiteBus modules. For detailed explanations, see the dedicated pages:

  • Handler Priority: Control the execution order of handlers using the [HandlerPriority] attribute, which is fundamental to the new event mediation model.
  • Handler Filtering: Selectively execute handlers based on context using tags or predicates.
  • Execution Context: Share data between handlers and control the execution flow within a single event pipeline.
  • Polymorphic Dispatch: Create handlers for base event types that can process derived events.
  • Generic Messages & Handlers: Build reusable, generic events and handlers.

Best Practices

  1. Immutable Events: Events represent historical facts and should be immutable. Use records or init-only properties.
  2. Idempotent Handlers: Design handlers to be idempotent, meaning they can safely process the same event multiple times without causing issues. This is crucial for building resilient, distributed systems.
  3. Keep Handlers Focused: Each handler should have a single, well-defined responsibility.
  4. Avoid Chaining Events in Handlers: Having one event handler publish another event can lead to complex, hard-to-debug chains of execution. Consider using a Saga or Process Manager for complex workflows.
Clone this wiki locally