Skip to content

feat: event upcaster registration extension point (ADR-010 follow-up, ADR-090) - #268

Merged
ivanball merged 1 commit into
mainfrom
feature/event-upcaster-extension-point
Aug 22, 2026
Merged

feat: event upcaster registration extension point (ADR-010 follow-up, ADR-090)#268
ivanball merged 1 commit into
mainfrom
feature/event-upcaster-extension-point

Conversation

@ivanball

Copy link
Copy Markdown
Owner

Ships the consumer-side mechanism ADR-010 named as follow-up work: the event upcaster registration extension point.

What's new

  • IEventUpcaster / IEventUpcaster<TSource, TTarget> (Application): a pure payload mapping from a retired integration-event contract to its successor; the non-generic members come from default interface implementations so authors write one method.
  • EventUpcasterRegistry: indexes by source type, follows chains (V1 -> V2 -> V3) by declared target type, preserves MessageId/DateOccurred across every hop (inbox dedup keys survive by construction), and throws at construction, naming the offenders, on duplicate sources, self-maps, or cycles. An internal hosted service resolves it at host start so misconfiguration fails startup, not the first message.
  • services.AddEventUpcaster<TSource, TTarget, TUpcaster>(): TryAddEnumerable idiom (same shape as AddScheduledJob), accumulates across modules.
  • Both delivery paths consult the registry: DomainEventDispatcher upcasts before selecting IIntegrationEventHandler<> (covers monolith mode and pre-upgrade outbox rows; domain-handler dispatch untouched), and broker hosts drain a retired contract via RegisterUpcastedIntegrationEventConsumer<TOld>() backed by the new dedicated UpcastingIntegrationEventConsumer<TOld> (existing consumer hot path and shipped ctor untouched).
  • Two new fitness functions on EventConventionTestsBase, inherited by every consumer tree with no edit: unique upcaster source types, and target SchemaVersion strictly higher than source. Pass trivially at zero upcasters.

Deliberately out of scope (recorded in ADR-090)

Outbox type-name aliasing for prematurely deleted types; producer-side rewriting of old rows.

Verification

  • dotnet build MMCA.Common.slnx -c Release: 0 warnings, 0 errors (TWAE + five analyzers + PublicAPI gate; all new public symbols baselined in PublicAPI.Unshipped).
  • Full suite Release: 3804 passed, 0 failed (35 new tests: registry chaining/cycle/envelope, dispatcher upcast + no-registry regression guard, broker consumer flow, startup validator, rule-level fitness tests with fixture maps, in-process end-to-end).
  • FACTS regenerated (fitness methods 104 -> 106); --check clean.
  • All V1/V2/V3 sample contracts live in test assemblies only, so frozen integration-event contract snapshots do not churn.

Companion docs PR: ivanball/ivanball.github.io#120 (ADR-090 + ADR-010 addendum). Additive change; release + consumer pin sweep will follow later via the normal /push-release flow (minor bump).

🤖 Generated with Claude Code

https://claude.ai/code/session_016usKhixm2drfHmwmmmqwbn

… ADR-090)

Ships the consumer-side mechanism ADR-010 promised: IEventUpcaster<TSource,
TTarget> + AddEventUpcaster<...>() (TryAddEnumerable idiom) assembled into a
chaining EventUpcasterRegistry that walks retired contracts to their terminal
type, preserves MessageId/DateOccurred across every hop, and fails startup on
duplicate sources, self-maps or cycles. Both delivery paths consult it:
DomainEventDispatcher upcasts before selecting integration handlers, and
broker hosts drain retired types via RegisterUpcastedIntegrationEventConsumer
backed by the new UpcastingIntegrationEventConsumer. Two new fitness
functions on EventConventionTestsBase gate upcaster shape (unique source
types; strictly increasing SchemaVersion). 35 new tests; FACTS regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016usKhixm2drfHmwmmmqwbn
@ivanball
ivanball merged commit e7822df into main Aug 22, 2026
13 checks passed
@ivanball
ivanball deleted the feature/event-upcaster-extension-point branch August 22, 2026 03:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant