FoundationsArchitecturemedium15 min read

Why Microservices Happened (and When You Don't Need Them)

Microservices aren't an upgrade you graduate to. They're a trade: you take on the hard problems of a distributed system to solve the human and scaling problems of a big team. If you don't have those problems yet, you're paying the price for nothing.

You've heard that serious companies build with microservices, and you've quietly assumed it's the professional way to do things — that a single app is a beginner's setup you're supposed to grow out of. Let's take that assumption apart, because it's backwards, and getting it wrong costs teams years.

We'll build the case for microservices the only honest way: start without them, feel exactly which pains push a team toward them, and be clear-eyed about the new pains they bring. By the end you'll know not just what microservices are, but the much more useful thing — when not to use them.

Start with the monolith

You build your app as one program. One codebase, one thing you deploy. Inside it there's code for users, for orders, for payments, for emails — but it's all one application, running as one process, talking to one database. This is a monolith, and the word gets said like an insult. It shouldn't be.

Browser / app
The applicationusers · orders · payments · email — all one program
One database
One application holds every feature and runs as one unit. Boring. Also great, for a long time.

For a small team and normal traffic, the monolith is genuinely the best design, and not by a little:

  1. Simple to run

    One thing to deploy, one thing to monitor, one set of logs. A new engineer runs the whole app on their laptop in minutes.

  2. Easy to change across features

    Need orders to call payments? It's a normal function call in the same codebase. The compiler catches you if you break it.

  3. Easy to keep data consistent

    Orders and payments share one database, so "save the order and record the payment together, or not at all" is one ordinary transaction.

A monolith carries most companies a remarkably long way. Some of the largest products you use are still, at their core, monoliths. So what goes wrong?

The pain that builds up

Nothing breaks suddenly. The monolith just gets heavier as two things grow: the number of people working on it, and the amount of traffic. Specific frictions start to show.

With a small team, all fine

Five engineers, one app. Everyone roughly knows the whole codebase. Deploys are calm. Merges rarely collide. The shared database is a convenience, not a problem.

With a big team, friction everywhere

Eighty engineers on one codebase. Every deploy bundles dozens of people's changes, so one person's bug blocks everyone's release. Teams step on each other's code. To ship a tiny change you redeploy the entire app. Everyone's afraid to touch the shared parts.

Three pains in particular sharpen as you grow:

  1. Deploys become scary and shared. Because it's one deployable unit, you can't ship the orders team's fix without also shipping everything the payments team merged this week. One risky change holds the whole release hostage, so releases get rarer and bigger, which makes them riskier still.
  2. One failure can sink everything. It's one process. A memory leak in the rarely-used reporting feature can crash the same process that's taking payments. A bug in one corner can take the whole app down, because there are no walls inside.
  3. You can't scale one part on its own. Suppose image processing is heavy and everything else is light. With a monolith, the only way to get more image-processing power is to run more copies of the entire app — paying for extra capacity of every feature just to scale the one that's hot.

Notice these are mostly people and scaling problems, not problems with the code being wrong. That detail is the whole key to the decision, and we'll come back to it.

The obvious fixes, and the wall

A good engineer tries the cheap fixes first. For a while they work.

  • Organise the code into clean modules inside the monolith, with clear boundaries between users, orders, payments. This genuinely helps the "stepping on each other" pain and you should do it regardless. It's free and it's good. But everyone still deploys together, and one crash still takes everything down.
  • Run more copies of the whole app behind a load balancer. This handles more traffic. But you're still scaling every feature together, and a bad deploy still rolls out everywhere at once.

The cheap fixes ease the friction without removing its source: it's still one unit that everyone shares, deploys together, and that fails together. This is worth saying plainly, because it's the crux of the whole decision: the one thing a separate service gives you that a tidy module inside a monolith does not is independent deployment (and the isolation and per-part scaling that ride along with it). Clean modules buy you almost everything else for free. So if your pain is "the code is a mess," modules fix that. If your pain is "we can't ship without coordinating eighty people," that's the wall, and only splitting the app gets you over it.

The move: split into services

You take the one application and break it into several smaller, independent applications, each owning one area. A user service, an orders service, a payments service. Each runs on its own, deploys on its own, and ideally owns its own database. These are microservices, and they talk to each other over the network instead of through function calls.

Orders service
network call
Payments service
Each service is its own small app with its own database, talking to the others over the network.

Look at what this directly fixes, pain for pain:

  1. Independent deploys

    The payments team ships payments whenever they want, without touching orders. Small, frequent, low-risk releases, owned by one team.

  2. Isolated failures

    If the reporting service crashes or leaks memory, payments keeps running — it's a separate process behind a wall. One feature falling over no longer sinks the rest.

  3. Independent scaling

    Image processing is hot? Run twenty copies of just that service and one of everything else. You pay for capacity only where you need it.

  4. Team ownership

    Each team owns a service end to end. They can even pick the right tool for their job without forcing it on everyone.

This is real, and it's why large organisations adopt microservices. But here's the part the hype skips.

The genuinely hard problems you just bought

The instant two pieces of your app talk over a network instead of through a function call, a long list of things that used to be free stops being free. This is the heart of why microservices are hard.

What splitting apart costs you

Network calls fail. A function call in a monolith always returns. A call to another service can time out, fail, or hang because the network is down or that service is slow. Every cross-service call now needs timeouts, retries, and a plan for "what if it doesn't answer?"

Your data is scattered. Orders and payments now live in separate databases. "Save the order and record the payment together, or neither" was one easy transaction in the monolith. Across two services it's a distributed-transaction problem — sagas, compensating actions, eventual consistency — and it's genuinely hard to get right.

Debugging spans many services. One user action might bounce through five services. When it fails, the cause is somewhere in that chain, across five sets of logs on five machines. You now need distributed tracing just to answer "what actually happened?"

Operations multiply. One app became twenty. Twenty things to deploy, monitor, secure, and keep talking to each other. You need real infrastructure — service discovery, centralised logging, dashboards — just to stand still.

Read that list again and notice what it is: microservices take the human problems of a big team (deploys, ownership, isolation) and solve them by taking on the technical problems of a distributed system. That's the trade. It is not a free upgrade. You are swapping one set of hard problems for another, and only coming out ahead if the problems you're solving are bigger than the ones you're buying.

So when should you actually do it?

This is the part to remember when someone proposes microservices for a brand-new project.

DecisionStart with a well-organised monolith. Split into services only when team size and scale force it.

A small team on a monolith ships faster, debugs easier, and keeps data consistent for free — the distributed-system problems simply don't exist for them yet. Microservices pay off when you have enough engineers that deploying together is a real bottleneck, when one part needs to scale or fail independently of the rest, and when you have the operational muscle to run many services. Adopt them to solve a pain you actually feel, never because they sound advanced. A messy distributed system is far worse than a tidy monolith.

The widely repeated advice, and it's good advice: start with a monolith, keep it cleanly modular, and split off services one at a time when a specific part earns its independence — the piece that needs to scale alone, or the team that needs to deploy alone. Splitting a clean monolith later is very doable. Building a distributed system before you need one usually just gives a small team all the pain of microservices and none of the payoff.

Where this leaves you

  1. One app does everything (monolith)

    Simple to run, change, and keep consistent. The right choice for a small team — not a beginner mistake.

  2. The team and traffic grow

    Shared scary deploys, one bug sinking everything, and the inability to scale one part start to hurt.

  3. Cheap fixes run out

    Clean modules and more copies help, but it's still one unit that deploys and fails together.

  4. Split into services

    Independent deploys, isolated failures, independent scaling, clear ownership.

  5. Pay the distributed-system tax

    Network calls fail, data is scattered, debugging spans services, operations multiply. Worth it only when the problems it solves outweigh these.

The one idea to take away

Microservices aren't where you arrive when you get good. They're a trade you make when a big team and big scale make a single shared app more painful than a fleet of distributed ones. For most teams, most of the time, a clean monolith is the right answer — and knowing that, and being able to say why, is more senior than reaching for microservices on day one.

Test yourself

Questions· say the answer out loud before you open it. If you can't, the chapter isn't done.

QWhy is calling a monolith a 'beginner' architecture a misconception?+

Because for a small team and normal traffic, a monolith is genuinely the best design, not a stepping stone you must outgrow. It's simpler to run, deploy, and monitor; cross-feature changes are ordinary function calls the compiler checks; and keeping data consistent is one normal database transaction. Many large products are still monoliths at their core. It's the right tool until specific team-size and scale pains force a change.

QWhat are the main pains that push a team off a monolith?+

Three, and they sharpen as the team and traffic grow: (1) deploys are scary and shared — you can't ship one team's change without shipping everyone's, so releases get big and risky; (2) failures aren't isolated — one process means a bug in any feature can crash the whole app; (3) you can't scale one part alone — adding capacity for one hot feature means running more copies of the entire app. Notice these are mostly people-and-scaling problems, not code-correctness problems.

QWhat hard problems do you take on the moment you split into microservices?+

The ones that come from talking over a network instead of via function calls: network calls can fail/time out, so you need retries and fallbacks; data is split across services, so a change spanning two of them is a hard distributed-transaction problem instead of one easy transaction; debugging now spans many services and machines, needing distributed tracing; and operations multiply, since one app became many things to deploy, monitor, and secure. Microservices trade human/team problems for distributed-systems problems.

QA new startup with four engineers wants to start with microservices 'to be ready to scale.' What do you advise?+

Advise against it. With four engineers they don't yet have the deploy-bottleneck, isolation, or independent-scaling problems microservices solve, but they would immediately pay the full distributed-systems cost: failing network calls, scattered data, harder debugging, and far more operations. Start with a cleanly modular monolith, which ships faster and stays consistent for free, and split off a service later when a specific part earns its independence. Premature microservices give a small team all the pain and none of the payoff.

QIf you keep your monolith well-organised into modules, does that remove the need for microservices?+

It removes some of the friction — clean modules reduce teams stepping on each other and are worth doing regardless — but not the core limits. It's still one deployable unit, so everyone still deploys together, and it's still one process, so a crash in one part can still take everything down, and you still can't scale one part alone. Modules ease the human friction; only splitting into separate services gives independent deploys, isolated failures, and independent scaling.

QWhat's the recommended path if you think you'll eventually need microservices?+

Start with a monolith and keep it cleanly modular with clear internal boundaries. Then split services off one at a time, when a specific part earns its independence — the component that needs to scale by itself or the team that needs to deploy by itself. Splitting a tidy monolith later is very doable, whereas building a full distributed system upfront usually just front-loads all the cost before any of the benefit exists.

Before you leave — how confident are you with this?

Your honest rating shapes when you'll see this again. No grades, no shame.

More deep dives

Comments

to join the discussion.

Loading comments…