DevelopmentSenior

Microservices

Microservices are an architecture in which one system is built as a set of small services, each deployed on its own, owning its own data and owned by one team. Independent deployment is what the style buys, and every call that now crosses a network is what it costs. The page opens with sixteen problems the network adds and the pattern that answers each, then covers the nine characteristics the term was defined by, what you need before the second service, data across services, calls between services, running many of them, five documented failures, and where it degrades.

Defining property
Deployed alone, owns its data
At scale
Uber, 2020: ~2,200 services, regrouped into 70 domains
Failure mode
Distributed monolith; retry storms

What the network adds — sixteen problems, the pattern that answers each, and what it costs

Inside one process, a call is a function call. It takes nanoseconds, and it either returns or throws. Data lives in one database, so one transaction keeps two tables consistent. Splitting a system into services turns every one of those calls into a network call, and every shared table into somebody else’s data. Each change creates a problem the monolith never had. Every one of those problems has a known pattern, and every pattern has a price. Here are sixteen of them, with the point at which you need each. Later sections cite the rows by number.

#The problemThe patternWhat it costsNeeded from
1A call hangs, and the caller’s threads pile up waiting for itA timeout on every call, set from the callee’s latency; a deadline passed downstreamA slow but healthy call gets cut off, so every timeout needs a planned answerThe first service
2A call fails once — a restarted instance, a dropped packetRetry with exponential backoff and jitter, at one layer onlyExtra load exactly when the callee struggles; three tries at each of five layers is 243 callsThe first service
3A dependency is down, and every caller keeps hammering itA retry budget, or a circuit breaker that stops calling for a whileA breaker is a second mode of behaviour to test, and it can slow recoveryMany callers share one dependency
4One slow dependency uses up every thread the caller hasBulkhead: a separate pool or concurrency limit per dependencyCapacity sits idle in pools that are not busyA service calls several others
5A retry repeats an action that has already happenedAn idempotency key: the receiver remembers what it has already doneState the receiver must store and expireThe first call that changes state
6More requests arrive than the service can serveLoad shedding and rate limits: reject early, before doing the workSome users get a fast error now instead of everyone getting a slow one laterThe first traffic peak
7Two services want the same tableDatabase per service: one owner, and everyone else asks through its APINo joins across services; reports need their own copyThe first service
8One business action spans several services, and there is no transaction across themSaga: a chain of local transactions, each with a compensating stepYou write the undo yourself, and concurrent sagas can see each other’s half-done stateThe first flow that writes to two services
9Saving a row and publishing an event must both happen, or neitherTransactional outbox: the event is written in the same transaction, and a relay publishes itA relay to run; events arrive at least once, sometimes twiceThe first event another service relies on
10The same event arrives twice, or out of orderIdempotent consumer: record processed message ids; check versionsA table of processed ids for every consumerThe first consumer
11One screen needs data owned by five servicesAPI composition, or a read model built from events (CQRS)Composition multiplies calls; a read model is always slightly behindThe first page that spans services
12Instances come and go, so addresses keep changingService discovery: a registry, or DNS provided by the platformThe registry becomes something every call depends onThe second service
13A client would have to call twenty servicesAn API gateway, or one backend for each frontend (BFF)One more hop, and a gateway that tends to grow business logicThe first client that needs two services
14Every service needs encryption, retries and metrics, in every languageA shared library, or a service mesh: a proxy beside each service or each nodeA library ties services to its languages; a mesh is one more distributed system to runDozens of services
15A request failed somewhere across eight servicesDistributed tracing: a trace id passed on every call, and logs that carry itInstrumentation in every service, and storage for the tracesThe second service
16A change to one API breaks a caller nobody knew aboutBackward-compatible changes, versioned APIs, consumer-driven contract testsOld versions to keep alive, and a contract suite per consumerThe second team
Each service is deployed independently and owns its own store, so no service reads another’s tables. The red path is a synchronous call chain: four components at 99.9% availability each multiply out to about 99.6% — nearly three hours of downtime a month instead of 43 minutes — which is worse than any single component in it. The dashed amber edge is the alternative: search reacts to an event, so it can be down without stopping checkout.

What makes a service a microservice — nine characteristics, and the one that defines it

Ignore the “micro” for a moment. It has caused more damage than any other syllable in this field, because it invites people to measure services in lines of code. The property that matters is a yes-or-no question. Can this service be changed, tested and deployed to production without deploying anything else? If yes, it is a microservice. If a release needs two services to go out together, you have one service in two repositories. Sam Newman puts it in one line: microservices are independently deployable services modelled around a business domain.

