Mar 18, 2026

Monolith first, and when to leave it

#architecture#engineering

Almost every microservices migration I've seen at small companies was a mistake.

The pitch is always the same. "We need to scale." "Teams will move independently." "We'll deploy in isolation." The reality is usually this: you split your monolith into seven services run by three engineers, and now every feature touches four repos, your CI takes thirty minutes, you've reinvented RPC badly, and the bug you're chasing lives somewhere in the seams between services.

I lead a platform with 110+ database tables, 211+ APIs, and a real on-prem customer. We are monolith first by default. The Node and Postgres service does most of the work. Two Rust services exist because two specific workloads, high throughput ingestion and a websocket fan out, needed performance characteristics the monolith couldn't give us.

That's the rule. Split a service when you have measured a workload that the monolith demonstrably cannot serve. Not when you imagine it can't. Not because Google does it. Because you have a P99 latency graph and a deployment cadence problem that the boundary actually solves.

Until then, a monolith with disciplined module boundaries scales further than people expect. The cost of premature splitting is real and immediate. The cost of a monolith you eventually outgrow is a refactor, and by then you'll know exactly where the seams should be, because production told you.

Internal boundaries matter more than service boundaries. If your monolith has a clean services/ layer with explicit interfaces, you can split it later in a week. If it doesn't, your microservices will be just as tangled as your monolith was, except now they fail over the network.

Build the monolith. Make the modules clean. Split when you have evidence. Not before.