Give each domain event type one dispatcher that owns run-all, aggregation and cancellation, so a cancelled publish stops and propagates unwrapped - #434
Merged
Conversation
…tion and cancellation, so a cancelled publish stops and propagates unwrapped
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Each domain event type now has one internal dispatcher that owns the whole dispatch contract, and a cancelled publish stops and propagates unwrapped.
Before, the contract was split:
DomainEventPublisherran the handler loop and aggregated failures, andNotificationHandlerWrapper<T>resolved the handlers and nested the pipeline handlers, joined by aFuncparameter that only ever received one value. The loop'scatch (Exception)also caughtOperationCanceledException, so after the caller's token fired the remaining handlers kept running, and the caller got anAggregateExceptioninstead of a cancellation. In the outbox relay thatAggregateExceptionpassed the shutdown filter: every message dispatched during a graceful stop was logged as an error, counted in the failed metric, and the relay kept dispatching the rest of the batch, so handlers that ignore the token ran their side effects during shutdown (and again later, because the cancelled record step rolls the batch back).DomainEventDispatcher<TEvent>(internal; replacesNotificationHandlerWrapper<T>) resolves the event's handlers and pipeline handlers, runs the pipeline handlers in registration order around the whole set of handlers, and runs the handlers in registration order.OperationCanceledExceptionpropagates unwrapped, even after an earlier handler failed, because the caller abandons the whole publish. A handler's ownOperationCanceledExceptionwhile the token is still live is an ordinary failure. This matches the delivery dispatcher's rule.AggregateException, even when only one handler failed.DomainEventPublisherkeeps only the untyped and typed entry points and the per-type cache. The one-valueFuncseam and theNotificationHandlerExecutorrecord are deleted.IDomainEventPublisherXML docs state the contract (with the exceptions it throws), anddocs/articles/domain-modeling.mdgets a short paragraph.No public API change.
Verification
dotnet build Vulthil.SharedKernel.slnx: 0 warnings, 0 errors.DomainEventPublisherTests, through the publisher's methods with real DI: a later handler still runs after a failure; several failures come in oneAggregateExceptionin handler order; a cancelled token stops the handlers and comes through unwrapped; a cancellation in the last handler comes through unwrapped; a cancellation after a failed handler still wins; a handler's own cancellation while the token is live is a failure; an already cancelled token runs no handler; pipeline handlers wrap the whole set of handlers; the event's runtime type picks the handlers.OutboxRelayCycleTests, with the real publisher andDomainEventOutboxDispatcher: a cancelled domain-event dispatch stops the relay cycle with nothing recorded, no second message dispatched and nothing logged.Vulthil.IntegrationTests(Docker): 70/70 on net10.0 and net9.0.MessagingIntegrationTests: 13/13 on net10.0.dotnet packpackage validation against 1.2.0 passes forVulthil.SharedKernel.Application.Backport to v1.0: no