Semaine 6
Cette semaine nous allons restructurer notre application pour la rendre plus maintenable, plus testable et plus propre.
Objectifs
Nous avons trois objectifs principaux pour cette séance :
- Architecture en Services : Sortir la logique métier des Contrôleurs.
- Gestion globale des erreurs : Supprimer les répétitions de
try/catchgrâce à un Middleware. - Intégrité des données : Utiliser des Transactions pour sécuriser les achats.
Partie 1 : Architecture en Services
Actuellement, vos contrôleurs contiennent probablement toute la logique : accès à la BDD, calculs, règles métier.
Nous allons alléger les contrôleurs pour qu’ils ne s’occupent que de leur rôle : recevoir des requêtes HTTP et renvoyer des réponses HTTP.
1. Extraction des Services
Consignes
- Créez un dossier
Services. - Créez les classes suivantes :
UserServiceGameServiceInventoryService
- N’oubliez pas d’enregistrer ces services dans
Program.csavecbuilder.Services.AddScoped<...>();. - Déplacez la logique de vos contrôleurs vers ces services.
Exemple de responsabilité :
- Le Contrôleur récupère l’ID utilisateur depuis le Token, appelle le Service, et transforme le résultat en 200 OK ou renvoie une erreur.
- Le Service contient le
DbContext, cherche l’utilisateur en BDD, vérifie s’il a assez d’argent, effectue l’achat, sauvegarde en BDD, et retourne le nouvel état (ou lance une exception).
Référez-vous au cours sur L’architecture Services.
Partie 2 : Middleware et Gestion d’Erreurs
Une fois vos services en place, nous allons nettoyer la gestion des erreurs.
2. Création des Exceptions Personnalisées
Commencez par créer un dossier Exceptions.
Vous créerez une classe GameException qui hérite de Exception et qui aura les propriétés Message, Code et StatusCode.
Remplacez les exceptions génériques dans votre code par ces exceptions personnalisées.
3. Le Middleware de Gestion d’Erreurs
Créez un dossier Middlewares et ajoutez une classe ErrorHandlingMiddleware.cs.
Ce middleware devra :
- Contenir un bloc
try/catchqui englobe l’appel au middleware suivant (await _next(context)). - Dans le
catch, appeler une méthode privée qui va gérer l’exception. - Cette méthode devra définir le
StatusCodede la réponse HTTP et écrire le JSON d’erreur dans le corps de la réponse.
Vous devrez mapper vos exceptions personnalisées aux codes HTTP et codes d’erreurs fonctionnels attendus par le frontend.
Par exemple :
GameException-> Status Code + ErrorResponseException(toutes les autres) -> 500 Internal Server Error + CodeINTERNAL_SERVER_ERROR
Référez-vous au cours sur les Middlewares pour la structure de base.
4. Enregistrement dans Program.cs
N’oubliez pas d’enregistrer votre middleware dans le pipeline de requête dans Program.cs.
5. Refactoring des Contrôleurs
C’est l’étape la plus importante. Vous allez devoir supprimer la gestion d’erreur manuelle de vos contrôleurs.
Vos méthodes de contrôleur ne devraient plus retourner de ActionResult en cas d’erreur (NotFound(), BadRequest()), mais laisser l’exception se propager.
Objectif : Le code de vos contrôleurs doit se concentrer uniquement sur le “Happy Path” (le cas où tout fonctionne).
Exemple conceptuel de ce à quoi cela doit ressembler :
[HttpGet("{id}")]
public ActionResult<UserPublic> GetUser(int id)
{
// Le service lance une exception si l'user n'existe pas.
// Le contrôleur ne s'en occupe plus.
var user = _userService.GetById(id);
return Ok(user);
}
Vous devrez donc aussi modifier vos Services pour qu’ils lèvent les exceptions que vous avez créées au lieu de retourner null ou des codes d’erreur.
Partie 3 : Transactions
Dans la fonction d’achat d’un objet (dans InventoryService), vous effectuez deux opérations critiques :
- Débiter l’utilisateur (modification de
Progression). - Ajouter l’objet (modification de
Inventory).
Si le serveur plante ou si une erreur survient entre ces deux étapes, vous risquez de débiter l’utilisateur sans lui donner l’objet, ou l’inverse.
6. Sécuriser les achats
Utilisez une transaction explicite d’Entity Framework pour englober ces deux opérations.
using var transaction = _context.Database.BeginTransaction();
try {
// 1. Débiter l'argent
// 2. Ajouter l'item
// 3. SaveChanges
transaction.Commit();
} catch {
transaction.Rollback();
throw;
}
Référez-vous au cours sur les Transactions.
Tâches à réaliser
- Extraire la logique métier vers des Services (
UserService,GameService,InventoryService). - Créer les exceptions personnalisées.
- Implémenter le
ErrorHandlingMiddleware. - Configurer
Program.cspour utiliser les services et le middleware. - Refactoriser tout le
GameController,UserControlleretInventoryControllerpour utiliser les services et supprimer lestry/catch. - Ajouter une Transaction dans la méthode d’achat de l’
InventoryService. - (Bonus) Ajouter des logs dans le middleware pour les erreurs 500.