Monolith vs Microservices
📖 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
1// ============================================2// Microservices vs Monolith — Architecture Comparison3// ============================================45// ---------- Monolith Architecture ----------6class MonolithApp {7 constructor(db) { this.db = db; }89 // ALL logic in one application10 async createOrder(userId, items) {11 return await this.db.transaction(async (tx) => {12 // All in one DB transaction — ACID guaranteed13 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 }2425 async checkInventory(tx, items) { /* ... */ }26 async chargePayment(tx, user, items) { return { total: 99 }; }27 async sendConfirmation(email, order) { /* ... */ }28}2930// ---------- Microservices Architecture ----------3132// Each service is independent with its own database33class OrderService {34 constructor(eventBus) { this.eventBus = eventBus; }3536 async createOrder(userId, items) {37 const order = await this.db.insert('orders', { userId, items, status: 'pending' });38 // Publish event — other services react independently39 await this.eventBus.publish('order.created', {40 orderId: order.id, userId, items, total: order.total,41 });42 return order;43 }44}4546class 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}5859class 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}6768class 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}7677// ---------- API Gateway Pattern ----------78class APIGateway {79 constructor() {80 this.routes = new Map();81 this.rateLimiter = new Map();82 }8384 registerRoute(path, service) {85 this.routes.set(path, service);86 }8788 async handleRequest(req) {89 // 1. Authentication90 const user = await this.authenticate(req);91 if (!user) return { status: 401, body: 'Unauthorized' };9293 // 2. Rate limiting94 if (this.isRateLimited(user.id)) return { status: 429, body: 'Too many requests' };9596 // 3. Route to correct service97 const service = this.routes.get(req.path);98 if (!service) return { status: 404, body: 'Not found' };99100 // 4. Forward request101 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 }108109 async authenticate(req) { return { id: 'user_123' }; }110 isRateLimited(userId) { return false; }111}112113console.log("Microservices architecture patterns demonstrated.");
🏋️ Practice Exercise
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?
API Gateway Design: Design an API gateway that handles: routing, authentication, rate limiting, request transformation, and response aggregation for 5 microservices.
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?
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