There’s a reflex in our industry that says the grown-up version of any system is a pile of microservices. Draw the boxes, put a queue between them, and you’ve arrived. But every time you take something that used to be a function call and turn it into a network call, you start paying a tax — and like most taxes, it’s easy to sign up for and hard to stop paying. Whether it’s worth it is, as ever, it depends. What I want to do here is itemise the bill, because too many teams sign up for it without reading it.
What you’re actually paying
The moment a call leaves the process, you inherit a list of problems you simply didn’t have before:
- Partial failure. An in-process call either runs or it doesn’t. A network call has a third outcome: it might have worked, and you don’t know. Now you need timeouts, retries, idempotency so the retries don’t double-charge anyone, and a story for what happens when the other end is simply gone.
- Latency, and the way it compounds. One hop is a millisecond you didn’t used to spend. The problem is chatty designs: a request that quietly fans out into five sequential service calls has stacked five round-trips, and the user feels every one.
- No more free transactions. Inside one process, “update these two things together or not at all” is a database transaction. Across a boundary it’s a distributed consistency problem — sagas, compensating actions, the dual-write problem, eventual consistency you now have to explain to the business. That’s not a line of code; it’s a project.
- Contracts and versioning. Two services that talk must agree on a wire format, and they’re deployed independently, so they’re never quite on the same version. Every change is now a rollout dance: add the new field, support both, migrate, remove the old.
- You can’t just read the code any more. “What happened to this request?” stops being a stack trace and becomes a correlation ID, a tracing tool, and a log search across N services. Observability shifts from nice-to-have to the only way you can debug at all.
- Operational surface. One thing to deploy, monitor, secure, and back up becomes a dozen. Each needs a pipeline, alerts, certificates, secrets, a scaling policy. That cost is real and it recurs forever.
None of these are reasons never to distribute. They’re the price. The mistake isn’t paying it — it’s paying it without noticing, and getting nothing back.
The worst deal: paying the tax, buying nothing
The failure mode I’ve watched most often isn’t a monolith that should have been split. It’s a system that was split for the aesthetics of microservices and paid the entire tax for none of the benefit. A dozen services that all share one database. A “feature” that can’t ship without coordinating a release across three of them. A request that can’t complete unless four services are all up at once. That’s a distributed monolith: you’ve taken on every cost of distribution and kept all the coupling of a monolith. The worst of both columns.
It almost always traces back to splitting on the wrong lines — carving by technical layer, or splitting before the boundaries were even clean — rather than along the seams the business actually has. (Which is the whole case for finding your bounded contexts first.)
When the tax is worth paying
You pay the tax when you’re buying something with it that you genuinely need and can’t get any other way — the same short list of real forces every time:
- A slice that must scale independently of the rest.
- A part that must be deployed on its own rhythm by a different team — the deliberate use of Conway’s Law.
- Something with a different blast radius — tighter security, compliance, or uptime — that earns its own isolation.
- A workload that genuinely needs different technology than the core.
If a split buys you one of those, the tax is a fair price. If it buys you “it feels more modern,” you’ve just volunteered for a standing bill.
The default that dodges the tax
This is why, for the bulk of a system, the better trade is to not distribute — to keep things in one process with strong internal boundaries, and reach for the network only where a real force pays the tax back. A well-built monolith isn’t the embarrassing option; it’s the one that skips the whole list above for the 90% of your system that gets nothing from being distributed.
It also keeps your options open. Splitting a system is close to a one-way door — re-merging services is a miserable job nobody budgets for — whereas not splitting stays cheap to change your mind about, especially if the internal boundaries are clean enough that you could extract a piece later if a force ever shows up. Keep the boundary crisp and deletable and the topology becomes the reversible decision it should have been all along.
So before you draw the boxes and reach for the queue, do the boring thing: add up the tax, and name what you’re buying with it. If the answer is “independent scaling for this one hot path,” pay it gladly. If the answer is a shrug, keep your money — and your function calls.