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
| Field | Storage | Required | Notes |
|---|---|---|---|
| Id | Guid primary key | Yes | |
| EventId | Guid | Yes | Unique index IX_OutboxMessages_EventId |
| EventType | varchar(160) | Yes | Integration event type name |
| PayloadJson | jsonb | Yes | Serialized event payload |
| OccurredAtUtc | timestamptz | Yes | Ordering field for the relay |
| ProcessedOnUtc | timestamptz | No | Set when successfully published |
| Status | varchar(40) | Yes | Pending, Failed, or Processed |
| RetryCount | integer | Yes | Incremented on failure |
| ErrorMessage | varchar(1000) | No | Set on failure |
| CorrelationId | varchar(120) | No | Correlation for tracing |
| TenantId | Guid | No | Envelope tenant; null for asset events (numeric tenant travels inside the payload) |
| SourceService | varchar(120) | Yes | Emitting 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
TenantIdcolumn 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
SaveChangesoverride 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 onEventId;IX_OutboxMessages_Status_OccurredAtUtcon(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
StatusPendingorFailed, ordered byOccurredAtUtc. Successful publishes are marked processed; failures are marked failed, incrementingRetryCountand recording an error message. - No broker, rows persist: when no local broker is available, rows remain in
Pendingand 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
Related Articles
See Also
Keywords
- Outbox
- Outbox Storage
- Draft database documentation
Source References
microservices/src/asset-service/Infrastructure/AssetDbContext.csmicroservices/src/asset-service/Messaging/OutboxRelayHostedService.csmicroservices/src/contracts/Events/AssetLifecycleEvents.csmicroservices/src/asset-service/Infrastructure/Migrations/AssetDbContextModelSnapshot.cs
Revision Information
- Status: Draft
- Last reviewed: 2026-07-17
- Review cycle: Quarterly