Microservices Architecture

📖 Concept

Microservices decompose a monolithic application into small, independently deployable services that communicate over APIs. Each service owns its own data and business logic.

Monolith vs. Microservices:

Aspect Monolith Microservices
Deployment Deploy everything together Deploy services independently
Scaling Scale entire app Scale individual services
Tech stack Single language/framework Any language per service
Database Shared database Database per service
Complexity Simpler at small scale Complex infrastructure
Team ownership Shared codebase Service ownership
Failure isolation One bug can crash everything Failures are contained

Key microservice patterns:

  1. API Gateway — single entry point that routes to services
  2. Service Discovery — services register and find each other dynamically
  3. Circuit Breaker — prevent cascading failures when a service is down
  4. Saga — distributed transactions across multiple services
  5. CQRS — separate read and write models for performance
  6. Event Sourcing — store events instead of current state

Communication patterns:

Pattern Use Case Examples
Synchronous (HTTP/gRPC) Request-response, real-time REST API, gRPC calls
Asynchronous (Message Queue) Fire-and-forget, eventual consistency RabbitMQ, Kafka, SQS
Event-driven React to state changes Redis Pub/Sub, EventBridge

When to use microservices:

  • ✅ Large teams (10+ developers) needing independent deployment
  • ✅ Services with very different scaling requirements
  • ✅ Need for technology diversity across components
  • ❌ Small teams or early-stage startups (stick with monolith)
  • ❌ If you can't invest in DevOps infrastructure

🏠 Real-world analogy: A monolith is a department store — everything under one roof. Microservices are a shopping mall — independent shops (services) connected by hallways (APIs). Each shop can renovate (deploy), hire (scale), and specialize independently. But the mall needs management (infrastructure) to work.

💻 Code Example

codeTap to expand ⛶
1// Microservices Architecture — Patterns
2
3// === API Gateway Pattern ===
4const express = require("express");
5const { createProxyMiddleware } = require("http-proxy-middleware");
6
7function createGateway() {
8 const app = express();
9
10 // Route to service based on path
11 const services = {
12 "/api/users": "http://user-service:3001",
13 "/api/orders": "http://order-service:3002",
14 "/api/products": "http://product-service:3003",
15 "/api/payments": "http://payment-service:3004",
16 };
17
18 for (const [path, target] of Object.entries(services)) {
19 app.use(path, createProxyMiddleware({
20 target,
21 changeOrigin: true,
22 pathRewrite: { [`^${path}`]: "" },
23 onError: (err, req, res) => {
24 res.status(503).json({ error: `Service unavailable: ${path}` });
25 },
26 }));
27 }
28
29 return app;
30}
31
32// === Circuit Breaker Pattern ===
33class CircuitBreaker {
34 constructor(options = {}) {
35 this.failureThreshold = options.failureThreshold || 5;
36 this.resetTimeout = options.resetTimeout || 30000;
37 this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
38 this.failureCount = 0;
39 this.lastFailureTime = null;
40 this.successCount = 0;
41 }
42
43 async execute(fn) {
44 if (this.state === "OPEN") {
45 if (Date.now() - this.lastFailureTime >= this.resetTimeout) {
46 this.state = "HALF_OPEN";
47 } else {
48 throw new Error("Circuit breaker is OPEN — service unavailable");
49 }
50 }
51
52 try {
53 const result = await fn();
54
55 if (this.state === "HALF_OPEN") {
56 this.successCount++;
57 if (this.successCount >= 3) {
58 this.reset();
59 }
60 }
61
62 return result;
63 } catch (err) {
64 this.failureCount++;
65 this.lastFailureTime = Date.now();
66
67 if (this.failureCount >= this.failureThreshold) {
68 this.state = "OPEN";
69 console.error(`Circuit OPENED after ${this.failureCount} failures`);
70 }
71
72 throw err;
73 }
74 }
75
76 reset() {
77 this.state = "CLOSED";
78 this.failureCount = 0;
79 this.successCount = 0;
80 console.log("Circuit CLOSED — service recovered");
81 }
82}
83
84// Usage
85const orderServiceBreaker = new CircuitBreaker({ failureThreshold: 3, resetTimeout: 10000 });
86
87async function getOrders(userId) {
88 return orderServiceBreaker.execute(async () => {
89 const response = await fetch(`http://order-service:3002/orders?userId=${userId}`);
90 if (!response.ok) throw new Error(`Order service: ${response.status}`);
91 return response.json();
92 });
93}
94
95// === Saga Pattern (Orchestration) ===
96class OrderSaga {
97 async execute(orderData) {
98 const steps = [];
99
100 try {
101 // Step 1: Reserve inventory
102 const reservation = await this.reserveInventory(orderData);
103 steps.push({ action: "reserve", data: reservation });
104
105 // Step 2: Process payment
106 const payment = await this.processPayment(orderData);
107 steps.push({ action: "payment", data: payment });
108
109 // Step 3: Create order
110 const order = await this.createOrder(orderData, reservation, payment);
111 steps.push({ action: "order", data: order });
112
113 // Step 4: Send notification
114 await this.sendNotification(order);
115
116 return order;
117 } catch (err) {
118 // Compensating transactions (rollback in reverse order)
119 console.error("Saga failed, rolling back:", err.message);
120
121 for (const step of steps.reverse()) {
122 try {
123 await this.compensate(step);
124 } catch (compensateErr) {
125 console.error(`Compensation failed for ${step.action}:`, compensateErr);
126 // In production: send to dead letter queue for manual resolution
127 }
128 }
129
130 throw err;
131 }
132 }
133
134 async compensate(step) {
135 switch (step.action) {
136 case "reserve": return this.releaseInventory(step.data);
137 case "payment": return this.refundPayment(step.data);
138 case "order": return this.cancelOrder(step.data);
139 }
140 }
141
142 async reserveInventory(data) { return { reservationId: "res_123" }; }
143 async processPayment(data) { return { paymentId: "pay_123" }; }
144 async createOrder(data, res, pay) { return { orderId: "ord_123" }; }
145 async sendNotification(order) { console.log("Notification sent"); }
146 async releaseInventory(data) { console.log("Inventory released"); }
147 async refundPayment(data) { console.log("Payment refunded"); }
148 async cancelOrder(data) { console.log("Order cancelled"); }
149}
150
151module.exports = { createGateway, CircuitBreaker, OrderSaga };

🏋️ Practice Exercise

Exercises:

  1. Design a microservice architecture for an e-commerce platform — draw the service boundaries and communication patterns
  2. Implement a Circuit Breaker class with CLOSED, OPEN, and HALF_OPEN states
  3. Build an API Gateway that routes requests to different backend services
  4. Implement the Saga pattern for a multi-step order process with compensating transactions
  5. Set up inter-service communication using both REST (synchronous) and a message queue (async)
  6. Implement service health checking — the gateway routes traffic only to healthy services

⚠️ Common Mistakes

  • Starting with microservices — most applications should start as a monolith and split into services when team size and complexity demand it

  • Creating too many services — 'nano-services' add overhead without benefit; each service should represent a significant business capability

  • Sharing databases between services — this creates tight coupling; each service should own its data and expose it via APIs

  • Not implementing circuit breakers — when a downstream service fails, the calling service also fails; circuit breakers prevent cascading failures

  • Using synchronous communication for everything — fire-and-forget operations (emails, notifications) should be asynchronous via message queues

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Microservices Architecture. Login to unlock this feature.