Skip to content
CommonLayer
Documentation menu
On this page

ADR-0002: Vertical domain crates, ports and the event outbox

Status: Accepted (plan decision D2). Implemented, with the deviations listed below.

Context

Domains are built in parallel by different engineers and must be testable on their own. Direct calls between domain crates would create a dependency web in which every change ripples everywhere and cycles become likely.

Decision

  • Each domain is one crate, layered inside: domain/ (pure types and algorithms, no I/O) → repo/ (SQL) → service/ (application services, the only layer that opens transactions and checks permissions) → http/ (handlers with no business logic).
  • Domain crates never depend on each other. Synchronous cross-domain reads go through read-only ports (traits) declared in crates/commonlayer-core/src/ports/ and implemented by the providing crate; the composition root wires them.
  • Cross-domain reactions go through the event outbox: a service writes a typed event to domain_events in its own transaction; a dispatcher job fans it out to subscriber jobs.
  • A crate writes only the tables it owns.

Consequences

  • Every domain has its own integration tests against real PostgreSQL with fakes for the ports it consumes (crates/commonlayer-testkit/src/fakes/).
  • Ports grew additively during implementation (for example MaintainerDirectory::payee_readiness, SecurityPosture::retention_protection, ArtifactCatalog::inventory); new methods carry default implementations so that providers and consumers could move independently.
  • Two outbound adapter traits (RegistryGateway, SignatureVerifier) also live in ports::artifacts, although ports were meant to be read- or enqueue-only, so that the artifacts and registry crates stay independent.
  • Deviations recorded in the status files: the projects crate inserts users rows for project service accounts (no port can create users; WP-10); the security crate keeps its own security_digest_index because ArtifactCatalog has no lookup by digest value (WP-12); domain crates depend on commonlayer-observability (ADR-0018).