Skip to main content

Service Provider System

The Hypermodern service provider system represents a significant advancement in module architecture, providing structured lifecycle management, dependency injection, and clean separation of concerns. This system bridges the gap between module metadata (manifests) and actual implementation, ensuring proper initialization order and resource cleanup.

Core Architecture

The Three-Phase Lifecycle

Service providers follow a structured lifecycle that ensures proper dependency management:

graph TD
    A[Module Loading] --> B[Registration Phase]
    B --> C[Initialization Phase]
    C --> D[Application Running]
    D --> E[Termination Phase]
    E --> F[Module Unloaded]
    
    B --> B1[Register Services]
    B --> B2[Configure Dependencies]
    B --> B3[No External Dependencies]
    
    C --> C1[Initialize Services]
    C --> C2[Connect to External Resources]
    C --> C3[Start Background Tasks]
    
    E --> E1[Stop Background Tasks]
    E --> E2[Close Connections]
    E --> E3[Clean Up Resources]

Service Provider Base Class

abstract class ModuleServiceProvider {
  final ModuleManifest manifest;
  final ModuleConfiguration config;
  
  // Lifecycle state
  bool get isRegistered;
  bool get isInitialized;
  bool get isTerminated;
  
  // Lifecycle methods
  Future<void> register();
  Future<void> initialize();
  Future<void> terminate();
  
  // Configuration access
  T getConfig<T>(String key);
  T getConfigOrDefault<T>(String key, T defaultValue);
  bool hasConfig(String key);
}

Advanced Service Provider Patterns

Factory Pattern Implementation

class DatabaseServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Register database factory
    registerSingleton<DatabaseFactory>(() => DatabaseFactory(
      defaultDriver: getConfig<String>('default_driver'),
      connections: getConfigMap<Map<String, dynamic>>('connections'),
    ));
    
    // Register connection manager
    registerSingleton<ConnectionManager>(() => ConnectionManager(
      factory: resolve<DatabaseFactory>(),
      poolSize: getConfigInt('pool_size', defaultValue: 10),
    ));
    
    // Register database service
    registerSingleton<DatabaseService>(() => DatabaseService(
      connectionManager: resolve<ConnectionManager>(),
    ));
  }
  
  @override
  Future<void> initialize() async {
    final connectionManager = resolve<ConnectionManager>();
    await connectionManager.initializeConnections();
    
    final dbService = resolve<DatabaseService>();
    await dbService.runHealthCheck();
  }
}

Plugin Architecture

class PluginServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Register plugin registry
    registerSingleton<PluginRegistry>(() => PluginRegistry());
    
    // Register plugin loader
    registerSingleton<PluginLoader>(() => PluginLoader(
      pluginDirectory: getConfig<String>('plugin_directory'),
      registry: resolve<PluginRegistry>(),
    ));
    
    // Register plugin manager
    registerSingleton<PluginManager>(() => PluginManager(
      loader: resolve<PluginLoader>(),
      registry: resolve<PluginRegistry>(),
    ));
  }
  
  @override
  Future<void> initialize() async {
    final pluginManager = resolve<PluginManager>();
    
    // Load and initialize plugins
    await pluginManager.loadPlugins();
    await pluginManager.initializePlugins();
    
    // Register plugin endpoints
    await pluginManager.registerEndpoints();
  }
  
  @override
  Future<void> terminate() async {
    final pluginManager = tryResolve<PluginManager>();
    if (pluginManager != null) {
      await pluginManager.shutdownPlugins();
      await pluginManager.unloadPlugins();
    }
  }
}

Event-Driven Service Provider

class EventServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Register event bus
    registerSingleton<EventBus>(() => EventBus(
      maxListeners: getConfigInt('max_listeners', defaultValue: 100),
      enableLogging: getConfigBool('enable_logging', defaultValue: false),
    ));
    
    // Register event dispatcher
    registerSingleton<EventDispatcher>(() => EventDispatcher(
      eventBus: resolve<EventBus>(),
    ));
    
    // Register event handlers
    _registerEventHandlers();
  }
  
  void _registerEventHandlers() {
    final handlers = getConfigList<String>('event_handlers');
    
    for (final handlerClass in handlers) {
      registerSingleton<EventHandler>(
        () => _createEventHandler(handlerClass),
        name: handlerClass,
      );
    }
  }
  
  @override
  Future<void> initialize() async {
    final eventBus = resolve<EventBus>();
    final dispatcher = resolve<EventDispatcher>();
    
    // Register all event handlers with the bus
    final handlerNames = getConfigList<String>('event_handlers');
    for (final handlerName in handlerNames) {
      final handler = resolveNamed<EventHandler>(handlerName);
      await eventBus.registerHandler(handler);
    }
    
    // Start event processing
    await dispatcher.start();
  }
  
  @override
  Future<void> terminate() async {
    final dispatcher = tryResolve<EventDispatcher>();
    if (dispatcher != null) {
      await dispatcher.stop();
    }
    
    final eventBus = tryResolve<EventBus>();
    if (eventBus != null) {
      await eventBus.shutdown();
    }
  }
}

Service Container Deep Dive

Advanced Container Features

class AdvancedServiceContainer extends ServiceContainer {
  // Scoped services
  void registerScoped<T>(T Function() factory, {String? name}) {
    // Implementation for request-scoped services
  }
  
  // Conditional registration
  void registerIf<T>(
    bool condition,
    T Function() factory, {
    bool singleton = false,
    String? name,
  }) {
    if (condition) {
      register<T>(factory, singleton: singleton, name: name);
    }
  }
  
  // Decorator pattern
  void decorate<T>(T Function(T original) decorator) {
    final original = resolve<T>();
    final decorated = decorator(original);
    registerInstance<T>(decorated);
  }
  
  // Lazy initialization
  void registerLazy<T>(Future<T> Function() factory, {String? name}) {
    // Implementation for lazy-loaded services
  }
}

Service Interception

class InterceptingServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Register interceptor
    registerSingleton<ServiceInterceptor>(() => LoggingInterceptor());
    
    // Register service with interception
    registerSingleton<UserService>(() => InterceptedUserService(
      original: UserService(),
      interceptor: resolve<ServiceInterceptor>(),
    ));
  }
}

class InterceptedUserService implements UserService {
  final UserService original;
  final ServiceInterceptor interceptor;
  
  InterceptedUserService({
    required this.original,
    required this.interceptor,
  });
  
  @override
  Future<User> getUser(int id) async {
    return await interceptor.intercept(
      'getUser',
      {'id': id},
      () => original.getUser(id),
    );
  }
}

Configuration Management

Advanced Configuration Patterns

class ConfigurationServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Register configuration sources
    registerSingleton<ConfigurationSource>(() => EnvironmentConfigurationSource());
    registerSingleton<ConfigurationSource>(() => FileConfigurationSource(
      filePath: getConfig<String>('config_file_path'),
    ), name: 'file');
    
    // Register configuration manager
    registerSingleton<ConfigurationManager>(() => ConfigurationManager([
      resolve<ConfigurationSource>(),
      resolveNamed<ConfigurationSource>('file'),
    ]));
    
    // Register typed configuration
    registerSingleton<DatabaseConfig>(() => DatabaseConfig.fromMap(
      resolve<ConfigurationManager>().getSection('database'),
    ));
  }
}

class DatabaseConfig {
  final String host;
  final int port;
  final String database;
  final String username;
  final String password;
  final int maxConnections;
  final Duration connectionTimeout;
  
  DatabaseConfig({
    required this.host,
    required this.port,
    required this.database,
    required this.username,
    required this.password,
    required this.maxConnections,
    required this.connectionTimeout,
  });
  
  factory DatabaseConfig.fromMap(Map<String, dynamic> config) {
    return DatabaseConfig(
      host: config['host'] as String,
      port: config['port'] as int,
      database: config['database'] as String,
      username: config['username'] as String,
      password: config['password'] as String,
      maxConnections: config['max_connections'] as int? ?? 10,
      connectionTimeout: Duration(
        seconds: config['connection_timeout_seconds'] as int? ?? 30,
      ),
    );
  }
}

