Middleware chains: criando pipelines de processamento de requests
1. Conceitos Fundamentais de Middleware Chains
Middleware é um componente de software que intercepta e processa requests antes que eles atinjam o manipulador final de uma aplicação. Baseado no padrão de projeto Chain of Responsibility (Cadeia de Responsabilidade), cada middleware executa uma função específica e decide se passa o controle adiante ou interrompe o fluxo.
A diferença fundamental entre middleware síncrono e assíncrono está no impacto sobre a performance. Middleware síncrono bloqueia a execução até que sua tarefa seja concluída, enquanto o assíncrono permite que outras operações ocorram em paralelo, essencial para operações de I/O como acesso a banco de dados ou chamadas a APIs externas.
O conceito de "próximo" (next) é o mecanismo que conecta os elos da cadeia. Cada middleware recebe uma referência para o próximo middleware na pipeline, podendo:
- Chamá-lo para continuar o processamento
- Ignorá-lo para interromper a cadeia
- Capturar exceções e decidir como propagá-las
// Exemplo conceitual de encadeamento
function middleware1(request, next) {
console.log("Middleware 1: iniciando")
const resultado = next(request) // chama o próximo
console.log("Middleware 1: finalizando")
return resultado
}
function middleware2(request, next) {
console.log("Middleware 2: processando")
return "resposta final"
}
2. Estrutura Típica de uma Pipeline de Middleware
Uma pipeline de middleware é composta por três elementos essenciais: o contexto (que carrega estado compartilhado), o objeto request (dados da requisição) e o objeto response (dados da resposta). As funções middleware são executadas em ordem sequencial, mas é possível implementar execução paralela ou condicional.
// Pipeline de validação, autenticação e logging
function loggingMiddleware(request, next) {
console.log(`[${new Date().toISOString()}] ${request.method} ${request.path}`)
return next(request)
}
function validationMiddleware(request, next) {
if (!request.body || !request.body.email) {
return { status: 400, body: "Email é obrigatório" }
}
return next(request)
}
function authMiddleware(request, next) {
const token = request.headers.authorization
if (!token || token !== "token-valido") {
return { status: 401, body: "Não autorizado" }
}
request.user = { id: 123, name: "Usuário" }
return next(request)
}
// Montagem da pipeline
const pipeline = [loggingMiddleware, validationMiddleware, authMiddleware]
3. Implementando Middleware com Estado e Contexto Compartilhado
O uso de contexto (context.Context em Go ou objetos similares em outras linguagens) permite propagar dados entre middlewares sem poluir as assinaturas das funções. É crucial gerenciar o estado mutável com cuidado, especialmente em ambientes concorrentes.
// Middleware de tracing com contexto compartilhado
function tracingMiddleware(request, next) {
const traceId = request.headers["x-trace-id"] || crypto.randomUUID()
request.context.traceId = traceId
request.context.startTime = Date.now()
const response = next(request)
console.log(`[TRACE ${traceId}] Duração: ${Date.now() - request.context.startTime}ms`)
return response
}
// Middleware de injeção de dependências
function dependencyInjectionMiddleware(request, next) {
request.context.db = new DatabaseConnection()
request.context.cache = new RedisClient()
request.context.logger = new Logger(request.context.traceId)
return next(request)
}
4. Padrões de Design para Middleware Reutilizáveis
Middlewares paramétricos são fábricas que retornam funções middleware configuráveis. Isso permite criar middlewares genéricos que se adaptam a diferentes contextos sem duplicação de código.
// Fábrica de middleware de rate limiting
function createRateLimiter(maxRequests, windowMs) {
const requests = new Map()
return function rateLimiterMiddleware(request, next) {
const ip = request.ip
const now = Date.now()
if (!requests.has(ip)) {
requests.set(ip, [])
}
const timestamps = requests.get(ip).filter(t => now - t < windowMs)
if (timestamps.length >= maxRequests) {
return { status: 429, body: "Muitas requisições" }
}
timestamps.push(now)
requests.set(ip, timestamps)
return next(request)
}
}
// Uso: const rateLimiter = createRateLimiter(100, 60000)
// Composição de middlewares
function compose(...middlewares) {
return function composedMiddleware(request) {
let index = 0
function next(req) {
const middleware = middlewares[index++]
if (!middleware) return { status: 404, body: "Not Found" }
return middleware(req, next)
}
return next(request)
}
}
5. Tratamento de Erros e Interrupção da Cadeia
Estratégias robustas de tratamento de erros são essenciais para pipelines resilientes. A interrupção precoce permite abortar a cadeia quando uma condição crítica não é satisfeita.
// Middleware de autorização que interrompe a cadeia
function authorizationMiddleware(request, next) {
if (!request.user) {
return { status: 403, body: "Acesso negado" }
}
if (!request.user.roles.includes("admin")) {
return { status: 403, body: "Permissão insuficiente" }
}
return next(request) // continua apenas se autorizado
}
// Middleware de captura de erros
function errorHandlerMiddleware(request, next) {
try {
return next(request)
} catch (error) {
console.error(`Erro no pipeline: ${error.message}`)
return { status: 500, body: "Erro interno do servidor" }
}
}
6. Monitoramento e Observabilidade em Pipelines
Inserir métricas e logs em cada middleware permite rastrear gargalos de performance e diagnosticar problemas. O rastreamento distribuído (tracing) através da cadeia é fundamental para sistemas complexos.
// Middleware de logging estruturado
function structuredLoggingMiddleware(request, next) {
const logEntry = {
timestamp: new Date().toISOString(),
method: request.method,
path: request.path,
query: request.query,
userId: request.user?.id
}
console.log(JSON.stringify(logEntry))
return next(request)
}
// Middleware de medição de latência
function latencyMeasurementMiddleware(request, next) {
const start = process.hrtime.bigint()
const response = next(request)
const end = process.hrtime.bigint()
const duration = Number(end - start) / 1e6 // converte para milissegundos
metrics.recordLatency(request.path, duration)
if (duration > 1000) {
console.warn(`Alerta: ${request.path} demorou ${duration}ms`)
}
return response
}
7. Boas Práticas e Armadilhas Comuns
O equilíbrio entre reuso e coesão é crucial. Middlewares muito genéricos tendem a acumular responsabilidades excessivas, enquanto muito específicos dificultam a manutenção. Efeitos colaterais e dependências ocultas entre middlewares podem causar bugs difíceis de rastrear.
// Boa prática: middleware coeso e testável
function sanitizeInputMiddleware(request, next) {
const sanitized = {}
for (const [key, value] of Object.entries(request.body)) {
sanitized[key] = typeof value === 'string' ? value.trim() : value
}
request.body = sanitized
return next(request)
}
// Teste isolado
function testSanitizeInput() {
const request = { body: { name: " João ", email: " joao@email.com " } }
const next = (req) => req
const result = sanitizeInputMiddleware(request, next)
console.assert(result.body.name === "João", "Nome deve ser trimado")
console.assert(result.body.email === "joao@email.com", "Email deve ser trimado")
}
Armadilhas comuns:
- Modificar o objeto request em um middleware e assumir o estado em outro
- Não tratar erros em middlewares assíncronos
- Criar middlewares que dependem da ordem específica de execução
- Ignorar a limpeza de recursos em middlewares que abrem conexões
Referências
- Express.js Middleware Guide — Documentação oficial sobre middlewares no framework Express.js, com exemplos práticos de criação e uso
- Go Middleware Pattern — Tutorial detalhado sobre implementação de middlewares em Go, incluindo pipelines e contexto compartilhado
- ASP.NET Core Middleware — Documentação oficial da Microsoft sobre pipelines de middleware no ASP.NET Core
- Python WSGI Middleware — Guia completo sobre middlewares no padrão WSGI para aplicações Python
- Chain of Responsibility Pattern — Explicação do padrão de design Chain of Responsibility, base conceitual para middlewares
- OpenTelemetry Middleware Tracing — Documentação sobre implementação de tracing distribuído em middlewares usando OpenTelemetry
- Rate Limiting Patterns — Estratégias e padrões para implementar rate limiting em middlewares