Skip to content

0002. Build a modular monolith with ports and adapters

  • Status: accepted
  • Date: 2026-10-26

GuestCraft must deploy in one click on Vercel and run in Docker, with PostgreSQL as the only required service (analysis, chapters 2 and 4). It must also stay extensible: country packs, channel managers other than Channex, payment providers, door locks. Microservices would multiply deployments and failure modes; a single unstructured application would make the domain depend on its infrastructure and turn every change into a risk.

  • One deployable application, split into workspace packages with enforced boundaries (docs/architecture/repository.md).
  • core holds the domain model and the use cases, and declares ports: interfaces for everything outside it (persistence, Channex, jobs, clock, identifiers, country rules).
  • Adapters implement the ports in their own packages (db, channex, jobs, countries, auth).
  • server is the only composition root: it builds the adapters from the validated configuration and injects them. apps/web and the worker entry point of server (server/worker) start the same composition in two runtimes.
  • The HTTP layer (api) calls use cases and never touches the database directly.
  • The domain is testable without a database or network, with deterministic clocks and identifiers.
  • A second implementation of a port (another channel manager, another storage provider) is a new adapter, not a change to the core.
  • Some code that would be a direct call in a flat application goes through an interface. This cost is accepted for the ports listed in the analysis (chapter 16) and refused elsewhere: a port exists only where there is, or will be, more than one implementation, or where the boundary protects the domain from I/O.
  • dependency-cruiser enforces the rules in CI.
  • Microservices. Rejected: several deployments for one hotel, distributed transactions between inventory and reservations, and no benefit at the expected scale (up to 300 properties per instance).
  • A single Next.js application with logic in routes. Rejected: the domain would depend on the framework and the worker runtime could not reuse it.