Table of Contents

Class TrackedAggregateDomainEventDispatchBehavior<TMessage, TResponse>

Namespace
Trellis.Mediator
Assembly
Trellis.Mediator.dll

Opt-in pipeline behavior that, after a successful command, dispatches domain events from every aggregate the unit of work tracked at commit time — regardless of the command's response shape. Companion to DomainEventDispatchBehavior<TMessage, TResponse>, which only fires for Result<TAggregate> responses.

public sealed class TrackedAggregateDomainEventDispatchBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse> where TMessage : ICommand<TResponse> where TResponse : IResult

Type Parameters

TMessage

The command type. Must implement Mediator.ICommand<TResponse>.

TResponse

The command response. Must implement IResult.

Inheritance
TrackedAggregateDomainEventDispatchBehavior<TMessage, TResponse>
Implements
IPipelineBehavior<TMessage, TResponse>
Inherited Members
Extension Methods

Remarks

Registered via AddTrackedAggregateDomainEventDispatch(IServiceCollection). The opt-in extension removes any registered DomainEventDispatchBehavior<TMessage, TResponse> to avoid double-dispatch for Result<TAggregate> handlers — auto-dispatch happens here, sourced from CommittedAggregates.

Ordering. Runs OUTSIDE TransactionalCommandBehavior so it sees the post-commit world. The opt-in registration handles the yank-and-reappend so this invariant holds regardless of the consumer's call order for AddTrellisUnitOfWork<TContext>().

Skips dispatch when:

  • IsFailure is true — including Result.FailAfterCommit. Persisted failures must dispatch events manually (typically by calling DispatchAggregateEventsAsync(IDomainEventPublisher, IAggregate, CancellationToken) before returning FailAfterCommit) or call AcceptChanges() to discard them; otherwise a later successful command in the same scope will auto-dispatch them.
  • The call is re-entrant — another tracked dispatch is already in progress on the same async flow. Domain-event handlers that send nested commands trigger this guard so the snapshot held by the outer behavior is not overwritten and outer events are not republished.

Re-entrant nested commands. A domain-event handler dispatched from this behavior is free to call IMediator.Send. The nested command runs its own pipeline (including its own TransactionalCommandBehavior) but the nested invocation of this behavior detects re-entrancy and short-circuits. Callers that need fan-out from nested commands must dispatch manually via DispatchAggregateEventsAsync(IDomainEventPublisher, IAggregate, CancellationToken).

Cascade detection. The behavior snapshots every tracked aggregate's pending events at entry, dispatches only those snapshotted events, and then validates that each aggregate's pending-event list still exactly matches its snapshot by length AND per-position reference equality. If any handler changed the pending list (raised new events, cleared via AcceptChanges, replaced, or reordered) on the originating aggregate or on any other aggregate participating in the snapshot, DomainEventHandlerCascadedException is thrown with every offending aggregate and AcceptChanges() is not called on any aggregate.

Constructors

TrackedAggregateDomainEventDispatchBehavior(ITrackedAggregateSource, IDomainEventPublisher, ILogger<TrackedAggregateDomainEventDispatchBehavior<TMessage, TResponse>>)

public TrackedAggregateDomainEventDispatchBehavior(ITrackedAggregateSource trackedAggregateSource, IDomainEventPublisher publisher, ILogger<TrackedAggregateDomainEventDispatchBehavior<TMessage, TResponse>> logger)

Parameters

trackedAggregateSource ITrackedAggregateSource

The unit-of-work sidecar that exposes the aggregates tracked at the most recent successful commit.

publisher IDomainEventPublisher

The publisher used to fan out events to registered handlers.

logger ILogger<TrackedAggregateDomainEventDispatchBehavior<TMessage, TResponse>>

The logger used to record dispatch diagnostics.

Exceptions

ArgumentNullException

Thrown when any constructor argument is null.

Methods

Handle(TMessage, MessageHandlerDelegate<TMessage, TResponse>, CancellationToken)

public ValueTask<TResponse> Handle(TMessage message, MessageHandlerDelegate<TMessage, TResponse> next, CancellationToken cancellationToken)

Parameters

message TMessage
next MessageHandlerDelegate<TMessage, TResponse>
cancellationToken CancellationToken

Returns

ValueTask<TResponse>