The term was defined on 25 March 2014, when James Lewis and Martin Fowler described “an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms”. They did not give a formal definition. Instead they listed nine characteristics that the systems they had seen tended to share. Most explanations skip the list, and that is a pity, because each item names a way the style is broken in practice. Here are all nine, in their order:

#CharacteristicWhat it meansWhat it looks like when it is missing
1Componentization via servicesA component is something deployed on its own and called over the network, not a library linked into the processServices that must be released together
2Organized around business capabilitiesServices and teams follow what the business does — orders, pricing, deliveryA “database service”, a “validation service”, an “API service” — layers with network calls between them
3Products not projectsOne team owns a service for its whole life, in production too — Amazon’s “you build it, you run it”A project team builds it, hands it to operations and moves on
4Smart endpoints and dumb pipesThe logic lives in the services; the transport — HTTP, a simple broker — only carries messagesAn enterprise service bus, or a gateway that routes on business rules
5Decentralized governanceEach team picks the tools that fit its service, within a few shared standardsEither one mandated stack for everything, or forty languages nobody can staff
6Decentralized data managementEach service owns its data and its model; “customer” may mean different things to two servicesA shared database — the defining feature of a distributed monolith
7Infrastructure automationTesting, deployment and provisioning are automatedA manual deploy step, multiplied by the number of services
8Design for failureAny call can fail, so every service must tolerate it — and the failure is tested, not assumedCalls without timeouts; nobody has ever switched a dependency off on purpose
9Evolutionary designServices can be rewritten or thrown away; boundaries follow what changes togetherServices nobody dares to delete

The data rule is older than the word. In 2006 Amazon’s CTO Werner Vogels described the service-oriented architecture the company had moved to after 2001, when its monolithic front end could no longer scale. Each service encapsulated its data with the business logic that operates on it. “No direct database access is allowed from outside the service,” he said, “and there’s no data sharing among the services.” In the same interview he gave the ownership rule its slogan: “You build it, you run it.” Lewis and Fowler cited the ownership slogan eight years later, and their sixth characteristic restates the data rule. Little about the rules has changed since; what has changed is how much tooling exists to follow them.

What you need before the second service

On 28 August 2014 Martin Fowler published a short list of what a team needs before it builds microservices, and announced it as “You must be this tall to use microservices” — the sign at a fairground ride. The list has four items. None of them is exotic, and a single application can live without any of them for years. Twenty services cannot live without them for a week. Here they are, with a test you can run on your own team today:

PrerequisiteWhat it meansWhy twenty services cannot do without itA test for today
Rapid provisioningA new server or environment in hours, by automation, not after a ticket queueIf a new service is expensive to stand up, teams bolt features onto existing ones and the boundaries driftHow long from an empty repository to a service running in production?
Basic monitoringTechnical failures and business signals seen quickly in productionWith many services, a failure shows up far from its causeCan you see right now how many orders failed in the last five minutes?
Rapid application deploymentAn automated pipeline to test and production environmentsA manual step happens twenty times as often, and it is what stops teams releasing aloneCan a team deploy one service without asking anyone?
DevOps cultureDevelopers and operations work as one, and teams run what they buildA service run by a separate operations team is not independently deployable in practice, whatever the diagram saysWho gets paged when the service fails at 3 a.m.?

Nine months later Fowler named the cost behind the list: the microservice premium. Automated deployment, monitoring, failure handling and are work that a monolith does not need, and the work starts before any benefit arrives. So a simple system is less productive as microservices, and only past a certain level of complexity does the style pay for itself. His conclusion was blunt: “don’t even consider microservices unless you have a system that’s too complex to manage as a monolith.” The Monolith page shows what to try before you reach that point, and the Monolith → Microservices page shows how to move once you are past it.

Data across services: no joins, no shared transactions

The rule is simple: each service owns its store, and no other service touches it (row 7). The consequences are where most of the real work lives. They surprise people who arrived expecting the hard part to be deployment. A monolith gave you several guarantees for free, and each of them now has to be rebuilt — or consciously given up. The table lists what you lose and what usually replaces it.

