service-registry-patterns.md

Comprehensive patterns for ServiceRegistry implementation and usage in micro-block architecture

service-registry-patterns.mdv1.0.011.5 KB
service-registry-patterns.md(markdown)
1# ServiceRegistry Patterns for Micro-Block Architecture
2
3## Overview
4
5The ServiceRegistry serves as the central dependency injection container and primary access point for all services and commands in the micro-block architecture. It acts as a configuration adapter that enables true service portability.
6
7## Core Responsibilities
8
91. **Central Access Point**: Single entry point for all service and command access
102. **Configuration Adapter**: Translates project-specific configuration to service-specific interfaces
113. **Dependency Injection**: Provides services and commands with their dependencies
124. **Service Lifecycle**: Manages service instantiation and caching
135. **Command Registry Access**: Provides controlled access to CommandRegistry
14
15## ServiceRegistry Interface
16
17```typescript
18interface ServiceRegistry {
19  // Singleton access - Primary entry point
20  static getInstance(): ServiceRegistry;
21  
22  // Service management
23  register<T>(serviceName: string, factory: () => T): void;
24  get<T>(serviceName: string): T;
25  
26  // Command registry access - THE ONLY CORRECT WAY
27  getCommandRegistry(): CommandRegistry;
28  
29  // Logger access
30  getLogger<T>(serviceName: string): ILogger;
31  
32  // Configuration access
33  getConfig(): any;
34}
35```
36
37## Configuration Adapter Pattern
38
39### Why Configuration Adaptation Matters
40
41The ServiceRegistry serves a **critical architectural role** as a configuration adapter that enables true service portability:
42
431. **Configuration Translation**: Translates project-specific configuration into service-specific interfaces
442. **Dependency Isolation**: Services never directly access project configuration
453. **Reusability Bridge**: Allows services to work in any project regardless of config structure
464. **Default Value Injection**: Provides sensible defaults for optional service properties
475. **Validation Layer**: Configuration mapping happens in one controlled location
48
49### Configuration Adapter Implementation
50
51```typescript
52private initialize(): void {
53  // Database Service Configuration Adapter
54  this.register('IDatabaseService', () => {
55    // 1. Take project-specific configuration
56    const projectDbConfig = this.getConfig().database;
57    
58    // 2. Map to service-specific configuration interface
59    const serviceConfig: DatabaseServiceConfig = {
60      connectionString: projectDbConfig.connectionString,    // Direct mapping
61      server: projectDbConfig.server,                       // Direct mapping
62      maxConnections: 10,                                   // Default value
63      connectionTimeout: projectDbConfig.options?.connectionTimeout || 30000,  // Default fallback
64      enableHealthMonitoring: true,                         // Service-specific enhancement
65      retryAttempts: 3                                      // Service best practice
66    };
67    
68    // 3. Create portable service instance
69    const logger = this.getLogger('DatabaseService');
70    const { DatabaseService } = require('@/services/DatabaseService');
71    return new DatabaseService(serviceConfig, logger);
72  });
73  
74  // Cache Service Configuration Adapter
75  this.register('ICacheService', () => {
76    const projectCacheConfig = this.getConfig().cache;
77    
78    const serviceConfig: CacheServiceConfig = {
79      maxMemoryMB: projectCacheConfig.maxMemoryMB || 100,
80      defaultTtlSeconds: projectCacheConfig.defaultTtlSeconds || 3600,
81      evictionPolicy: projectCacheConfig.evictionPolicy || 'LRU',
82      enableMetrics: projectCacheConfig.enableMetrics ?? true
83    };
84    
85    const logger = this.getLogger('CacheService');
86    const metricsService = this.get<IMetricsService>('IMetricsService');
87    const { MemoryCacheService } = require('@/services/MemoryCacheService');
88    return new MemoryCacheService(serviceConfig, logger, metricsService);
89  });
90}
91```
92
93## Registry Access Patterns
94
95### ✅ CORRECT ACCESS PATTERNS
96
97#### Pattern 1: Constructor Injection (Frequent Command Users)
98
99```typescript
100export class JobManagerService extends BaseService {
101  constructor(
102    private config: JobManagerConfig,
103    private logger: ILogger,
104    private commandRegistry: CommandRegistry  // ✅ Injected by ServiceRegistry
105  ) {
106    super();
107  }
108  
109  async processJobs(): Promise<void> {
110    // CommandRegistry readily available for all methods
111    const command = await this.commandRegistry.get(ProcessJobCommand, input);
112    await command.execute();
113  }
114}
115
116// ServiceRegistry registration with CommandRegistry injection
117this.register('IJobManagerService', () => {
118  const config = this.mapJobManagerConfig(this.getConfig().jobManager);
119  const logger = this.getLogger('JobManagerService');
120  const commandRegistry = this.getCommandRegistry(); // ✅ Provided by ServiceRegistry
121  
122  return new JobManagerService(config, logger, commandRegistry);
123});
124```
125
126#### Pattern 2: Runtime Access (Occasional Command Users)
127
128```typescript
129export class MonitorService extends BaseService {
130  private async getTokenUsageStats(params: Record<string, any>): Promise<any> {
131    // ✅ CORRECT: Get CommandRegistry through ServiceRegistry
132    const serviceRegistry = ServiceRegistry.getInstance();
133    const commandRegistry = serviceRegistry.getCommandRegistry();
134    
135    const { GetTokenUsageStatsCommand } = await import('@/commands/analytics/GetTokenUsageStatsCommand');
136    const command = await commandRegistry.get(GetTokenUsageStatsCommand, params);
137    return await command.execute();
138  }
139}
140```
141
142#### Pattern 3: API Route Access
143
144```typescript
145// API routes should use ServiceRegistry for consistency
146export async function POST(request: Request) {
147  try {
148    const serviceRegistry = ServiceRegistry.getInstance();
149    const commandRegistry = serviceRegistry.getCommandRegistry();
150    
151    const { CreateUserCommand } = await import('@/commands/user/CreateUserCommand');
152    const command = await commandRegistry.get(CreateUserCommand, input);
153    
154    const result = await command.execute();
155    return Response.json(result);
156  } catch (error) {
157    return handleError(error);
158  }
159}
160```
161
162### ❌ INCORRECT ACCESS PATTERNS
163
164```typescript
165// WRONG: Direct CommandRegistry access bypasses ServiceRegistry
166const commandRegistry = CommandRegistry.getInstance();
167
168// WRONG: Direct command instantiation bypasses registry entirely
169const command = new SomeCommand();
170
171// WRONG: Multiple pathways to same object creates inconsistency
172```
173
174## Service Registration Patterns
175
176### Service Factory Pattern
177
178```typescript
179export class ServiceRegistry {
180  private serviceFactories = new Map<string, any>();
181  private services = new Map<string, any>();
182  
183  register<T>(serviceName: string, factory: (registry: ServiceRegistry) => T): void {
184    this.serviceFactories.set(serviceName, factory);
185  }
186  
187  get<T>(serviceName: string): T {
188    // Return cached instance if exists
189    if (this.services.has(serviceName)) {
190      return this.services.get(serviceName);
191    }
192    
193    // Create and cache service
194    const factory = this.serviceFactories.get(serviceName);
195    if (!factory) {
196      throw new Error(`Service ${serviceName} not registered`);
197    }
198    
199    const service = factory(this);
200    this.services.set(serviceName, service);
201    return service;
202  }
203}
204```
205
206### Service with Service Dependencies
207
208```typescript
209this.register('IEmailService', () => {
210  // Map configuration
211  const serviceConfig: EmailServiceConfig = {
212    smtpHost: this.getConfig().email.smtpHost,
213    smtpPort: this.getConfig().email.smtpPort,
214    username: this.getConfig().email.username,
215    password: this.getConfig().email.password,
216    fromAddress: this.getConfig().email.fromAddress
217  };
218  
219  // Inject dependencies
220  const logger = this.getLogger('EmailService');
221  const templateService = this.get<ITemplateService>('ITemplateService');
222  const metricsService = this.get<IMetricsService>('IMetricsService');
223  
224  const { EmailService } = require('@/services/EmailService');
225  return new EmailService(serviceConfig, logger, templateService, metricsService);
226});
227```
228
229## CommandRegistry Integration
230
231### CommandRegistry Registration
232
233```typescript
234this.register('CommandRegistry', () => {
235  const { CommandRegistry } = require('@/core/registry/CommandRegistry');
236  const registry = CommandRegistry.createInstance();
237  
238  // Set service resolver for dependency injection
239  registry.setServiceResolver((serviceName: string) => {
240    return this.get(serviceName);
241  });
242  
243  return registry;
244});
245```
246
247### Command Registry Access
248
249```typescript
250getCommandRegistry(): CommandRegistry {
251  return this.get<CommandRegistry>('CommandRegistry');
252}
253```
254
255## Testing Patterns
256
257### ServiceRegistry Testing
258
259```typescript
260describe('ServiceRegistry', () => {
261  let registry: ServiceRegistry;
262  
263  beforeEach(() => {
264    registry = new ServiceRegistry();
265    registry.initialize();
266  });
267  
268  afterEach(() => {
269    registry.destroy();
270  });
271  
272  it('should provide singleton access', () => {
273    const instance1 = ServiceRegistry.getInstance();
274    const instance2 = ServiceRegistry.getInstance();
275    expect(instance1).toBe(instance2);
276  });
277  
278  it('should cache service instances', () => {
279    const service1 = registry.get('ICacheService');
280    const service2 = registry.get('ICacheService');
281    expect(service1).toBe(service2);
282  });
283  
284  it('should provide CommandRegistry access', () => {
285    const commandRegistry = registry.getCommandRegistry();
286    expect(commandRegistry).toBeDefined();
287    expect(commandRegistry.constructor.name).toBe('CommandRegistry');
288  });
289});
290```
291
292### Service Factory Testing
293
294```typescript
295describe('Service Factory', () => {
296  it('should create service with proper configuration', () => {
297    const registry = new ServiceRegistry();
298    
299    registry.register('ITestService', () => {
300      const config: TestServiceConfig = {
301        setting1: 'value1',
302        setting2: 42
303      };
304      return new TestService(config);
305    });
306    
307    const service = registry.get<ITestService>('ITestService');
308    expect(service).toBeInstanceOf(TestService);
309    expect(service.isHealthy()).toBe(true);
310  });
311});
312```
313
314## Best Practices
315
316### Service Design
3171. **Configuration Interfaces**: Each service defines its own configuration interface
3182. **Dependency Injection**: Services receive all dependencies via constructor
3193. **Health Monitoring**: Services implement health checks
3204. **Graceful Shutdown**: Services implement proper cleanup in destroy()
3215. **Logging Integration**: Services receive logger instances
322
323### Registry Management
3241. **Single Instance**: Use singleton pattern for ServiceRegistry
3252. **Lazy Loading**: Services are created on first access
3263. **Error Handling**: Throw clear errors for missing services
3274. **Configuration Validation**: Validate configuration during service creation
3285. **Lifecycle Management**: Properly initialize and destroy services
329
330### Access Patterns
3311. **Consistent Access**: Always use ServiceRegistry.getInstance()
3322. **Command Registry**: Access CommandRegistry only through ServiceRegistry
3333. **Service Dependencies**: Register services with their dependencies
3344. **Testing Support**: Design for easy testing with mock services
3355. **Performance**: Cache service instances for performance
336
337### Anti-Patterns to Avoid
3381. **Direct Registry Access**: Never call CommandRegistry.getInstance()
3392. **Service Location**: Don't use registry as service locator in business logic
3403. **Circular Dependencies**: Avoid circular service dependencies
3414. **Configuration Coupling**: Don't couple services to project configuration
3425. **Multiple Registries**: Use only one ServiceRegistry instance
343
344This ServiceRegistry pattern ensures consistent, testable, and maintainable access to services and commands while enabling true service portability through configuration adaptation.

Metadata

Path
utaba/main/patterns/micro-block/service-registry-patterns.md
Namespace
utaba/main/patterns/micro-block
Author
utaba
Category
patterns
Technology
typescript
Contract Version
1.0.0
MIME Type
text/markdown
Published
18-Jul-2025
Last Updated
18-Jul-2025