Menu
Coddy logo textTech

Middleware en tant que 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 patron Decorator en Go. En développement web, les fonctions de middleware enveloppent les gestionnaires HTTP pour ajouter des préoccupations transversales telles que la journalisation (logging), l'authentification ou le chronométrage (timing) sans modifier le gestionnaire d'origine.

L'idée centrale est que le middleware prend un gestionnaire (handler) et renvoie un nouveau gestionnaire avec un comportement ajouté. Cela suit exactement le patron Decorator — la même interface en entrée et en sortie, avec des fonctionnalités enrichies :

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 enveloppe le gestionnaire suivant dans la chaîne, en ajoutant son comportement avant ou après l'appel du gestionnaire enveloppé. 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 /users

L'ordre d'imbrication 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 permet d'ajouter ou de supprimer facilement des fonctionnalités sans toucher à la logique principale du gestionnaire (handler).

challenge icon

Défi

Facile

Construisons un pipeline de traitement de requêtes en utilisant le modèle Middleware as Decorator ! Vous allez créer un système où des fonctions middleware enveloppent des gestionnaires (handlers) pour ajouter des fonctionnalités telles que le chronométrage, la validation et le formatage—le tout sans modifier la logique de base du gestionnaire.

Vous organiserez votre code sur trois fichiers :

  • handler.go : Définissez votre type de gestionnaire et le gestionnaire de base qui traite les requêtes.

    Créez un type Handler sous forme de fonction qui prend une chaîne de requête (request string) et retourne une chaîne de réponse (response string) :

    type Handler func(request string) string

    Implémentez une fonction BaseHandler qui retourne Processed: [request] pour toute requête qu'elle reçoit.

  • middleware.go : Créez vos fonctions middleware qui enveloppent les gestionnaires avec un comportement supplémentaire.

    Construisez trois fonctions middleware, chacune prenant un Handler et retournant un nouveau Handler :

    • TimingMiddleware — enveloppe la réponse avec des informations de chronométrage, retournant [TIMING] [response]
    • ValidationMiddleware — ajoute un préfixe de validation, retournant [VALID] [response]
    • UppercaseMiddleware — convertit l'intégralité de la réponse en majuscules

    Chaque middleware doit appeler le gestionnaire enveloppé et améliorer son résultat.

  • main.go : Construisez un pipeline de traitement en composant les middlewares.

    Lisez le nombre de couches de middleware à appliquer. Ensuite, pour chaque couche, lisez le type de middleware (timing, validation, ou uppercase) 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 de middleware
  • Lignes suivantes : Un type de middleware par ligne (timing, validation, ou uppercase)
  • Dernière ligne : La requête à traiter

Par exemple, avec :

2
timing
validation
GET /users

Votre sortie devrait être :

[TIMING] [VALID] Processed: GET /users

Et avec :

3
uppercase
timing
validation
POST /data

Votre sortie devrait être :

[TIMING] [VALID] PROCESSED: POST /DATA

Et avec :

1
uppercase
hello world

Votre sortie devrait être :

PROCESSED: HELLO WORLD

Et avec :

0
simple request

Votre sortie devrait être :

Processed: simple request

Remarquez comment l'ordre d'enveloppement du middleware affecte la sortie—le premier middleware appliqué (le plus externe) s'exécute en premier et voit le résultat final de tous les middlewares internes. Le middleware uppercase dans le deuxième exemple s'exécute en dernier (le plus interne), il met donc en majuscules la réponse de base avant que le chronométrage et la validation n'ajoutent leurs préfixes !

Aide-mémoire

Les middlewares en Go suivent le motif Décorateur (Decorator pattern) en enveloppant les gestionnaires (handlers) pour ajouter des fonctionnalités sans modifier le gestionnaire d'origine.

Définissez un type de gestionnaire sous forme de fonction :

type Handler func(request string) string

Les fonctions de middleware prennent un gestionnaire et renvoient un nouveau gestionnaire avec un comportement amélioré :

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)
    }
}

Empilez plusieurs middlewares en les composant :

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// Sortie : [LOG] [AUTH] Response to: GET /users

Le middleware le plus externe s'exécute en premier, déterminant l'ordre d'exécution. Chaque middleware peut ajouter un comportement avant ou après l'appel du gestionnaire enveloppé.

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 l'enveloppe la plus externe
	// Hint: Parcourir middlewareTypes dans l'ordre INVERSE 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")
}
quiz iconTestez-vous

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