Art of Codeartofcode
LLeeaarrnniinngg
BBlloogg
TToooollss
GGaammeess AArreennaa
JJoobbss
LLiivvee CCoouurrsseess

Courses

  • System DesignHigh-level system design, one everyday scene at a time: 30 concepts and 15 interview rounds.712 lessons
All coursesTutorialsMy Learning

Categories

  • 01AI & Tools
  • 02Tutorials
  • 03Engineering
  • 04Reviews
  • 05Build Logs
  • 06Tales of Misfits
  • 07Opinion
  • 08Career
  • 09News

Read & write

All storiesEvery post, newest first.Write for usPublish your stories and earn from what you write.Writer leaderboardsWho is writing the most read posts.

Categories

  • PDF Tools25
  • Development09
  • Utilities10
  • Design04
  • Converters04
  • Generators02

Featured

JSON FormatterFormat, validate and minify JSON.Regex TesterTest patterns against live text.Merge PDFCombine PDFs in your browser.JWT DecoderRead a token’s header and claims.Password GeneratorStrong passwords, generated locally.Gradient GeneratorBuild CSS gradients visually.
All 53 tools

Play a round

DevdleOne developer word a day, six guesses.Python RushClassic snake, with coding icons to eat.Blink or BreakA quick test of your reflexes.
Games Arena

Learn with us

Live CoursesInstructor-led courses. Join a waitlist and be first in when one starts.LearningStructured programming courses with AI assistance and certificates.

System Design

27. Monolith vs Microservices

Lesson 15 of 17

27. Monolith vs Microservices

1
The picture to keep in your head
2
27.1 What is a monolith?
3
27.2 What are a monolith's strengths?
4
27.3 What is a modular monolith?
5
27.4 What are microservices?
6
27.5 What is the real cost of microservices?
7
27.6 How do you draw service boundaries?
8
27.7 How does service discovery work?
9
27.8 Sync vs async calls between services: when to use which?
10
27.9 What does "database per service" mean?
11
27.10 When should you split?
12
Memory card and where the analogy breaks
13
Practice: five levels
14
Interview questions
15
Solutions
16
Sources
17
Code lab
27. Monolith vs Microservices

Solutions

3 min read+10 XP

Solutions

Level 1

reveal

Level 2

reveal

Level 3

reveal

Level 4

reveal

Level 5 (sketch)

reveal

No tracks available

Lofi Player

artofcode

artofcode.in

The den of devs.

Stories, tools, games, and gear for developers.

Read the StoriesEExxpplloorree TToooollss
Art of Codeartofcode

Platform

  • BBlloogg
  • TToooollss
  • GGaammeess AArreennaa
  • CCoouurrsseess
  • LLeeaarrnniinngg
  • CCaammppuuss

Company

  • AAbboouutt UUss
  • JJoobbss && IInntteerrnnsshhiippss
  • CCoonnttaacctt

Legal

  • TTeerrmmss ooff SSeerrvviiccee
  • PPrriivvaaccyy PPoolliiccyy
  • RReeffuunndd PPoolliiccyy

Copyright © 2026 artofcode.in, All Rights Reserved.

IST·--:--

