Skip to main content

Outbox Storage

Summary

Source-backed persistence of the transactional outbox (OutboxMessages) — the table that captures asset integration events in the same transaction as the aggregate change and is later drained by a polling relay. It carries no tenant query filter.

Audience

Backend engineers, database reviewers, architects, QA, support, operations, security reviewers, and product owners.

Overview

The Asset Service uses a transactional outbox to publish integration events reliably. When an aggregate change maps to an integration event, a row is written to the OutboxMessages table within the same transaction that persists the aggregate. A background relay later reads pending rows and publishes them. The outbox row uses the shared-kernel OutboxMessage type and is messaging infrastructure — it is not tenant-filtered.

Confirmed persistence behavior

FieldStorageRequiredNotes
IdGuid primary keyYes
EventIdGuidYesUnique index IX_OutboxMessages_EventId
EventTypevarchar(160)YesIntegration event type name
PayloadJsonjsonbYesSerialized event payload
OccurredAtUtctimestamptzYesOrdering field for the relay
ProcessedOnUtctimestamptzNoSet when successfully published
Statusvarchar(40)YesPending, Failed, or Processed
RetryCountintegerYesIncremented on failure
ErrorMessagevarchar(1000)NoSet on failure
CorrelationIdvarchar(120)NoCorrelation for tracing
TenantIdGuidNoEnvelope tenant; null for asset events (numeric tenant travels inside the payload)
SourceServicevarchar(120)YesEmitting service name
  • No tenant query filter: the outbox is messaging infrastructure and is not covered by the global tenant query filter.
  • Envelope tenant nullability: the envelope TenantId column is a nullable Guid and is null for asset events; the numeric tenant scope is carried inside the payload rather than in this column.
  • Schema version: the event schema version is 1, expressed inside the event rather than as a separate database column.
  • Save-boundary participation: outbox rows are written in the same transaction as the aggregate. The SaveChanges override dispatches domain events to the outbox and then calls the base save, so the aggregate and its outbox rows commit together. A completeness guard runs before the outbox row is written.
  • Indexes: IX_OutboxMessages_EventId (unique) supports duplicate detection on EventId; IX_OutboxMessages_Status_OccurredAtUtc on (Status, OccurredAtUtc) supports the relay's pending scan.
  • Relay behavior: a hosted background service polls on a fixed interval (approximately every 10 seconds), resolves a scoped processor, and processes a bounded batch (up to 50). The pending selection reads rows with Status Pending or Failed, ordered by OccurredAtUtc. Successful publishes are marked processed; failures are marked failed, incrementing RetryCount and recording an error message.
  • No broker, rows persist: when no local broker is available, rows remain in Pending and are retried on subsequent passes; they are observable via the outbox diagnostics read path.
  • Event coverage: six asset lifecycle integration event types are emitted through the outbox (AssetRequested, AssetAssigned, AssetReturned, AssetRejected, AssetLostOrDamaged, AssetRetired).
  • No cleanup or dead-letter: there is no cleanup worker, no replay mechanism, and no dead-letter table in source.

Classification

Implemented transactional-outbox persistence with a polling relay.

Requires confirmation

Outbox retention, replay, and dead-letter handling are absent from source and require confirmation.

Diagram

See Also

Keywords

  • Outbox
  • Outbox Storage
  • Draft database documentation

Source References

  • microservices/src/asset-service/Infrastructure/AssetDbContext.cs
  • microservices/src/asset-service/Messaging/OutboxRelayHostedService.cs
  • microservices/src/contracts/Events/AssetLifecycleEvents.cs
  • microservices/src/asset-service/Infrastructure/Migrations/AssetDbContextModelSnapshot.cs

Revision Information

  • Status: Draft
  • Last reviewed: 2026-07-17
  • Review cycle: Quarterly