architecture decision

ADR-001: Publish product events via a transactional outbox

Product change events are written to an outbox table in the same transaction as the change, then relayed to the broker, so an event is never lost or published for a rolled-back change.

Decision RecordFebruary 10, 2026AcceptedMessagingReliability

Context

The Product Catalog System is the source of truth for products, and other systems — notably the Search SystemSearch SystemSystemv1.0.0Internal system that keeps product data searchable. Consumes product change events from the Product Catalog System, main...Ownersearch-platformMapView docs — depend on a reliable stream of product-created, product-updated and product-deleted events. Publishing to the broker directly from the Product APIProduct APIServicev1.0.0The public-facing API for the product catalog. Handles commands to create, update and delete products, serves product re...Subscribescreate-product, update-product +2APIsOpenAPIOwnerproduct-platformMapRepoView docs request path creates a dual-write problem: if the database commit succeeds but the broker publish fails (or vice versa), the catalog and its consumers drift out of sync. Lost or phantom events are hard to detect and expensive to reconcile.

Decision

The Product API writes every product change and a corresponding row in an outbox table inside a single database transaction in the Product DatabaseProduct DatabaseContainerv1.0.0PostgreSQL database that is the system of record for all product data.MapView docs. A separate relay, the Product Search PublisherProduct Search PublisherServicev1.0.0Reads product changes from the product database (outbox) and reliably publishes product-created, product-updated and pro...Publishesproduct-created, product-updated +1Ownerproduct-platformMapRepoView docs, reads unpublished outbox rows in order and publishes them to the broker with at-least-once delivery, marking each row published once acknowledged.

This decouples persistence from publishing: the request path only touches the database, and event delivery becomes an independent, retryable concern.

Consequences

  • Events are never lost and never published for a change that rolled back — the change and its event share a transaction boundary.
  • Consumers must be idempotent, since at-least-once delivery means an event can be redelivered.
  • There is a small publish latency between the commit and the relay draining the outbox.
  • The outbox table needs a retention/cleanup policy so it doesn’t grow unbounded.