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.
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.