miércoles, 22 de marzo de 2017

Microservices y ApiGateways. Un Ejemplo.

Los microservices son funciones que realizan una tarea específica. Son ejecutadas tras algún evento (por ejemplo un cambio en un registro de una base de datos) o por la invocación a una URL o Endpoint, esta última controlada o gestionada por una Api Gateway.

En primer lugar te pediré que olvides lo que sabes de LAMP, WAMP, Hosting, VPC y similares entornos en donde antes desarrollabas tu aplicación web, la cual era dependiente de todo un entorno de software que tú debias crear (Instalar el sistema operativo,  actualizar paquetes, crear un loadbalancer, cuidar la cache y finalmente instalar un servidor http). Todo ese proceso proveerá un sistema monolítico en la mayoría de los casos.

Los Microservices llevan al desarrollador al siguiente nivel de desarrollo distribuido, realmente en la nube.

Hay dos conceptos clave que debes comprender:

1) Microservices. 


Son funciones que reciben argumentos mediante alguna via, realizan una tarea y crean un resultado que otras piezas o funciones pueden ver.  Por ejemplo, en Amazon AWS existe la base de datos DynamoDB, la cual puede integrarse con Funciones Lambda (Los microservices de Amazon), de modo que cuando un registro de la base de datos se actualiza o se inserta entonces una función se ejecuta y hace algo al respecto, por ejemplo, enviar un email, un mensaje de texto, una inserción en otra tabla etc.   Otro ejemplo es cuando una función se ejecuta mediante una llamada a una Url a través del uso de una ApiGateway y entonces esta función devolverá algo (contenido Json, Html, una imagen, procesará un POST, etc).

Como ejemplos de Microservices tenemos:

1.1) Lambda Functions de Amazon
1.2) Funciones de Google Cloud
1.3) Azure Functions (Windows)


2) ApiGateway (o Endpoints)


Un ApiGateway provee una URL que será manejada por una función (Un microservice) u otra Api Restul. En términos simples, un ApiGateway permite tener una Url para ejecutar un servicio dependiendo de: Parametros entregados en la Url o mediante Headers, principalmente el Content-Type.  Por ejemplo, según el Content-Type una Api podria filtrar el cuerpo del request para pasarle a la función que se ejecuta solo aquello que deba recibir, igualmente podria filtrar la respuesta y entregarla en un formato específico.

Un ApiGateway puede ser considerada una fachada (Facade) y también un Proxy entre una función y un cliente que la invoca.

Puede verse como "el cable" que une un browser con una función. El ApiGateway también sirve como "mapeador" de argumentos lo cual la hace muy útil ya que permite que podamos usar una misma función para responder a diferentes tipos de invocaciones. Por ejemplo, devolver JSON si tal o cual Header existe, o devolver Bytes etc.

El ApiGateway es como una mascara en frente de una Api o Función, de hecho, se considerá como una "Facade" (Fachada), y efectivamente lo es. Nuestra aplicación podría lidiar con el ApiGateway y esta podrá decidir en base a los argumentos dados y al tipo de contenido esperado para finalmente hacer la llamada a su función destino o a su api destino.

Si por ejemplo quien llama a la ApiGateway expone un encabezado "Accept: image/jpeg" entonces el ApiGateway podrá tomar lo que la api destino ha emitido y convertirlo para entregar los bytes de la imagen, por el contrario podria devolver solo texto en formato JSON.

Como ejemplos de ApiGateways tenemos:

2.1) Amazon Api Gateway
2.2) Kong
2.3) IBM Bluemix
2.4) Google Endpoints

Caso de Ejemplo de un ApiGateway


En la siguiente imagen muestro el diseño de una ApiGateway que dará Endpoints (Urls para usar) para los siguientes servicios (ver imagen adjunta). De esta imagen tomaré el endpoint "Media", el cual se define así:



























(Imagen: DISEÑO DE ENDPOINTS)


Ejemplo:

Siguiendo la imagen anterior, pondré el foco en el recurso "/page/media", el cual procesará un GET.

https://<ALGUN_IDENTIFICADOR>.amazon.aws.com/<version>/page/media/imagen.png/1

En donde "ALGUN_IDENTIFICADOR" será dado por la ApiGateway y la "<version>" será dada al momento de hacer despliegue (Deploy, algun equivalente lejano seria: "compilar").  

Cuando se  inserta esta URL en un elemento "<img src='URL' />" entonces suceden dos cosas:
  
1) El ApiGateway recibe una petición GET a ese Endpoint (ver la imagen abajo)
2) El request trae un Header: "Accept:  image/jpeg" (por el hecho de venir de un tag IMG)
En base a eso entonces el ApiGateway sabrá a qué función o Api invocar y sabrá además cómo procesar el resultado de la función o Api, en este caso retornando los Bytes de la imagen.  

Podriamos por ejemplo programar el ApiGateway para que devuelva contenido JSON si el Header "Accept" no esta presente. Este caso podría darse cuando pegamos la URL de la imagen en la barra de direcciones del browser, lo cual seria diferente a que un elemento IMG la invoque (en este caso el request traería el Header "Accept: image/jpeg"). 

