Microservices don't work?
“Microservices don’t work.”
That’s the kind of sentence that makes an architect close their eyes for a second, take a deep breath, and hope a qualifying clause follows.
Because the sentence might even be true.
Just probably not in the way it’s meant.
The more interesting question isn’t whether microservices work. The more interesting question is:
What exactly didn’t work?
Were they really microservices? Or was it a distributed monolith? Were the boundaries cut along domain lines? Or just along technical artifacts? Could teams ship independently? Or did releases still need coordination across five services, three teams, and a change board?
And especially important from a frontend perspective:
Was the UI part of the domain boundary — or just the place where every bad boundary got glued back together?

Take the statement seriously
Section titled “Take the statement seriously”When someone says, after 15 years, “microservices don’t work,” you shouldn’t wave that off as arrogance.
There’s probably real experience behind it. There was probably pain. There were probably expensive projects, painful releases, unstable environments, unclear ownership, hard-to-test workflows, debugging across seven systems, and meetings that only existed because the architecture demanded them.
That’s real.
But it doesn’t automatically prove that microservices don’t work.
For now, it only proves:
This particular form of distributed architecture didn’t work.
And that’s where it gets interesting.
Because a lot of systems sold as microservices aren’t, from a business perspective, microservice architectures at all. Technically they’re distributed applications built with a centralized way of thinking.
Or shorter:
Splitting a monolith across HTTP doesn’t give it clean domain boundaries.
What exactly didn’t work?
Section titled “What exactly didn’t work?”If microservices supposedly don’t work, you have to dig.
Not to prove a point. Otherwise, the diagnosis remains useless.
Did independent deployability not work? Were there really independent deployments in the first place?
Did team autonomy not work? Was there real team ownership at all?
Did the services fail to be organized around domain boundaries? Or were only tables, technical modules, or CRUD areas split up?
Did clear data ownership not work? Or did several services reach into the same data, the same models, the same source of truth?
Did observability not work? Or was tracing an error across service boundaries about as reliable as tracking a package with no tracking number?
Did the UI fail to get simpler? Or did the frontend have to guess what a user is even allowed to see, based on five APIs, three status codes, and two historical edge cases?
These are different problems.
And they have different causes.
Lumping all of that under “microservices don’t work” is taking the easy way out. It’s about as precise as saying “cars don’t work” after driving on ice with summer tires.
Might be true. Not very useful, though.
Not every distributed application is a microservice architecture
Section titled “Not every distributed application is a microservice architecture”A microservice architecture isn’t simply:
- lots of repositories
- lots of deployments
- lots of REST APIs
- lots of Docker containers
- lots of teams with lots of Jira boards
That’s just distribution.
Architecture doesn’t come from quantity.
It comes from boundaries, responsibility, and how cheap change stays.
A service only helps when it encapsulates a genuine business capability, owns its data clearly, is observable in a meaningful way, can be tested on its own, and is owned by a team in a way that keeps changes from routinely traveling through half the company.
If a change in service A almost always means a change in services B, C, and D, nothing is really independent.
If releases have to be coordinated, the deployment boundary is just decoration.
If services share a domain model, they usually share their pain too.
If a UI flow needs five synchronous service calls just to enable a button correctly, the domain boundary probably isn’t where the architecture diagram thinks it is.
A distributed monolith is a monolith that learned to produce network errors.
That sounds harsh. It’s often surprisingly accurate.
The UI doesn’t just sit on top
Section titled “The UI doesn’t just sit on top”Maybe the most important fallacy hides in an old architecture picture:
The database at the bottom. Services above it. APIs above that. The UI on top.
As if the UI were a thin coat of paint. A little surface. A little polish. A little form, a little table, a little button.
That was already questionable back then.
Today it’s simply wrong.
Modern frontends are interactive, asynchronous, state-driven applications. They contain user flows, permissions, validation, error states, intermediate steps, optimistic updates, ViewModels, business decisions, and a lot of meaning.
The UI doesn’t sit on top.
It’s part of the domain boundary.
Users don’t experience layers.
They experience a flow.
They want to log a request, check an order, close out a task, understand an invoice, make a decision, or fix an error.
No user thinks:
“Ah, good, I’m currently in the API layer between service B and the aggregation of status C.”
Except maybe architects. But they should only be allowed near real users under supervision anyway.

