Menu
Coddy logo textTech

Middleware als Decorator

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 97 von 107.

Middleware ist eine der praktischsten Anwendungen des Decorator-Musters in Go. In der Webentwicklung umschließen Middleware-Funktionen HTTP-Handler, um querschnittliche Belange wie Logging, Authentifizierung oder Zeitmessung hinzuzufügen, ohne den ursprünglichen Handler zu verändern.

Die entscheidende Erkenntnis ist, dass Middleware einen Handler entgegennimmt und einen neuen Handler mit zusätzlichem Verhalten zurückgibt. Dies folgt exakt dem Decorator-Muster – dieselbe Schnittstelle am Eingang und Ausgang, mit erweiterter Funktionalität:

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

Jede Middleware umschließt den nächsten Handler in der Kette und fügt ihr Verhalten vor oder nach dem Aufruf des umschlossenen Handlers hinzu. Sie können mehrere Middlewares stapeln, indem Sie diese kombinieren:

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

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Antwort auf: GET /users

Die Reihenfolge des Wrappings bestimmt die Ausführungsreihenfolge – die äußerste Middleware wird zuerst ausgeführt. Dieses Muster wird in Go-Web-Frameworks ausgiebig für Request-Processing-Pipelines verwendet, was es einfach macht, Funktionalität hinzuzufügen oder zu entfernen, ohne die Kernlogik des Handlers zu berühren.

challenge icon

Aufgabe

Einfach

Lassen wir uns eine Anfrageverarbeitungspipeline mit dem Middleware-als-Decorator-Muster bauen! Sie erstellen ein System, in dem Middleware-Funktionen Handler umschließen, um Funktionen wie Zeitmessung, Validierung und Formatierung hinzuzufügen – und das alles, ohne die Kernlogik des Handlers zu ändern.

Sie organisieren Ihren Code in drei Dateien:

  • handler.go: Definieren Sie Ihren Handler-Typ und den Basis-Handler, der Anfragen verarbeitet.

    Erstellen Sie einen Handler-Typ als Funktion, die einen Request-String entgegennimmt und einen Response-String zurückgibt:

    type Handler func(request string) string

    Implementieren Sie eine BaseHandler-Funktion, die Processed: [request] für jede empfangene Anfrage zurückgibt.

  • middleware.go: Erstellen Sie Ihre Middleware-Funktionen, die Handler mit zusätzlichem Verhalten umschließen.

    Erstellen Sie drei Middleware-Funktionen, die jeweils einen Handler entgegennehmen und einen neuen Handler zurückgeben:

    • TimingMiddleware — umschließt die Antwort mit Zeitmessungsinformationen und gibt [TIMING] [response] zurück
    • ValidationMiddleware — fügt ein Validierungspräfix hinzu und gibt [VALID] [response] zurück
    • UppercaseMiddleware — wandelt die gesamte Antwort in Großbuchstaben um

    Jede Middleware sollte den umschlossenen Handler aufrufen und dessen Ergebnis erweitern.

  • main.go: Bauen Sie eine Verarbeitungspipeline durch Komposition von Middleware auf.

    Lesen Sie die Anzahl der anzuwendenden Middleware-Schichten ein. Lesen Sie dann für jede Schicht den Middleware-Typ (timing, validation oder uppercase) ein und umschließen Sie Ihren aktuellen Handler mit dieser Middleware. Wenden Sie diese in der Reihenfolge an, in der sie gelesen werden – die zuerst gelesene Middleware wird zur äußersten Hülle.

    Nachdem Sie die Pipeline aufgebaut haben, lesen Sie den Request-String ein, verarbeiten ihn durch Ihren zusammengesetzten Handler und geben das Ergebnis aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Anzahl der Middleware-Schichten
  • Folgende Zeilen: Ein Middleware-Typ pro Zeile (timing, validation oder uppercase)
  • Letzte Zeile: Die zu verarbeitende Anfrage

Zum Beispiel, gegeben:

2
timing
validation
GET /users

Sollte Ihre Ausgabe wie folgt aussehen:

[TIMING] [VALID] Processed: GET /users

Und gegeben:

3
uppercase
timing
validation
POST /data

Sollte Ihre Ausgabe wie folgt aussehen:

[TIMING] [VALID] PROCESSED: POST /DATA

Und gegeben:

1
uppercase
hello world

Sollte Ihre Ausgabe wie folgt aussehen:

PROCESSED: HELLO WORLD

Und gegeben:

0
simple request

Sollte Ihre Ausgabe wie folgt aussehen:

Processed: simple request

Beachten Sie, wie die Reihenfolge der Middleware-Umschließung die Ausgabe beeinflusst – die zuerst angewendete Middleware (die äußerste) wird zuerst ausgeführt und sieht das Endergebnis aller inneren Middlewares. Die Uppercase-Middleware im zweiten Beispiel wird zuletzt (als innerste) ausgeführt, sodass sie die Basisantwort in Großbuchstaben umwandelt, bevor Timing und Validation ihre Präfixe hinzufügen!

Spickzettel

Middleware in Go folgt dem Decorator-Muster, indem Handler eingepackt werden, um Funktionalität hinzuzufügen, ohne den ursprünglichen Handler zu verändern.

Definieren Sie einen Handler-Typ als Funktion:

type Handler func(request string) string

Middleware-Funktionen nehmen einen Handler entgegen und geben einen neuen Handler mit erweitertem Verhalten zurück:

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

Stapeln Sie mehrere Middlewares, indem Sie sie zusammensetzen:

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

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

Die äußerste Middleware wird zuerst ausgeführt und bestimmt die Reihenfolge der Ausführung. Jede Middleware kann Verhalten vor oder nach dem Aufruf des eingepackten Handlers hinzufügen.

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Anzahl der Middleware-Schichten lesen
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Middleware-Typen lesen
	middlewareTypes := make([]string, n)
	for i := 0; i < n; i++ {
		line, _ := reader.ReadString('\n')
		middlewareTypes[i] = strings.TrimSpace(line)
	}

	// Die Anfrage lesen
	request, _ := reader.ReadString('\n')
	request = strings.TrimSpace(request)

	// TODO: Mit dem BaseHandler beginnen

	// TODO: Die Pipeline aufbauen, indem der Handler mit Middleware umschlossen wird
	// Middleware in der richtigen Reihenfolge anwenden - die zuerst gelesene Middleware wird zur äußersten Hülle
	// Hinweis: Durchlaufe middlewareTypes in UMGEKEHRTER Reihenfolge, um dies zu erreichen
	// Die zuletzt angewendete Middleware wird die äußerste sein (wird zuerst ausgeführt)

	// TODO: Die Anfrage durch den zusammengesetzten Handler verarbeiten und das Ergebnis ausgeben
	fmt.Println("TODO: implement pipeline")
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung