Wise Hustlers — Digital Product & App Development Studio Logo
Get Consultation
By Wise Hustler Admin9/1/20269 min read

Microservices vs Monolith: The Decision Nobody Should Make Before 10 Engineers

Microservices vs Monolith: The Decision Nobody Should Make Before 10 Engineers

# Microservices vs Monolith: The Decision Nobody Should Make Before 10 Engineers

TL;DR: Microservices trade a code-organization problem you can fix with discipline for a distributed-systems problem you can only fix with headcount — network calls, partial failures, data consistency across service boundaries, and a deployment pipeline per service. Below roughly 8-10 engineers, that trade rarely pays off. Start with a monolith, keep its internals modular, and split services out only when a specific team-coordination or scaling problem actually shows up.

The question is usually asked backwards

Most "microservices vs monolith" content frames this as an architecture-quality question: which pattern is more scalable, more resilient, more "modern." That framing is why so many small teams end up running six services, an API gateway, and a message broker for an app with 200 daily active users.

The better framing is organizational: microservices are a solution to a communication problem between teams, not a performance problem in your code. Melvin Conway's observation — that systems end up shaped like the communication structure of the organization that built them — is the actual mechanism here. If you have one team, you will get one system, no matter how many repositories you split it into. You'll just be paying network latency and serialization overhead to enforce boundaries that a linter and a code review policy could enforce for free.

Martin Fowler wrote about this directly in "MonolithFirst": the teams that succeeded with microservices almost always started with a monolith that got too big, and split it once real boundaries emerged from actual usage. Teams that started greenfield with microservices struggled, because they were guessing at service boundaries before they had the domain knowledge to draw them correctly. Guessing wrong with a monolith costs you a refactor. Guessing wrong with microservices costs you a distributed refactor — coordinated deploys, data migrations across services, and API versioning while both the old and new boundary are live.

What "operational cost" actually means

