Skip to the content
Software Made Clear Diagrams that show the mechanism About

SOA vs microservices

Compare an ESB-centred service design with microservices, change a message contract, and see why independent deployment still needs compatible boundaries.

6 min read Updated October 5, 2026 Last verified August 19, 2026
ANSWER

An ESB-centred design puts integration in a shared bus; microservices aim for independently deployable services. SOA also includes other designs, including microservices. Compare where coordination lives and how message contracts and data affect independence.

IN PLAIN TERMS

Two services disagree about the fields in an order message. One design translates it in a shared bus. Another makes the endpoints agree directly. Either way, someone owns the contract: changing a field can break an old caller even when each service has its own deployment pipeline.

In an illustrative change, an order service renames a message field, and an older consumer no longer finds the order identifier. Putting a translator in a shared bus and keeping the contract compatible at the endpoints are two ways to address that problem. The distinction here is between those designs. Lewis and Fowler explicitly describe SOA as a term with several meanings, including forms close to microservices; a shared bus is not its universal definition.

The argument is about the middle#

A common ESB-centred service design concentrates integration in shared middleware: a bus that routes messages, translates between formats and can coordinate a sequence of calls. That is the specific SOA implementation compared here. Central translation can help independently built systems interoperate, while introducing a shared component whose changes and availability affect its users.

The cost is that the bus becomes the thing every change has to go through. A new field on a message, a new routing rule, a new transformation — each one is a change to shared infrastructure, usually owned by a team that is not the team asking for the change, and usually queued behind whatever else that team already has scheduled. The bottleneck is not really technical; a large enough team could, in principle, keep the bus responsive. It is organisational: one shared thing sitting on the path between every pair of services concentrates the decision rights over that path in whoever owns the box.

Microservices respond to that specific cost, not to the presence of shared infrastructure in general. The newer style keeps the layer between services deliberately thin — pass a request from one service to another with as little shared logic acting on it along the way — and pushes the problems the bus used to absorb out to the services themselves. The phrase the microservices literature has settled on for its own instinct is “smart endpoints and dumb pipes”: the decisions live at the ends of a connection, not inside it. Removing the queue at the bus does not remove the problems that used to queue there. It hands each one to whichever service now owns it.

What came back#

Independent deployment still needs a compatible contract. In this illustrative field experiment, keep an old caller fixed while changing the server’s message. Adding a field, renaming a JSON key and reusing a protobuf field number have different consequences. The same compatibility question matters in both service designs; choosing a bus or separate endpoints does not answer it automatically.

Change a field's contract

The old caller expects an order identifier. Its JSON field is order_id; its protobuf field is string tag 1. The server now changes its schema. Predict what the old caller reads.

JSON rename: the old order_id lookup is missing.

Protobuf rename with tag 1 retained: the old caller still reads ord-42.

A name-only rename can preserve protobuf binary compatibility while breaking a JSON key lookup. Enable JavaScript to compare field changes.

A semantic model of one string field and an old caller, not an actual encoder or network call. It excludes protobuf JSON mapping, unknown-field policy, schema validators, generated-code compile checks and business-version migrations.

This is a model of one old caller, not an encoder or a complete migration. The protobuf schema guide distinguishes safe binary changes and warns against reusing field numbers. The gRPC and REST comparison explains the wire-format and API distinctions. A registry can check a schema rule; it cannot establish that a changed field still means the same thing to the business.

Push the logic out to the edges and, past a certain number of services, several of the problems the bus existed to solve reappear anyway — just not inside one shared box. Routing a request to the right instance, and retrying it when a call fails, start showing up again, now handled by something that sits beside each service rather than between all of them: a sidecar process running next to the service, or a gateway at the edge of the system. When there is no shared bus applying one retry policy on everyone’s behalf, deciding what a retry actually means becomes each service’s own responsibility — this article works through what handling the same request twice correctly requires, once nothing upstream is deciding that for it. Keeping message formats compatible as they change starts showing up again too, usually as a schema registry that producers and consumers check before they trust a payload — a narrower job than the transformation a bus used to do, but aimed at the same failure: two systems disagreeing about what a message means. And the visibility a bus gave for free, because every message already passed through it, has to be rebuilt on purpose, usually as centralised logging or tracing that every service is made to write into.

