
Somewhere along the way, “microservices” became shorthand for “doing it properly,” and “monolith” became shorthand for legacy or unambitious. In our experience building both, that framing is backwards more often than not, and it's led more than one client toward a decision that cost them months of extra complexity for a problem they didn't actually have.
What microservices actually solve
Microservices earn their complexity when you have genuinely independent teams that need to deploy on different schedules without blocking each other, or when specific parts of a system have wildly different scaling needs — a payment processing service that needs to scale independently from a content management service, say. They also help when different parts of a system are best built in different languages or by teams with different specializations.
What they cost in return is real: network calls where you used to have a function call, distributed transaction handling instead of a database transaction, more complex deployment and monitoring, and a much harder debugging story when something breaks across service boundaries. None of that is free, and all of it needs someone on the team who's actually done it before, not just read about it.
How we actually make the call
For the majority of the client projects we build — a business application with one team, a reasonably clear scope, and no extreme scaling requirements on day one — we start with a well-structured monolith. Not a tangled one; a monolith with clean internal module boundaries, so that if a piece of it genuinely needs to be pulled out into its own service later, that extraction is a clean cut rather than a rewrite. That's the part people skip: you can build a monolith today in a way that keeps a microservices path open later, without paying the microservices tax before you need it.
We do go straight to a services-based architecture when a client already has multiple independent teams that need separate deploy cycles, or when we can point to a specific, concrete scaling need — not a hypothetical one. “We might need to scale this part independently someday” isn't a strong enough reason on its own; “this specific service handles 50x the traffic of everything else in the system” is.
The pattern we'd push back on hardest is a small team adopting microservices because it's what they read about at a much larger company. The operational overhead of running and monitoring multiple services is a genuine tax on a small team's time, paid every single sprint, and it's rarely worth it until the org and the traffic have actually grown into needing it.
A middle path worth knowing about
There's also a middle ground we use more than either extreme: a modular monolith, where the codebase is organized into clearly separated modules with well-defined boundaries and minimal cross-dependencies, deployed as a single unit. It gives most of the organizational clarity of microservices — you can reason about one module without reading the whole system — without the network overhead and operational complexity of running separate services. For a lot of the client projects that ask us about microservices, this is what we actually end up recommending once we've talked through what they're really trying to solve.
If a client is still unsure which way to go, the question we ask is blunt but useful: name the specific failure mode you're trying to avoid. If the answer is “our deploys are slow” or “one bug takes down the whole app,” a modular monolith with better boundaries and better testing usually solves that more cheaply than a full services split. If the answer is “this specific part of our system needs 50 times the infrastructure of everything else,” that's a real microservices case.
















