Chapter 15 — Monolith vs Microservices

Chapter 15 — Monolith vs Microservices

Hey everyone! Welcome back to Namaste System Design! 🙏

This is the architecture debate everyone has an opinion on — and most opinions are wrong. Junior engineers think "microservices = modern = better." Senior engineers know it's a trade-off, and that starting with microservices is often a costly mistake. Let's give you the mature, interview-winning take.

What we will cover:

  • What a monolith is (one big box)
  • What microservices are (many small boxes)
  • The restaurant-kitchen analogy
  • The trade-offs, one dimension at a time — honestly
  • How services actually talk to each other (sync vs async)
  • The distributed transaction problem & the Saga pattern
  • The hidden costs of microservices
  • Which to start with (the senior answer)
  • When NOT to use microservices
  • Interview Questions

1. The Monolith — One Big Box

A monolith is a single application where all features live in one codebase and deploy together.

   ┌─────────────────────────────────┐
   │        THE MONOLITH             │
   │  ┌────────┐ ┌────────┐          │
   │  │ Users  │ │ Orders │          │   ONE codebase
   │  ├────────┤ ├────────┤          │   ONE deploy
   │  │Payments│ │ Search │          │   ONE database (usually)
   │  └────────┘ └────────┘          │
   └─────────────────────────────────┘
        deploys as a single unit

Every startup should basically start here. It's simple, fast to build, and easy to reason about.


2. Microservices — Many Small Boxes

Microservices split the app into small, independent services, each owning one business capability, its own database, and its own deployment.

   ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐
   │ Users  │  │ Orders │  │Payments│  │ Search │
   │ service│  │ service│  │ service│  │ service│
   │  +DB   │  │  +DB   │  │  +DB   │  │  +DB   │
   └───┬────┘  └───┬────┘  └───┬────┘  └───┬────┘
       └───────────┴─── talk via APIs / queues ───┘

   Each: own codebase, own DB, own deploy, own team.

3. The Restaurant Analogy

   MONOLITH  = one chef 👨‍🍳 who does EVERYTHING — starters,
               mains, desserts, dishes. Simple when small.
               But if that chef is sick, the whole kitchen stops.
               And you can't "hire a dessert specialist" easily.

   MICROSERVICES = separate stations: a starter chef, a grill
               chef, a pastry chef 👨‍🍳👩‍🍳🧑‍🍳. Each is expert &
               independent. Grill station busy? Add another grill
               cook without touching pastry. But now they must
               COORDINATE — and coordination is hard.

4. The Trade-offs, One Dimension at a Time

"Microservices are more complex" is true but vague. Here's exactly where that complexity shows up — and where the monolith pays a different price.

4.1 Simplicity

   MONOLITH:  one repo, one build, one place to search
              for "where is checkout logic?" → grep the repo.

   MICROSERVICES:  the same logic might live in 3 repos, talking
              over the network. "Where is checkout logic?" now
              means tracing calls across services.

👉 Use it when: a small team and a small codebase — a monolith is simply easier to hold in your head.

4.2 Speed to Start

   MONOLITH:  clone repo, npm install, run. Shipping day 1.

   MICROSERVICES:  need service discovery, inter-service auth,
              multiple CI/CD pipelines, multiple databases —
              before you've shipped a single feature.

👉 Use it when: you're validating an idea and speed to market matters more than architectural purity — that's almost every early-stage product.

4.3 Scaling

   Traffic spike hits ONLY the search feature.

   MONOLITH:  must scale the WHOLE app (users, payments,
              everything) just to give search more capacity.
              Wasteful — you're paying to scale code paths
              nobody is hitting harder right now.

   MICROSERVICES:  scale just the search service. Add 5 more
              search instances; payments and users don't move.

👉 Use it when: different parts of your system have wildly different, unpredictable load patterns.

4.4 Deployment

   A bug is found in the payments code, day before a release.

   MONOLITH:  the WHOLE release is blocked — payments, users,
              search, everything ships together or nothing does.

   MICROSERVICES:  only the payments service is held back.
              Orders, search, users ship on schedule, unaffected.

