Amazon, Netflix, and Google use microservices. Therefore microservices are the right architecture. This is the reasoning that has cost more engineering teams more money than any other architectural decision in the past decade.
Kelsey Hightower, Google's former Distinguished Engineer, put it bluntly: "Monoliths are the future because the people who use microservices will learn to consolidate them." Sam Newman, who literally wrote the book on microservices (Building Microservices, O'Reilly), says most teams should not start with microservices. Martin Fowler calls the phenomenon "Microservice Envy."
The InfoQ Architecture Trends report for 2025 shows microservices adoption plateauing while "modular monolith" is trending upward as the recommended starting architecture. The industry is correcting a decade of premature decomposition.
I have built software for 11 years. Every client that came to us with a premature microservices architecture had the same complaint: "We are slower than we should be." None of them had the traffic to justify distributed systems. All of them had the complexity costs.
What Microservices Actually Cost
Operational overhead
Each microservice needs: its own deployment pipeline. Its own monitoring and alerting. Its own logging and log aggregation. Its own database (or shared database with schema boundaries). Its own health checks. Its own scaling rules. Its own error handling and retry logic. Its own API versioning.
For a monolith with 20 features, you have 1 deployment pipeline, 1 monitoring setup, 1 database. For 20 microservices, you have 20 of each. The operational surface area increases 20x. The team size required to maintain that surface area does not scale linearly — it scales worse than linearly because of cross-service coordination.
A DevOps team that can manage a monolith deployment in their sleep needs twice the capacity to manage 10 microservices. And the services interact, so a deployment of service A can break service B through an API contract change that tests did not catch.
Network complexity
In a monolith, a function call between modules takes nanoseconds. In microservices, the same call becomes an HTTP or gRPC request that takes milliseconds. That is 1,000-10,000x slower. For a single call, it does not matter. For a request path that touches 5 services, the latency adds up.
Then add: network failures (timeout handling, retries, circuit breakers), serialization and deserialization overhead, distributed tracing (you need it to debug anything), service discovery, load balancing, and API gateway configuration. Each of these is a solved problem — with dedicated tools, configuration, and cognitive load.
A team that chose microservices for a product serving 1,000 daily users is paying the full complexity cost of distributed systems without the scale benefits that justify it.
Distributed debugging
When a monolith has a bug, you set a breakpoint, step through the code, and find it. When a microservice architecture has a bug, the failure might be in service A, service B, the message queue between them, the API gateway in front, the load balancer, or the network configuration. You need distributed tracing (Jaeger, Zipkin), correlated logging across services, and the ability to reproduce the exact sequence of inter-service calls that caused the failure.
The tool sprawl required for microservices debugging is substantial: distributed tracing, log aggregation, API monitoring, service mesh observability. Each tool adds context-switching overhead. The debugging tax for a microservices architecture is 2-4x higher than for a monolith.
Data consistency nightmares
In a monolith, database transactions guarantee consistency. "Transfer $100 from account A to account B" is a single transaction: debit A and credit B atomically. In microservices, account A is in the Accounts service and account B is in the Payments service. The transfer requires a distributed transaction or a saga pattern — both of which are orders of magnitude more complex than a single database transaction.
Most teams that adopt microservices prematurely discover the data consistency problem 6-12 months in, when they encounter their first race condition that would have been impossible in a monolith. The fix (implementing sagas with compensation logic) takes more engineering effort than the original feature.
When Microservices Make Sense
Microservices are the right architecture under specific conditions:
Team scale. When you have 50+ engineers and need independent deployment by multiple teams. Amazon's "two-pizza team" rule works because Amazon has thousands of engineers. If your team is 5-15 people, you do not need organizational decoupling.
Traffic scale. When different parts of the system have radically different scaling needs. The search service handles 10x the traffic of the admin panel. Scaling them independently saves infrastructure cost. At 1,000 daily users, the entire application runs on a single server.
Technology diversity. When different parts of the system genuinely benefit from different languages or frameworks. The ML pipeline needs Python. The real-time messaging needs Node.js. The core business logic needs Laravel. If the entire system can be built in one language, it should be.
Deployment independence. When one team must deploy without coordinating with other teams. This is an organizational need, not a technical one. It matters at 50+ engineers. It does not matter at 10.
The Modular Monolith
The modular monolith gives you the code organization benefits of microservices without the operational complexity. Modules have clear boundaries, defined interfaces, and independent testability. But they deploy as one unit, share one database, and communicate through function calls instead of HTTP.
When a module outgrows the monolith — when it genuinely needs independent scaling, different technology, or independent deployment — you extract it. The extraction is straightforward because the boundaries are already defined. You are moving a well-defined module to its own service, not trying to untangle a spaghetti monolith.
This is the architecture we recommend for most of our clients. HeyTutor started as a monolith (Vue + Laravel) and served 10,000+ tutors and 10,000+ students. No microservices. The platform handled tens of thousands of sessions per month on a modular Laravel application.
Nautical Commerce — a marketplace platform processing 200K+ monthly transactions — runs on Django. Not 15 microservices. A well-architected Python application with clear module boundaries.
MyFlyRight has processed 1M+ claims over 10 years on a Laravel monolith. No microservices. The architecture from year 1 still works in year 10.
These are not small applications. They handle real traffic, real money, and real users. They work as monoliths because the team size (3-10 engineers per project), the traffic volume (thousands to hundreds of thousands per month), and the organizational structure (one team per product) do not require distributed architecture.
The Decision Framework
Start with a monolith when:
- Your team is under 20 engineers
- Your traffic is under 1M monthly requests
- You are building a new product and the domain is not fully understood
- You want to ship fast and iterate based on user feedback
Consider microservices when:
- Your team exceeds 30-50 engineers and needs organizational independence
- Specific components have proven scaling needs that differ by 10x+
- You need technology diversity for specific components
- You have the DevOps capacity to manage distributed infrastructure
The test: If you are debating microservices and your team is under 15 people, the answer is monolith. If you are debating microservices and you do not have a dedicated DevOps engineer, the answer is definitely monolith.
We build modular monoliths that are designed for future extraction. The architecture is clean. The module boundaries are defined. If and when a module needs to become a service, the extraction is a structured project, not an emergency. But we do not pay the complexity cost of distributed systems until the business justifies it.
Last updated October 27, 2024