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

  • 01Every module owns its data. No other module touches its tables directly.
  • 02Modules communicate through a shared in-process bus or direct interface calls, never by importing another module's internals.
  • 03Each module exposes a single public contract (facade). The contract is the only import surface other modules may use.
  • 04Database 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.