Study Notes
Notecore

Monoliths & Microservices

Monolith versus microservices isn't a binary — it's a spectrum with a big ball of mud at one end and a fleet of independently deployed services at the other, plus several structured stops in between that let you get the benefits of separation without the operational tax.

35 min read·verified Content review 2026·reviewed 2026-08-30

Assumes you know

After this you should be able to

  • Explain the core idea in Monoliths & Microservices
  • Apply Monoliths & Microservices in a representative design
System Design · Chapter 07

Monolith versus microservices isn't a binary — it's a spectrum with a big ball of mud at one end and a fleet of independently deployed services at the other, plus several structured stops in between that let you get the benefits of separation without the operational tax.

🧭
How to read this

This chapter is one long spectrum. It opens with the two poles — monolith and microservices — and their honest trade-offs, grounded in real companies (Stack Overflow, LinkedIn, Uber, Segment, Istio) that moved in both directions. Then it pivots to a key insight: many teams reach for microservices only to escape a big ball of mud, and there are cheaper structural fixes — layered, modular-monolith, and hexagonal architectures — before you pay the distributed-systems tax. It closes with the communication styles (blocking vs. non-blocking, event-driven) that make those structures work. Anything marked [+] is context I added; everything else is rewritten from the fifteen teaching lessons.

01

The monolith puts everything in one box#

An application is a monolith when its entire code lives in a single codebase — one self-contained, tightly coupled unit. Take a social network like Facebook: user posts, comments, groups, marketplace, ads, photo storage, live streaming, a recommendation engine. In a monolith, every one of those features is coded together in the same repository, tightly coupled, rather than each having its own dedicated service.

The upside is right there: monoliths are simple to build, test, and deploy. There's no overhead of managing infrastructure for many services or wiring up inter-service communication.

single codebase · one deployable Posts Comments Groups Marketplace Ads Photos Streaming Recommend one shared database
Figure 1 — A monolith: every feature compiled and deployed together, backed by one shared database. Original diagram after the course's Illustration 1.33.

Starting monolith, growing into microservices

Teams often start with a monolith intending to break out into microservices later. LinkedIn began as a monolith called "Leo" wired to a handful of databases, then moved to a distributed architecture. Uber started with a single repo holding all its logic — matching drivers and riders, background checks, billing, payments — and hit the classic monolith walls as it grew: scaling pain, tight coupling, redeploying everything for one small change, and the constant risk of a change cascading into unrelated features, forcing heavy regression testing.

💸
"Start monolith, migrate later" is itself a trade-off

Both LinkedIn and Uber transitioned when cloud and microservices weren't yet the norm and traffic was limited. Today, refactoring a tightly coupled monolith into services carries real cost — dismantling and re-implementing features takes serious time and resources. If you already know you have many distinct features and expect rapid growth, picking loosely coupled microservices from the start can be the wiser bet. If requirements are simple, microservices are overkill.

02

When a monolith is actually the right call#

The monolith's single virtue is simplicity — one repo to develop, test, deploy, monitor, and manage. But that single box has real costs:

Cost of the monolithWhat it means
Painful continuous deployment Even a tiny change in one layer or feature forces a redeploy of the entire application.
Heavy regression testing Because everything is tightly coupled, a small change in one feature can break others — so the whole app must be re-tested after each deploy.
Single point of failure A bug in any one feature can bring the entire application down.
Scalability limits You can't scale one feature on its own; any change applies to the whole app, and management gets harder as the code grows.
No heterogeneous tech Mixing languages in one codebase is messy or impossible — combining Java and NodeJS in a single codebase is, optimistically, "tricky."
Often not cloud-ready Legacy monoliths tend to hold state in static variables; a cloud-native app needs to be stateless for consistent behavior.

Pick a monolith when the app is genuinely simple and won't need serious expansion soon — a to-do app, a mostly read-only sports news app, an internal tax-calculation tool.

🏆
There's no formula — and Stack Overflow proves it

There is no rule that use case X demands architecture Y; it's always trade-offs. Stack Overflow started as a monolith and remains one — serving 4 billion requests/month (3,000 req/s peak) and 800M SQL queries/day (8,500/s peak). At that scale, monolith still works for them.

