Skip to content
Insights
Technical guide

Microservices: when the boundary earns its cost, and when it does not

Service count is not the goal — independent deployability is, and the two are frequently unrelated. A decision framework, the arithmetic a network boundary adds, and the five situations where the correct answer is one service.

Author
AppRiddle Engineering
Published
Length
16 min read

In short

  • Microservices deliver nothing on their own. The measurable outcome is deploying one service on a Tuesday afternoon without coordinating with another team — and most service-oriented estates cannot do that.
  • The common failure is the distributed monolith: many deployables that must ship together, usually because they share a database. It carries every cost of distribution and returns none of the benefit.
  • Cut along business capabilities, never along technical layers. A boundary drawn between API, logic, and data means every feature crosses all three services.
  • Each hop compounds: five services at 99.9% in series is 99.5% available, which is 3.6 hours a month. Availability targets have to be set per boundary, not per service.
  • Fewer teams than proposed services is the clearest signal to stop. So is a domain you cannot yet draw, a tightly coupled data path, and a hard transactional requirement across the split.

Microservices are the most reliably over-applied architecture of the last decade, and the reason is a category error: the pattern is adopted as the objective rather than as a means to one. Splitting a system into services is not itself valuable. What is valuable is a team being able to change and release its part of the system without waiting for anyone else — and it is entirely possible to have twenty services and none of that.

This guide is the decision we walk clients through: what the boundary buys, what it costs in arithmetic rather than opinion, and the situations where we recommend against it.

The framing we use throughout is deliberately narrow. A boundary in the right place compounds: it makes the next change cheaper, because a team can reason about less and ship without asking permission. A boundary in the wrong place accumulates: it adds a network call, a deployment dependency, and a failure mode to every release from then on. The pattern is identical in both cases. Only the placement differs, which is why placement is most of the work.

The outcome, and the test for it

DORA’s research is unambiguous that architecture predicts delivery performance, and equally unambiguous that the service count is not the mechanism. Their caution is worth quoting directly: “many so-called service-oriented architectures don’t permit testing and deploying services independently of each other, and thus won’t let teams achieve higher software delivery performance.”

So the test is behavioural, not structural. A team should be able to answer yes to all five:

  1. Make a large-scale change to its own service without permission from another team
  2. Complete that work without fine-grained coordination outside the team
  3. Deploy on demand, independently of the services it depends on
  4. Do most of its testing without an integrated test environment
  5. Deploy during business hours with negligible downtime

If the answer to any of these is no, adding services will not fix it, and will usually make it worse. The concrete version of the test we use in an assessment: can one team deploy one service at 2 p.m. on a Tuesday, alone? Everything else in this guide is in service of that question.

Three architectures, and the one nobody chooses

MonolithDistributed monolithMicroservicesdbsvc 1svc 2svc 3dbsvc 1dbsvc 2dbsvc 3db1 deploy · 1 schema3 deploys · 1 schema3 deploys · 3 schemas
The middle case is where most estates actually land. Three deployables, one schema — so every change is still a coordinated release, but now it fails over the network too.

The distributed monolith is not a strawman; it is the median outcome of a microservices programme. It arrives by a reasonable-sounding path: the code is split into services first, because that is the visible part, and the database is left shared because splitting data is hard and nobody wants to block the migration on it.

The result carries every cost of distribution — network failure, serialisation, partial writes, distributed debugging, N pipelines to maintain — and returns none of the benefit, because a schema change still requires every service to ship together. It is strictly worse than the monolith it replaced.

The diagnostic is a single question: if one service needs a schema change, how many services must be redeployed in lockstep? If the answer is more than one, you have a distributed monolith regardless of how the boxes are drawn on the architecture diagram.

Where the boundary goes

Assuming the split is justified, the boundary’s placement determines whether it helps. The most common error is to cut along technical layers, because that matches how engineering teams are often organised.

Split by layerSplit by capabilityAPI layerBusiness logicData accessone featurecrosses 3 deploy unitsOrdersapilogicdataBillingapilogicdataCatalogapilogicdatalands in 1 deploy unit
A layer boundary guarantees that every feature is a three-team, three-deploy negotiation. A capability boundary means most features are one team’s work, start to finish.

Cut along business capabilities — orders, billing, catalogue, identity — each owning its own data and exposing a contract. The heuristic that has served us best: a boundary is probably right if a typical feature request lands inside one service, and probably wrong if a typical feature request needs three pull requests in three repositories.

This is Conway’s law used deliberately rather than suffered. Service boundaries will come to mirror team boundaries whether you plan for it or not, so the honest sequence is to decide the team structure you want and derive the services from it — not the reverse.

What a network boundary costs

The costs of distribution are usually discussed qualitatively. They are mostly arithmetic, and the arithmetic is unforgiving.

