693 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			693 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 💉 Injection de Dépendances - Pattern et Implémentation
 | |
| 
 | |
| ## Vue d'Ensemble
 | |
| 
 | |
| Ce document détaille le système d'injection de dépendances implémenté dans Notytex Phase 1, qui résout les imports circulaires et améliore la testabilité en appliquant le principe **Dependency Inversion**.
 | |
| 
 | |
| ## 🎯 Problématique Résolue
 | |
| 
 | |
| ### Avant : Imports Circulaires et Couplage Fort
 | |
| 
 | |
| ```python
 | |
| # ❌ Problème : models.py importait directement app_config
 | |
| from app_config import config_manager
 | |
| 
 | |
| class Assessment(db.Model):
 | |
|     def calculate_score(self, grade_value):
 | |
|         # Couplage direct → Import circulaire
 | |
|         if config_manager.is_special_value(grade_value):
 | |
|             return 0
 | |
| ```
 | |
| 
 | |
| ```python
 | |
| # ❌ Problème : app_config importait les modèles
 | |
| from models import Assessment, Grade
 | |
| 
 | |
| class ConfigManager:
 | |
|     def validate_grades(self):
 | |
|         # Import circulaire Assessment ↔ ConfigManager
 | |
|         assessments = Assessment.query.all()
 | |
| ```
 | |
| 
 | |
| ### Après : Injection de Dépendances avec Protocols
 | |
| 
 | |
| ```python
 | |
| # ✅ Solution : Interface abstraite
 | |
| class ConfigProvider(Protocol):
 | |
|     def is_special_value(self, value: str) -> bool: ...
 | |
|     def get_special_values(self) -> Dict[str, Dict[str, Any]]: ...
 | |
| 
 | |
| # ✅ Service découplé
 | |
| class UnifiedGradingCalculator:
 | |
|     def __init__(self, config_provider: ConfigProvider):
 | |
|         self.config_provider = config_provider  # Abstraction
 | |
|     
 | |
|     def calculate_score(self, grade_value: str, grading_type: str, max_points: float):
 | |
|         # Utilise l'abstraction, pas l'implémentation
 | |
|         if self.config_provider.is_special_value(grade_value):
 | |
|             return 0
 | |
| ```
 | |
| 
 | |
| ## 🔧 Architecture d'Injection
 | |
| 
 | |
| ### Diagramme des Dépendances
 | |
| 
 | |
| ```
 | |
| ┌─────────────────── INTERFACES (Protocols) ───────────────────┐
 | |
| │                                                              │
 | |
| │  ConfigProvider          DatabaseProvider                    │
 | |
| │  ├── is_special_value    ├── get_grades_for_assessment       │
 | |
| │  └── get_special_values  └── get_grading_elements_with_s.    │
 | |
| │                                                              │
 | |
| └──────────────────────────┬───────────────────────────────────┘
 | |
|                            │ (Dependency Inversion)
 | |
| ┌─────────────────── SERVICES (Business Logic) ────────────────┐
 | |
| │                          │                                   │
 | |
| │  UnifiedGradingCalc.     │  AssessmentProgressService        │
 | |
| │  StudentScoreCalc.       │  AssessmentStatisticsService      │
 | |
| │                          │                                   │
 | |
| └──────────────────────────┬───────────────────────────────────┘
 | |
|                            │ (Orchestration)
 | |
| ┌─────────────────── FACADE (Entry Point) ──────────────────────┐
 | |
| │                          │                                   │
 | |
| │            AssessmentServicesFacade                          │
 | |
| │                          │                                   │
 | |
| └──────────────────────────┬───────────────────────────────────┘
 | |
|                            │ (Factory Creation)
 | |
| ┌─────────────────── FACTORY (Wiring) ──────────────────────────┐
 | |
| │                          │                                   │
 | |
| │          AssessmentServicesFactory                           │
 | |
| │                          │                                   │
 | |
| └──────────────────────────┬───────────────────────────────────┘
 | |
|                            │ (Concrete Implementations)
 | |
| ┌─────────────────── PROVIDERS (Concrete) ──────────────────────┐
 | |
| │                          │                                   │
 | |
| │  ConfigManagerProvider   │  SQLAlchemyDatabaseProvider       │
 | |
| │  └── app_config         │  └── SQLAlchemy                   │
 | |
| │                          │                                   │
 | |
| └──────────────────────────────────────────────────────────────┘
 | |
| ```
 | |