The SOA legacy: technical integration instead of product flow
Section titled “The SOA legacy: technical integration instead of product flow”SOA wasn’t stupid.
That much has to be said fairly.
SOA had the right ideas:
- services
- contracts
- loose coupling
- integration
- reuse
- clear interfaces
The problem wasn’t structuring systems around services.
The problem was that technical integration often got mistaken for business architecture.
A lot of SOA landscapes thought heavily in terms of central models, integration flows, ESBs, orchestration, and governance. There were reasons for that. Large enterprise landscapes aren’t simple. Legacy systems don’t disappear just because an architect draws a new diagram. Someone has to integrate, translate, stabilize, and secure things.
But from a product and frontend perspective, a core conflict emerges quickly.
SOA often thought:
- models get built centrally, in technical terms.
- services deliver data.
- the UI sits on top.
- the context of actual use comes later.
Except users don’t work with canonical data models.
They work with intentions, states, decisions, errors, permissions, intermediate steps, and meaning.
A canonical model can be technically consistent and still be a bad fit for a concrete UI situation.
A canonical model is not yet a product model.
Technical models explain how systems exchange data.
Product models explain what that data means to users.
That’s a massive difference.
Ignore that difference, and the UI eventually becomes a translation office for an architecture that never really modeled the user flow in the first place.

When architecture knowledge sits with a handful of people
Section titled “When architecture knowledge sits with a handful of people”A lot of SOA landscapes had another problem: the core knowledge lived with very few people.
With ESB specialists. With integration architects. With people who genuinely understood the canonical model. With the ones who knew which service was authoritative in which case. With people who knew the historical transformations. With whoever knew which exception applied for which customer.
As long as those people were around, the system worked.
Not beautifully, maybe. Not fast. But it worked.
Once they left, architecture turned into archaeology.
And the digging began:
Why does this field get renamed here?
Why isn’t status ACTIVE allowed to mean active in this flow?
Why does the process call service A, then B, then A again?
Why does this transformation exist twice?
Why does the truth live in system X, but only for tenant Y?
Why does nobody know this except Ralf?
That’s not a personal failure on Ralf’s part.
Ralf probably held the place together for years.
The problem is an architecture and an organization that never translated critical knowledge into clear boundaries, terms, tests, ownership, and traceable flows.
Knowledge monopolies are rarely intentional.
They form when complexity gets centralized.
And centralized complexity feels efficient for a while. Until it doesn’t. Then it’s a risk with an office.
Microservices don’t automatically fix SOA problems
Section titled “Microservices don’t automatically fix SOA problems”Microservices can help solve some of these problems.
But they don’t do it automatically.
If you replace SOA with smaller services while keeping the same technical modeling mindset, you build the next distributed integration hub — just packaged more fashionably.
Then the ESB might be called an API gateway instead.
The central integration knowledge might now sit with the platform team.
The canonical model gets spread across several DTO packages.
The central orchestration moves into chains of synchronous service calls.
And the frontend still ends up with technical data it has to reconstruct domain meaning from.
Congratulations.
The elephant is now cloud-native.
The problem remains.
Microservices don’t work when they only cut backend artifacts.
They only work when they carve out a vertical business-capability boundary — including product, UX, frontend, backend, tests, and operations.
Or more directly:
A microservice whose business logic only comes together in the frontend isn’t organized around a clean domain boundary. It’s just a small backend building block inside a bigger architecture problem.
The distributed monolith
Section titled “The distributed monolith”Not every distributed application is a microservice architecture.
There are warning signs.
A change in service A needs a change in services B, C, and D.
Releases have to be coordinated.
Services share a database.
Services share a common domain model.
A UI flow needs synchronous call chains across many services.
Teams can’t decide independently.
API contracts follow technical tables instead of domain terms.
Domain meaning only emerges in the frontend through mapping.
There’s no clear data ownership.
Observability is missing.
Errors are barely traceable across service boundaries.
That’s not an autonomous service landscape.
That’s a distributed dependency graph with a deployment pipeline attached.
The difference isn’t academic. It decides whether architecture enables change or blocks it.
If everything has to change together, nothing is really independent.
And if nothing is independent, it’s worth honestly asking whether the distribution is still helping — or just creating new categories of failure.
Network errors. Versioning problems. Timeouts. Retries. Consistency questions. Monitoring across system boundaries. Local setup from hell.
All things you can choose to accept.
Just not for an architecture that stays coupled from a business perspective anyway.
Why bad service boundaries show up in the frontend
Section titled “Why bad service boundaries show up in the frontend”The frontend is often where bad backend boundaries get exposed.
Not because the frontend is at fault.
Because that’s where user flows become concrete.
An architecture diagram can look elegant. A sequence diagram can look plausible. A service cut can look clean at the backend level.
And then a screen shows up.
Suddenly the UI has to piece together data from five services.
It knows technical IDs, internal status codes, and historical flags.
It assembles domain meaning out of mapping logic.
Error states are API-centered instead of user-centered.
Buttons depend on multiple backend states nobody can name in domain terms.
ViewModels turn into repair layers for the wrong boundaries.
UX terms don’t match API terms.
Tests check technical responses, not domain user flows.
Product decisions get stuck on service-ownership discussions.
And at some point the frontend stops being the place where usage gets modeled.
It becomes the integration layer.
Just without the status, the mandate, or the tools of an integration layer.
If your frontend constantly has to reconstruct domain context, your backend boundaries probably aren’t as domain-driven as they look on the architecture diagram.
That’s one of the best architecture tests there is.
Not: “How many services do we have?”
But: “How much business logic does the UI have to repair because it was never modeled cleanly anywhere?”