service 199.9%service 299.9%service 399.9%service 499.9%service 599.9%0.9995 = 99.5% available on the path — about 3.6 hours a month
Availability multiplies along a synchronous path. Five nines-and-a-nine services in series produce a path that is materially worse than any of its parts.

Three consequences follow, and all three are routinely missed at design time:

  • Availability targets belong to paths, not services. A 99.9% target per service is meaningless if the checkout path traverses six of them. You either shorten the chain, make hops asynchronous, or accept a lower number and say so.
  • Latency accumulates and the tail dominates. Each hop adds serialisation, network transit, and connection handling. Worse, a p99 at each of five hops means a request is quite likely to hit at least one of them — tail latency compounds faster than the average.
  • Transactions stop being free. A single database transaction across two tables becomes a saga with compensating actions, an outbox for reliable publication, idempotent consumers, and a reconciliation job for when it still drifts. That is real, ongoing engineering work in place of one BEGIN.

None of this is an argument against microservices. It is an argument for counting the cost before deciding, and for putting boundaries where they are crossed rarely rather than on every request.

Complexity has to earn it

The rule we apply is one-directional: the burden of proof sits with the split, never with the status quo. Distribution is not the neutral default a diagram makes it look like — it is a permanent operating cost, and something specific has to be bought with it.

So a proposed boundary has to name three things before we will recommend it:

  1. The constraint it removes.Not “scalability” or “flexibility” — a specific thing that is blocked today, with the name of the team or the number that is blocked by it.
  2. The cost it accepts. The extra hop in the critical path, the transaction that becomes a saga, the pipeline and on-call rotation that now exist and did not before.
  3. What would reverse it. If the constraint disappears, is merging the services back an afternoon or a quarter?

A boundary that cannot answer all three is not ready. Writing the answers down costs an afternoon, and we keep them as decision records beside the code — the team inheriting this in two years needs the reasoning far more than it needs the diagram.

When the answer is yes

Four situations where we recommend the split, with the shape of the case in each:

SituationWhy a boundary helpsRepresentative case
Independent team throughputSeveral teams are contending for one release train and blocking each other. The boundary buys deployment autonomy, which is the actual outcome.A platform with eight product teams where every release requires a coordinated regression cycle
Divergent scaling profilesOne component needs ten times the capacity of the rest, or different hardware entirely. Scaling the whole process to serve one hot path is pure waste.Document OCR or model inference alongside a low-traffic administrative CRUD application
Different compliance or residency boundaryA subsystem handles cardholder or health data and benefits from a smaller audit surface, or must be deployed in a specific jurisdiction.Isolating a payment vault so PCI scope covers one small service instead of the estate
Genuinely different rate of changeA component changes weekly next to one that changes twice a year. Coupling them means the stable one absorbs the unstable one’s release risk.A pricing or promotions engine attached to a stable ledger

When the answer is no

This is the more useful half of the guide, and the half that occasionally costs us the engagement. Saying it in week one is far cheaper for a client than reaching the same conclusion in month nine, so we say it in week one. Any one of the following is sufficient reason to stop.

1. You have fewer teams than proposed services

The primary benefit of the boundary is team autonomy. One team with six services gets none of it and pays all of the cost — six pipelines, six on-call surfaces, six dependency upgrade cycles, and a distributed system to debug alone at 3 a.m. A single team should own a single deployable until it demonstrably cannot.

Team Topologies names the real limit, and it is not headcount: “Each new tool, responsibility or domain your team is given taxes their mental bandwidth,” and overloaded teams make worse decisions more slowly. Six services do not divide a team’s attention into six neat parts. They multiply the context it has to hold at once.

Uber’s experience is instructive at the other extreme. Having grown to roughly 2,200 microservices, they reorganised them into about 70 domains behind gateways — reducing the touchpoints needed to onboard a feature by 25–50%. They also measured the half-life of a microservice at around 1.5 years, meaning half the estate churns every eighteen months. Every service is a standing maintenance liability, not a one-off cost.

2. You cannot yet draw the domain

Boundaries are expensive to move. Moving a module boundary inside a monolith is a refactor an IDE can largely perform; moving a service boundary is a data migration, a contract change, two deployment sequences, and a rollback plan.

For a system whose domain model is still being discovered — a new product, a first entry into a market, anything pre-product-market-fit — the correct answer is a modular monolith with enforced internal boundaries. You get the design discipline while the cost of being wrong stays near zero.

3. The workload is a tightly coupled data path

Where components exchange large volumes of data at high frequency, a network boundary between them is not a decoupling; it is a bottleneck with extra billing attached.

Amazon’s Prime Video team published a clear account of this in 2023. Their audio/video quality monitoring service — one service, not Prime Video as a whole — was built as distributed components orchestrated by Step Functions, passing video frames through S3 as intermediate storage. They hit an orchestration scaling limit at roughly 5% of the expected load, with S3 request costs dominating. Consolidating the components into a single process, passing data in memory, reduced infrastructure cost by over 90% and raised the ceiling substantially.

