Middleware come Decorator
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 97 di 107.
Middleware è una delle applicazioni più pratiche del pattern Decorator in Go. Nello sviluppo web, le funzioni middleware avvolgono gli handler HTTP per aggiungere funzionalità trasversali come la registrazione dei log, l'autenticazione o la misurazione dei tempi, senza modificare l'handler originale.
L'intuizione chiave è che il middleware prende un handler e restituisce un nuovo handler con un comportamento aggiunto. Questo segue esattamente il pattern Decorator: la stessa interfaccia in ingresso e in uscita, con funzionalità potenziate:
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)
}
}Ogni middleware racchiude il gestore successivo nella catena, aggiungendo il proprio comportamento prima o dopo aver chiamato il gestore racchiuso. Puoi impilare più middleware componendoli:
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Response to: GET /usersL'ordine dei wrapper determina l'ordine di esecuzione: il middleware più esterno viene eseguito per primo. Questo schema è ampiamente utilizzato nei framework web Go per le pipeline di elaborazione delle richieste, rendendo facile aggiungere o rimuovere funzionalità senza modificare la logica principale dell'handler.
Sfida
FacileCreiamo una pipeline di elaborazione delle richieste usando il pattern Middleware come Decorator! Creerai un sistema in cui le funzioni middleware avvolgono gli handler per aggiungere funzionalità come il rilevamento dei tempi, la convalida e la formattazione, tutto senza modificare la logica dell'handler principale.
Organizzerai il codice in tre file:
handler.go: definisci il tipo di handler e l'handler di base che elabora le richieste.Crea un tipo
Handlercome funzione che accetta una stringa di richiesta e restituisce una stringa di risposta:type Handler func(request string) stringImplementa una funzione
BaseHandlerche restituisceProcessed: [request]per qualsiasi richiesta ricevuta.middleware.go: crea le funzioni middleware che avvolgono gli handler con comportamenti aggiuntivi.Crea tre funzioni middleware, ognuna delle quali accetta un
Handlere restituisce un nuovoHandler:TimingMiddleware: avvolge la risposta con informazioni sui tempi e restituisce[TIMING] [response]ValidationMiddleware: aggiunge un prefisso di convalida e restituisce[VALID] [response]UppercaseMiddleware: converte l'intera risposta in maiuscolo
Ogni middleware deve chiamare l'handler avvolto e arricchirne il risultato.
main.go: crea una pipeline di elaborazione componendo i middleware.Leggi il numero di livelli middleware da applicare. Poi, per ogni livello, leggi il tipo di middleware (
timing,validationouppercase) e avvolgi l'handler corrente con quel middleware. Applicali nell'ordine in cui vengono letti: il primo middleware letto diventa l'involucro più esterno.Dopo aver creato la pipeline, leggi la stringa della richiesta, elaborala tramite l'handler composto e stampa il risultato.
Verranno forniti i seguenti input:
- Riga 1: numero di livelli middleware
- Righe successive: un tipo di middleware per riga (
timing,validationouppercase) - Ultima riga: la richiesta da elaborare
Ad esempio, dato:
2
timing
validation
GET /usersL'output dovrebbe essere:
[TIMING] [VALID] Processed: GET /usersE dato:
3
uppercase
timing
validation
POST /dataL'output dovrebbe essere:
[TIMING] [VALID] PROCESSED: POST /DATAE dato:
1
uppercase
hello worldL'output dovrebbe essere:
PROCESSED: HELLO WORLDE dato:
0
simple requestL'output dovrebbe essere:
Processed: simple requestNota come l'ordine in cui vengono avvolti i middleware influenzi l'output: il primo middleware applicato (quello più esterno) viene eseguito per primo e riceve il risultato finale di tutti i middleware interni. Nel secondo esempio, il middleware maiuscolo viene eseguito per ultimo (è quello più interno), quindi converte in maiuscolo la risposta di base prima che timing e validation aggiungano i loro prefissi!
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il numero di livelli di middleware
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Leggi i tipi di middleware
middlewareTypes := make([]string, n)
for i := 0; i < n; i++ {
line, _ := reader.ReadString('\n')
middlewareTypes[i] = strings.TrimSpace(line)
}
// Leggi la richiesta
request, _ := reader.ReadString('\n')
request = strings.TrimSpace(request)
// TODO: Inizia con BaseHandler
// TODO: Costruisci la pipeline avvolgendo l'handler con i middleware
// Applica i middleware nell'ordine indicato: il primo letto diventa il livello più esterno
// Suggerimento: scorri middlewareTypes in ordine INVERSO per ottenere questo risultato
// L'ultimo middleware applicato sarà il più esterno (verrà eseguito per primo)
// TODO: Elabora la richiesta tramite l'handler composto e stampa il risultato
fmt.Println("TODO: implement pipeline")
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online