Profundizando en API Gateway de AWS

Hoy en día mucho se ha estudiado (y comentado) sobre lo interesante y óptimas que son las arquitecturas basadas en microservicios serverless.

De hecho, en este mismo blog, varios de mis compañeros han hablando de las distintas formas de trabajar con arquitecturas serverless, incluso de la integración de los servicios Lambda.

Sin embargo, poco se ha profundizado sobre este último, que nos provee de una serie de funcionalidades y mecanismos para desplegar gestionar APIs bastante interesantes.

En el siguiente post veremos cómo el servicio de API Gateway de AWS nos simplifica la vida a la hora de desplegar y gestionar APIs ahorrándonos tiempo, algo que siempre se agradece.

¿Qué es API Gateway?

Es un servicio de Amazon Web Services (AWS), que nos permite crear, implementar y administrar una interfaz de programación de aplicaciones (API) REST para exponer los endpoints HTTP del backend, funciones AWS Lambda u otros servicios de AWS.

Su principal característica es la de servir como frontal o proxy a través del cual podemos acceder a funcionalidades y/o servicios.

¿Por qué puede ser interesante?

Múltiples versiones y simplicidad en el despliegue

Permite ejecutar varias versiones de la misma API simultáneamente, lo que facilita la iteración, puesta a prueba y publicación de nuevas versiones con rapidez. Y esto cuando hablamos de desarrollos ágiles es un gran punto a favor. Por ejemplo, es posible definir diferentes fases de publicación (o bien llamados entornos integración, preproducción y producción).

Es cierto que los servicios hay que programarlos, pero el hecho de que podamos publicarlos de forma casi inmediata es otro punto a favor y de mucho valor, teniendo en cuenta el dinamismo que está presente hoy día en el desarrollo de aplicaciones.

Sin embargo, una vez tenemos listos nuestros microservicios, el despliegue de una nueva versión de la API se consigue prácticamente con un clic. Basta solo con indicar (o definir) sobre qué fase (entorno) deseamos hacer el despliegue y poco más. ¡A correr!

Performance

Dispone de un sistema de escalado automático con el fin de administrar y gestionar la cantidad de tráfico recibido (manejo de un alto número de peticiones concurrentes).  

Como un complemento al auto escalado, también provee un sistema de limitación controlada, que permite la gestión del tráfico de peticiones simultáneas. Es decir, si dicha característica está activada, todas las peticiones excedentes al umbral definido serían descartadas y así, en cierta manera, podríamos evitar que se desborde la infraestructura backend durante picos de tráfico impredecibles.

Adicionalmente es posible activar el sistema de caché, que ha sido pensado para mejorar el rendimiento evitando las llamadas innecesarias al backend. La duración de la misma se define en segundos y su tamaño en GBs.

Gestión de la seguridad

La seguridad puede gestionarse a través de un conjunto de herramientas que provee AWS, que permiten controlar la autenticación y el acceso a las diferentes operaciones de una API.

Entre las herramientas encontramos IAM (Identity Access Management) o Cognito de AWS, que permiten la identificación, definición de políticas de acceso e incluso la verificación de tokens, entre otros. Cabe destacar que la tecnología que utiliza dicho sistema de gestión de autenticación y autorización es la misma que utiliza AWS para sus propios servicios.

Otro punto importante con respecto a la seguridad, es que todos los recursos (servicios) son expuestos a través del protocolo HTTPS, es decir, API Gateway no admite comunicaciones no cifradas.

Monitorización

Provee un conjunto de métricas sobre la cantidad de llamadas, latencia, tasa de errores sobre las APIs, así como también logs para facilitar las tareas de debugging.

También es posible realizar extracción de analíticas, siendo este un punto fuerte que, personalmente, relaciono con la puesta a punto, ya que en función de los datos obtenidos es posible hacer los ajustes necesarios y, acorde a nuestras necesidades, podamos tener un mejor rendimiento.

Mocks

