Monolith, Microservices, or Neither: An Honest Architecture Guide

DJ
Deepak Jha
8 min read

I have talked more founders out of a microservices migration than into one.

That is not a contrarian pose. It is what happens when you ask the second question. The first question is always "should we split the monolith?" The second is "what is actually slow — the software, or the team?" Most of the time the honest answer is the team, and splitting the codebase makes a people problem worse by adding a distributed-systems problem on top of it.

This is the guide I wish existed when someone asks me that first question.

Start here: what is actually hurting?

Architecture arguments go badly because everyone is answering a different question. Before anything else, work out which of these is true. They have completely different fixes.

"Deploys are scary." Nobody wants to ship on Friday. One change can break something unrelated. → This is a testing and deployment problem. Splitting services does not fix it; it multiplies it across more repos.

"Two engineers cannot work without colliding." Merge conflicts, blocked branches, one person's migration breaking another's feature. → This is a module boundary problem. You can fix it inside a monolith, and you should try that first.

"One part of the system needs ten times the resources of the rest." Your image processing, or your AI inference, or your export job. → This one is a real architecture problem, and it is the single best reason to extract a service.

"The whole thing falls over when one thing breaks." → This is a resilience problem — timeouts, back-pressure, circuit breakers. Distributing it without fixing that makes outages more frequent, not less, because now the network is a failure mode too.

Only the third reliably justifies splitting. If you are in the first, second or fourth, a migration will consume a quarter or more of your team's capacity and leave the actual problem untouched — you will arrive on the other side with the same scary deploys, now spread across more repositories.

The monolith is not the enemy

A well-structured monolith — clear modules, enforced boundaries, one deploy — will take you further than most founders expect. It is also dramatically cheaper to operate: one thing to deploy, one place to look when something breaks, no distributed tracing required to answer "why was that request slow?"

The failure mode is not "monolith". It is the monolith with no internal boundaries — the single service handling auth, payments, notifications, uploads and analytics, where every module imports every other. I see this in nearly every codebase I audit, and I wrote about the shape it takes in I Audited Multiple Startup Backends. They All Had the Same 3 Problems..

Here is the useful move most teams skip: enforce the boundaries you would have between services, but inside one deployable. Separate modules, no cross-imports except through defined interfaces, separate database schemas or at minimum separate tables per domain. Do that and two things happen. Your collision problem largely goes away. And if you ever do need to extract a service, it becomes a weekend rather than a quarter, because the seam already exists.

That is the actual answer for most companies reading this. Not monolith, not microservices — a modular monolith, with the option to split held in reserve.

When splitting genuinely earns its cost

Extract a service when at least one is clearly true:

  1. It scales differently. Inference, video processing, report generation — work that needs different hardware or bursts on a different schedule.
  2. It fails differently, and you need it isolated. Payments should not go down because notifications did.
  3. It has a genuinely separate lifecycle. A different team, a different release cadence, a different compliance boundary.

Notice all three are about operational difference, not code tidiness. "It would be cleaner" is not on the list. Cleanliness is free inside a monolith and expensive across a network.

What the migration actually costs

This is the part nobody writes down honestly, so here it is.

The engineering is the smaller half. The bigger half is everything that becomes newly necessary the moment you have more than one service:

  • Service-to-service auth
  • Distributed tracing (without it, debugging goes from minutes to hours)
  • A story for local development that does not require running nine things
  • Deployment orchestration and rollback per service
  • Someone who can operate all of it at 3am

Data consistency is where migrations actually die. In a monolith a transaction either happens or it does not. Split payments from orders and you have introduced a distributed transaction — and there is no clean way to make it atomic. You now need sagas, or an outbox, or idempotency keys and reconciliation. This is not exotic; it is the normal cost, and it is why I wrote The Microservices Trap: Maintaining Data Consistency When Transactions Span Multiple Services.

Timeline and budget reality is in Monolith to Microservices: The Honest Timeline, Budget, and Risk Nobody Talks About. The short version: teams routinely estimate a quarter, and the overrun almost never comes from the services themselves — it comes from the data-consistency work above, which is invisible until you are already committed.

You probably do not need Kubernetes

Related, and worth saying plainly: the number of companies running Kubernetes is far larger than the number that need it.

Kubernetes solves a real problem — scheduling many services across many machines with automated failover. If you have three services and one region, it is sophisticated machinery aimed at a problem you do not have, and it adds a full operational discipline your team now has to learn while also shipping product.

A managed container platform or plain VMs behind a load balancer will carry a growing startup a very long way. I made the full argument in You Don't Need Kubernetes Yet.

How to do it without stopping the world

If you have concluded a split is genuinely warranted, the method matters more than the destination.

Do not do a big-bang rewrite. Build-the-new-one-alongside-and-switch fails with grim reliability, because the old system keeps changing while you rebuild it and you are perpetually chasing a moving target. The reasons are in The "Big Bang" Rewrite Is a Trap.

Strangle it instead. Put a routing layer in front. Move one route to the new service. Verify. Move the next. The old system shrinks until it is gone. It is unglamorous, it is reversible at every step, and it is how we moved a legacy Java backend to Node.js without a single day of downtime.

The reversibility is the whole point. Every step is independently safe, so a bad step costs you a rollback rather than a quarter.

The communication decision underneath

Once you have more than one service, they have to talk, and the default choice — one service calling another and waiting — quietly rebuilds the monolith with worse failure characteristics. If A must call B must call C synchronously, you have not decoupled anything; you have added network calls to a chain that still fails as one unit.

Events decouple properly: A publishes what happened, B and C react on their own schedule, and B being down does not take A with it. That argument is in Stop Polling, Start Reacting.

There is a newer wrinkle worth flagging: heavy fragmentation actively hurts AI features. When your data is scattered across nine services with nine schemas, assembling the context an LLM needs becomes its own engineering project — which is what I meant in The 'Context Window' Paradox. If AI features are on your roadmap, that is an argument for fewer, larger services than fashion suggests.

The short version

  • Diagnose first. Most "architecture problems" are testing, boundary or resilience problems wearing a costume.
  • A modular monolith is the right answer for most companies at most stages.
  • Split for operational difference — scaling, failure isolation, lifecycle — not for tidiness.
  • Budget for the second half: tracing, auth, local dev, and above all data consistency.
  • Strangle, never rewrite. Keep every step reversible.
  • Skip Kubernetes until you have a problem it solves.

The best architecture decision I have helped a team make in the last two years was to not migrate, spend six weeks on module boundaries and back-pressure inside the monolith, and revisit in a year. They never needed to revisit.

If you are mid-decision on this, the useful thing is usually a second opinion from someone with no incentive to sell you the bigger project.

Read next

Tags:engineeringarchitecturestartups
Deepak J. - Technical Lead

Deepak J.

Technical Lead @ Kosi Digital

15+ years building scalable backend systems, AI integrations, and enterprise platforms. Architected systems serving 60M+ monthly active users.

Building something?

Whether you're starting from scratch or scaling an existing system, we can help. Let's talk about what you're working on.