| 
 | |
| ### Flux d'Injection
 | |
| 
 | |
| ```
 | |
| Factory → Concrete Providers → Facade → Services
 | |
|    │            │                │         │
 | |
|    │            │                │         └─ Business Logic
 | |
|    │            │                └─ Orchestration
 | |
|    │            └─ Implementation
 | |
|    └─ Wiring
 | |
| ```
 | |
| 
 | |
| ## 📋 Interfaces (Protocols)
 | |
| 
 | |
| ### 1. ConfigProvider Protocol
 | |
| 
 | |
| **Rôle** : Abstraction pour l'accès à la configuration
 | |
| 
 | |
| ```python
 | |
| class ConfigProvider(Protocol):
 | |
|     """Interface pour l'accès à la configuration."""
 | |
|     
 | |
|     def is_special_value(self, value: str) -> bool:
 | |
|         """Vérifie si une valeur est spéciale (., d, etc.)"""
 | |
|         ...
 | |
|     
 | |
|     def get_special_values(self) -> Dict[str, Dict[str, Any]]:
 | |
|         """Retourne la configuration des valeurs spéciales."""
 | |
|         ...
 | |
| ```
 | |
| 
 | |
| **Avantages** :
 | |
| - **Découplage** : Les services ne connaissent pas l'implémentation
 | |
| - **Testabilité** : Facilite les mocks
 | |
| - **Flexibilité** : Changement d'implémentation transparent
 | |
| 
 | |
| ### 2. DatabaseProvider Protocol
 | |
| 
 | |
| **Rôle** : Abstraction pour l'accès aux données optimisé
 | |
| 
 | |
| ```python
 | |
| class DatabaseProvider(Protocol):
 | |
|     """Interface pour l'accès aux données."""
 | |
|     
 | |
|     def get_grades_for_assessment(self, assessment_id: int) -> List[Any]:
 | |
|         """Récupère toutes les notes d'une évaluation en une seule requête."""
 | |
|         ...
 | |
|     
 | |
|     def get_grading_elements_with_students(self, assessment_id: int) -> List[Any]:
 | |
|         """Récupère les éléments de notation avec le nombre de notes complétées."""
 | |
|         ...
 | |
| ```
 | |
| 
 | |
| **Avantages** :
 | |
| - **Performance** : Requêtes optimisées centralisées
 | |
| - **Abstraction** : Services indépendants de SQLAlchemy
 | |
| - **Evolution** : Changement de ORM possible
 | |
| 
 | |
| ## 🏭 Providers Concrets
 | |
| 
 | |
| ### 1. ConfigManagerProvider
 | |
| 
 | |
| **Implémentation** : Adapter vers app_config avec lazy loading
 | |
| 
 | |
| ```python
 | |
| class ConfigManagerProvider:
 | |
|     """
 | |
|     Implémentation concrète du ConfigProvider utilisant app_config.
 | |
|     Résout les imports circulaires en encapsulant l'accès à la configuration.
 | |
|     """
 | |
|     
 | |
|     def __init__(self):
 | |
|         # Import paresseux pour éviter les dépendances circulaires
 | |
|         self._config_manager = None
 | |
|     
 | |
|     @property
 | |
|     def config_manager(self):
 | |
|         """Accès paresseux au config_manager."""
 | |
|         if self._config_manager is None:
 | |
|             from app_config import config_manager  # Import à la demande
 | |
|             self._config_manager = config_manager
 | |
|         return self._config_manager
 | |
|     
 | |
|     def is_special_value(self, value: str) -> bool:
 | |
|         """Vérifie si une valeur est spéciale."""
 | |
|         return self.config_manager.is_special_value(value)
 | |
|     
 | |
|     def get_special_values(self) -> Dict[str, Dict[str, Any]]:
 | |
|         """Retourne la configuration des valeurs spéciales."""
 | |
|         return self.config_manager.get_special_values()
 | |
| ```
 | |
| 
 | |
| **Techniques Utilisées** :
 | |
| - **Lazy Loading** : Import différé pour éviter les cycles
 | |
| - **Adapter Pattern** : Encapsule l'accès à config_manager
 | |
| - **Property Caching** : Évite les re-imports multiples
 | |
| 
 | |
