The Case Against Microservices

The Case Against Microservices

3 min readarchitecture

Your startup doesn't need Kubernetes. It doesn't need a service mesh. It doesn't need 47 microservices communicating over gRPC with an event bus and a saga orchestrator.

It needs a well-structured monolith and a deployment script.

I know this is heresy. Let me make the case.

The Hidden Costs

Every architecture discussion about microservices focuses on the benefits — independent deployment, team autonomy, technology flexibility. Nobody talks about what you're actually signing up for:

CapabilityMonolithMicroservices
DeploymentOne artifact, one processN artifacts, orchestration layer
DebuggingStack traceDistributed tracing (Jaeger, Zipkin)
LatencyFunction call (~1μs)Network hop (~1-10ms)
Data consistencySQL transactionSagas, eventual consistency
RefactoringIDE rename, doneMulti-repo coordinated deploy
Local devpython main.pyDocker Compose + 8GB RAM
Team size neededAnyNeeds dedicated platform team
MonitoringApplication metricsPer-service metrics + mesh metrics

That table represents real engineering time. Every cell in the "Microservices" column is a team that needs to build, maintain, and on-call for that capability.

Monolith vs microservices architecture comparison
Monolith vs microservices architecture comparison

The Modular Monolith

The false dichotomy is "monolith vs microservices." The real answer for most teams is a modular monolith: clear internal boundaries, shared deployment.

# Project structure for a modular monolith
project/
├── modules/
│   ├── auth/
│   │   ├── __init__.py      # Public API only
│   │   ├── service.py
│   │   ├── models.py
│   │   └── repository.py
│   ├── billing/
│   │   ├── __init__.py
│   │   ├── service.py
│   │   └── stripe_client.py
│   ├── content/
│   │   ├── __init__.py
│   │   ├── service.py
│   │   └── search.py
│   └── notifications/
│       ├── __init__.py
│       └── service.py
├── shared/
│   ├── db.py               # Single database connection
│   ├── events.py           # In-process event bus
│   └── config.py
└── main.py                 # One entry point

Rules of the modular monolith:

  1. Modules communicate through their public API (the __init__.py), never by reaching into internal files.
  2. No cross-module database queries. If billing needs user data, it calls auth.get_user(), not SELECT * FROM users.
  3. Shared state goes through the event bus, not direct imports.
# modules/auth/__init__.py — the public API
from .service import AuthService
from .models import User, Session
 
__all__ = ["AuthService", "User", "Session"]
 
# modules/billing/service.py — uses auth through its public API
from modules.auth import AuthService, User
 
class BillingService:
    def __init__(self, auth: AuthService):
        self.auth = auth
 
    def create_subscription(self, user_id: str, plan: str) -> Subscription:
        user = self.auth.get_user(user_id)
        if user is None:
            raise UserNotFoundError(user_id)
        return self._create_stripe_subscription(user, plan)

When Microservices Actually Make Sense

I'm not saying microservices are always wrong. They make sense when:

  • You have 100+ engineers who can't coordinate releases
  • Components have fundamentally different scaling profiles (e.g., your video transcoding service needs GPUs but your API doesn't)
  • You need polyglot runtimes for genuine technical reasons (not resume-driven development)
  • Regulatory requirements demand isolation between components

Notice the common thread: these are organizational and operational constraints, not technical ones. If you don't have these constraints, you don't need microservices.

Modular monolith boundaries and service extraction path
Modular monolith boundaries and service extraction path

You can always extract a service from a well-structured monolith. You cannot easily merge poorly-designed microservices back into something coherent.

Start with a monolith. Structure it well. Extract services when you have a specific, measurable reason — not when a conference speaker tells you to.

Dopey

Written by Dopey

Just one letter away from being Dope.

Discussion3

Naughty Pheasant04 Feb

Finally someone says it. We spent 6 months migrating to microservices and velocity dropped 40%. We're going back to a modular monolith.

Hidden Snail04 Feb

Devil's advocate: at 200 engineers we absolutely needed service boundaries. The monolith was a bottleneck on deploy frequency.

Naughty Pheasant04 Feb

At 200, sure. But most teams doing microservices are under 20. That's the problem — the pattern is adopted for prestige, not need.

Subscribe above to join the conversation.