⚡ Architecture

Microservices Architecture Design

Last updated: 2025-09-25 12:47:03

Microservices Architecture

Design and implement scalable microservices architecture with proper service communication, data management, and deployment strategies.

Service Design Principles

// User Service - Domain-driven design
class UserService {
  constructor(
    private userRepository: UserRepository,
    private eventPublisher: EventPublisher,
    private logger: Logger
  ) {}
  
  async createUser(userData: CreateUserRequest): Promise {
    try {
      // Validate input
      const validatedData = await this.validateUserData(userData);
      
      // Check business rules
      await this.checkEmailUniqueness(validatedData.email);
      
      // Create user
      const user = await this.userRepository.create(validatedData);
      
      // Publish domain event
      await this.eventPublisher.publish('user.created', {
        userId: user.id,
        email: user.email,
        timestamp: new Date()
      });
      
      this.logger.info(`User created: ${user.id}`);
      return user;
    } catch (error) {
      this.logger.error(`Failed to create user: ${error.message}`);
      throw error;
    }
  }
  
  private async validateUserData(data: CreateUserRequest): Promise {
    const schema = {
      email: 'required|email',
      name: 'required|string|min:2|max:100',
      password: 'required|string|min:8'
    };
    
    return validate(data, schema);
  }
  
  private async checkEmailUniqueness(email: string): Promise {
    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      throw new ConflictError('Email already exists');
    }
  }
}

API Gateway Pattern

// API Gateway with Express and middleware
const express = require('express');
const httpProxy = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');

const app = express();

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP'
});

app.use(limiter);

// Authentication middleware
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Access token required' });
  }
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.status(403).json({ error: 'Invalid token' });
    req.user = user;
    next();
  });
};

// Service proxies
const userServiceProxy = httpProxy({
  target: process.env.USER_SERVICE_URL,
  changeOrigin: true,
  pathRewrite: { '^/api/users': '' },
  onError: (err, req, res) => {
    res.status(503).json({ error: 'User service unavailable' });
  }
});

const orderServiceProxy = httpProxy({
  target: process.env.ORDER_SERVICE_URL,
  changeOrigin: true,
  pathRewrite: { '^/api/orders': '' }
});

// Routes
app.use('/api/auth', require('./auth-routes'));
app.use('/api/users', authenticateToken, userServiceProxy);
app.use('/api/orders', authenticateToken, orderServiceProxy);

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'healthy', timestamp: new Date() });
});

Event-Driven Communication

// Event Bus implementation with Redis
const Redis = require('ioredis');

class EventBus {
  constructor() {
    this.publisher = new Redis(process.env.REDIS_URL);
    this.subscriber = new Redis(process.env.REDIS_URL);
    this.handlers = new Map();
  }
  
  async publish(event, data) {
    const message = {
      event,
      data,
      timestamp: new Date(),
      id: generateId()
    };
    
    await this.publisher.publish('events', JSON.stringify(message));
    console.log(`Published event: ${event}`);
  }
  
  subscribe(event, handler) {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, []);
    }
    this.handlers.get(event).push(handler);
  }
  
  async startListening() {
    await this.subscriber.subscribe('events');
    
    this.subscriber.on('message', async (channel, message) => {
      try {
        const { event, data, timestamp, id } = JSON.parse(message);
        const handlers = this.handlers.get(event) || [];
        
        for (const handler of handlers) {
          try {
            await handler(data, { event, timestamp, id });
          } catch (error) {
            console.error(`Handler error for event ${event}:`, error);
          }
        }
      } catch (error) {
        console.error('Failed to process event:', error);
      }
    });
  }
}

// Usage in service
class OrderService {
  constructor(eventBus) {
    this.eventBus = eventBus;
    this.setupEventHandlers();
  }
  
  setupEventHandlers() {
    this.eventBus.subscribe('user.created', async (userData) => {
      // Create user profile in order service
      await this.createUserProfile(userData);
    });
    
    this.eventBus.subscribe('payment.processed', async (paymentData) => {
      // Update order status
      await this.updateOrderStatus(paymentData.orderId, 'paid');
    });
  }
}

Service Discovery and Load Balancing

// Service registry
class ServiceRegistry {
  constructor() {
    this.services = new Map();
  }
  
  register(serviceName, serviceUrl, metadata = {}) {
    if (!this.services.has(serviceName)) {
      this.services.set(serviceName, []);
    }
    
    const serviceInfo = {
      url: serviceUrl,
      metadata,
      registeredAt: new Date(),
      lastHeartbeat: new Date()
    };
    
    this.services.get(serviceName).push(serviceInfo);
    console.log(`Service registered: ${serviceName} at ${serviceUrl}`);
  }
  
  discover(serviceName) {
    const services = this.services.get(serviceName) || [];
    return services.filter(service => 
      Date.now() - service.lastHeartbeat.getTime() < 30000 // 30 second timeout
    );
  }
  
  heartbeat(serviceName, serviceUrl) {
    const services = this.services.get(serviceName) || [];
    const service = services.find(s => s.url === serviceUrl);
    if (service) {
      service.lastHeartbeat = new Date();
    }
  }
}

// Load balancer
class LoadBalancer {
  constructor(serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
    this.roundRobinIndex = new Map();
  }
  
  getService(serviceName, strategy = 'round-robin') {
    const services = this.serviceRegistry.discover(serviceName);
    
    if (services.length === 0) {
      throw new Error(`No healthy instances of ${serviceName} found`);
    }
    
    switch (strategy) {
      case 'round-robin':
        return this.roundRobin(serviceName, services);
      case 'random':
        return services[Math.floor(Math.random() * services.length)];
      case 'least-connections':
        return this.leastConnections(services);
      default:
        return services[0];
    }
  }
  
  roundRobin(serviceName, services) {
    const currentIndex = this.roundRobinIndex.get(serviceName) || 0;
    const nextIndex = (currentIndex + 1) % services.length;
    this.roundRobinIndex.set(serviceName, nextIndex);
    return services[currentIndex];
  }
}