Diseño de software

Swift: middleware con decoradores y cadena de responsabilidad

Al crear la capa de red, este momento llega inevitablemente.

3 min de lectura
Imagen de portada de Swift: middleware con decoradores y cadena de responsabilidad

Al crear la capa de red, este momento llega inevitablemente.

Para enviar una solicitud, hay que añadir el token de autenticación, registrar logs y reintentar si falla.

Si metes todo en una función, send() acaba teniendo 200 líneas.

La herramienta adecuada aquí es el patrón middleware.

La conclusión es esta: en Swift, el middleware resulta más limpio si envuelves cada función con el patrón Decorator y conectas lo envuelto en orden con Chain of Responsibility. No compiten; forman un equipo.

Hoy veremos por qué usar esta combinación y cómo ensamblarla con un ejemplo práctico.


¿Qué es el patrón middleware?

El middleware es una capa intermedia que interviene entre la solicitud y la respuesta.

Si has usado RequestInterceptor de Alamofire o Middleware de Vapor en el servidor, ya conoces el concepto.

La idea central es sencilla.

Mantén intacto el núcleo que procesa la solicitud y permite insertar o retirar funciones antes y después.

Crea autenticación, logging, caché y reintentos como piezas independientes.

Así puedes combinar libremente solo autenticación para una solicitud, o autenticación y caché para otra.


Decorador y cadena de responsabilidad: ¿en qué se diferencian?

Mucha gente confunde estos dos conceptos.

En resumen:

Distinción Decorador Cadena de responsabilidad
Propósito Añadir funcionalidad a un objeto existente Pasar por varios procesadores en orden
Relación Estructura envolvente (anidada) Estructura conectada (cadena)
¿Continúa? Siempre pasa al siguiente Puede detenerse en medio
Papel en el middleware Implementación de cada función Conectar y ordenar esas funciones

Al probarlos, descubrí que no se oponen: pertenecen a niveles distintos.

El Decorator responde a «cómo añadir funciones» y la cadena de responsabilidad a «en qué orden ejecutarlas».

Por eso, juntos compensan las debilidades del otro.


Combinación práctica: ensamblémoslos con código

Primero definimos la interfaz común del middleware: recibe una solicitud y la pasa al siguiente middleware.

protocol Middleware {
    // requestrecibir la solicitud, procesarla y pasarla, next al siguiente paso
    func handle(_ request: Request,
                next: (Request) async throws -> Response)
    async throws -> Response
}

Ahora creamos cada función como un decorador. Este es el middleware de logging.

struct LoggingMiddleware: Middleware {
    func handle(_ request: Request,
                next: (Request) async throws -> Response)
    async throws -> Response {
        print("➡️ solicitud: \(request.url)")
        let response = try await next(request)  // al siguiente paso
        print("⬅️ respuesta: \(response.status)")
        return response
    }
}

La parte que llama a next(request) es el eslabón de la cadena de responsabilidad.

Hace su trabajo (registrar el log) y pasa el resto al siguiente middleware.

Por último, plegamos varios middlewares en una sola cadena.

func buildChain(_ middlewares: [Middleware],
                final: @escaping (Request) async throws -> Response)
-> (Request) async throws -> Response {
    middlewares.reversed().reduce(final) { next, mw in
        { req in try await mw.handle(req, next: next) }
    }
}

La clave es envolver desde atrás con reduce.

Si insertas el array en orden [인증, 로깅, 재시도], la solicitud lo recorre en ese orden y la respuesta sale en orden inverso. Piensa en las capas de una cebolla.

La solicitud entra y la respuesta sale en sentido inverso
La solicitud entra y la respuesta sale en sentido inverso
Antes de escribir código, dibujé la estructura de capas en papel
Antes de escribir código, dibujé la estructura de capas en papel

¿Qué ventajas ofrece este diseño?

Estas son las ventajas que observé al aplicarlo en un proyecto real.

  1. Añadir una función consiste en insertar una línea en el array. Si necesitas caché, basta con insertar CachingMiddleware() en el array.

  2. Es fácil cambiar el orden. Para ejecutar autenticación antes o después del logging, solo cambia el orden del array.

  3. Las pruebas son sencillas. Como cada middleware es independiente, puedes añadir pruebas unitarias por separado.

  4. El núcleo queda limpio. El send() de 200 líneas vuelve a tener menos de diez.

También hay algunos puntos que conviene vigilar.

Cuando hay demasiados middlewares, cuesta seguir por dónde pasa una solicitud.

Por eso, cuando la cadena supera cinco o seis elementos, pongo el middleware de logging al principio y registro la ruta recorrida.


Conclusión

Esta combinación de crear funciones con Decorator y ordenarlas con Chain of Responsibility sirve, una vez dominada, no solo para capas de red, sino también para procesamiento de eventos y validación.

Te recomiendo añadir el ejemplo de hoy a un pequeño proyecto personal. Al quitar las capas directamente, el concepto se asimila mucho más rápido. ¡Feliz refactorización!

Lecturas recomendadas