👉 Use it when: teams need to ship independently, multiple times a day, without blocking each other.

4.5 Fault Isolation

   The search feature has a memory leak and crashes.

   MONOLITH:  search runs in the SAME process as everything
              else → crash can take down users, orders,
              payments too. One bug, total outage.

   MICROSERVICES:  only the search service crashes. Users can
              still log in, browse, and pay — search is just
              temporarily broken.

👉 Use it when: you need one weak component to not endanger the whole system.

4.6 Team Autonomy

   MONOLITH:  every team commits to the SAME repo → merge
              conflicts, shared release calendar, "who broke
              the build?" finger-pointing as headcount grows.

   MICROSERVICES:  the payments team owns their service and
              their deploy schedule completely. They don't
              wait on the search team, and vice versa.

👉 Use it when: you have multiple independent teams, not multiple independent engineers on one team.

4.7 Debugging

   A user reports "checkout is slow."

   MONOLITH:  one process, one stack trace, one set of logs.
              Attach a profiler, done.

   MICROSERVICES:  the request touched the gateway, the users
              service, the inventory service, the payments
              service, and the notification service. WHICH one
              was slow? You need distributed tracing (Ch 16)
              just to answer that question.

👉 Use it when: you've already invested in observability tooling — otherwise this is the cost that surprises teams the most.

4.8 Data Consistency

   "Place an order" needs to: create the order AND charge
   the payment AND reserve inventory.

   MONOLITH:  one database → wrap it in ONE transaction.
              Either all three happen, or none do. Easy.

   MICROSERVICES:  order DB, payment DB, and inventory DB are
              THREE separate databases. No single transaction
              can span all three. You inherit distributed
              consistency problems (Chapter 14) — see §6 below.

👉 Use it when: you're prepared to redesign "transactions" as a multi-step process instead of a single atomic operation — this is often the trade-off teams underestimate most.


Visual Comparison

MonolithMicroservices
Simplicity✅ Simple to build, test, deploy❌ Complex — many moving parts
Speed to start✅ Fast (one codebase)❌ Slow (infra, networking, DevOps)
Scaling❌ Scale the WHOLE app even if only search is hot✅ Scale just the busy service
Deployment❌ One bug can block the whole release✅ Deploy services independently
Fault isolation❌ A crash can take everything down✅ One service fails, others survive
Team autonomy❌ Everyone in one codebase (merge pain)✅ Teams own services independently
Debugging✅ One place, easy stack traces❌ Hard — trace across many services (Ch 16)
Data consistency✅ Easy (one DB, transactions)❌ Hard (distributed data, no easy transactions)

5. How Services Actually Talk to Each Other

Split the app into services, and you must now pick how they communicate. Two families:

5.1 Synchronous — call and wait

   Order service ──REST/gRPC call──▶ Payment service
        │                                  │
        │◀─────── "charged ✅" ────────────┘
        │
   Order service now knows the outcome, continues.

   Simple to reason about — feels like a normal function call.
✅ PROS   Simple mental model. Caller gets an immediate answer.
❌ CONS   Caller is BLOCKED waiting. If Payment is slow or down,
          Order is stuck too — failures cascade (Chapter 08:
          circuit breakers exist because of exactly this).

👉 USE IT WHEN  The caller genuinely needs the answer before
                it can continue (e.g. "was the charge approved?").

5.2 Asynchronous — fire an event, move on

   Order service ──publishes──▶ [ "OrderPlaced" event ]
                                       │
                    ┌──────────────────┼──────────────────┐
                    ▼                  ▼                  ▼
              Payment svc        Inventory svc      Notification svc
              (charges card)     (reserves stock)   (sends email)

   Order service does NOT wait for any of them. Each
   subscriber reacts on its own time, independently.
✅ PROS   Caller isn't blocked. New subscribers can be added
          later without touching the Order service at all.
❌ CONS   Order service doesn't immediately know if payment
          succeeded — the result comes back later, asynchronously.
          Harder to reason about "did this actually work?"