The lesson is narrower than the headline it generated, and more useful: do not put a network boundary inside a hot data path. That is not a verdict on microservices. It is a verdict on where the boundary was drawn.

4. You need a transaction across the split

If two pieces of state must be consistent — a ledger entry and a balance, a seat reservation and a payment — and the domain cannot tolerate a window of inconsistency, then a boundary between them buys you a distributed transaction problem in exchange for nothing.

Sagas and eventual consistency are legitimate tools, and we implement them routinely. But they are a response to a requirement, not a default. When the business genuinely requires atomicity, the cheapest correct implementation is one database and one transaction.

5. The operational baseline is not there yet

Microservices convert design problems into operations problems. Without the following already working, a split makes the system less reliable, not more:

  • Automated deployment and rollback, per service, without manual steps
  • Distributed tracing that actually crosses every hop, including queues
  • Centralised structured logging correlated by trace ID
  • Service-level objectives, and alerting derived from them
  • A team genuinely on call for what it ships

If those are missing, they are the project. Building them inside a monolith is substantially easier, and they are prerequisites for the split rather than consequences of it. Our observability guide covers the instrumentation half of this in detail.

There is a second reason this weighs heavily for us. We hand every engagement over to the client’s team, and handover surface is proportional to service count — each boundary is another runbook, another deployment path, another thing someone must be able to operate at 3 a.m. without us on the call. An architecture that is genuinely easy to hand over is usually smaller than the one that looks impressive on a slide.

The path we recommend most often

For the majority of clients the answer is neither a monolith nor microservices, but a sequence:

  1. Modular monolith. One deployable, one database, but hard internal boundaries: modules with explicit public interfaces, a dependency rule enforced in CI, and a schema partitioned by module with no cross-module joins. The toolchain here is mature: ArchUnit or Spring Modulith on the JVM — which as of 2.0 verifies boundaries at application startup as well as at test time — and dependency-cruiser or an ESLint boundaries plugin in TypeScript. The rule fails the build, not the code review.
  2. Separate the data first. When a module is a candidate for extraction, split its tables and remove cross-module queries — while it is still one deployable and a mistake is a revert rather than an incident. This is the hard part, and doing it early is what makes the rest routine.
  3. Extract on evidence, one at a time. Pull a module out when it has a real reason: a team that needs its own cadence, a scaling profile that diverges, a compliance boundary. Each extraction gets a decision record naming the constraint it removed and what would justify merging it back — so the next team can tell a deliberate choice from an accident.
  4. Stop when the reasons stop. There is no target service count. Most estates should be smaller than they are.

The dependency rule in step one is what makes the whole sequence work, and it needs to be machine-enforced. A module boundary that relies on reviewer discipline is a boundary that will be gone within two quarters.

What would change our minds

  • Split earlier than the evidence warrants when a hard external constraint exists from day one — a regulator requiring data residency, or an acquisition that must remain separable.
  • Accept a shared database temporarily during a strangler migration, with a dated plan to remove it. Permanent is the problem; transitional with an end date is a legitimate trade.
  • Consolidate services when two are always deployed together and always changed together. That is one service wearing two costumes, and merging it is a cheap, unglamorous win.
  • Stay with the monolith indefinitely if the five deployment tests all pass. They can pass in a monolith, and when they do there is nothing left for a split to buy.

How we assess it

This is the substance of a two-week architecture review. It ends in a written assessment you own rather than a proposal, and the finding is frequently that the estate should get smaller. The sequence:

  1. Run the five deployment tests against the system as it exists today
  2. Count the deployables that must ship together, and ask why — the answer is almost always shared data
  3. Map the synchronous call graph and compute the availability of the two or three paths that matter commercially
  4. Compare team topology against proposed service boundaries and flag every service without a clear owner
  5. Check the operational baseline, and if it is missing, say plainly that it is the project
  6. Recommend the smallest change that makes the failing tests pass — which is frequently fewer services, not more

The uncomfortable finding, delivered often enough that it is worth stating up front: many estates would deploy faster and fail less with half the services they have. Consolidation is rarely proposed because it is nobody’s promotion, but it is regularly the correct recommendation.

Which returns to where this started. The objective was never a number of services. It is an architecture where each decision makes the next one cheaper — and that is served about as often by removing a boundary as by adding one.

Researched against primary sources and the practices we apply in production. Architecture and configuration are given in full. Vendor prices and version-specific details were accurate at the date above and are worth re-checking; nothing that would identify a client is included.

Want this reviewed against your own system?

A two-week architecture review ends in a written assessment you own — including the finding that you should change nothing.

Book an architecture review