pattern

Modularity via the Modular Monolith

also called Modulith

A single deployable unit with strictly enforced internal module boundaries — the default most organisations should exhaust before distributing.

modularitymonolithmicroservicesboundaries

A modular monolith keeps one deployment artefact and one database connection, but enforces boundaries inside the process: modules expose explicit interfaces, cross-module access is compile-time or lint-time forbidden, and each module owns its tables with no cross-module joins.

The point is that module boundaries and process boundaries are separable decisions. Most arguments for microservices are really arguments for boundaries, and boundaries can be had without a network in the middle.

What you keep

Transactions that are actually transactions. Refactoring across a boundary with a compiler checking you. One deployment, one log stream, a stack trace that spans the whole request. Local function calls that cannot time out.

What you give up

Independent deployment and independent scaling. A single runtime failure domain. One technology stack. Team autonomy is bounded by a shared release train.

Industry example

Shopify runs one of the largest and best-documented modular monoliths in commerce, deliberately. Handling flash-sale traffic that spikes by orders of magnitude within seconds is usually assumed to be a microservices problem; Shopify's answer has been to keep the core monolith and attack the actual bottlenecks — componentising the codebase with enforced boundaries, sharding the database into independent "pods" so one large merchant's sale is isolated, and moving specific workloads out only where they genuinely differ.

The instructive inversion: their scaling unit is the data partition, not the service. That choice is available precisely because the application stayed one deployable, which meant a pod could be a complete self-contained copy of the system.

When to break it up

Extract a module when there is a concrete forcing reason, and extract only that module: it needs a different scaling profile (GPU inference, video transcoding), a different availability class, a different compliance boundary, or it is owned by a team whose release cadence genuinely cannot be reconciled with the train. "We have twenty engineers and we would like to be modern" is not one.

Trade-off

The honest cost is that discipline is required forever. Nothing physically stops a developer from reaching across a module boundary, so the boundaries must be enforced by tooling in CI, not by convention, or the modular monolith decays into a plain one.

Interview question

"A 20-engineer team with steady traffic proposes splitting into 12 microservices. Make the case against, and name the conditions under which you would change your mind."