There’s a debate that refuses to die: microservices or monolith? It generates conference talks, hot takes, and the occasional rewrite that nearly kills a company. And like most binary architecture arguments, the honest answer is it depends — and in practice, what you end up wanting is usually a bit of both.
I’ve taken to calling the result a modular microlith: a modular monolith at its core, with a small number of services deliberately peeled off where — and only where — they earn it. It’s not a fudge or a halfway house you got stuck in. It’s the shape I’d reach for on purpose.
Start with a modular monolith
A modular monolith is one deployable unit with strong internal boundaries. Each module owns its own data and its own language — ideally one per bounded context — and talks to the others through clean, explicit interfaces rather than reaching into their tables. From the outside it deploys as a single thing. On the inside it’s as decoupled as a set of services, minus the network.
That “minus the network” is the whole point. In-process calls don’t fail halfway, don’t need retries, don’t go stale, and don’t need a distributed trace to debug. You get most of the modularity benefit and pay almost none of the distributed-systems tax. For the bulk of a system, that’s the better trade.
Then peel off a service — but only with a reason
A module graduates to its own deployable when there’s a specific force pulling it out, not because microservices are the done thing. The reasons that have actually justified it for me:
- It scales differently. One slice gets hammered while the rest idles, and you want to scale (and pay for) just that slice.
- It changes at a different rhythm. A part that’s redeployed daily doesn’t want to be welded to one that ships quarterly and nervously.
- It has a different blast radius. Something with tighter security, compliance, or uptime needs wants its own isolation.
- It’s owned by a different team. Independent deployability buys real autonomy — the Conway’s Law lever, used deliberately.
- It genuinely needs different tech. A workload that wants a different runtime or datastore than the core.
Absent one of those, a module is usually happier staying in-process. “It feels cleaner” is not a force. “We might need to scale it one day” is not a force either — it’s a guess, and an expensive, hard-to-reverse one.
The discipline that makes it work
The combination only stays pleasant if you hold one line: keep the module boundaries clean enough that extraction is cheap. If a module already owns its data, speaks through an interface, and translates at its edges, pulling it out into a service is a contained job — you’re replacing an in-process call with a network one behind the same boundary. That’s the deletion/replaceability discipline pointed at deployment topology.
Which reframes the whole decision nicely. The boundary is the important, durable choice — and it’s a one-way door you should think hard about. Whether that boundary is a function call or an HTTP call is a deferred, reversible one. Get the boundaries right and you can change your mind about the topology later, cheaply, module by module, as real forces show up rather than imagined ones.
I’ve watched this work on a platform that kept a modular core and extracted a handful of services exactly where those forces were real — and left everything else comfortably in-process. The result was far easier to run than the all-microservices version anyone would have drawn on a whiteboard, and far more scalable than a big-ball-of-mud monolith. The point wasn’t the count of services. It was that each split had a reason.
The trap to avoid
The failure mode isn’t “too few services” — it’s the distributed monolith: services split along the wrong lines, so they share a database, or can’t be deployed without each other, or need a chain of synchronous calls to do anything useful. That’s the worst of both worlds — the operational cost of distribution with none of the independence. It almost always comes from splitting on technical lines instead of bounded contexts, or from splitting before the boundaries were clean.
So: build the modular core first. Draw the boundaries where the business already cut them. Peel off a service when a real force demands it, and not a moment sooner. Optimise the boundary; defer the topology. A modular microlith isn’t a compromise — it’s just refusing to answer a false binary.