tech, developers, and the code underneath

issue 120· essay·

What happened to microservices

The pendulum swung back and the useful part is the specific reasons, not the vibe.

Around 2015, splitting your application into many small services was the default recommendation. Around 2022, "modular monolith" started appearing in the same conference slots. Now the consensus is roughly "start with a monolith, extract when you have a reason."

The pendulum framing is unhelpful. What is useful is naming the specific reasons the original argument was wrong, because those reasons generalize.

what the case for microservices actually was#

Four claims, all plausible:

  1. Independent deployment. Teams ship without coordinating.
  2. Independent scaling. Scale the busy part, not everything.
  3. Technology freedom. Each service picks its own stack.
  4. Fault isolation. One service's failure does not take down the rest.

how each one went#

Independent deployment: real, and the main benefit. This one held up. If two teams can deploy without coordinating, that is genuinely valuable and it scales with organization size.

The catch: it requires the services to be genuinely independent. If service A cannot deploy without service B deploying a compatible change first, you have a distributed monolith — all the operational cost, none of the benefit. That is the outcome most organizations actually got, because splitting a system along the wrong seams produces services that must change together.

Independent scaling: real and usually irrelevant. Most applications are not scale-constrained in a way where this matters. A monolith on a bigger machine handles a very large amount of traffic, and modern machines are enormous.

Where it matters — one component with wildly different resource requirements, like a video transcoder or an ML inference path — extract that one thing. That is not microservices, that is a service.

Technology freedom: real and a mistake. Six languages means six toolchains, six sets of libraries, six deployment stories, six on-call rotations that cannot help each other, and an engineer who cannot move between teams without a month of ramp-up.

Most organizations that got this eventually mandated standardization anyway, which means they paid the cost and gave up the benefit.

Fault isolation: inverted. This is the one that went most wrong.

In a monolith, a failing component throws an exception. In a distributed system, a failing component times out — after 30 seconds, while holding a connection, in every caller, simultaneously. Cascading failure, retry amplification, and metastable states are failure modes that a monolith simply does not have.

You do not get fault isolation for free by adding a network. You get it by implementing circuit breakers, timeouts, bulkheads, and backpressure — deliberate engineering that most teams did not do because they thought the architecture gave it to them.

the costs nobody budgeted#

Debugging. A single user action touches nine services. Understanding what happened requires distributed tracing, correlated logs, and a mental model of the whole call graph. This is dramatically harder than a stack trace and it is a permanent tax on every incident.

Data consistency. A transaction across services is not a transaction. You get sagas, compensating actions, eventual consistency, and a class of bug where the system is in a state that should be impossible.

Local development. Running the system on a laptop is a project. Teams end up developing against shared environments, which serializes work and reintroduces the coordination cost that microservices were supposed to remove.

Latency. In-process call: nanoseconds. Network call: milliseconds. A chain of nine is a user-visible delay that did not exist before.

Operational surface. Nine services means nine deployment pipelines, nine sets of dashboards, nine on-call runbooks, nine dependency trees to patch.

the synthesis#

Start with a monolith. One deployable, one database, clear internal module boundaries with enforced dependency rules.

Enforce the boundaries in code. This is the part people skip and it is the part that matters. Modules with explicit public interfaces, dependency rules that fail the build, and no reaching into another module's tables. If you do this, you have most of the benefit and none of the network.

Extract a service when you have a specific reason. Good reasons: a genuinely different scaling profile, a team that needs independent release cadence and can have it, a compliance boundary, a component that must be written in a different language.

Bad reasons: it feels cleaner, we read a blog post, our architecture diagram would look better.

The extraction should be easy if the boundaries were real. If pulling a module out is a six-month project, that tells you the boundary was not real, and it also tells you extracting it would not have given you independence.

the general lesson#

Every architectural decision trades one kind of complexity for another. Microservices trade code complexity for operational complexity.

For a large organization with many teams, that is frequently a good trade — operational complexity can be handled by a platform team, and coordination complexity cannot be handled by anyone.

For a team of twelve, it is a terrible trade, and the industry spent five years recommending it to teams of twelve.

Dom, February 11, 2026

get README in your inbox

One dispatch, no noise. Tech and developer news, plus the occasional long piece on the craft.

subscribe →