What one database gave youWhat replaces it across servicesThe catch
A joinAPI composition, or a read model fed by events (row 11)Composition is chatty; the read model lags
A transaction across two tablesA saga with compensating steps (row 8)You write the undo; no isolation between sagas
A foreign keyAn id plus a check in code, and a job that finds orphansThe other service can delete what you point at
A unique constraintOne service owns the rule and is asked before anyone writesThat service is now on the critical path
Reading your own writeSend the user’s next read to the owner, or show the pending stateA read model shows the old value for a moment
One query for a reportA copy in an analytics store, fed by events or the database logMinutes or hours of lag, and one more pipeline

Sagas: a transaction you write yourself

Distributed transactions exist. Two-phase commit has a coordinator ask every participant to prepare and then to commit, and databases have supported it for decades. Between services it is rarely used, for three reasons. Every participant, including the message broker, must support it, and many do not. Participants hold their locks while they wait. And if the coordinator fails at the wrong moment, they stay blocked until it comes back. So the usual answer is older and simpler. Hector Garcia-Molina and Kenneth Salem described it in 1987 for long-running transactions inside one database, and called it a saga.

A saga is a sequence of local transactions. Each one commits in its own service, then triggers the next. Every step that can be undone has a compensating step that undoes it — not a rollback, but a new action with the opposite business meaning. An order saga creates the order as pending, reserves stock, charges the card, then confirms. If the charge is declined, the saga runs backwards: it releases the stock and marks the order rejected. Chris Richardson’s “Microservices Patterns” gives the order of steps a useful vocabulary. Compensatable steps come first. Then comes the pivot — the step after which the saga only moves forward. Every step after the pivot must be retried until it succeeds.

A saga replaces one transaction with a chain of local ones, each committed in its own service. Steps that can be undone come first, then the pivot — here the card charge — after which the saga only moves forward, and the steps after it are retried until they succeed. When the charge is declined, nothing rolls back by itself: the saga runs compensating steps in reverse order, releasing the stock and rejecting the order. Each compensation is a new action someone has to write, and between steps other requests can see the half-done state, such as reserved stock for an order that is about to be rejected.

There are two ways to coordinate the steps. In orchestration, one component — a service or a workflow engine — tells each participant what to do next and knows the whole flow. In choreography, nobody knows the whole flow: each service listens for events and reacts. The choice decides where the saga’s state can be seen and where its bugs will hide.

OrchestrationChoreography
Who knows the whole flowThe orchestratorNobody; each service knows its own reaction
Where to see one saga’s stateIn one placeReconstructed from events across services
Adding a stepChange the orchestratorAdd a subscriber, and check nothing assumed the old order
How it goes wrongThe orchestrator absorbs business logic that belongs to the participantsEvent chains nobody can follow, sometimes in a loop
Good forLong flows with several compensations, such as order fulfilmentShort flows with two or three participants

The outbox: saving and publishing as one step

Every saga step ends with two writes: the step’s own data, and a message telling the next service to go. That pair is where data quietly goes missing (row 9). Save the order, then publish “order created” — and if the process dies between the two, the order exists and nobody else ever hears of it. Publish first, then save — and a crash leaves an event about an order that does not exist. Two writes to two systems are not a transaction, however close together they sit in the code.

Saving a row and then publishing an event are two writes to two systems, and a crash between them leaves the order saved and nobody told. The transactional outbox writes the event into a table in the same database transaction as the order, so both rows commit or neither does. A separate relay then publishes the outbox rows, either by polling the table or by reading the database’s change log. Because the relay can crash after sending and before recording that it sent, an event may arrive twice, which is why every consumer keeps the ids it has processed and ignores repeats.

The transactional outbox removes the gap. The service writes the event into an outbox table in its own database, in the same transaction as the order itself. Either both rows commit or neither does. A separate relay then reads the outbox and publishes each event. It can poll the table, or it can read the database’s own change log — , the approach tools such as Debezium take. The relay may crash after publishing and before marking the event sent, so it will sometimes publish twice. That is why row 10 exists: every consumer records the ids it has processed and ignores a repeat.

All of this inherits eventual consistency as a product question, not a technical one. If stock is reserved a second before the payment clears, someone has to decide what the customer sees during that second, and what happens if the payment then fails. That decision belongs to the business. It has to be made explicitly for every flow that crosses services, and pretending it away is how systems end up charging people twice.

Calls between services: timeouts, retries and the availability bill

