I've lost count of how many architecture diagrams I've seen where microservices are drawn as a bunch of clean little boxes with arrows between them, everything labeled, everything tidy. Reality never looks like that. Reality looks like a whiteboard somebody gave up on halfway through, with three arrows crossing each other and a sticky note that just says "ask Felipe."
That gap between the diagram and the whiteboard is basically what this whole architecture style is about, and it's the part almost nobody talks about when they're trying to convince you to adopt it.
The pitch is always the same
Every conference talk on microservices starts the same way: monoliths don't scale, teams step on each other's toes, deploys are scary, and the fix is to break everything into small, independent services that each do one thing well. Netflix did it, Amazon did it, so surely your 12-person team building an internal invoicing tool should do it too.
Except Netflix and Amazon didn't adopt microservices because it was elegant. They adopted it because they had hit a wall that most companies never come close to. Thousands of engineers, deploy cadences measured in minutes, traffic patterns that would melt a monolith. For them, the complexity of running fifty services was cheaper than the complexity of coordinating fifty teams inside one codebase.
Most teams reaching for microservices aren't solving that problem. They're solving "our codebase feels messy," which is a real problem, but it's an organizational one, not a networking one. You don't fix a messy room by moving the furniture into five different rooms.
What you're actually buying
When you split a monolith into services, you're not removing complexity. You're relocating it, and usually you're paying interest on the move. A function call that used to fail in one obvious way, with a stack trace pointing at the exact line, becomes a network call that can fail in a dozen new ways: timeout, partial response, the other service deployed a breaking change an hour ago, DNS took its sweet time, the load balancer routed you to a pod that's mid-restart. None of that existed when everything lived in one process.
I spent a good chunk of the last year elbow-deep in a billing system that has to reconcile tariff data, tax rules, and energy compensation numbers across a pile of services. The business logic itself isn't the hard part anymore. The hard part is knowing which service holds the source of truth for a given number at a given moment, and what happens when two of them disagree because one was still processing a batch job when the other one answered a request. That's not a bug you fix once. That's a category of bug you manage forever, and it only exists because the system is distributed in the first place.
Data is where the theory falls apart
The diagrams never show data. They show services and arrows, as if the hard part is drawing boxes around code. The hard part is deciding who owns which piece of data, and what "the truth" even means when three services each have their own copy of something that's supposed to represent the same fact.
Say you split "orders" from "inventory" from "billing." Congratulations, you now have to answer questions that a monolith with one database answers for free: what happens if the inventory update succeeds but the billing update fails? Do you roll both back? Can you even roll both back, or did the billing service already send an email? This is why sagas, outbox patterns, and eventual consistency exist — not because someone thought distributed transactions were an interesting academic problem, but because splitting services forces you to solve a problem you didn't have before, using tools that are all trade-offs and none of them clean.
If your team has never sat down and had the argument about which service owns which data before failure recovery, and not after, you are going to have that argument during an incident instead, at 2 a.m., with a customer on the phone.
The org chart problem, but backwards
There's a version of Conway's Law that gets quoted constantly: your architecture will mirror your communication structure. What gets quoted less is the part that runs backwards. If you build microservices before your teams are actually structured to own them independently, you don't get independent teams. You get a distributed monolith: five services that all have to deploy together anyway, because the boundaries were drawn along technical lines instead of ownership lines, and now every change ripples through three repos instead of one file.
I've watched this happen. The services exist, the repos are separate, the diagram looks great. But a single feature still needs four different pull requests reviewed by four different people who all sit in the same daily stand-up, because nobody actually owns a vertical slice of the business. All the network overhead of microservices, all the coordination overhead of a monolith, none of the benefits of either. That's the actual failure mode, and it's far more common than the ones people warn you about in intro-level blog posts.
When it's genuinely worth it
None of this means microservices are a bad idea. They're the right tool when you have distinct pieces of a system that scale differently, fail independently, and are owned by teams that can genuinely move at their own pace without waiting on each other. A payments team and a recommendations team probably don't need to deploy on the same schedule, and probably shouldn't share a failure domain — if the recommendation engine falls over, that should never be able to take checkout down with it.
The tell that you're in the right situation isn't "we have a lot of code." It's "we have parts of this system that need to fail, scale, and deploy on genuinely different rhythms, and keeping them in one process is actively getting in the way of that." If you can't point to that specific friction, you're probably not solving a scaling problem. You're solving a boredom problem, and there are cheaper ways to do that.
What I'd tell a team about to start this
Start with the boring, unglamorous work first: agree on who owns which data, decide how you'll observe a request as it hops across five services when something goes wrong at 3 a.m., and figure out your rollback story before you need it, not while you need it. Distributed tracing isn't optional tooling you add later, it's the thing that determines whether an incident takes twenty minutes or four hours.
And be honest about whether you're solving a technical problem or a people problem. A lot of "let's go microservices" conversations are actually "we don't like working in the same codebase as that other team" conversations wearing an architecture costume. If that's the real issue, splitting the code won't fix it. It'll just give the disagreement more surface area to happen on.
The diagram will still look clean either way. It's only once you're running the thing that you find out whether it was actually the right call.
Top comments (0)