lunes, 22 de junio de 2020

Capítulo 6: Estrategia de despliegue de microservicios - Microservices Designing Deployment

El despliegue de micro servicios es un tanto distinto del despliegue un monolito. Cada microservicio podría estar escrito en su propio lenguaje y framework. Hay unos cuantos patrones de  despliegue de microservicios.

Multiple Service Instance Per Host Pattern

Cuando se usa este patrón se provisionan uno o más host, ya sean físicos o virtuales, y se ejecuta una instancia del servicio en cada uno de ellos. Esta es la estrategia tradicional para el despliegue de aplicaciones. 


Hay variantes de este patrón como puede ser ejecutar varias instancias de servicio. En teoría, el uso de recursos es más eficiente dado que comparten el servidor y el sistema operativo. Otro beneficio de este patrón es que simplemente copias el servicio al host y se inicializa.


La principal desventaja es que no hay un aislamiento de los servicios dado que comparten los recursos de la máquina. Esto implica que un servicio pueda llegar a consumir el 100% de los recursos de esa instancia.


Otra desventaja es que el equipo de operaciones debe conocer los detalles específicos de cómo desplegar ese servicio. Esto incrementa la complejidad de desplegar el servicio.

Service Instance per Host Pattern

Con este patrón se ejecuta cada instancia del servicio en propio host. Hay dos especializaciones distintas: por máquina virtual o por concendor.

Service Instance per Virtual Machine Pattern 

Se crea una máquina virtual con todo lo necesario para ejecutar el servicio. A partir de esa imagen se pueden crear múltiples instancias de la máquina virtual.


El principal beneficio es que cada máquina corre aislada y tiene una CPU y una memoria propia que no comparte. 


Otro beneficio es que la máquina virtual encapsula la implementación del servicio. Una vez que el servicio está empaquetado se convierte en una caja negra para el administrador de máquinas virtuales.


Por el contrario, es que la utilización de los recursos es menos eficiente. Cada máquina virtual tiene el overhead de la máquina virtual incluyendo el sistema operativo.


Otro dificultad es que es difícil reaccionar rápidamente a los cambios bajo demanda. Debido a esto hay que hacer una previsión lo cuál encarece el coste de despliegue.


Otra desventaja es que desplegar una nueva versión del servicio es un proceso lento. Las máquinas virtuales son lentas de instanciar y tardan tiempo en empezar. 


Todo este proceso conlleva un tiempo que, aunque es necesario, no se está invirtiendo en el negocio.


Service Instance per Container Pattern

Con esta estrategia cada instancia del servicio se ejecuta en su propio contenedor. Los contenedores son un mecanismo de virtualización al nivel del sistema operativo.


Un contenedor consiste en uno o más procesos ejecutándose en un sandbox. Desde la perspectiva de procesos estos tienen su propio sistema de archivos y puertos. Se puede limitar los recursos de CPU y memoria de un contenedor.


Para llevar a cabo este patrón cada servicio se empaqueta como un contenedor. Esta contenedor es un imagen del sistema de archivo y las librerías que el servicio necesita para su ejecución.


Una vez que se ha empaquetado el servicio como un contenedor se pueden lanzar una o varias instancias tanto en una máquina física como en una máquina virtual. El administrador de recursos, como Kubernetes, administra los host como un pool de recursos. Este decide donde ubicar cada instancia dependiendo de los requerimientos de la misma y los recursos disponibles.


Los beneficios son aislar las instancias unas de otras. Se pueden monitorizar los recursos de cada contenedor. Los contenedores encapsulan el servicio.


Los contenedores, no como las máquinas virtuales, tienen la ventaja de ser una tecnología más ligera. Se pueden construir muy rápido y también empiezan muy rápido ya que no hay un mecanismo de arranque de un sistema operativo. Cuando un contendor empieza lo que ejecuta es el servicio.


