Synchronous request/response
The default for internal and external APIs. Caller blocks until response or timeout.
Strengths: Simple mental model, immediate error feedback, easy to reason about in code (response = client.get(url)).
Weaknesses: Cascading failures, thread/goroutine pool exhaustion under load, latency stacks multiplicatively in deep call chains (A→B→C→D).
Senior mitigations (application layer; mesh-level in Deep Cuts):
- Aggressive timeouts (connect vs read vs total)
- Bulkheads — separate connection pools per downstream (Concurrency → Thread Pools for pool sizing intuition)
- Circuit breakers — fail fast when downstream unhealthy (Release It! at the client; mesh policies in Deep Cuts)
- Retries with jitter — only on idempotent ops or with idempotency keys
Asynchronous patterns
Fire-and-forget
Caller sends message, does not wait for processing result. Example: enqueue analytics event. Risk: silent loss unless broker guarantees durability.
Callback / webhook
Async result delivered to caller's endpoint later. Caller must expose a callback URL; callee POSTs when done. Challenges: authentication (HMAC signatures), retries, ordering, duplicate delivery.
Polling
Client repeatedly asks "is it done yet?" Simple but wasteful. Use exponential backoff and ETag/If-None-Match or 304 to reduce payload. Prefer webhooks or SSE when possible.
Long polling
Server holds request open until event or timeout; client immediately reconnects. Bridge technology before WebSockets; still used behind restrictive proxies.
Event-driven integration (preview)
Producer emits facts ("OrderPlaced"); consumers react. Decouples teams and deployment cadence. See Topic 7 for webhooks and Distributed Systems for broker internals.
Key distinction (Fowler):
- Event notification — thin event, consumer fetches details via API
- Event-carried state transfer — fat event with all data; reduces chatter but couples schemas
Sync vs async — when staff engineers push back
| Scenario | Often wrong choice | Better choice |
|---|---|---|
| User waiting on UI | Async + poll every 500ms | Sync with timeout, or SSE/WebSocket |
| Cross-team bulk export | Sync HTTP streaming 500GB | Presigned URL + async job notification |
| Payment confirmation | Fire-and-forget to ledger | Sync with strong consistency or saga (see Distributed Systems → Transactions) |
| Fan-out to 50 services | Sync chain | Event bus + idempotent consumers |
Backpressure (application layer)
When producer outpaces consumer:
- HTTP 429 +
Retry-After— rate limit at API gateway - Bounded queues — drop or shed load with metrics
- Streaming flow control — HTTP/2 windowing (Networking); gRPC per-stream flow control
- Client-side throttling — limit concurrent outbound calls
Concurrency owns semaphores, thread pools, and event-loop mechanics inside a process. Here: apply those ideas at service boundaries — e.g. a gRPC client pool sized to match downstream capacity, not unbounded goroutines per request.
Observability owns measuring queue depth and saturation; here: design APIs that expose retryability (503 + Retry-After) and shed load before OOM.
Timeouts and deadlines
Propagate deadline or timeout budget across call chains (gRPC metadata grpc-timeout, OpenTelemetry trace context). Child calls get remaining budget, not full default timeout — prevents pile-up.
Rule of thumb: if parent timeout is 500ms and B+C each need 300ms serially, design parallel calls or async path.
Further Reading
Hands-On Tasks (Optional)
API design drills and whiteboard exercises — protocol selection, contract design, and bulk-transfer architecture. Assumes Networking and sibling tracks on the hub page (Distributed Systems, Databases, Concurrency, LLD).
- Design timeout and retry policy20m
Service A calls B over HTTP with p99=200ms. Define client timeout, retry count, idempotency requirements, and what happens when B is down for 5 minutes. Sketch a circuit breaker state machine.