Domain ownership doesn’t end at the controller
Section titled “Domain ownership doesn’t end at the controller”Another fallacy: a capability belongs to the backend.
No.
A capability doesn’t belong to the backend.
It belongs to the product flow.
Domain ownership doesn’t end at the controller.
Genuine ownership of a business capability includes:
- the product problem
- user intent
- UX
- frontend
- backend
- tests
- operations
- monitoring
- domain language
- data ownership
- API contracts
- error behavior
That doesn’t mean every person has to be able to do everything.
Specialization is obviously still needed. Frontend, backend, UX, product, testing, and operations bring different perspectives, tools, and depth.
But the team has to be able to own the capability as a whole.
If a team only owns backend endpoints but doesn’t understand the user flow, the cut is incomplete.
If a frontend team only consumes what others deliver, with no shared ownership of API language, ViewModels, and error behavior, the cut is incomplete.
If UX introduces terms that never show up in APIs, tests, or monitoring, the cut is incomplete.
If tests only check technical responses instead of user intent, the cut is incomplete.
Frontend, backend, UX, product, and tests aren’t separate worlds.
They’re different perspectives on the same underlying business capability.

Language is architecture
Section titled “Language is architecture”A shared domain language isn’t a backend concern.
It forms everywhere.
In UX copy. In labels. In error messages. In ViewModels. In API contracts. In events. In tests. In backend use cases. In monitoring dashboards. In support processes.
If backend, frontend, UX, product, and testing use different terms for the same thing, that’s not a cosmetic problem.
It’s an architecture problem.
An example:
Backend says Case.
Frontend says Ticket.
UX says “request.”
Testing says “transaction.”
The business side says “matter.”
The API returns ProcessItem.
Everyone believes they’re talking about the same thing.
Until someone builds a change.
Then it turns out: they’re talking about almost the same thing. Not quite. And that “not quite” is exactly where bugs, misunderstandings, edge cases, and endless coordination live.
Language is architecture.
Ambiguous terms produce ambiguous systems.
That doesn’t mean a company needs one global enterprise model for everything. That would just be the old trap again.
Good architecture doesn’t need one universal language for everything.
It needs an unambiguous language within one domain context.
A term is allowed to mean something different in a different context. That’s normal. Sometimes even healthy.
But within one domain boundary, it has to be clear what’s meant.
Not roughly. Not “however it grew historically.” Not “we all know what that means, right?”
Clear enough that UX, frontend, backend, tests, operations, and support can describe the same domain reality.