Por otra parte, API Gateway también ofrece la opción bastante útil para la fase de diseño y prototipado. Y es que según un modelo definido, es posible crear mocks, algo que me parece de gran utilidad para hacer pruebas de integración con el front-end.  

¿Con qué podemos usar API Gateway?

Dentro de sus capacidades tenemos que destacar que es fácilmente integrable con otros servicios de AWS, siempre que los mismos pertenezcan a la misma cuenta AWS. Por ejemplo: Lambda (acciones o funciones que se ejecutan a demanda y las cuales no requieren la gestión de un servidor de aplicaciones), DynamoDB (base de datos NoSQL en AWS, la cual expone todos sus servicios a través de endpoints HTTP), entre otros.

Incluso es posible hacer una integración mixta, es decir, que cada método de la petición (POST, GET, PUT, etc.) esté asociado a distintos tipos de servicios y tecnologías.

En el mundo de los microservicios serverless, uno de los ámbitos más interesantes que provee API Gateway es la sencilla integración con el servicio Lambda AWS, que permite ejecutar su propio código para responder a diferentes tipos de eventos, sin la necesidad de provisionar y/o administrar servidores (las funcionalidades pueden ser desarrolladas en diferentes lenguajes de programación).

En otras palabras, nuestro backend puede estar compuesto por diversos servicios y tecnologías. Incluso pueden estar alojados en diferentes localidades y con la ayuda del API Gateway podemos hacer que esto sea totalmente transparente para quienes van a hacer uso de nuestra API.

Veamos un ejemplo

Nos planteamos el siguiente escenario:

Supongamos que tenemos una API para gestionar un club de fútbol y donde hay definido un recurso llamado /players/.

A través de su método GET retorna un listado de todos los jugadores (enlazando directamente a una tabla en una DynamoDB).

Por otro lado, el método POST del mismo recurso invoca directamente a un endpoint definido dentro de algún contenedor de AWS que se encarga de la validación y después de un conjunto de operación, de persistir los datos del nuevo jugador.

Por último, la implementación del método DELETE la delegamos a una función Lambda.  

De esta manera estaríamos integrando diferentes servicios, diferentes tecnologías, e incluso hasta diferentes lenguajes de programación. Por supuesto todo dependiendo de nuestras necesidades, pero ciertamente nos ofrece flexibilidad.

En la figura podemos observar una arquitectura donde la API por cada método HTTP hace un tipo de integración diferente.

Nota: es importante aclarar que el acceso a los distintos sistemas siempre se realiza por medio de endpoint y verbo.

API Gateway, integración en acción

A continuación, os voy a mostrar lo sencillo que sería hacer la integración de API Gateway con DynamoDB. En otras palabras, crearemos una capa de acceso a recursos de datos a través de una API.

Para comenzar es necesario considerar un par de premisas:

  1. Debemos disponer de una cuenta en AWS.
  2. Asumimos que tenemos una base de datos en DynamoDB con una tabla llamada player.

¡Comencemos!

Paso 1: API

Lo primero será crear una nueva API. Esto lo conseguimos directamente sobre la consola de Amazon API Gateway clicando sobre el botón Create API, asignamos un nombre y lo tenemos.

Paso 2: Recursos y métodos

Ya que se basa en RESTful, para hacer la publicación de algún servicio, tendremos que definir el recurso y los métodos por los que atenderá a las peticiones.

En el menú Actions encontramos todas las operaciones que podemos realizar sobre una API.

Primero, creamos el recurso que queremos exponer.

En este caso, crearemos un recurso llamado players/{id}.

Segundo, creamos el método HTTP al que vamos a asociar la operación.

Paso 3: Enlace con el servicio

En el siguiente paso debemos definir el tipo de integración que deseamos realizar. En este caso concreto, sería enlazar el recurso a la tabla en base de datos (recordemos que la misma debe ser previamente creada en DynamoDB).

Tal como mencionamos previamente, en esta pantalla pueden observarse todas las opciones que ofrece API Gateway para ser integrado.

