
Choose a modular monolith before microservices
Start with one deployable system that has real internal boundaries, then split into services only when constraints force it.
Quick Take
Microservices are an operational strategy, not a default architecture. A modular monolith gives you most of the separation benefits while keeping deployments, debugging, and consistency tractable. Split into services only when you can name the constraint you are buying relief from.
Modular monolith means boundaries you can enforce
A modular monolith is a single deployable unit with explicit internal separation: modules own their data, expose narrow interfaces, and avoid reaching across boundaries through shared tables, shared models, or "just call this internal function."
The goal is not style. The goal is constraint: if a change crosses module boundaries, it should feel expensive enough that the team notices and fixes the boundary rather than normalizing the coupling.
Design boundaries around change, not around org charts
Define modules by what changes together, not by what sounds like a domain name. If two areas frequently evolve in lockstep, splitting them early creates coordination overhead without reducing risk.
A useful test is this: if a module can be understood, tested, and changed without needing to read or run the rest of the codebase, it is a boundary. If it cannot, it is a folder.
Split into services only when a specific constraint demands it
Microservices are justified when you have a concrete constraint that internal boundaries cannot solve, such as independent scaling needs, regulatory isolation, separate uptime requirements, or multiple teams blocked by deployment coupling.
When you do split, treat it as extracting a module with a proven interface. Keep the data ownership rule intact, and treat cross-service calls as a cost that must be paid deliberately, not accidentally.
Signal
Most teams underestimate how quickly microservices turn "architecture" into "operations." If you cannot describe your boundaries as enforceable rules with ownership and testing around them, you do not have microservices yet, you have distributed coupling.