← All articles

When Not to Go Distributed

The monolith-versus-microservices argument is usually fought on vibes because nobody itemises the bill. Itemised, microservices are the weakest option on simplicity and raw performance at the highest operational cost — while a modular monolith gets most of the same organisational benefit for a fraction of it.

The migration took a year. The product does the same things, slightly slower, and there is now an on-call rota for the parts that used to be function calls.

Everyone involved was competent. The decision was just never actually made — it was assumed, somewhere around the third planning meeting, and everything after that was execution.

The argument is usually fought on instinct because nobody itemises the bill. It can be itemised.

The two axes people collapse into one

The single most useful move here is separating two decisions that get bundled:

Partitioning — how the code is organised at the top level. Technical partitioning groups by capability: presentation, business rules, persistence, with specialists owning layers. Domain partitioning groups by business concern: order, inventory, payment, with cross-functional teams owning domains.

Deployment model — whether it ships as one unit or many.

These are independent. That is the whole point, and it is what the microservices conversation obscures.

Most teams reaching for microservices actually want the benefits of domain partitioning: a team can own "payments" end to end without negotiating with three other teams. That is a real and worthwhile goal. It is also achievable without distributing anything, and the conflation is what turns a code-organisation problem into a distributed-systems problem.

The bill, itemised

Score the three candidates on what each one structurally favours — not on what a good team could achieve with any of them, but on which direction the style leans before anyone writes a line. Operational cost is the all-in bill: infrastructure, tooling, and the human time to run the thing.

Layered monolithModular monolithMicroservices
Maintainabilityweakgoodstrong
Deployabilityweakgoodstrong
Simplicitystronggoodweak
Evolvabilityweakgoodstrong
Raw performancegoodgoodweak
Elastic scalabilityweakweakstrong
Fault isolationweakweakstrong
Operational cost$$$$$$$$

Read the simplicity and elastic-scalability rows against each other. They are near-perfectly opposed — everything gained on one is surrendered on the other. Most "should we do microservices?" arguments are that single trade, unacknowledged.

Two things in this table surprise people. Microservices come out weak on raw performance, worse than a layered monolith, because every in-process call you replace with a network hop costs latency and adds a failure mode — distribution buys you the ability to scale out, and charges you on every individual request to do it. And a modular monolith reaches the good band on maintainability, deployability and evolvability at $$: most of the organisational benefit, a fraction of the bill.

Treat this as a shape, not a score. A single verdict on "performance" across every conceivable layered system is obviously lossy. Use it to see which direction a style leans — not to compute a winner.

What distribution genuinely buys

The honest yes, because there is one:

  • Independent deployability. Ship payments without coordinating a release train.
  • Per-service scaling. One component takes a hundred times the traffic of the rest.
  • Fault isolation. Recommendations dying must not take checkout with it.
  • Team autonomy — in practice the real driver, and the best of the four. Past some headcount, teams stepping on each other in one codebase costs more than the network does.

If none of those is your binding constraint, you are paying $$$$$ for nothing. And note that the first one is conditional: independent deployability is falsified by shared operational data, not granted by separate repositories. Split twelve services over one schema and you have twelve deployables in a single quantum — the cost without the benefit.

The option nobody costs out

A modular monolith keeps one deployment unit and partitions by domain instead of by technical concern. Each module is one subdomain and holds everything that subdomain needs: its own domain logic, its own persistence, its own API surface. The cut runs vertically. A layered monolith slices horizontally and makes every feature travel through all the slices; a modular monolith slices per domain, so a feature lives inside one.

src/modules/orders/      ← one module: its own domain, persistence and API
src/modules/inventory/   ← another
src/modules/payments/    ← another

Team ownership by domain. Enforced boundaries — with a fitness function failing the build when one module reaches into another's internals. One deployment, one database transaction where you need it, one stack trace when it breaks. At $$.

Its ceiling is real and worth stating: one set of architectural characteristics for the whole application. You cannot scale the order module independently, or give inventory a different availability target. When you genuinely need divergent characteristics per domain, that is the moment distribution starts paying — and it is a moment you can wait for rather than predict.

It is also the better starting position, because module boundaries that survived a year of real change are far better information about where to split than a whiteboard guess made before the domain was understood.

How to actually decide

Do this before the style discussion, not after:

  1. Rank your characteristics, in order. Not a list — everyone wants scalability, maintainability and simplicity. A ranking, where you name what you are giving up. If nothing was given up, the ranking is fake.
  2. Name the binding constraint. What is actually hurting today: deploy coordination, scale, blast radius, or teams colliding?
  3. Check it against the table. With a ranking in hand this stops being a debate and becomes a lookup.
  4. Ask whether domain partitioning alone solves it. Frequently it does, at $$.

The takeaway

Distribution is not an achievement, a maturity level, or a default. It is a purchase, with a listed price and a specific set of goods.

Name the one characteristic you are buying. If you can't, you are paying $$$$$ for a diagram.

And if you have already split: count your quanta before splitting further. The boundary you are looking for may already exist — and the split you are planning may be the one that leaves you with another deployable and still no boundary.