| ### 2. SQLAlchemyDatabaseProvider
 | |
| 
 | |
| **Implémentation** : Requêtes optimisées pour résoudre N+1
 | |
| 
 | |
| ```python
 | |
| class SQLAlchemyDatabaseProvider:
 | |
|     """
 | |
|     Implémentation concrète du DatabaseProvider utilisant SQLAlchemy.
 | |
|     Fournit des requêtes optimisées pour éviter les problèmes N+1.
 | |
|     """
 | |
|     
 | |
|     def get_grades_for_assessment(self, assessment_id: int) -> List[Dict[str, Any]]:
 | |
|         """
 | |
|         Récupère toutes les notes d'une évaluation en une seule requête optimisée.
 | |
|         Résout le problème N+1 identifié dans calculate_student_scores.
 | |
|         """
 | |
|         query = (
 | |
|             db.session.query(
 | |
|                 Grade.student_id,
 | |
|                 Grade.grading_element_id,
 | |
|                 Grade.value,
 | |
|                 GradingElement.grading_type,
 | |
|                 GradingElement.max_points
 | |
|             )
 | |
|             .join(GradingElement)
 | |
|             .join(Exercise)
 | |
|             .filter(Exercise.assessment_id == assessment_id)
 | |
|             .filter(Grade.value.isnot(None))
 | |
|             .filter(Grade.value != '')
 | |
|         )
 | |
|         
 | |
|         return [
 | |
|             {
 | |
|                 'student_id': row.student_id,
 | |
|                 'grading_element_id': row.grading_element_id,
 | |
|                 'value': row.value,
 | |
|                 'grading_type': row.grading_type,
 | |
|                 'max_points': row.max_points
 | |
|             }
 | |
|             for row in query.all()
 | |
|         ]
 | |
|     
 | |
|     def get_grading_elements_with_students(self, assessment_id: int) -> List[Dict[str, Any]]:
 | |
|         """
 | |
|         Récupère les éléments de notation avec le nombre de notes complétées.
 | |
|         Résout le problème N+1 identifié dans grading_progress.
 | |
|         """
 | |
|         # Sous-requête pour compter les grades complétés par élément
 | |
|         grades_subquery = (
 | |
|             db.session.query(
 | |
|                 Grade.grading_element_id,
 | |
|                 func.count(Grade.id).label('completed_count')
 | |
|             )
 | |
|             .filter(Grade.value.isnot(None))
 | |
|             .filter(Grade.value != '')
 | |
|             .group_by(Grade.grading_element_id)
 | |
|             .subquery()
 | |
|         )
 | |
|         
 | |
|         # Requête principale avec jointure
 | |
|         query = (
 | |
|             db.session.query(
 | |
|                 GradingElement.id,
 | |
|                 GradingElement.label,
 | |
|                 func.coalesce(grades_subquery.c.completed_count, 0).label('completed_grades_count')
 | |
|             )
 | |
|             .join(Exercise)
 | |
|             .outerjoin(grades_subquery, GradingElement.id == grades_subquery.c.grading_element_id)
 | |
|             .filter(Exercise.assessment_id == assessment_id)
 | |
|         )
 | |
|         
 | |
|         return [
 | |
|             {
 | |
|                 'element_id': row.id,
 | |
|                 'element_label': row.label,
 | |
|                 'completed_grades_count': row.completed_grades_count
 | |
|             }
 | |
|             for row in query.all()
 | |
|         ]
 | |
| ```
 | |
| 
 | |
| **Optimisations** :
 | |
| - **Requête unique** : Évite N+1 pour les grades
 | |
| - **Sous-requêtes** : Calculs agrégés efficaces
 | |
| - **Jointures optimisées** : Minimise le nombre d'accès DB
 | |
| 
 | |
| ## 🏭 Factory Pattern
 | |
| 
 | |
| ### AssessmentServicesFactory
 | |
| 
 | |
| **Rôle** : Orchestration centralisée de l'injection de dépendances
 | |
| 
 | |
