Add transactional outbox with trace propagation - #7
Merged
Conversation
… relay Tests for the outbox module are deferred and will be written from scratch.
Matches the rest of the project (tools.jackson). Jackson 3 throws unchecked JacksonException, so the rewrapping catch blocks are gone.
…rammatically Drops the redundant spring.notify.outbox.enabled flag — adding the module is the opt-in, matching how the channel modules activate. The relay now runs on the application TaskScheduler at the interval from OutboxProperties, so the poll interval has a single source of truth. Documents the feature.
…caller's transaction
Adds the configuration-processor so spring.notify.outbox.* keys autocomplete, with additional metadata correcting the two int defaults the processor reads as 0. Drops the unused notify-spring-boot-autoconfigure dependency and declares spring-context, spring-tx, and jackson-core instead of relying on transitives.
…greSQL Creates examples/outbox-email — a Spring Boot app proving the transactional outbox guarantee: notifications enqueued in a business transaction are delivered if the transaction commits, never sent if it rolls back. Demonstrates Order flow using JdbcClient, OutboxNotifier, and OutboxRelay. Integration-tested with Testcontainers (PostgreSQL + Mailpit) for end-to-end verification of commit/rollback semantics.
The relay required a TaskScheduler bean, which Boot only auto-configures when the application opts into scheduling — so using the outbox meant annotating your own application class. It now supplies a dedicated single-thread scheduler, keeping relay polling off the application's shared scheduler too. Also marks the new API @SInCE 1.1.0 and returns Optional from the example repository instead of null.
…er in tests poll() no longer lets a claimBatch failure escape to the scheduler: a database outage is logged and the next interval retries, instead of dumping a stack trace every cycle (and risking the scheduled task being cancelled). Replaces the hand-written notifier stub with RecordingNotifier from notify-test.
…order The no-op fallback relied on the nested tracing configuration being processed first; a reorder would have silently disabled propagation with no test failing. It is now conditional on the tracing beans being absent.
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
Adds
notify-spring-boot-outbox: a channel-agnostic transactional outbox so a notification can be persisted in the same database transaction as the business change that triggers it. Commit the transaction and delivery is guaranteed; roll it back and nothing is sent.OutboxNotifier.enqueue(request)persists in the caller's transaction and returns the outbox entry id (deliberately not a provider message id — nothing has been sent yet).OutboxRelaypolls withFOR UPDATE SKIP LOCKED(safe across instances, no leader election) and delivers through the normalNotifier, so interceptors, events, and observations still apply.attempts/next_attempt_atlive in the table, with capped exponential backoff and a terminalFAILEDstate. An undeserializable payload fails that entry immediately rather than blocking the batch.micrometer-tracingis optional; without it the outbox behaves exactly as before.DataSource— adding the module is the opt-in, so there is noenabledflag. Supplies its own scheduler, so applications need not enable scheduling.OutboxStoreSPI (JdbcClientimplementation ships); the canonical DDL is shipped but never auto-run against your database.Docs:
docs/outbox.md.Test plan
JdbcOutboxStoreagainst real PostgreSQL via Testcontainers, initialised from the shippedschema-notification-outbox.sqlso the published DDL is itself coveredSKIP LOCKEDconcurrency test: two concurrent claimers receive disjoint rowsOutboxTracePropagationOtelTestdrives the real enqueue → relay pipeline against an OpenTelemetry SDK and asserts the delivery span shares the request's trace id — verified to fail if the propagator is swapped for the no-opexamples/outbox-email: end-to-end proof against Postgres + Mailpit that the commit path delivers the email and the rollback path never sendsverifygreenNote: the outbox's Postgres tests require Docker, so CI's
verifynow needs it (~13s added).