Las desventajas es que mientras la infraestructura de contenedores madura rápidamente no está tan madura como la infraestructura de máquinas virtuales. Los contendores no son tan seguros como las máquinas virtuales desde que estos comparten el kernel.


Otro inconveniente es que hay que administrar los contenedores.

Serverless Deployment

Es un concepto en el cual se trata de evitar tener que lidiar con las máquinas virtuales o contenedores. 


AWS Lambda es un ejemplo de tecnología serverless. Sólo hay que empaquetar el microservicio, proporcionar unos metadatos y AWS Lambda levanta suficientes instancias del microservicio para manejar las solicitudes.


Como en todo, AWS lambda tiene sus limitaciones aunque no deja de ser atractivo el poder abstraerse de la parte de infraestructura.

domingo, 14 de junio de 2020

Capítulo 5: Event Driven Data Management - Microservices Designing Deployment

Microservicios y el problema del tratamiento de los datos distribuidos

Una aplicación monolítica suele tener todos sus datos en una única base de datos. Si es una base de datos relacional entonces la aplicación puede usar los beneficios de las transacciones ACID:


  • Atomicity: los cambios se hacen atómicamente.

  • Consistencia: el estado de la base de datos siempre es consistente.

  • Isolation: incluso aunque las transacciones se ejecuten concurrentemente estas aparecen como si se hubieran ejecutado en serie.

  • Duration: Una vez que una transacción ha finalizado esta no se deshace.


Esto permite a la aplicación hacer una transacción, cambiar múltiples filas y persistir la transacción con los beneficios anteriores.


Otro beneficio de tener una base de datos relacional es que está provee SQL con lo que se puede escribir consultas entre varias tablas mientras que el motor de SQL se encarga de todos los detalles para ejecutar la consulta liberando al programador de esta tarea.


Sin embargo, el tratamiento de los datos es mucho más complejo en la arquitectura de microservicios. Esto se debe a que cada microservicio tiene la responsabilidad de tratar sus propios datos y sólo se puede acceder a estos a través de su API. Encapsular los datos garantiza que los microservicios no estén acoplados y puedan evolucionar de manera independiente.


La dificultad se incrementa si los microservicios utilizan distintas bases de datos. Desde que cada microservicio es independiente este puede requerir de una base de datos que se adapte más a sus necesidades. Por ejemplo, un microservicio que almacena grandes cantidades de texto y hace búsquedas sobre este tendería a usar un motor de base de datos de búsqueda de texto como puede ser Elasticsearch.


El primer reto para los microservicios es mantener la consistencia entre los múltiples servicios.


Se podría usar transacciones distribuidas, también conocidas como Two-Phase Commits (2PC). Sin embargo 2PC no es viable en aplicaciones modernas debido al teorema del CAP.


El teorema del CAP, también conocido por Brewer’s theorem, establece que es imposible para un almacén de datos distribuidos proveer más de de dos de las siguientes garantías:


  • Consistency: cada lectura recibe la escritura más reciente o un error.

  • Availability: cada petición recibe una respuesta (no error) sin la garantía de que tenga la escritura más reciente.

  • Partition tolerance: el sistema continúa funcionando incluso si un número arbitrario de mensajes se han descartado o se retrasan entre nodos de la red.


El segundo reto es cómo implementar las consultas que obtienen datos de múltiples servicios dado que cada microservicio tiene sus propios datos.

Arquitectura dirigida por eventos o «Event-Driven Architecture»

La solución pasa por usar una arquitectura dirigida por eventos o «Event-Driven Architecture». En esta arquitectura, un microservicio publica un evento cuando algo notable ocurre. Por ejemplo, la creación de una nueva entidad. Otros microservicios pueden estar suscritos a estos eventos y pueden reaccionar ante tales eventos ya sea creando una nueva entidad, modificando una existente, borrando, etc. lanzando a su vez sus propios eventos.


Es importante advertir que estas no son transacciones ACID lo cuál ofrece garantías mucho más débiles que la consistencia eventual.


