Aller au contenu principal

Les Décorateurs : Architecture & Philosophie

Le package my-fastify-decorators repose sur un concept fondamental du développement TypeScript moderne : les décorateurs.

Pourquoi utiliser des décorateurs ?

Dans une application Node.js standard, la logique est souvent dispersée : la définition des routes est à un endroit, la validation à un autre, et l'injection de dépendances est souvent gérée "au petit bonheur la chance".

Les décorateurs permettent la Métaprogrammation. Ils nous permettent d'attacher des métadonnées à des classes et des méthodes de manière déclarative.

Les avantages clés

  1. Déclaratif vs Impératif : Au lieu d'écrire comment une route doit être enregistrée (fastify.get(...)), tu déclares simplement ce qu'elle est (@Get()). Le framework s'occupe de connecter la plomberie.
  2. Séparation des responsabilités (SoC) : Tes classes ne sont plus polluées par la logique d'infrastructure HTTP. Un contrôleur reste une classe TypeScript standard testable unitairement.
  3. Réutilisabilité (L'effet Lego) : Une fois que tu as écrit un Service ou un Guard, il n'est pas marié à une route spécifique. Tu as besoin de ton AuthService dans le UserController ? Hop, injecté. Tu le veux aussi dans ton ChatGateway ? Hop, injecté aussi. Tu codes une fois, tu utilises partout.
  4. Standardisation : Cette approche impose une structure rigoureuse (Et c'est COOL ! 😈). Tous les développeurs de l'équipe suivent le même pattern (Modules -> Contrôleurs -> Services). Fini le code spaghetti.
  5. Sécurité & Centralisation : Les Guards (@UseGuards) et Middlewares (@Middleware) sont appliqués directement sur les cibles concernées. Il est visuellement impossible d'oublier qu'une route est protégée.
  6. Architecture Évolutive : C'est la force cachée du système. Comme le framework t'oblige à tout découper en Modules isolés, tu ne te fermes aucune porte. Tu peux commencer en mode Monolithe pour aller vite. Le jour où tu as besoin de scaler ? Tu prends ton module, tu fais un copier-coller dans un nouveau repo, et BOOM : tu as un microservice fonctionnel. Le découpage est déjà fait !

Qu'est-ce que l'Injection de Dépendances (DI) ?

L'Injection de Dépendances est un motif de conception (design pattern) dans lequel une classe ne crée pas elle-même les objets dont elle a besoin. Au lieu de cela, ces objets lui sont fournis (injectés) de l'extérieur.

C'est une forme d'Inversion de Contrôle (IoC) : ce n'est plus ton code qui décide quand créer une instance, c'est le framework.

Sans Injection (Couplage Fort) ❌

Dans une approche classique, le contrôleur est responsable de créer le service. Si le service change (ex: il a besoin d'une nouvelle config dans son constructeur), tu dois modifier tous tes contrôleurs. C'est l'enfer à maintenir.

// Mauvaise pratique
export class UserController {
private userService: UserService;

constructor() {
// Le contrôleur est "collé" à cette implémentation précise
this.userService = new UserService();
}
}

Avec Injection (Découplage) ✅

Avec my-fastify-decorators, tu déclares juste tes besoins. Le système te sert sur un plateau. Il existe deux façons de faire selon tes préférences.

Option 1 : Via le Constructeur (Recommandé)

C'est la méthode la plus propre. TypeScript devine automatiquement le type.

@Controller('/users')
export class UserController {
// Le framework voit "UserService" et l'injecte automatiquement
constructor(private userService: UserService) {}

@Get()
getAll() {
return this.userService.findAll();
}
}

Option 2 : Via la Propriété (@Inject)

Utile si tu ne veux pas toucher au constructeur ou si tu préfères une déclaration explicite.

import { Inject, Controller } from 'my-fastify-decorators';

@Controller('/gamehttp')
export class GamehttpController {

// Injection directe sur la propriété
@Inject(GamehttpService)
private gamehttpService!: GamehttpService; // L'opérateur "!" (Definite Assignment Assertion) est obligatoire ici. Il garantit à TypeScript que la propriété sera initialisée par le framework lors du bootstrap.

@Inject(UserService)
private userService!: UserService;

@Get()
play() {
return this.gamehttpService.startGame();
}
}

⚙️ Comment ça marche sous le capot ?

remarque

Cette partie n'est pas obligatoire pour utiliser le framework, mais si tu es curieux de savoir comment la magie opère, c'est ici que ça se passe.

Pour bien comprendre, il faut visualiser le démarrage de l'application en deux phases distinctes.

Personnellement, j'aime voir ça comme une "compilation de droite à gauche". On commence par la fin (les feuilles de l'arbre) pour remonter vers la racine, ce qui peut paraître contre-productif, mais c'est logique dans une architecture déclarative.

Phase 1 : La Définition (Le Marquage)

C'est le passage "passif".

Lorsque Node.js lance ton application, il commence par lire (importer) tous tes fichiers. À ce moment précis, les décorateurs s'exécutent immédiatement.

Cependant, ils ne créent pas de routes et ne lancent pas de serveur. Ils agissent comme des étiqueteurs. Quand le décorateur @Controller('/users') s'exécute, il colle simplement un "Post-it" (métadonnée) invisible sur ta classe : "Attention, ceci est un contrôleur avec le préfixe /users".

// Phase 1 : Node.js lit le fichier
@Controller('/users') // <- Le décorateur s'exécute et stocke { path: '/users' }
class UserController {
@Get() // <- Stocke { method: 'GET', path: '/' }
getAll() {}
}

Phase 2 : Le Bootstrap (L'Assemblage)

C'est le passage "actif" et récursif.

C'est ici que ma théorie du "droite à gauche" (ou plutôt bas vers haut) prend tout son sens. Quand tu lances bootstrap(app, MainModule) :

  1. Exploration : Le moteur part du module racine.
  2. Récursion : Il plonge dans les imports pour charger les modules enfants, puis les petits-enfants, jusqu'au bout de l'arbre.
  3. Lecture : Une fois sur un module "feuille", il récupère les contrôleurs associés.
  4. Assemblage : Il lit les "Post-its" (métadonnées) posés lors de la Phase 1.
    • "Ah, cette classe a un Post-it Contrôleur !"
    • "Ah, cette méthode a un Post-it GET !"
  5. Action : Il traduit ces Post-its en appels Fastify réels (app.get('/users', ...)).

Résumé visuel

On passe d'une approche impérative (exécuter les ordres au fur et à mesure) à une approche déclarative (tout déclarer d'abord, tout assembler ensuite).

  1. Code : DéfinitionMémoire : Métadonnées
  2. Bootstrap : Lecture MétadonnéesFastify : Routes Actives