03

Microservices split the app by feature#

In a microservices architecture, each feature of a large service is deployed separately as a small, loosely coupled service, and these work together to form the whole. It's the direct application of two design principles: single responsibility (each service runs one feature) and separation of concerns (each is isolated from the others).

That separation pays off across the board: cleaner maintenance, easier feature development and testing, and independent deployment of each module. Large projects can hand each service to a dedicated team with minimal friction. Scaling is built in — a service that needs more capacity scales independently without touching the others. And critically, each microservice ideally has its own database, which removes shared single points of failure and bottlenecks.

API gateway REST interface Posts svc own DB Chat svc own DB Video svc own DB Photo svc own DB
Figure 2 — Microservices: each feature is its own service with its own database (SQL or NoSQL), reached through an API gateway. Dashed boxes mark service boundaries. Original diagram after Illustration 1.35.
04

When microservices earn their cost#

✅ No single point of failure Loosely coupled — a few services can go down and the app as a whole stays up.
✅ Heterogeneous tech Services talk over a REST API gateway, so you can mix Java, Python, Ruby, NodeJS, and polyglot persistence (SQL + NoSQL together).
✅ Independent, continuous deploys Dedicated teams per service, each scaled and shipped without impacting others.
⚠️ Management complexity A distributed environment needs a node manager (e.g. Apache ZooKeeper), distributed tracing, and often a dedicated team to run it.
⚠️ Weak consistency Strong consistency across services is hard; you usually get eventual consistency because of the distributed design.
🎯 Best for complex, fast-moving apps A social network (messaging, live chat, video, photos, likes/shares) is the canonical fit — features evolve independently with little regression testing.

So there are three ways forward: pick a monolith, pick microservices, or start monolith and later scale out into microservices. Which one depends entirely on the use case — the course's advice is to keep things simple and understand requirements thoroughly before deciding.

05

The trade-offs that actually decide it#

Beyond the pro/con lists, two forces tend to drive the real decision.

Fault isolation

With microservices, a glitch is contained. You fix the issue in the one service without scanning the whole codebase to find and repair it — that containment is fault isolation.

Development team autonomy

In a monolith, once the number of developers and teams on one codebase grows past a point, productivity drops. As the codebase grows, compile time and test runtime grow with it — because the entire codebase recompiles after any change, not just your module. Worse:

  • Another team's change can directly break your feature, so thorough regression testing is required on every push.
  • Everyone may have to stop working while a change goes to production.
  • Your change may need approval from other teams sharing the codebase — a bottleneck.

Microservices flip this: dedicated teams own their codebases outright, with full development and deployment autonomy and separate pipelines. If you need to move fast, launch many features, and scale — microservices are a good bet. But that delight comes with cost: distributed logging, monitoring, inter-service communication, service discovery, alerts, tracing, dedicated build/release pipelines, health checks, and often custom tooling written from scratch. There's no perfect solution — only clarity about your use case.

06

The round trip: Segment and Istio went back#

The most instructive trade-off stories are the companies that reversed course.

Segment: monolith → microservices → monolith

Segment, a customer data platform, ingests hundreds of thousands of events per second and routes them to 100+ server-side destinations (APIs and webhooks) via message queues. The journey:

  1. The monolith. One API ingested events and pushed them to a single distributed queue, which forwarded payloads to destinations. Failed events were retried — so the one queue filled with both new and retried events, eventually flooding and delaying delivery.
  2. The split to microservices. They gave every destination its own microservice with its own queue. A flooded queue no longer hurt other destinations — clean fault isolation, better throughput.
  3. The complexity wall. As destinations grew, each service+queue had different throughput and load patterns; no single scaling policy fit. Autoscaling still needed manual per-service CPU/memory tuning. More queues meant more maintenance resources.
  4. Back to a monolith. They reverted, combining all the per-destination queues into a single service called Centrifuge, giving up fault isolation to reclaim manageability.
1 · Monolith events flooded dest dest dest 2 · Microservices events q svc q svc q svc q svc isolation, but N queues to tune 3 · Centrifuge events Centrifuge one service queues recombined
Figure 3 — Segment's round trip: one flooded queue → per-destination isolation that grew unmanageable → recombined into the Centrifuge monolith. Original diagram after Illustrations 1.36–1.37.

