Flow
Decisions

0015 — Domain-scoped outbox and table prefixes

Each domain owns its outbox, event types, and table prefix inside the service database.

Decided. Event delivery infrastructure and domain state live in per-domain tables, not one service-wide events / event_types / commands set. Every table that belongs to a domain is named with that domain's slug as a prefix. The service database holds only cross-domain platform tables (principals and related auth projections) plus one prefixed table group per domain.

Alternatives

  • One outbox per service (previous layout). Simpler migrations early on, but blurs domain ownership — events, event_types, and commands look like platform tables when they are domain concerns. A service with chart and ledger domains would share one outbox and one idempotency namespace.
  • Schema per domain (widget.events, order.events). Same isolation, but Postgres schema-per-tenant patterns add search-path and tooling friction we do not need inside one service database.
  • Unprefixed domain tables (widgets, items). No consistent rule for spotting which domain owns a table or where outbox rows belong.

Table naming

Each domain declares a domain slug: short, lowercase, snake_case (order, widget, chart, ledger).

RuleExample (domain order)
Root entity table is named exactly the domain slugorder
Every other domain table starts with <slug>_order_item, order_line
Outbox and command idempotency use the same prefixorder_events, order_event_types, order_commands

Service-scoped tables (no domain prefix): principals, principal_roles, principal_groups, and any future tables that exist only for HTTP/process wiring shared across domains in that service.

Outbox per domain

Each domain's outbox is its own <domain>_events table. The publisher drains every domain outbox in the service. Broker topology is one fanout per domain; see 0016.

Why

  • Ownership matches structure. Chart, ledger, and widget domains in one service each carry their own outbox, event-type registry, and idempotency namespace.
  • Table names document the boundary. Prefixes make code review and SQL inspection show which domain owns a migration without reading comments.
  • Same transactional outbox. This does not change 0004; it scopes the table to the domain that emits the events.

Consequences

  • Adding a domain adds a migration bundle (<domain>, <domain>_events, …), a publisher drain target, and a feed route — not rows in shared platform tables.
  • Event types are registered per domain, so cross-domain name collisions inside one service database are impossible.
  • The template service implements this layout with domain1 and domain2 sample domains.
  • 0004 remains binding for outbox semantics; the "one events table per service" wording there is superseded by this ADR for table placement and naming.

Canonical source: docs/decisions/0015-domain-scoped-outbox-and-table-prefixes.md.

On this page