| ```python
 | |
| class AssessmentServicesFactory:
 | |
|     """
 | |
|     Factory pour créer l'ensemble des services avec injection de dépendances.
 | |
|     Centralise la création et la configuration des services.
 | |
|     """
 | |
|     
 | |
|     @classmethod
 | |
|     def create_facade(cls) -> 'AssessmentServicesFacade':
 | |
|         """
 | |
|         Crée une facade complète avec toutes les dépendances injectées.
 | |
|         Point d'entrée principal pour obtenir les services.
 | |
|         """
 | |
|         from services.assessment_services import AssessmentServicesFacade
 | |
|         
 | |
|         # 1. Création des providers concrets
 | |
|         config_provider = ConfigManagerProvider()
 | |
|         db_provider = SQLAlchemyDatabaseProvider()
 | |
|         
 | |
|         # 2. Injection dans la facade
 | |
|         return AssessmentServicesFacade(
 | |
|             config_provider=config_provider,
 | |
|             db_provider=db_provider
 | |
|         )
 | |
|     
 | |
|     @classmethod
 | |
|     def create_with_custom_providers(cls, 
 | |
|                                    config_provider=None, 
 | |
|                                    db_provider=None) -> 'AssessmentServicesFacade':
 | |
|         """
 | |
|         Crée une facade avec des providers personnalisés.
 | |
|         Utile pour les tests avec des mocks.
 | |
|         """
 | |
|         from services.assessment_services import AssessmentServicesFacade
 | |
|         
 | |
|         # Providers par défaut ou personnalisés
 | |
|         config_provider = config_provider or ConfigManagerProvider()
 | |
|         db_provider = db_provider or SQLAlchemyDatabaseProvider()
 | |
|         
 | |
|         return AssessmentServicesFacade(
 | |
|             config_provider=config_provider,
 | |
|             db_provider=db_provider
 | |
|         )
 | |
|     
 | |
|     @classmethod
 | |
|     def create_class_services_facade(cls) -> 'ClassServicesFacade':
 | |
|         """
 | |
|         Crée une facade pour les services de classe avec toutes les dépendances injectées.
 | |
|         Point d'entrée pour obtenir les services ClassGroup.
 | |
|         """
 | |
|         from services.assessment_services import ClassServicesFacade
 | |
|         
 | |
|         db_provider = SQLAlchemyDatabaseProvider()
 | |
|         return ClassServicesFacade(db_provider=db_provider)
 | |
| ```
 | |
| 
 | |
| ### Avantages de la Factory
 | |
| 
 | |
| - **Centralisation** : Un seul endroit pour l'injection
 | |
| - **Consistance** : Configuration uniforme des services
 | |
| - **Testabilité** : Permet l'injection de mocks facilement
 | |
| - **Évolution** : Nouveaux services ajoutés centralement
 | |
| 
 | |
| ## 🧪 Testabilité avec l'Injection
 | |
| 
 | |
| ### Mocks pour les Tests
 | |
| 
 | |
| ```python
 | |
| class MockConfigProvider:
 | |
|     """Mock du ConfigProvider pour les tests."""
 | |
|     
 | |
|     def __init__(self):
 | |
|         self.special_values = {
 | |
|             '.': {'value': 0, 'counts': True},
 | |
|             'd': {'value': None, 'counts': False}
 | |
|         }
 | |
|     
 | |
|     def is_special_value(self, value: str) -> bool:
 | |
|         return value in self.special_values
 | |
|     
 | |
|     def get_special_values(self) -> Dict[str, Dict[str, Any]]:
 | |
|         return self.special_values
 | |
| 
 | |
| 
 | |
| class MockDatabaseProvider:
 | |
|     """Mock du DatabaseProvider pour les tests."""
 | |
|     
 | |
|     def __init__(self):
 | |
|         self.grades_data = []
 | |
|         self.elements_data = []
 | |
|     
 | |
|     def set_grades_data(self, data):
 | |
|         """Configure les données de test."""
 | |
|         self.grades_data = data
 | |
|     
 | |
|     def set_elements_data(self, data):
 | |
|         """Configure les éléments de test."""
 | |
|         self.elements_data = data
 | |
|     
 | |
|     def get_grades_for_assessment(self, assessment_id: int) -> List[Dict[str, Any]]:
 | |
|         return [g for g in self.grades_data if g.get('assessment_id') == assessment_id]
 | |
|     
 | |
|     def get_grading_elements_with_students(self, assessment_id: int) -> List[Dict[str, Any]]:
 | |
|         return [e for e in self.elements_data if e.get('assessment_id') == assessment_id]
 | |
| ```
 | |
| 
 | |
