Per-message metadata override for projection side-effect publishing (GH-2545)#2550
Merged
jeremydmiller merged 1 commit intomainfrom Apr 21, 2026
Merged
Per-message metadata override for projection side-effect publishing (GH-2545)#2550jeremydmiller merged 1 commit intomainfrom
jeremydmiller merged 1 commit intomainfrom
Conversation
…H-2545) Lets a Marten projection's RaiseSideEffects pass per-message metadata (correlation id, causation id, tenant, headers) when it publishes a Wolverine command, and have those values flow all the way through to the receiving handler's IMessageContext and Marten IDocumentSession. This is the first-class way to thread a business correlation id through an event → side-effect command → derived event chain (the "todo-list" pattern from the issue). ## Plumbing - Bump JasperFx.Events 1.28.1 -> 1.29.0, Marten 8.31 -> 8.32, Marten.AspNetCore 8.31 -> 8.32, and Weasel.* 8.14.0 -> 8.14.1 in Directory.Packages.props to pick up the new IMessageSink / IEventSlice / IProjectionBatch metadata overloads (#186, marten#4269) and the Spectre.Console 0.55 fix (weasel#252). - DeliveryOptions: add CorrelationId and CausationId properties. Override(envelope) sets envelope.CorrelationId directly and stores CausationId in envelope.Headers["causation-id"] (string semantics preserve user-supplied values that aren't Guids — Wolverine's native ConversationId is Guid-typed and would lose information). - MessageBus.TrackEnvelopeCorrelation no longer clobbers a pre-existing envelope.CorrelationId. This was the load-bearing reason a DeliveryOptions.CorrelationId override didn't survive routing — the late-running correlation tracker was overwriting it with the (null) MessageContext correlation id. - MartenToWolverineMessageBatch: implement the new IMessageSink.PublishAsync<T>(T, MessageMetadata) overload by mapping IMetadataContext fields onto a DeliveryOptions and delegating to Context.PublishAsync. - OutboxedSessionFactory.configureSession: when an envelope carries the new "causation-id" header (set via DeliveryOptions.CausationId), use it as session.CausationId in preference to the default ConversationId.ToString() chain. This is the receiving-side hook that closes the loop on the metadata override — events written by the handler inherit the user's chosen causation id. ## Test MartenTests/Bugs/Bug_2545_raise_side_effects_with_metadata_override.cs runs an async-daemon projection that publishes a side-effect command with explicit MessageMetadata, and asserts the handler observes the overridden CorrelationId on its MessageContext and both the overridden CorrelationId AND CausationId on its Marten session. Local verification: - Bug_2545 integration test: 1/1 pass - MartenTests subset (publish/outbox/ancillary): 23/23 pass - Wolverine CoreTests: 1346/1346 pass ## Docs New "Overriding Side-Effect Message Metadata" section appended to docs/guide/durability/marten/event-forwarding.md, covering the motivating "todo-list" use case and the field-by-field mapping table (MessageMetadata -> envelope -> handler/session). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 21, 2026
Closed
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
Closes #2545.
Lets a Marten projection's
RaiseSideEffectspass per-message metadata (correlation id, causation id, tenant, headers) when it callsslice.PublishMessage(...), and have those values flow all the way through to the receiving handler'sIMessageContextand MartenIDocumentSession. This is the first-class way to thread a business correlation id through an event → side-effect command → derived event chain — the "todo-list" pattern from the issue.Cross-repo recap
This PR is the final step in a four-PR chain:
JasperFx.Events 1.29.0:MessageMetadatastruct + metadata overloads onIMessageSink,IEventSlice<T>,IProjectionBatch.ProjectionBatch.PublishMessageAsync(object, MessageMetadata)overload.DeliveryOptions, and stamps the receiving handler.Plumbing
Directory.Packages.props:JasperFx.Events1.28.1 → 1.29.0,Marten/Marten.AspNetCore8.31 → 8.32,Weasel.*8.14.0 → 8.14.1.DeliveryOptions: newCorrelationIdandCausationIdproperties.Override(envelope)setsenvelope.CorrelationIddirectly and storesCausationIdinenvelope.Headers["causation-id"](string semantics preserve user-supplied values that aren'tGuids — Wolverine's nativeConversationIdisGuid-typed and would lose information).MessageBus.TrackEnvelopeCorrelation: no longer clobbers a pre-existingenvelope.CorrelationId. This was the load-bearing reason aDeliveryOptions.CorrelationIdoverride didn't survive routing — the late-running correlation tracker was overwriting it with the (null)MessageContextcorrelation id.MartenToWolverineMessageBatch: implements the newIMessageSink.PublishAsync<T>(T, MessageMetadata)overload by mappingIMetadataContextfields onto aDeliveryOptionsand delegating toContext.PublishAsync.OutboxedSessionFactory.configureSession: when an envelope carries the new"causation-id"header (set viaDeliveryOptions.CausationId), use it assession.CausationIdin preference to the defaultConversationId.ToString()chain. This is the receiving-side hook that closes the loop — events written by the handler inherit the user's chosen causation id.Test
MartenTests/Bugs/Bug_2545_raise_side_effects_with_metadata_override.csruns an async-daemon projection that publishes a side-effect command with explicitMessageMetadata, and asserts the handler observes:IMessageContext.CorrelationIdmatches the overrideIDocumentSession.CorrelationIdmatches the overrideIDocumentSession.CausationIdmatches the overrideDocs
New "Overriding Side-Effect Message Metadata" section appended to
docs/guide/durability/marten/event-forwarding.md, covering the motivating todo-list use case and a field-by-field mapping table (MessageMetadata→ envelope → handler/session).Test plan
dotnet test src/Testing/CoreTests/CoreTests.csproj --framework net9.0— 1346/1346 passdotnet test src/Persistence/MartenTests/MartenTests.csproj --framework net9.0 --filter "publish_messages|Bug_2545|MartenOutbox|AncillaryStores"— 23/23 passBug_2545integration test — pass🤖 Generated with Claude Code