Middleware como Decorator
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 97 de 107.
Middleware es una de las aplicaciones más prácticas del patrón Decorator en Go. En el desarrollo web, las funciones de middleware envuelven a los manejadores HTTP para añadir aspectos transversales como el registro (logging), la autenticación o el cronometraje (timing) sin modificar el manejador original.
La idea clave es que el middleware toma un manejador y devuelve un nuevo manejador con comportamiento añadido. Esto sigue exactamente el patrón Decorator: la misma interfaz de entrada y salida, con funcionalidad mejorada:
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 envuelve al siguiente manejador en la cadena, añadiendo su comportamiento antes o después de llamar al manejador envuelto. Puedes apilar múltiples middleware componiéndolos:
func MainHandler(request string) string {
return "Response to: " + request
}
handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Response to: GET /usersEl orden de envoltura determina el orden de ejecución: el middleware más externo se ejecuta primero. Este patrón se utiliza ampliamente en los frameworks web de Go para los pipelines de procesamiento de solicitudes, lo que facilita agregar o eliminar funcionalidades sin tocar la lógica central del manejador.
Desafío
Fácil¡Vamos a construir un pipeline de procesamiento de solicitudes utilizando el patrón Middleware como Decorador! Crearás un sistema donde las funciones de middleware envuelven a los manejadores para añadir funcionalidades como temporización, validación y formato, todo sin modificar la lógica del manejador principal.
Organizarás tu código en tres archivos:
handler.go: Define tu tipo de manejador y el manejador base que procesa las solicitudes.Crea un tipo
Handlercomo una función que recibe un string de solicitud y devuelve un string de respuesta:type Handler func(request string) stringImplementa una función
BaseHandlerque devuelvaProcessed: [request]para cualquier solicitud que reciba.middleware.go: Crea tus funciones de middleware que envuelven a los manejadores con comportamiento adicional.Construye tres funciones de middleware, cada una de las cuales recibe un
Handlery devuelve un nuevoHandler:TimingMiddleware— envuelve la respuesta con información de tiempo, devolviendo[TIMING] [response]ValidationMiddleware— añade un prefijo de validación, devolviendo[VALID] [response]UppercaseMiddleware— convierte toda la respuesta a mayúsculas
Cada middleware debe llamar al manejador envuelto y mejorar su resultado.
main.go: Construye un pipeline de procesamiento componiendo los middleware.Lee el número de capas de middleware a aplicar. Luego, para cada capa, lee el tipo de middleware (
timing,validation, ouppercase) y envuelve tu manejador actual con ese middleware. Aplícalos en el orden en que se leen: el primer middleware leído se convierte en el envoltorio más externo.Después de construir el pipeline, lee el string de la solicitud, procésalo a través de tu manejador compuesto e imprime el resultado.
Se proporcionarán las siguientes entradas:
- Línea 1: Número de capas de middleware
- Líneas siguientes: Un tipo de middleware por línea (
timing,validation, ouppercase) - Última línea: La solicitud a procesar
Por ejemplo, dado:
2
timing
validation
GET /usersTu salida debería ser:
[TIMING] [VALID] Processed: GET /usersY dado:
3
uppercase
timing
validation
POST /dataTu salida debería ser:
[TIMING] [VALID] PROCESSED: POST /DATAY dado:
1
uppercase
hello worldTu salida debería ser:
PROCESSED: HELLO WORLDY dado:
0
simple requestTu salida debería ser:
Processed: simple requestObserva cómo el orden de envoltura del middleware afecta a la salida: el primer middleware aplicado (el más externo) se ejecuta primero y ve el resultado final de todos los middleware internos. El middleware de mayúsculas en el segundo ejemplo se ejecuta al último (el más interno), por lo que convierte a mayúsculas la respuesta base antes de que la temporización y la validación añadan sus prefijos.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el número de capas de middleware
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Leer los tipos de middleware
middlewareTypes := make([]string, n)
for i := 0; i < n; i++ {
line, _ := reader.ReadString('\n')
middlewareTypes[i] = strings.TrimSpace(line)
}
// Leer la solicitud
request, _ := reader.ReadString('\n')
request = strings.TrimSpace(request)
// TODO: Comenzar con el BaseHandler
// TODO: Construir el pipeline envolviendo el handler con middleware
// Aplicar el middleware en orden - el primer middleware leído se convierte en el envoltorio más externo
// Hint: Recorrer middlewareTypes en orden INVERSO para lograr esto
// El último middleware aplicado será el más externo (se ejecuta primero)
// TODO: Procesar la solicitud a través del handler compuesto e imprimir el resultado
fmt.Println("TODO: implement pipeline")
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos11Biblioteca estándar y POO
io.Reader e io.WriterInterfaz sort.InterfaceInterfaz fmt.Stringerencoding/json con StructsInterfaz http.HandlerRepaso - Modelos de REST API14Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeMiddleware como Decorator3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool