Menu
Coddy logo textTech

Middleware jako dekorator

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 97 z 107.

Middleware to jedno z najbardziej praktycznych zastosowań wzorca Dekorator w Go. W programowaniu internetowym funkcje middleware opakowują procedury obsługi HTTP, aby dodawać przekrojowe funkcje, takie jak rejestrowanie zdarzeń, uwierzytelnianie czy pomiar czasu, bez modyfikowania oryginalnej procedury obsługi.

Kluczowy wniosek jest taki, że oprogramowanie pośredniczące przyjmuje procedurę obsługi i zwraca nową procedurę obsługi z dodatkowym zachowaniem. Dokładnie na tym polega wzorzec Dekorator: ten sam interfejs na wejściu i wyjściu, z rozszerzoną funkcjonalnością:

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

Każde oprogramowanie pośredniczące opakowuje kolejny program obsługi w łańcuchu, dodając swoje działanie przed wywołaniem opakowanego programu obsługi lub po nim. Możesz łączyć wiele elementów oprogramowania pośredniczącego:

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

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

Kolejność opakowywania określa kolejność wykonywania: najpierw uruchamia się najbardziej zewnętrzne oprogramowanie pośredniczące. Ten wzorzec jest szeroko stosowany w frameworkach internetowych Go do tworzenia potoków przetwarzania żądań, co ułatwia dodawanie lub usuwanie funkcjonalności bez ingerowania w logikę głównego programu obsługi.

challenge icon

Wyzwanie

Łatwy

Zbudujmy potok przetwarzania żądań, używając wzorca Middleware jako dekoratora! Utworzysz system, w którym funkcje middleware opakowują handlery, aby dodawać funkcjonalności, takie jak pomiar czasu, walidacja i formatowanie — a wszystko to bez modyfikowania logiki głównego handlera.

Podzielisz kod na trzy pliki:

  • handler.go: Zdefiniuj typ handlera oraz podstawowy handler, który przetwarza żądania.

    Utwórz typ Handler jako funkcję, która przyjmuje ciąg znaków żądania i zwraca ciąg znaków odpowiedzi:

    type Handler func(request string) string

    Zaimplementuj funkcję BaseHandler, która dla każdego otrzymanego żądania zwraca Processed: [request].

  • middleware.go: Utwórz funkcje middleware, które opakowują handlery, dodając dodatkowe zachowanie.

    Zbuduj trzy funkcje middleware, z których każda przyjmuje Handler i zwraca nowy Handler:

    • TimingMiddleware: opakowuje odpowiedź informacją o pomiarze czasu i zwraca [TIMING] [response]
    • ValidationMiddleware: dodaje prefiks walidacji i zwraca [VALID] [response]
    • UppercaseMiddleware: zamienia całą odpowiedź na wielkie litery

    Każde middleware powinno wywołać opakowany handler i wzbogacić jego wynik.

  • main.go: Zbuduj potok przetwarzania, komponując middleware.

    Odczytaj liczbę warstw middleware do zastosowania. Następnie dla każdej warstwy odczytaj typ middleware (timing, validation lub uppercase) i opakuj bieżący handler tym middleware. Zastosuj je w kolejności odczytu: pierwsze odczytane middleware staje się najbardziej zewnętrzną warstwą opakowującą.

    Po zbudowaniu potoku odczytaj ciąg znaków żądania, przetwórz go za pomocą utworzonego handlera i wypisz wynik.

Dane wejściowe będą miały następującą postać:

  • Linia 1: Liczba warstw middleware
  • Kolejne linie: Po jednym typie middleware w każdej linii (timing, validation lub uppercase)
  • Ostatnia linia: Żądanie do przetworzenia

Na przykład dla danych wejściowych:

2
timing
validation
GET /users

Wynik powinien być następujący:

[TIMING] [VALID] Processed: GET /users

A dla danych wejściowych:

3
uppercase
timing
validation
POST /data

Wynik powinien być następujący:

[TIMING] [VALID] PROCESSED: POST /DATA

A dla danych wejściowych:

1
uppercase
hello world

Wynik powinien być następujący:

PROCESSED: HELLO WORLD

A dla danych wejściowych:

0
simple request

Wynik powinien być następujący:

Processed: simple request

Zwróć uwagę, jak kolejność opakowywania przez middleware wpływa na wynik: pierwsze zastosowane middleware (najbardziej zewnętrzne) uruchamia się jako pierwsze i otrzymuje końcowy wynik wszystkich wewnętrznych middleware. W drugim przykładzie middleware zamieniające tekst na wielkie litery uruchamia się jako ostatnie (najbardziej wewnętrzne), więc zamienia odpowiedź podstawowego handlera na wielkie litery, zanim middleware pomiaru czasu i walidacji dodadzą swoje prefiksy!

Spróbuj swoich sił

package main

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

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

	// Odczytaj liczbę warstw middleware
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

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

	// Odczytaj żądanie
	request, _ := reader.ReadString('\n')
	request = strings.TrimSpace(request)

	// TODO: Zacznij od BaseHandler

	// TODO: Zbuduj potok, opakowując handler kolejnymi warstwami middleware
	// Dodawaj middleware w odpowiedniej kolejności — pierwsza odczytana warstwa stanie się najbardziej zewnętrznym opakowaniem
	// Wskazówka: Aby to osiągnąć, przejdź przez middlewareTypes w ODWROTNEJ kolejności
	// Ostatnia dodana warstwa middleware będzie najbardziej zewnętrzna (uruchomi się jako pierwsza)

	// TODO: Przetwórz żądanie za pomocą złożonego handlera i wypisz wynik
	fmt.Println("TODO: implement pipeline")
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online