Every call between services can be slow, can fail, or — the worst of the three — can succeed on the other side while returning an error to you. A synchronous call also couples availability. Suppose a request passes through a gateway, then orders, then billing, then a ledger. If each is up 99.9% of the time — about 43 minutes of downtime a month — the chain is up 99.6%, which is nearly three hours a month. That is worse than any component in it, and worse than the monolith that did the same work in one process.

An asynchronous edge breaks that coupling. If checkout publishes an event and search consumes it, search can be down for an hour and no customer notices at checkout; search catches up when it returns. The cost is that search shows stale results for that hour, and someone has to be comfortable with that. Here is the trade in one sentence: synchronous calls buy freshness and pay in availability, asynchronous ones buy availability and pay in staleness.

Start with the timeout (row 1), because without one nothing else works: a caller with no timeout holds a thread until the dependency answers, and a dead dependency never does. Amazon’s practice, as Marc Brooker describes it, is to choose an acceptable rate of false timeouts — say 0.1% — and set the timeout at the matching latency percentile of the callee — the 99.9th percentile in that case. Pass the remaining time downstream as a deadline, too. Otherwise a service five hops down keeps working for a caller that gave up long ago.

Retries (row 2) are where good intentions turn into outages. A retry helps with a failure that will not repeat, such as a restarted instance. Against an overloaded service it adds load exactly when load is the problem. Retries also multiply. Brooker’s example is a request that passes through a five-deep stack of services, with three tries at each layer. If the database at the bottom starts failing under load, it receives 3 × 3 × 3 × 3 × 3 = 243 queries for every one the customer made. Amazon’s rule is to retry at a single point in the stack, with and jitter — waits that grow and are randomised, so that clients do not retry in step.

In Amazon’s example a customer request passes through a five-deep stack of services ending in a database, and every layer tries each call three times. When the database starts failing under load, every layer retries independently, so the load multiplies: 3, then 9, 27, 81, and 243 queries reach the database for one customer request — an overloaded system that is now unlikely ever to recover. Retrying at a single point in the stack, with exponential backoff and jitter, sends at most three. The two columns share one scale, which is why the right-hand bars almost disappear.

A cap on retries keeps a bad hour from becoming a bad day. Google’s SRE book describes two. Each request gets at most three attempts. And each client keeps track of what share of its requests are retries, and stops retrying once that share reaches 10%. In Google’s worst-case example, the first rule alone still lets traffic grow to almost three times normal; the two together hold the growth to about 1.1 times. A per-client token bucket does the same job, and AWS has built one into its since 2016.

Circuit breakers (row 3) are the best-known answer and the most argued about. Michael Nygard named the circuit breaker and the bulkhead in “Release It!” in 2007. A breaker counts failures, and once they cross a threshold it stops calling the dependency for a while and fails fast instead. Netflix’s Hystrix library, open-sourced in 2012, made both patterns standard in the Java world. On 19 November 2018 Netflix put Hystrix into maintenance mode at version 1.5.18. Its focus had moved to adaptive concurrency limits, which react to measured latency instead of thresholds set by hand, and it pointed new projects at resilience4j. Amazon goes further in its own guidance. Breakers add a second mode of behaviour that is hard to test, Brooker writes, and they can add significant time to recovery. Amazon prefers to limit retries with a local token bucket instead.

MechanismWhat it protects againstWhat it costsA sound default
TimeoutThreads piling up behind a dead dependencySlow but healthy calls get cut offThe callee’s p99.9, for 0.1% false timeouts
Deadline propagationWork done for a caller that has already given upEvery hop must read it and pass it ongRPC deadlines; a header over HTTP
Retry with backoff and jitterFailures that will not repeatExtra load during troubleThree attempts, at one layer only
Retry budgetRetry stormsSome requests fail that a retry would have savedRetries under 10% of requests per client
Circuit breakerCalling a dependency that is clearly downA second mode to test; slower recoveryOnly where a real fallback exists
BulkheadOne slow dependency taking every threadIdle capacity in quiet poolsA concurrency limit per dependency
Load sheddingOverload turning into collapseSome users get a fast errorReject before doing any work
Idempotency keyDuplicate side effects from retriesThe receiver stores keysOn every call that changes state

The last row is the one juniors are never warned about and every senior has been burned by (row 5). A network failure does not tell you whether the operation happened: the call may have succeeded and only the response was lost. So any retry of anything that changes state must be safe to repeat, which means the receiver has to recognise a request it has already processed. The client sends a unique with the request; the receiver stores the key with the result and returns the stored result on a repeat. Retries without idempotency are not resilience. They are a machine for duplicating side effects.

