Reference Guide · Architecture

Modular Monolith vs. Microservices

Not a holy war, but a decision about where a system's complexity should live. For CTOs, architects and technical directors who own the cut.

What is this? · Reference Guide

A solid guide to an engineering question — with trade-offs, costs and the case in which we decide differently. Not an opinion piece, but a reference text. Go to overview

Author
Batunet Engineering
Reading time
16 min
Level
In depth
Status
Approved
Last reviewed
21 July 2026
Updated
21 July 2026
On this page

Few decisions in software engineering are argued so much as a matter of identity and so rarely as an engineering question. "Microservices" counts as modern, "monolith" as backward — and just like that, a trade-off has become a creed. Yet neither word names a quality; both name a topology: how a system is deployed and where its boundaries run.

This text does not prejudge the answer. It treats the question as what it is: a decision about where to put the unavoidable complexity of a system. Into the code or into the network. Into the compiler or into operations. Both paths have their price, and neither is more advanced than the other. The art is not choosing the right style, but recognizing the forces your own system is actually exposed to.


Why this debate exists at all

The debate exists because a word became a status symbol. When a few very large companies broke their systems into services so that hundreds of teams could ship independently, the industry adopted the topology — but not the forces that had made it necessary. "Microservices" became the badge of serious engineering, "monolith" a slur for everything old. That is the heart of the misunderstanding: an answer to an organizational scaling problem was sold as general progress.

The sober view is simpler. Every non-trivial system carries a certain amount of complexity, and that amount does not disappear — it can only be moved. In a modular monolith it lives in the code: boundaries are module boundaries, calls are function calls, consistency is a transaction. In microservices the same complexity lives in the network and in operations: boundaries are process boundaries, calls are network calls with failure modes, consistency becomes a distributed problem. The question is never "which style is better" but "which distribution of complexity fits the forces acting on this system".

One detail makes the decision asymmetric, and it is often overlooked: the two mistakes do not cost the same. A boundary in the code that turns out to be wrong can be moved — it is a refactoring within one deployable. A boundary in the network that turns out to be wrong is a door that is expensive to reopen: separate databases, versioned contracts and operational machinery all have to be dismantled. So when in doubt, choose the reversible mistake — the one you can cheaply correct later.

What a modular monolith really is

The modular monolith is usually misunderstood because it gets confused with its caricature: the unstructured monolith, the "Big Ball of Mud", where everything accesses everything and every change ripples everywhere. That is not what is meant. A modular monolith is a single deployable that is clearly divided into modules internally — each module owns its domain, exposes a narrow interface and does not reach into the internals or tables of another module. The boundaries are real; they are just not enforced by the network, but by discipline and tooling.

This deliberately places the modular monolith between two extremes. It has the clear boundaries the unstructured monolith lacks — and it avoids the network and operational costs that microservices demand. You get most of the benefit of modularity without paying the full price of distribution.

Modular monolith Module Module Module one DB Microservices Service · DB Service · DB Service · DB

Diagram: The same three capabilities — once as modules behind a boundary of discipline, once as services behind a boundary of network.

The same decision can be viewed along several dimensions. No row is an advantage without a downside — each one only describes where the complexity moves:

DimensionModular monolithMicroservices
Boundaries enforced bydiscipline and toolingprocess and network boundary
Deploymentone deployableseparately per service
Data storageone database, transactionsown data per service
Internal callfunction callnetwork call with failure modes
Consistencylocal transactiondistributed, often eventual
Scalingthe whole thing togetherservices independently
Failure domainone processisolated per service
Operational effortlow and fixedhigh, per service
Team autonomycoordination around the deployableindependent delivery
Debuggingone stack trace in-processacross service boundaries, via traces
Where complexity livesin the codein the network and operations

One property is decisive, and the migration path later builds on it: a module with a clean, narrow interface is the seam along which you can extract a service if a force later demands it. The modular monolith is therefore not the alternative to microservices, but often their precursor.

Why many teams split too early

The most common mistake is not choosing microservices, but choosing them at the wrong time — before the forces that justify them are present. The reasons are human and recurring. The style is seen as a sign of maturity, so teams pick it to be taken seriously. People confuse logical boundaries with physical ones and believe only a network call makes a boundary "real". They hope distribution will force good design — yet a badly drawn boundary does not get better through a network call, only more expensive. And they fear scaling problems nobody has measured yet.

The price of this early split is high and due immediately: distributed transactions instead of a local one; network calls with their failure modes instead of function calls; eventual consistency where a transaction used to suffice; operational machinery that was not needed before; debugging across process boundaries; versioned contracts between services you built yourself. You pay the full bill for distribution long before you redeem its benefits.

The recommendation: start with a modular monolith and split only when a concrete, present force demands it. The price is that you will have to extract a service later instead of having it separate from the start — and retrofitting under pressure costs more than having had it from the beginning. We decide otherwise when one of these forces is demonstrably and irreversibly present on day one — such as a regulatory separation required by law; then you draw that one boundary immediately, but in a targeted way, not as a principle across the whole system.

