System Design · Core
Microservices
Split a system along business boundaries: how to cut services, sync vs async links, API gateways, sagas — and when a monolith is the right call.
1. What “microservice” means here
Independently deployable services, each owning its data and a clear business capability (billing, catalog, identity). Not “one Lambda per function” and not “shared DB with many repos.”
2. Finding boundaries
- Start from domain verbs and nouns (DDD-ish): bounded contexts.
- Ask: can this team ship without coordinating a release with another?
- Ask: does this need a different scale/SLA/storage engine?
- Avoid splitting by technical layer alone (all “controllers” vs all “DAOs”).
Prefer a modular monolith first if the domain is unclear. Wrong cuts create distributed monoliths: network calls everywhere, one deploy still couples everyone.
3. Sync vs async between services
| Synchronous (HTTP/gRPC) | Asynchronous (events/queues) | |
|---|---|---|
| When | Need answer now (authorize, price) | Side effects, fan-out, spikes |
| Coupling | Runtime availability coupling | Temporal decoupling |
| Failure | Cascading latency | Lag, eventual consistency |
4. API gateway
- Single public entry; hide internal topology.
- Cross-cutting: auth, rate limits, request IDs, WAF.
- Do not put heavy business logic in the gateway — keep it a facade.
- BFF (backend-for-frontend) variant: per-client gateways (mobile vs web).
5. Data ownership and consistency
- No shared writable database across services.
- Share via APIs or events; duplicate read models if needed (CQRS).
- Cross-service invariants → sagas / workflows, not 2PC by default.
Database-per-service
| Pros | Cons |
|---|---|
| Independent schema evolution and deploys | No cross-service JOINs; need APIs/events |
| Pick the right store per domain (SQL/KV) | Duplicated data / eventual consistency |
| Failure isolation (one DB down ≠ all) | Harder reporting; need data lake / CDC |
| Clear ownership and blast radius | Distributed transactions become sagas |
6. Service discovery
| Client-side discovery | Server-side discovery | |
|---|---|---|
| How | Client queries registry, picks instance, calls directly | Client calls LB/DNS; LB queries registry |
| Pros | Fewer hops; client controls load-balance policy | Simpler clients; central policy |
| Cons | Clients need registry SDK + refresh logic | LB is extra hop / dependency |
| Examples | Eureka + Ribbon-style; Consul client | Kubernetes Service/DNS, AWS ALB/NLB, Envoy |
Modern default in interviews: platform LB or service mesh (server-side / sidecar) so app code stays dumb about peer IPs.
7. Circuit breaker (states in detail)
Protect callers from a flaky dependency. Three states:
- Pair with timeouts — a breaker without timeouts still hangs threads.
- Fallback: cached response, default, or graceful error — say which.
- Scope breakers per dependency (and sometimes per tenant) so one bad API does not trip everything.
8. Saga (recap in microservice context)
Long business transactions become steps with compensations. Orchestrator service or choreographed events. Expose “pending” states in the UX.
9. Operational tax
- Service discovery, mTLS, observability (trace IDs across hops).
- CI/CD per service; contract tests between neighbors.
- Versioned APIs; backward compatible changes.
- On-call multiplies with service count.
10. When NOT to use microservices
| Prefer monolith / modular monolith when |
|---|
| Small team, unclear domain boundaries |
| Strong consistency across many entities in one request |
| You cannot yet afford platform (CI, mesh, tracing) |
| Latency budget cannot pay multiple network hops |
Interview flex: “I would start with a modular monolith, extract services when a module needs independent scale or team ownership.” That reads as mature, not anti-microservice.
11. Strangler pattern
Quick revision
- Cut by business capability and data ownership, not by layers.
- DB-per-service: autonomy vs joins/consistency cost.
- Discovery: client-side vs server-side/mesh — know both.
- Circuit breaker: closed → open → half-open; always with timeouts.
- Sync for immediate needs; async for decoupling and fan-out.
- API gateway thin; sagas for cross-service workflows.
- Monolith first when boundaries or platform are immature.