Feature/projection message outbox 84#100
Merged
Merged
Conversation
…ssages Closes #84. Marten projections can publish side-effect messages on commit (transactionally with the projection write) via Wolverine.Marten's MartenToWolverineMessageBatch. Polecat's projection daemon had no equivalent — the IProjectionBatch.PublishMessageAsync hook was a Task.CompletedTask stub. This adds the Polecat-side plumbing only — a future Wolverine.Polecat will implement IMessageOutbox to bridge into Wolverine's outgoing- message machinery. The user explicitly scoped this PR Polecat-only. ## Added - `Polecat.Events.Aggregation.IMessageBatch` — extends `JasperFx.Events.IMessageSink` with `BeforeCommitAsync(token)` and `AfterCommitAsync(token)` hooks. The batch buffers messages in PublishAsync; the implementer chooses which hook to flush in based on the desired delivery guarantee. - `Polecat.Events.Aggregation.IMessageOutbox` — factory that vends a fresh `IMessageBatch` per projection daemon batch. The signature takes the public `IDocumentSession` (rather than internal `DocumentSessionBase`) so downstream integrations don't need InternalsVisibleTo; `IDocumentSession` already exposes `ITransactionParticipantRegistrar` for enrollment. - `Polecat.Events.Aggregation.NulloMessageOutbox` — singleton no-op default (drops messages, fires no hooks). Apps that don't integrate a message bus pay zero overhead. - `EventStoreOptions.MessageOutbox` property defaulting to NulloMessageOutbox. This is the registration point for downstream integrations. ## Wired through PolecatProjectionBatch - New `_messageBatch` field + `_messageBatchGate` semaphore for thread-safe lazy initialization. Stays null when no projection in this batch publishes. - `PublishMessageAsync(object, string)` — was a Task.CompletedTask stub; now lazily creates the batch via `IMessageOutbox.CreateBatch` and forwards the message via cached generic `IMessageSink.PublishAsync<T>` MethodInfo. The reflection lookup is done once at static init; per-publish cost is one MakeGenericMethod + one Invoke (off the per-event hot path — only fires when a projection explicitly emits a side-effect). - New `PublishMessageAsync(object, MessageMetadata)` overload mirroring the same pattern; satisfies the JFx.Events.IProjectionBatch contract added in the canonical interface (default impl forwards tenant-only; we wire the metadata-aware path so downstream integrations can stamp correlation / causation / headers). - ExecuteAsync passes the snapshot batch into the resilience lambda's state tuple. `BeforeCommitAsync` fires inside the SQL transaction (right before `tx.CommitAsync`) so an at-least-once outbox can persist its envelopes in the same transaction. `AfterCommitAsync` fires outside the resilience pipeline (after success) so it does not re-fire on a transient SQL retry. ## Tests `src/Polecat.Tests/Daemon/projection_message_outbox_tests.cs` — 4 cases: - No publish in the batch → `IMessageOutbox.CreateBatch` is never called and no hooks fire (the common no-op path) - Publish creates the batch exactly once and forwards every published message with the right type and tenant id - BeforeCommit + AfterCommit hooks both fire after `ExecuteAsync` completes, in the right order (publish → before → after) - The metadata overload correctly extracts tenant id (default IMessageSink impl) and forwards through the same path All 4 pass against SQL Server 2025 docker on net9.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds docs/events/projections/side-effects.md adapted from the Marten equivalent: how to override RaiseSideEffects, what AppendEvent and PublishMessage do, and how to register a custom IMessageOutbox to actually deliver the published messages. Notes that a Wolverine.Polecat integration is on the roadmap. Polecat does not (yet) support side effects on Inline projections, so the corresponding Marten section is omitted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
No description provided.