Ahora, mostraré qué hace la función que sirve al Endpoint de la URL de arriba (/page/media/..).  Este Endpoint invoca a una Función Lambda de Amazon, la cual tiene algunos detalles de seguridad que no cubriré acá pero que hacen referencia a un Rol que se le debe dar a esta para que tenga permisos para ejecutarse y para acceder a otros recursos.

El Flujo de un ApiGateway

Acá muestro el flujo que da vida al Endpoint "/page/media" dentro de un diseño con Amazon Api Gateway:



 "Method Request" gobierna la configuración de los argumentos de la Url. Un argumento puede ser una variable en la URL ("?abc=123&xyz=890)", o puede ser un nombre especial entre llaves como es el caso de esta Api de ejemplo: /page/media/{attribute_name}/{id}.

"Integration Request" recibe los argumentos, el cuerpo (Body) de la petición (Request) y decide el método a usarse: Si invocará una función lambda u otra Api (y otras opciones), además podrá mapear o reasignar las variables entregadas contra las variables que la función destino o api destino necesitan. 

"Lambda WhMarketingApiMedia" es el nombre de la función que se esta ejecutando.

"Integration Response" procesará lo que la función retorna. Esta podría retornar los Bytes de la imagen o un objeto JSON aunque es mejor que esta ultima opción sea la preferida porque nos dará el poder de elegir elementos de la respuesta ayudando así a procesar lo que queremos devolver. 

"Method Response" se encarga de lidiar con Headers, para así responder de acuerdo a estos. Es acá donde finalmente se entrega la información.


La Función Lambda


Nuestra función Lambda hace lo siguiente (escrita en NodeJS):


La función lambda sobre estas lineas es invocada con unos argumentos (event, context, callback) que son procesados por el motor del ApiGateway y del mecanismo de Lambda Functions de Amazon.  Nuestra función se ejecutará dentro de un contenedor seguro gestionado por Amazon.

El argumento "event" es creado por nosotros en la configuración del ApiGateway, dentro de "Integration Request".

El Argumento "context" es entregado y manejado por Amazon.

El argumento "callback" es una función que debemos invocar cuando nuestra función ha terminado.

Lo que el cuerpo de la función hace es buscar en un "Bucket S3" de amazón por un nombre de archivo indicado en los argumentos de llamada (dentro de "event").  Un Bucket S3 es un almacen de contenido de Amazon.

Como resultado devolvemos los Bytes de la imagen, pudimos haber devuelto una estructura JSON, pero para el caso particular de las imagenes se debe entregar codificado en base64, debido a que recibirá un tratamiento especial por el ApiGateway.

Ejemplo de Diagrama mas complejo de una aplicación real diseñada en la Nube


Esta primera versión ilustra la idea principal. Queremos crear una LandingPage para un cliente dado su ID de cliente. Las piezas estan distribuidas en diferentes servicios de Amazon (BucketS3, DynamoDb, Funciones Lambda, Etc).


Luego acá una idea mas detallada de cómo quedó el diseño finalizado, ilustrando las piezas usadas y sus dependencias:



Funciona así:

Una URL es ejecutada en el browser y esta presenta a un LandingPage que es construido con información que se sacó de una tabla en DynamoDb. Esta landingpage usa imagenes, las cuales son referenciadas por su URL.

Las imagenes se obtienen de BucketsS3 y se piden mediante una Url especial diseñada para entregar Bytes de estas (Endpoint "/page/media/..").

El Formulario se fabrica en una parte especial de la función que crea la pagina (FormBuilder.js) y se incrusta en el contenido Html. Es bien sabido que cuando un formulario es "enviado" entonces hará una llamada de tipo POST a una URL, pues bien, esta URL también ha sido considerada en el diseño de endpoints (ver imagen "Diseño de Endpoints").

Una función lambda (la que maneja el Post) es especializada en capturar el POST, guardarlo en una tabla de DynamoDB y nada mas...

La tabla de DynamoDb ha sido diseñada para que tenga un "Trigger" que será ejecutado cuando un registro sea insertado o modificado, en tal situación se ejecutará otra función lambda que examinará el dato modificado y en consecuencia le enviará un email a alguien.

Finalmente:



¿ Y qué hacemos con esa Horrible URL ?

Dos vías,

1) puedes crear un CNAME para el hostname, pero los recursos serán aún visibles, es decir, si hacemos un CNAME para que sustituya el nombre del host quedaría así:

    http://129898192.apigateway.aws.com/page/1

con un CNAME quedaría asi:

   http://algo.mipagina.com/page/1

en donde el CNAME sería así:

   algo.mipagina.com => 129898192.apigateway.aws.com

2) Otra solución sería implementar un Proxy desde el servidor de una pagina que provea una url amigable.

 por ejemplo:

Cuando alguien acceda a "http://abc.com/xyz/123" entonces invocamos al recurso indicado via CURL desde el servidor, a modo de Proxy.

Este es mi método preferido, por la razón del uso de SSL y los certificados. Si creamos un certificado SSL para un dominio abc.com entonces la solución basada en CNAME no funcionará, pero esta última sí.  

No hay comentarios:

Publicar un comentario