"La recompensa de los grandes hombres, es que mucho tiempo después de su muerte, no se tiene la entera seguridad de que hayan muerto"

Cuando se trabaja con frameworks como express, se tiende a utilizar mucho el concepto de middleware, este es básicamente un patron que busca intervenir en diferentes momentos del ciclo de ejecución de otra función, esto viene del patrón composition, del cual hable hace poco en python y que ahora trabajaremos en typescript, estos middlewares potencializan el desarrollo en express, ya que permiten acceder al request y al response para validar parámetros, datos, y captura de errores, cuando se trabaja en lambdas en AWS muchas veces se puede extrañar un poco este comportamiento el cual se puede apreciar en siguiente Figura, por esto, en el post de hoy, lo dedicaremos a hablar de la herramienta middy

Figura 1: Interacción de diferentes componentes durante el ciclo de ejecución del handler

Middy

Esta herramienta implementa el concepto de composition, en donde se escribe una función conocida como middleware que va a ejecutar otra función, y en esa ejecución va a aplicar cierta lógica, que en el contexto de lambdas puede ser modificar el event request o el event response, middy implementa el concepto de onion middleware como se vio en la Figura 1, y se puede apreciar otro ejemplo en Figura 2 en laravel,  donde los middlewares se ejecutan por capas, y cada capa se encarga de gestionar una responsabilidad, después  la respuesta viaja desde el middleware mas interno hasta el middleware que se encuentra en la capa superior, veamos como sería un ejemplo

Figura 2: Middlewares en laravel

Ejemplo

vamos a escribir el código en typecript, para ello vamos a tener los siguientes configuraciones en los diferentes archivos


service: middleware-ts
variablesResolutionMode: 20210326
frameworkVersion: '3'
package:
  individually: true
provider:
  name: aws
  runtime: nodejs14.x

functions:
  hello:
    handler: handler.handler
    events:
    - sqs:
        arn:
          Fn::Join:
            - ':'
            - - arn
              - aws
              - sqs
              - Ref: AWS::Region
              - Ref: AWS::AccountId
              - MyQueue
        batchSize: 1

plugins:
  - serverless-plugin-typescript
serverless.yml

ahora escribimos el código del handler y el middleware y los explicaremos a  continuación

import { MiddlewareObj } from "@middy/core";
import createError from "http-errors";
import { SQSEvent, Context } from "aws-lambda";

export interface IRequest {
  event?: SQSEvent;
  context?: Context;
}

const defaults = {};

export const MyMiddleware = (opts = {}): MiddlewareObj => {
  const options = { ...defaults, ...opts };

  const MiddlewareBefore = async (request: IRequest) => {
    const { event } = request;

    if (
      event &&
      Object.keys(event).length !== 0 &&
      event?.Records &&
      event.Records?.length > 0
    ) {
      try {
        const body = JSON.parse(event.Records[0].body)
        request.event = {...body}
      } catch (e: any) {
        console.error(e);
        console.log(e)
        throw createError(400, e.toString());
      }
    } else {
      console.error("error");
      throw createError(400);
    }
  };

  return {
    before: MiddlewareBefore,
  };
};

middleware.ts
import { Context } from "aws-lambda";
import middy from "@middy/core";
import { MyMiddleware } from "./middleware";
import httpErrorHandler from "@middy/http-error-handler";

interface User {
  name: string;
  lastName: string;
}

const baseHandler = async function hello(event: User, context: Context) {
  console.log(event);
  return { statusCode: 200, body: "OK" };
};

export const handler = middy(baseHandler)
  .use(MyMiddleware())
  .use(httpErrorHandler());
handler.ts
{
  "name": "middleware-ts",
  "version": "1.0.0",
  "description": "",
  "main": "handler.ts",
  "scripts": {
    "lint:eslint": "eslint handler.ts middleware.ts",
    "lint:prettier": "prettier --write handler.ts middleware.ts",
    "lint": "npm run lint:prettier && npm run lint:eslint"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@middy/core": "^2.5.7",
    "@middy/http-error-handler": "^2.5.7",
    "http-errors": "^2.0.0"
  },
  "devDependencies": {
    "@types/aws-lambda": "^8.10.95",
    "@types/http-errors": "^1.8.2",
    "aws-sdk": "^2.1130.0",
    "eslint": "^8.15.0",
    "prettier": "^2.6.2",
    "serverless-plugin-typescript": "^2.1.2",
    "typescript": "^4.6.4"
  }
}
package.json

El contexto general es que el código anterior funciona como consumidor de una cola SQS, SQS tiene una estructura definida que envía a los consumidores, en donde contiene un batch de información, en este caso como se quiere procesar uno solo elemento al mismo tiempo, se realiza un middleware que obtiene el primer objeto que viene en formato string, se convierte a JSON, luego si todo salio bien, middy busca otro middleware, en este caos al no existir más, le pasa al control a la función handler con todas las transformaciones realizadas anteriormente, en al siguiente Figura se puede ver el inicio de la invocación de la lambda enviando un mensaje a una cola

Figura 3: Creación de un mensaje en SQS

La clave para entender el código está en la función llamada MiddlewareBefore dentro del módulo middlewares, estas funciones de tipo middleware se invocan primero (gracias al metodo Before) esto quiere decir que reciben el llamado directo de SQS, por eso la entrada de esa función es un IRequest que tiene un event de tipo SQSEvent, este tiene una lógica, en la cual válida valores nulos y vacíos y si toda esta ok, reasigna ese valor de request.event  con el contenido transformado que viene en la cola, y en ese modelo de onion de la función middleware devuelve el control al handler inicial con el request modificado, por lo tanto, en el módulo handler la funcion handler recibe un objeto de tipo User, este ejecuta una logica, y responde a la cola con un success o un error

Gestión de errores

En el código hay dos librerías interesantes que se están usando y giran alrededor de los middlewares, estas son:

  • http-errors usada para generar errores con los codigo definidos en RESTFULL, esta librería es muy usada por ejemplo en express
  • httpErrorHandler este es un middleware de errores, captura todos los errores, en este caso los de http-erros, y los transforma a una  estructura de errores de tipo AWS lambdas, porque recuerden las respuestas en las lambdas tienen un formato diferente

Estas dos librerías son muy importantes, sobre todo la librería httpErrorHandler, pues es un handler escrito en donde llegaran todos los errores que se generen en los diferentes middlewares (siempre y cuando algún middleware no implemente la función onError),  esto nos brinda una gestión muy útil para la respuesta automática de códigos de error, para visualizar mejor el proyecto todo el código de ejemplo lo pueden encontrar aquí

Conclusiones

Cuando se viene de express a trabajar con serverless generalmente uno de los features que más se extraña es poder utilizar middlewares, middy hace un gran trabajo para traer esa filosofía que expreses ha implementado, obteniendo excelente resultados, en mi opinión estos resultados se replican de buena forma en serverless gracias a middy, además que tiene ya middlewares escritos que conectan directo con servicios de AWS, los cuales la mayoría son solo de instalar y usar

¿Te gusto el post y quisieras poder aplicar lo aprendido?

Únete al equipo de Simetrik AQUI