Voodoo AIVoodoo AI
Book a Consultation
Back to Architecture & Systems
Architecture 9 min read April 2026

The Modular Monolith Alternative

When a well-structured monolith outperforms microservices — and how to keep it from becoming a big ball of mud.

The Case for Monoliths

Microservices dominate conference talks, but monoliths dominate production systems. For teams under 50 engineers, a well-structured monolith often delivers faster development, simpler operations, and lower cognitive load than distributed systems.

The microservices hype cycle peaked around 2018. Every conference talked about breaking monoliths into services. The reality: most teams that adopted microservices early have regretted it. They traded a monolith for a distributed monolith.

Hidden Costs of Microservices: Network latency between services adds 1-10ms per hop. Distributed transactions require sagas or compensations. Debugging requires distributed tracing. Testing requires contract testing and service virtualization. Deployment requires orchestration, service discovery, and circuit breakers. These are not free.

Modular Structure

The key is modularity: clear internal boundaries, explicit interfaces between modules, and independent deployment units within the monolith. Each module owns its domain logic, data access, and API. The monolith is a deployment boundary, not a code boundary.

Package-by-Feature: Each module is a top-level package containing all code for that feature. Contrast with package-by-layer (controllers, services, repositories) which creates tangled dependencies across features. Package-by-feature keeps related code together and unrelated code apart.

The module boundary is enforced by the build system. Each module is a separate compilation unit. Cross-module dependencies are explicit and versioned. Circular dependencies are prohibited. This creates a dependency graph that mirrors the domain model.

Extracting When Ready

When a module grows too large, has independent scaling requirements, or needs separate team ownership, extract it into a service. The modular structure makes extraction straightforward because boundaries already exist.

Extraction Timeline: From a modular monolith: 2-4 weeks (1 week to create the service, 1 week for testing, 1 week to route traffic, 1 week to remove from monolith). From a non-modular monolith: 3-6 months of untangling dependencies, rewriting interfaces, and fixing regressions.
Coupling, Not Size: A module that is 10,000 lines but has no external dependencies does not need extraction. A module that is 1,000 lines but changes every time another module changes is a candidate for extraction. Coupling, not size, determines service boundaries.

Our Recommendation

Start with a modular monolith. Extract services only when clear boundaries and independent scaling requirements emerge. This avoids premature complexity while preserving future flexibility.

The organisations that succeed start simple and evolve complexity. They build a modular monolith, grow it until boundaries emerge, and extract services when the benefits justify the costs. The organisations that fail start with microservices, create complexity prematurely, and spend years paying off the technical debt.

Premature Extraction Anti-Pattern: Teams that extract services before they have clear boundaries create distributed monoliths: services that are tightly coupled, share databases, and require coordinated deployments. The result is worse than the original monolith.

Voodoo AI Engineering Team

We have built both monoliths and microservices at scale.

Choosing your architecture?

We have built both monoliths and microservices at scale.

Book a Consultation