🔧 Level 6 — Microservices Architecture
Breaking monoliths apart — and the trade-offs that come with it.
Microservices architecture splits a large application into small, independent services. Each service owns one business capability, runs in its own process, and can be deployed, scaled, and maintained on its own.
Think of it like a restaurant kitchen: instead of one chef doing everything (monolith), you have specialists — grill, pastry, drinks — each with their own station and tools, coordinated by a head chef (API Gateway).
📚 Table of Contents
- What Are Microservices?
- Monolith vs Microservices
- Anatomy of a Microservices System
- API Gateway
- How Services Communicate
- Real-World Example: E-Commerce Order Flow
- Service Discovery
- Circuit Breaker Pattern
- Data Management — Database per Service
- Service Mesh (Istio / Linkerd)
- When to Use (and When Not To)
- Trade-offs — The Honest Picture
- Checklist Before Moving On
6.1 What Are Microservices?
A monolith is one big deployable unit:
- One codebase
- One database (usually)
- One deployment
A microservices system is many small deployable units:
- One service per business domain (Auth, Orders, Payments, etc.)
- Database per service — each team owns its data
- Independent deployments and scaling
TIP
Rule of thumb: Start with a monolith. Move to microservices when team size, scale, or deployment pain justify the operational cost. Interviewers love hearing this — it shows maturity, not hype.
6.2 Monolith vs Microservices
Architecture Comparison
| Monolith | Microservices | |
|---|---|---|
| Simplicity | ✅ Simple | ❌ Complex |
| Scaling | ❌ Scale all | ✅ Scale specific services |
| Deployment | ✅ One deploy | ❌ Many deploys |
| Tech diversity | ❌ One stack | ✅ Best tool per service |
| Failure isolation | ❌ One bug = all down | ✅ Isolated failures |
| Team ownership | ❌ Shared codebase | ✅ Clear ownership |
The Trade-off in One Line
Monolith: Simple to develop, test, and deploy at small scale.
Painful at large scale (tight coupling, team bottlenecks).
Microservices: Complex to operate (networking, discovery, observability).
Essential at FAANG scale (hundreds of services, thousands of engineers).6.3 Anatomy of a Microservices System
Here is the full picture of how the pieces fit together. Clients never call microservices directly — they go through the API Gateway.
Key Building Blocks
| Component | Role |
|---|---|
| API Gateway | Single entry point; handles auth, rate limiting, routing |
| Service Discovery | Services find each other dynamically (Consul, Eureka, K8s DNS) |
| Message Queue | Async communication (Kafka, RabbitMQ, SQS) |
| Circuit Breaker | Stops cascading failures when a service is down |
| Service Mesh | Handles networking (mTLS, retries, tracing) via sidecar proxies |
| Database per Service | Each service owns its data — no shared tables across services |
6.4 API Gateway
The API Gateway is the single entry point for all client requests. It handles cross-cutting concerns so individual services stay focused on business logic.
What an API Gateway Does
Request Flow
API Gateway vs Load Balancer
| Feature | Load Balancer | API Gateway |
|---|---|---|
| Purpose | Distribute traffic across instances | Smart routing + cross-cutting concerns |
| Auth | ❌ No | ✅ Yes |
| Rate limiting | ❌ No | ✅ Yes |
| Protocol translation | ❌ No | ✅ Yes (REST ↔ gRPC) |
| Routing | By IP/port | By URL path, method, header |
Gateway Routing (JavaScript Example)
Using express and http-proxy-middleware:
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const app = express();
// Log every request
app.use((req, res, next) => {
console.log(`[GATEWAY] ${req.method} ${req.path}`);
next();
});
// Route paths to the right microservice
app.use("/auth", createProxyMiddleware({ target: "http://auth-service:3001" }));
app.use(
"/orders",
createProxyMiddleware({ target: "http://order-service:3002" })
);
app.use(
"/payments",
createProxyMiddleware({ target: "http://payment-service:3003" })
);
app.listen(8080);6.5 How Services Communicate
Services talk to each other in two main ways: synchronously (wait for a response) or asynchronously (fire and forget).
Communication Styles
Synchronous — REST vs gRPC
| REST + JSON | gRPC + Protobuf | |
|---|---|---|
| Best for | Public APIs, browsers | Internal service-to-service |
| Speed | Good | Faster (binary, HTTP/2) |
| Debugging | Easy (human-readable) | Harder (needs tooling) |
| Contract | Implicit | Strict .proto schema |
INFO
For a deep dive on when to pick gRPC over REST, see gRPC — When & Why to Use It.
Asynchronous — Events & Message Queues
The caller publishes an event and moves on. Other services react when ready.
Good for: notifications, analytics, search indexing — anything that does not need an immediate response.
Trade-off: You get eventual consistency instead of instant consistency. That is usually fine for emails and dashboards, but not for payment confirmation.
TIP
Level 5 covers messaging in depth: Messaging & Event-Driven Architecture.
6.6 Real-World Example: E-Commerce Order Flow
Imagine a marketplace like Amazon, Shopify, or Daraz. Here is how microservices handle "User places an order" — a flow everyone can relate to.
Service Breakdown
| Service | Responsibility | Database |
|---|---|---|
| Product | Catalog, search | PostgreSQL + Elasticsearch |
| User | Accounts, addresses | PostgreSQL |
| Order | Order lifecycle | PostgreSQL |
| Inventory | Stock levels | PostgreSQL + Redis |
| Payment | Card, wallet, refunds | PostgreSQL |
| Notification | Email, SMS, push | None (stateless) |
High-Level Architecture
Step-by-Step: Placing an Order
Example API Request
POST /api/v1/orders
Authorization: Bearer <jwt>{
"items": [{ "productId": "P-101", "quantity": 2, "price": 1200 }],
"shippingAddressId": "ADDR-55",
"paymentMethod": "card",
"idempotencyKey": "uuid-client-generated-key"
}IMPORTANT
The idempotencyKey prevents double-charging. If the mobile app retries after a timeout, the server recognizes the duplicate key and returns the original response instead of creating a second order.
Why Microservices Here?
| Problem | Microservices Solution |
|---|---|
| Product browsing is read-heavy | Scale Product + cache separately |
| Payments need strict isolation | Own service + own database |
| Flash sales spike orders 10× | Scale Order + Inventory only |
| Notifications can lag slightly | Async via Kafka — no blocking |
TIP
Want the full production case study with capacity math, saga patterns, and deployment? See E-Commerce Microservices Case Study.
6.7 Service Discovery
In the cloud, service IP addresses change constantly — containers restart, auto-scaling adds new instances. Service Discovery lets services find each other without hard-coded URLs.
Common tools: Consul, Eureka, etcd. Kubernetes does this automatically via DNS: inventory-service.default.svc.cluster.local.
Client-Side Discovery (JavaScript Example)
async function callOrderService() {
// 1. Fetch available instances from registry (e.g. Consul)
const instances = await serviceRegistry.get("order-service");
// Example: ["10.0.0.1:3001", "10.0.0.2:3001"]
// 2. Pick one instance (simple random load balancing)
const target = instances[Math.floor(Math.random() * instances.length)];
// 3. Make the call
return fetch(`http://${target}/orders`);
}6.8 Circuit Breaker Pattern
If the Payment Service is slow or down, the Order Service should not keep hammering it and drag the whole system down. The Circuit Breaker acts like an electrical fuse — it trips open when failures exceed a threshold.
State Transitions
| State | Behavior |
|---|---|
| CLOSED | Normal — requests go through |
| OPEN | Service is down — return fallback immediately, no waiting |
| HALF_OPEN | Try one test request to see if the service recovered |
Logic (JavaScript Example)
class CircuitBreaker {
constructor(requestFunc) {
this.requestFunc = requestFunc;
this.state = "CLOSED";
this.failures = 0;
}
async exec() {
if (this.state === "OPEN") {
return { fallback: true, message: "Payment service unavailable" };
}
try {
const data = await this.requestFunc();
this.failures = 0;
this.state = "CLOSED";
return data;
} catch (err) {
this.failures++;
if (this.failures > 3) {
this.state = "OPEN";
setTimeout(() => (this.state = "HALF_OPEN"), 5000);
}
throw err;
}
}
}TIP
More on circuit breakers in production: Circuit Breakers Pattern.
6.9 Data Management — Database per Service
Each microservice owns its data. No direct SQL joins across services — if Order Service needs stock info, it calls Inventory Service's API.
The Hard Part: No ACID Across Services
In a monolith, one database transaction can update orders and inventory atomically. In microservices, that is gone. You need patterns to stay consistent.
| Pattern | Use Case |
|---|---|
| Saga | Multi-step workflows with compensating rollback steps |
| Event Sourcing | Rebuild state from an immutable event log |
| Idempotency keys | Safe retries on payment and order APIs |
| 2PC (Two-Phase Commit) | Rare — too slow and brittle for microservices |
Saga Example — Order Fails After Payment Declined
Step 1: Reserve inventory ✅ success
Step 2: Charge payment ❌ fails
Step 3: Compensate → Release inventory (rollback)WARNING
Never share one database between two microservices. It creates hidden coupling — two teams editing the same tables, schema conflicts, and you lose independent deployment.
6.10 Service Mesh (Istio / Linkerd)
At large scale (Netflix, Uber), networking logic moves into sidecar proxies so application code stays clean. A service mesh handles encryption, retries, and tracing at the infrastructure level.
Sidecars handle:
- Mutual TLS encryption between services
- Automatic retries and timeouts
- Distributed tracing (Jaeger, Zipkin)
- Traffic splitting for canary deployments
INFO
You do not need a service mesh on day one. Most teams add it after they have 20+ services and networking code is duplicated everywhere.
6.11 When to Use (and When Not To)
✅ Use Microservices When
- Multiple teams need to deploy independently
- Different parts of the system have very different scale profiles (search vs payments)
- You need fault isolation for critical paths
- You want polyglot services — different languages per domain
❌ Avoid Microservices When
- Team is small (< 10 engineers)
- Product is still finding product-market fit
- You lack strong DevOps / observability practices
- Domain boundaries are unclear
Quick Decision Guide
Small startup (5 engineers): Monolith is usually better
Mid-size (20–50 engineers): Start splitting hot domains
FAANG scale (1000+ engineers): Microservices are essential6.12 Trade-offs — The Honest Picture
Benefits
- Independent scaling — scale Payment during sales, not Email
- Independent deployment — ship Order Service without touching User Service
- Team autonomy — each team owns one service end-to-end
- Technology freedom — Go for payments, Node for API, Python for ML
- Fault isolation — Email down does not break checkout
Costs
- Distributed complexity — network failures, latency, partial failures
- Data consistency — no simple transactions across services
- Observability — need tracing, centralized logging, metrics
- DevOps overhead — CI/CD per service, Kubernetes, service mesh
- Testing — integration tests across services are harder
✅ Checklist Before Moving On
- [ ] I can explain what microservices are in plain language
- [ ] I can compare monolith vs microservices with real trade-offs
- [ ] I know what an API Gateway does (auth, routing, rate limiting)
- [ ] I understand sync (REST/gRPC) vs async (Kafka/events) communication
- [ ] I can walk through an e-commerce order flow step by step
- [ ] I can describe the Circuit Breaker pattern (CLOSED → OPEN → HALF_OPEN)
- [ ] I understand database-per-service and the Saga compensation pattern
- [ ] I know when not to use microservices
📖 Related Reading
| Topic | Link |
|---|---|
| gRPC for internal communication | gRPC — When & Why |
| Messaging & event-driven design | Level 5 — Messaging |
| E-commerce full case study | E-Commerce Microservices |
| Circuit breaker deep dive | Circuit Breakers |
| Saga pattern | Saga Pattern |
| Interview prep | Microservices — Interview Guide |
➡️ Next: Level 7 — Real-World Systems