Para continuar con el ejemplo, en este caso debemos rellenar toda la plantilla con los datos de localización y nombre del servicio con el que deseamos integrar, así como también la acción que será invocada.

En este punto hay que hacer énfasis en lo siguiente:

  1. Es importante resaltar que en la selección de HTTP method no se trata del mismo método que estamos exponiendo en la API que hemos creado previamente. Este se refiere al método que debemos utilizar para conectar con un servicio el particular al que vamos a conectar. Para el servicio DynamoDB, todas las peticiones deben ser realizadas a través del método POST, aún cuando lo que vayamos a realizar se una consulta de lectura.
  2. Action, se refiere a la acción o función que queremos invocar dentro del servicio. En este caso vamos a realizar una consulta sobre la tabla player filtrando por el id. La API DynamoDB ofrece varias funcionalidades, tanto de consulta como de modificación (GetItem, UpdateItem, DeleteItem, PutItem, etc). El detalle del servicio DynamoDB está fuera del ámbito de este post.
  3. Por razones obvias de seguridad, en AWS, para establecer la comunicación entre servicios, es estrictamente necesario definir las políticas y unos roles donde indiquemos las reglas de visibilidad entre servicios, e incluso qué funciones pueden ser invocadas entre sí. Esto se conoce dentro del ámbito de AWS como el Execution Role. Todo esto se define en la consola IAM, específicamente en los apartados de Policies y Roles. En el primero definimos acceso y visibilidad (reglas del juego), y el segundo es el perfil a quién le asignamos tales políticas y reglas de acceso.

Nota: La relación entre el recurso y la tabla de base de datos que vamos a consultar se define dentro del apartado Policies.

¡Ya casi lo tenemos! Ya hemos creado tanto el recurso como el método (/players/{id} – GET). Ahora solo quedaría definir de qué manera deberán ser mapeados los parámetros de la API con respecto a los argumentos que espera el servicio a ser invocado (siempre que esto aplique, obviamente).  

Paso 4: parámetros y transformación de datos

Dado que estamos hablando de peticiones HTTP y que los parámetros pueden viajar bien sea a través de la URL (path/querystring), en el cuerpo o en la cabecera de la misma (body y header respectivamente), la configuración de los mismos vendrá condicionada al servicio que se desee invocar.

Ahora bien, para el ejemplo que hemos propuesto, para invocar la acción Query dentro del servicio DynamoDB, es necesario proporcionar nombre de la tabla y los campos por los que que realizará el filtro (requisitos mínimos de la acción).

En todo caso, el mapeo y transformación tanto de los argumentos de entrada como los de salida, los podemos configurar dentro de la sección Method Execution.

Donde:

  • En el Method Request se indican los parámetros de la petición, cadena de parámetros, el cuerpo y cabeceras.
  • El Integration Request se refiere a la captura y transformación de los datos que enviaremos al servicio a invocar (bien sea una función Lambda, un endpoint interno o externo, u otro servicio dentro de AWS). Este paso puede ser también visto como el pase de parámetros de la API al servicio.
  • Por lo general, las respuestas de los servicios no vienen expresadas en el formato más adecuado, o mejor dicho, no vienen en el formato que requerimos. De igual manera que sucede en los parámetros de entrada, es posible hacer un mapeo o transformación de la respuesta recibida desde el servicio que invocamos y así podamos presentarla en nuestra API de la forma deseada. Esto lo podemos conseguir editando el apartado Integration Response.
  • Por último, tenemos el Method Response en el que podemos hacer alguna transformación o proyección de datos con respecto a la presentación de resultados desde la API.

Con respecto al ejemplo:

  • El Method Request no lo vamos a modificar, ya que nuestro método recibirá el parámetro de entrada desde el mismo recurso.
  • El Integration Request, este apartado sí que lo configuraremos, ya que para la integración con el servicio DynamoDB es necesario indicar una serie de valores tales como nombre de la tabla a consultar, campos que deseamos mostrar, filtro a aplicar, ordenación de resultados entre otros, que son requeridos para la ejecución de la acción.

