JavaScript Architecture

JavaScript Microservices Architecture: Node.js, API Gateways, and Distributed Systems

Build scalable microservices with Node.js. Master service discovery, API gateways, event-driven architecture, and distributed system patterns.

By JavaScript Document Team
microservicesnodejsapi-gatewayservice-discoverydistributed-systemsevent-driven

Microservices architecture with JavaScript enables building scalable, maintainable distributed systems using Node.js. This approach breaks applications into small, independent services that communicate through APIs and events. This comprehensive guide covers service design, API gateways, service discovery, event-driven patterns, and production deployment strategies.

Microservices Foundation

Service Registry and Discovery

// Service Registry for Microservices Discovery
class ServiceRegistry {
  constructor() {
    this.services = new Map();
    this.healthChecks = new Map();
    this.watchers = new Map();
    this.config = {
      healthCheckInterval: 30000,
      timeoutThreshold: 10000,
      maxRetries: 3,
    };

    this.startHealthChecking();
  }

  // Register a service
  async register(serviceName, serviceInfo) {
    const service = {
      id: serviceInfo.id || this.generateServiceId(serviceName),
      name: serviceName,
      host: serviceInfo.host,
      port: serviceInfo.port,
      protocol: serviceInfo.protocol || 'http',
      version: serviceInfo.version || '1.0.0',
      metadata: serviceInfo.metadata || {},
      tags: serviceInfo.tags || [],
      healthEndpoint: serviceInfo.healthEndpoint || '/health',
      registeredAt: new Date(),
      lastHealthCheck: null,
      status: 'starting',
    };

    // Store service
    if (!this.services.has(serviceName)) {
      this.services.set(serviceName, new Map());
    }

    this.services.get(serviceName).set(service.id, service);

    // Initial health check
    await this.performHealthCheck(service);

    // Notify watchers
    this.notifyWatchers(serviceName, 'register', service);

    console.log(`Service registered: ${serviceName}:${service.id}`);
    return service.id;
  }

  // Deregister a service
  deregister(serviceName, serviceId) {
    const serviceMap = this.services.get(serviceName);
    if (serviceMap && serviceMap.has(serviceId)) {
      const service = serviceMap.get(serviceId);
      serviceMap.delete(serviceId);

      // Clean up empty service maps
      if (serviceMap.size === 0) {
        this.services.delete(serviceName);
      }

      // Notify watchers
      this.notifyWatchers(serviceName, 'deregister', service);

      console.log(`Service deregistered: ${serviceName}:${serviceId}`);
      return true;
    }

    return false;
  }

  // Discover services
  discover(serviceName, filters = {}) {
    const serviceMap = this.services.get(serviceName);
    if (!serviceMap) {
      return [];
    }

    let services = Array.from(serviceMap.values());

    // Filter by status
    if (filters.status) {
      services = services.filter((s) => s.status === filters.status);
    }

    // Filter by version
    if (filters.version) {
      services = services.filter((s) => s.version === filters.version);
    }

    // Filter by tags
    if (filters.tags) {
      services = services.filter((s) =>
        filters.tags.every((tag) => s.tags.includes(tag))
      );
    }

    // Filter healthy services only
    if (filters.healthyOnly !== false) {
      services = services.filter((s) => s.status === 'healthy');
    }

    return services;
  }

  // Get service URL
  getServiceUrl(serviceName, serviceId = null) {
    const services = this.discover(serviceName, { healthyOnly: true });

    if (services.length === 0) {
      throw new Error(`No healthy instances of ${serviceName} found`);
    }

    let service;
    if (serviceId) {
      service = services.find((s) => s.id === serviceId);
      if (!service) {
        throw new Error(`Service ${serviceName}:${serviceId} not found`);
      }
    } else {
      // Load balancing - round robin
      service = this.selectService(services);
    }

    return `${service.protocol}://${service.host}:${service.port}`;
  }

  // Watch for service changes
  watch(serviceName, callback) {
    if (!this.watchers.has(serviceName)) {
      this.watchers.set(serviceName, new Set());
    }

    this.watchers.get(serviceName).add(callback);

    // Return unwatch function
    return () => {
      const callbacks = this.watchers.get(serviceName);
      if (callbacks) {
        callbacks.delete(callback);
        if (callbacks.size === 0) {
          this.watchers.delete(serviceName);
        }
      }
    };
  }

