System Design Masterclass · Module 09 / Week 2

API Gateways & Service Discovery

The edge's job description, how services find each other and how that knowledge goes stale, the fat-gateway failure mode, and the honest service-mesh trade-off for a 200-service estate.

~2.5h study~1.5h exercise1 interactive simFast track ~70 min

Fast track — living this daily?

Compresses to ~70 min. Responsibility lists collapse; discovery staleness math, gateway anti-patterns, and the mesh decision stay mandatory.

  1. §01 takeaway · 5 min
  2. §02–§03 discovery + staleness sim, in full · 25 min · MANDATORY
  3. §04 anti-patterns, in full · 15 min · MANDATORY
  4. §05 mesh trade-off, in full · 15 min · MANDATORY
  5. Exercise step 3 · 15 min
STEP 01

What the gateway owns

SKIM
Fast-track takeawayThe gateway owns cross-cutting edge concerns exactly once: TLS termination, authN (token validation — not authZ business rules), coarse rate limiting, routing/canaries, request normalization, edge observability. The test for whether logic belongs there: "is this identical for every route?" Yes → gateway. No → it's business logic wearing a gateway costume (§04). BFFs sit beside it: per-client-type aggregation layers (mobile BFF, TV BFF) that own screen-shaping so neither gateway nor domain services do.
STEP 02

Discovery: who knows where everyone is

MANDATORY
STEP 03

The staleness window

MANDATORY

Every discovery system is a cache of cluster topology, and Module 01's rules apply: it has a TTL, and it lies during the window. When an instance dies, requests keep routing to the corpse until heartbeat timeout + client refresh interval elapse:

Registry staleness · kill an instance

Reality

10.0.1.4 · instance A
10.0.1.5 · instance B
10.0.1.6 · instance C

Client's cached view (heartbeat 5s ×3 + refresh 10s)

10.0.1.4 · instance A
10.0.1.5 · instance B
10.0.1.6 · instance C
Requests round-robin A→B→C. Kill B and watch the client keep sending it traffic through the staleness window.

The three defenses, in the order they save you:

Staff expectationBeing able to state your estate's actual worst-case staleness window as a number — heartbeat interval × miss threshold + client refresh period + DNS/connection-pool TTLs stacked on top — and what percentage of a deploy's traffic that window can misroute. Most teams discover their stack has three stacked caches of topology (registry, client list, connection pool) only during the incident.
STEP 04

Gateway anti-patterns

MANDATORY
STEP 05

Service mesh: the honest trade

MANDATORY

A mesh (Istio/Linkerd-class: Envoy at every pod, sidecar or ambient) moves Module 07/08's client concerns — per-call LB, retries, timeouts, outlier detection, mTLS, telemetry — out of application code into infrastructure, uniformly across every language and legacy service. That uniformity is the entire value proposition, and it's real: one policy change rolls consistent retry budgets or mTLS across 200 services without touching a single pom.xml.

The honest costs: per-pod resource overhead × every pod; +1–2 network hops of latency per call each way; a control plane that is itself a critical distributed system to operate and upgrade; and debugging that now includes "is it the app or the sidecar" on every incident.

The decision ruleCount how many of these you're already solving per-service in libraries: mTLS everywhere, uniform retry/timeout policy, per-call gRPC balancing, consistent traffic-split canaries, uniform golden-signal telemetry. Solving ≥3 of them badly in N languages/framework-versions ≥ the mesh's operating cost → mesh wins. Solving them fine in one shared Spring Boot starter your platform team controls → the starter is your mesh, cheaper. The variable is organizational: heterogeneity and platform-team capacity, not technology.
STEP 06

Exercise

MANDATORY

Fast trackStep 3 (~20 min): measuring your own stacked staleness window converts §03 from concept to number.

1
Build the edge. Spring Cloud Gateway in front of two services: route predicates, a token-validation filter attaching X-User-Id, a Redis-backed rate limiter, and a 90/10 weighted canary route. Verify each with curl.
2
Wire discovery. Eureka (or Consul) + Spring Cloud LoadBalancer, two instances of a service. Watch the gateway's instance list; scale to three and observe propagation delay.
3
Measure the window. kill -9 one instance under steady load. Time from kill → last failed request, with default heartbeat/refresh settings. Then add connect-failure retry-next-instance and repeat: errors → zero, staleness window → latency blip. Record both numbers; compare with your production stack's settings.
4
Paper. Audit your real gateway config: list everything it does, mark each item policy vs domain-logic-in-disguise, and write the eviction plan for the latter. Then apply the §05 decision rule to your estate honestly — count the ≥3 list.

Self-check

Why is an AP registry usually right, given it guarantees serving lies?
A CP registry that refuses reads during partitions makes discovery a single point of total failure — nobody can find anybody, including healthy pairs. Stale lists + client-side defenses (retry-next, outlier ejection) degrade to a few misrouted-then-recovered requests. Discovery data is self-healing and loss-tolerant; the PACELC call (Module 03) is clear.
Where does authorization belong and why not the gateway?
Authentication (who is this) at the gateway — uniform, route-independent. Authorization (may they do this) in domain services — it *is* domain logic: entitlement windows, plan tiers, regional rights. Edge-enforced authZ duplicates domain rules, drifts, and silently fails open on routes added after the rule.
A wedged-but-heartbeating instance: which registration model catches it, and what catches it regardless?
Self-registration can't — the heartbeat thread happily outlives the useful process. Third-party registration keyed on real health (k8s readiness) can. Regardless: passive outlier ejection in callers ejects it on observed errors — reality-based signal beats any registration model, which is why you run both.
Your gateway team wants to add "combine catalog + entitlement into one response for mobile". Ruling?
Not in the gateway — that's aggregation with domain knowledge (what mobile screens need, how entitlement shapes catalog). It belongs in a mobile BFF owned by the mobile-facing team, deployed on their cadence. The gateway routes to the BFF. Same feature, right blast radius.