Architectural Refactoring Patterns
Six fundamental architectural principles for OpenHCS development.
Six Fundamental Principles
1. ABC Contract Enforcement
Use ABCs to enforce explicit contracts and enable polymorphism.
Examples: StorageBackend, FilenameParser, MetadataHandler, LibraryRegistryBase
2. Explicit Dependency Injection
Dependencies are explicitly provided, never implicitly created.
Examples: create_microscope_handler(filemanager=filemanager), factory functions require all dependencies
3. Indirection Minimization
Eliminate unnecessary layers; prefer direct method calls.
Examples: getattr(self, method_name) instead of dispatch tables, direct enum usage
4. Genericism Enforcement
Create truly generic systems that work with any valid configuration.
Examples: ComponentConfiguration[T], DynamicParserMeta generates methods from any enum
5. Fail-Loud Error Handling
Specific exceptions with context; no silent failures or defensive programming.
Examples: MemoryConversionError, StorageResolutionError, allow_cpu_roundtrip=False by default
6. Consistent Interface Design
Uniform patterns across all subsystems.
Examples: ABC + Factory pattern, consistent method naming, enum-driven behavior
Refactoring Patterns
Pattern 1: ABC Contract Enforcement
Before: Inconsistent interfaces across similar classes
class ImageXpressParser:
def parse_well(self, filename): pass
class OperaPhenixParser:
def extract_well(self, filename): pass # Different method name
After: ABC enforces consistent contracts
class FilenameParser(ABC):
@abstractmethod
def parse_well(self, filename): pass
class ImageXpressParser(FilenameParser):
def parse_well(self, filename): pass # Contract enforced
class OperaPhenixParser(FilenameParser):
def parse_well(self, filename): pass # Must match ABC
Pattern 2: Explicit Dependency Injection
Before: Hidden dependencies and global state
class MicroscopeHandler:
def __init__(self, microscope_type: str):
self.filemanager = get_global_filemanager() # Hidden dependency
self.parser = create_parser(microscope_type) # Hidden creation
After: Explicit dependency injection
class MicroscopeHandler:
def __init__(self, parser: FilenameParser, filemanager: FileManager):
self.parser = parser
self.filemanager = filemanager
# Factory function with explicit dependencies
def create_microscope_handler(microscope_type: str,
filemanager: FileManager) -> MicroscopeHandler:
parser = create_parser(microscope_type)
return MicroscopeHandler(parser, filemanager)
Pattern 3: Indirection Minimization
Before: Unnecessary dispatch tables and routing layers
class ComponentProcessor:
def __init__(self):
self.dispatch_table = {
'DAPI': self._process_dapi,
'GFP': self._process_gfp,
'RFP': self._process_rfp
}
def process(self, component: str, data):
handler = self.dispatch_table.get(component)
if handler:
return handler(data)
After: Direct method calls with enum-driven behavior
class ComponentProcessor:
def process(self, component: Component, data):
method_name = f"_process_{component.value.lower()}"
method = getattr(self, method_name)
return method(data)
Pattern 4: Genericism Enforcement
Before: Pseudo-generic code with hardcoded assumptions
class ConfigValidator:
def validate_pipeline_config(self, config):
# Hardcoded for PipelineConfig only
if not config.steps:
return False
return True
def validate_zarr_config(self, config):
# Separate method for each config type
if not config.compression:
return False
return True
After: Truly generic validation using metaprogramming
class ConfigValidator(Generic[T]):
def validate(self, config: T) -> ValidationResult:
errors = []
for field in fields(config):
if field.metadata.get('required') and getattr(config, field.name) is None:
errors.append(f"Required field {field.name} is None")
return ValidationResult(errors)
Pattern 5: Fail-Loud Error Handling
Before: Defensive programming with silent failures
def process_image(image_path: str):
try:
image = load_image(image_path)
if image is not None:
return process(image)
except Exception:
pass # Silent failure
return default_image() # Fallback masks problems
After: Explicit error handling with specific exceptions
def process_image(image_path: str) -> ProcessedImage:
try:
image = load_image(image_path)
except FileNotFoundError as e:
raise ImageLoadError(f"Image not found: {image_path}") from e
except PermissionError as e:
raise ImageLoadError(f"Permission denied: {image_path}") from e
if image.ndim != 3:
raise ImageValidationError(f"Expected 3D image, got {image.ndim}D")
return process(image)
Pattern 6: Consistent Interface Design
Before: Inconsistent interfaces across subsystems
class StorageBackend:
def load_file(self, path): pass # Different method name
def write_file(self, data, path): pass
class MicroscopeHandler:
def get_data(self, path): pass # Different method name
def put_data(self, data, path): pass
After: Consistent interface pattern
class StorageBackend(ABC):
@abstractmethod
def load(self, path): pass # Consistent naming
@abstractmethod
def save(self, data, path): pass
class MicroscopeHandler(ABC):
@abstractmethod
def load(self, path): pass # Same interface pattern
@abstractmethod
def save(self, data, path): pass
# Factory pattern used consistently
def create_storage_backend(backend_type: str) -> StorageBackend: pass
def create_microscope_handler(microscope_type: str) -> MicroscopeHandler: pass
Refactoring Methodology
Step 1: Identify Violations
ABC violations: Similar classes with inconsistent interfaces
Dependency violations: Hidden object creation or global state access
Indirection excess: Unnecessary dispatch tables or routing layers
Pseudo-genericism: Hardcoded assumptions in “generic” code
Defensive programming: hasattr checks, silent fallbacks
Interface inconsistency: Different method names for similar operations
Step 2: Apply Patterns
Create ABCs for similar functionality
Extract dependencies to constructor parameters
Remove dispatch layers in favor of direct method calls
Use Generic[T] and metaprogramming for true genericism
Replace defensive code with explicit error handling
Standardize interfaces across subsystems
Step 3: Validate Consistency
All subsystems follow ABC + Factory pattern
Dependencies are explicitly injected
Error handling is fail-loud with specific exceptions
Systems work with any valid configuration
Interfaces are consistent across similar functionality
Evidence from OpenHCS
Stable Systems (Storage, Microscope): Established foundational patterns Refactored Systems (Memory, Component): Applied same principles to new domains Cross-System Integration: All systems work together due to consistent architecture
Summary
OpenHCS refactoring follows six fundamental principles that create architectural consistency:
ABC Contract Enforcement - Explicit interfaces enable polymorphism
Explicit Dependency Injection - No hidden dependencies or object creation
Indirection Minimization - Direct method calls, fewer layers
Genericism Enforcement - True genericism via metaprogramming
Fail-Loud Error Handling - Specific exceptions, no silent failures
Consistent Interface Design - Uniform patterns across all subsystems
These principles appear consistently across both stable legacy code and newly refactored systems, creating a coherent architectural methodology.