-
Notifications
You must be signed in to change notification settings - Fork 12
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 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.
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);
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.
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.
-
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 crucial 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.