Monolith vs Microservices

0/2 in this phase0/45 across the roadmap

📖 Concept

Monolith: A single deployable application containing all business logic. Microservices: Multiple small, independently deployable services, each owning a bounded context.

Comparison

Aspect Monolith Microservices
Deployment Single unit (all or nothing) Independent per-service deployment
Scaling Scale the entire application Scale individual services independently
Technology One tech stack Each service can use different tech
Data Shared database Each service owns its database
Complexity Simple to develop and deploy Distributed systems complexity
Team structure One team or feature teams Service teams with ownership
Failure One bug can crash everything Failure isolated to one service
Testing Simple end-to-end tests Complex distributed testing

When to Use Each

Start with Monolith When:

  • Startup / new product (unknown requirements)
  • Small team (< 10 engineers)
  • Simple domain with clear boundaries
  • Need to move fast and iterate

Migrate to Microservices When:

  • Monolith is too large for one team to understand
  • Different components need different scaling
  • Deployment of one feature blocks others
  • Teams can't work independently (merge conflicts, coordination)

The Strangler Fig Pattern

Gradually migrate from monolith to microservices by extracting one service at a time, routing new traffic to the new service while the monolith handles remaining features.

Interview tip: "I'd start with a well-structured monolith and extract microservices when the team or scale demands it." This shows pragmatism — interviewers appreciate engineers who don't over-engineer.

💻 Code Example

codeTap to expand ⛶
1// ============================================
2// Microservices vs Monolith — Architecture Comparison
3// ============================================
4
5// ---------- Monolith Architecture ----------
6class MonolithApp {
7 constructor(db) { this.db = db; }
8
9 // ALL logic in one application
10 async createOrder(userId, items) {
11 return await this.db.transaction(async (tx) => {
12 // All in one DB transaction — ACID guaranteed
13 const user = await tx.query('SELECT * FROM users WHERE id = $1', [userId]);
14 const inventory = await this.checkInventory(tx, items);
15 const payment = await this.chargePayment(tx, user, items);
16 const order = await tx.query(
17 'INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING *',
18 [userId, payment.total]
19 );
20 await this.sendConfirmation(user.email, order);
21 return order;
22 });
23 }
24
25 async checkInventory(tx, items) { /* ... */ }
26 async chargePayment(tx, user, items) { return { total: 99 }; }
27 async sendConfirmation(email, order) { /* ... */ }
28}
29
30// ---------- Microservices Architecture ----------
31
32// Each service is independent with its own database
33class OrderService {
34 constructor(eventBus) { this.eventBus = eventBus; }
35
36 async createOrder(userId, items) {
37 const order = await this.db.insert('orders', { userId, items, status: 'pending' });
38 // Publish event — other services react independently
39 await this.eventBus.publish('order.created', {
40 orderId: order.id, userId, items, total: order.total,
41 });
42 return order;
43 }
44}
45
46class InventoryService {
47 constructor(eventBus) {
48 eventBus.subscribe('order.created', (e) => this.reserveInventory(e));
49 eventBus.subscribe('payment.failed', (e) => this.releaseInventory(e));
50 }
51 async reserveInventory(event) {
52 console.log(`[Inventory] Reserving items for order \${event.orderId}`);
53 }
54 async releaseInventory(event) {
55 console.log(`[Inventory] Releasing items for order \${event.orderId}`);
56 }
57}
58
59class PaymentService {
60 constructor(eventBus) {
61 eventBus.subscribe('order.created', (e) => this.processPayment(e));
62 }
63 async processPayment(event) {
64 console.log(`[Payment] Charging for order \${event.orderId}`);
65 }
66}
67
68class NotificationService {
69 constructor(eventBus) {
70 eventBus.subscribe('payment.charged', (e) => this.sendEmail(e));
71 }
72 async sendEmail(event) {
73 console.log(`[Notification] Email confirmation for order \${event.orderId}`);
74 }
75}
76
77// ---------- API Gateway Pattern ----------
78class APIGateway {
79 constructor() {
80 this.routes = new Map();
81 this.rateLimiter = new Map();
82 }
83
84 registerRoute(path, service) {
85 this.routes.set(path, service);
86 }
87
88 async handleRequest(req) {
89 // 1. Authentication
90 const user = await this.authenticate(req);
91 if (!user) return { status: 401, body: 'Unauthorized' };
92
93 // 2. Rate limiting
94 if (this.isRateLimited(user.id)) return { status: 429, body: 'Too many requests' };
95
96 // 3. Route to correct service
97 const service = this.routes.get(req.path);
98 if (!service) return { status: 404, body: 'Not found' };
99
100 // 4. Forward request
101 try {
102 const response = await service.handle(req);
103 return { status: 200, body: response };
104 } catch (error) {
105 return { status: 500, body: 'Internal error' };
106 }
107 }
108
109 async authenticate(req) { return { id: 'user_123' }; }
110 isRateLimited(userId) { return false; }
111}
112
113console.log("Microservices architecture patterns demonstrated.");

🏋️ Practice Exercise

  1. Service Decomposition: Take a monolithic e-commerce app (users, products, orders, payments, search, recommendations, notifications) and design the microservice boundaries. Which services should be separate? Which should stay together?

  2. API Gateway Design: Design an API gateway that handles: routing, authentication, rate limiting, request transformation, and response aggregation for 5 microservices.

  3. Database Per Service: Your monolith uses one PostgreSQL database with JOINs between user, order, and product tables. How do you split this into per-service databases? How do you handle cross-service queries?

  4. Strangler Fig Migration: Design a step-by-step plan to extract the "notification" service from a monolith. Include: API routing, data migration, feature flags, and rollback strategy.

⚠️ Common Mistakes

  • Adopting microservices too early — microservices add significant operational complexity (service mesh, distributed tracing, eventual consistency). For a team < 10 engineers, a monolith is usually better.

  • Creating too-small services (nano-services) — a service that makes 5 HTTP calls to do one thing is over-decomposed. Each service should represent a bounded context with meaningful business logic.

  • Sharing a database between microservices — this creates tight coupling and defeats the purpose. Each service should own its data and expose it via APIs or events.

  • Synchronous communication everywhere — if Service A calls B calls C calls D synchronously, one slow service blocks everything. Use async events for non-critical paths.

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for Monolith vs Microservices