Una desventaja es que, como no son transacciones ACID, entonces hay que implementar el sistema de manera que se pueda recuperar de los fallos por negocio. 


Imagina que para que un cliente pueda realizar un pedido deba disponer del crédito suficiente. En este caso, el cliente realizará el pedido de manera que el microservicio de pedidos recibirá, a través de su api, un petición para dar de alta un pedido. Este guardará el pedido a través de un mecanismo de persistencia, como puedes una base de datos relaciona MariaDb, para posteriormente enviar un evento de tipo «pedido creado» a un software de negociación de mensajes o broker de mensajería como pueden ser RabbitMQ o Apache Kafka.



A su vez, el microservicio de créditos, estará escuchando este tipo de eventos («pedido creado»). Este procesará el evento de manera que compruebe si el cliente tiene el crédito necesario. En el caso de no tener el crédito necesario lanzará un evento «crédito insuficiente»



 El servicio de pedidos estará escuchando este tipo de eventos. Procesará el evento y actualizará el pedido con un estado de crédito insuficiente. 



Como se puede apreciar, tanto la arquitectura software como hardware es más compleja que utilizando una única base de datos con las transacciones ACID ya que hay que tener cubrir los fallos de negocio, como por ejemplo que no haya crédito suficiente.


Otros retos a los que se enfrentan los desarrolladores son la inconsistencia de los datos debido a que ya no hay transacciones ACID y que los suscriptores de eventos deben detectar e ignorar los eventos duplicados.

Atomicidad

En la arquitectura dirigida por eventos o «Event-Driven Architecture» hay un problema de atomicidad en cuanto a actualizar la base de datos y publicar el evento.


Por ejemplo, el microservicio de pedidos debe registrar el nuevo pedido en su base de datos y publicar el evento «Pedido creado». Es esencial que este par de operaciones se hagan atómicamente. Si por alguna razón la ejecución del servicio se interrumpe después de actualizar la base de datos, entonces el sistema quedará en un estado inconsistente.


La manera estándar de asegurar la atomicidad sería usar una transacción distribuida involucrando a la base de datos y el «broker de mensajería». Sin embargo, por el teorema del CAP, eso es lo que no queremos hacer.


Para solventar el problema se pueden utilizar 3 estrategias: 


  • Usar la base de datos como una cola de mensajes.

  • Minado de del log de transacciones.

  • “event sourcing”.


Estas estrategias pasan a desarrollarse a continuación


Publicando eventos usando transacciones locales

Una estrategia para lograr la atomicidad es utilizar un proceso en varios pasos utilizando transacciones locales. El truco es tener una tabla «EVENTS» de manera que la aplicación actualice la entidad, inserte un evento en registro en la tabla «EVENTS» y haga commit de la transacción. Por otro lado, otro proceso, un administrador de eventos, se encarga de publicar estos eventos en el «broker de mensajería» y actualizarlos como publicados



La ventaja es que garantiza que el evento se publique sin recurrir al «two phase commit» o 2pc.


Una desventaja es que responsabiliza al programador de añadir los eventos, con lo que en algún momento puede olvidarse. Otra posible inconveniente podría ser el uso de algunas bases de datos NoSql que no tengan las capacidades de transacciones.

Minando el log de transacciones de la base de datos

Otra manera de lograr la atomicidad «two phase commit» o 2pc es tener otro proceso que examine el log de transacciones o commits. Cuando este proceso encuentra una transacción entonces publica un evento.


Con esta estrategia se garantiza que se publica un evento por cada transacción a la vez que simplifica la aplicación separando esta de la publicación de eventos.


Una desventaja es que cada base de datos tiene su propio formato de log de transacciones y que, para que sea un reto más interesante, este puede cambiar entre versiones. También puede ser difícil hacer ingeniería inversa de los eventos de alto nivel de las actualizaciones.

Usar Event Sourcing

