Clean Architecture
Software design philosophy emphasizing separation of concerns, dependency inversion, and autonomous components to create maintainable, testable, and evolution-friendly systems. Particularly valuable for production systems requiring long-term maintainability and team handovers.
Core Principles
Separation of Concerns
Each module should have a single, well-defined responsibility:
# Instead of monolithic pipeline.py (1561 lines)
pipeline.py # Orchestration only (300 lines)
query_processor.py # Query analysis and reformulation
retriever.py # Multi-source data retrieval
section_aggregator.py # Chunk to section transformation
context_builder.py # Document aggregation logic
generator.py # Response generation
Dependency Inversion
High-level modules should not depend on low-level modules. Both should depend on abstractions:
# Bad: Direct dependency on specific implementation
from src.rag_v2.embedder import ScalewayEmbedder
# Good: Internal abstraction
from .embedder import Embedder # Internalized interface
Autonomous Components
Each major component should be self-contained with minimal external dependencies. This enables independent testing, deployment, and evolution.
Implementation Strategies
Layered Architecture
Presentation Layer: UI components, API endpoints, user interfaces
Application Layer: Business logic, orchestration, workflow management
Domain Layer: Core business entities, rules, and domain-specific logic
Infrastructure Layer: Database access, external APIs, system interfaces
Component Internalization
Rather than maintaining complex shared libraries, internalize and simplify components for specific use cases:
# Before: General-purpose shared component
class FallbackEmbedder:
"""Supports 5 embedding providers with complex fallback logic"""
# 425 lines handling multiple providers, retry logic, caching, etc.
# After: Clean, specific implementation
class Embedder:
"""Albert + Scaleway embeddings for HR document processing"""
# 200 lines focused on actual requirements
Interface Consistency
Define clear contracts between components that remain stable even as implementations change:
@dataclass
class RetrievalResult:
chunks: List[DocumentChunk]
sections: List[DocumentSection]
confidence: float
source_tables: List[str]
class Retriever:
def retrieve(self, query: str, top_k: int = 20) -> RetrievalResult:
"""Consistent interface regardless of implementation details"""
Real-World Application: RAG System Migration
The assistant-rh clean architecture migration demonstrates these principles in practice:
Before (Unclear Separation):
pipeline.py: 1561 lines mixing orchestration, business logic, and data access- Complex imports across