Running many services: tracing, discovery, gateways and the mesh

With one service, a failed request leaves one . With many, it leaves fragments in eight sets of logs, and nothing joins them unless you planned for it (row 15). The fix is a trace id created at the edge and passed on every call, so every log line and every timing can be grouped by request. Two standards make this portable. W3C Trace Context, a Recommendation since February 2020, defines the traceparent header that carries the id between services. provides the libraries and the protocol to collect traces, metrics and logs without tying you to one vendor; it graduated in the Cloud Native Computing Foundation on 11 May 2026. Uber described what the absence costs: one investigation meant working through around 50 services across 12 teams to find the root cause.

Service discovery (row 12) is the address book that lets services find each other as instances come and go. On Kubernetes the platform provides it through DNS; elsewhere a registry such as Consul does. Either way, it is now something every call depends on, and it deserves the care of a database. Roblox learned this over 73 hours in October 2021, in one of the five cases below.

A gateway (row 13) gives clients one entry point instead of twenty. Its risk is gravity: authentication, then routing rules, then a little business logic, until the gateway is the monolith again. Lewis and Fowler’s fourth characteristic — smart endpoints, dumb pipes — is the test to hold it to. A useful variant came out of SoundCloud and was written up by Sam Newman in 2015: a backend for each frontend, or BFF. The mobile app and the website each get a thin backend owned by the team that builds that client, so neither client’s needs bend a shared API.

A service mesh (row 14) moves encryption between services, retries, timeouts and metrics out of the code and into a proxy. Classic meshes put the proxy beside each service as a sidecar; Istio’s ambient mode, generally available since version 1.24 in November 2024, runs a shared proxy per node instead. The benefit is uniformity across languages. The cost is a second distributed system to operate and upgrade, and a trap worth naming: if the mesh retries and the application retries too, you have built the multiplying stack from row 2 without writing a line of code. Decide which layer owns retries, and switch them off in the other.

The last row is about change (row 16). A service is independent only if its callers survive its releases. That means changes that stay backward-compatible, a version when they cannot, and a way to know who calls you at all. Consumer-driven contract tests make the last part checkable: each caller publishes the requests it makes and the fields it reads, and the provider’s build fails when a change would break one. The API Design page covers versioning and contracts in depth.

Services and the teams that own them

Microservices are an organisational design as much as a technical one. Melvin Conway observed in 1968 that a system ends up mirroring the communication structure of the organisation that builds it, and the style uses that on purpose: one team, one set of services, one on-call rota. The right size of a service follows from it. It is the size one team can understand, change and run, not a number of lines. And the right number of services is bounded by the number of teams that can own them. The table lists the signals that the two have drifted apart.

SignalHealthyTrouble
Services per teamA handful the team can run on callMore services than people
Services touched by a typical featureOne, most of the timeThree, released in lockstep
Who calls whomA graph generated from code or tracesA change breaks a caller nobody knew about
On callEach team for its own servicesA central operations team for everything
Shared codeSmall, versioned librariesA “common” library every service must upgrade at once

Five cases, and what each one was missing

The cases on the Monolith page went back from services to one process. These five stayed with services, and each documents a cost from the table at the top of this page — in the words of the engineers who paid it.

CaseWhat happenedWhat it shows
Monzo, 2019Over 1,500 services and 9,300 connections; every service could call every other until the bank generated an allow-list from its codeThe call graph exists only in the code until you extract it (row 16)
Uber, 2020Around 2,200 critical services regrouped into 70 domains behind gatewaysPast some size, the unit you design with has to be bigger than one service
AWS Kinesis, 2020A small capacity addition pushed every front-end server past its thread limit; a “best effort” dependency then blocked CognitoA soft dependency is soft only if the code that calls it is tested with it gone
Roblox, 2021A 73-hour outage of the Consul cluster that service discovery, scheduling and secrets all depended onShared platform pieces are single points of failure, including for the monitoring (row 12)
AWS us-east-1, 2025An empty DNS record for DynamoDB was fixed in three hours; EC2’s recovery work then timed out and requeued until engineers throttled itRetries can keep a system down after the cause is gone (rows 2–3)

