← All writing

Sep 30, 2025 · 2 min read

Monolith until it hurts

Microservices solve an organizational problem you do not have yet. A defense of the boring deployable, with the actual thresholds where the answer changes.

architecturestartups

A three-person team asked us to review their architecture: eleven services, two message queues, a service mesh, and a product that had not launched. Every service was clean. The system was a disaster. Deploys took coordination across repos, local development required a container orchestra, and a feature touching three services took a week of plumbing for a day of logic.

Nobody makes this mistake out of stupidity. They make it out of ambition. Microservices are what serious companies run, so building them from day one feels like skipping ahead. But that reasoning inverts cause and effect. Large companies did not succeed because they adopted microservices. They adopted microservices because they succeeded, and success brought a specific problem: too many engineers trying to change the same deployable without stepping on each other.

That is the actual problem microservices solve. An organizational one. Service boundaries let team A deploy without asking team B. If there is no team A and team B, if there are three of you in one room, you have taken on the costs of distribution (network failures between components, versioned contracts, distributed tracing, eventual consistency bugs that only appear under load) in exchange for solving a coordination problem you do not have.

The monolith, meanwhile, keeps embarrassing its critics. One repository, one deploy, one process to debug. Function calls instead of network calls, which means stack traces instead of correlation IDs. A transaction is just a transaction. Refactoring across module boundaries is an afternoon with the type checker instead of a cross-team migration project. For a small team iterating toward product-market fit, each of these is worth real money weekly.

The honest counterargument is that monoliths rot into big balls of mud. True, but the rot comes from missing internal boundaries, not from missing network boundaries. A monolith with clear modules, enforced dependency directions, and a disciplined schema stays maintainable for a very long time. And crucially, those internal boundaries are exactly what you would need to draw anyway before extracting a service. A well-structured monolith keeps the microservices option open. A premature distribution forecloses the monolith option at enormous cost.

So when does the answer change? Watch for these, not for vanity metrics:

Deploy contention: multiple teams regularly blocked on each other's releases. A component with wildly different scaling needs than the rest, like a CPU-heavy processing pipeline strapped to a lightweight API. A hard isolation requirement, regulatory or security, where a shared process is genuinely unacceptable. A part of the system needing a different runtime for defensible technical reasons.

Notice that all four thresholds are concrete and observable. None of them is "we might need to scale someday." Might-need-someday is how three people end up operating eleven services.

Extract the first service when it hurts, from a codebase whose modules already told you where to cut. Until then, the boring deployable is not technical debt. It is the fastest vehicle you can drive.

We build products and AI systems for founders and teams at MoonShift Lab. If this resonated, say hello.