Configuration Validation and Transformation

class ValidatingServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Validate configuration before registration
    await _validateConfiguration();
    
    // Transform configuration
    final transformedConfig = await _transformConfiguration();
    
    // Register services with validated config
    registerSingleton<MyService>(() => MyService(transformedConfig));
  }
  
  Future<void> _validateConfiguration() async {
    final validator = ConfigurationValidator([
      RequiredFieldRule('api_key'),
      TypeRule('port', int),
      RangeRule('port', min: 1, max: 65535),
      PatternRule('email', r'^[^@]+@[^@]+\.[^@]+$'),
      CustomRule('database_url', _validateDatabaseUrl),
    ]);
    
    final result = await validator.validate(config.getAllValues());
    if (!result.isValid) {
      throw ConfigurationException(
        'Configuration validation failed: ${result.errors.join(', ')}',
      );
    }
  }
  
  Future<Map<String, dynamic>> _transformConfiguration() async {
    final transformer = ConfigurationTransformer([
      EnvironmentVariableTransformer(),
      SecretDecryptionTransformer(),
      DefaultValueTransformer(),
    ]);
    
    return await transformer.transform(config.getAllValues());
  }
  
  bool _validateDatabaseUrl(dynamic value) {
    if (value is! String) return false;
    return Uri.tryParse(value)?.scheme != null;
  }
}

Testing Service Providers

Unit Testing

import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
import 'package:hypermodern/modules.dart';

class MockDatabaseService extends Mock implements DatabaseService {}
class MockConfigurationService extends Mock implements ConfigurationService {}

void main() {
  group('UserServiceProvider', () {
    late UserServiceProvider provider;
    late ServiceContainer container;
    late MockDatabaseService mockDatabase;
    late MockConfigurationService mockConfig;

    setUp(() {
      container = ServiceContainer.instance;
      container.clear();
      
      mockDatabase = MockDatabaseService();
      mockConfig = MockConfigurationService();
      
      // Pre-register mocks
      container.registerInstance<DatabaseService>(mockDatabase);
      container.registerInstance<ConfigurationService>(mockConfig);
      
      provider = UserServiceProvider(
        manifest: _createTestManifest(),
        config: _createTestConfig(),
      );
    });

    test('should register all required services', () async {
      await provider.register();
      
      expect(container.isRegistered<UserService>(), isTrue);
      expect(container.isRegistered<UserRepository>(), isTrue);
      expect(container.isRegistered<UserValidator>(), isTrue);
    });

    test('should initialize services with correct dependencies', () async {
      await provider.register();
      await provider.initialize();
      
      final userService = container.resolve<UserService>();
      expect(userService, isNotNull);
      
      // Verify dependencies were injected
      verify(mockDatabase.connect()).called(1);
    });

    test('should handle initialization errors gracefully', () async {
      when(mockDatabase.connect()).thenThrow(Exception('Connection failed'));
      
      await provider.register();
      
      expect(
        () => provider.initialize(),
        throwsA(isA<ServiceProviderException>()),
      );
    });

    test('should clean up resources during termination', () async {
      await provider.register();
      await provider.initialize();
      await provider.terminate();
      
      verify(mockDatabase.disconnect()).called(1);
      expect(provider.isTerminated, isTrue);
    });
  });
}

Integration Testing