Monzo’s story starts as a security project and ends as a lesson about knowledge. In November 2019 the bank’s security team wanted the service that manages images on savings pots to be unable to talk to a service that moves money. With a handful of services, an allow-list could be kept by hand. Monzo had over 1,500, with more than 9,300 unique connections between them, and the list changed every day. Nobody had it. So the team wrote rpcmap, a tool that reads the Go code of every service and finds the calls it makes to others. It generates one rule file per called service and checks the files on every push, and the owners of critical services can ask to review every new caller. The ledger was locked down first. The lesson reaches beyond security: in a large system of services, the true dependency graph lives only in the code until a tool extracts it, and row 16 cannot be answered without that graph.

Uber is the same problem at a larger size. By July 2020 the company ran around 2,200 critical microservices, and its engineers described the cost plainly. Services had become black boxes whose behaviour could change at any time. Calls went many layers deep, so a latency spike in one dependency cascaded upstream. Finding one root cause had meant working through around 50 services owned by 12 teams. The answer, Domain-Oriented Microservice Architecture, did not merge the services. It grouped them: related services form a domain, domains form layers that limit what may depend on what, and each domain is reached through a single gateway. Uber classified its 2,200 services into 70 domains, and reported that onboarding a new feature onto a platform got 25–50% faster. One more number from the post is worth keeping: Uber measured the half-life of a microservice at 1.5 years. Half of them were replaced every eighteen months, and gateways stopped every replacement from becoming a migration for its callers.

Where it degrades

Microservices fail in public. A monolith rots quietly over a year; a set of services shows its weak spot during the first bad hour. Almost every failure is one of these:

  • A shared database, which turns a set of services into a distributed monolith (row 7).
  • Services named after technical layers, so every feature touches all of them and nothing releases alone.
  • Releases that need several services to go out in a fixed order — the definition of not being independent.
  • Long synchronous call chains, where availability multiplies down below what the monolith had.
  • Retries at every layer — in the client, the library and the mesh — that multiply into a storm (row 2).
  • Retries without idempotency keys, which duplicate side effects under exactly the conditions retries exist for (row 5).
  • Save-then-publish code with no outbox, which loses an event every time a process dies at the wrong moment (row 9).
  • Sagas with no one asking what a concurrent request sees halfway through (row 8).
  • A gateway that has grown business logic, so it is the monolith again — with an extra hop.
  • One service per developer, which produces a topology that reflects hiring rather than the domain.
  • No distributed tracing, which turns every cross-service incident into archaeology (row 15).

The honest limit is that microservices solve an organisational problem with a technical mechanism. If the constraint is that twelve teams cannot ship without coordinating, the style removes it, and it is worth the bill. If the constraint is that the code is tangled, that deploys are scary or that the database is slow, the style adds a network to the problem and charges for it every month. The network that makes a boundary expensive to cross is also what makes it expensive to move. A wrong boundary in a monolith costs a refactor; between services it costs a migration. And even companies that are good at this end up thinking in bigger units again, as Uber did with its 70 domains.

The practical check takes an afternoon. Pick a recent feature and count the services it touched. Find a release from last month that needed two services deployed together. Check whether every outgoing call has a timeout, and whether retries happen at more than one layer. Search the code for an event published right after a database write without an outbox. Ask who is paged for each service, and whether any service has more owners than one or none. And trace one failed request end to end: if that takes more than ten minutes, row 15 is the first thing to fix.

When to use it

Use it when

  • Several teams genuinely block each other on one deployment, and the cost has been measured.
  • Parts of the system have genuinely different scaling profiles, availability targets or compliance scopes.
  • Automated deployment, distributed tracing and rapid provisioning already work.
  • Teams can run their own services in production, out of hours included.
  • The business can say what a user should see while two services are briefly inconsistent.

Avoid it when

  • A small team — the coordination problem this solves does not exist yet, and the bill still arrives.
  • The domain boundaries are not understood yet, so service boundaries would be guesses that are expensive to move.
  • The real problem is tangled code — splitting along existing coupling produces a distributed version of it.
  • Deployment is still manual, and the manual step would multiply by the number of services.
  • Strong consistency across the whole system is a business requirement rather than a habit.

Sources and method

What this page stands on

  • 19 sources
  • 3 documented failures
  • Checked October 13, 2026

Researched with Claude. Finding and cross-checking the primary sources was done with Claude (Anthropic). Selection, writing and fact-checking: the Anatomy of IT Product editors.

Found this useful?

Share it with someone who is working on the same problem.