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 handlers HTTP para adicionar preocupações transversais, como logging, autenticação ou temporização, sem modificar o handler original.
A principal percepção é 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 handler na cadeia, adicionando seu comportamento antes ou depois de chamar o handler envolvido. Você pode empilhar múltiplos middlewares compondo-os:
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Response to: GET /usersA ordem de encapsulamento determina a ordem de execução — o middleware mais externo executa primeiro. Este padrão é usado extensivamente em frameworks web Go para pipelines de processamento de requisições, tornando fácil adicionar ou remover funcionalidades sem tocar na lógica central do handler.
Desafio
FácilVamos construir um pipeline de processamento de requisições usando o padrão Middleware como Decorator! Você criará um sistema onde funções de middleware envolvem handlers para adicionar funcionalidades como temporização, validação e formatação — tudo sem modificar a lógica central 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 retornaProcessed: [request]para qualquer requisição que receber.middleware.go: Crie suas funções de middleware que envolvem os handlers com comportamentos adicionais.Construa três funções de middleware, cada uma recebendo um
Handlere retornando um novoHandler:TimingMiddleware— envolve a resposta com informações de tempo, retornando[TIMING] [response]ValidationMiddleware— adiciona um prefixo de validação, retornando[VALID] [response]UppercaseMiddleware— converte toda a resposta para maiúsculas
Cada middleware deve chamar o handler envolvido e aprimorar seu resultado.
main.go: Construa um pipeline de processamento compondo os middlewares.Leia o número de camadas de middleware a serem aplicadas. Então, 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 torna-se o invólucro mais externo.Após construir o pipeline, leia a string da requisição, processe-a através do seu 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, dado:
2
timing
validation
GET /usersSua saída deve ser:
[TIMING] [VALID] Processed: GET /usersE dado:
3
uppercase
timing
validation
POST /dataSua saída deve ser:
[TIMING] [VALID] PROCESSED: POST /DATAE dado:
1
uppercase
hello worldSua saída deve ser:
PROCESSED: HELLO WORLDE dado:
0
simple requestSua saída deve ser:
Processed: simple requestObserve como a ordem de envolvimento do middleware afeta a saída — o primeiro middleware aplicado (o mais externo) executa primeiro e vê o resultado final de todos os middlewares internos. O middleware uppercase no segundo exemplo executa por último (o mais interno), então ele converte a resposta base para maiúsculas antes que o timing e a validação adicionem seus prefixos!
Folha de consulta
Middleware em Go segue o padrão Decorator ao envolver handlers para adicionar funcionalidade sem modificar o handler original.
Defina um tipo de handler como uma função:
type Handler func(request string) stringFunções de middleware recebem um handler e retornam um novo handler com comportamento aprimorado:
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)
}
}Empilhe múltiplos middlewares compondo-os:
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// Output: [LOG] [AUTH] Response to: GET /usersO middleware mais externo executa primeiro, determinando a ordem de execução. Cada middleware pode adicionar comportamento antes ou depois de chamar o handler envolvido.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lê o número de camadas de middleware
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Lê os tipos de middleware
middlewareTypes := make([]string, n)
for i := 0; i < n; i++ {
line, _ := reader.ReadString('\n')
middlewareTypes[i] = strings.TrimSpace(line)
}
// Lê 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
// Aplica o middleware em ordem - o primeiro middleware lido torna-se o invólucro 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 ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser11Biblioteca Padrão & POO
io.Reader & io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRecapitulação - Modelos de API REST14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool