Interface IDomainEventHandler<TEvent>
Handles a domain event raised by an IAggregate. Implementations are resolved via DI by the DomainEventDispatchBehavior<TMessage, TResponse> after a successful command and invoked once per matching event.
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Domain event handler is a DDD term of art and is unrelated to System.EventHandler.")]
public interface IDomainEventHandler<in TEvent> where TEvent : IDomainEvent
Type Parameters
TEventThe concrete event type. Dispatch matches the runtime type of the event exactly; base-type and interface-type handlers are not resolved automatically.
- Extension Methods
Examples
public sealed class OrderConfirmationEmailHandler : IDomainEventHandler<OrderSubmitted>
{
private readonly IEmailService _email;
public OrderConfirmationEmailHandler(IEmailService email) => _email = email;
public async ValueTask HandleAsync(OrderSubmitted domainEvent, CancellationToken cancellationToken)
{
await _email.SendOrderConfirmationAsync(domainEvent.OrderId, cancellationToken).ConfigureAwait(false);
}
}
Remarks
Domain event handlers run after the command's transaction commits (when
TransactionalCommandBehavior is registered) or after the command handler
returns success (when no transactional behavior is in the pipeline).
Handlers must be idempotent and treat their work as a best-effort side effect: non-cancellation exceptions thrown by a handler are logged at error level and swallowed so that other handlers, other events, and the originating command still complete. OperationCanceledException matching the request's cancellation token is the one exception that propagates — handlers that observe cancellation should throw it; the dispatcher will let it abort the remaining work. If a side effect must block command completion, do that work inside the command handler instead.
Handlers should treat themselves as side-effect-only. The dispatcher publishes only
the entry snapshot and then validates by length + reference equality that handlers
did not change the participating aggregate's pending-event list — raising new events,
clearing via AcceptChanges, replacing, or reordering all trip cascade detection.
If a handler changes the pending list, dispatch throws
DomainEventHandlerCascadedException and does not call AcceptChanges().
The originating command's transaction has already committed, so persist-and-emit chains
belong inside command handlers or separate top-level commands, not domain-event handlers.
Methods
HandleAsync(TEvent, CancellationToken)
Handles the specified domain event.
ValueTask HandleAsync(TEvent domainEvent, CancellationToken cancellationToken)
Parameters
domainEventTEventThe event raised by an aggregate during command execution.
cancellationTokenCancellationTokenPropagated from the originating command pipeline.