Modern architecture cuts vertically
Section titled “Modern architecture cuts vertically”The horizontal architecture model is convenient.
Database. Backend. API. Frontend.
It’s easy to draw. It looks orderly. It fits nicely on slides. And it reliably produces handoffs.
The problem: user flows don’t run horizontally.
They run vertically through the system.
A real product flow includes:
- the product goal
- user intent
- the UX flow
- UI state
- the frontend ViewModel
- the API contract
- the backend use case
- the data model
- tests
- operations
- monitoring
When these things are spread across different owners with no shared language and no clear ownership, friction follows.
Then every product change turns into an integration task.
Rename a field? Needs coordination.
Show a status differently? Needs coordination.
Rewrite an error message to be user-centered? Needs coordination.
A button should only be active in certain cases? Nobody knows who owns that domain state.
That’s not a frontend problem.
That’s a boundary problem.
So modern architecture doesn’t start by asking:
“How do we cut services?”
It asks:
“Who owns which domain user flow — from intent to operations?”
Only after that does it become clear whether the result is a microservice, a module, a self-contained system, a bounded context, an event flow, or something else.
Not cutting services.
Cutting responsibility.
When microservices can work
Section titled “When microservices can work”Microservices can work.
But not because they’re small.
They work when they carry a real domain boundary.
That takes at least a few uncomfortable prerequisites:
The domain context is clear.
The language inside that context is unambiguous.
The team owns responsibility, not just endpoints.
Data ownership is settled.
API contracts follow domain terms, not table structures.
The UI doesn’t have to reconstruct meaning out of technical leftovers.
Tests check relevant user flows and business rules, not just responses.
Deployment is genuinely independent.
Operations and observability are taken seriously.
Error behavior is understood in user terms, not just technical ones.
And above all:
The team can make decisions without starting a procession through other teams for every change.
Microservices aren’t an architectural magic wand.
They’re an amplifier.
They amplify good boundaries.
And they amplify bad boundaries.
With good boundaries, they bring autonomy, the ability to scale teams, clear ownership, and independent development.
With bad boundaries, they bring latency, coordination overhead, monitoring pain, local setup problems, and a lot of meetings with the word “interface” in them.
When a modular monolith is the better choice
Section titled “When a modular monolith is the better choice”The alternative to bad microservices isn’t automatically SOA.
And it’s also not automatically one big monolith where everything clumps back together comfortably, like cheese fondue with database access.
The better alternative is a deliberate architecture decision.
Sometimes a modular monolith makes more sense.
When the team is small. When domain boundaries are still fuzzy. When deployment complexity doesn’t bring any real benefit. When operational maturity isn’t there yet. When observability isn’t sufficient yet. When data ownership isn’t understood yet. When fast domain-learning cycles matter more than technical distribution.
A modular monolith can be very clean.
It can have clear domain modules, stable internal boundaries, good tests, clear language, sensible ViewModels, and an architecture that allows extraction later.
That’s often better than microservices that only got distributed because the architecture diagram wouldn’t have looked modern enough otherwise.
Self-contained systems can make just as much sense.
Or an event-driven architecture.
Or API-first.
Or an integration layer for legacy connectivity.
Or real microservices.
Or a domain-modular architecture within a single deployment.
The point isn’t to defend a favorite pattern.
The point is to understand the forces at play.
Domain boundary. Team structure. Rate of change. Operational maturity. Data ownership. Deployment requirements. Testability. Product flow.
Architecture isn’t a matter of faith.
Even though some discussions get surprisingly close to religious wars.
What’s wrong isn’t microservices
Section titled “What’s wrong isn’t microservices”Microservices aren’t fundamentally wrong.
SOA wasn’t fundamentally wrong.
Monoliths aren’t fundamentally wrong.
What’s wrong is mistaking technical distribution for domain-driven architecture.
What’s wrong is cutting backend services and assuming the product flow will somehow sort itself out on top.
What’s wrong is treating the UI as a surface, even though it models domain meaning, state, and user decisions.
What’s wrong is treating central technical models as product models.
What’s wrong is aligning ownership with technical layers instead of business capabilities.
What’s wrong is not taking terminology seriously.
Because sooner or later, every unclear boundary becomes visible.
Often in the frontend. Often in testing. Often in operations. Almost always too late.

The takeaway
Section titled “The takeaway”The UI doesn’t sit on top. It’s part of the domain boundary.
Microservices aren’t a backend concept.
If the cut doesn’t hold up across product, UX, frontend, backend, tests, and operations, it isn’t organized around the domain.
It’s just distributed.