Oh My Learning

The Monolithic Hell and the Microservice Architecture

microservices-patterns/01 mid drafted · 13 min read

Microservices Patterns (Chris Richardson), Chapter 1

The Monolithic Hell and the Microservice Architecture#

TL;DR#

A monolith starts as the right choice - one codebase, one deployment, easy to build - but past a certain size and team count it enters "monolithic hell": every change is slow and scary, the build takes forever, one bug can crash everything, and you are locked into an aging tech stack. Microservices attack this by splitting the system into small, independently deployable services organized around business capabilities, trading the monolith's simplicity for team autonomy and deployability. The catch is that microservices are not "better" - they buy independence at the price of distributed-systems complexity, so they pay off only when the pain of the monolith outweighs that cost.

The idea#

Almost every successful system begins as a monolith: a single deployable unit (one WAR, one binary, one process) containing all the code. This is the correct starting point. It is simple to develop in one IDE, simple to test, simple to deploy (ship one artifact), and simple to scale (run more copies behind a load balancer). For a small app and a small team, nothing beats it.

The problem is success. As the application grows to millions of lines and dozens of developers across many teams, the monolith's virtues curdle into what Richardson calls monolithic hell:

  • Slow, fearful changes. The codebase is too big for anyone to fully understand, so changes have unpredictable ripple effects. Every commit risks breaking a distant part of the system.
  • Glacial builds and test suites. A large monolith takes many minutes (or hours) to build and test, throttling how often anyone can iterate.
  • All-or-nothing deployment. To ship a one-line change to the billing code, you redeploy the entire application - and coordinate with every other team touching the same artifact. Deployment becomes a rare, high-ceremony event.
  • A single failure domain. A memory leak or an unhandled exception in one module can take down the whole process - reporting can crash checkout.
  • Scaling is coarse. You cannot scale just the CPU-hungry image-processing part; you scale entire copies of everything, wasting resources.
  • Locked-in technology. The whole thing is one language and one framework version. Adopting a new language, or even upgrading the framework, is a monumental, all-at-once migration - so you stay stuck on aging technology.

Microservices are the architectural response: decompose the application into a set of small, independently deployable services, each owning a slice of the business (orders, payments, inventory), each with its own codebase, database, and deployment pipeline, communicating over the network. The central promise is independence: a team can build, test, deploy, and scale its service without coordinating a lockstep release with everyone else.

But the honest framing - and the whole spirit of this book - is that microservices are a trade-off, not an upgrade. You gain deployability and team autonomy; you pay with the hard problems of distributed systems (network calls fail, data is split across databases, transactions span services, testing is harder). The rest of this subject is the catalog of patterns for paying that price competently.

How it works#

Defining "microservice": independently deployable, loosely coupled, capability-aligned#

There is no rule that a service must be under N lines. The load-bearing properties are:

  1. Independently deployable. You can deploy a new version of one service without redeploying any other. This is the single most important property - it is what delivers the autonomy benefit, and everything else (separate database, separate pipeline) exists to protect it.
  2. Loosely coupled. Services interact only through stable, published interfaces (APIs, messages) - never by reaching into each other's database or internals. Coupling through a shared database is the classic anti-pattern that destroys independence.
  3. Organized around business capabilities. A service owns a coherent business function (Order Management, Delivery, Accounting), not a technical layer (there is no "the database service" or "the UI service" tier).
  4. Owns its own data. Each service has a private database; no other service reads or writes it directly. This is what makes independent deployment and schema evolution possible (and is also the source of the distributed-data pain that sagas and CQRS address in later lessons).

The scale cube: three independent ways to scale#

Richardson borrows the scale cube (from The Art of Scalability) to place microservices precisely. Scaling has three axes:

        Z axis: data partitioning (shard by customer/key)
        ^
        |
        |
        +--------> X axis: horizontal duplication (clone the whole app behind an LB)
       /
      /
     v
   Y axis: functional decomposition (split by function -> microservices)
  • X-axis - run N identical copies of the whole app behind a load balancer. This is what a monolith already does; it scales throughput but not complexity, and every copy still carries the entire codebase.
  • Z-axis - partition by data (shard): each instance handles a subset of customers. Scales data volume, still runs the whole app.
  • Y-axis - split by function: break the app into services by business capability. This is the microservice axis, and it is the only one that also tackles development-time scaling (many teams working in parallel) - not just runtime load.

