-
Notifications
You must be signed in to change notification settings - Fork 16
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.
-
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 useful way to decouple 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.
LiteBus is highly flexible in how events are defined.
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; }
}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 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 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 { }// 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 = "..."
});
}
}Version 4.0 introduced a completely redesigned EventMediationSettings API, providing granular control over handler execution.
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);When priority groups run sequentially, lower priority groups finish before later groups start. When priority groups run in parallel, priority is grouping metadata only; later groups may start or finish before earlier groups.
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 receives a full IHandlerDescriptor, allowing filtering based on handler type, priority, tags, and message type. The predicate applies to both PublishAsync(IEvent, settings) and PublishAsync<TEvent>(TEvent, settings).
The Event Module uses 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.
- Open Generic Handlers: Write a single pre/post/error handler that automatically applies to all events matching its constraints, useful for auditing, telemetry, and cross-cutting side effects.
-
Immutable Events: Events represent historical facts and should be immutable. Use records or
init-only properties. - Idempotent Handlers: Design handlers to be idempotent, meaning they can safely process the same event multiple times without causing issues. This is required for building resilient, distributed systems.
- Keep Handlers Focused: Each handler should have a single, well-defined responsibility.
- 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.