This is the part that gets waved away in most comparisons. It's not abstract — it's a specific list of things a small team now owns:

  • A deployment pipeline per service. Not conceptually — literally. Each service needs its own build, its own versioning, its own rollback path, and its own health checks.
  • Distributed tracing and correlated logging, because a single user request now crosses process (and often network) boundaries, and grep-ing one log file no longer tells you what happened.
  • Data consistency without transactions. A monolith gets ACID guarantees from its database for free. Split that same data across two services and you're now implementing sagas, outbox patterns, or eventual consistency by hand — and debugging the failure modes those introduce.
  • Service discovery and network reliability. Calls that used to be function calls (fast, can't partially fail) are now HTTP or gRPC calls (slow relative to a function call, and can fail in ways a function call cannot — timeout, partial response, retry storms).
  • On-call surface area. Every service is a thing that can page someone at 2am. Ten services means ten sets of alerts, ten sets of runbooks, ten sets of dashboards to know cold.
  • Local development friction. New engineers can't just clone one repo and run one command. They need several services running together, which usually means Docker Compose, Kubernetes-in-a-box (kind/minikube), or a shared staging environment they're afraid to break.

None of this is free even with good tooling, and a five-person team absorbing all of it simultaneously is a five-person team not shipping product features.

The case studies people cite (and get half right)

Two examples get cited constantly in this debate, and it's worth being precise about what each one actually showed.

Segment (2018). Twilio Segment documented publicly that they had grown to 140+ microservices to process customer data, and it had become unsustainable for their team size: engineers spent more time on cross-service maintenance than on features, and the isolation microservices were supposed to provide had turned into unowned complexity. They consolidated back into a single service and reported meaningfully faster iteration afterward. The lesson isn't "microservices are bad" — it's that 140 services for a problem one team owns is a boundary count with no relationship to the org chart or the domain.

Amazon Prime Video (2023). Prime Video's own engineering team published a case study describing how their audio/video quality-monitoring tool moved from a distributed, serverless/microservices design (using Step Functions and S3 as an intermediate store between components) to a monolithic process, and cut infrastructure cost by over 90% while raising scaling ceiling. It's worth being precise here too, because the story gets flattened online: this was one team's one internal tool, not Amazon or Prime Video abandoning microservices as a company-wide strategy — Prime Video runs plenty of microservices elsewhere. The point still stands: the orchestration and network hops between components were pure overhead for a workload that was fundamentally a tight, sequential media pipeline better served by one process.

Both cases are really the same lesson from opposite directions: architecture that doesn't match the actual coupling of the problem — either because a team's boundaries don't match the org, or because a workload's steps don't need network isolation between them — creates cost with no corresponding benefit.

A concrete look at the complexity delta

To make "operational cost" tangible, compare what it takes to run a small e-commerce backend as a modular monolith versus as microservices — same functionality, different physical boundaries.

Modular monolith, one deployable:

# docker-compose.yml — modular monolith
services:
  app:
    build: .
    ports: ["3000:3000"]
    environment:
      DATABASE_URL: postgres://app:app@db:5432/app
    depends_on: [db]
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: app
    volumes: ["db-data:/var/lib/postgresql/data"]
volumes:
  db-data:

Internally, that app service still has clear module boundaries — orders/, inventory/, payments/ — each with its own repository/service layering and no direct imports across modules except through defined interfaces. You get most of the design benefit of microservices (enforced boundaries, independent reasoning about each domain) without paying the deployment cost.

The equivalent split into microservices needs, at minimum:

# docker-compose.yml — microservices (dev-only approximation of prod topology)
services:
  orders:
    build: ./orders
    environment:
      DATABASE_URL: postgres://orders:orders@orders-db:5432/orders
      INVENTORY_URL: http://inventory:3001
      BROKER_URL: amqp://broker:5672
    depends_on: [orders-db, broker]
  inventory:
    build: ./inventory
    environment:
      DATABASE_URL: postgres://inventory:inventory@inventory-db:5432/inventory
      BROKER_URL: amqp://broker:5672
    depends_on: [inventory-db, broker]
  payments:
    build: ./payments
    environment:
      DATABASE_URL: postgres://payments:payments@payments-db:5432/payments
      BROKER_URL: amqp://broker:5672
    depends_on: [payments-db, broker]
  orders-db:
    image: postgres:16
    environment: { POSTGRES_PASSWORD: orders }
  inventory-db:
    image: postgres:16
    environment: { POSTGRES_PASSWORD: inventory }
  payments-db:
    image: postgres:16
    environment: { POSTGRES_PASSWORD: payments }
  broker:
    image: rabbitmq:3-management

Three services, three databases, one message broker — and this is still missing an API gateway, distributed tracing (e.g., OpenTelemetry collector), and the retry/circuit-breaker logic each service needs when a downstream call fails. In production, each of those six-plus containers becomes its own Kubernetes deployment, its own CI job, and its own on-call surface. For a three-person team, that's not "more scalable" — it's more surface area than they can keep in their heads.

Where the trade actually pays off

Microservices earn their cost when the problem is genuinely about independent teams, not independent code:

  • Multiple teams need to deploy independently, without a merge queue or release-train blocking each other. DORA's research on loosely coupled architecture as a delivery-performance capability is specifically about this — teams that can test and deploy without depending on other teams' schedules ship faster, and a modular monolith with a shared release train caps that above a certain team count.
  • Different components have wildly different scaling or resource profiles — a video transcoding pipeline and a billing API have nothing in common operationally, and forcing them to scale together wastes infrastructure spend.
  • You need independent failure domains — one component's outage genuinely should not take others down, and that boundary matters enough to justify the added complexity of handling partial failure everywhere else.
  • Different parts of the system need different tech stacks for good technical reasons (a Python ML inference service alongside a Node.js API layer), and forcing them into one runtime is the bigger compromise.

Below roughly 8-10 engineers, most teams don't yet have more than one or two of these conditions. They have a monolith-sized problem and a microservices-sized org chart drawn on a whiteboard before the product had real usage patterns to design around.

A path that doesn't require betting the architecture upfront

Build the modular monolith first: enforce module boundaries in code (one module per bounded context, no cross-module database access, communication through defined interfaces — the same repository → service → view-model layering pattern works well here). When a specific module consistently needs independent deploys, independent scaling, or a dedicated team, extract that one module into a service. You're then making an evidence-based extraction decision about one component, not a speculative bet on the whole system's shape. Teams evaluating this trade-off for a new build, or auditing whether an existing system's service boundaries still match their team's shape, are the kind of architecture decision custom software engineering work is built around — it's a decision worth getting an outside, unbiased second opinion on before locking in six months of infrastructure work.

FAQ

How many engineers do I need before microservices make sense?

There's no hard number, but somewhere around 8-10 engineers is where a single deploy pipeline and shared codebase starts creating real merge and release contention across teams — that's the signal to look at splitting, not a fixed team size on its own.

Can I get the benefits of microservices without the operational overhead?

Partially — a modular monolith with strict internal boundaries (separate modules, no cross-module database access, clear interfaces) gives you most of the design-time benefits (isolated reasoning, enforced boundaries) without the network hops, distributed data consistency problems, or per-service deployment pipelines.

Is Amazon Prime Video's move back to monolith proof microservices don't scale?

No — it's proof that one internal tool's orchestration overhead (network hops and an S3 intermediate store between tightly sequential steps) didn't match its workload. Prime Video and Amazon overall still run large microservices estates elsewhere; the case study is about matching architecture to a specific workload's coupling, not a blanket verdict on the pattern.

What's the biggest hidden cost of microservices for a small team?

Data consistency. Splitting a database across services means giving up transactions across that boundary, which forces you into sagas, outbox patterns, or eventual consistency — patterns that are straightforward to describe but genuinely hard to implement and debug correctly, especially for a team without prior distributed-systems experience.

Sources