Implements the Transactional Outbox pattern:
DatabaseOutboxPublisherBasewrites events to a database outbox table within the business transaction, andDatabaseOutboxRelayBase/DatabaseOutboxRelayHostedServiceBasepoll and forward them to anIEventPublisher.
The transactional outbox pattern ensures that domain events are reliably published even in the face of network or messaging failures. The pattern works in two parts:
-
Write: When a business mutation is committed,
DatabaseOutboxPublisherBase(anIEventPublisher) inserts serializedEventDatarows into an outbox table inside the same database transaction. Because the event write and the business write are in the same transaction, both succeed or both roll back atomically. -
Relay:
DatabaseOutboxRelayHostedServiceBaseis aTimerHostedServiceBasethat periodically polls the outbox table, deserializes the pending events, forwards them to the configuredIEventPublisher(e.g. Azure Service Bus), and marks them as sent — in a separate transaction with idempotency protection.
DatabaseOutboxRelayBase<TDatabase, TSelf> contains the relay logic and is parameterized by both the IDatabase type and the self-referencing relay type, enabling per-deployment SQL statement customization via SetStatementsByConvention(schemaName?).
- ⚛️ Atomic write:
DatabaseOutboxPublisherBase.PublishAsyncinserts serialized events into the outbox table within the callingIUnitOfWorktransaction — no two-phase commit required. - ⏱️ Timer-driven relay:
DatabaseOutboxRelayHostedServiceBaseinheritsTimerHostedServiceBase's configurableInterval,FirstInterval, error back-off, and pause/resume behavior. - 📦 Batch publishing: The relay reads a configurable
MaxDequeueSizeof pending rows per poll, publishes them as a batch toIEventPublisher, then marks all as sent in a single update. - 🔒 Idempotent relay: Each outbox row has a
Statussentinel; the relay skips already-sent rows, enabling safe replay after partial failures. - ⚙️ Convention SQL:
SetStatementsByConvention(schemaName?)derives the dequeue and complete stored procedure names from the schema name (e.g.[Outbox].[EventOutboxDequeue]), reducing boilerplate for standard deployments. - 📡 Relay invoker tracing:
DatabaseOutboxRelayInvokerwraps each relay batch with an OpenTelemetry span tagging batch size and result.
| Type | Description |
|---|---|
DatabaseOutboxPublisherBase |
Abstract IEventPublisher that inserts EventData rows into the outbox table within the current database transaction via a configured SQL statement. |
DatabaseOutboxRelayBase<TDatabase, TSelf> |
Abstract relay implementation: dequeues pending outbox rows, deserializes EventData, publishes to IEventPublisher, marks as sent. Configure SQL via SetStatementsByConvention(schema?) or set DequeueStatement / CompleteStatement directly. |
DatabaseOutboxRelayHostedServiceBase |
TimerHostedServiceBase that drives IDatabaseOutboxRelay.RelayAsync on a configurable interval. |
DatabaseOutboxRelayHostedServiceBase<TRelay> |
Generic variant accepting the IDatabaseOutboxRelay implementation via DI. |
DatabaseOutboxRelayArgs |
Configuration for the relay: MaxDequeueSize, PartitionKey, and whether to partition dequeue by partition key. |
DatabaseOutboxRelayInvoker |
InvokerBase<DatabaseOutboxRelayBase<TDatabase, TSelf>> emitting OpenTelemetry spans for relay batch executions. |
IDatabaseOutboxRelay |
Interface with single RelayAsync(CancellationToken) method implemented by DatabaseOutboxRelayBase. |
CoreEx.Database-IDatabaseandDatabaseCommandare used by relay and publisher implementations to execute outbox SQL.CoreEx.Events-IEventPublisheris the relay's publication target;EventDatais the serialized event payload stored in the outbox table.CoreEx.Hosting-TimerHostedServiceBaseis the base class forDatabaseOutboxRelayHostedServiceBase, providing timer, pause/resume, and health-check integration.CoreEx.Invokers-DatabaseOutboxRelayInvokerextendsInvokerBasefor relay-batch OpenTelemetry tracing.