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