"We need to move to microservices." I hear this in maybe a third of my discovery calls. When I ask why, the answers fall into two categories: "because our monolith is getting hard to manage" (sometimes valid) and "because that's what modern companies do" (never valid).

The microservices movement has been one of the most oversold architectural ideas of the last decade. Not because microservices are bad — they solve real problems at real scale. But because the industry adopted an architecture designed for organizations with 500+ engineers and applied it to teams of 12.

The Hidden Costs of Microservices

Every blog post about microservices talks about the benefits: independent deployment, technology flexibility, fault isolation, team autonomy. These are real. What they don't mention is the operational tax.

Networking complexity. Your monolith made function calls. Your microservices make network calls. Network calls fail. They time out. They retry and cause thundering herds. They create cascading failures when one service goes down and everything that depends on it follows. You now need circuit breakers, retry policies, service mesh, and engineers who understand distributed systems.

Observability overhead. In a monolith, a stack trace tells you what happened. In microservices, a request touches 5-15 services and you need distributed tracing to understand a single user journey. Tools like Jaeger or Datadog APM help, but they add cost and complexity, and someone needs to maintain them.

Data consistency. Your monolith had one database with transactions. Your microservices have multiple databases, and now you need to think about eventual consistency, saga patterns, and what happens when service A succeeds but service B fails. This is genuinely hard computer science, and most teams underestimate it.

Deployment coordination. Yes, you can deploy services independently. In theory. In practice, when service A depends on service B's new API, you need to deploy B first, verify it works, then deploy A. Multiply this across 20 services and you have a deployment coordination problem that requires its own tooling.

Testing. Testing a monolith means running the application and testing it. Testing microservices means running a local environment with 15 services, or maintaining contract tests between services, or accepting that you can't fully test locally and relying on staging environments. Each approach has significant trade-offs.

The Modular Monolith

The architecture that most companies in the $5M-$50M range actually need is a modular monolith: a single deployable application with clear internal boundaries between modules. Each module owns its own data access, exposes a clean internal API, and could theoretically be extracted into a service — but isn't, because the operational overhead isn't justified yet.

This gives you most of the benefits of microservices — team autonomy (teams own modules), independent development (clear boundaries reduce merge conflicts), and clear separation of concerns — without the distributed systems complexity.

Rails, Django, Spring Boot, and even Next.js all support modular monolith patterns well. You can organize your code into domain modules with explicit interfaces, enforce boundaries with linting rules or architecture tests, and extract a module into a service when you have a concrete reason to do so.

When to Actually Extract a Service

There are legitimate reasons to move a piece of functionality out of the monolith. The key word is "reasons" — plural, concrete, and measurable.

Scaling independently. If one module needs to scale to handle 100x more traffic than the rest of the application, extracting it lets you scale it independently without scaling (and paying for) everything else. This is a real reason, but only if you've actually hit the scaling limit, not if you theoretically might someday.

Different technology requirements. If one module needs to be written in Python for ML capabilities while the rest of your application is in TypeScript, extraction makes sense. But think twice — is the technology difference a genuine requirement or a preference?

Organizational boundaries. If a separate team owns a module and the deployment coordination with the rest of the monolith is causing friction, extraction can improve team autonomy. But this usually applies at 30+ engineers, not 12.

Fault isolation. If a bug in one module can crash the entire application and that module is less critical than the core product, extracting it provides genuine fault isolation. But first, consider whether better error handling within the monolith could achieve the same thing more cheaply.

The Netflix Trap

Netflix has 2,000+ engineers, a dedicated platform team of hundreds, and custom-built tools for managing their microservices architecture. Spotify has a similar investment. When someone says "Netflix uses microservices, so we should too," they're proposing to adopt an architecture designed for an organization 100x their size.

Your 15-person engineering team does not have the capacity to build and maintain the platform infrastructure that makes microservices manageable. They'll spend 40% of their time on service-to-service communication, distributed tracing, deployment pipelines for 20 services, and debugging production issues that only manifest under specific network conditions.

That 40% is better spent building your product.


Related: The Prototype-to-Production Gap | Build, Buy, or Partner | Scaling From 1 to 20 Engineers