Middleware comme décorateur
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy. Leçon 97 sur 107.
Le middleware est l’une des applications les plus pratiques du pattern Decorator en Go. Dans le développement web, les fonctions de middleware enveloppent les gestionnaires HTTP afin d’ajouter des préoccupations transversales telles que la journalisation, l’authentification ou le chronométrage, sans modifier le gestionnaire d’origine.
L’idée clé est que middleware prend un handler et renvoie un nouveau handler avec un comportement ajouté. Cela suit exactement le patron Decorator : même interface en entrée et en sortie, avec des fonctionnalités améliorées :
type Handler func(request string) string
func LoggingMiddleware(next Handler) Handler {
return func(request string) string {
result := next(request)
return "[LOG] " + result
}
}
func AuthMiddleware(next Handler) Handler {
return func(request string) string {
return "[AUTH] " + next(request)
}
}Chaque middleware encapsule le gestionnaire suivant dans la chaîne, en ajoutant son comportement avant ou après l’appel du gestionnaire encapsulé. Vous pouvez empiler plusieurs middlewares en les composant :
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Réponse à : GET /usersL’ordre d’encapsulation détermine l’ordre d’exécution : le middleware le plus externe s’exécute en premier. Ce modèle est largement utilisé dans les frameworks web Go pour les pipelines de traitement des requêtes, ce qui facilite l’ajout ou la suppression de fonctionnalités sans toucher à la logique du gestionnaire principal.
Défi
FacileConstruisons un pipeline de traitement des requêtes en utilisant le modèle Middleware comme décorateur ! Vous allez créer un système dans lequel des fonctions middleware enveloppent des gestionnaires afin d’ajouter des fonctionnalités comme le chronométrage, la validation et le formatage, le tout sans modifier la logique du gestionnaire principal.
Vous organiserez votre code sur trois fichiers :
handler.go: définissez votre type de gestionnaire ainsi que le gestionnaire de base qui traite les requêtes.Créez un type
Handlersous la forme d’une fonction qui prend une chaîne de requête et renvoie une chaîne de réponse :type Handler func(request string) stringImplémentez une fonction
BaseHandlerqui renvoieProcessed: [request]pour toute requête reçue.middleware.go: créez vos fonctions middleware qui enveloppent les gestionnaires avec un comportement supplémentaire.Créez trois fonctions middleware, chacune prenant un
Handleret renvoyant un nouveauHandler:TimingMiddleware: enveloppe la réponse avec des informations de chronométrage et renvoie[TIMING] [response]ValidationMiddleware: ajoute un préfixe de validation et renvoie[VALID] [response]UppercaseMiddleware: convertit toute la réponse en majuscules
Chaque middleware doit appeler le gestionnaire enveloppé et enrichir son résultat.
main.go: construisez un pipeline de traitement en composant les middleware.Lisez le nombre de couches middleware à appliquer. Ensuite, pour chaque couche, lisez le type de middleware (
timing,validationouuppercase) et enveloppez votre gestionnaire actuel avec ce middleware. Appliquez-les dans l’ordre où ils sont lus : le premier middleware lu devient l’enveloppe la plus externe.Après avoir construit le pipeline, lisez la chaîne de requête, traitez-la via votre gestionnaire composé et affichez le résultat.
Les entrées suivantes seront fournies :
- Ligne 1 : nombre de couches middleware
- Lignes suivantes : un type de middleware par ligne (
timing,validationouuppercase) - Dernière ligne : la requête à traiter
Par exemple, avec :
2
timing
validation
GET /usersVotre sortie doit être :
[TIMING] [VALID] Processed: GET /usersEt avec :
3
uppercase
timing
validation
POST /dataVotre sortie doit être :
[TIMING] [VALID] PROCESSED: POST /DATAEt avec :
1
uppercase
hello worldVotre sortie doit être :
PROCESSED: HELLO WORLDEt avec :
0
simple requestVotre sortie doit être :
Processed: simple requestRemarquez comment l’ordre d’enveloppement des middleware affecte la sortie : le premier middleware appliqué (le plus externe) s’exécute en premier et reçoit le résultat final de tous les middleware internes. Dans le deuxième exemple, le middleware en majuscules s’exécute en dernier (le plus interne), il convertit donc la réponse de base en majuscules avant que le chronométrage et la validation n’ajoutent leurs préfixes !
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le nombre de couches de middleware
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Lire les types de middleware
middlewareTypes := make([]string, n)
for i := 0; i < n; i++ {
line, _ := reader.ReadString('\n')
middlewareTypes[i] = strings.TrimSpace(line)
}
// Lire la requête
request, _ := reader.ReadString('\n')
request = strings.TrimSpace(request)
// TODO: Commencer avec le BaseHandler
// TODO: Construire le pipeline en enveloppant le handler avec le middleware
// Appliquer le middleware dans l'ordre - le premier middleware lu devient le wrapper le plus externe
// Hint: Parcourir middlewareTypes dans l'ordre REVERSE pour y parvenir
// Le dernier middleware appliqué sera le plus externe (s'exécute en premier)
// TODO: Traiter la requête via le handler composé et afficher le résultat
fmt.Println("TODO: implement pipeline")
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et modules GoPackages et importsNoms exportés et non exportésIntroduction à la POO en GoStructs comme classesDéfinir des méthodes sur des structsRécepteurs pointeurs ou par valeurInitialisation des structsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL’interface comme contratInterface vide (any)Assertion de typeCommutation de typeComposition d’interfacesInterfaces Stringer et ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter et SetterDissimulation de l’information en GoRécapitulatif – Fiches d’étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesSolution de contournement pour les méthodes génériquesRécapitulatif - Collection générique2Plongée approfondie dans les types et les structs
Types de base et compositesDéfinitions de types personnalisésTags de structStructs anonymesStructs imbriquésValeurs zéro et valeurs par défautRécapitulatif - Carnet de contacts5La composition plutôt que l’héritage
Pourquoi Go n’a pas d’héritageBases de l’inclusion de structsPromotion des méthodesInclure plusieurs structsInclusion ou agrégationMasquage des méthodes inclusesRécapitulatif : hiérarchie des employés8Gestion des erreurs et POO
L’interface errorTypes d’erreurs personnalisésEnrobage des erreurs (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - Analyseur de fichiers11Bibliothèque standard et POO
io.Reader et io.Writersort.InterfaceInterface fmt.Stringerencoding/json avec des structsInterface http.HandlerRécapitulatif - Modèles d’API REST14Modèles de conception – Partie 2
Modèle CommandeModèle AdaptateurModèle DécorateurModèle Méthode modèleModèle ÉtatModèle CompositeMiddleware comme décorateur3Pointeurs et mémoire
Notions de base des pointeurs en GoPointeurs vers des structuresPassage par valeur ou par référenceLa fonction new()Garbage collection en GoRécapitulatif - Constructeur de listes chaînées6Polymorphisme en Go
Polymorphisme via les interfacesDuck typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif – Processeur de paiements9Concurrence et POO
Bases des GoroutinesCanaux et communicationCanaux tamponnés ou non tamponnésInstruction selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures thread-safeRécapitulatif - Pool de workersEntraînez-vous par vous-même : Compilateur Go en ligne