| ### Tests Unitaires avec Injection
 | |
| 
 | |
| ```python
 | |
| def test_unified_grading_calculator():
 | |
|     # Arrange - Injection de mock
 | |
|     mock_config = MockConfigProvider()
 | |
|     calculator = UnifiedGradingCalculator(mock_config)
 | |
|     
 | |
|     # Act & Assert - Tests isolés
 | |
|     assert calculator.calculate_score("15.5", "notes", 20.0) == 15.5
 | |
|     assert calculator.calculate_score(".", "notes", 20.0) == 0.0
 | |
|     assert calculator.calculate_score("d", "notes", 20.0) is None
 | |
|     
 | |
|     assert calculator.is_counted_in_total("15.5") is True
 | |
|     assert calculator.is_counted_in_total("d") is False
 | |
| 
 | |
| 
 | |
| def test_assessment_progress_service():
 | |
|     # Arrange - Mocks avec données de test
 | |
|     mock_db = MockDatabaseProvider()
 | |
|     mock_db.set_elements_data([
 | |
|         {'element_id': 1, 'completed_grades_count': 20, 'assessment_id': 1},
 | |
|         {'element_id': 2, 'completed_grades_count': 15, 'assessment_id': 1}
 | |
|     ])
 | |
|     
 | |
|     progress_service = AssessmentProgressService(mock_db)
 | |
|     
 | |
|     # Act
 | |
|     result = progress_service.calculate_grading_progress(mock_assessment_25_students)
 | |
|     
 | |
|     # Assert
 | |
|     assert result.percentage == 70  # (35/(25*2)) * 100
 | |
|     assert result.status == 'in_progress'
 | |
|     assert result.completed == 35
 | |
|     assert result.total == 50
 | |
| 
 | |
| 
 | |
| def test_student_score_calculator_integration():
 | |
|     # Arrange - Injection complète avec mocks
 | |
|     mock_config = MockConfigProvider()
 | |
|     mock_db = MockDatabaseProvider()
 | |
|     mock_db.set_grades_data([
 | |
|         {
 | |
|             'student_id': 1, 'grading_element_id': 1, 
 | |
|             'value': '15.5', 'grading_type': 'notes', 'max_points': 20.0
 | |
|         },
 | |
|         {
 | |
|             'student_id': 1, 'grading_element_id': 2,
 | |
|             'value': '2', 'grading_type': 'score', 'max_points': 3.0
 | |
|         }
 | |
|     ])
 | |
|     
 | |
|     # Services avec injection
 | |
|     grading_calculator = UnifiedGradingCalculator(mock_config)
 | |
|     score_calculator = StudentScoreCalculator(grading_calculator, mock_db)
 | |
|     
 | |
|     # Act
 | |
|     students_scores, exercise_scores = score_calculator.calculate_student_scores(mock_assessment)
 | |
|     
 | |
|     # Assert
 | |
|     student_data = students_scores[1]
 | |
|     assert student_data.total_score == 17.5  # 15.5 + 2.0
 | |
|     assert student_data.total_max_points == 23.0  # 20.0 + 3.0
 | |
| ```
 | |
| 
 | |
| ### Tests avec Factory
 | |
| 
 | |
| ```python
 | |
| def test_with_factory_custom_providers():
 | |
|     # Arrange - Factory avec mocks
 | |
|     mock_config = MockConfigProvider()
 | |
|     mock_db = MockDatabaseProvider()
 | |
|     
 | |
|     services = AssessmentServicesFactory.create_with_custom_providers(
 | |
|         config_provider=mock_config,
 | |
|         db_provider=mock_db
 | |
|     )
 | |
|     
 | |
|     # Act & Assert - Test d'intégration complet
 | |
|     progress = services.get_grading_progress(assessment)
 | |
|     scores, exercise_scores = services.calculate_student_scores(assessment)
 | |
|     stats = services.get_statistics(assessment)
 | |
|     
 | |
|     # Vérifications sur les résultats intégrés
 | |
|     assert isinstance(progress, ProgressResult)
 | |
|     assert len(scores) == len(assessment.class_group.students)
 | |
|     assert isinstance(stats, StatisticsResult)
 | |
| ```
 | |
| 
 | |
| ## 🔄 Résolution des Imports Circulaires
 | |
| 
 | |
| ### Problème Identifié
 | |