El cuerpo de la petición lo establecemos desde la opción Body Mapping Templates del Integration Request. En este punto podemos capturar los datos que viajan desde la entrada hasta el método, parsearlos (si es necesario) y luego inyectarlos a la llamada del servicio con el que estamos enlazando.

El cuerpo de la petición se expresa en formato JSON, por lo que el Content-Type será application/json. Siempre podemos partir de una plantilla que exista o una en blanco.

En la instrucción #set($inputRoot = $input.path(‘$’)) obtendremos todos los datos con la que se ha realizado la petición. De allí podremos extraer: parámetros, cabeceras, cuerpo y contexto; bien para añadir algún tipo de lógica o transformación, o bien para redirigirlos como entrada del siguiente punto de enlace que no es más que la invocación al servicio.

El JSON definido establece lo siguiente:

  1. TableName: nombre de la tabla a consultar.
  2. PrimaryKey: campo primario dentro de la tabla.
  3. KeyConditionExpression: condición de filtrado (campo id).
  4. ExpressionAttributeValues: es aquí donde hacemos la magia, es decir, aquí indicamos de dónde provienen los datos de entrada ($input.params(‘id’)) y dónde los inyectamos.

Con esto último nuestra API está preparada para consultar el registro de la tabla player a través del método GET del recurso /players/{id}. Lo podemos comprobar ejecutando el test que nos provee la consola.

De forma similar que ocurre en el Integration Request, en el Integration Response podemos configurar el apartado Body Mapping Templates. Pero en este caso, para hacer una proyección de los datos obtenidos, es decir, podemos aplicar alguna transformación a la respuesta, y de esta manera presentar la salida como sea más conveniente.

Como podemos observar, aquí preparamos la salida para que tenga una estructura más manejable. ¡Ojo! Esto es siempre que lo necesitemos, ya que este punto no es imprescindible para llevar a cabo la integración de la API con un servicio.

Si ejecutamos nuevamente el test veremos claramente la diferencia:

Paso 5: Despliegue y publicación

El despliegue de una API es más sencillo de lo que podríamos imaginar, solo es necesario ejecutar la acción Deploy API.

Seguidamente mostrará un popup donde indicaremos la fase o entorno, acompañado por un nombre y una breve descripción.

Con este paso ya hemos conseguido publicar los servicios a través de nuestra API.

Cada Stage que creamos nos suministrará una URL a través de la cual podremos atracar un servicio utilizando el método adecuado.

Paso 6: Ajustes y puesta a punto

Una vez realizado el despliegue podemos realizar una serie de configuraciones con respecto al rendimiento, seguridad y monitorización de la API. Esto lo podemos conseguir editando el apartado Stages.

Aquí podemos activar el uso de la caché para mejorar los tiempos de respuesta. También podemos ajustar el umbral de peticiones por segundo y ráfagas de peticiones concurrentes.

Y en caso de que la integración así lo requiera, también podemos habilitar la opción de envío de certificado digital. Los certificados podemos añadirlos directamente en el apartado Client Certificates.  

Finalmente, cada Stage proporciona una URL con el que podemos consumir la API.

Conclusión

Para la mayoría de las aplicaciones basadas en microservicios tiene sentido implementar una API Gateway que actúe como un único punto de entrada en un sistema, asumiendo la responsabilidad del enrutamiento de solicitudes, la composición y la traducción de protocolos.

Adicionalmente es una herramienta de fácil gestión, con la que podemos acortar los tiempos de despliegue y puesta en producción. Algo sumamente importante si queremos que nuestros desarrollos sean dinámicos y donde los deadlines están a la orden del día.

Daniel Enrique Colina Rodríguez es un Ingeniero informático venezolano, amante de las nuevas tecnologías, especialmente de todo lo relacionado con el mundo cloud y arquitecturas distribuidas. Posee gran experiencia y actualmente trabaja como desarrollador de software especializado en Java y Spring Framework en Paradigma Digital. Es un fiel creyente de que el verdadero poder está en el conocimiento.

Ver toda la actividad de Daniel Colina