"We need microservices to scale" is one of the most expensive sentences in software. Microservices scale organizations, not code — and adopting them before the organizational problem exists trades a manageable codebase for a distributed systems problem nobody asked for.
The Real Cost of Microservices
Every service boundary is a network call that used to be a function call. That means new failure modes (partial failures, retries, timeouts), new operational surface (service discovery, distributed tracing, per-service CI/CD), and new consistency problems (no more database transactions across a workflow). None of this is free, and all of it needs to be staffed and maintained regardless of team size.
When a Modular Monolith Wins
A single deployable unit with strict internal module boundaries — a modular monolith — gives most of what teams actually want from microservices (separation of concerns, independent testing, clear ownership) without the distributed systems tax. It's a single build, a single deploy pipeline, and cross-module calls stay in-process, transactional, and debuggable with a normal stack trace. For teams under roughly 20-30 engineers, this is almost always the better default.
A Decision Framework
Ask four questions before splitting a service out:
1. Deployment cadence — does this module genuinely need to ship on its own schedule, independent of the rest of the system?
2. Team ownership — is there a dedicated team that owns this domain end-to-end, or would splitting it just create a service with no clear owner?
3. Scaling profile — does this component have a resource profile (CPU, memory, throughput) different enough from the rest of the app to justify scaling it separately?
4. Bounded context clarity — is the domain boundary actually stable, or is it still being discovered? Splitting along a boundary that's still moving guarantees expensive rework.
If the answer to at least two of these isn't a clear yes, the module stays in the monolith.
Migration Path: Monolith-First
The teams that end up with a healthy microservices architecture almost always got there by starting with a modular monolith, enforcing module boundaries with linting and code ownership, and extracting a service only once a specific module has proven it needs independent deployment or scaling. Extracting from a well-modularized monolith is a mechanical refactor; retrofitting boundaries onto a codebase that was split too early is a rewrite.
Signals It's Time to Split
The honest signals are operational, not aspirational: a single module's deploys are blocking every other team's releases, a module needs a different scaling strategy than the rest (e.g., a video transcoding worker vs. a CRUD API), or a team has grown large enough that a shared deploy pipeline creates constant merge and release contention.
Wise Hustlers architects systems for the team that has to operate them, not the architecture diagram that looks impressive in a pitch deck. We default new products to modular monoliths and extract services only when the data says it's warranted.