La arquitectura «Event Sourcing» logra la atomicidad sin el «Two Phase Commit» (2PC) con un enfoque distinto para persistir las entidades de negocio. Más que guardar el estado de la entidad, lo que guarda la aplicación es la secuencia de eventos de cambio. De manera que la aplicación puede reconstruir el estado actual de una entidad recorriendo todos los eventos. Cuando el estado de una entidad cambia, se añade un nuevo evento y esta operación ya es atómica de por sí.


Los eventos persisten en un almacén de eventos o «Event Store». Este almacén tiene una API para añadir y recibir eventos de una entidad. Este también se comporta como el «Broker de mensajería» el cual entrega los eventos a los suscriptores.


Si continuamos con el ejemplo del a creación del pedido, el microservicio de pedidos haría una petición a la API del «Event Store» para crear el evento de «pedido creado». Este registraría el evento y enviaría un evento «pedido creado» al microservicio de crédito. Este procesaría el evento y haría una petición para registrar el evento «crédito insuficiente». El «Event store» procesaría la petición registrando el evento y enviaría este al microservicio de pedidos.



Los principales beneficios de esta arquitectura es que solventa los problemas de la consistencia de datos a la par que provee un log de los cambios de una entidad de negocio. Otro beneficio es que la lógica de negocio consiste en entidades de negocio pobremente acopladas que intercambian eventos.


Como desventajas tiene que es un estilo de programación un tanto diferente y, por ende, tiene una curva de aprendizaje. El almacén de eventos sólo admite la búsqueda de entidades de negocio por clave primaria. Se debe usa la «Separación de comandos y consultas», o por sus siglas en inglés, el «Command Query Responsibility Separation» (CQRS) para implementar las consultas. 


Resumen

En la arquitectura de microservicios cada microservicio almacena sus datos en su propia base de datos, ya sea Sql y NoSql. Esta distribución de los datos conlleva nuevos retos que afrontar como mantener  la consistencia entre los múltiples microservicios o recuperar los datos desde múltiples microservicios.


Para muchas aplicaciones la solución pasa por usar una arquitectura dirigida por eventos. El reto de esta arquitectura es actualizar el estado de forma atómica y cómo publicar eventos. Para acometer tal reto hay varias estrategias como usar la base de datos como cola de mensajes, minar el log de transacciones y el almacén de eventos o «Event Sourcing».


domingo, 7 de junio de 2020

Capítulo 4: Service Discovery - Microservices Designing Deployment

¿Por qué usar un descubridor de servicios?

Cuando un cliente hace una petición a una API REST necesita saber la localización en la red (dirección IP y puerto) de la instancia del servicio. En aplicaciones más tradicionales las localizaciones eran relativamente estáticas ya que solían ejecutarse en el mismo hardware físico. En aplicaciones más actuales, basadas en la nube con máquinas virtuales o contenedores, los localizaciones cambian con relativa frecuencia.


Con la llegada de estas nuevas estrategias en la nube también surgen nuevos retos como el cambio dinámico de las localizaciones debido a la auto escalada de servicios, los fallos, las actualizaciones. Los clientes necesitan usar un servicio que sea capaz de aportar soluciones de manera que para los servicios sea lo más transparente posible.


En la siguiente imagen se puede apreciar la problemática que puede llegar a tener un cliente para saber a qué instancia del servicio hacer la petición y más si las localizaciones son dinámicas.



Principalmente hay dos patrones para el descubrimiento de servicio: client-side discovery y server-side discovery.

Client Side Discovery Pattern

En esta estrategia, el cliente es el responsable de determinar la red de localizaciones de instancia de servicios disponibles y de balancear las peticiones entre ellas. El «Registro de servicios» tiene la responsabilidad de conocer la localización de todos los servicios disponibles. El cliente pregunta al «Registro de servicios» y usa un algoritmo de balanceo de carga para seleccionar una instancia.