Istio

Istio — the open-source service mesh for connecting, securing, controlling, and observing microservices — itself moved from microservices back to a monolith, saying it let them deliver value and hit their goals. It's cited as an example of when not to do microservices.

07

The big ball of mud — the thing everyone's fleeing#

The big ball of mud (aka spaghetti code) is code with no definite structure — tightly coupled, hard to maintain, hard to refactor. It's a software-design anti-pattern that shows up when delivery speed is prioritized over code quality, piling up massive technical debt.

Picture an online food-delivery service: taking orders, routing them to restaurants and drivers, payments, memberships, inventory. In spaghetti form, all of it is coded together, tightly coupled — no layers (controller, service, DAO), no SOLID principles, no interfaces, no modular design. Adding a feature means first reverse-engineering the code flow, then changing it and hoping nothing breaks — an endless cycle of bugs spawning bugs, and eventually financial loss.

Big ball of mud orders pay routing members inv Structured orders payment routing membership inventory
Figure 4 — The big ball of mud (tangled, tightly coupled) versus a structured decomposition. Original diagram after Illustration 1.38.

How code turns to mud

The causes are cultural as much as technical: no time invested in design early on; inexperienced developers with little design knowledge; many devs over years each coding in their own style with no standard structure (this is where frameworks like Spring help enforce consistency); always choosing delivery speed over quality; no team code reviews led by seasoned devs (if it worked, it got merged); and no regular refactoring, quality checks, or test coverage.

The fix the course prescribes: don't stuff everything in one layer — use a layered architecture.

08

Layered architecture: give each job a floor#

Layered architecture (the "lasagna code") splits code into layers — typically controller, service, and data-access — each with one role, communicating through interfaces to stay loosely coupled. The controller handles requests, the service layer runs business logic, the DAO talks to the database. Each layer does its job without worrying about the others; it's the separation of concerns principle in practice. A change in one layer barely touches the rest, which keeps the code maintainable, testable, and extendable. In Java Spring enterprise projects, this is the default pattern.

Controller Service DAO orders orders orders routing routing routing payment payment payment inventory inventory inventory request ↓ · response ↑
Figure 5 — Business features run as vertical slices across the controller/service/DAO layers. A request flows down one feature's column and the response back up. Original diagram after Illustrations 1.39–1.40.

But layers aren't full isolation. Business features cross-communicate, and in complex projects that cross-talk can turn the code back into a big ball of mud. The escape hatches: move to microservices, or implement a modular monolith. The course also lists non-structural safeguards — seasoned-dev code reviews as a rule, educating juniors on design, practicing domain-driven development, and fixing root causes instead of duct-taping bugs.

09

The modular monolith: the middle ground#

A modular monolith is a monolith organized into loosely coupled modules split by business feature, each with its own layers, rather than everything coded together. It sits squarely between the monolith and microservices. If your only reason to adopt microservices is to escape the big ball of mud, this gets you most of the benefit without the distributed-systems overhead.

Modules are drawn along business functionality — inventory in one module, orders in another, payments in another — and they can even connect to different databases rather than sharing the single one a classic monolith uses. The payoff mirrors microservices: code reusability, easier maintenance and extension, better comprehension, observability, lower complexity, and easier testing.

🔗
The one thing it can't do

The modules are separated logically, not physically — so unlike microservices, they cannot be deployed independently. If you need physical separation and independent deployment, you must move to microservices.

Modularity and domain-driven design

The modular monolith is an implementation of domain-driven design (DDD) — structuring code to align with the business domain. In enterprise projects, domain experts and technical staff model the system together, defining bounded contexts (logical boundaries in a domain with consistent rules — e.g. inventory management is one bounded context, user management another) and converting those contexts into modules. Once built, modules have dependencies on each other — inventory talks to orders, orders talk to payment — which raises the question of how they communicate.

10

How modules talk: sync or async#

Modules in a modular monolith communicate one of two ways.

Synchronous — through interfaces

A module calls another only through the interface that module exposes, with no knowledge of its implementation. The interface is the entry point, so a module can swap its implementation anytime without breaking callers — loosely coupled by design. (This is also how hexagonal architecture, coming next, does synchronous communication.)

