Introduction: The Microservices Crisis
42% of organizations are abandoning or simplifying their microservices architectures. This finding, documented in industry reports and confirmed by the experiences of companies like Amazon, Uber, and Twitter, marks a turning point in software architecture history. After a decade of unchecked enthusiasm, the industry is finally reckoning with the hidden costs of distribution.
In this first article of the "From Microservices to Modular Monolith" series, we will analyze the warning signs indicating when a microservices architecture is becoming a problem rather than a solution, examine real-world failure cases, and introduce the modular monolith as a pragmatic alternative.
What You Will Learn in This Article
- The 5 red flags indicating a dysfunctional microservices architecture
- Hidden costs of microservices: infrastructure, latency, operational complexity
- Real case studies: Amazon Prime Video, Twitter, Segment
- The scalability fallacy: when independent scaling is not truly needed
- The decision framework for choosing between microservices and alternatives
- Introduction to the modular monolith as an emerging paradigm
The Distributed Monolith: The Worst Possible Scenario
Many organizations that adopted microservices actually end up with a distributed monolith: a system combining the disadvantages of a monolith (tight coupling, coordinated deploys) with those of distribution (network latency, operational complexity, difficult debugging). It is the worst possible architectural scenario.
A distributed monolith is characterized by:
- Synchronized deploys: you cannot deploy one service without also updating 3-4 other services
- Shared database: multiple services access the same tables, creating data-level coupling
- Chained synchronous calls: a user request traverses 5-8 services before completing
- Blocked teams: inter-service dependencies prevent teams from working autonomously
// Example of a distributed monolith: OrderService depends on 4 services
public class OrderService {
private final UserServiceClient userClient;
private final InventoryServiceClient inventoryClient;
private final PaymentServiceClient paymentClient;
private final NotificationServiceClient notificationClient;
public Order createOrder(CreateOrderRequest request) {
// Synchronous call 1: verify user
User user = userClient.getUser(request.getUserId());
// Synchronous call 2: check availability
InventoryCheck check = inventoryClient.checkAvailability(
request.getItems()
);
// Synchronous call 3: process payment
PaymentResult payment = paymentClient.processPayment(
user, request.getTotal()
);
// Synchronous call 4: send notification
notificationClient.sendOrderConfirmation(user, order);
// If any single service is down, the entire flow fails
return order;
}
}
In this example, OrderService has synchronous dependencies on four distinct services. If even
one of them fails to respond, the entire order creation process fails. These are not independent
microservices: it is a monolith distributed over the network.
The 5 Red Flags
Five key indicators signal when a microservices architecture is becoming a liability rather than an asset. Recognizing them early can save months of work and significant budget.
1. Complexity Overload
When the number of services exceeds the team's capacity to manage them, complexity becomes uncontrollable. A team of 15 developers managing 40 microservices does not have the bandwidth to properly maintain, monitor, and update all services. The ideal ratio is at most 2-3 services per developer, and that is already under optimal conditions.
Complexity manifests in concrete forms: service mesh configuration, TLS certificate management between services, internal API versioning, inter-team contract management. Each additional service increases complexity in a non-linear fashion.
2. 6x Cost Compared to Monolith
Industry studies indicate that the operational cost of a microservices architecture averages 6 times higher than an equivalent monolith. This includes:
- Infrastructure: each service requires its own container, load balancer, health check
- Observability: distributed tracing, log aggregation, per-service metrics
- Networking: service mesh, API gateway, circuit breakers
- Team: dedicated DevOps, platform team, specialized SREs
# Example: Kubernetes resources for ONE single microservice
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
template:
spec:
containers:
- name: order-service
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
# You also need: Service, Ingress, HPA, PDB,
# ConfigMap, Secret, ServiceMonitor, NetworkPolicy...
# Multiply by 20+ services = enormous complexity
3. Unsustainable Operational Overhead
Every microservice carries operational overhead: dedicated CI/CD pipeline, monitoring, alerting, dependency management, security patching. With 30 services, you have 30 pipelines to maintain, 30 alert sets to configure, 30 dockerfiles to update. Time spent on operations surpasses time dedicated to feature development.
4. Increased Latency
Each inter-service call introduces network latency. A request traversing 5 services accumulates the latency of 5 network hops, plus serialization/deserialization time, plus service discovery lookup time. In a monolith, the same operation would be an in-process method call with latency in the order of nanoseconds.
5. Distributed Fragility
In a distributed system, failures are the norm, not the exception. Every communication point between services is a potential failure point. Circuit breakers, retry with backoff, timeouts, fallbacks: these are necessary patterns but add significant complexity to application code.
The Common Sense Rule
If your team spends more than 40% of their time on operational activities (deploys, debugging, infrastructure configuration) rather than feature development, your architecture is working against you, not for you. It is time to reassess architectural choices.
Case Studies: When Giants Turn Back
The experiences of leading technology companies demonstrate that returning from distributed to consolidated is not a sign of weakness, but of engineering maturity.
Amazon Prime Video
In 2023, the Amazon Prime Video team published an emblematic case: the video quality monitoring system, initially built with a serverless microservices pipeline (AWS Step Functions + Lambda), was consolidated into a single process. The result? A 90% cost reduction and a significant improvement in scalability. The bottleneck was not the application logic but the orchestration overhead between distributed components.
Twitter (X)
After the 2022 acquisition, the engineering team drastically reduced the number of microservices, consolidating functionality into larger services and removing layers of indirection. Regardless of opinions on corporate management, the technical result was a significant reduction in infrastructure costs and operational simplification.
Segment
Segment, a customer data platform, documented their experience returning from microservices to a monolith. With over 140 microservices managed by a relatively small team, maintenance costs had become unsustainable. Migration to a more consolidated architecture reduced deployment time by 80% and team operational load by 60%.
The Scalability Fallacy
The most common argument in favor of microservices is independent scalability: the ability to scale only the services that need it. But how many organizations truly need to scale individual components at different levels?
The reality is that in most systems:
- 80% of traffic hits 20% of endpoints
- The database is almost always the real bottleneck, not compute
- A single well-optimized process handles thousands of requests per second
- Horizontal scaling of a monolith (multiple instances behind a load balancer) covers 95% of cases
// Most applications do not need independent
// per-service scaling.
// A horizontally scaled monolith is sufficient:
// Scenario: 10,000 requests/second
// Monolith: 4 instances x 2,500 req/s each
// Microservices: 20 services x 3 instances = 60 containers
// Monolith cost: 4 containers x $50/month = $200/month
// Microservices cost: 60 containers x $30/month = $1,800/month
// + Load balancer, service mesh, monitoring = $500/month
// Total microservices: ~$2,300/month (11.5x more expensive)
The Decision Framework: When to Choose What
There is no universally better architecture. The choice depends on the specific context of the organization. Here is a decision framework based on objective criteria:
Choose Microservices When
- You have large teams (50+ developers) requiring complete autonomy
- You have extreme scaling requirements (millions of requests/second) with very different load patterns between components
- You have heterogeneous technology requirements: components requiring different languages or runtimes
- You have a mature DevOps/Platform team dedicated to infrastructure
- Each feature's time-to-market depends on team independence
Choose a (Modular) Monolith When
- You have a small-to-medium team (3-30 developers)
- Your product is in early or growth stage and boundaries are not yet clear
- You need rapid iteration and deployment simplicity
- Your infrastructure budget is limited
- You need strong consistency between components (ACID transactions)
The Golden Rule
Always start with a modular monolith. Extract microservices only when you have concrete data justifying it: load metrics, measured bottlenecks, specific scaling needs. Premature microservice extraction is one of the leading causes of architectural failure.
Introduction to Modular Monolith
The modular monolith is an architecture combining the best of both worlds: the operational simplicity of a monolith with the logical modularity of microservices. A single deployable artifact contains independent modules with clear boundaries, well-defined internal APIs, and per-module data ownership.
This architecture offers immediate advantages:
- Simple deployment: a single artifact, one pipeline, zero inter-service coordination
- Direct debugging: complete stack trace, no distributed tracing required
- ACID transactions: guaranteed consistency within the process
- Safe refactoring: the compiler verifies interfaces between modules
- Future extraction: modules can become microservices when needed
// Structure of a modular monolith in Java/Spring Boot
// Each module has its own package with clear boundaries
// com.app.order/ (Order module)
// ├── api/ -> module public interfaces
// ├── internal/ -> private implementation
// ├── domain/ -> entities and domain logic
// └── infrastructure/ -> persistence, events
public interface OrderModuleApi {
OrderDto createOrder(CreateOrderCommand cmd);
OrderDto getOrder(UUID orderId);
List<OrderDto> getOrdersByUser(UUID userId);
}
// Implementation is hidden in the internal package
class OrderModuleImpl implements OrderModuleApi {
private final OrderRepository orderRepo;
private final EventPublisher eventPublisher;
public OrderDto createOrder(CreateOrderCommand cmd) {
Order order = Order.create(cmd);
orderRepo.save(order);
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
return OrderDto.from(order);
}
}
What We Will Cover in the Next Articles
This 8-article series will guide you through a complete journey, from understanding the problem to practical migration:
- Article 2: Detailed modular monolith architecture, module boundaries, internal APIs, Spring Modulith
- Article 3: Bounded Contexts and DDD for defining module boundaries
- Article 4: Database design, per-module vs shared schema, data ownership
- Article 5: Communication patterns: in-process events, mediator, CQRS
- Article 6: Deployment and smart scaling, feature flags, blue-green
- Article 7: Step-by-step migration from legacy monolith to modular monolith
- Article 8: Case study with quantified ROI and before/after metrics
Next Article
In the next article we will explore in detail the modular monolith architecture: how to define boundaries between modules, how to design internal APIs, and how frameworks like Spring Modulith can help you maintain boundaries over time. We will move from theory to practice with architectural diagrams and reference code.