En el siguiente diagrama se puede ver como cada instancia utiliza un cliente de registro para indicar su localización al «Servicio de registro». Cuando la aplicación cliente pregunta al servicio, este devuelve un listado con las instancias disponibles y su localización con lo que la aplicación cliente ya puede seleccionar una de las instancias.




La ventaja de este patrón es que no cambia mucho la arquitectura a la cuál sólo se le añade el «Servicio de registro». Esto da pie a que la aplicación cliente pueda tomar decisiones para el balanceo.


La principal desventaja es que este patrón es que acopla a la aplicación cliente el «Registro de servicios». Se debe implementar esa lógica para todas las aplicaciones clientes y estas pueden estar en distintos lenguajes de programación.

Service-Side Discovery Pattern

Esta estrategia también utiliza el «Registro de servicios» y la distintas instancias como en la estrategia anterior en la cual se incluye un nuevo componente: el balanceador de carga.


El balanceador de carga tiene la responsabilidad de redirigir la petición de la aplicación cliente a una de las instancias del servicio. El balanceador de carga pregunta al «Servicio de registro» las instancias de los servicios que están disponibles y la localización de estas.



Nota: los ELB de AWS usan esta estrategia.


Uno de los principales beneficios de esta estrategia es que para la aplicación cliente sólo hay un servicio con los que se abstrae todo el comportamiento de registro de servicios. El cliente se simplifica al no tener que implementar toda esta parte. 


Una desventaja es que se incrementa la complejidad de la arquitectura del sistema al tener que añadir otro componente. El balanceador de carga sería otro elemento de alta disponibilidad que se necesita desplegar, configurar y administrar.

El servicio de registro

El servicio de registros es otro servicio, el cual trata con la gestión de las instancias de servicio disponibles y sus localizaciones. Los clientes que hacen uso de ese servicio pueden almacenar en caché el listado devuelto por el servicio de registro pero puede quedar desactualizado rápidamente.


Un ejemplo de servicio de registro podría ser Netflix Eureka, el cual provee un API REST para la gestión de las instancias de servicio. Una instancia de servicio registra su localización haciendo una petición POST. Cada 30 segundos, este debe refrescar su estado usando una petición PUT. Una instancia deja de estar disponible si hace una petición DELETE o si el servicio de registro hace una petición al servicio de instacia y obtiene un timeout. Para obtener el listado de servicios disponibles se debe realizar una petición de tipo GET.


Para registrar y desregistrar las instancias se pueden utilizar dos estrategias:


  • Self-Registration Pattern.

  • Third-party Registration Pattern.

El patrón de auto registro o «Self-Registration Pattern»


Con el Self-Registration Pattern es la propia instancia la que tiene la responsabilidad de registrar y desregistrar del servicio de registro. También tiene la responsabilidad de mandar el heartbeat para saber que sigue operativa.


La principal ventaja es que es relativamente simple y no requiere de otros componentes del sistema.


Por otro lado, se está acoplando las instancias de servicio a al servicio de registro. Esto se puede solventar con el patrón «Third-Party Registration Pattern».

El patrón de registro de terceros o «Third-Party Registration Pattern»


El Third-Party Registration Pattern utiliza un componente nuevo, «Service Registration», que tiene la responsabilidad de gestionar los registros.


Este componente monitoriza los cambios del conjunto de instancias registrando ya sea sondeando el entorno de despliegue o suscribiéndose a eventos. Cuando este advierte una nueva instancia entonces la registra en el «Registro de servicios».


El mayor beneficio es que los servicios están desacoplados del servicio de registro. El registro de instancias  de servicio se trata de una manera centralizada dentro de un servidor dedicado.


La desventaja es que se añade un nuevo componente a la arquitectura del sistema que se necesita desplegar, configurar y administrar.


Resumen

En una aplicación de microservicios, el conjunto de instancias de servicio puede cambiar dinámicamente. Para que un cliente pueda hacer una petición a un servicio se debe usar un mecanismo de descubrimiento de servicios.