Asynchronous — through an event bus

Async communication is event-based, via an in-memory event bus or a message broker like Kafka. A module publishes events to the bus; other modules subscribed to it read those events. The bus is a central collection point for events across the app, letting modules interact with no direct coupling between them. Guava's EventBus is one example. This is the publish-subscribe pattern.

Synchronous · interfaces module A module B interface Asynchronous · event bus event bus / Kafka mod 1 mod 2 mod 3 publish ↑↓ subscribe · no direct coupling
Figure 6 — Synchronous (direct call through an interface) vs. asynchronous (publish/subscribe through an event bus or broker). Original diagram after Illustrations 1.42–1.43.
11

Hexagonal architecture: business logic at the core#

Hexagonal architecture — aka Ports and Adapters — keeps business logic isolated at the center and pushes all input/output to the edges. It's one way to implement a modular monolith's synchronous communication. Three components:

  • Domain — the business logic, sitting at the core; external entities never touch it directly.
  • Ports — interfaces/APIs; all input enters through a port.
  • Adapters — the implementations of those ports; they convert data from a port into a form the business logic can process.

The hexagon shape means nothing — it's just a visual convention. The name comes by analogy to computer ports (the interface to external devices) and adapters (which convert the port's signals for the chips inside).

Application layer Mobile app REST controller Domain business logic inbound ports+adapters outbound ports+adapters Infrastructure database messaging
Figure 7 — Ports & adapters. Inbound ports carry requests from the application layer into the domain; outbound ports carry the domain's calls out to infrastructure. Original diagram after Illustrations 1.44–1.46.

Why it beats spreading logic across layers

In plain layered architecture, business logic tends to leak across layers — even into the UI and database stored procedures — making things tightly coupled and hard to test or refactor. Hexagonal keeps all business logic at the core, so a change to it doesn't force refactoring every layer. It still has controller/service/DAO pieces, but organizes them under three high-level layers: application (UI, REST controllers), domain (the model plus the port interfaces), and infrastructure (database, messaging server, and the adapters implementing the ports). Ports/adapters handling incoming traffic are inbound; those handling outgoing traffic to infrastructure are outbound.

Upsides: business logic can be unit-tested in isolation (no need for integration tests or cloud deploys just to test logic — integration tests are better spent on the surrounding layers), and the code stays loosely coupled, extendable, and therefore scalable.

12

Blocking vs. non-blocking — the async foundation#

The asynchronous, event-based module communication above rests on a lower-level distinction. Modern frameworks like NodeJS, Play, Tornado, and Akka gained popularity over traditional tech (Java, PHP) for real reasons — starting here.

Blocking (synchronous)Non-blocking (asynchronous)
Behavior Execution pauses at a call and waits for it to return before moving on.Execution doesn't wait — it moves to the next line immediately, whether or not the call has returned.
Trade-off Predictable; the default in typical CRUD apps.Slightly less consistent (the called function might error or return nothing, yet the next line still runs), but frees the flow.
Best for Straightforward request/response work.IO-intensive operations — disk, network, hardware — i.e. lots of events.
🧩
Events, defined

An event is anything from a tweet to a button click, an HTTP request, an ingested message, or a variable change. When events happen very frequently, that's a stream. In web apps, "IO" essentially means events — many IO operations means many events over time.

13

Event-driven architecture: react to the stream#

Event-driven architecture (also called reactive or non-blocking) is built to process asynchronous streams of events — the system continually monitors the stream and reacts. The application becomes inherently asynchronous.

At the service level

Non-blocking technologies — NodeJS, and Java-ecosystem frameworks like Play, Akka, and Spring Reactor — let a service handle a large number of concurrent connections with minimal resources. They're built to run on a cluster and handle large-scale concurrency without the developer wrestling with threads, locks, or out-of-memory issues from high IO. Doing reactive work with traditional thread-based frameworks is painful precisely because of shared mutable state and locks. NodeJS is single-threaded and non-blocking with an event-loop architecture, ideal for IO-intensive work — but a poor fit for CPU-intensive tasks like graphics rendering or ML. Every tech has its use case; non-blocking doesn't make traditional tech obsolete.

