For the last decade one mantra ruled software: "Monoliths are bad; to scale, you must move to microservices." Many companies caught up in the trend split their systems into dozens of independent services before they had settled their customer base or grown their team.
Today many teams are buried under the complexity microservices bring: DevOps overhead, distributed-system failures and high cloud bills. The example that came to symbolise the debate is Amazon Prime Video: the team described moving a video-quality monitoring service from a distributed architecture to a single process and cutting that service's infrastructure cost by more than 90%.
So can you keep a monolith's simplicity and speed while getting the clean boundaries microservices offer? The answer: the modular monolith.
Why microservices aren't the right choice for every company
In theory, microservices are great: each service scales independently, different technologies can be used, and teams can ship without waiting for each other. In practice, these obstacles appear.
Over-engineered infrastructure
Instead of one database query, you have to talk to five services over the network. Every call means network latency and a new point of failure.
Distributed data management
Once databases are split, ACID transactions across services become hard. Achieving consistency after the fact (eventual consistency) creates serious technical debt.
High cloud and DevOps costs
Running a separate CI/CD pipeline, containers, Kubernetes resources and monitoring for every service strains the budget and time of small and mid-sized teams.
What is a modular monolith?
A modular monolith is an architecture in which the application runs as a single deployment unit, but the codebase is made of modules separated by strict boundaries (bounded contexts) rather than tangled "spaghetti code."
In an e-commerce system, for example, user management, orders, payments and inventory live in the same project, but none can reach directly into another's database tables or internal logic. Communication happens only through clearly defined module interfaces and events.
Monolith vs microservices vs modular monolith
| Criterion | Traditional monolith | Microservices | Modular monolith |
|---|---|---|---|
| Deployment | Single unit, fast | Many services, complex | Single unit, fast |
| Code boundaries | Weak, prone to spaghetti | Very strict (network level) | Strict (module level) |
| DevOps and cloud cost | Low | High | Low |
| Development speed | Fast at first, then slow | Slow due to service dependencies | Consistently high |
| Network latency | None (in-memory) | Yes (HTTP / gRPC) | None (in-memory) |
4 big advantages of a modular monolith
1. Simple CI/CD and low operational overhead
One repository and one CI/CD pipeline are enough. You don't lose time managing Kubernetes clusters or analysing latency between services.
2. Boundaries are easy to change and refactor
If you don't fully know your domain yet, drawing microservice boundaries wrong becomes a nightmare. In a modular monolith, merging two modules or redistributing responsibilities is just a few refactoring steps.
3. High performance, no network latency
Modules talk through in-process function calls instead of HTTP or gRPC. Network latency between services — and the error handling those calls need — disappears.
4. Ready to split into microservices later
This is the biggest advantage: in a well-designed modular monolith, if the payments module hits very high traffic tomorrow, turning it into an independent microservice is far easier, because its boundaries and interface are already defined.
Microservices shouldn't be a goal; they should be a natural result of the need to scale.
When should you move to microservices?
If one or more of these apply, a modular monolith is very likely the right choice for you:
- Your engineering team has fewer than 20–30 people,
- There are no radical differences in the scaling needs (CPU, memory, traffic) of your modules,
- Your business model and product boundaries haven't settled yet (MVP or fast-growth phase).
Conversely, when many teams are waiting on each other in the same codebase, or one module scales very differently from the rest or needs a different technology, it's time to split that module into a microservice.
How we apply it at oigoworks
Each of our products runs as an independent application in its own industry, with a single deployment unit; inside, areas such as bookings, payments and reporting are kept as separate modules. When products need to talk to each other, we connect them through APIs and webhooks. We follow the same approach in the custom web software we build for clients: simple and fast today, splittable tomorrow if needed.
Frequently asked questions
What's the difference between a modular monolith and microservices?
In both, code is split into modules with clear boundaries. The difference is that in microservices each module is a separate application communicating over the network, while in a modular monolith all modules live in one application and communicate through function calls and events.
Is a monolithic architecture bad?
No. The problem isn't the monolith itself but the "spaghetti" monolith with no module boundaries. A monolith with clear boundaries is faster and cheaper than microservices for most teams.
How do modules communicate in a modular monolith?
Each module exposes only an explicit interface (a service class or contract) to the others and publishes events. A module never reaches directly into another module's database tables.
Is moving from a modular monolith to microservices hard?
It's relatively easy if the boundaries were drawn right from the start: since the module's interface is already defined, you turn its in-process calls into network calls and deploy it as a separate service.
Conclusion: the balance between speed and sustainability
In software architecture, the point isn't to pick the "coolest" option but the one that best fits your business goals and your team's capacity. A modular monolith offers a sustainable, easy-to-maintain starting point without unnecessary cost — and keeps your system flexible enough to split into microservices when you need to.
