Skip to content

Insight // Architecture

All insights

Modular monolith before micro­services

Why you want the boundaries, not the operational bill.

The question comes up sooner or later in almost every project: should we not move to microservices? It is usually asked before there is a concrete problem that calls for it. By then the answer is already taking shape before anyone has looked at what it costs.

Microservices solve a real problem: they make it possible to develop, deploy and scale parts independently. But it is not a free advantage. You trade internal complexity for network complexity, and the latter is harder to control in practice.

Short answer

  • Split to microservices only when you can point at concrete drivers: measurably different scaling needs, teams blocking each other, a clear data boundary or a real need for independent deployment.
  • If you cannot point at two or three of those drivers, consider a modular monolith instead of microservices.
  • A modular monolith is one deployable system with strict internal boundaries and clear interfaces.
  • The difference between the two is not the structure but whether a network sits between the parts.
  • A poorly bounded microservices architecture becomes a distributed monolith: all the downsides of both, without the advantage.
  • Large systems run successfully as a single deployable application. Large companies do this with strict internal separation and tooling that fails the build when boundaries are crossed.

The difference is not the structure

You can draw the same modules two ways. As internal boundaries inside one application, or as separate services with a network between them. The domains are identical: orders, billing, catalogue. What changes is whether a network sits between those parts, and what that network gives you for which price.

That is the core of the choice. A network between modules gives you independent deployment and scaling. In return you get everything distributed systems bring: network failures, latency, data that no longer fits in a single transaction and the need to observe what happens between services.

A modular monolith tries to take the best of both. It is one thing to deploy, with the structure and boundaries people normally go to microservices for. You get the discipline of clear modules without the operational bill of a network.

What a real module boundary is

A module boundary is more than a folder. Grouping files into orders/, billing/ and catalogue/ does not make a modular system. The boundary means a module can only be reached from outside through a defined interface, and that its internal parts are not called from outside.

In practice that is the hard part. Without enforcement the boundary disappears within a few months, because it is always easier to reach directly into another module's internals than to extend the interface. That is why a good modular monolith also has tooling: the build fails when one module reaches into another's internals.

That is exactly what a large company did when it declined to split its sizeable application. Instead of pulling the system apart, it drew firm boundaries inside it, grouped the code into components with fixed interfaces and built tooling that failed the build on a violation. The application stayed one thing to deploy and still got the structure people normally reach for microservices to achieve.

Why drawing boundaries is easier in a modulith

Defining the boundaries is the hardest part in microservices, and it is one of the main reasons many microservices architectures stall. Once a boundary is drawn wrongly and a network sits between the parts, moving that boundary is a costly project.

In a modular monolith you can still make that call. Modules that are too small or talk to each other too much can be merged. Modules that have grown too large or carry too many dependencies can be split. That flexibility is exactly what you need early on, when your domain is not yet fully understood.

When microservices are the right choice

This is not an argument against microservices. It is an argument for a concrete reason. There are situations where the advantages outweigh the costs. When one part of your system has a very different scaling need from the rest, independent scaling is real gain. When several teams block each other in one release process, a boundary can solve that. When you can draw a clear data boundary or genuinely need independent deployment, the network earns its keep.

The pattern that works: divide your system into logical modules, but keep the physical deployment as one whole. Only when you experience concrete problems that nothing but a real separation can solve do you move a module to its own service. That order is safer than the reverse.

The distributed monolith

The most expensive outcome is when you split without really drawing the boundaries. You then get the same tightly coupled system, but now spread across a network. Every change still touches several services, deployment is more complex and failures are harder to find, without anything in return.

This often happens when the urge to split comes from a trend rather than a problem. The system is divided into the shape of microservices, but the dependencies remain as they were. Then you have all the downsides of both worlds.

Conclusion

The question is not whether you want microservices, but whether you want the boundaries and are prepared to pay the operational bill. For most teams the answer is: the boundaries yes, the bill not yet. Start with a modular monolith, draw the boundaries properly and enforce them with tooling. Split only when a concrete problem calls for it.

Want to know whether your architecture is ready to split, or how to draw the boundaries inside your system properly? GitCity is happy to look at the setup and the choices behind it with you. Get in touch for a first, no-obligation conversation.