  // Perform health check
  async performHealthCheck(service) {
    const url = `${service.protocol}://${service.host}:${service.port}${service.healthEndpoint}`;

    try {
      const response = await fetch(url, {
        method: 'GET',
        timeout: this.config.timeoutThreshold,
      });

      if (response.ok) {
        service.status = 'healthy';
        service.lastHealthCheck = new Date();
        return true;
      } else {
        service.status = 'unhealthy';
        return false;
      }
    } catch (error) {
      service.status = 'unhealthy';
      console.error(
        `Health check failed for ${service.name}:${service.id}:`,
        error.message
      );
      return false;
    }
  }

  // Start health checking
  startHealthChecking() {
    setInterval(async () => {
      const allServices = [];

      for (const serviceMap of this.services.values()) {
        allServices.push(...serviceMap.values());
      }

      // Perform health checks in parallel
      await Promise.all(
        allServices.map((service) => this.performHealthCheck(service))
      );
    }, this.config.healthCheckInterval);
  }

  // Load balancing
  selectService(services) {
    // Simple round-robin implementation
    if (!this.roundRobinCounters) {
      this.roundRobinCounters = new Map();
    }

    const serviceName = services[0].name;
    const counter = this.roundRobinCounters.get(serviceName) || 0;
    const selectedIndex = counter % services.length;

    this.roundRobinCounters.set(serviceName, counter + 1);

    return services[selectedIndex];
  }

  // Notify watchers
  notifyWatchers(serviceName, event, service) {
    const callbacks = this.watchers.get(serviceName);
    if (callbacks) {
      callbacks.forEach((callback) => {
        try {
          callback(event, service);
        } catch (error) {
          console.error('Watcher callback error:', error);
        }
      });
    }
  }