The insight: X and Z scale traffic and data, but only Y scales the organization and the codebase. Microservices are fundamentally about the Y axis - and you can still apply X and Z within each service.

Microservices as a solution to a people problem, not just a tech problem#

The deepest reason to reach for microservices is often organizational. A monolith forces every team to share one codebase and one release train, so teams block each other: your feature waits for their bug fix to clear the shared deploy. By giving each team its own service - its own repo, pipeline, and deploy cadence - you let teams move independently. This ties directly to Conway's law (systems mirror the communication structure of the org that builds them): microservices are, in large part, a way to align service boundaries with team boundaries so teams can own their piece end to end.

This is why "should we use microservices?" is rarely a purely technical question. With one small team, the coordination cost the monolith imposes is near zero, so microservices add complexity for no benefit. With twenty teams, the coordination cost of a shared monolith is enormous, and the independence microservices buy is worth its distributed-systems tax.

The price: distributed-systems complexity (what the rest of the book is about)#

Splitting into services introduces problems the monolith never had, and naming them now frames the whole subject:

  • The network is now in your call path. In-process method calls become remote calls that are slow and can fail partially (lesson 03, IPC).
  • Data is scattered. With a database per service, a business operation that updates two services cannot use a single ACID transaction - you need sagas (lesson 04). Querying data that lives across services needs API composition or CQRS (lesson 07).
  • The business logic must be designed for this using aggregates and domain events (lesson 05), sometimes event sourcing (lesson 06).
  • Testing, deployment, and operations get harder - contract testing (lesson 09), production-readiness (lesson 10), deployment patterns (lesson 11).

Microservices do not delete complexity; they relocate it from "one big tangled codebase" to "many small services plus the space between them." The patterns exist to manage that between-space.

Worked example 1: FTGO tips into monolithic hell#

FTGO ("Food to Go") is the book's running example - a food-delivery app. It began as a tidy monolith: Order, Restaurant, Delivery, Billing, Consumer modules in one Java WAR.

  1. Year 1: 5 developers, one module each. Builds take 2 minutes; they deploy weekly. Life is good.
  2. Year 3: 60 developers across 8 teams; 2 million lines. The build takes 40 minutes; the test suite is flaky and takes hours.
  3. A one-line fix to Billing requires redeploying the whole WAR, which means a release-coordination meeting with all 8 teams, because everyone's unmerged work ships together.
  4. During peak dinner traffic, a memory leak in the reporting module exhausts the heap and crashes the process - taking down order taking with it. Reporting and checkout share one failure domain.
  5. The team wants to adopt a faster runtime for the latency-critical order path, but cannot - it is all one Java version. They are locked in.

Every symptom here is a monolithic-hell symptom, and none is about the business logic being wrong - it is about size plus team count overwhelming a single-deploy architecture.

Worked example 2: the same system on the Y axis#

Now FTGO decomposes along the Y axis into services aligned to capabilities: Order Service, Kitchen Service, Delivery Service, Accounting Service, Consumer Service, each with its own database and pipeline.

  1. The Accounting team fixes a billing bug and deploys Accounting Service alone at 2pm - no other team involved, no shared release train.
  2. The reporting workload is isolated in its own service; when it leaks memory, only reporting degrades - order taking keeps running.
  3. The Order team rewrites the latency-critical path in a faster stack, deployed as just the Order Service, while everything else stays as-is. Technology is adopted incrementally.
  4. Under dinner load, they scale only the Order and Kitchen services (X-axis within those services), leaving Accounting at one instance - fine-grained, cheaper scaling.

The independence is the payoff. But note the new costs that now appear: placing an order must coordinate Order, Kitchen, and Accounting across three databases (a saga), and "show my order with its delivery status" must gather data from multiple services (composition/CQRS). Those costs are the subject of the following lessons.

Worked example 3: when microservices would be the wrong call#

A two-person startup builds an MVP for a niche scheduling tool.

  1. They split it into 9 microservices "to be scalable," following blog-post fashion.
  2. Now every feature that touches scheduling and notifications requires changing two services, versioning an API between them, and running a message broker locally.
  3. They spend weeks building deployment pipelines, a service registry, and distributed tracing - for an app with 200 users.
  4. A simple monolith would have shipped the MVP in a fraction of the time; the distributed complexity is pure overhead with no team-autonomy or scale benefit to offset it.

