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.