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** ! 💪 |