Una parte clave del descubrimiento de servicios es el servicio de registro. Este es otro servicio el cual modela el conjunto de instancias que están disponibles y sus localizaciones. Este provee una API para la gestión.


Hay dos estrategias a la hora para el descubrimiento de servicios.


  • Descubrimiento del lado del cliente: los clientes consultan al servicio de registro, seleccionan a qué servicio realizar la llamada y, posteriormente, hacen la llamada.

  • Descubrimiento del lado del servidor: los clientes hacen la llamada a un router el cual consulta al servicio de registro y redirige la petición a las instancias disponibles.


Hay dos maneras en las que las instancias de servicio se registran y se desregistran en el servicio de registro:


  • El patrón de auto-registro: las propias instancias se registran en el registro de servicio.

  • El patrón de registro de terceros: otro componente trata con el registro de las instancias de servicio.

martes, 2 de junio de 2020

Capítulo 3: Inter Process Communication (IPC) - Microservices Designing Deployment

En las aplicaciones monolíticas la comunicación entre los distintos módulos se hace mediante llamadas a funciones del lenguaje. Por regla general, todo se ejecuta en el mismo proceso. Sin embargo, en las aplicaciones basadas en microservicios los módulos se ejecutan ya no en procesos distintos si no en distintas máquinas.


Para que los microservicios se puedan comunicar hay que definir qué tipo de comunicación va a utilizar un microservicio determinado.


La comunicación de los servicios puedes ser:


  • One to one: un servicio se comunica con una instancia de otro servicio.

  • One to many: un servicio se comunica más de una instancia de otro servicio.


Y esta comunicación se puede producir de forma:


  • Síncrona: donde el servicio espera la respuesta de la instancia de otro servicio.

  • Asíncrona: donde el servicio no espera respuesta alguna.


Los IPC se pueden categorizar en los siguientes:



One to one

One to many

Sincrono

Request/response

-

Asíncrono

Notification

Publish/subscribe

Request/async response

Publish/Async response


Como se puede apreciar, las peticiones “One to one” se dividen en:


  • Request/Response: un cliente hace la petición y espera la respuesta.

  • Notification: el cliente envía una petición al servicio pero no espera una respuesta del mismo.

  • Request/Async response: un cliente envía una petición al servicio  el cual responde asíncronamente.


Las peticiones “one to many” se dividen en:


  • Publish/subscribe: un cliente publica un mensaje de notificación en una cola en la cual pueden estar escuchando de cero a n consumidores.

  • Publish/async responses: Un cliente publica un mensaje de solicitud y entonces espera cierta cantidad de tiempo por las respuestas de aquellos servicios que están interesados.


Los servicios puedes utilizar uno varios de estos de mecanismos de comunicación.


En el siguiente ejemplo se puede ver una posible solución a la solicitud de un viaje para una empresa tipo Uber.



El pasajero solicita un viaje desde su aplicación al «administrador de viajes». Este consulta los datos necesarios al «administrador de pasajeros» mediante una llamada REST y, si el usuario puede solicitar viajes, entonces manda una notificación al administrador de notificaciones para alertar a los posibles conductores. Las aplicaciones de los conductores están escuchando en una cola de mensajes las solicitudes de viajes.

Definiendo APIs

Una API es una interfaz que define el intercambio de información con un componente a través de un límite establecido. Esta debería definir el tipo de comunicación, las respuestas, los formatos de datos, etc.


Para definir una API es fundamental utilizar algún tipo de «Interface Definition Language» (IDL). Este vendrá definido por el tipo de mecanismo (IPC) que se use. Por ejemplo, si es HTTP la API consiste en URL y los formatos de petición y respuesta, si se usa message en la API dependerá de los tipo de canal y mensajes.

Envolviendo APIs

