The choice is usually argued in technical terms — scalability, fault isolation, technology freedom. In practice the deciding question is organisational: how many teams need to deploy without waiting for each other?
If the answer is one, microservices give you the costs and almost none of the benefits.
What microservices actually buy
Independent deployment. Team A ships without coordinating with Team B. This is the real prize, and it only matters when there are several teams.
Independent scaling. One component needs ten times the capacity of the others. Genuine, but rarer than assumed — most systems scale fine as one unit on bigger hardware.
Fault isolation. One service failing does not take the others down. Real, but achievable inside a monolith too, with queues and timeouts.
What they cost
A network hop where a function call used to be. Distributed transactions, or the eventual consistency you have to design around instead. Debugging across process boundaries. Deployment orchestration. And versioned contracts between services, forever.
For one team, this is a large tax paid for something they are not using.
The modular monolith
One deployable unit, but internally bounded like several services. The discipline of boundaries; the simplicity of one deployment.
The difficulty is that a boundary you cannot enforce is not a boundary. Every monolith that turned into a tangle started with modules someone was supposed to respect.
Three ways to make boundaries real:
Separate the data. Each module owns its own schema and its own database context, and no query joins across a boundary. In one platform we built, eighteen modules sit in one PostgreSQL database as eighteen isolated schemas. A module reads another module's data only through its public contract.
Talk through events. Cross-module work — grant a course after payment, credit a wallet after a refund — goes through a transactional outbox rather than a direct call, so a slow module never rolls back another module's transaction.
Test the boundaries. Architecture tests that fail the build when a module reaches somewhere it should not. Design rules that are only documentation get broken; rules that turn the build red do not.
The three signals to split
Split a module out when at least one of these is true, not before:
- A second team owns it and is blocked by your release cycle.
- Its scaling profile is genuinely different — measured, not predicted.
- Its failure must not touch the rest, and queues are not enough.
Absent those, splitting adds operational cost and buys nothing.
Why the modular monolith is easier to split later
This is the part usually missed. A well-bounded monolith is the cheapest possible path to microservices, because the boundaries already exist. Extracting a module means changing its transport, not discovering where it begins.
A monolith without boundaries cannot be split at all — which is why the mistake is not choosing a monolith, it is choosing one without discipline.
The honest default
For a team of fewer than ten engineers shipping one product: a modular monolith, with boundaries enforced at the data layer. Revisit when one of the three signals appears.
If you want to see the shape in production, our .NET and React work describes the method in detail.



Comments
No comments yet. Be the first.