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