👉 USE IT WHEN  The caller doesn't need to wait for the result
                right away, or many services need to react to
                one event (this is the backbone of event-driven
                architectures, often built on Kafka or SQS).

6. The Distributed Transaction Problem & the Saga Pattern

Back to §4.8. "Place an order" really means three separate database writes, in three separate services. What happens when the second one fails?

   Step 1: Order service creates the order        ✅ succeeds
   Step 2: Inventory service reserves stock        ✅ succeeds
   Step 3: Payment service charges the card        ❌ FAILS
                                                    (card declined)

   Now what? The order exists. The stock is reserved. But the
   payment never went through. Without a plan, you've sold an
   item to nobody and paid for nothing. Data is now INCONSISTENT
   across three databases, and there's no single transaction
   that can roll all three back at once.

The fix is the Saga pattern: break the operation into a sequence of local transactions, and define a compensating action for each step — an "undo" — that runs if a later step fails.

   FORWARD PATH:
   Order created ──▶ Stock reserved ──▶ Payment charged ──▶ DONE

   Payment FAILS → run compensations, in reverse:
   Stock reserved ──▶ [ COMPENSATE: release stock ]
   Order created  ──▶ [ COMPENSATE: cancel order ]

   End state: order cancelled, stock released, nobody charged.
   Consistent again — just reached via a series of small,
   reversible steps instead of one big atomic transaction.
✅ PROS   Each step is a normal local transaction — no need for
          a fragile system-wide lock across three databases.
❌ CONS   You must write a compensating action for every step,
          and there's a real window where the system is
          temporarily inconsistent (order exists, not yet
          confirmed) — eventual consistency, applied to workflows.

👉 USE IT WHEN  A single business operation spans multiple
                services and their databases — this is the
                standard answer to "how do transactions work
                in microservices?" in an interview.

7. The Hidden Costs of Microservices

The brochure sells the benefits. Here's what it doesn't tell you:

❌ NETWORK is now everywhere → calls that were function calls
   are now network calls (slow, can fail, need retries).

❌ DISTRIBUTED transactions → covered in §6 above — no free
   lunch, every cross-service operation needs a Saga or similar.

❌ DEBUGGING is painful → one user request touches 10 services.
   Where did it break? (This is why we need distributed tracing.)

❌ OPERATIONAL overhead → dozens of deployments, service
   discovery, monitoring, a whole DevOps/Kubernetes practice.

❌ EVENTUAL CONSISTENCY → separate DBs drift; you inherit all
   the CAP/consistency problems from Chapters 09 & 14.

The famous warning: "Microservices solve an organizational problem (many teams stepping on each other), not a technical one. If you have one small team, they mostly add pain."


8. Which Should You Start With?

┌─────────────────────────────────────────────────────────────┐
│   THE SENIOR ANSWER: "Start with a monolith."               │
│                                                             │
│   → Build a well-structured ("modular") monolith first.     │
│   → Split OUT microservices later, ONLY when a real pain    │
│     appears: a team is too big, or one part needs to scale  │
│     very differently.                                       │
│                                                             │
│   This is the "MonolithFirst" strategy. Amazon, Netflix,    │
│   Shopify all STARTED as monoliths and split later.         │
└─────────────────────────────────────────────────────────────┘

When microservices genuinely make sense: large org with many independent teams; parts of the system with wildly different scaling needs; the need to deploy pieces independently many times a day.


9. When NOT to Use Microservices

The flip side, stated plainly — because "it depends" isn't a satisfying interview answer on its own.

❌ Small team (a handful of engineers) — there's nobody for
   the extra services to give autonomy TO. It's pure overhead.

❌ Unclear or fast-changing domain boundaries — if you don't
   yet know where "orders" ends and "payments" begins, you'll
   draw the service boundaries wrong and pay to redraw them.

❌ Pre-product-market-fit — you need to iterate on the PRODUCT
   fast. Cross-service coordination slows down exactly the kind
   of rapid, sweeping changes an early product needs.

