Monolith or Microservices? Stop Copying Netflix and Start Reading Your Own Roadmap

Sathiyan GS
CTO @ Playxoft
•8 min read
ArchitectureMicroservicesEngineeringScaling
A modular monolith compared with a set of independently deployed services

A four-person team once showed us an architecture diagram with eleven services on it. They had not launched. They had no users. They had built a distributed system to serve traffic that did not exist, and they were spending most of their week on the plumbing between services rather than on the product.

They had not made a stupid decision. They had read the engineering blogs of very large companies and reasonably assumed those patterns represented good practice.

They do represent good practice — for those companies, solving their problems, at their size. The monolith vs microservices question has a sensible answer for most teams, and it is not the one the conference talks suggest.

What microservices actually solve

Here is the thing that gets lost: microservices are primarily an organisational solution, not a technical one.

The problem they address is many engineers needing to ship independently without coordinating every release. When you have eight teams touching one codebase, the merge conflicts, coupled release schedules and shared test suites become the bottleneck. Splitting into services lets each team deploy on its own schedule.

That is a genuine and serious problem, and microservices are a genuine and effective answer to it.

It is also a problem you do not have with six engineers. With six engineers, coordinating a release is a conversation.

The scaling argument is much weaker than commonly assumed. A well-built single application on decent hardware handles far more traffic than most products ever see. And when you do need to scale, running several copies of the same application behind a load balancer is dramatically simpler than splitting it into services.

What microservices cost

The costs are real, ongoing, and consistently underestimated because they are invisible on an architecture diagram.

Every call can now fail. A function call within one process either runs or does not. A network call between services can time out, partially succeed, or succeed slowly. Every boundary you create needs retries, timeouts, and a decision about what happens when the other side is unavailable.

Transactions stop being simple. Updating two things atomically inside one database is a solved problem. Across two services it is a distributed systems problem, requiring the saga pattern or something like it, with compensating actions for partial failures.

Debugging becomes archaeology. A request touching six services leaves its story in six places. Without distributed tracing — which is itself infrastructure you must build and maintain — you are correlating timestamps across logs.

Local development gets painful. Running the whole system on a laptop means running many services. Teams end up with elaborate tooling to make this bearable, or they stop running it locally and lose fast feedback.

Someone becomes a platform team. Deployment pipelines, service discovery, monitoring, shared libraries. With a small team, that someone is one of your product engineers, and they are now doing less product work.

A modular monolith compared with a set of independently deployed services

The modular monolith is the right default

The false choice is between a tangled single application and a distributed system. There is a third option that gets most of the benefit of both.

A modular monolith is one deployable application with strong internal boundaries. Modules have defined interfaces. They do not reach into each other's data. The separation is enforced by convention, code structure and review rather than by network calls.

You get the clarity of separation and none of the distributed systems cost. Better, you get something important: the boundaries are cheap to change. Getting boundaries right first time is nearly impossible, because you do not understand the domain until you have built it. Moving a boundary inside a monolith is a refactor. Moving one between deployed services is a migration.

Martin Fowler's argument for starting with a monolith makes this point directly: teams that began with microservices generally struggled, while teams that split a working monolith later did well. The reason is that the successful splits were informed by knowledge the early splitters did not yet have.

When splitting is genuinely justified

Some signals do warrant extracting a service. They are specific, and if none apply you probably do not need one.

Team coordination has become the bottleneck. Multiple teams are genuinely blocking each other on releases, and you have already tried module ownership within the monolith.

One component has wildly different resource needs. A video transcoder, a machine learning inference path, a report generator that occasionally consumes enormous memory. Isolating something that needs different hardware or different scaling behaviour is a real technical reason.

Different availability requirements. A background job failing should not take down checkout. If a component's failure must be contained, a boundary is warranted.

A genuinely different technology is needed. You need a library that only exists in another language, or a specialised runtime.

Notice that three of the four are about isolating one specific thing. The usual right answer is extracting a service, not splitting everything.

If you are being asked to approve an architecture

Architecture decisions are difficult to evaluate as a non-technical stakeholder, and expensive to reverse.

If you have a proposal and want an independent read on whether the complexity is justified for your stage, we are happy to look at it with you — and to tell you when it is.

How to split well, if you should

When the time comes, a few things separate a clean extraction from a painful one.

Split along business boundaries, not technical layers. A service that owns "billing" is coherent. A service that owns "the database layer" is a distributed monolith — all the operational cost, none of the independence.

One service, one datastore. If two services share a database, they are coupled through the schema and neither can change independently. You have gained deployment complexity without gaining deployment independence.

Extract one thing first and live with it. Run it for a few months. Learn what monitoring you need, what your deployment story looks like, what breaks. That experience should inform whether you extract a second.

Keep the monolith. The goal is rarely to eliminate it. A large, well-organised core application with two or three extracted services around it is a perfectly healthy, and very common, end state.

Checklist of architecture decisions that scale well and ones that add cost

A rough guide by team size

Not a law, but it reflects what we see working:

One to six engineers. Modular monolith. Anything else is a tax on a team too small to pay it.

Seven to twenty. Modular monolith, plus services extracted for specific reasons from the list above. Usually one to three.

Twenty to fifty. Real pressure appears. Teams start blocking each other, and services organised around team ownership begin to earn their cost.

Fifty and beyond. Microservices are likely the right structure, and you will have the platform engineering capacity to support them.

The number that matters is engineers, not users. A product with a million users and five engineers should still be a monolith.

The failure mode nobody plans for

There is a specific bad outcome worth naming, because it is common and it is the worst of every option: the distributed monolith.

This is what you get when services are split but remain tightly coupled. They share a database, or they call each other synchronously in long chains, or deploying one requires deploying three others in a particular order.

You now pay the full operational cost of a distributed system — network failures, tracing, complex deployments, painful local development — while getting none of the independence that was the entire justification.

The signs are recognisable. A release requires coordinating several services. One service being down makes several others useless. Changing a database column means changing three codebases. Nobody can run a meaningful subset of the system locally.

The path here is almost always the same: services split before anyone understood the domain, along boundaries that turned out to be wrong, at a point when moving those boundaries was already expensive. It is the specific outcome that starting with a modular monolith is designed to avoid.

If you recognise your own system in that description, the fix is usually to merge services back together rather than to split further — which is unpopular advice and frequently the correct one.

The question to ask instead

When someone proposes splitting, the useful question is not "is this good architecture?" It is: what specific problem does this solve that we have today?

Good answers sound like: "the payment team cannot deploy without waiting for the reporting team," or "the report generator uses eight gigabytes of memory and keeps taking down the web servers."

Weak answers sound like: "it will scale better," "it is more modern," or "it is what larger companies do." Those are aspirations, not problems, and aspirations are an expensive thing to build infrastructure around.

Build for the team you have

The monolith vs microservices debate has produced a strange inversion where the simple, well-understood option requires justification and the complex one is assumed to be sophisticated.

It should be the other way round. Distribution is a cost you pay to solve a specific problem. If you do not have that problem, you are paying for nothing, in a currency — engineering time — that a small team cannot spare.

Start with one well-organised application. Keep its internal boundaries clean so that splitting stays cheap. Extract a service when something concrete forces your hand. That path has taken plenty of products from nothing to substantial scale, and it leaves your team free to work on the product rather than the plumbing.

If you are making an architecture decision and want an outside opinion on whether the complexity is warranted at your stage, book a free consultation call or email info@playxoft.com. We will recommend the simplest thing that solves your actual problem, which is usually less than you were expecting to be sold.

Sathiyan GS

Sathiyan GS

CTO @ Playxoft