Fundamentals· 30 min

Microservices Architecture

Split monolith into independently deployable services

FundamentalsIntermediate30 min read

Split a monolith into small, independently deployable services, each owning one business capability.

Microservices Architecture — diagram
🖱️ Drag nodes · Scroll to zoom · Drag background to pan
/users/orders/payeventWeb AppMobile AppAPI GatewayAuth · Rate limitUser ServiceOrder ServicePayment SvcUser DBOrder DBPay DB

High-level design

API Gateway → [User Service, Order Service, Payment Service, Inventory Service]. Each service has its own DB, deploys independently, communicates via REST/gRPC/events.

Low-level design

Service boundaries: bounded contexts (DDD). Data: database-per-service. Communication: sync (REST/gRPC) vs async (events). Service discovery: DNS, Consul, K8s. Resilience: circuit breaker, bulkhead, timeout.

Trade-offs

  • →Independent deploys vs operational complexity
  • →Per-service DB = clean boundaries but distributed transactions are hard
  • →Smaller services = more flexibility but more network overhead

Used in production by

Netflix, Amazon, Uber, Spotify

Questions to ask your interviewer

  • →What's the team structure? (Conway's Law)
  • →Can we afford the operational overhead vs a modular monolith?
  • →How will we debug cross-service requests?

System design deep dive

Microservices Architecture: deep dive

Microservices split an application into independently deployable services that each own one business capability and its data. They help large organizations move fast in parallel, but they replace in-process function calls with a distributed system, and interviewers want to see you weigh that cost honestly.

How a request travels through microservices, step by step

  1. A mobile app calls POST /orders. The request lands at the API gateway, which validates the JWT and forwards it to the Order service with a trace ID header.
  2. The Order service finds a healthy Inventory instance through service discovery (Kubernetes DNS, Consul, or a mesh) and calls it over gRPC with a 300 ms timeout to reserve stock.
  3. It then calls the Payment service. A circuit breaker wraps this call: if payment has been failing, the breaker is open and the order fails fast instead of piling up threads.
  4. The Order service commits the order in its own database and writes an OrderPlaced event to its outbox.
  5. Shipping, Notification, and Analytics services consume that event asynchronously, each updating its own data store.
  6. Every hop emits spans tagged with the same trace ID, so an engineer can see in a tracing tool exactly where time was spent.

When to use microservices (and when not to)

Microservices pay off when many teams work on one product and keep blocking each other in a shared codebase and release train, when components have very different scaling needs (video encoding versus profile pages), or when parts need different technology stacks. They are usually the wrong starting point for a small team or a new product whose domain boundaries are still shifting, because drawing boundaries in the wrong place produces a distributed monolith: services that must be deployed together and call each other constantly. A well-structured modular monolith with clear internal modules can be split later along the seams that prove stable.

Monolith vs modular monolith vs microservices

DimensionMonolithModular monolithMicroservices
DeploymentOne unitOne unitEach service independently
DataShared databaseShared DB, schemas per moduleDatabase per service
Calls between partsIn-process, nanosecondsIn-process, enforced interfacesNetwork, milliseconds, can fail
TransactionsLocal ACIDLocal ACIDSagas and eventual consistency
Operational costLowLowHigh: CI/CD, observability, discovery per service
Best fitSmall team, early productGrowing team, stable domainMany teams, independent scaling

Patterns you should be able to name

  • Saga: a sequence of local transactions with compensating actions (refund the payment if shipping fails), coordinated by events (choreography) or a central orchestrator.
  • Strangler fig: migrate from a monolith by routing one endpoint at a time to a new service until the old code can be removed.
  • Backend for frontend: a thin aggregation service per client type so mobile and web each get tailored responses.
  • Bulkhead: isolate resources (thread pools, connection pools) per dependency so one slow service cannot exhaust everything.
  • Sidecar / service mesh: move retries, mTLS, and telemetry into a proxy next to each service rather than every codebase.

Common mistakes in interviews

  • Splitting by technical layer (a database service, a validation service) instead of by business capability.
  • Letting several services share one database, which couples their schemas and deployments and defeats the point.
  • Long synchronous call chains: if each of 5 services is 99.9% available, the chain is about 99.5% available, and latencies add up.
  • No plan for distributed tracing, centralized logging, and correlation IDs.
  • Ignoring data consistency across services, or reaching for two-phase commit everywhere instead of sagas.

Interview questions

›How do you decide service boundaries?

Start from business capabilities and Domain-Driven Design bounded contexts: things that change together and share a vocabulary belong together. A good boundary means most features touch one service and the service can own its data outright. If two services are always changed and deployed together, they probably should be one.

›How do you handle a transaction that spans Order, Payment, and Inventory?

Use a saga: each service performs a local transaction and publishes an event, and on failure earlier steps are undone with compensating actions such as releasing reserved stock. The system is eventually consistent, so the UI shows states like pending until the saga completes.

›What is a circuit breaker and why is it needed?

It wraps calls to a dependency and tracks failures. After a threshold, it opens and fails calls immediately for a cool-down period, then lets a few trial calls through (half-open) to test recovery. This prevents cascading failures where threads pile up waiting on a dead service.

›How do services find each other?

Through service discovery: instances register themselves (or are registered by the orchestrator), and callers resolve a logical name to healthy addresses. In Kubernetes this is a Service with a stable DNS name; elsewhere it is Consul or Eureka, often paired with client-side load balancing.

›Sync or async communication between services?

Use synchronous REST or gRPC when the caller needs an answer to continue, and keep those chains short. Use asynchronous events through a message queue for notifications and side effects, which decouples availability and lets consumers be added without changing the producer.

More System Design Concepts

Compare this lesson with the other 13 concepts in the course. Expand any card for its design, trade-offs and diagram.

Category:
Difficulty:
Showing 13 of 13