This is the counter-case that keeps the pattern honest: with a tiny team and modest scale, the monolith's coordination cost is ~0, so microservices' distributed tax has nothing to pay for. Start monolithic; decompose when the pain arrives (and lesson 12 is about doing exactly that migration).

Pros#

  • Independent deployability: each team ships its service on its own cadence, without a coordinated big-bang release - the core benefit.
  • Team autonomy and parallel development: service boundaries aligned to team boundaries let many teams work without blocking each other (the Y axis / Conway's law payoff).
  • Fault isolation: a failure in one service need not crash the others; the blast radius shrinks from "the whole app" to "one service."
  • Independent, fine-grained scaling: scale only the hot services, and choose each service's own scaling axis.
  • Technology flexibility: adopt new languages/frameworks per service and evolve them incrementally instead of one monumental migration.

Cons#

  • Distributed-systems complexity: network calls fail partially, latency enters the call path, and debugging spans many services - problems the monolith never had.
  • Distributed data management: a database per service means no cross-service ACID transactions (need sagas) and hard cross-service queries (need composition/CQRS).
  • Operational overhead: many services demand orchestration, service discovery, centralized observability, and mature CI/CD before it works at all.
  • Harder testing: verifying interactions across services is more complex than testing one process (drives contract testing).
  • Not free and not always worth it: for small teams/low scale, the tax exceeds the benefit.

Alternatives#

  • Monolith (single deployable): the right default - simplest to build, test, deploy, and reason about; only becomes a liability at large size/team count.
  • Modular monolith: one deployable, but with strictly enforced internal module boundaries (and even module-owned schemas). Captures much of the code-organization benefit without the distributed tax - often the best middle ground and a natural pre-microservice step.
  • Service-based / coarse-grained services: a handful of larger services (not fine-grained microservices) sharing some infrastructure - less autonomy, less overhead.
  • Serverless functions: decompose to an even finer, event-driven granularity for suitable workloads - trades long-lived services for managed, scale-to-zero functions.

When to use it#

  • A large application with many teams where a shared monolith imposes heavy coordination cost and slow, fearful releases.
  • You need independent deployability and want service boundaries aligned to team boundaries (Conway's law).
  • Different parts have genuinely different scaling or technology needs that a single monolith cannot serve well.
  • You have (or will build) the operational maturity - CI/CD, containers/orchestration, observability - that microservices require.

When NOT to use it#

  • Small team, early-stage product, or modest scale - start with a monolith (or modular monolith); the distributed tax has nothing to pay for.
  • You lack the deployment/observability maturity to run many services - you will drown in operational overhead.
  • The domain is not yet well understood - premature service boundaries are expensive to move; wait until the seams are clear (or extract them from a monolith later, lesson 12).
  • The problem is genuinely simple - a monolith will ship faster and be easier to operate.

Key takeaways / mental model#

Think of a monolith as one big house where everyone shares one front door and one fuse box: fine for a small family, but with twenty tenants, every renovation needs everyone's sign-off and one blown fuse darkens the whole building. Microservices give each tenant their own unit with its own door and fuse box - independence - but now there are hallways, utilities, and building management to run (the distributed complexity). Two rules of thumb:

  1. Microservices are the Y axis of scaling - they scale the organization and codebase, not just traffic. Their headline benefit is independent deployability and team autonomy; if you do not have a team-coordination or independent-evolution problem, you probably do not need them.
  2. It is a trade-off, not an upgrade. You exchange the monolith's simplicity for autonomy by taking on distributed-systems complexity. Start monolithic, and adopt microservices only when the monolith's pain outweighs that tax - then the rest of this catalog is how you pay it competently.

Self-check questions#

  1. List four concrete symptoms of "monolithic hell" and, for each, explain why it is caused by size plus team count rather than by bad code.
  2. What are the three axes of the scale cube, and why is the Y axis (functional decomposition) the one that microservices are really about? What can X and Z still do inside a service?
  3. Which single property is the most important definition of a microservice, and why do "database per service" and "separate pipeline" exist to protect it?
  4. Explain how Conway's law reframes microservices as an answer to a people problem, not just a technical one. When does that answer stop being worth it?
  5. Name three distinct new problems microservices introduce that a monolith never had, and match each to the later pattern that addresses it.
  6. A 4-person team building an MVP asks whether to start with microservices "to be future-proof." What do you advise, and what would have to change for the answer to flip?

References#