Las API tienden a ir evolucionando con el tiempo esto conlleva a determinados cambios como nueva funcionalidad, cambios en la misma o incluso eliminar funcionalidades obsoletas. Estos cambios tienen un impacto directo en los clientes que utilizan esa API.


En el caso de un monolito los cambios son más directos y se cambian aquellas llamadas que hacen uso de la API. En el caso de los microservicios no es tan sencillo ya que no se puede formar a los clientes a actualizar sus servicios. 


Si los cambios son cambios menores los cuales son retrocompatibles como por ejemplo añadir atributos, lo clientes que usaban la API vieja podrán utilizar la nueva versión si el servicio establece valores por defecto.


En el caso de cambios mayores que no sean retrocompatibles no se puede forzar a los clientes a actualizar a la nueva versión por lo que el servicio debe poder soportar las versiones más antiguas por un periodo de tiempo. 


Una estrategia para abordar este problema sería desplegar nuevas versiones del servicio de manera que se ejecuten simultáneamente. Por ejemplo, si se está usando un mecanismo HTTP como REST una estrategia comúnmente utilizada es añadir un número de versión en la URL. Cada servicio podría servir múltiples versiones simultáneamente.

Tratando con los fallos parciales

En cualquier sistema distribuido siempre está presente el riesgo de fallo. Cualquier de los servicios podría estar caído o no responder en un tiempo adecuado por esta razón es fundamental diseñar el sistema para tratar con estos fallos parciales.


Entre las estrategias para tratar con los fallos parciales se incluyen:


  • Networks timeouts: cuando se espera una respuesta de un servicio esta debe tener un tiempo límite establecido de manera que no se quede bloqueada indefinidamente.

  • Limitar el número de solicitudes pendientes: se debería establecer un límite de solicitudes pendientes de manera que cuando se alcance ese límite las peticiones entrantes existe una alta probabilidad de que el servicio se degrade y no pueda hacerse cargo de las peticiones por lo que estas deberían fallar inmediatamente.

  • Circuit breaker pattern: si se monitorizan las peticiones se puede conocer el porcentaje que estás que están fallando. Se puede establecer un límite de peticiones fallidas de manera que cuando este límite se supere se rompa el circuito y provoque que el resto de las llamadas fallen inmediatamente. El componente «circuit breaker» monitorizará el servicio de manera que cuando vuelva a estar operativo permita las llamadas al mismo.

  • Establecer fallbacks: si una petición falla se debería proporcionar qué hacer. Por ejemplo, si el servicio de comentarios no está disponible se podría utilizar datos guardados  o valores por defecto.

IPC síncrona

Los mecanismos IPC syncronos basados en petición y respuesta consisten en que un cliente hace una petición a un servicio, este procesa la petición y envía la respuesta al cliente. En estos casos los clientes se quedan a la espera de la respuesta.


Los dos protocolos más populares para este tipo de comunicación son REST y Thrift.

REST

REST es un mecanismo IPC que utiliza el protocolo HTTP. 


Existen dos conceptos clave:


  • Recurso: modela un objeto de negocio como un cliente o producto.

  • Colección: modela un conjunto de recursos.


REST establece que los recursos y colecciones tienen una URL la cual define cada uno de estos. Para manipular las colecciones y recursos se realizan peticiones utilizando los verbos del protocolo HTTP. Por ejemplo, las peticiones de tipo GET obtendran un recurso o colección, las peticiones de tipo POST crearán un nuevo recurso mientras que las de tipo PUT actualizarán un recurso.


Roy Fielding es el creador de REST y establece la arquitectura en:



Un ejemplo de REST podría ser la aplicación móvil de un pasajero creando un nuevo viaje en el administrador de viajes.



La aplicación hace una petición de tipo POST a la url «/trips» de la API REST del administrador de viajes. El servicio trata la petición verificando los datos enviados en la petición para, en el caso de crear el viaje,  devolver una respuesta con el código HTTP 201 (recurso creado).