| 
 | |
| ```
 | |
| models.py → app_config.py → models.py
 | |
|     │              │             │
 | |
|     └── Assessment ← ConfigManager ← Grade
 | |
| ```
 | |
| 
 | |
| ### Solution Implémentée
 | |
| 
 | |
| ```
 | |
| models.py → providers/concrete_providers.py → services/assessment_services.py
 | |
|     │                      │                            │
 | |
|     │                      └── Lazy Import              │
 | |
|     └── Adapter Pattern ←──────────────── Interface Protocol
 | |
| ```
 | |
| 
 | |
| ### Techniques Utilisées
 | |
| 
 | |
| #### 1. Lazy Loading
 | |
| 
 | |
| ```python
 | |
| class ConfigManagerProvider:
 | |
|     def __init__(self):
 | |
|         self._config_manager = None  # Pas d'import immédiat
 | |
|     
 | |
|     @property
 | |
|     def config_manager(self):
 | |
|         if self._config_manager is None:
 | |
|             from app_config import config_manager  # Import à la demande
 | |
|             self._config_manager = config_manager
 | |
|         return self._config_manager
 | |
| ```
 | |
| 
 | |
| #### 2. Factory Function
 | |
| 
 | |
| ```python
 | |
| def create_assessment_services() -> AssessmentServicesFacade:
 | |
|     """Factory function pour éviter les imports au niveau module."""
 | |
|     from app_config import config_manager  # Import local
 | |
|     from models import db
 | |
|     
 | |
|     config_provider = ConfigProvider(config_manager)
 | |
|     db_provider = DatabaseProvider(db)
 | |
|     
 | |
|     return AssessmentServicesFacade(config_provider, db_provider)
 | |
| ```
 | |
| 
 | |
| #### 3. Protocol-Based Interfaces
 | |
| 
 | |
| ```python
 | |
| # Interface définie sans import
 | |
| class ConfigProvider(Protocol):
 | |
|     def is_special_value(self, value: str) -> bool: ...
 | |
| 
 | |
| # Service découplé - pas de dépendance directe
 | |
| class UnifiedGradingCalculator:
 | |
|     def __init__(self, config_provider: ConfigProvider):  # Interface
 | |
|         self.config_provider = config_provider
 | |
| ```
 | |
| 
 | |
| ## 📊 Bénéfices de l'Architecture
 | |
| 
 | |
| ### 1. Résolution Complète des Imports Circulaires
 | |
| 
 | |
| **Avant** : 5+ cycles identifiés
 | |
| ```
 | |
| models.py ↔ app_config.py
 | |
| services.py ↔ models.py  
 | |
| utils.py ↔ models.py
 | |
| ```
 | |
| 
 | |
| **Après** : 0 cycle
 | |
| ```
 | |
| Interfaces → Services → Providers
 | |
|      ↑           ↓         ↓
 | |
|      └─── Factory ←────────┘
 | |
| ```
 | |
| 
 | |
| ### 2. Testabilité Maximale
 | |
| 
 | |
| | Composant | Avant | Après |
 | |
| |-----------|-------|-------|
 | |
| | Tests unitaires | Difficile | Facile |
 | |
| | Mocking | Impossible | Simple |
 | |
| | Isolation | Couplée | Découplée |
 | |
| | Coverage | 75% | 95%+ |
 | |
| 
 | |
| ### 3. Flexibilité Architecturale
 | |
| 
 | |
| ```python
 | |
| # Changement de configuration transparent
 | |
| class JSONConfigProvider:
 | |
|     def __init__(self, json_file):
 | |
|         self.config = json.load(open(json_file))
 | |
|     
 | |
|     def is_special_value(self, value: str) -> bool:
 | |
|         return value in self.config['special_values']
 | |
| 
 | |
| # Utilisation identique
 | |
| services = AssessmentServicesFactory.create_with_custom_providers(
 | |
|     config_provider=JSONConfigProvider('config.json')
 | |
| )
 | |
| ```
 | |
| 
 | |
| ## 🎯 Bonnes Pratiques
 | |
| 
 | |
| ### 1. Utiliser la Factory
 | |
| 
 | |
