All articles

From Monolith to Microservices: When Breaking Things Apart Actually Helps

Jean-Eudes ASSOGBANovember 10, 20256 min read
From Monolith to Microservices: When Breaking Things Apart Actually Helps

From Monolith to Microservices: When Breaking Things Apart Actually Helps

The word "monolith" has become a slur in software engineering conversations. Say you're running a monolith and half the room will look at you with the kind of sympathy usually reserved for people who still use fax machines.

This is absurd. Some of the most successful software companies in the world run monoliths — or ran them for years before decomposing, and decomposed only specific pieces that genuinely needed it.

Shopify's core platform is a monolith. It processes $6.9 billion in merchant sales per day. Stack Overflow served 100 million monthly visitors on what they affectionately called a "monolith that's really well cared for." Basecamp has been a profitable monolith for over twenty years.

The question was never "monolith or microservices?" It was always "where are the boundaries, and which ones need to be independent?"

Why Monoliths Are Actually Good

A monolith is a single deployable unit where all your application code lives. One repository, one build pipeline, one deployment. And that simplicity carries real advantages:

Easy to reason about. When a bug happens, you grep one codebase. When you need to understand a flow, you trace function calls in one project. You don't need a distributed tracing system to understand why an order failed.

Simple operations. One process to monitor. One deployment to manage. One database to back up. The operational overhead of a monolith is genuinely tiny compared to even a modest microservices architecture.

Refactoring is cheap. Rename a function and your IDE finds every call site. Move a module between packages with confidence. This trivial-seeming benefit becomes enormous when your architecture needs to evolve — and it always does.

Transactions are easy. When your order service and inventory service and notification service all live in the same process, ACID transactions just work. In a microservices world, you need sagas, eventual consistency, and compensating transactions — all complex, all with failure modes.

A well-structured monolith with clear module boundaries and proper separation of concerns is an excellent architecture for most applications. It is not a sign of technical immaturity. It is a sign of pragmatism.

When the Monolith Starts Hurting

That said, there are legitimate pain points that emerge as a monolith scales:

Deployment coupling. When your team grows past 15-20 engineers, coordinating deployments becomes a negotiation exercise. The payments team wants to ship a fix, but the search team's half-finished feature is in the same deployment pipeline. Merge conflicts multiply. Release trains slow down.

Scaling granularity. You can only scale a monolith by running more copies of the entire thing. If your image processing module needs 10x more CPU than your auth module, you're paying 10x more for auth capacity you don't need.

Technology constraints. The entire monolith runs on one stack. If a specific problem domain would benefit enormously from a different language or framework, you're stuck.

Blast radius. A memory leak in one module can take down the entire application. In a microservices architecture, a failing service can be isolated while everything else continues functioning.

If you're experiencing these problems — and you should verify that you actually are, not just fear them — then selective decomposition starts to make sense.

The Strangler Fig Pattern

The safest migration strategy has a name borrowed from botany. A strangler fig germinates in the canopy of a host tree, sending roots down to the ground. Over decades, its roots thicken and eventually replace the host tree entirely — but the host continues functioning throughout the process.

Applied to software:

  1. Identify a bounded context in your monolith that has clear inputs and outputs. The billing module. The notification system. The search engine.

  2. Build the new service alongside the monolith. It has its own database, its own deployment pipeline, its own team ownership.

  3. Route traffic gradually. Start by sending 1% of requests to the new service. Monitor. Increase to 10%, then 50%, then 100%.

  4. Remove the old code from the monolith only after the new service has been running in production for weeks without issues.

This approach is slow. That's the point. You never take a risky big-bang migration. The monolith keeps running throughout, and if the new service has problems, you route traffic back instantly.

We used this exact approach with a client's e-commerce platform. Their order processing module was the bottleneck — handling inventory checks, payment processing, and email notifications all synchronously. We extracted it into three services over four months, keeping the monolith running the whole time. Total downtime during migration: zero.

What Most Teams Get Wrong

Extracting Too Many Services Too Fast

The most common failure mode is a team that reads about microservices, gets excited, and decomposes their monolith into 30 services over a quarter. Suddenly they have 30 repositories, 30 CI pipelines, 30 things to monitor, and a distributed system they don't have the operational maturity to manage.

Start with one extraction. Learn the operational patterns. Build your deployment tooling. Then consider the next one.

Ignoring the Network

Function calls within a monolith take microseconds. Network calls between services take milliseconds — 1000x slower. And network calls can fail in ways function calls can't: timeouts, partial failures, network partitions, DNS resolution delays.

Every service boundary you introduce is a potential failure point. Design for it: implement retries with exponential backoff, circuit breakers, fallback behaviors, and timeouts. If that sounds like a lot of work, it is. That's the cost of distribution.

Creating a Distributed Monolith

If your microservices need to be deployed together, share a database, or can't function when one of them is down, you don't have microservices. You have a distributed monolith — which gives you the complexity of microservices with the coupling of a monolith. The worst possible outcome.

True microservices can be deployed independently, own their data, and degrade gracefully when dependencies are unavailable.

The Practical Decision Framework

Use this to decide whether to extract a service:

Extract when:

  • Different parts of the system need to scale independently
  • A team wants to deploy their domain without coordinating with others
  • A module has fundamentally different technology requirements
  • A failure in one area shouldn't cascade to others

Don't extract when:

  • You're doing it for "best practices" without specific pain
  • The module shares significant data or transactions with other modules
  • Your team is smaller than 10 engineers
  • You don't have the operational maturity (monitoring, logging, deployment automation) yet

Prepare first:

  • Centralized logging (ELK, Datadog, or similar)
  • Distributed tracing (OpenTelemetry)
  • Container orchestration (Kubernetes, ECS, or similar)
  • Service mesh or API gateway for traffic management
  • Contract testing between services

A Middle Path: The Modular Monolith

There's an architecture that gives you most of the benefits of microservices without the operational complexity: the modular monolith.

Structure your monolith with strict module boundaries. Each module has its own directory, its own types, its own database schema. Modules communicate through defined interfaces — not by reaching into each other's internals.

This gives you clear ownership boundaries, independent development within modules, and easy refactoring within modules — while keeping the operational simplicity of a single deployment.

And here's the beautiful part: if you build a modular monolith well, extracting a module into a service later is straightforward, because the boundaries are already clean.

The Honest Advice

If you're building something new, start with a modular monolith. Decompose when and where you have evidence that you need to, not where you imagine you might.

If you're running a monolith that's causing real pain, extract incrementally using the strangler fig pattern. One service at a time. Prove each extraction before starting the next.

And if someone tells you monoliths are outdated or unprofessional, ask them to explain the operational cost of managing 40 microservices. The silence is instructive.