Skip to content
SP StackPractices
intermediate Por Mathias Paulenko

Decorator Pattern para Pipelines de Peticiones HTTP

Usa el Decorator pattern para componer preocupaciones transversales como logging, metricas y reintentos en pipelines de peticiones HTTP sin modificar logica central

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Decorator Pattern para Pipelines de Peticiones HTTP

El Decorator pattern envuelve un objeto para agregar responsabilidades dinamicamente. Cuando se aplica a clientes HTTP, se convierte en una forma limpia de componer preocupaciones transversales — logging, reintentos, metricas, autenticacion — sin contaminar la logica central de la peticion.

Cuando Usar Esto

  • Multiples preocupaciones transversales deben envolver cada llamada a API
  • Quieres agregar o remover preocupaciones sin cambiar codigo existente
  • La logica central de la peticion debe mantenerse testeable y enfocada

Problema

Agregar logging, reintentos, metricas y autenticacion a cada llamada HTTP lleva a clases de cliente monoliticas o boilerplate copiado en cada punto de llamada.

Solucion

// api/HttpClient.ts
interface HttpClient {
  request(url: string, options: RequestInit): Promise<Response>;
}

// api/FetchClient.ts
class FetchClient implements HttpClient {
  async request(url: string, options: RequestInit): Promise<Response> {
    return fetch(url, options);
  }
}

// decorators/BaseClientDecorator.ts
abstract class BaseClientDecorator implements HttpClient {
  constructor(protected client: HttpClient) {}
  abstract request(url: string, options: RequestInit): Promise<Response>;
}

// decorators/LoggingDecorator.ts
class LoggingDecorator extends BaseClientDecorator {
  async request(url: string, options: RequestInit): Promise<Response> {
    const start = performance.now();
    try {
      const response = await this.client.request(url, options);
      console.log(`${options.method || 'GET'} ${url} → ${response.status} (${(performance.now() - start).toFixed(0)}ms)`);
      return response;
    } catch (error) {
      console.error(`${options.method || 'GET'} ${url} → ERROR`);
      throw error;
    }
  }
}

// decorators/RetryDecorator.ts
class RetryDecorator extends BaseClientDecorator {
  constructor(client: HttpClient, private maxRetries: number = 3) {
    super(client);
  }

  async request(url: string, options: RequestInit): Promise<Response> {
    let lastError: Error;
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        return await this.client.request(url, options);
      } catch (error) {
        lastError = error as Error;
        if (attempt < this.maxRetries) {
          await new Promise(r => setTimeout(r, 1000 * attempt));
        }
      }
    }
    throw lastError!;
  }
}

// decorators/AuthDecorator.ts
class AuthDecorator extends BaseClientDecorator {
  constructor(client: HttpClient, private token: string) {
    super(client);
  }

  async request(url: string, options: RequestInit): Promise<Response> {
    const headers = new Headers(options.headers);
    headers.set('Authorization', `Bearer ${this.token}`);
    return this.client.request(url, { ...options, headers });
  }
}

Uso

const client = new AuthDecorator(
  new RetryDecorator(
    new LoggingDecorator(new FetchClient()),
    3
  ),
  process.env.API_TOKEN!
);

Variaciones

  • Conditional Decorator: Aplica logica solo para URLs o metodos HTTP especificos
  • Metrics Decorator: Envuelve tiempos y distribuciones de codigos de estado a Prometheus
  • Cache Decorator: Combina con Proxy pattern para cachear respuestas GET

Mejores Practicas

  • Manten los decorators enfocados en una sola responsabilidad cada uno
  • Asegura que los delegates deleguen a client.request() sin tragar errores
  • Haz los decorators stateless cuando sea posible para evitar efectos secundarios

Errores Comunes

  • Mutar el objeto de peticion en lugar de crear uno nuevo
  • Olvidar reenviar la respuesta o el error al siguiente decorator
  • Agregar demasiados decorators, haciendo el stack de llamadas dificil de rastrear

FAQ

P: Como se diferencia del middleware en Express? R: El middleware de Express opera en objetos request/response en secuencia. Los decorators envuelven una sola interfaz de cliente y pueden componerse a cualquier granularidad.

P: Los decorators pueden removerse dinamicamente? R: Solo si reasignas la referencia del cliente. Los decorators se componen tipicamente al inicializar y permanecen fijos.