Factory Pattern Implementation¶
This document describes the implementation of various Factory patterns in the Open Host Factory Plugin, including Simple Factory, Factory Method, and Abstract Factory patterns for creating objects based on configuration and runtime conditions.
Factory Pattern Overview¶
The plugin uses factory patterns to:
- Encapsulate object creation: Hide complex creation logic behind simple interfaces
- Configuration-driven creation: Create objects based on configuration parameters
- Runtime selection: Choose implementations at runtime based on conditions
- Dependency management: Handle complex dependency injection scenarios
Provider Strategy Factory¶
The Provider Strategy Factory creates appropriate provider strategies based on configuration.
Factory Implementation¶
# src/infrastructure/factories/provider_strategy_factory.py
from typing import Dict, Any, List
from src.domain.base.ports import LoggingPort, ConfigurationPort
from src.providers.base.strategy.provider_strategy import ProviderStrategy
from src.providers.aws.strategy.aws_provider_strategy import AWSProviderStrategy
from src.providers.aws.configuration.config import AWSProviderConfig
class ProviderStrategyFactory:
"""Factory for creating provider strategies from configuration."""
def __init__(self,
config_manager: ConfigurationPort,
logger: LoggingPort):
self._config_manager = config_manager
self._logger = logger
self._strategy_registry: Dict[str, callable] = {}
self._register_strategy_creators()
def _register_strategy_creators(self):
"""Register strategy creation functions."""
self._strategy_registry = {
"aws": self._create_aws_strategy,
# Future providers can be added here
# "azure": self._create_azure_strategy,
# "gcp": self._create_gcp_strategy,
}
def create_strategy(self, provider_type: str, config_override: Dict[str, Any] = None) -> ProviderStrategy:
"""Create provider strategy based on type and configuration."""
self._logger.info(f"Creating provider strategy: {provider_type}")
if provider_type not in self._strategy_registry:
available_types = list(self._strategy_registry.keys())
raise ValueError(f"Unsupported provider type: {provider_type}. Available: {available_types}")
creator_func = self._strategy_registry[provider_type]
strategy = creator_func(config_override)
self._logger.info(f"Created provider strategy: {provider_type}")
return strategy
def _create_aws_strategy(self, config_override: Dict[str, Any] = None) -> AWSProviderStrategy:
"""Create AWS provider strategy with configuration."""
# Get base configuration
aws_config_data = self._config_manager.get_section("aws")
# Apply overrides if provided
if config_override:
aws_config_data.update(config_override)
# Validate required configuration
self._validate_aws_config(aws_config_data)
# Create configuration object
aws_config = AWSProviderConfig(**aws_config_data)
# Create and return strategy
return AWSProviderStrategy(
config=aws_config,
logger=self._logger
)
def _validate_aws_config(self, config: Dict[str, Any]) -> None:
"""Validate AWS configuration parameters."""
required_fields = ["region"]
for field in required_fields:
if field not in config:
raise ValueError(f"Missing required AWS configuration field: {field}")
def get_available_providers(self) -> List[str]:
"""Get list of available provider types."""
return list(self._strategy_registry.keys())
def get_provider_info(self) -> Dict[str, Any]:
"""Get information about available providers."""
return {
"available_providers": self.get_available_providers(),
"default_provider": self._config_manager.get("provider.default", "aws")
}
Factory Registration and Usage¶
# src/infrastructure/di/provider_services.py
def create_provider_strategy_factory(container: DIContainer) -> ProviderStrategyFactory:
"""Factory function for provider strategy factory."""
return ProviderStrategyFactory(
config_manager=container.get(ConfigurationPort),
logger=container.get(LoggingPort)
)
# Registration in DI container
container.register_factory(ProviderStrategyFactory, create_provider_strategy_factory)
# Usage
strategy_factory = container.get(ProviderStrategyFactory)
aws_strategy = strategy_factory.create_strategy("aws")
AWS Handler Factory¶
The AWS Handler Factory creates appropriate handlers for different provisioning methods.
Handler Factory Implementation¶
# src/providers/aws/infrastructure/aws_handler_factory.py
from typing import Dict, Type, Any
from src.domain.base.dependency_injection import injectable
from src.domain.base.ports import LoggingPort, ConfigurationPort
from src.providers.aws.infrastructure.aws_client import AWSClient
from src.providers.aws.infrastructure.handlers.base_handler import AWSHandler
from src.domain.template.aggregate import Template
@injectable
class AWSHandlerFactory:
"""Factory for creating AWS handlers based on template requirements."""
def __init__(self,
aws_client: AWSClient,
logger: LoggingPort,
config: ConfigurationPort):
self._aws_client = aws_client
self._logger = logger
self._config = config
self._handler_registry: Dict[str, Type[AWSHandler]] = {}
self._handler_cache: Dict[str, AWSHandler] = {}
self._register_handlers()
def _register_handlers(self):
"""Register available handler implementations."""
from src.providers.aws.infrastructure.handlers.ec2_fleet_handler import EC2FleetHandler
from src.providers.aws.infrastructure.handlers.spot_fleet_handler import SpotFleetHandler
from src.providers.aws.infrastructure.handlers.asg_handler import ASGHandler
from src.providers.aws.infrastructure.handlers.run_instances_handler import RunInstancesHandler
self._handler_registry = {
"ec2_fleet": EC2FleetHandler,
"spot_fleet": SpotFleetHandler,
"auto_scaling_group": ASGHandler,
"run_instances": RunInstancesHandler
}
self._logger.info(f"Registered {len(self._handler_registry)} handler types")
def create_handler(self, handler_type: str) -> AWSHandler:
"""Create handler for specified type."""
self._logger.debug(f"Creating handler for type: {handler_type}")
# Check if handler type is supported
if handler_type not in self._handler_registry:
available_types = list(self._handler_registry.keys())
raise ValueError(f"Unsupported handler type: {handler_type}. Available: {available_types}")
# Check cache first
if handler_type in self._handler_cache:
return self._handler_cache[handler_type]
# Create new handler instance
handler_class = self._handler_registry[handler_type]
handler = handler_class(
aws_client=self._aws_client,
logger=self._logger,
config=self._config
)
# Cache for reuse
self._handler_cache[handler_type] = handler
self._logger.debug(f"Created handler: {handler_type}")
return handler
def create_handler_for_template(self, template: Template) -> AWSHandler:
"""Create appropriate handler based on template configuration."""
self._logger.debug(f"Creating handler for template: {template.template_id}")
# Determine handler type from template
handler_type = self._determine_handler_type(template)
# Create and return handler
return self.create_handler(handler_type)
def _determine_handler_type(self, template: Template) -> str:
"""Determine appropriate handler type based on template attributes."""
attributes = template.attributes
# Check for spot instance preference
if attributes.get("use_spot_instances", False):
return "spot_fleet"
# Check for auto scaling preference
if attributes.get("use_auto_scaling", False):
return "auto_scaling_group"
# Check for fleet preference (default)
if attributes.get("use_fleet", True):
return "ec2_fleet"
# Fallback to run instances
return "run_instances"
def get_available_handlers(self) -> List[str]:
"""Get list of available handler types."""
return list(self._handler_registry.keys())
def get_handler_info(self) -> Dict[str, Any]:
"""Get information about available handlers."""
return {
"available_handlers": self.get_available_handlers(),
"cached_handlers": list(self._handler_cache.keys()),
"default_handler": self._config.get("aws.handlers.default", "ec2_fleet")
}
Repository Factory¶
The Repository Factory creates appropriate repository implementations based on storage configuration.
Repository Factory Implementation¶
# src/infrastructure/utilities/factories/repository_factory.py
from typing import Dict, Type, Any
from src.domain.base.ports import LoggingPort, ConfigurationPort
from src.domain.template.repository import TemplateRepository
from src.domain.request.repository import RequestRepository
from src.domain.machine.repository import MachineRepository
class RepositoryFactory:
"""Factory for creating repository implementations based on storage type."""
def __init__(self,
config: ConfigurationPort,
logger: LoggingPort):
self._config = config
self._logger = logger
self._repository_registry: Dict[str, Dict[str, Type]] = {}
self._register_repository_implementations()
def _register_repository_implementations(self):
"""Register available repository implementations."""
# Import implementations
from src.infrastructure.persistence.dynamodb.template_repository import DynamoDBTemplateRepository
from src.infrastructure.persistence.dynamodb.request_repository import DynamoDBRequestRepository
from src.infrastructure.persistence.dynamodb.machine_repository import DynamoDBMachineRepository
from src.infrastructure.persistence.memory.template_repository import InMemoryTemplateRepository
from src.infrastructure.persistence.memory.request_repository import InMemoryRequestRepository
from src.infrastructure.persistence.memory.machine_repository import InMemoryMachineRepository
self._repository_registry = {
"dynamodb": {
"template": DynamoDBTemplateRepository,
"request": DynamoDBRequestRepository,
"machine": DynamoDBMachineRepository
},
"memory": {
"template": InMemoryTemplateRepository,
"request": InMemoryRequestRepository,
"machine": InMemoryMachineRepository
}
}
self._logger.info(f"Registered repository implementations for storage types: {list(self._repository_registry.keys())}")
def create_template_repository(self) -> TemplateRepository:
"""Create template repository based on configuration."""
storage_type = self._get_storage_type()
return self._create_repository("template", storage_type)
def create_request_repository(self) -> RequestRepository:
"""Create request repository based on configuration."""
storage_type = self._get_storage_type()
return self._create_repository("request", storage_type)
def create_machine_repository(self) -> MachineRepository:
"""Create machine repository based on configuration."""
storage_type = self._get_storage_type()
return self._create_repository("machine", storage_type)
def _create_repository(self, repo_type: str, storage_type: str) -> Any:
"""Create repository of specified type and storage implementation."""
self._logger.debug(f"Creating {repo_type} repository with {storage_type} storage")
# Validate storage type
if storage_type not in self._repository_registry:
available_types = list(self._repository_registry.keys())
raise ValueError(f"Unsupported storage type: {storage_type}. Available: {available_types}")
# Validate repository type
storage_implementations = self._repository_registry[storage_type]
if repo_type not in storage_implementations:
available_repos = list(storage_implementations.keys())
raise ValueError(f"Repository type {repo_type} not available for storage {storage_type}. Available: {available_repos}")
# Get repository class
repo_class = storage_implementations[repo_type]
# Create repository with appropriate configuration
if storage_type == "dynamodb":
return self._create_dynamodb_repository(repo_class)
elif storage_type == "memory":
return self._create_memory_repository(repo_class)
else:
raise ValueError(f"Unknown storage type: {storage_type}")
def _create_dynamodb_repository(self, repo_class: Type) -> Any:
"""Create DynamoDB repository with configuration."""
dynamodb_config = self._config.get_section("storage.dynamodb")
return repo_class(
table_name=dynamodb_config.get("table_name"),
region=dynamodb_config.get("region"),
profile=dynamodb_config.get("profile"),
logger=self._logger
)
def _create_memory_repository(self, repo_class: Type) -> Any:
"""Create in-memory repository."""
return repo_class(logger=self._logger)
def _get_storage_type(self) -> str:
"""Get configured storage type."""
return self._config.get("storage.type", "memory")
def get_available_storage_types(self) -> List[str]:
"""Get list of available storage types."""
return list(self._repository_registry.keys())
Abstract Factory Pattern¶
The Abstract Factory pattern is used for creating families of related objects.
Provider Component Factory¶
# src/providers/base/factory/provider_component_factory.py
from abc import ABC, abstractmethod
from typing import Any
from src.domain.base.ports import LoggingPort, ConfigurationPort
class ProviderComponentFactory(ABC):
"""Abstract factory for creating provider-specific components."""
@abstractmethod
def create_client(self) -> Any:
"""Create provider-specific client."""
pass
@abstractmethod
def create_instance_manager(self) -> Any:
"""Create provider-specific instance manager."""
pass
@abstractmethod
def create_resource_manager(self) -> Any:
"""Create provider-specific resource manager."""
pass
@abstractmethod
def create_template_adapter(self) -> Any:
"""Create provider-specific template adapter."""
pass
class AWSComponentFactory(ProviderComponentFactory):
"""Concrete factory for AWS components."""
def __init__(self,
config: ConfigurationPort,
logger: LoggingPort):
self._config = config
self._logger = logger
def create_client(self) -> AWSClient:
"""Create AWS client."""
from src.providers.aws.infrastructure.aws_client import AWSClient
aws_config = self._config.get_section("aws")
return AWSClient(aws_config, self._logger)
def create_instance_manager(self) -> AWSInstanceManager:
"""Create AWS instance manager."""
from src.providers.aws.managers.aws_instance_manager import AWSInstanceManager
client = self.create_client()
return AWSInstanceManager(client, self._config, self._logger)
def create_resource_manager(self) -> AWSResourceManagerImpl:
"""Create AWS resource manager."""
from src.providers.aws.managers.aws_resource_manager import AWSResourceManagerImpl
client = self.create_client()
return AWSResourceManagerImpl(client, self._config, self._logger)
def create_template_adapter(self) -> AWSTemplateAdapter:
"""Create AWS template adapter."""
from src.providers.aws.infrastructure.adapters.template_adapter import AWSTemplateAdapter
client = self.create_client()
return AWSTemplateAdapter(client, self._logger, self._config)
Configuration-Driven Factory Selection¶
Factories are selected and configured based on application configuration:
Factory Configuration¶
# config/factories.yml
storage:
type: dynamodb
dynamodb:
table_name: hostfactory-data
region: us-east-1
profile: default
providers:
default: aws
aws:
region: us-east-1
profile: default
handlers:
default: ec2_fleet
repositories:
caching: true
cache_ttl: 300
Factory Registration¶
# src/infrastructure/di/factory_services.py
def register_factory_services(container: DIContainer) -> None:
"""Register factory services in DI container."""
# Register provider strategy factory
container.register_factory(
ProviderStrategyFactory,
lambda c: ProviderStrategyFactory(
config_manager=c.get(ConfigurationPort),
logger=c.get(LoggingPort)
)
)
# Register repository factory
container.register_singleton(
RepositoryFactory,
lambda c: RepositoryFactory(
config=c.get(ConfigurationPort),
logger=c.get(LoggingPort)
)
)
# Register AWS handler factory (if AWS provider is configured)
config = container.get(ConfigurationPort)
if config.get("providers.default") == "aws":
container.register_singleton(AWSHandlerFactory)
Benefits of Factory Pattern Implementation¶
Encapsulation of Creation Logic¶
- Complex object creation is hidden behind simple interfaces
- Creation parameters are managed centrally
- Dependencies are handled automatically
Configuration-Driven Behavior¶
- Object types selected based on configuration
- Runtime behavior modification without code changes
- Easy switching between implementations
Extensibility¶
- New implementations can be added easily
- Factory registration system supports plugins
- Abstract factories enable provider families
Testability¶
- Factories can create mock objects for testing
- Different configurations for different test scenarios
- Easy isolation of creation logic
Maintainability¶
- Creation logic centralized in factories
- Clear separation between creation and usage
- Consistent object creation patterns
This comprehensive factory pattern implementation provides flexible, configurable, and maintainable object creation throughout the Open Host Factory Plugin.