When microservices are objectively the better choice

There are situations where splitting is not fashion but the technically superior answer. They have one thing in common: a real force makes the process boundary worth more than its price.

Independent scaling is the clearest case. When one part of the system has a fundamentally different load and resource profile — compute-intensive versus memory-intensive, rarely versus constantly in demand — you can scale it separately and more cheaply instead of sizing the entire system for its most expensive part. Independent delivery and team autonomy count just as much: when there are enough teams that need to ship without coordinating around a shared deployable, the process boundary becomes an organizational necessity. Fault isolation is a genuine gain where one part must be able to fail independently without dragging the rest down — isolation that only the process boundary truly provides. Technological heterogeneity justifies a cut when one part demonstrably needs a different runtime or language. And independent lifecycles — separate compliance, data residency, different availability requirements — can force a separation that could not be represented cleanly within a shared deployable.

An example that needs no figures: a system has a transactional core that processes many small, fast operations, and alongside it a compute-intensive task — say, an analysis that briefly needs a lot of processing power and runs most cheaply on different hardware. In a shared deployable, the compute-intensive task forces the entire core onto expensive machines and can crowd it out under load. As a separate service, it scales independently, on the hardware that suits it, without touching the core. Here the process boundary buys something concrete — separate scaling and fault isolation — that the monolith cannot offer. That is exactly what a force is.

The common denominator matters: it is the forces that justify the cut, not the size. And a cut is rarely all-or-nothing. You can extract exactly the one part a force acts on and leave the rest in the monolith. The superior architecture is often not "microservices" but "a modular monolith with a few services extracted where it counts".

The recommendation: extract a service when a named, present force demands it — and only that service. The price is the full operational and consistency effort for the extracted part, paid from the first day of its existence. We decide otherwise when the same force can also be handled within the monolith — such as a load spike absorbed by a cache or an asynchronous queue; then the cut buys nothing the cheaper solution would not also provide.

The forces at a glance

ForceHow to recognize itWhat the cut buysThe price
Independent scalingone part has a very different load/resource profileseparate, cheaper scalingown operations, network latency
Team autonomymany teams block each other on the shared deployableindependent delivery without coordinationcontract maintenance, coordination
Fault isolationone part may fail without dragging the rest downreal isolation at the process boundaryredundancy, distributed debugging
Technology heterogeneityone part demonstrably needs a different runtimefree technology choice per servicemore stacks, more operations
Separate lifecyclecompliance, data residency, different availability classclean, enforceable separationduplicated infrastructure

Without the force, all that remains of the cut is the price.

Organizational prerequisites

Microservices are an organizational decision first and a technical one second. Process boundaries follow team boundaries: a service needs a team that owns it — designs, ships and operates it. Without that assignment you get the worst outcome: a distributed system that belongs to everyone and no one, where every change touches several teams and nobody carries responsibility.

The prerequisites are concrete. You need enough teams to meaningfully own services; a culture of ownership in which a team also operates its service; the maturity for on-call duty and incident handling; and the discipline to maintain contracts between teams instead of coordinating over shared internals. Without these, you get the costs of distribution without its benefits.

The recommendation: split a system only as far as your organization can actually own services. The price is that the architecture stays tied to the organization — if the company grows, you have to adjust the cut; if it shrinks, you carry too many services for too few teams. We decide otherwise when a purely technical force mandates a single service that even a small team can operate — then the cut is justified even though the organization is not set up for broad distribution.

Technical prerequisites

You cannot cleanly cut what you do not understand. The first technical prerequisite is clear domain boundaries — bounded contexts that already exist as module boundaries in the modular monolith. Teams that carve out services before the boundaries are understood cement a wrong cut into the network and operations, where it is most expensive to correct.

The further prerequisites build on this: stable, versioned contracts between services, so that one service can change without breaking the others; asynchronous communication where tight coupling threatens, with idempotent receivers so that duplicate delivery does no harm; separate data storage per service, because a shared database cancels out the very boundary you just drew; and end-to-end traceability across service boundaries, so that an operation stays visible across the whole system.

The recommendation: carve out a service only once its boundary has been proven in the monolith and the contract, data storage and traceability are in place. The price is up-front work that shows no visible progress before the first service even exists. We decide otherwise for a service at the clear outer edge of the system — an integration with a third-party service, for instance — whose boundary is unambiguous anyway; there the cut may come earlier, because the risk of a wrong boundary is low.

Operational prerequisites

Operational effort is the most underestimated part of the bill. It is largely fixed: you pay it as soon as you distribute, almost regardless of whether you run three services or thirty. A single deployable needs one pipeline, one log destination, one deploy. Distributed services need more, and they need it for every service.

Concretely, distributed operations require: automated delivery and rollback per service; containerization and orchestration that manages many services; centralized logs, metrics and distributed traces, because without end-to-end observability a distributed system is a black box; service discovery and configuration; secrets management across service boundaries; and an on-call and incident process that grows with the number of services. Observability belongs on day one, not at the first incident.