1Show the Level 1 solution
2**A.**
31. **Monolith** (ideally modular from the start).
42. **Microservices**, at least for the teams that block each other; probably a modular monolith core plus several services.
53. **Modular monolith**: fix the coupling first; splitting a mess makes it worse.
64. **Split payments out** as its own service.
7
8**B.**
91. **False.** Run many copies behind a load balancer.
102. **False.** They make them harder; you need sagas and outboxes.
113. **True.**
124. **True.** Others use its API or events.
135. **False.** Sync for what's needed now; async for side effects.
1Show the Level 2 solution
21. 0.999⁶ ≈ **99.4%**. Failure: 0.6% of 43,200 minutes ≈ **259 minutes** (about 4.3 hours), versus about 43 minutes for one service at 99.9%.
32. 1 − 0.99¹⁰ ≈ **9.6%**; 1 − 0.99¹⁰⁰ ≈ **63%**.
43. 50 × 3 ms = **150 ms** one by one; batched, about **3 ms**.
54. **20** with the layer split; **11** with the capability split.
65. No timeout: 100 × 5 s = **500** threads needed, more than 200, so the pool is exhausted and checkout stops. With 300 ms: 100 × 0.3 = **30** threads, well within the pool.
1Show the Level 3 solution
2Tested solution (Python 3), the key parts:
3
4```python
5def chain_availability(per_service, services):
6    return per_service ** services
7
8
9def p_any_slow(p_slow_each, fanout):
10    return 1 - (1 - p_slow_each) ** fanout
11
12
13def boundary_violations(imports):
14    return sorted((a, b) for a, b in imports
15                  if a != b and b not in ALLOWED.get(a, set()))
16
17
18def cross_calls(split):
19    return sum(n for (a, b), n in CALLS.items() if split[a] != split[b])
20
21
22class Registry:
23    def healthy(self, service, now):
24        return sorted(a for (s, a), t in self.instances.items()
25                      if s == service and now - t <= self.ttl)
26
27    def pick(self, service, now):
28        live = self.healthy(service, now)
29        if not live:
30            raise LookupError(f"no healthy {service}")
31        i = self.rr.get(service, 0)
32        self.rr[service] = i + 1
33        return live[i % len(live)]
34```
35
36All checks pass: six 99.9% services give 99.4%; fan-out of 10 hits a slow call 9.6% of the time; the boundary checker flags two illegal imports; the capability split crosses 11 calls instead of 20; the registry drops an instance that stopped heartbeating; async email keeps orders flowing; batching turns 50 calls into 1; and without a timeout a 5-second dependency needs 500 threads against a pool of 200. The full file is in `code/ch27_services.py`.
1Show the Level 4 solution
21. **How it spread:** `http.get` has **no timeout**, so each confirmation waits as long as the catalogue takes, **for every item**, one after another. At 100 requests a second and 5 seconds per catalogue call, about **500** requests are in flight at once (Little's law), but the order service has **200** threads. The pool fills, new requests queue, health checks time out, and checkout appears down, even for orders that don't need the catalogue at all. The chapter's code shows the arithmetic.
32. **Fixes:**
4   - **Timeouts** on every call (say 300 ms), within the page's latency budget.
5   - **One batched call** (`GET /books?ids=...`) instead of one per item.
6   - **Store titles in the order** at purchase time, so confirmation doesn't need the catalogue at all.
7   - **A fallback:** if the catalogue fails, show the order with book IDs or cached titles instead of failing.
8   - **A circuit breaker** and a **separate connection pool (bulkhead)** for the catalogue, so its slowness can't eat every thread (Chapter 29).
9
10```python
11def confirm_order(order):
12    titles = {i.book_id: i.title_at_purchase for i in order.items}       # stored with the order
13    missing = [b for b, t in titles.items() if t is None]
14    if missing:
15        try:
16            books = catalog_client.get_many(missing, timeout=0.3)         # one call, bounded
17            titles.update({b["id"]: b["title"] for b in books})
18        except (Timeout, CircuitOpen):
19            pass                                                           # show IDs instead
20    lines = [(titles.get(i.book_id) or f"Book #{i.book_id}", i.qty) for i in order.items]
21    return render_confirmation(order, lines)
22```
1Show the Level 5 (sketch) solution
21. **Target:** a **modular monolith core** (catalogue, cart, orders, users) owned by several teams with enforced modules, plus extracted services: **search** (Elasticsearch, scales independently, Chapter 19), **recommendations** (ML stack, heavy compute), **payments** (isolation, audits), and **notifications** (async, Chapter 33).
32. **Migration:** enforce module boundaries in the monolith; build the platform (per-service CI/CD, tracing, discovery, gateway); extract **notifications** first (async, low risk), then **search** (fed by CDC/outbox events), then **payments** (strangler fig behind a facade, with idempotency keys); measure after each.
43. **Communication:** Kubernetes Services/DNS for discovery; gRPC with timeouts and circuit breakers for synchronous calls (payments, stock); Kafka events (`order_placed`, `book_updated`) through outboxes for everything else.
54. **Data:** each service owns its store (payments: PostgreSQL; search: Elasticsearch; recommendations: its own feature store). Order history stores titles and prices at purchase time, so it needs no cross-service join. **Placing an order** is a saga orchestrated by the orders module: reserve stock, charge payment (idempotency key), confirm, publish `order_placed`; compensations on failure (Chapter 17).
65. **Measures:** deploy frequency per team, lead time from commit to production, change failure rate, incidents per service, p99 latency of checkout, and infrastructure cost. If deploys don't get more frequent and incidents don't drop, the split isn't paying for itself.