Table of Contents

Class DomainEventPublisherExtensions

Namespace
Trellis.Mediator
Assembly
Trellis.Mediator.dll

Extension methods over IDomainEventPublisher for call sites that need to dispatch an aggregate's UncommittedEvents() manually (outside the DomainEventDispatchBehavior<TMessage, TResponse> pipeline).

public static class DomainEventPublisherExtensions
Inheritance
DomainEventPublisherExtensions
Inherited Members

Methods

DispatchAggregateEventsAsync(IDomainEventPublisher, IAggregate, CancellationToken)

POST-COMMIT ONLY. Publishes a defensive snapshot of aggregate's uncommitted domain events and then calls AcceptChanges() when the aggregate's pending-event list still exactly matches that snapshot after dispatch. Provided as the manual counterpart to DomainEventDispatchBehavior<TMessage, TResponse> for handlers whose response type is not an aggregate-valued result (e.g., Result<Unit>, Result<TDto>, Result<(A,B)>) and for non-Mediator call sites such as BackgroundService workers.

public static Task DispatchAggregateEventsAsync(this IDomainEventPublisher publisher, IAggregate aggregate, CancellationToken cancellationToken = default)

Parameters

publisher IDomainEventPublisher

The publisher used to fan out each event to its registered handlers.

aggregate IAggregate

The aggregate whose UncommittedEvents() are dispatched.

cancellationToken CancellationToken

Accepted for signature compatibility but deliberately not observed. This helper is post-commit only, so honoring cancellation here would strand an already-durable write with a partially published event set. Retained rather than removed to avoid a source-breaking change for existing callers.

Returns

Task

A Task that completes once every snapshotted event has been published and AcceptChanges() has cleared the aggregate's pending list.

Remarks

POST-COMMIT ONLY. Domain events must be published only after the underlying unit of work has committed. Calling this helper from inside a command handler that relies on DomainEventDispatchBehavior<TMessage, TResponse>'s sibling TransactionalCommandBehavior for its commit will publish events before the database transaction is durable; if the commit then fails, the events have already escaped to their handlers and AcceptChanges() has cleared them off the aggregate — making the failure non-replayable.

Safe call sites:

  • After a manual IUnitOfWork.CommitAsync in a handler that does not chain the transactional behavior.
  • From an outer IPipelineBehavior that runs after TransactionalCommandBehavior (i.e., registered earlier in the pipeline so its post-await section executes later).
  • From a BackgroundService tick after the underlying DbContext.SaveChangesAsync call has succeeded.

Strict snapshot semantics match DomainEventDispatchBehavior<TMessage, TResponse>: events are dispatched sequentially from the entry snapshot only. Handlers must be side-effect-only and must not change the aggregate's pending-event list during dispatch — no raising new events, no clearing via AcceptChanges, no replacing or reordering. If the pending-event list differs from the entry snapshot at the end of dispatch (length or reference equality), the helper throws DomainEventHandlerCascadedException and leaves the aggregate unchanged.

Re-entrant calls on the same aggregate are not supported. Treat domain event handlers as side-effect-only and dispatch is owned by exactly one outer call.

Exceptions

ArgumentNullException

Thrown when publisher or aggregate is null.

DomainEventHandlerCascadedException

Thrown when the aggregate's pending-event list no longer matches the entry snapshot at the end of dispatch — i.e. a handler raised, cleared, replaced, or reordered events on the aggregate. Validation is strict (length + reference equality), so any mutation of the pending list during dispatch trips it. AcceptChanges() is not called in this case so the caller can inspect the aggregate's current pending events.