Middleware como Decorator
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy. Lição 97 de 107.
Middleware é uma das aplicações mais práticas do padrão Decorator em Go. No desenvolvimento web, as funções de middleware envolvem manipuladores HTTP para adicionar preocupações transversais, como registro de logs, autenticação ou medição de tempo, sem modificar o manipulador original.
A principal ideia é que o middleware recebe um handler e retorna um novo handler com comportamento adicional. Isso segue exatamente o padrão Decorator: a mesma interface na entrada e na saída, com funcionalidade aprimorada:
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)
}
}Cada middleware envolve o próximo manipulador na cadeia, adicionando seu comportamento antes ou depois de chamar o manipulador envolvido. Você pode empilhar vários middlewares compondo-os:
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Resposta para: GET /usersA ordem de encapsulamento determina a ordem de execução: o middleware mais externo é executado primeiro. Esse padrão é amplamente utilizado em frameworks web Go para pipelines de processamento de requisições, facilitando a adição ou remoção de funcionalidades sem alterar a lógica central do handler.
Desafio
FácilVamos criar um pipeline de processamento de requisições usando o padrão Middleware como Decorator! Você criará um sistema em que funções de middleware envolvem handlers para adicionar funcionalidades como temporização, validação e formatação — tudo isso sem modificar a lógica principal do handler.
Você organizará seu código em três arquivos:
handler.go: defina seu tipo de handler e o handler base que processa as requisições.Crie um tipo
Handlercomo uma função que recebe uma string de requisição e retorna uma string de resposta:type Handler func(request string) stringImplemente uma função
BaseHandlerque retorneProcessed: [request]para qualquer requisição recebida.middleware.go: crie suas funções de middleware que envolvem handlers com comportamentos adicionais.Crie três funções de middleware, cada uma recebendo um
Handlere retornando um novoHandler:TimingMiddleware: envolve a resposta com informações de temporização, retornando[TIMING] [response]ValidationMiddleware: adiciona um prefixo de validação, retornando[VALID] [response]UppercaseMiddleware: converte toda a resposta para letras maiúsculas
Cada middleware deve chamar o handler envolvido e aprimorar seu resultado.
main.go: crie um pipeline de processamento compondo os middlewares.Leia o número de camadas de middleware a serem aplicadas. Em seguida, para cada camada, leia o tipo de middleware (
timing,validationouuppercase) e envolva seu handler atual com esse middleware. Aplique-os na ordem em que forem lidos: o primeiro middleware lido se torna o wrapper mais externo.Depois de criar o pipeline, leia a string de requisição, processe-a por meio do handler composto e imprima o resultado.
As seguintes entradas serão fornecidas:
- Linha 1: número de camadas de middleware
- Linhas seguintes: um tipo de middleware por linha (
timing,validationouuppercase) - Última linha: a requisição a ser processada
Por exemplo, considerando:
2
timing
validation
GET /usersSua saída deverá ser:
[TIMING] [VALID] Processed: GET /usersE considerando:
3
uppercase
timing
validation
POST /dataSua saída deverá ser:
[TIMING] [VALID] PROCESSED: POST /DATAE considerando:
1
uppercase
hello worldSua saída deverá ser:
PROCESSED: HELLO WORLDE considerando:
0
simple requestSua saída deverá ser:
Processed: simple requestObserve como a ordem de envolvimento dos middlewares afeta a saída: o primeiro middleware aplicado (o mais externo) é executado primeiro e vê o resultado final de todos os middlewares internos. O middleware de letras maiúsculas no segundo exemplo é executado por último (o mais interno), portanto converte a resposta base para letras maiúsculas antes que a temporização e a validação adicionem seus prefixos!
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leia o número de camadas de middleware
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Leia os tipos de middleware
middlewareTypes := make([]string, n)
for i := 0; i < n; i++ {
line, _ := reader.ReadString('\n')
middlewareTypes[i] = strings.TrimSpace(line)
}
// Leia a requisição
request, _ := reader.ReadString('\n')
request = strings.TrimSpace(request)
// TODO: Comece com o BaseHandler
// TODO: Construa o pipeline envolvendo o handler com middleware
// Aplique os middlewares em ordem - o primeiro middleware lido se torna o wrapper mais externo
// Dica: Percorra middlewareTypes em ordem REVERSA para conseguir isso
// O último middleware aplicado será o mais externo (executa primeiro)
// TODO: Processe a requisição através do handler composto e imprima o resultado
fmt.Println("TODO: implement pipeline")
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos11Biblioteca Padrão e POO
io.Reader e io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRevisão - Modelos de API REST14Padrões de Projeto — Parte 2
Padrão de ComandoPadrão AdapterPadrão DecoratorPadrão de Método TemplatePadrão de EstadoPadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online