Class DomainEventPublisherExtensions
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
publisherIDomainEventPublisherThe publisher used to fan out each event to its registered handlers.
aggregateIAggregateThe aggregate whose UncommittedEvents() are dispatched.
cancellationTokenCancellationTokenAccepted 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.CommitAsyncin a handler that does not chain the transactional behavior. - From an outer
IPipelineBehaviorthat runs afterTransactionalCommandBehavior(i.e., registered earlier in the pipeline so its post-await section executes later). - From a
BackgroundServicetick after the underlyingDbContext.SaveChangesAsynccall 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
publisheroraggregateis 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.