Microservices Architecture
Microservices architecture breaks applications into small, independent, loosely coupled services. Learn to design, implement, and manage microservices-based systems.
Challenge
Section titled “Challenge”Design and implement a microservices architecture that enables independent deployment, scaling, and team autonomy while managing complexity and communication overhead.
Explanation
Section titled “Explanation”Microservices vs Monolithic
Section titled “Microservices vs Monolithic”Monolithic: Single codebase, single deployment unit, tightly coupled Microservices: Multiple codebases, independent deployments, loosely coupled services
Key Principles
Section titled “Key Principles”Single Responsibility: Each service handles one business capability Autonomy: Independent development, deployment, and scaling Communication: Synchronous (REST/gRPC) or asynchronous (events) Resilience: Handle failures gracefully Observability: Comprehensive monitoring and logging
Code Examples
Section titled “Code Examples”1. Microservice Structure
Section titled “1. Microservice Structure”const express = require('express');const logger = require('./logger');const routes = require('./routes');
const app = express();
app.use(express.json());app.use(require('./middleware/logging'));app.use(require('./middleware/errorHandler'));
app.use('/api/users', routes.users);
// Health checkapp.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'user-service' });});
app.listen(3001, () => logger.info('User service running on 3001'));2. Service-to-Service Communication
Section titled “2. Service-to-Service Communication”const axios = require('axios');
class UserService { constructor(baseUrl = process.env.USER_SERVICE_URL) { this.baseUrl = baseUrl; this.client = axios.create({ baseURL: baseUrl, timeout: 5000, }); }
async getUser(userId) { try { const response = await this.client.get(`/api/users/${userId}`); return response.data; } catch (error) { if (error.response?.status === 404) { throw new Error(`User ${userId} not found`); } throw error; } }
async validateUsers(userIds) { const promises = userIds.map(id => this.getUser(id)); return Promise.all(promises); }}
module.exports = new UserService();3. API Gateway Pattern
Section titled “3. API Gateway Pattern”const express = require('express');const httpProxy = require('express-http-proxy');
const app = express();
// Service routesconst services = { users: process.env.USER_SERVICE_URL || 'http://localhost:3001', orders: process.env.ORDER_SERVICE_URL || 'http://localhost:3002', products: process.env.PRODUCT_SERVICE_URL || 'http://localhost:3003',};
// Request logging middlewareapp.use((req, res, next) => { req.logger.info('Incoming request', { method: req.method, path: req.path, query: req.query, }); next();});
// Route to user serviceapp.use('/api/users', httpProxy(services.users, { proxyReqPathResolver: (req) => `/api/users${req.url}`,}));
// Route to order serviceapp.use('/api/orders', httpProxy(services.orders, { proxyReqPathResolver: (req) => `/api/orders${req.url}`,}));
// Route to product serviceapp.use('/api/products', httpProxy(services.products, { proxyReqPathResolver: (req) => `/api/products${req.url}`,}));
app.listen(8000, () => console.log('API Gateway on 8000'));4. Event-Driven Architecture
Section titled “4. Event-Driven Architecture”const EventEmitter = require('events');const amqp = require('amqplib');
class EventBus { async connect() { this.connection = await amqp.connect(process.env.RABBITMQ_URL); this.channel = await this.connection.createChannel(); }
async publish(eventType, data) { const exchange = 'events'; await this.channel.assertExchange(exchange, 'topic', { durable: true });
this.channel.publish( exchange, eventType, Buffer.from(JSON.stringify(data)) ); }
async subscribe(eventType, handler) { const exchange = 'events'; const queue = `${eventType}.queue`;
await this.channel.assertExchange(exchange, 'topic', { durable: true }); await this.channel.assertQueue(queue); await this.channel.bindQueue(queue, exchange, eventType);
this.channel.consume(queue, async (message) => { if (message) { const data = JSON.parse(message.content.toString()); await handler(data); this.channel.ack(message); } }); }}
module.exports = new EventBus();5. Saga Pattern for Distributed Transactions
Section titled “5. Saga Pattern for Distributed Transactions”const eventBus = require('../eventBus');
class OrderSaga { async createOrder(orderData) { const orderId = generateId();
try { // Step 1: Reserve inventory await this.reserveInventory(orderId, orderData.items);
// Step 2: Process payment await this.processPayment(orderId, orderData.amount);
// Step 3: Create order record await this.createOrderRecord(orderId, orderData);
// Publish success event await eventBus.publish('order.created', { orderId, status: 'confirmed' });
return orderId; } catch (error) { // Compensate transactions on failure await this.compensate(orderId); throw error; } }
async compensate(orderId) { await eventBus.publish('order.cancelled', { orderId }); // Handle compensation logic }}6. Service Discovery
Section titled “6. Service Discovery”const consul = require('consul');
class ServiceRegistry { constructor() { this.consul = new consul({ host: process.env.CONSUL_HOST || 'localhost', port: process.env.CONSUL_PORT || 8500, }); }
async register(serviceName, port) { await this.consul.agent.service.register({ id: `${serviceName}-${port}`, name: serviceName, address: process.env.SERVICE_HOST || 'localhost', port: port, check: { http: `http://localhost:${port}/health`, interval: '10s', timeout: '5s', }, }); }
async discover(serviceName) { const result = await this.consul.health.service({ service: serviceName, passing: true, });
return result.map(entry => ({ address: entry.Service.Address, port: entry.Service.Port, })); }}
module.exports = new ServiceRegistry();Best Practices
Section titled “Best Practices”-
Service Boundaries
- Clear, well-defined responsibilities
- Data ownership and isolation
- Minimize inter-service dependencies
-
Communication
- Use synchronous for real-time operations
- Use asynchronous for eventual consistency
- Implement timeouts and circuit breakers
-
Deployment
- Independent deployment pipelines
- Version management
- Rollback strategies
-
Monitoring and Debugging
- Distributed tracing
- Centralized logging
- Service health monitoring
-
Resilience
- Graceful degradation
- Retry logic with backoff
- Fallback mechanisms
Real-World Applications
Section titled “Real-World Applications”- E-Commerce Platforms: User, Product, Order, Payment services
- Streaming Platforms: Content, User, Recommendation services
- SaaS Applications: Tenant isolation with service architecture
- Mobile Backends: Scalable API services
- Financial Systems: Payment, Transaction, Settlement services