Modules vs. Microservices: The Architecture Battle
Table of contents
- Key takeaways
- Modular architecture: structure without fragmentation
- Microservices: decoupling at the cost of operational complexity
- When to choose each option
- Quick comparison
- Conclusion
- Frequently asked questions
- At what team size do microservices pay off over a modular monolith?
- What hidden costs do microservices carry that a monolith does not?
- Can a large monolith be maintained without degenerating into chaos?
- Sources
Choose modular architecture when your team has fewer than ten people and ships the system as a single unit; choose microservices when separate teams need to deploy independently or when specific components require very different scaling, in exchange for higher operational complexity.
Choosing between modular architecture and microservices is one of the most important decisions when designing a software system, and one of the hardest to undo once made.
Key takeaways
-
Modular architecture groups functionality into reusable components within a single deployment.
-
Microservices decouple each capability into an independent service with its own lifecycle.
-
The operational cost of microservices is real: orchestration, observability, and eventual consistency are not free.
-
Team size and frequency of independent deployment are better predictors of the right choice than project size.
-
The default is to start modular and extract microservices only where there is concrete friction.
Modular architecture: structure without fragmentation
Modular architecture divides a project into components with well-defined responsibilities, maintained as cohesive units within a single deployment process. Each module exposes a clear interface to the rest of the system.
The strengths are reuse and traceability: a well-designed module can move between projects, and debugging a failure has a predictable blast radius. Integration is direct: in-process calls, with no network latency or serialisation.
The downside is coupling. If modules share a database, global state, or concrete types in their interfaces, a change in one can silently break others.
Shopify documents the most-cited case for this approach[1]: a Ruby monolith with over 2.8 million lines of code and 500,000 commits, split into 37 components. An internal tool, Packwerk, enforces the boundaries, and about a third of the components have adopted it already. The biggest risk is not modular architecture itself, but modules that grow without governance and end up as a structureless monolith.
(Image: Igabriel85, CC BY-SA 4.0, via Wikimedia Commons)
Microservices architecture diagram with API gateway and independent services.
Microservices: decoupling at the cost of operational complexity
Microservices take the single-responsibility principle to the process and deployment level. Each service:
-
Has its own database (or at least its own schema).
-
Is deployed, scaled, and versioned independently.
-
Communicates with the rest via synchronous APIs (REST, gRPC) or asynchronous messaging (events).
-
Can be written in a different language if the use case justifies it.
The real advantage of microservices is organisational, not technical: small teams with full ownership of a service can iterate without coordinating every change with the rest of the organisation. James Lewis and Martin Fowler described it back in 2014[2] with Amazon’s "two-pizza team" rule. A team should be no bigger than two pizzas can feed, in practice a dozen people at most per service. Horizontal scalability per service is a natural consequence, not the primary goal: Lyft, for instance, runs more than 100 microservices on AWS[3].
The cost is high and should not be underestimated:
-
Distributed observability: correlated traces between services, aggregated logs, per-service metrics. Tools like Pixie for Kubernetes or a Prometheus + Jaeger stack are nearly mandatory. See observability with Pixie on Kubernetes.
-
Eventual consistency: without ACID transactions crossing service boundaries, the design must accept that data may be temporarily inconsistent.
-
Network latency: every cross-service call adds latency and a failure point that did not exist in a monolith.
-
API version management: contracts between services must evolve without breaking existing consumers.
When to choose each option
The decision depends less on project size than on two concrete variables:
Choose modular architecture when:
-
The team is small (< 10 people) and works on the same domain.
-
Deployment cycles are joint: shipping a version of the system is the unit of work.
-
The operational complexity of orchestrating services outweighs the benefit of decoupling them.
-
The project is in product exploration: the right structure is not yet clear.
Choose microservices when:
-
Distinct teams need to deploy autonomously without manual coordination.
-
Specific components have radically different scaling requirements (e.g. the search engine vs. the billing service).
-
The domain is already well understood and context boundaries are stable.
-
Mature platform infrastructure exists (orchestration, observability, per-service CI/CD).
Quick comparison
| Criterion | Modular | Microservices |
|---|---|---|
| Operational complexity | Low | High |
| Initial iteration speed | High | Medium |
| Independent scaling | No | Yes |
| Team autonomy | Medium | High |
| Debugging | Simple | Requires distributed traces |
| Data consistency | ACID | Eventual |
Conclusion
The battle between modules and microservices has no universal winner: it has a winner per context. Projects that adopt microservices prematurely accumulate operational debt without the organisational benefits that justify them. Those that never refactor their monolith end up with components impossible to scale independently. The key is choosing the architecture that fits the current team size and current domain clarity, with a migration path traced but not executed until there is a real need.
This article is also available in Spanish: Módulos vs. Microservicios: La Batalla de la Arquitectura.
Sources:
- James Lewis and Martin Fowler, Microservices (martinfowler.com, 2014)[2]
- AWS, What Are Microservices?[3]
- Shopify Engineering, Under Deconstruction: The State of Shopify’s Monolith[1]
Frequently asked questions
At what team size do microservices pay off over a modular monolith?
Team size and the frequency of independent deployment predict the decision better than project size. With fewer than 10 people working on the same domain and joint deployment cycles, modular architecture is the sensible choice. Microservices pay off when distinct teams need to deploy autonomously; Amazon's two-pizza rule, described by Lewis and Fowler in 2014, caps a team at about a dozen people per service.
What hidden costs do microservices carry that a monolith does not?
Four main ones. Distributed observability needs correlated traces between services, aggregated logs and per-service metrics, for which a Prometheus plus Jaeger stack or Pixie on Kubernetes is nearly mandatory. Eventual consistency follows, since there are no ACID transactions crossing service boundaries; every cross-service call adds network latency and a new failure point. And API versions have to be managed so contracts evolve without breaking existing consumers.
Can a large monolith be maintained without degenerating into chaos?
Yes, with governance of module boundaries. Shopify maintains a Ruby monolith of over 2.8 million lines of code and 500,000 commits, split into 37 components. Its internal tool Packwerk enforces the boundaries, and about a third of the components have adopted it. The real risk is not modular architecture itself but modules that share a database, global state or concrete types and grow unchecked into a structureless monolith.