We've been called in to rescue three microservices projects in the past year. All three were early-stage startups — one pre-revenue, two with under $500k ARR — that had built distributed systems before they had distributed teams. All three were in serious trouble. Here's the pattern we keep seeing, and why a well-structured monolith is almost always the right call for your first year.
Microservices Solve an Organizational Problem
The original case for microservices — made by teams at Netflix, Amazon, and Uber — was about organizational scale. When you have 50 teams working on the same codebase, you need deployment independence. A change by Team A shouldn't require Team B to coordinate a release. Microservices give each team ownership of their service and the ability to deploy independently.
If you have 3 engineers, you don't have this problem. You have the opposite problem: you need to move fast, iterate quickly, and change your data model every two weeks as you learn what your customers actually want. Microservices make all of that dramatically harder.
The Real Costs Nobody Talks About
The pitch for microservices focuses on the benefits: independent scaling, technology flexibility, fault isolation. The costs are less often discussed:
- Distributed transactions — what was a single database transaction in a monolith becomes a saga pattern with compensating transactions. This is genuinely hard to get right and easy to get wrong in ways that corrupt data silently.
- Network latency — a function call that takes microseconds becomes an HTTP request that takes milliseconds. At scale this matters; at 100 users it just adds complexity.
- Observability overhead — debugging a bug that spans three services requires distributed tracing, correlation IDs, and centralized logging. Setting this up correctly takes weeks.
- Local development complexity — running 8 services locally requires Docker Compose, service discovery, and a developer machine with enough RAM to handle it. Onboarding a new engineer takes days instead of hours.
- Schema changes — changing a shared data model requires coordinating deployments across multiple services. In a monolith, it's a migration and a deploy.
What We Saw in the Rescue Projects
In all three cases, the founding team had read about microservices, seen the Netflix architecture diagrams, and decided to build that way from day one. By the time we were called in, the symptoms were the same: velocity had dropped to near zero, every feature required touching 4-5 services, and the team spent more time on infrastructure than product.
One team had built a separate service for user authentication, another for notifications, another for billing, and another for the core product — before they had a single paying customer. The authentication service alone had more infrastructure code than product code.
The fix in all three cases was the same: consolidate into a monolith, ship features, find product-market fit, then extract services only when you have a specific, measurable reason to do so.
The Modular Monolith: Best of Both Worlds
The alternative isn't a big ball of mud. A well-structured monolith has clear module boundaries, explicit interfaces between modules, and no circular dependencies. The difference from microservices is that the modules communicate via function calls rather than network requests, and they share a database.
When you eventually need to extract a service — because a specific module needs independent scaling, or because a team needs deployment independence — you can do it. The module boundaries you established in the monolith become the service boundaries. The extraction is a refactor, not a rewrite.
Module Structure Example
src/
modules/
auth/ ← clear boundary
billing/ ← clear boundary
notifications/ ← clear boundary
core/ ← your product logic
shared/ ← types, utils, db client
api/ ← route handlers only
When to Actually Use Microservices
There are legitimate reasons to extract a service early. If you have a component with genuinely different scaling characteristics — a video processing pipeline that needs GPU instances, or a real-time WebSocket server that needs different infrastructure from your API — extract it. If you have a compliance requirement that mandates data isolation for a specific module, extract it.
The test is: do you have a specific, measurable problem that microservices solve, and does the benefit outweigh the operational overhead? If the answer is yes, extract that one service. Don't extract everything.
The rule of thumb we use: start with a monolith, extract services when you have 10+ engineers or a specific technical reason, not before.
Building a new product and want to get the architecture right from the start? We do architecture reviews and can help you avoid the patterns that slow teams down.