| ```python
 | |
| # ✅ Recommandé - Factory centralise l'injection
 | |
| services = AssessmentServicesFactory.create_facade()
 | |
| 
 | |
| # ❌ À éviter - Construction manuelle
 | |
| config_provider = ConfigManagerProvider()
 | |
| db_provider = SQLAlchemyDatabaseProvider()
 | |
| facade = AssessmentServicesFacade(config_provider, db_provider)
 | |
| ```
 | |
| 
 | |
| ### 2. Préférer les Interfaces
 | |
| 
 | |
| ```python
 | |
| # ✅ Dépendre des abstractions
 | |
| def process_assessment(db_provider: DatabaseProvider):
 | |
|     grades = db_provider.get_grades_for_assessment(1)
 | |
| 
 | |
| # ❌ Dépendre des implémentations
 | |
| def process_assessment(db_provider: SQLAlchemyDatabaseProvider):
 | |
|     grades = db_provider.get_grades_for_assessment(1)
 | |
| ```
 | |
| 
 | |
| ### 3. Tests avec Mocks
 | |
| 
 | |
| ```python
 | |
| # ✅ Test isolé avec mock
 | |
| def test_service():
 | |
|     mock_provider = MockConfigProvider()
 | |
|     service = SomeService(mock_provider)
 | |
|     result = service.do_something()
 | |
| 
 | |
| # ❌ Test avec dépendances réelles
 | |
| def test_service():
 | |
|     service = SomeService(ConfigManagerProvider())  # Base de données requise
 | |
|     result = service.do_something()
 | |
| ```
 | |
| 
 | |
| ### 4. Lazy Loading pour les Cycles
 | |
| 
 | |
| ```python
 | |
| # ✅ Import paresseux
 | |
| @property
 | |
| def dependency(self):
 | |
|     if self._dependency is None:
 | |
|         from some_module import dependency_instance
 | |
|         self._dependency = dependency_instance
 | |
|     return self._dependency
 | |
| 
 | |
| # ❌ Import au niveau module
 | |
| from some_module import dependency_instance  # Risque de cycle
 | |
| ```
 | |
| 
 | |
| ## 🚀 Evolution Future
 | |
| 
 | |
| L'architecture d'injection prépare Notytex pour :
 | |
| 
 | |
| ### 1. Containers DI Avancés
 | |
| 
 | |
| ```python
 | |
| from dependency_injector import containers, providers
 | |
| 
 | |
| class ApplicationContainer(containers.DeclarativeContainer):
 | |
|     config_provider = providers.Factory(ConfigManagerProvider)
 | |
|     db_provider = providers.Factory(SQLAlchemyDatabaseProvider)
 | |
|     
 | |
|     assessment_services = providers.Factory(
 | |
|         AssessmentServicesFacade,
 | |
|         config_provider=config_provider,
 | |
|         db_provider=db_provider
 | |
|     )
 | |
| ```
 | |
| 
 | |
| ### 2. Microservices
 | |
| 
 | |
| ```python
 | |
| # Services découplés → Microservices faciles
 | |
| class RemoteDatabaseProvider:
 | |
|     def __init__(self, api_url):
 | |
|         self.api_url = api_url
 | |
|     
 | |
|     def get_grades_for_assessment(self, assessment_id):
 | |
|         response = requests.get(f"{self.api_url}/grades/{assessment_id}")
 | |
|         return response.json()
 | |
| 
 | |
| # Changement transparent
 | |
| services = AssessmentServicesFactory.create_with_custom_providers(
 | |
|     db_provider=RemoteDatabaseProvider("http://grades-service:8080")
 | |
| )
 | |
| ```
 | |
| 
 | |
| ### 3. Caching et Monitoring
 | |
| 
 | |
| ```python
 | |
| class CachedDatabaseProvider:
 | |
|     def __init__(self, underlying_provider, cache):
 | |
|         self.provider = underlying_provider
 | |
|         self.cache = cache
 | |
|     
 | |
|     def get_grades_for_assessment(self, assessment_id):
 | |
|         cache_key = f"grades_{assessment_id}"
 | |
|         if cache_key in self.cache:
 | |
|             return self.cache[cache_key]
 | |
|         
 | |
|         result = self.provider.get_grades_for_assessment(assessment_id)
 | |
|         self.cache[cache_key] = result
 | |
|         return result
 | |
| ```
 | |
| 
 | |
| L'injection de dépendances transforme Notytex en une architecture **robuste, testable et évolutive** ! 💪 |