None of these three is the bus reappearing under a different name, and treating them as a tidy one-for-one replacement overstates what actually happened. A schema registry does not orchestrate a business process, and a sidecar does not enforce a business rule the way a bus’s rule engine could. What is accurate to say is narrower and more interesting than a clean correspondence: the functions the bus used to perform did not disappear, because the problems behind them — mismatched formats, calls that need retrying, a system nobody can see all of at once — do not disappear just because the bus did. What changed is whether the thing solving them sits on the request path, owned by one team every other team depends on, or sits beside it, adopted piece by piece and owned however each team chooses to own its own copy.

Splitting the services, keeping the database#

Consider another illustrative design: each service has its own repository and deployment pipeline, but several services read and write the same order tables. An incompatible table change can require coordination with the consumers of those tables. Shared database capacity can also expose other services to contention from an expensive query. Integration tests may need the shared schema, even though isolated logic can still be unit-tested without it. Separate deployments alone do not remove these dependencies.

The tell is in the calendar, not the architecture diagram: a deploy that is supposed to be independent still needs a coordination meeting before it can ship, because the boundary that actually matters — the data — was never drawn. Calling the result microservices describes the deployment units, not the coupling, and the coupling is what decides whether teams can really move independently. Where that data boundary gets drawn, service by service, is the deeper subject of the architecture path — this comparison has no place on that roadmap itself, only a question in common with it.

Splitting a shared database is genuinely hard, though, and it is not always worth doing. A system built around one shared schema is often not ready to be split at the service boundary at all, and the more useful conclusion is frequently to leave it alone rather than draw service lines around a database that still has to be touched as one thing regardless of how many deploy pipelines sit above it.

Sharing a database server is different from sharing writable tables. Separate schemas and permissions can reduce schema coupling while capacity and availability remain shared. The boundary question is also separate from hosting: serverless architecture delegates infrastructure operation but leaves data contracts and safe repeated processing with the application.

Situation Take Because
Many mismatched systems must interoperate Shared bus Translation in one place earns its keep
Teams need to deploy without coordinating Thin middle A shared bus queues every change
Policy must be enforced identically Shared bus One place to enforce it
Services would jointly change the same tables Define ownership first Incompatible changes can still require coordinated releases
Situation
Many mismatched systems must interoperate
Take
Shared bus
Because
Translation in one place earns its keep
Situation
Teams need to deploy without coordinating
Take
Thin middle
Because
A shared bus queues every change
Situation
Policy must be enforced identically
Take
Shared bus
Because
One place to enforce it
Situation
Services would jointly change the same tables
Take
Define ownership first
Because
Incompatible changes can still require coordinated releases

IF YOU REMEMBER ONE THING

In this comparison, a shared bus concentrates integration work while microservices aim for independently deployable endpoints. SOA also includes other designs. In either approach, message compatibility and data ownership can require coordination despite separate deployment pipelines.

Questions people also ask

5 QUESTIONS
Are microservices just SOA done properly?

Microservices can be viewed as a form of SOA, but SOA has several meanings. The comparison here is with a common implementation that concentrates integration in an enterprise service bus. Independently deployable endpoints offer different trade-offs; their name alone does not prove a better boundary or a better implementation.

What is an enterprise service bus?

Shared middleware that a system's services all route through, conventionally providing message routing, format transformation between mismatched systems, and orchestration of a multi-step process across several of them. Putting a rule or a monitor on the bus applies it everywhere at once — and makes every change to it a change every dependent team has to wait on.

Can microservices share a database?

Yes, but distinguish shared database infrastructure from jointly owned tables. Two services changing the same tables can need coordinated schema releases. Separate schemas or access boundaries on one database can preserve more independence, while resource contention and that database's availability remain shared concerns.

Is a service mesh the new bus?

Partly, and only for some of what a bus did. A mesh's sidecars can standardise routing and retries across services, which is real overlap. They do not transform message formats or orchestrate a business process the way a bus did, so the resemblance covers a narrower slice of the job than the name suggests.

How small should a service be?

Small enough that one team can own it, deploy it, and change its schema without asking another team first. That is a statement about ownership and boundaries, not a target measured in code — a service sized by any other rule can still end up coupled to its neighbours through a database or a bus it never accounted for.

The library Browse all articles