Registry System Architecture#
The FairDM registry system is a core component that enables automatic generation of Django components (forms, tables, filters, etc.) from model definitions. This document explains the fundamental architecture, design decisions, and implementation details.
Why We Need a Registry System#
The Problem#
Research data portals typically need:
Forms for data entry and editing
Tables for displaying data lists with sorting/pagination
Filters for searching and filtering data
Import/Export functionality for bulk operations
REST API serializers for programmatic access
Admin interfaces for management
Traditionally, developers must manually create each of these components for every model, leading to:
Repetitive boilerplate code (forms, admin classes, serializers)
Inconsistent UI patterns across different models
Maintenance overhead when models change
High barrier to entry for domain experts who aren’t Django developers
The Solution#
The FairDM registry system provides:
Auto-generation: Automatically create all necessary components from model definitions
Convention over configuration: Sensible defaults that work out of the box
Declarative configuration: Simple, readable configuration classes
Progressive complexity: Start simple, add customization as needed
Type safety: Full type hints and Protocol definitions
Core Architecture#
Key Components#
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ @register │───▶│ ModelConfiguration│───▶│ Components │
│ Decorator │ │ │ │ │
└─────────────────┘ └──────────────────┘ │ • Forms │
▲ │ • Tables │
│ │ • Filters │
▼ │ • Serializers │
┌─────────────────┐ ┌──────────────────┐ │ • Resources │
│ Registry │◀───│ FieldResolver │ │ • Admin │
│ │ │ │ └─────────────────┘
│ • Model Storage │ └──────────────────┘
│ • Introspection │
│ • Validation │ ┌──────────────────┐
└─────────────────┘ │ Factories │
│ │
│ Component │
│ Generation │
└──────────────────┘
Registration Flow#
Model Definition: User creates Sample/Measurement subclass
Configuration: User defines ModelConfiguration with fields and options
Registration:
@registerdecorator stores configuration in registryValidation: Registry validates model inheritance and field existence
Component Access: Properties generate components on-demand using cached_property
Field Resolution: FieldResolver determines appropriate fields for each component
Component Generation#
# User defines model and configuration
@register
class MySampleConfig(ModelConfiguration):
model = MySample
fields = ["name", "location", "temperature"]
# Framework auto-generates components
config = registry.get_for_model(MySample)
form_class = config.form # Auto-generated ModelForm
table_class = config.table # Auto-generated django-tables2 Table
filter_class = config.filterset # Auto-generated django-filter FilterSet
# ... etc
Design Principles#
1. Zero-Configuration Default#
Models should work with minimal configuration:
@register
class MinimalSampleConfig(ModelConfiguration):
model = MySample # Only model is required
# Everything else auto-generated with smart defaults
2. Progressive Enhancement#
Start simple, add complexity as needed:
# Level 1: Basic field list
fields = ["name", "location", "temperature"]
# Level 2: Component-specific fields
table_fields = ["name", "location"]
form_fields = ["name", "location", "temperature", "notes"]
# Level 3: Custom component classes
form_class = MyCustomForm
table_class = MyCustomTable
3. Type Safety#
Full type hints enable IDE support and catch errors early:
def get_for_model(self, model: type[Model]) -> ModelConfiguration:
"""Get configuration for model with full type safety."""
@cached_property
def form(self) -> type[ModelForm]:
"""Return form class with correct typing."""
4. Performance Optimization#
Components are generated lazily and cached:
@cached_property
def form(self) -> type[ModelForm]:
"""Generated once, cached forever per configuration instance."""
return FormFactory(self.model, self.form_fields).create()
Implementation Details#
Registry Storage#
The registry uses a simple dictionary mapping models to configurations:
class FairDMRegistry:
def __init__(self):
self._registry: dict[type[Model], ModelConfiguration] = {}
This provides O(1) lookup performance and simple introspection.
Field Resolution Algorithm#
The FieldResolver implements a 3-tier fallback system:
def resolve_fields_for_component(self, component_type: str) -> list[str]:
# Tier 1: Component-specific fields (e.g., form_fields)
if hasattr(config, f"{component_type}_fields"):
return getattr(config, f"{component_type}_fields")
# Tier 2: General fields list
if config.fields:
return config.fields
# Tier 3: Smart defaults (exclude auto-generated, non-editable)
return config.get_default_fields()
Component Factories#
Each component type has a dedicated factory:
class FormFactory:
def create(self) -> type[ModelForm]:
fields = self.resolver.resolve_fields_for_component("form")
return modelform_factory(
model=self.model,
fields=fields,
widgets=self.get_widgets()
)
Validation System#
Registration-time validation prevents common errors:
def _validate_model_inheritance(self, model: type[Model]) -> None:
"""Ensure model inherits from Sample or Measurement."""
if not issubclass(model, (Sample, Measurement)):
raise ConfigurationError(...)
def _validate_field_existence(self, fields: list[str]) -> None:
"""Ensure all field names exist on model."""
valid_fields = {f.name for f in self.model._meta.get_fields()}
for field_name in fields:
if field_name not in valid_fields:
suggestions = difflib.get_close_matches(field_name, valid_fields)
raise FieldValidationError(f"Invalid field: {field_name}. Did you mean: {suggestions}?")
Introspection API#
The registry provides powerful introspection capabilities for dynamic workflows:
Model Discovery#
# Get all registered Sample models
for sample_model in registry.samples:
print(f"Sample: {sample_model.__name__}")
# Get all registered Measurement models
for measurement_model in registry.measurements:
print(f"Measurement: {measurement_model.__name__}")
# Get all registered models
for model in registry.models:
print(f"Model: {model.__name__}")
Configuration Access#
# Check if model is registered
if registry.is_registered(MySample):
config = registry.get_for_model(MySample)
# String-based lookup for dynamic scenarios
config = registry.get_for_model("myapp.MySample")
# Bulk operations
all_configs = registry.get_all_configs()
for config in all_configs:
print(f"{config.model.__name__}: {len(config.fields)} fields")
Performance Characteristics#
Registration Performance#
Target: <10ms per model registration
Actual: ~4 microseconds (well under target)
Scalability: Linear with number of models
Component Generation Performance#
Target: <50ms per component on first access
Actual: ~100 microseconds (well under target)
Caching: Subsequent access ~1 microsecond
Memory Usage#
Registry: O(n) where n = number of registered models
Components: Generated lazily, cached per configuration
Footprint: Minimal until components are accessed
Extension Points#
Custom Component Classes#
Users can provide custom implementations:
@register
class AdvancedSampleConfig(ModelConfiguration):
model = MySample
form_class = MyCustomForm # Override auto-generation
table_class = MyCustomTable # Custom table implementation
# Other components still auto-generated
Custom Factories#
Advanced users can extend factory behavior:
class CustomFormFactory(FormFactory):
def get_widgets(self) -> dict[str, Any]:
widgets = super().get_widgets()
# Add custom widget logic
return widgets
Plugin Integration#
The registry enables plugin development:
class AnalysisPlugin:
def get_compatible_models(self) -> list[type[Model]]:
"""Find models that work with this plugin."""
compatible = []
for config in registry.get_all_configs():
if self.is_compatible(config.model):
compatible.append(config.model)
return compatible
Testing Strategy#
Unit Tests#
Component generation: Test each factory independently
Field resolution: Test 3-tier fallback algorithm
Validation: Test error cases and edge conditions
Introspection: Test registry API methods
Integration Tests#
End-to-end registration: Model → config → components
Django integration: Forms work with views, tables render correctly
Performance: Benchmark against requirements
Contract Tests#
Protocol compliance: Verify implementations match Protocol definitions
API stability: Ensure public API remains consistent
Future Enhancements#
Planned Features#
Dynamic field discovery: Automatically detect model fields and relationships
UI generation: Auto-generate complete CRUD views
Plugin marketplace: Registry for community plugins
Visual configuration: GUI for non-developers
Extension Areas#
Component types: Add support for charts, dashboards, reports
Field types: Enhanced support for geospatial, scientific data types
Validation: Runtime schema validation and data quality checks
Caching: Distributed caching for multi-instance deployments
Contributing Guidelines#
When working on the registry system:
Add comprehensive tests - both unit and integration tests for new features
Update type hints - maintain 100% mypy compliance
Document performance impact - benchmark any changes affecting registration/generation
Follow naming conventions - use consistent naming for new components/methods
Validate with demo app - ensure changes work in fairdm_demo
The registry system is the foundation of FairDM’s ease of use - changes here impact every user, so maintain high quality standards and thorough testing.