❌ No dedicated infra/DevOps capability — someone has to run
   service discovery, monitoring, CI/CD for N services, and
   on-call for N things that can each fail independently.

❌ Most operations need strong, immediate consistency across
   the whole dataset — if nearly every action is a
   cross-cutting transaction, you'll spend all your time
   fighting distributed consistency instead of building.

👉 Rule of thumb: microservices are a tool for organizational scale, not a badge of technical maturity. If you can't name the team that's in pain today, you probably don't need them yet.


Interview Questions — Quick Fire!

Q: What's the difference between a monolith and microservices?

"A monolith is a single application where all features share one codebase, one deployment, and usually one database. Microservices split the application into small, independent services, each owning a business capability with its own database and deployment, communicating over APIs or message queues. Monoliths are simpler; microservices offer independent scaling and deployment at the cost of significant complexity."

Q: What are the main benefits of microservices?

"Independent scaling — you scale only the busy service; independent deployment — teams ship without coordinating one big release; fault isolation — one service failing doesn't necessarily bring down the rest; and team autonomy — separate teams own separate services and move faster. They shine when you have many teams and components with very different scaling needs."

Q: How do services communicate, and what's the trade-off?

"Synchronously, like REST or gRPC calls, where the caller waits for a direct answer — simple, but the caller is blocked and failures can cascade if the callee is slow or down. Or asynchronously, publishing an event that other services subscribe to and react to independently — the caller isn't blocked and new subscribers can be added without touching the publisher, but you lose the immediate 'did it work?' answer. Most real systems use both: synchronous for calls that truly need an immediate result, asynchronous for everything else."

Q: How do you handle a transaction that spans multiple microservices?

"You can't use a single database transaction across services, so you use the Saga pattern: break the operation into a sequence of local transactions, each in its own service, and define a compensating action for each step that undoes it if a later step fails. For example, placing an order might create the order, reserve stock, then charge payment — if the charge fails, you run compensations in reverse: release the stock, then cancel the order. It trades atomicity for a temporary window of inconsistency that resolves itself."

Q: What are the downsides of microservices?

"They add a lot of complexity: network calls replace function calls and can fail; transactions across services become hard, needing patterns like Saga; debugging a request that spans many services requires distributed tracing; and there's heavy operational overhead — service discovery, many deployments, monitoring, orchestration. You also inherit distributed data consistency problems."

Q: Should a new startup start with microservices?

"Generally no. The mature advice is to start with a well-structured monolith, which is faster to build and easier to operate, and only extract microservices later when a concrete pain emerges — like a team growing too large or a component needing very different scaling. Many big companies started as monoliths and split gradually. Microservices mostly solve an organizational scaling problem, not a technical one."

Q: When should you explicitly avoid microservices?

"When the team is small, when domain boundaries aren't settled yet, when you're still searching for product-market fit and need to move fast across the whole system, when there's no one to own the operational overhead, or when most operations need strong consistency across the whole dataset. In all of those cases, the coordination cost of microservices outweighs any benefit."


Key Points to Remember

ConceptKey Takeaway
MonolithOne codebase/deploy/DB. Simple, fast to start, easy to debug. Scales as a whole.
MicroservicesMany independent services. Independent scale/deploy + fault isolation, but complex.
CommunicationSync (REST/gRPC, blocks, can cascade) vs async (events, non-blocking, delayed result).
Saga patternNo cross-service transactions — chain local transactions + compensating "undo" steps.
Hidden costsNetwork everywhere, distributed transactions, hard debugging, ops overhead.
Start withA modular monolith. Split out services only when a real pain appears.
When NOT toSmall team, unclear boundaries, pre-PMF, no DevOps capacity, need for pervasive strong consistency.
Real reasonMicroservices solve an organizational problem (many teams), not a technical one.

What's Next?

Once you have many services and machines, a scary question appears: "It's broken — but WHERE?" Chapter 16 covers observability — logs, metrics, and traces — the tools that let you see inside a running system.

Keep designing, keep scaling! See you in the next one!