  generateServiceId(serviceName) {
    return `${serviceName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
}

// API Gateway for Microservices
class APIGateway {
  constructor(serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
    this.routes = new Map();
    this.middleware = [];
    this.rateLimiter = new RateLimiter();
    this.circuitBreakers = new Map();
    this.cache = new Map();
    this.metrics = new MetricsCollector();
  }

  // Register route
  route(path, serviceName, options = {}) {
    const route = {
      path: this.normalizePath(path),
      serviceName,
      options: {
        timeout: options.timeout || 30000,
        retries: options.retries || 3,
        cache: options.cache || false,
        cacheTTL: options.cacheTTL || 300000,
        rateLimit: options.rateLimit || null,
        auth: options.auth || false,
        transform: options.transform || null,
      },
    };

    this.routes.set(route.path, route);
  }

  // Add middleware
  use(middleware) {
    this.middleware.push(middleware);
  }

  // Handle request
  async handleRequest(req, res) {
    const startTime = Date.now();

    try {
      // Apply middleware
      for (const middleware of this.middleware) {
        await middleware(req, res);
      }

      // Find matching route
      const route = this.findRoute(req.path);
      if (!route) {
        return this.sendError(res, 404, 'Route not found');
      }

      // Rate limiting
      if (route.options.rateLimit) {
        const allowed = await this.rateLimiter.checkLimit(
          req.ip,
          route.options.rateLimit
        );

        if (!allowed) {
          return this.sendError(res, 429, 'Rate limit exceeded');
        }
      }

      // Check cache
      if (route.options.cache) {
        const cached = this.getFromCache(req);
        if (cached) {
          this.metrics.recordHit('cache', req.path);
          return res.json(cached);
        }
      }

      // Get service instance
      const serviceUrl = this.serviceRegistry.getServiceUrl(route.serviceName);

      // Circuit breaker check
      const circuitBreaker = this.getCircuitBreaker(route.serviceName);
      if (circuitBreaker.isOpen()) {
        return this.sendError(res, 503, 'Service temporarily unavailable');
      }

      // Forward request
      const response = await this.forwardRequest(
        serviceUrl,
        req,
        route.options
      );

      // Transform response if needed
      let responseData = response.data;
      if (route.options.transform) {
        responseData = route.options.transform(responseData);
      }

      // Cache response
      if (route.options.cache) {
        this.setCache(req, responseData, route.options.cacheTTL);
      }

      // Record metrics
      this.metrics.recordRequest(req.path, Date.now() - startTime, 200);

      res.json(responseData);
    } catch (error) {
      console.error('Gateway error:', error);

      // Record metrics
      this.metrics.recordRequest(req.path, Date.now() - startTime, 500);

      this.sendError(res, 500, 'Internal server error');
    }
  }

  // Forward request to service
  async forwardRequest(serviceUrl, req, options) {
    const url = `${serviceUrl}${req.path}`;

    const fetchOptions = {
      method: req.method,
      headers: this.forwardHeaders(req.headers),
      timeout: options.timeout,
    };

    if (req.body && req.method !== 'GET') {
      fetchOptions.body = JSON.stringify(req.body);
    }

    let lastError;
    for (let attempt = 0; attempt <= options.retries; attempt++) {
      try {
        const response = await fetch(url, fetchOptions);

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const data = await response.json();
        return { data, status: response.status };
      } catch (error) {
        lastError = error;

        if (attempt < options.retries) {
          // Exponential backoff
          await this.delay(Math.pow(2, attempt) * 1000);
        }
      }
    }

    throw lastError;
  }

  // Circuit breaker
  getCircuitBreaker(serviceName) {
    if (!this.circuitBreakers.has(serviceName)) {
      this.circuitBreakers.set(
        serviceName,
        new CircuitBreaker({
          failureThreshold: 5,
          recoveryTimeout: 60000,
        })
      );
    }

    return this.circuitBreakers.get(serviceName);
  }

  // Cache operations
  getFromCache(req) {
    const key = this.getCacheKey(req);
    const cached = this.cache.get(key);

    if (cached && cached.expires > Date.now()) {
      return cached.data;
    }

    this.cache.delete(key);
    return null;
  }

  setCache(req, data, ttl) {
    const key = this.getCacheKey(req);
    this.cache.set(key, {
      data,
      expires: Date.now() + ttl,
    });
  }

  getCacheKey(req) {
    return `${req.method}:${req.path}:${JSON.stringify(req.query)}`;
  }

  // Utility methods
  findRoute(path) {
    for (const [routePath, route] of this.routes) {
      if (this.matchPath(routePath, path)) {
        return route;
      }
    }
    return null;
  }

  matchPath(routePath, requestPath) {
    const routeParts = routePath.split('/');
    const requestParts = requestPath.split('/');

    if (routeParts.length !== requestParts.length) {
      return false;
    }

    return routeParts.every((part, index) => {
      return part.startsWith(':') || part === requestParts[index];
    });
  }

  normalizePath(path) {
    return path.startsWith('/') ? path : `/${path}`;
  }

  forwardHeaders(headers) {
    const forwardedHeaders = { ...headers };

    // Remove hop-by-hop headers
    delete forwardedHeaders['connection'];
    delete forwardedHeaders['keep-alive'];
    delete forwardedHeaders['transfer-encoding'];

    return forwardedHeaders;
  }

  sendError(res, status, message) {
    res.status(status).json({
      error: message,
      timestamp: new Date().toISOString(),
    });
  }

  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

Event-Driven Architecture

Message Bus and Event System

// Event Bus for Microservices Communication
class EventBus {
  constructor(options = {}) {
    this.events = new Map();
    this.subscribers = new Map();
    this.middleware = [];
    this.config = {
      maxRetries: options.maxRetries || 3,
      retryDelay: options.retryDelay || 1000,
      deadLetterQueue: options.deadLetterQueue || true,
      persistence: options.persistence || false,
    };

    this.deadLetterQueue = new Map();
    this.eventStore = options.persistence ? new EventStore() : null;
  }

  // Subscribe to events
  subscribe(eventType, handler, options = {}) {
    if (!this.subscribers.has(eventType)) {
      this.subscribers.set(eventType, new Set());
    }

    const subscription = {
      id: this.generateId(),
      handler,
      options: {
        priority: options.priority || 0,
        filter: options.filter || null,
        timeout: options.timeout || 30000,
        retries: options.retries || this.config.maxRetries,
      },
    };

    this.subscribers.get(eventType).add(subscription);

    console.log(`Subscribed to ${eventType}: ${subscription.id}`);

    // Return unsubscribe function
    return () => {
      this.subscribers.get(eventType)?.delete(subscription);
    };
  }

  // Publish events
  async publish(eventType, data, metadata = {}) {
    const event = {
      id: this.generateId(),
      type: eventType,
      data,
      metadata: {
        ...metadata,
        timestamp: new Date(),
        source: metadata.source || 'unknown',
        version: metadata.version || '1.0',
      },
    };

    // Store event if persistence enabled
    if (this.eventStore) {
      await this.eventStore.store(event);
    }

    // Apply middleware
    for (const middleware of this.middleware) {
      try {
        event = (await middleware(event)) || event;
      } catch (error) {
        console.error('Middleware error:', error);
      }
    }

    console.log(`Publishing event: ${eventType}:${event.id}`);

    // Get subscribers
    const subscribers = this.subscribers.get(eventType) || new Set();

    // Sort by priority
    const sortedSubscribers = Array.from(subscribers).sort(
      (a, b) => b.options.priority - a.options.priority
    );

    // Dispatch to subscribers
    const promises = sortedSubscribers.map((subscription) =>
      this.dispatchToSubscriber(event, subscription)
    );

    // Wait for all dispatches
    const results = await Promise.allSettled(promises);

    // Handle failures
    const failures = results
      .map((result, index) => ({
        result,
        subscription: sortedSubscribers[index],
      }))
      .filter(({ result }) => result.status === 'rejected');

    if (failures.length > 0) {
      console.error(
        `Event ${event.id} failed for ${failures.length} subscribers`
      );
    }

    return {
      eventId: event.id,
      subscribersNotified: sortedSubscribers.length,
      failures: failures.length,
    };
  }

  // Dispatch to individual subscriber
  async dispatchToSubscriber(event, subscription) {
    // Apply filter if present
    if (subscription.options.filter) {
      try {
        const shouldProcess = subscription.options.filter(event);
        if (!shouldProcess) {
          return { skipped: true };
        }
      } catch (error) {
        console.error('Filter error:', error);
        return { error: 'Filter failed' };
      }
    }

    let lastError;
    const maxRetries = subscription.options.retries;

    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        // Execute with timeout
        const result = await this.executeWithTimeout(
          subscription.handler,
          [event],
          subscription.options.timeout
        );

        return { success: true, result, attempts: attempt + 1 };
      } catch (error) {
        lastError = error;
        console.error(`Subscription error (attempt ${attempt + 1}):`, error);

        if (attempt < maxRetries) {
          await this.delay(this.config.retryDelay * Math.pow(2, attempt));
        }
      }
    }

    // Send to dead letter queue
    if (this.config.deadLetterQueue) {
      this.addToDeadLetterQueue(event, subscription, lastError);
    }

    throw lastError;
  }

  // Execute handler with timeout
  async executeWithTimeout(handler, args, timeout) {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error('Handler timeout'));
      }, timeout);

      try {
        const result = handler(...args);

        if (result && typeof result.then === 'function') {
          // Promise
          result
            .then(resolve)
            .catch(reject)
            .finally(() => clearTimeout(timer));
        } else {
          // Synchronous
          clearTimeout(timer);
          resolve(result);
        }
      } catch (error) {
        clearTimeout(timer);
        reject(error);
      }
    });
  }

  // Dead letter queue
  addToDeadLetterQueue(event, subscription, error) {
    const key = `${event.type}:${subscription.id}`;

    if (!this.deadLetterQueue.has(key)) {
      this.deadLetterQueue.set(key, []);
    }

    this.deadLetterQueue.get(key).push({
      event,
      subscription,
      error: error.message,
      timestamp: new Date(),
    });
  }

  // Get dead letter queue entries
  getDeadLetterQueue(eventType = null) {
    if (eventType) {
      const entries = [];
      for (const [key, items] of this.deadLetterQueue) {
        if (key.startsWith(eventType + ':')) {
          entries.push(...items);
        }
      }
      return entries;
    }

    return Array.from(this.deadLetterQueue.values()).flat();
  }

  // Middleware
  use(middleware) {
    this.middleware.push(middleware);
  }

  // Utility methods
  generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }

  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

// Saga Pattern for Distributed Transactions
class SagaOrchestrator {
  constructor(eventBus) {
    this.eventBus = eventBus;
    this.sagas = new Map();
    this.instances = new Map();
  }

  // Define saga
  defineSaga(name, definition) {
    const saga = {
      name,
      steps: definition.steps || [],
      compensations: definition.compensations || {},
      timeout: definition.timeout || 300000, // 5 minutes
    };

    this.sagas.set(name, saga);

    // Subscribe to saga events
    this.eventBus.subscribe(`saga.${name}.start`, (event) => {
      this.startSaga(name, event.data);
    });

    this.eventBus.subscribe(`saga.${name}.step.completed`, (event) => {
      this.handleStepCompleted(event.data.instanceId, event.data.step);
    });

    this.eventBus.subscribe(`saga.${name}.step.failed`, (event) => {
      this.handleStepFailed(
        event.data.instanceId,
        event.data.step,
        event.data.error
      );
    });
  }

  // Start saga instance
  async startSaga(sagaName, data) {
    const saga = this.sagas.get(sagaName);
    if (!saga) {
      throw new Error(`Saga ${sagaName} not found`);
    }

    const instanceId = this.generateId();
    const instance = {
      id: instanceId,
      sagaName,
      data,
      currentStep: 0,
      completedSteps: [],
      status: 'running',
      startedAt: new Date(),
      lastActivity: new Date(),
    };

    this.instances.set(instanceId, instance);

    console.log(`Starting saga ${sagaName}: ${instanceId}`);

    // Execute first step
    await this.executeStep(instance);

    return instanceId;
  }

  // Execute saga step
  async executeStep(instance) {
    const saga = this.sagas.get(instance.sagaName);
    const step = saga.steps[instance.currentStep];

    if (!step) {
      // Saga completed
      instance.status = 'completed';
      instance.completedAt = new Date();

      await this.eventBus.publish(`saga.${instance.sagaName}.completed`, {
        instanceId: instance.id,
        data: instance.data,
      });

      console.log(`Saga completed: ${instance.id}`);
      return;
    }

    try {
      console.log(
        `Executing step ${instance.currentStep + 1} of saga ${instance.id}`
      );

      // Publish step event
      await this.eventBus.publish(`saga.step.${step.name}`, {
        instanceId: instance.id,
        data: instance.data,
        step: step,
      });

      instance.lastActivity = new Date();
    } catch (error) {
      console.error(`Step execution failed: ${error.message}`);
      await this.handleStepFailed(instance.id, step, error);
    }
  }

  // Handle step completion
  async handleStepCompleted(instanceId, stepResult) {
    const instance = this.instances.get(instanceId);
    if (!instance) return;

    instance.completedSteps.push({
      step: instance.currentStep,
      result: stepResult,
      completedAt: new Date(),
    });

    instance.currentStep++;
    instance.lastActivity = new Date();

    // Execute next step
    await this.executeStep(instance);
  }

  // Handle step failure
  async handleStepFailed(instanceId, step, error) {
    const instance = this.instances.get(instanceId);
    if (!instance) return;

    console.error(
      `Saga step failed: ${instanceId}, step: ${step.name}, error: ${error.message}`
    );

    instance.status = 'compensating';
    instance.error = error.message;
    instance.lastActivity = new Date();

    // Start compensation
    await this.startCompensation(instance);
  }

  // Start compensation (rollback)
  async startCompensation(instance) {
    console.log(`Starting compensation for saga: ${instance.id}`);

    const saga = this.sagas.get(instance.sagaName);

    // Execute compensations in reverse order
    for (let i = instance.completedSteps.length - 1; i >= 0; i--) {
      const completedStep = instance.completedSteps[i];
      const step = saga.steps[completedStep.step];

      if (step.compensation) {
        try {
          await this.eventBus.publish(
            `saga.compensation.${step.compensation}`,
            {
              instanceId: instance.id,
              data: instance.data,
              stepResult: completedStep.result,
            }
          );

          console.log(`Compensation executed: ${step.compensation}`);
        } catch (error) {
          console.error(`Compensation failed: ${step.compensation}`, error);
        }
      }
    }

    instance.status = 'compensated';
    instance.compensatedAt = new Date();

    await this.eventBus.publish(`saga.${instance.sagaName}.compensated`, {
      instanceId: instance.id,
      error: instance.error,
    });
  }

  // Get saga instance
  getInstance(instanceId) {
    return this.instances.get(instanceId);
  }

  // List saga instances
  listInstances(sagaName = null, status = null) {
    let instances = Array.from(this.instances.values());

    if (sagaName) {
      instances = instances.filter((i) => i.sagaName === sagaName);
    }

    if (status) {
      instances = instances.filter((i) => i.status === status);
    }

    return instances;
  }

  generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }
}

Service Implementation

Complete Microservice Template

// Base Microservice Class
class Microservice {
  constructor(serviceName, options = {}) {
    this.serviceName = serviceName;
    this.serviceId = options.serviceId || this.generateServiceId();
    this.port = options.port || 3000;
    this.host = options.host || 'localhost';

    // Dependencies
    this.serviceRegistry = options.serviceRegistry;
    this.eventBus = options.eventBus;
    this.logger = options.logger || console;

    // Service state
    this.isReady = false;
    this.isHealthy = true;
    this.metadata = options.metadata || {};

    // Express app
    this.app = express();
    this.setupMiddleware();
    this.setupRoutes();
    this.setupErrorHandling();
  }

  // Setup middleware
  setupMiddleware() {
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));

    // Request logging
    this.app.use((req, res, next) => {
      const start = Date.now();
      res.on('finish', () => {
        const duration = Date.now() - start;
        this.logger.info(
          `${req.method} ${req.path} - ${res.statusCode} - ${duration}ms`
        );
      });
      next();
    });

    // Request ID
    this.app.use((req, res, next) => {
      req.id = this.generateRequestId();
      res.set('X-Request-ID', req.id);
      next();
    });

    // Health check endpoint
    this.app.get('/health', (req, res) => {
      res.json({
        service: this.serviceName,
        status: this.isHealthy ? 'healthy' : 'unhealthy',
        timestamp: new Date().toISOString(),
        uptime: process.uptime(),
        memory: process.memoryUsage(),
        version: this.metadata.version || '1.0.0',
      });
    });

    // Ready check endpoint
    this.app.get('/ready', (req, res) => {
      if (this.isReady) {
        res.json({ status: 'ready' });
      } else {
        res.status(503).json({ status: 'not ready' });
      }
    });

    // Metrics endpoint
    this.app.get('/metrics', (req, res) => {
      res.json(this.getMetrics());
    });
  }

  // Setup routes - to be overridden by subclasses
  setupRoutes() {
    // Default routes
    this.app.get('/', (req, res) => {
      res.json({
        service: this.serviceName,
        version: this.metadata.version || '1.0.0',
        description: this.metadata.description || 'Microservice',
      });
    });
  }

  // Setup error handling
  setupErrorHandling() {
    // 404 handler
    this.app.use('*', (req, res) => {
      res.status(404).json({
        error: 'Not Found',
        path: req.path,
        method: req.method,
      });
    });

    // Error handler
    this.app.use((error, req, res, next) => {
      this.logger.error('Request error:', error);

      res.status(error.status || 500).json({
        error: error.message || 'Internal Server Error',
        requestId: req.id,
      });
    });
  }

  // Start service
  async start() {
    try {
      // Initialize dependencies
      await this.initialize();

      // Start HTTP server
      this.server = this.app.listen(this.port, this.host, () => {
        this.logger.info(
          `${this.serviceName} listening on ${this.host}:${this.port}`
        );
      });

      // Register with service registry
      if (this.serviceRegistry) {
        await this.serviceRegistry.register(this.serviceName, {
          id: this.serviceId,
          host: this.host,
          port: this.port,
          metadata: this.metadata,
        });
      }

      // Subscribe to events
      this.setupEventHandlers();

      this.isReady = true;
      this.logger.info(`${this.serviceName} started successfully`);

      // Graceful shutdown
      this.setupGracefulShutdown();
    } catch (error) {
      this.logger.error('Failed to start service:', error);
      process.exit(1);
    }
  }

  // Stop service
  async stop() {
    this.logger.info(`Stopping ${this.serviceName}...`);

    this.isReady = false;
    this.isHealthy = false;

    // Deregister from service registry
    if (this.serviceRegistry) {
      this.serviceRegistry.deregister(this.serviceName, this.serviceId);
    }

    // Close HTTP server
    if (this.server) {
      await new Promise((resolve) => {
        this.server.close(resolve);
      });
    }

    // Cleanup
    await this.cleanup();

    this.logger.info(`${this.serviceName} stopped`);
  }

  // Initialize - to be overridden
  async initialize() {
    // Override in subclasses
  }

  // Cleanup - to be overridden
  async cleanup() {
    // Override in subclasses
  }

  // Setup event handlers - to be overridden
  setupEventHandlers() {
    // Override in subclasses
  }

  // Service discovery
  async discoverService(serviceName) {
    if (!this.serviceRegistry) {
      throw new Error('Service registry not available');
    }

    return this.serviceRegistry.discover(serviceName);
  }

  // Call other service
  async callService(serviceName, path, options = {}) {
    const serviceUrl = this.serviceRegistry.getServiceUrl(serviceName);
    const url = `${serviceUrl}${path}`;

    const fetchOptions = {
      method: options.method || 'GET',
      headers: {
        'Content-Type': 'application/json',
        'X-Request-ID': this.generateRequestId(),
        ...options.headers,
      },
      timeout: options.timeout || 30000,
    };

    if (options.body) {
      fetchOptions.body = JSON.stringify(options.body);
    }

    try {
      const response = await fetch(url, fetchOptions);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } catch (error) {
      this.logger.error(`Service call failed: ${serviceName}${path}`, error);
      throw error;
    }
  }

  // Publish event
  async publishEvent(eventType, data, metadata = {}) {
    if (!this.eventBus) {
      throw new Error('Event bus not available');
    }

    return this.eventBus.publish(eventType, data, {
      ...metadata,
      source: this.serviceName,
    });
  }

  // Subscribe to event
  subscribeToEvent(eventType, handler, options = {}) {
    if (!this.eventBus) {
      throw new Error('Event bus not available');
    }

    return this.eventBus.subscribe(eventType, handler, options);
  }

  // Get metrics
  getMetrics() {
    return {
      service: this.serviceName,
      uptime: process.uptime(),
      memory: process.memoryUsage(),
      cpu: process.cpuUsage(),
      eventLoopLag: this.getEventLoopLag(),
    };
  }

  // Setup graceful shutdown
  setupGracefulShutdown() {
    const shutdown = async (signal) => {
      this.logger.info(`Received ${signal}, shutting down gracefully`);
      await this.stop();
      process.exit(0);
    };

    process.on('SIGTERM', () => shutdown('SIGTERM'));
    process.on('SIGINT', () => shutdown('SIGINT'));
  }

  // Utility methods
  generateServiceId() {
    return `${this.serviceName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  generateRequestId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }

  getEventLoopLag() {
    const start = process.hrtime.bigint();
    setImmediate(() => {
      const lag = Number(process.hrtime.bigint() - start) / 1000000;
      return lag;
    });
  }
}

// Example implementation
class UserService extends Microservice {
  constructor(options = {}) {
    super('user-service', {
      ...options,
      port: 3001,
      metadata: {
        version: '1.0.0',
        description: 'User management service',
      },
    });

    this.users = new Map();
  }

  setupRoutes() {
    super.setupRoutes();

    // Get all users
    this.app.get('/users', (req, res) => {
      const users = Array.from(this.users.values());
      res.json(users);
    });

    // Get user by ID
    this.app.get('/users/:id', (req, res) => {
      const user = this.users.get(req.params.id);
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      res.json(user);
    });

    // Create user
    this.app.post('/users', async (req, res) => {
      const { name, email } = req.body;

      const user = {
        id: this.generateUserId(),
        name,
        email,
        createdAt: new Date(),
      };

      this.users.set(user.id, user);

      // Publish event
      await this.publishEvent('user.created', user);

      res.status(201).json(user);
    });
  }

  setupEventHandlers() {
    // Subscribe to events
    this.subscribeToEvent('order.created', async (event) => {
      const { userId } = event.data;
      this.logger.info(`User ${userId} created an order`);
    });
  }

  generateUserId() {
    return (
      'user_' +
      Date.now().toString(36) +
      Math.random().toString(36).substr(2, 9)
    );
  }
}

This comprehensive guide covers JavaScript microservices architecture from service registry to complete implementation. The examples provide production-ready patterns for building scalable, resilient distributed systems with Node.js, including service discovery, API gateways, event-driven communication, and saga patterns for distributed transactions.