Service Layer Architecture
Framework-agnostic business logic extraction enabling cross-framework compatibility.
Overview
The service layer architecture emerged from a practical problem: pyqt-reactive supports both PyQt6 and Textual UIs, but the business logic for parameter forms was duplicated between them. Every time we fixed a bug or added a feature, we had to implement it twice.
UI frameworks naturally encourage mixing presentation logic with business logic. PyQt6 widgets know how to display themselves, so it’s tempting to put parameter analysis logic directly in the widget creation code. But this creates tight coupling and makes code reuse impossible.
The solution extracts all business logic into framework-agnostic service classes. UI frameworks become thin presentation layers that consume services. The same service can power both PyQt6 and Textual implementations.
This pattern eliminated duplicated code and reduced parameter form bugs since fixes only need to be made once.
Core Service Pattern
The core pattern is simple: create classes that contain only business logic, with no dependencies on UI frameworks. These services take data as input and return data as output, making them easy to test and reuse.
Framework-Agnostic Services
Services focus purely on the “what” and “how” of business logic, leaving the “where to display it” to UI frameworks.
class ParameterFormService:
"""Framework-agnostic service for parameter form business logic."""
def __init__(self, debug_config: Optional[DebugConfig] = None):
self.debugger = get_debugger(debug_config)
self._field_id_generator = FieldIdGenerator()
self._type_utils = ParameterTypeUtils()
self._name_formatter = ParameterNameFormatter()
def analyze_parameters(self, parameters: Dict[str, Any],
parameter_types: Dict[str, Type],
field_id: str) -> FormStructure:
"""Analyze parameters and create framework-agnostic form structure."""
# Business logic separated from UI framework
structure = FormStructure(field_id=field_id)
for param_name, param_type in parameter_types.items():
param_info = self._analyze_single_parameter(
param_name, param_type, parameters.get(param_name)
)
structure.parameters.append(param_info)
return structure
Service Integration Pattern
PyQt6 Integration
class PyQt6ParameterFormManager:
"""PyQt6-specific UI implementation using shared service."""
def __init__(self):
self.service = ParameterFormService() # Inject service
self.widget_strategies = PyQt6WidgetStrategies()
def build_form(self, parameters: Dict[str, Any],
parameter_types: Dict[str, Type],
field_id: str):
# Use service for business logic
structure = self.service.analyze_parameters(parameters, parameter_types, field_id)
# UI framework handles only presentation
for param_info in structure.parameters:
widget = self._create_widget(param_info)
self.layout.addWidget(widget)
Textual Integration
class TextualParameterFormManager:
"""Textual-specific UI implementation using same service."""
def __init__(self):
self.service = ParameterFormService() # Same service
self.widget_strategies = TextualWidgetStrategies()
def compose(self, parameters: Dict[str, Any],
parameter_types: Dict[str, Type],
field_id: str):
# Identical business logic via service
structure = self.service.analyze_parameters(parameters, parameter_types, field_id)
# Different UI framework, same logic
for param_info in structure.parameters:
widget = self._create_textual_widget(param_info)
yield widget
Cross-Framework Compatibility
Strategy Pattern Integration
# Framework-specific strategies consume service output
class PyQt6WidgetStrategies:
def create_widget_for_parameter(self, param_info: ParameterInfo) -> QWidget:
# Use service-provided parameter info for widget creation
if param_info.param_type == bool:
widget = QCheckBox()
widget.setChecked(param_info.default_value)
elif param_info.is_optional:
widget = self._create_optional_widget(param_info)
return widget
Benefits
Framework Independence: Same business logic works across PyQt6 and Textual
Code Reuse: Eliminates duplication between UI implementations
Testability: Business logic can be unit tested without UI dependencies
Maintainability: Changes to logic don’t require UI modifications
Separation of Concerns: Clear boundary between business logic and presentation
Context Awareness: Services adapt behavior based on usage context
See Also
Parameter Form Service Architecture - Service-oriented refactoring of parameter forms (in development)
configuration_framework - Configuration system architecture used by services
step-editor-generalization - Step editors that use service layer patterns
code_ui_interconversion - Code/UI interconversion patterns