void main() {
  group('Service Provider Integration', () {
    late ModuleManager manager;
    late TestDatabase testDatabase;

    setUp(() async {
      testDatabase = await TestDatabase.create();
      manager = ModuleManager.getInstance();
      await manager.initialize();
    });

    tearDown(() async {
      await manager.stop();
      await testDatabase.cleanup();
    });

    test('should load and start multiple modules', () async {
      // Load database module
      final dbResult = await manager.loadModule('database', DatabaseServiceProvider(
        manifest: _createDatabaseManifest(),
        config: _createDatabaseConfig(testDatabase.connectionString),
      ));
      expect(dbResult.success, isTrue);

      // Load user module (depends on database)
      final userResult = await manager.loadModule('users', UserServiceProvider(
        manifest: _createUserManifest(),
        config: _createUserConfig(),
      ));
      expect(userResult.success, isTrue);

      // Start all modules
      await manager.start();

      // Verify services are available
      final userService = ServiceLocator.get<UserService>();
      expect(userService, isNotNull);

      // Test service functionality
      final user = await userService.createUser(CreateUserRequest(
        email: 'test@example.com',
        username: 'testuser',
      ));
      expect(user.id, isNotNull);
    });

    test('should handle module dependency resolution', () async {
      // Load modules in wrong order (user before database)
      final userResult = await manager.loadModule('users', UserServiceProvider(
        manifest: _createUserManifest(),
        config: _createUserConfig(),
      ));
      expect(userResult.success, isTrue);

      final dbResult = await manager.loadModule('database', DatabaseServiceProvider(
        manifest: _createDatabaseManifest(),
        config: _createDatabaseConfig(testDatabase.connectionString),
      ));
      expect(dbResult.success, isTrue);

      // Should still start successfully due to proper dependency resolution
      await manager.start();

      final userService = ServiceLocator.get<UserService>();
      expect(userService, isNotNull);
    });
  });
}

Performance Considerations

Lazy Loading

class LazyLoadingServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Register expensive service as lazy
    registerLazy<ExpensiveService>(() async {
      final service = ExpensiveService();
      await service.initialize();
      return service;
    });
    
    // Register proxy that loads on demand
    registerSingleton<ExpensiveServiceProxy>(() => ExpensiveServiceProxy(
      loader: () => resolve<ExpensiveService>(),
    ));
  }
}

class ExpensiveServiceProxy implements ExpensiveService {
  final Future<ExpensiveService> Function() loader;
  ExpensiveService? _instance;
  
  ExpensiveServiceProxy({required this.loader});
  
  @override
  Future<String> performExpensiveOperation() async {
    _instance ??= await loader();
    return await _instance!.performExpensiveOperation();
  }
}

Service Caching

class CachingServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    // Register cache
    registerSingleton<Cache>(() => MemoryCache(
      maxSize: getConfigInt('cache_max_size', defaultValue: 1000),
      ttl: getConfigDuration('cache_ttl'),
    ));
    
    // Register cached service
    registerSingleton<UserService>(() => CachedUserService(
      original: UserService(),
      cache: resolve<Cache>(),
    ));
  }
}

Best Practices and Patterns

Service Provider Composition

class CompositeServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  final List<ModuleServiceProvider> providers;
  
  CompositeServiceProvider({
    required super.manifest,
    required super.config,
    required this.providers,
  });
  
  @override
  Future<void> register() async {
    for (final provider in providers) {
      await provider.register();
    }
  }
  
  @override
  Future<void> initialize() async {
    for (final provider in providers) {
      await provider.initialize();
    }
  }
  
  @override
  Future<void> terminate() async {
    // Terminate in reverse order
    for (final provider in providers.reversed) {
      await provider.terminate();
    }
  }
}

Health Check Integration

class HealthCheckServiceProvider extends ModuleServiceProvider with ServiceContainerMixin {
  @override
  Future<void> register() async {
    registerSingleton<HealthCheckService>(() => HealthCheckService());
  }
  
  @override
  Future<void> initialize() async {
    final healthCheck = resolve<HealthCheckService>();
    
    // Register health checks for all services
    healthCheck.register('database', () async {
      final db = resolve<DatabaseService>();
      return await db.isHealthy();
    });
    
    healthCheck.register('cache', () async {
      final cache = resolve<CacheService>();
      return await cache.ping();
    });
    
    // Start health check endpoint
    await healthCheck.start();
  }
}

Conclusion

The service provider system in Hypermodern provides a robust foundation for building modular, maintainable applications. By following the structured lifecycle, leveraging dependency injection, and implementing proper error handling, you can create services that are easy to test, configure, and maintain.

The system's flexibility allows for various architectural patterns while maintaining consistency and reliability across your application's modules.