0002. Build a modular monolith with ports and adapters
- Status: accepted
- Date: 2026-10-26
Context
Section titled “Context”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.
Decision
Section titled “Decision”- One deployable application, split into workspace packages with enforced boundaries (
docs/architecture/repository.md). coreholds 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). serveris the only composition root: it builds the adapters from the validated configuration and injects them.apps/weband the worker entry point ofserver(server/worker) start the same composition in two runtimes.- The HTTP layer (
api) calls use cases and never touches the database directly.
Consequences
Section titled “Consequences”- 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.
Alternatives considered
Section titled “Alternatives considered”- 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.