The recommendation: distribute only once the operational machinery — pipelines, orchestration, end-to-end observability, rollback — is demonstrably in place. The price is a substantial initial investment that only pays for itself beyond a certain number of services and teams. We decide otherwise when a single force mandates exactly one service and the additional operational effort for that one service stays manageable — then you deliberately carry the small fixed cost instead of distributing the whole system.

Migration path

The wrong road to microservices is the same as the wrong road away from a legacy system: the big rewrite. Breaking a system into services on a greenfield means hard-coding boundaries into the network that you do not yet understand — and concentrating all the risk on a single cutover date. The right road is step-by-step, reversible extraction, as with any modernization.

It starts with a modular monolith with clean boundaries. When a concrete force appears — one part needs to scale separately, one team needs to ship independently — you extract exactly that one module along its existing interface. Because the module already has a narrow interface, the extraction is largely mechanical: you give it its own data, replace the previous function call with a network call and its failure modes, and provide the necessary operational machinery for exactly this service. You extract one service after another, prove each one in production and leave the rest in the monolith. That way, distribution grows with the forces instead of anticipating them.

unstructured monolith Modular monolith selectively extracted services draw boundaries extract where a force acts

Diagram: The modular monolith is the seam — you extract from it instead of replacing it.

The recommendation: migrate by extracting individual modules step by step, not through a big-bang conversion into services. The price is a transition period in which the monolith and individual services coexist and have to be operated together. We decide otherwise when the existing system has no viable boundaries to extract from — then you draw the boundaries in the monolith first, before any service exists, instead of distributing along a wrong cut.

Common mistakes

The same patterns keep making distribution expensive or dangerous:

  • The distributed monolith: services that depend on each other synchronously and share a database — the costs of distribution, none of its benefits.
  • Cutting along technical layers instead of domain boundaries, so that every business change touches several services.
  • The shared database that cancels out exactly the boundary the cut was supposed to draw.
  • Nanoservices: split so finely that more calls happen between services than within them.
  • Distribution without observability — a system whose operations can no longer be traced across service boundaries.
  • The big-bang conversion into microservices that hard-codes wrong boundaries into the network.
  • Microservices for a single team that now manages more operations than it gains in benefit.
  • The network call where a function call would have sufficed — distribution for its own sake.

Decision checklist

Questions a team can ask before making the cut. They are diagnostic questions, not verdicts.

  • Did we find and prove the boundaries in the monolith first? What you cannot cleanly delimit, you should not distribute.
  • Is there a named, present force — scaling, fault isolation, team autonomy, technology, compliance? A presumed future is not a force.
  • Can the same force be handled within the monolith — with a cache, a queue or a module? If so, the cut buys nothing.
  • Can our organization actually own and operate the services? Without owners, you get a distributed system without accountability.
  • Is the operational machinery in place — pipelines, orchestration, end-to-end observability, rollback? The fixed operational price is due immediately.
  • Is this a selectively extracted service along a real boundary — or a breakup of the entire system? The cut is rarely all-or-nothing.
  • Can we name the force we are buying and the price we are paying in one sentence? If not, the decision is not ready.
  • When in doubt: did we choose the reversible default — the modular monolith? A boundary in the code is cheaper to take back than one in the network.

FAQ

Isn't the monolith simply outdated? No. "Monolith" describes a deployment topology, not a quality or an age. A modular monolith can be more cleanly structured than a badly cut tangle of services. Being modern does not mean distributing; it means putting complexity where it costs least.

Don't microservices force better design? No. A badly drawn boundary does not get better through a network call — it gets more expensive and harder to correct. Good design comes from understood boundaries — which you can draw just as well in a monolith, where they are cheaper to move again.

Should we start with microservices so we don't have to migrate later? That inverts the costs. You pay the full operational and consistency price immediately and hard-code boundaries you understand least at the start. The modular monolith is the cheaper starting setup, precisely because you can extract from it later.

Isn't a modular monolith just a monolith with extra steps? The "extra steps" are the boundaries — and they are the whole point. They provide the modularity you benefit from and that you can later cut along, without paying the network and operational costs of distribution in advance.

How many services is the right number? As few as the forces demand. The right number does not follow from an ideal but from the number of places where a real force makes the process boundary worth more than its price. Often the result is a monolith with a few extracted services, not a field of many.

And serverless functions? They are a further step along the same axis: even finer distribution, even more operations in the network instead of in the code. The same question applies — which force justifies the boundary? Without a force, here too only the price remains.

Further reading

It is grounded in the Batunet Engineering Method: no premature complexity, reversibility over prediction, boundaries drawn deliberately and in small steps.


There is no winner in this question — only a system exposed to the forces acting on it, and an architecture that knows those forces. If you can name the price and the force in one sentence, you have decided correctly, whatever the answer.

A concrete project in this field?

Reference Guides show how we think. For your system, talk to our management — technical, no sales pitch.