const EventEmitter = require('events'); /** * Service Container for dependency injection and service lifecycle management */ class ServiceContainer extends EventEmitter { constructor() { super(); this.services = new Map(); this.factories = new Map(); this.singletons = new Map(); this.circuitBreakers = new Map(); this.isShuttingDown = false; this.startupOrder = []; this.shutdownOrder = []; } /** * Register a service factory * @param {string} name - Service name * @param {Function} factory - Factory function that creates the service * @param {Object} options - Registration options * @param {boolean} options.singleton - Whether to create only one instance * @param {Array} options.dependencies - List of service dependencies * @param {Object} options.circuitBreaker - Circuit breaker configuration * @param {number} options.startupPriority - Startup priority (lower = earlier) * @param {Function} options.healthCheck - Health check function */ register(name, factory, options = {}) { if (this.factories.has(name)) { throw new Error(`Service '${name}' is already registered`); } const serviceConfig = { factory, singleton: options.singleton || false, dependencies: options.dependencies || [], circuitBreaker: options.circuitBreaker || null, startupPriority: options.startupPriority || 100, healthCheck: options.healthCheck || null, startupTimeout: options.startupTimeout || 30000, shutdownTimeout: options.shutdownTimeout || 10000 }; this.factories.set(name, serviceConfig); // Add to startup order based on priority this.updateStartupOrder(name, serviceConfig.startupPriority); console.log(`Service '${name}' registered${serviceConfig.singleton ? ' (singleton)' : ''}`); } /** * Register a service instance directly * @param {string} name - Service name * @param {*} instance - Service instance * @param {Object} options - Registration options */ registerInstance(name, instance, options = {}) { if (this.services.has(name)) { throw new Error(`Service instance '${name}' is already registered`); } this.services.set(name, instance); if (options.healthCheck) { this.factories.set(name, { healthCheck: options.healthCheck }); } console.log(`Service instance '${name}' registered`); } /** * Get a service instance * @param {string} name - Service name * @returns {*} Service instance */ get(name) { if (this.isShuttingDown) { throw new Error(`Cannot get service '${name}' during shutdown`); } // Return existing instance if available if (this.services.has(name)) { return this.services.get(name); } // Return singleton if available if (this.singletons.has(name)) { return this.singletons.get(name); } // Create new instance return this.createInstance(name); } /** * Create a new service instance * @param {string} name - Service name * @returns {*} Service instance */ createInstance(name) { const serviceConfig = this.factories.get(name); if (!serviceConfig) { throw new Error(`Service '${name}' is not registered`); } // Check for circular dependencies this.checkCircularDependencies(name, new Set()); // Create dependency instances const dependencies = {}; for (const depName of serviceConfig.dependencies) { dependencies[depName] = this.get(depName); } // Create service instance let instance; try { instance = serviceConfig.factory(dependencies, this); } catch (error) { console.error(`Failed to create service '${name}':`, error); throw new Error(`Service creation failed for '${name}': ${error.message}`); } // Store instance if (serviceConfig.singleton) { this.singletons.set(name, instance); } else { this.services.set(name, instance); } // Set up circuit breaker if configured if (serviceConfig.circuitBreaker) { this.setupCircuitBreaker(name, instance, serviceConfig.circuitBreaker); } this.emit('serviceCreated', { name, instance, config: serviceConfig }); console.log(`Service '${name}' created successfully`); return instance; } /** * Check for circular dependencies * @param {string} name - Service name * @param {Set} visited - Set of visited services */ checkCircularDependencies(name, visited) { if (visited.has(name)) { const cycle = Array.from(visited).concat(name).join(' -> '); throw new Error(`Circular dependency detected: ${cycle}`); } const serviceConfig = this.factories.get(name); if (!serviceConfig) return; visited.add(name); for (const depName of serviceConfig.dependencies) { this.checkCircularDependencies(depName, new Set(visited)); } } /** * Set up circuit breaker for a service * @param {string} name - Service name * @param {*} instance - Service instance * @param {Object} circuitBreakerConfig - Circuit breaker configuration */ setupCircuitBreaker(name, instance, circuitBreakerConfig) { const CircuitBreaker = require('./CircuitBreaker'); const breaker = new CircuitBreaker({ name: `Service_${name}`, ...circuitBreakerConfig, onStateChange: (event) => { this.emit('circuitBreakerStateChange', { serviceName: name, ...event }); }, onFailure: (event) => { this.emit('circuitBreakerFailure', { serviceName: name, ...event }); } }); this.circuitBreakers.set(name, breaker); // Wrap service methods with circuit breaker this.wrapServiceWithCircuitBreaker(instance, breaker); } /** * Wrap service methods with circuit breaker * @param {*} instance - Service instance * @param {CircuitBreaker} breaker - Circuit breaker instance */ wrapServiceWithCircuitBreaker(instance, breaker) { const methodsToWrap = ['execute', 'run', 'process', 'analyze', 'generate']; for (const methodName of methodsToWrap) { if (typeof instance[methodName] === 'function') { const originalMethod = instance[methodName].bind(instance); instance[methodName] = (...args) => { return breaker.execute(originalMethod, ...args); }; } } } /** * Check if a service is registered * @param {string} name - Service name * @returns {boolean} Registration status */ has(name) { return this.factories.has(name) || this.services.has(name); } /** * Update startup order based on priority * @param {string} name - Service name * @param {number} priority - Startup priority */ updateStartupOrder(name, priority) { // Remove if already exists const existingIndex = this.startupOrder.findIndex(item => item.name === name); if (existingIndex !== -1) { this.startupOrder.splice(existingIndex, 1); } // Insert at correct position based on priority const insertIndex = this.startupOrder.findIndex(item => item.priority > priority); if (insertIndex === -1) { this.startupOrder.push({ name, priority }); } else { this.startupOrder.splice(insertIndex, 0, { name, priority }); } // Update shutdown order (reverse of startup) this.shutdownOrder = [...this.startupOrder].reverse(); } /** * Start all services in priority order */ async startServices() { console.log('Starting services...'); for (const { name } of this.startupOrder) { const serviceConfig = this.factories.get(name); if (!serviceConfig) continue; try { console.log(`Starting service: ${name}`); // Create instance with timeout const instance = await this.createInstanceWithTimeout(name, serviceConfig.startupTimeout); // Call startup method if it exists if (instance && typeof instance.startup === 'function') { await instance.startup(); } this.emit('serviceStarted', { name, instance }); console.log(`Service '${name}' started successfully`); } catch (error) { console.error(`Failed to start service '${name}':`, error); this.emit('serviceStartFailed', { name, error }); // Decide whether to continue or stop based on service criticality if (serviceConfig.critical !== false) { throw new Error(`Critical service '${name}' failed to start: ${error.message}`); } } } console.log('All services started successfully'); } /** * Create service instance with timeout * @param {string} name - Service name * @param {number} timeout - Timeout in milliseconds * @returns {Promise<*>} Service instance */ async createInstanceWithTimeout(name, timeout) { return Promise.race([ Promise.resolve(this.createInstance(name)), new Promise((_, reject) => { setTimeout(() => reject(new Error(`Service startup timeout`)), timeout); }) ]); } /** * Shutdown all services in reverse order */ async shutdown() { if (this.isShuttingDown) { return; } console.log('Shutting down services...'); this.isShuttingDown = true; for (const { name } of this.shutdownOrder) { try { await this.shutdownService(name); } catch (error) { console.error(`Error shutting down service '${name}':`, error); } } // Stop circuit breakers for (const [name, breaker] of this.circuitBreakers) { try { breaker.stopMonitoring(); } catch (error) { console.error(`Error stopping circuit breaker for '${name}':`, error); } } this.services.clear(); this.singletons.clear(); this.circuitBreakers.clear(); console.log('All services shut down'); this.emit('shutdown'); } /** * Shutdown a specific service * @param {string} name - Service name */ async shutdownService(name) { const instance = this.services.get(name) || this.singletons.get(name); if (!instance) return; const serviceConfig = this.factories.get(name); const timeout = serviceConfig?.shutdownTimeout || 10000; console.log(`Shutting down service: ${name}`); try { // Call shutdown method if it exists if (typeof instance.shutdown === 'function') { await Promise.race([ instance.shutdown(), new Promise((_, reject) => { setTimeout(() => reject(new Error('Shutdown timeout')), timeout); }) ]); } this.emit('serviceShutdown', { name, instance }); console.log(`Service '${name}' shut down successfully`); } catch (error) { console.error(`Error during shutdown of service '${name}':`, error); this.emit('serviceShutdownError', { name, error }); } // Remove from containers this.services.delete(name); this.singletons.delete(name); } /** * Get health status of all services */ async getHealthStatus() { const healthStatus = { status: 'healthy', services: {}, circuitBreakers: {}, summary: { total: 0, healthy: 0, unhealthy: 0, unknown: 0 } }; // Check service health for (const [name, serviceConfig] of this.factories) { const instance = this.services.get(name) || this.singletons.get(name); if (!instance) { healthStatus.services[name] = { status: 'not_started' }; healthStatus.summary.unknown++; continue; } healthStatus.summary.total++; try { let serviceHealth = { status: 'healthy' }; // Use custom health check if available if (serviceConfig.healthCheck) { serviceHealth = await serviceConfig.healthCheck(instance); } else if (typeof instance.getHealthStatus === 'function') { serviceHealth = await instance.getHealthStatus(); } else if (typeof instance.healthCheck === 'function') { serviceHealth = await instance.healthCheck(); } healthStatus.services[name] = serviceHealth; if (serviceHealth.status === 'healthy') { healthStatus.summary.healthy++; } else { healthStatus.summary.unhealthy++; } } catch (error) { healthStatus.services[name] = { status: 'unhealthy', error: error.message }; healthStatus.summary.unhealthy++; } } // Check circuit breaker status for (const [name, breaker] of this.circuitBreakers) { healthStatus.circuitBreakers[name] = breaker.getStatus(); } // Determine overall status if (healthStatus.summary.unhealthy > 0) { healthStatus.status = 'unhealthy'; } else if (healthStatus.summary.unknown > 0) { healthStatus.status = 'degraded'; } return healthStatus; } /** * Get service statistics */ getStats() { return { registered: this.factories.size, active: this.services.size, singletons: this.singletons.size, circuitBreakers: this.circuitBreakers.size, startupOrder: this.startupOrder.map(item => item.name), isShuttingDown: this.isShuttingDown }; } /** * List all registered services */ listServices() { const services = []; for (const [name, config] of this.factories) { const isActive = this.services.has(name) || this.singletons.has(name); const hasCircuitBreaker = this.circuitBreakers.has(name); services.push({ name, isActive, isSingleton: config.singleton, hasCircuitBreaker, dependencies: config.dependencies, startupPriority: config.startupPriority }); } return services.sort((a, b) => a.startupPriority - b.startupPriority); } } // Create singleton instance const serviceContainer = new ServiceContainer(); module.exports = serviceContainer;