The microservices trap
There is a pattern we see repeatedly in the startup world. A founding team of two or three engineers starts a new product. Before writing their first line of business logic, they set up Kubernetes, configure a service mesh, create five separate repositories, and design an event-driven architecture with Apache Kafka.
Six months later, they have impressive infrastructure and almost no product.
We are not against microservices. We have designed and operated microservice architectures in banking environments where they were absolutely the right choice. But the key phrase there is "where they were absolutely the right choice." For most startups, especially in the early stages, microservices are a premature optimization that slows you down when speed is the only thing that matters.
The real cost of microservices
The marketing around microservices emphasizes the benefits: independent deployment, team autonomy, technology flexibility, scalability per service. What gets less attention is the operational tax you pay for those benefits.
Deployment complexity. Instead of deploying one application, you are deploying five, ten, or twenty. Each needs its own CI/CD pipeline, its own health checks, its own rollback strategy. The deployment of a simple feature that touches three services requires coordinating three deployments.
Debugging complexity. When a request fails in a monolith, you look at one set of logs. When a request fails in a microservice architecture, you need distributed tracing, correlation IDs, and the ability to follow a request across multiple services, message queues, and databases.
Data consistency. In a monolith, you have one database and can use transactions. In microservices, each service owns its data, and maintaining consistency across services requires implementing patterns like sagas or eventual consistency — patterns that are genuinely hard to get right.
Network overhead. Every call between services is a network call. Network calls fail. Network calls have latency. Network calls need authentication, serialization, and error handling. A function call in a monolith takes nanoseconds. An HTTP call between services takes milliseconds at best.
For a team of 2-5 engineers working on a product that does not have product-market fit yet, this operational overhead is devastating. You are spending engineering cycles on infrastructure problems instead of product problems.
When monolith is the right call
A well-structured monolith is not a ball of mud. It is a single deployable unit with clear internal boundaries. You can have well-defined modules, clean interfaces between them, and strict dependency rules — all within one codebase and one deployment.
The monolith is the right call when:
Your team is small (under 10 engineers). The primary benefit of microservices — team autonomy — does not matter when the entire team fits in one room.
You do not have product-market fit yet. You need to iterate fast, change direction quickly, and sometimes throw away entire features. Doing that in a monolith means deleting some files. Doing that in microservices means decommissioning services, removing infrastructure, and cleaning up integration points.
Your scale does not require it. Most startups are not processing millions of requests per second. A single well-optimized application server can handle more load than most startups will see in their first two years.
The modular monolith middle ground
The approach we recommend for most startups is the modular monolith. This gives you the architectural benefits of microservices — clear boundaries, defined interfaces, independent domains — without the operational complexity.
In a modular monolith, you structure your code as if it were microservices, but deploy it as one unit. Each module has its own domain, its own data access layer, and communicates with other modules through well-defined interfaces. The modules are not allowed to reach into each other's internals.
The key rules:
Each module owns its data. Even though you might use one database, each module has its own tables and does not read from or write to another module's tables. If Module A needs data from Module B, it calls Module B's public interface.
Interfaces are explicit. Communication between modules goes through defined contracts — function calls with typed parameters, not direct database queries. This is the same principle as microservices, but without the network.
Dependencies are enforced. Use your build system or architectural testing tools to enforce that modules do not import from each other's internals. This keeps the boundaries real, not just aspirational.
This structure means that when you do eventually need to extract a service — because one module needs to scale independently, or because a separate team is going to own it — the extraction is straightforward. The boundaries already exist. You are just moving them from in-process to over-the-network.
Signs you are ready to split
We have seen teams extract microservices at the right time, and we have seen teams do it too early. The signs that you are actually ready:
Deployment conflicts. Different teams are frequently blocked by each other's deployments. Releases are delayed because Team A's feature is not ready but Team B's is.
Scaling mismatch. One part of your system needs ten times the resources of everything else. You are scaling the entire application to meet the needs of one module.
Team ownership boundaries. You have grown to the point where distinct teams own distinct domains, and they are stepping on each other's code. The organizational structure is ready for service boundaries.
Technology mismatch. One module would genuinely benefit from a different technology stack. Not "we want to try Rust" but "this specific workload would be 10x more efficient with a different approach."
If none of these apply to you, you do not need microservices yet. And that is fine. Some of the most successful products in the world ran as monoliths far longer than you would expect.
The best architecture is the one that lets you ship product. At the early stages, that is almost always a monolith.
The real lesson
Architecture decisions should be driven by actual constraints, not by what is trendy. Microservices solve real problems — but only if you actually have those problems. If your constraint is "we need to ship features fast with a small team," microservices are actively working against you.
Start with a modular monolith. Ship product. Find product-market fit. When you hit real scaling or organizational constraints, extract services surgically, one at a time, with clear justification for each extraction.
The companies that win are not the ones with the most sophisticated architecture. They are the ones that shipped the right product at the right time. Architecture is a tool for that, not a goal in itself.