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 übergreifende Belange wie Protokollierung, Authentifizierung oder Zeitmessung hinzuzufügen, ohne den ursprünglichen Handler zu ändern.

Die zentrale Erkenntnis ist, dass Middleware einen Handler übernimmt und einen neuen Handler mit zusätzlichem Verhalten zurückgibt. Dies entspricht genau dem Decorator-Muster: dieselbe Schnittstelle hinein und hinaus, 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. Du kannst mehrere Middleware kombinieren, indem du sie zusammensetzt:

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 der Umhüllung bestimmt die Ausführungsreihenfolge: Die äußerste Middleware wird zuerst ausgeführt. Dieses Muster wird in Go-Webframeworks häufig für Pipelines zur Anfrageverarbeitung verwendet und ermöglicht das einfache Hinzufügen oder Entfernen von Funktionalität, ohne die Kernlogik des Handlers zu verändern.

challenge icon

Aufgabe

Einfach

Erstellen wir eine Anfrageverarbeitungspipeline mit dem Middleware-als-Decorator-Muster! Du erstellst ein System, in dem Middleware-Funktionen Handler umschließen, um Funktionalität wie Zeitmessung, Validierung und Formatierung hinzuzufügen – und das alles, ohne die Kernlogik des Handlers zu verändern.

Du organisierst deinen Code auf drei Dateien verteilt:

  • handler.go: Definiere deinen Handler-Typ und den Basis-Handler, der Anfragen verarbeitet.

    Erstelle einen Handler-Typ als Funktion, die einen Anfrage-String entgegennimmt und einen Antwort-String zurückgibt:

    type Handler func(request string) string

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

  • middleware.go: Erstelle deine Middleware-Funktionen, die Handler mit zusätzlichem Verhalten umschließen.

    Erstelle drei Middleware-Funktionen, die jeweils einen Handler entgegennimmt und einen neuen Handler zurückgibt:

    • 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: Erstelle eine Verarbeitungspipeline, indem du Middleware kombinierst.

    Lies die Anzahl der anzuwendenden Middleware-Ebenen ein. Lies anschließend für jede Ebene den Middleware-Typ (timing, validation oder uppercase) ein und umschließe deinen aktuellen Handler mit dieser Middleware. Wende sie in der Reihenfolge an, in der sie eingelesen werden: Die zuerst eingelesene Middleware wird zum äußersten Wrapper.

    Lies nach dem Erstellen der Pipeline den Anfrage-String ein, verarbeite ihn durch deinen kombinierten Handler und gib das Ergebnis aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Anzahl der Middleware-Ebenen
  • Folgende Zeilen: Je eine Middleware-Art pro Zeile (timing, validation oder uppercase)
  • Letzte Zeile: Die zu verarbeitende Anfrage

Beispielsweise bei:

2
timing
validation
GET /users

Die Ausgabe sollte sein:

[TIMING] [VALID] Processed: GET /users

Und bei:

3
uppercase
timing
validation
POST /data

Die Ausgabe sollte sein:

[TIMING] [VALID] PROCESSED: POST /DATA

Und bei:

1
uppercase
hello world

Die Ausgabe sollte sein:

PROCESSED: HELLO WORLD

Und bei:

0
simple request

Die Ausgabe sollte sein:

Processed: simple request

Beachte, wie sich die Reihenfolge der Middleware-Umschachtelung auf die Ausgabe auswirkt: Die zuerst angewendete Middleware (die äußerste) wird zuerst ausgeführt und erhält das endgültige Ergebnis aller inneren Middleware-Funktionen. Die Uppercase-Middleware im zweiten Beispiel wird zuletzt ausgeführt (als innerste Middleware), sodass sie die Basisantwort in Großbuchstaben umwandelt, bevor Timing und Validierung ihre Präfixe 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: Beginne mit dem BaseHandler

	// TODO: Baue die Pipeline auf, indem du den Handler mit Middleware umhüllst
	// Middleware der Reihe nach anwenden – die zuerst gelesene Middleware wird zum äußersten Wrapper
	// Hinweis: Gehe middlewareTypes in UMGEKEHRTER Reihenfolge durch, um dies zu erreichen
	// Die zuletzt angewendete Middleware wird die äußerste sein (läuft zuerst)

	// TODO: Verarbeite die Anfrage durch den zusammengesetzten Handler und gib das Ergebnis aus
	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

Übe selbstständig: Online-Go-Compiler