Modular monolith first: when microservices are worth the cost
Microservices solve organisational and scaling problems, but they add distributed-systems complexity. Why a well-structured modular monolith is often the better starting point, and the signals that justify splitting services out.

A modular monolith is one deployable application built from strictly separated internal modules, each with a public interface and private implementation. It gives teams most of the structural benefits associated with microservices — clear boundaries, ownership, testability — without the cost of running a distributed system. For most new products and many mature ones, it is the better starting point.
What microservices really cost
Microservices move complexity from code into infrastructure and operations:
- network calls that can fail, time out or be slow,
- data spread across databases, with consistency handled by events and sagas,
- distributed tracing, service discovery and per-service deployment pipelines,
- versioned contracts between services,
- harder local development and end-to-end testing.
Those costs are worth paying when they solve real problems. Paid too early, they slow delivery for years.
What makes a monolith modular
The difference between a modular monolith and a “big ball of mud” is enforced boundaries:
- Modules by business domain — for a travel platform: search, pricing, orders, payments, customers, content.
- Public interfaces only — other modules call a module’s service interface, never its internal classes or tables.
- Owned data — each module owns its tables; others read through its interface, not by joining across domains.
- Automated boundary checks — architecture tests or static analysis fail the build when a module reaches into another’s internals.
- Internal events — modules communicate state changes through in-process events, which later map naturally to a message bus.
Signals that justify splitting a service out
Extract a module into a separate service when there is evidence, such as:
- Different scaling profile — search fan-out needs many instances; the admin panel needs two.
- Different availability needs — the booking path must stay up during a heavy reporting job.
- Isolation requirements — payment card handling scoped for compliance, or supplier adapters that run untrusted parsing.
- Team autonomy — independent teams blocked by a shared release cadence.
- Technology fit — a component better served by a different runtime, such as a Go service for high-concurrency fan-out next to a PHP application.
In a travel booking engine, search aggregation and supplier adapters are often the first candidates; orders and payments usually benefit from staying together for transactional consistency.
Extract one piece at a time
When extraction is justified:
- Make sure the module’s interface is clean and its data owned.
- Put the interface behind a network boundary with the same contract.
- Move its data to its own database or schema.
- Replace in-process events with a durable message bus, using an outbox — see reliable webhooks for the pattern.
- Add tracing and monitoring before routing production traffic.
Common mistakes
- Distributed monolith — services that must deploy together and share a database. All the costs, none of the benefits.
- Splitting by technical layer — a “database service” and an “API service” instead of domain boundaries.
- Too many services for the team — a rule of thumb is that each service needs a clear owner who understands its operations.
The takeaway
Architecture should follow evidence. Start with a modular monolith that enforces domain boundaries, measure where scaling, availability and team needs actually diverge, and extract services one at a time when the benefit is clear. The boundaries matter far more than the deployment units.
Frequently asked questions
What is a modular monolith?
A modular monolith is a single deployable application divided into well-defined modules with explicit public interfaces and private internals, so domains stay separated in code even though they run in one process and often share one database server.
When should you move to microservices?
When a part of the system has clearly different scaling, availability or deployment needs, when independent teams are blocked by shared releases, or when isolation is required for security or compliance, and the team can operate the added infrastructure.
Can a modular monolith be split into microservices later?
Yes, and that is one of its main advantages. Modules with clean interfaces and owned data can be extracted into services one at a time, when evidence shows it is worth it.