Architecture Recipe
Modular Monolith
Ship fast with a single deployable, then carve module boundaries that let you extract services later without a rewrite.
Why
Microservices pay off at scale, but they impose coordination tax from day one. A modular monolith keeps everything in one process while enforcing strict module contracts. When a module outgrows the monolith, you extract it into its own service with the interface already defined.
Core Rules
- 01 — Every module owns its data. No other module touches its tables directly.
- 02 — Modules communicate through a shared in-process bus or direct interface calls, never by importing another module's internals.
- 03 — Each module exposes a single public contract (facade). The contract is the only import surface other modules may use.
- 04 — Database schema is namespaced per module (prefix or schema). Cross-module queries are forbidden.
Directory Shape
src/ ├── modules/ │ ├── billing/ │ │ ├── internal/ # private │ │ └── public/ # contract │ ├── identity/ │ └── inventory/ ├── shared/ # cross-cutting └── bootstrap.ts
Extraction Signal
When a module needs independent scaling, its own deploy cadence, or a different data store, extract it. Because the contract is already a narrow interface, you swap the in-process call for an HTTP or gRPC adapter without touching callers.