At the system level

For streams across many services — changing stock prices, e-commerce transaction events, changing game states — an external message broker (like Kafka) connects the services and routes events between them. In the vocabulary of event-driven systems: modules that publish events are publishers/producers, modules that consume them are consumers, and the broker in the middle is the event router. If you split modules into microservices, users become the event producers and the microservices become the event consumers, with all events flowing through the router.

user user user producers Message broker event router · Kafka order svc payment svc membership svc consumers
Figure 8 — System-level event-driven architecture: producers → event router (broker) → consumer services. Original diagram after Illustrations 1.48–1.49.

Why it's worth it

Loose coupling. Say a customer joins a group when they place an order. If services were tightly coupled and the membership service were momentarily down, the whole order flow would fail. With event-driven design, the broker persists the membership events while that service is down; when it recovers, it pulls and processes them — so the business doesn't lose the customer. Parallel processing. Because communication is async through queues, services run their work in parallel without waiting on each other, and if one goes down mid-task, its events sit safely in the queue without affecting the rest.

14

Recap#

Monolith One codebase, tightly coupled. Simple to ship; but SPOF, hard to scale per-feature, painful redeploys. Great for simple apps (Stack Overflow scaled one).
Microservices Feature-split, loosely coupled, own databases, independent deploys, polyglot tech. Cost: distributed-systems complexity and eventual consistency.
The decision is a trade-off Fault isolation and team autonomy pull toward microservices; management overhead pulls back. Segment and Istio both reversed course.
Big ball of mud Structureless spaghetti from prioritizing speed over quality. The thing most teams are really fleeing.
Layered → modular → hexagonal Structural fixes that give separation without the distributed tax. Modular monolith is the middle ground; hexagonal isolates business logic at the core.
Event-driven glue Non-blocking + a broker (Kafka) let modules/services publish and subscribe, decoupled and parallel, surviving each other's outages.
💡
The single idea underneath it all

Coupling is the enemy, but distribution is a tax. Every architecture here is a different answer to "how loosely coupled do I need to be, and how much operational cost will I pay for it?" You can buy decoupling with structure inside one process (layered, modular, hexagonal) long before you buy it with a fleet of services — and the companies that forgot this, like Segment and Istio, paid the tax and then asked for a refund.

15

Further reading#

The lessons cite several external links. Below are the ones that resolve live today, grouped by topic. Several other cited links — a LinkedIn scaling article, an Uber scaling write-up, a Node.js event-loop guide, and a Stack Exchange performance page — returned errors or 404s and are omitted.

Real-world monolith ↔ microservices journeys

[1]
Scaling the Stack Overflow Monolithic App by Obsessing Over Performance — InfoQ Why read it: backs Section 02's claim that a monolith can scale enormously — how Stack Overflow stays a monolith at billions of requests.
[2]
Goodbye Microservices: From 100s of problem children to 1 superstar — Segment Why read it: the primary source for Section 06's Segment story and the decision to revert to a monolith.
[3]
Centrifuge: a reliable system for delivering billions of events per day — Segment Why read it: the architecture Segment landed on after the round trip — the Centrifuge design in depth.

Structure & communication patterns

[4]
Domain-driven design — Wikipedia Why read it: expands Section 09's bounded contexts and domain modeling that underpin the modular monolith.
[5]
Guava EventBus Explained — GitHub Why read it: the concrete in-memory event bus named in Section 10's publish-subscribe communication.

Rewritten in full from the Monolith and Microservices section of Web Application & Software Architecture 101 — merging its fifteen teaching lessons (monolithic architecture and when to pick it; microservice architecture and when to pick it; trade-offs Parts 1–2; the big ball of mud; layered architecture; modular monolithic architecture Parts 1–2; hexagonal architecture; event-driven architecture Parts 1–3; and the chapter summary). The end-of-section quiz was skipped. All eight diagrams are original SVGs built from the lessons' descriptions of their illustrations (1.33–1.49), not reproductions of the source images. [+] marks context added beyond the source. External links were verified live on Aug 13, 2026; dead links (LinkedIn scaling, Uber scaling, Node.js event-loop guide, Stack Exchange performance) were dropped.