ServiceRegistry.ts

Singleton service registry for micro-block architecture with lazy initialization, dependency injection, and enhanced logging support including ICommandLogger

ServiceRegistry.tsv1.1.08.8 KB
ServiceRegistry.ts(typescript)
1import { AppConfig } from "@/config/index";
2import { ConsoleLogger } from "../logging/ConsoleLogger";
3import { ILogger, ICommandLogger, ILoggerFactory } from '../logging/ILogger';
4import { LoggerFactory } from '../logging/LoggerFactory';
5import { CommandRegistry } from "./CommandRegistry";
6import { BaseService, ServiceMetadata } from "../interfaces/BaseService";
7
8// Example service interface - replace with your own services
9interface ICacheService {
10    get(key: string): Promise<string | null>;
11    set(key: string, value: string, ttl?: number): Promise<void>;
12    delete(key: string): Promise<void>;
13    clear(): Promise<void>;
14}
15
16// Example cache service implementation - replace with your own
17class CacheService implements ICacheService {
18    private cache = new Map<string, { value: string; expires?: number }>();
19    
20    async get(key: string): Promise<string | null> {
21        const item = this.cache.get(key);
22        if (!item) return null;
23        
24        if (item.expires && item.expires < Date.now()) {
25            this.cache.delete(key);
26            return null;
27        }
28        
29        return item.value;
30    }
31    
32    async set(key: string, value: string, ttl?: number): Promise<void> {
33        const expires = ttl ? Date.now() + ttl * 1000 : undefined;
34        this.cache.set(key, { value, expires });
35    }
36    
37    async delete(key: string): Promise<void> {
38        this.cache.delete(key);
39    }
40    
41    async clear(): Promise<void> {
42        this.cache.clear();
43    }
44}
45
46export class ServiceRegistry extends BaseService {
47    getMetadata(): ServiceMetadata {
48        return {
49            name: 'ServiceRegistry',
50            displayName: 'Service Registry',
51            description: 'Central registry for managing and providing access to application services',
52            contract: 'ServiceRegistry',
53            version: '1.0.0',
54            contractVersion: '1.0'
55        } as ServiceMetadata;         
56    }
57    
58    isHealthy(): Promise<boolean> | boolean {
59        return true;
60    }
61    
62    destroy(): Promise<void> | void {
63        this.clear();
64    }
65
66    private static instance: ServiceRegistry | null = null;
67    private config: AppConfig = AppConfig.getInstance();
68    private serviceFactories = new Map<string, any>();
69    private services = new Map<string, any>();
70    private logger: ILogger = new ConsoleLogger('ServiceRegistry');
71    
72    private constructor() {
73        super();
74    }
75    
76    public configure(appConfig: AppConfig): void {
77        this.config = appConfig;
78        this.logger.info('ServiceRegistry configured', { environment: appConfig.server.environment });
79        this.clear();
80        this.initialize();
81    }
82    
83    public clear(): void {
84        this.services.clear();
85        this.serviceFactories.clear();
86        this.logger.info('ServiceRegistry cleared');
87    }
88    
89    public getConfig(): AppConfig {
90        return this.config;
91    }
92
93    public static getInstance(): ServiceRegistry {
94        if (!ServiceRegistry.instance) {
95            const instance = new ServiceRegistry();
96            instance.initialize();
97            ServiceRegistry.instance = instance;
98        }
99        return ServiceRegistry.instance;
100    }
101    
102    public initialize(): void {
103        this.logger.info('Initializing ServiceRegistry');
104        
105        // Register logger factory
106        this.register('ILoggerFactory', () => {
107            return new LoggerFactory(this.getConfig());
108        });
109        
110        // Register command registry
111        this.register('CommandRegistry', () => {
112            const registry = CommandRegistry.createInstance();
113            registry.setServiceResolver((serviceName: string) => {
114                return this.get(serviceName);
115            });
116            return registry;
117        });
118
119        // Example service registration - replace with your own services
120        this.register('ICacheService', () => {
121            return new CacheService();
122        });
123        
124        // Add your own service registrations here following the pattern:
125        // this.register('IYourService', () => {
126        //     const config = this.getConfig().yourService;
127        //     const logger = this.getLogger('YourService');
128        //     return new YourService(config, logger);
129        // });
130    }
131    
132    getCommandRegistry(): CommandRegistry {
133        return this.get('CommandRegistry') as CommandRegistry;
134    }
135    
136    /** Gets an ILogger which is also a ICommandLogger */
137    getLogger(serviceName: string): ILogger {
138        // For UCM MVP, we only support console logging
139        let loggerFactory = this.get<ILoggerFactory>('ILoggerFactory');
140        if (loggerFactory) {
141            return loggerFactory.getLogger(serviceName);
142        } else {
143            this.logger.warn(`ILoggerFactory not registered, using ConsoleLogger for ${serviceName}`);
144            return new ConsoleLogger(serviceName);
145        }
146    }   
147    /** Gets an ICommandLogger which is also an ILogger */
148    getCommandLogger(serviceName: string): ICommandLogger {
149        return this.getLogger(serviceName) as ICommandLogger;
150    }
151    register(serviceName: string, factory: any): void {
152        this.serviceFactories.set(serviceName, factory);
153        this.logger.debug(`Service factory registered: ${serviceName}`);
154    }
155
156    registerWithValidation<T extends BaseService>(
157        serviceName: string,
158        serviceClass: new (...args: any[]) => T,
159        configFactory: (appConfig: AppConfig) => any,
160        dependencyResolver?: (registry: ServiceRegistry) => any[]
161    ): void {
162        this.register(serviceName, () => {
163            try {
164                const serviceConfig = configFactory(this.config);
165                const logger = this.getLogger(serviceName);
166                
167                let dependencies: any[] = [];
168                if (dependencyResolver) {
169                    dependencies = dependencyResolver(this);
170                }
171                
172                const service = new serviceClass(serviceConfig, logger, ...dependencies);
173                
174                if (service.getMetadata) {
175                    const metadata = service.getMetadata();
176                    this.validateServiceMetadata(serviceName, metadata);
177                }
178                
179                return service;
180            } catch (error) {
181                this.logger.error(`Failed to register service '${serviceName}': ${error instanceof Error ? error.message : String(error)}`);
182                throw error;
183            }
184        });
185    }
186
187    private validateServiceMetadata(serviceName: string, metadata: ServiceMetadata): void {
188        if (!metadata) {
189            throw new Error(`Service '${serviceName}' must provide metadata`);
190        }
191
192        const requiredFields = ['name', 'displayName', 'description', 'contract', 'version', 'contractVersion'];
193        for (const field of requiredFields) {
194            if (!metadata[field as keyof ServiceMetadata]) {
195                throw new Error(`Service '${serviceName}' metadata missing required field: ${field}`);
196            }
197        }
198
199        if (metadata.dependencies) {
200            if (metadata.dependencies.services && !Array.isArray(metadata.dependencies.services)) {
201                throw new Error(`Service '${serviceName}' metadata dependencies.services must be an array`);
202            }
203        }
204
205        this.logger.debug(`Service metadata validated for: ${serviceName}`);
206    }
207
208    getAllServiceKeys(): string[] {
209        let serviceKeys: string[] = [];
210        for (let [key] of this.serviceFactories.entries()) {
211            try {
212                this.get(key);
213                serviceKeys.push(key);
214            } catch (error) {
215                this.logger.error(`Failed to get service '${key}': ${error instanceof Error ? error.message : String(error)}`);
216            }
217        }
218        return serviceKeys;
219    }
220
221    get<T = any>(serviceName: string): T {
222        if (serviceName == 'ServiceRegistry' || serviceName == 'IServiceRegistry') return this as unknown as T;
223        
224        if (this.services.has(serviceName)) {
225            return this.services.get(serviceName);
226        }
227
228        const factory = this.serviceFactories.get(serviceName);
229        if (!factory) {
230            const availableServices = Array.from(this.serviceFactories.keys());
231            const error = `Service '${serviceName}' not registered. Available: ${availableServices.join(', ')}`;
232            this.logger.error(error);
233            throw new Error(error);
234        }
235
236        try {
237            const service = factory(this);
238            this.services.set(serviceName, service);
239            this.logger.debug(`Service created: ${serviceName}`);
240            return service;
241        } catch (error) {
242            const errorMessage = `Failed to create service '${serviceName}': ${error instanceof Error ? error.message : String(error)}`;
243            this.logger.error(errorMessage);
244            throw new Error(errorMessage);
245        }
246    }
247}

Metadata

Path
utaba/main/services/micro-block/ServiceRegistry.ts
Namespace
utaba/main/services/micro-block
Author
utaba
Category
services
Technology
typescript
Contract Version
1.1.0
MIME Type
application/typescript
Published
23-Jul-2025
Last Updated
23-Jul-2025