Cómo se dijo antes, REST es un mecanismo IPC que se basa en el protocolo HTTP y, por lo tanto, puede hacer uso de este protocolo de distintas maneras.


Leonard Richardson define un modelo de madurez para REST con 4 niveles.

 

Nivel 0


La API tiene un sólo punto final de comunicación el cual recibe las peticiones de tipo POST de los clientes. Cada petición especifica qué acción se quiere realizar, sobre qué recursos y los datos necesarios para llevar a cabo la acción.


Nivel 1


Este nivel incluyen la idea de recursos. Ya no existe un único punto final de comunicación si no que cada recurso tiene su propio punto final. Los clientes harán una petición de tipo POST que especifica la acción a realizar y los datos necesarios para llevar a la acción.

Nivel 2


Al nivel anterior se le añade el uso de los verbos HTTP para indicar la acción que se quiere realizar sobre el recurso o la colección. Por ejemplo, una petición tipo POST creará un recurso mientras que una petición de tipo GET obtendrá un recurso ya creado.


Nivel 3


En este nivel, la representación de un recurso obtenido por una llamada de tipo GET contiene las acciones que se pueden realizar sobre ese recurso y los puntos finales donde realizar cada acción.


Este diseño se basa en el principio Hypertext As The Engine Of Application State o, por sus siglas, HATEOAS. Una de las principales ventajas de este principio es que los clientes no están acoplados a los puntos finales de cada recurso. Otra ventaja es que, al venir qué acciones se pueden realizar sobre ese recurso los clientes se evitan peticiones a acciones que no están disponibles en esos momentos.


Hay muchos beneficios de usar un protocolo que esté basado en HTTP:


  • HTTP es simple y familiar.

  • Hay una gran cantidad de herramientas para probar las API.

  • Soporta la comunicación petición/respuesta.

  • Es amigable con los cortafuegos.

  • No requiere de un intermediario por lo que simplifica la arquitectura del sistema.


Los inconvenientes de usar un protocolo HTTP:


  • Sólo soporta la comunicación petición/respuesta. 

  • Tanto el cliente y el servicio deben estar ejecutándose durante el intercambio de comunicación.

  • El cliente debe conocer la localización de cada instancia de servicio. Como se describió en la parte del API Gateway esto no es un problema trivial.

Thrift

Apache thrift es una alternativa a REST. Es un framework para escribir llamadas a procedimientos remotos o RPC entre clientes y servidores. Este provee un IDL del estilo de C para definir las API.

Formatos de los mensajes

Es importante que el formato de los lenguajes sea independiente de un lenguaje en concreto incluso si se está utilizando un mismo lenguaje para implementar los distintos microservicios. Nunca se sabe si en un futuro se utilizará otro lenguaje.


Hay dos tipos de mensajes: binarios y de texto. 


Hay varios formatos de mensajes binarios. Si se utiliza Thrift RPC se puede utilizar el formato Thrift binary. Otras opciones populares son Procotol Buffers y Apache Avro.


En cuanto a los mensajes de texto hay dos formatos muy populares: XML y JSON. Estos formatos no sólo tienen la ventaja de que se pueden leer directamente si no que son auto descriptivos. Por ejemplo, en formato JSON los atributos de un objetos se representan mediante una colección de pares nombre-valor. En XML los elementos se representan por elementos y valores.


La principal desventaja de estos formatos de texto es que tienden a ser verbosos, en especial el XML lo que puede resultar en una perdida de rendimiento al tener que analizar estos. 

Resumen

Los servicios se tienen que comunicar a través de un mecanismo de comunicación entre procesos. Cuando se describe cómo se van a comunicar los servicios se tiene que especificar la API para cada servicio, ćomo interactuan los servicios, cómo envolver esas API, cómo tratar con los fallos parciales. Hay dos tipos de mecanismos que los servicios pueden utilizar: mensajes asíncronos o peticiones y respuestas síncronas.