Graduado en Ingenier´ıa Informatica´ - Archivo Digital...

26
“Ingeniamos el futuro” CAMPUS DE EXCELENCIA INTERNACIONAL Graduado en Ingenier´ ıa Inform´ atica Universidad Polit´ ecnica de Madrid Escuela T´ ecnica Superior de Ingenieros Inform´ aticos TRABAJO FIN DE GRADO Comunicaci´ on de microservicios usando colas de mensajes Autor: esar Guzm´ an Alp´ ızar Director: ´ Angel Herranz Nieva MADRID, JUNIO DE 2019

Transcript of Graduado en Ingenier´ıa Informatica´ - Archivo Digital...

Page 1: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

“Ingeniamos el futuro”

CAMPUS DE EXCELENCIAINTERNACIONAL

Graduado en Ingenierıa Informatica

Universidad Politecnica de Madrid

Escuela Tecnica Superior de

Ingenieros Informaticos

TRABAJO FIN DE GRADO

Comunicacion de microservicios usando colas de mensajes

Autor: Cesar Guzman Alpızar

Director: Angel Herranz Nieva

MADRID, JUNIO DE 2019

Page 2: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Indice1. Resumen 2

1.1. Version en espanol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

1.2. English version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

2. Introduccion 32.1. Arquitectura monolıtica . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

2.2. Arquitectura orientada a microservicios . . . . . . . . . . . . . . . . . . 3

3. Colas de mensajes 43.1. Protocolo AMQP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

3.2. RabbitMQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3.2.1. Elementos de RabbitMQ . . . . . . . . . . . . . . . . . . . . . . 7

3.2.2. Ejemplo practico usando RabbitMQ . . . . . . . . . . . . . . . . 9

4. Diseno 164.1. Caso de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

4.2. Eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

4.3. Listeners y Processors . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

4.3.1. Listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

4.3.2. Processors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4.4. RabbitMQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

5. Tecnologıas usadas 21

6. Detalles de implementacion 216.1. Metaprogramacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

6.2. Behaviours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

7. Conclusiones 23

8. Referencias 24

Page 3: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

1. Resumen

1.1. Version en espanolEn esta trabajo, se ha transformado una parte de un sistema con una arquitectura monolıti-

ca de una empresa real para conseguir una arquitectura orientada a microservicios.

Una vez separados varios modulos en microservicios, se han comunicado entre si median-

te colas de mensajes, en concreto la implementacion que ofrece RabbitMQ.

El resultado del trabajo es una clara mejorıa en cuanto a escalabilidad, mantenibilidad

y rendimiento del sistema de la empresa gracias a la separacion de funcionalidades en

microservicios.

1.2. English versionIn this thesis, a monolithic system from a real company has been transformed into a mi-

croservice system.

Once separated several modules in microservices, they have been connected between

them with message queues so they can communicate between them, this communication

system has been implemented with RabbitMQ.

The results of this thesis shows a clear improvement of scalability, maintainability and

performance of the company system thanks to splitting some functionalities into micro-

services.

2

Page 4: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

2. IntroduccionLa arquitectura del software son las estructuras de alto nivel que componen un sistema

software, de las multiples que existen, en este trabajo se hablara de dos ampliamente

conocidas: arquitectura monolıtica [1, Chapter 1] y arquitectura orientada a microservi-cios [2] [3].

2.1. Arquitectura monolıticaEn la arquitectura monolıtica se opta por tener un solo sistema que ofrezca todas las fun-

cionalidades que se desean, gracias a la modularizacion se puede separar correctamente

cada parte de este unico sistema. Un ejemplo de este tipo de arquitectura es el de una apli-

cacion web Java que se compone de un archivo WAR que se despliega en un contenedor

web como Apache Tomcat, los principales beneficios de este sistema son:

Facil de desarrollar con una programacion modular

Facil de desplegar, hay que desplegar unicamente una aplicacion, o como se ha

mencionado anteriormente, un solo archivo WAR

Facil de escalar, se puede escalar ejecutando multiples copias tras un balanceador

de carga

Aun ası actualmente se evita la arquitectura monolıtica por las siguientes razones:

Una vez comenzado el proyecto, la gran cantidad de codigo en una sola aplicacion

puede intimidar a nuevos desarrolladores si no se ha modularizado correctamente

Al ser un solo sistema tardara mucho en arrancar y detenerse, lo cual puede ser un

problema para sistemas que pretender dar una alta cobertura de servicio

Escalar el desarrollo puede ser complicado, ya que los equipos de desarrollo se

dividiran distintas areas de la aplicacion y esto fuerza la coordinacion entre distintos

equipos de desarrollo y evita que puedan trabajar independientemente

Requiere un compromiso a largo plazo con la tecnologıa que se use desde el prin-

cipio ya que puede ser difıcil mas adelante cambiar de tecnologıa

2.2. Arquitectura orientada a microserviciosLa arquitectura orientada a microservicios se compone de varios servicios que realizan

cada uno una sola tarea, una aplicacion en este caso se compondrıa de varios de estos

servicios. Esta arquitectura permite combinar componentes independientes para realizar

las tareas que se deseen. En este caso las principales ventajas son:

Software mas mantenible, al ser servicios relativamente pequenos son faciles de

entender y, si fuera necesario, de cambiar

3

Page 5: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

Facil de probar, al ser funcionalidades concretas encapsuladas en un servicio pe-

queno es mas sencillo crear tests para comprobar su buen funcionamiento

Facil de desplegar, al contrario que en la arquitectura monolıtica no hace falta re-

desplegar toda la aplicacion, basta con parar y arrancar un solo servicio

Permite la organizacion del desarrollo en equipos independientes

Permite cambio de tecnologıas si es necesario evitando ası comprometerse con una

sola tecnologıa

Permite un escalado dependiendo de la demanda, si un servicio requiere mas uso

de CPU se puede redesplegar en una maquina con mas capacidad de computo

Al igual que la arquitectura monolıtica los microservicios tienen varias complicaciones:

Los desarrolladores deben lidiar con la comunicacion de los microservicios

Crear tests para probar la interaccion entre los servicios es complicado

Los despliegues pueden llegar a ser muy complejos si hay excesivas dependencias

entre los servicios, ya que tendrıan que arrancarse en un orden concreto lo cual

anula la ventaja de un despliegue sencillo

La comunicacion entre servicios puede hacer que el sistema sea impredecible y por

lo tanto mas complejo de entender plenamente

Uno de los retos que tiene la arquitectura de microservicios es saber cuando se deberıa

usar. En un desarrollo pequeno no suele tener mucho sentido invertir esfuerzo en sepa-

rar distintas funcionalidades en microservicios, ya que esto ralentiza el desarrollo. Esto

perjudica a startups que necesitan un desarrollo rapido para tener un sistema funcional lo

antes posible.

Otro gran reto es saber definir los distintos microservicios. Hay muchas maneras de sepa-

rar las distintas funcionalidades de un sistema, pero idealmente cada parte deberıa tener

pocas responsabilidades de las que hacerse cargo.

En este trabajo se mostrara un caso real de un cambio de arquitectura monolıtica a arqui-

tectura orientada a microservicios, comunicando los distintos servicios gracias a colas de

mensajes.

3. Colas de mensajesComo se ha visto en la seccion 2.2, los microservicios requieren un sistema de comu-

nicacion [4] entre ellos para poder llevar a cabo tareas complejas, este puede ser de dos

tipos:

Sıncrono: suponiendo que tenemos dos servicios, un cliente que envıa un mensaje

y un servidor que lo recibe. Para que la comunicacion sea sıncrona el cliente debe

esperar a que el servidor procese el mensaje y envıe una respuesta

4

Page 6: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

Asıncrono: una vez mas con los mismos dos servicios mencionados en el punto

anterior, para que la comunicacion sea asıncrona el cliente envıa el mensaje sin

esperar respuesta, de esta manera el servidor recibira el mensaje y el cliente puede

seguir realizando cualquier otra tarea pendiente

Un ejemplo de comunicacion sıncrona es uno peticion a traves del protocolo HTTP a

una API como puede ser la de Spotify. Al realizar una peticion a dicha API para pedir

informacion sobre una cancion el servicio que realiza dicha peticion tiene que esperar

a que el servidor de Spotify conteste con una respuesta. A continuacion se muestra el

resultado de hacer una peticion con restclient-mode un cliente HTTP que ofrece Emacs:

{"artists": [

{"id": "12Chz98pHFMPJEknJQMWvI","name": "Muse","type": "artist","uri": "spotify:artist:12Chz98pHFMPJEknJQMWvI"

}],"href":"https://api.spotify.com/v1/tracks/6KR9NaynH8bF9w727wKQBL",

"id": "6KR9NaynH8bF9w727wKQBL","name": "New Born","popularity": 47,"type": "track","uri": "spotify:track:6KR9NaynH8bF9w727wKQBL"

}// GET https://api.spotify.com/v1/tracks/6KR9NaynH8bF9w727wKQBL// HTTP/1.1 200 OK// Content-Type: application/json; charset=utf-8// Cache-Control: public, max-age=7200// Access-Control-Allow-Origin: *// Access-Control-Allow-Headers: Accept,// Authorization, Origin, Content-Type, Retry-After// Access-Control-Allow-Methods: GET, POST,// OPTIONS, PUT, DELETE, PATCH// Access-Control-Allow-Credentials: true// Access-Control-Max-Age: 604800// content-length: 2274// Date: Thu, 30 May 2019 08:03:38 GMT// Via: 1.1 google// Alt-Svc: clear// Request duration: 0.133811s

Como se indica en los metadatos de la peticion se han tardado 0.133811 segundos en

5

Page 7: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

completar la comunicacion.

Por otro lado, un ejemplo de comunicacion asıncrona es un envıo de un mensaje de texto

a traves Telegram, una aplicacion de mensajerıa instantanea. A continuacion se muestra

un ejemplo de un bot enviando un mensaje a un usuario:

iex(1)> ExGram.send_message(14977303, "Hello"){:ok,%ExGram.Model.Message{

audio: nil,text: "Hello",date: 1559203979,chat: %{

first_name: "Cuwano",id: 14977303,type: "private",username: "Cuwano"

},venue: nil,reply_to_message: nil,pinned_message: nil,...,from: %{

first_name: "Iron Test Bot",id: 376323488,is_bot: true,username: "irontest_bot"

}}}

En el codigo anterior da la sensacion de que el servidor ha contestado, pero la tupla

resultante es simplemente la informacion del mensaje que se ha enviado.

Aunque tambien puede utilizarse para la comunicacion sıncrona el protocolo AMQP [5]

es usado habitualmente para comunicacion asıncrona, en la siguiente seccion se explicara

en que consiste este protocolo y una de sus implementaciones. AMQP define una manera

de usar las colas de mensajes, que son buffers a los que se les envıan mensajes para ser

guardados a la espera de ser consumidos por servicios.

3.1. Protocolo AMQPEl protocolo AMQP permite comunicar varios servicios al mismo tiempo para que inter-

cambien mensajes de negocio ofreciendo un servidor central, el cual puede replicarse y

distribuirse, por el que pasan todos los mensajes los cuales se distribuyen en colas segun

la configuracion del servidor. Esto permite a cualquier sistema comunicarse con cualquier

otro independientemente de la tecnologıa, es decir, un servicio hecho Golang puede enviar

6

Page 8: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

mensajes al servidor para que otro servicio hecho en Scala procese informacion.

Actualmente existen multiples implementaciones de AMQP [6], cada una de ellas imple-

mentadas en distintos lenguajes de programacion, algunos de ellos son:

RabbitMQ

SwiftMQ

Kaazing

Apache Qpid

En este trabajo se explicara detenidamente RabbitMQ, dado que es una implementacion

Open Source que permite investigarlo a fondo.

3.2. RabbitMQRabbitMQ [7] [8] es uno de los sistemas de colas de mensajes Open Source mas desple-

gados gracias a su simplicidad a la hora de desplegar y su ligereza. El servidor esta pro-

gramado en Erlang, un lenguaje disenado para hacer aplicaciones altamente escalables y

mantenibles, esto permite a RabbitMQ heredar esas propiedades para tener la posibilidad

de ser un sistema distribuido.

3.2.1. Elementos de RabbitMQ

A continuacion se explicaran los conceptos mas importantes necesarios para usar Rab-bitMQ desde un cliente.

Connection

Es una conexion a un servidor de RabbitMQ, a partir de este elemento se podra operar

con el servidor.

La conexion requiere un host, un puerto, un usuario y una contrasena. El host y puerto

son necesarios ya que RabbitMQ puede usarse de manera distribuida y no necesita estar

en la misma maquina que el resto de componentes de la comunicacion. Por otro lado, el

usuario y contrasena determinaran los permisos que un cliente tiene en el servidor.

Channel

Permite multiplexar la conexion TCP/IP con el servidor, cada canal es independiente y en

clientes que soportan multi-threading se recomienda usar un canal por thread.

Queue

Es una cola FIFO (First In First Out) que almacena los mensajes hasta que son consumi-

dos por un cliente. Para que el comportamiento sea estrictamente FIFO solo debe haber

7

Page 9: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

un cliente consumiendo mensajes de cada cola, aunque RabbitMQ permite multiples con-

sumidores en una misma cola.

Exchange

Es uno de los elementos clave de RabbitMQ es el componente que recibe los mensajes y

los encamina o descarta hacia distantas colas de mensajes dependiendo de las propiedades

del exchange o del propio mensaje.

Antes de explicar los distintos tipos de exchanges es conveniente conocer los bindings,

son enlaces entre un exchange y una queue. Dependiendo del tipo de exchange los bin-dings se realizaran con distintos parametros.

Los exchanges tienen distintos tipos:

Direct: este tipo de exchange enruta los mensajes segun una cadena de texto llamada

routing key, cuando una cola se enlaza a un exchange de este tipo se especifica

su routing key. Cuando un mensaje es enviado a este tipo de exchanges se debe

especificar una routing key, de esta manera el exchange compara la routing key del

mensaje y envıa dicho mensaje a todas las colas que han usado la misma routingkey para enlazarse.

Fanout: este tipo de exchange no requiere ningun parametro para enlazarse a el

porque envıa todos los mensajes a todas las colas enlazadas a el.

Topic: este tipo de exchange actua de manera parecida al direct, pero cambia el fun-

cionamiento de la routing key. Ahora la routing key no es simplemente una cadena

de texto, es una cadena de texto que se compone de cero a mas palabras separadas

por puntos, por ejemplo: log.error. Tambien se ofrece una sintaxis para que los

mensajes se envıen a varias colas que satisfagan un patron, los caracteres especia-

les son el asterısco * que corresponde con una sola palabra sin importar cual y la

almohadilla # que corresponde con cero o mas palabras. Gracias a esta sintaxis se

pueden enviar mensajes con routing keys como: log.*, log.#, square.red.*o *.blue.#.

Headers: este tipo de exchange enruta los mensajes gracias a unos parametros que

se envıan junto a cada mensaje llamados headers. Los headers son una lista de

tuplas que se comparan al igual que se hace en el caso de un topic exchange con

la routing key. Un ejemplo de headers serıa: [{“format”, “pdf”}, {“type”, “log”},

{“x-match”, “any”}]. Como se ve en el ejemplo es necesario anadir un parametro

llamado x-match el cual solo puede tener dos valores: all o any. Si el valor de

x-match es any los mensajes se enrutaran si tienen alguno de los headers que se

hayan definido en el binding. Pero si el valor es all, para que se enrute un mensaje

sus headers deben coincidir completamente con los especificados a excepcion de

x-match que es ignorado.

8

Page 10: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

3.2.2. Ejemplo practico usando RabbitMQ

Para ilustrar las posibilidades de RabbitMQ se mostrara un ejemplo practico en el que se

usara un exchange de tipo topic para enrutar los mensajes a cinco colas distintas.

El ejemplo consiste en un sistema de logs distribuidos. Cualquier sistema enviara un men-

saje JSON serializado con el siguiente formato:

{"log_level": "level","log_message": "Example message"

}

Cada mensaje sera enviado a un exchange llamado logs que tendra cinco colas enlaza-

das, en la siguiente tabla se muestran las colas con sus respectivas routing keys:

Queue Routing keydebug queue logs.debug

info queue logs.info

warn queue logs.warn

error queue logs.error

all queue logs.*

Gracias a estos bindings cada cola recibira un nivel distinto de log, a excepcion de all queueque recibira todos los mensajes de log.

A este sistema podran enviar mensajes cualquier cantidad de servicios que llamaremos

Producers, estos seran los encargados de introducir mensajes en RabbitMQ. Por otro lado,

a cada una de las colas estara un Consumer conectado que ira procesando los mensajes.

Por simplicidad en este ejemplo solo habra un Producer y los Consumers simplemente

mostraran en pantalla los mensajes que reciban. A continuacion se muestra un diagrama

que describe la estructura de este sistema:

9

Page 11: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

Log Generator

Producer

logs

Exchangewarn queue

info queue

debug queue

error queue

all queue

Debug Client

Consumer

Info Client

Consumer

Warn Client

Consumer

Error Client

Consumer

All Client

Consumer

Figura 2: Ejemplo practico

Para este ejemplo se ha usado Elixir un lenguaje de programacion funcional influenciado

por Erlang.

Se han disenado dos modulos LogSystem.LogGenerator que sera el Producer de

nuestro sistema y LogSystem.LogReceiver que seran los Consumers. En un modu-

lo llamado LogSystem se ha creado una funcion que declara en el servidor de RabbitMQlos elementos de la figura 2. A continuacion se muestra el codigo de los distintos modulos:

LogSystem

En este modulo se define la funcion declare system que crea una conexion, un canal,

un exchange y las distintas colas.

1 defmodule LogSystem do2 use AMQP3

4 def declare_system() do5 {:ok, connection} = Connection.open()6 {:ok, channel} = Channel.open(connection)7

8 exchange_name = "logs"9 Exchange.declare(channel, exchange_name, :topic)

10

11 # Create a queue for each log level12 ["debug", "info", "warn", "error"]13 |> Enum.each(fn queue ->14 queue_name = "#{queue}_queue"15 routing_key = "logs.#{queue}"16 Queue.declare(channel, queue_name)17

10

Page 12: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

18 Queue.bind(19 channel,20 queue_name,21 exchange_name,22 routing_key: routing_key23 )24 end)25

26 # Create a general queue27 Queue.declare(channel, "all_queue")28

29 Queue.bind(30 channel,31 "all_queue",32 exchange_name,33 routing_key: "logs.*"34 )35

36 Channel.close(channel)37 end38 end

Como se puede ver en el codigo anterior se usa el modulo AMQP de la librerıa amqp de Eli-xir para comunicarse con el servidor de RabbitMQ. Al llamar a la funcion Connection.open()sin parametros AMQP intentara conectarse a un servidor en localhost en el puerto

5672 con usuario guest y contrasena guest.

Producer

1 defmodule LogSystem.LogGenerator do2 use GenServer3 use AMQP4

5 require Logger6

7 def random_log_level(),8 do: Enum.random(["debug", "info", "warn", "error"])9

10 def random_phrase() do11 number_of_words = Enum.random(5..10)12

13 Task.async_stream(14 1..number_of_words,15 fn _ ->16 Dictionary.random_word()17 end,18 ordered: false19 )20 |> Stream.map(&elem(&1, 1))21 |> Enum.join(" ")22 end23

11

Page 13: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

24 # Client API25 def start_link() do26 {:ok, connection} = Connection.open()27 GenServer.start_link(__MODULE__, connection)28 end29

30 # Server callbacks31 def init(state) do32 # Start to send phrases in 2 seconds33 {:ok, state, 2_000}34 end35

36 def handle_info(:timeout, connection) do37 {:ok, channel} = Channel.open(connection)38 log_level = random_log_level()39 routing_key = "logs.#{log_level}"40 phrase = random_phrase()41

42 Logger.info("""43 Sending:44 #{IO.ANSI.bright()}[#{log_level}] #{IO.ANSI.blue()}#{phrase}45 """)46

47 message =48 Jason.encode!(%{49 log_level: log_level,50 log_message: phrase51 })52

53 Basic.publish(channel, "logs", routing_key, message)54 Channel.close(channel)55

56 # Send a phrase every second57 {:noreply, connection, 1_000}58 end59 end

En este caso el modulo LogSystem.LogGenerator es un GenServer [9]. Un Gen-Server es un proceso Erlang que permite ejecutar codigo de manera asıncrona y mantener

un estado propio, en este caso se usa para generar texto aleatorio y enviarlo al servidor de

RabbitMQ indicando un nivel log.

Para enviar los mensajes de manera automatica se usa una propiedad de los GenServersque es el timeout, como se puede ver en la lınea 33 cuando el GenServer comienza su

ejecucion se le asigna un timeout de 2,000 milisegundos, esto hara que al iniciar esperara

2 segundos y se enviara a si mismo el mensaje :timeout que se trata en la funcion

handle info que aparece en la lınea 36.

La funcion handle info generara un texto automatico al recibir el mensaje de :timeout,

creara el JSON con el formato que se especifıca al principio de esta seccion y lo enviara

con la funcion Basic.publish que esta en la lınea 53. Una vez hecho esto el GenSer-

12

Page 14: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

ver vuelve a esparar, pero esta vez 1,000 milisegundos.

Para generar el texto automatico se usa una librerıa llamada Dictionary, es una librerıa

de codigo libre publicada en GitHub, un host de repositorios Git.

LogReceiver

1 defmodule LogSystem.LogReceiver do2 use GenServer3 use AMQP4

5 require Logger6

7 # Client API8 def start_link(log_level) do9 GenServer.start_link(__MODULE__, log_level)

10 end11

12 defp log_level_map(level) do13 case level do14 "debug" -> &Logger.debug/115 "info" -> &Logger.info/116 "warn" -> &Logger.warn/117 "error" -> &Logger.error/118 _ -> &IO.puts/119 end20 end21

22 def log(level, message) do23 f = log_level_map(level)24 (IO.ANSI.bright() <> message) |> f.()25 end26

27 # Server callbacks28 def init(log_level) do29 Logger.info("""30 Log receiver started, reading #{log_level} level logs31 """)32

33 {:ok, connection} = Connection.open()34 {:ok, channel} = Channel.open(connection)35

36 Queue.declare(channel, "#{log_level}_queue")37 Basic.consume(channel, "#{log_level}_queue")38

39 {:ok, %{log_level: log_level, channel: channel}}40 end41

42 def handle_info(43 {44 :basic_deliver,45 payload,

13

Page 15: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

46 %{delivery_tag: tag}47 },48 state49 ) do50 spawn(fn ->51 Basic.ack(state[:channel], tag)52 end)53

54 %{55 "log_level" => log_level,56 "log_message" => log_message57 } = Jason.decode!(payload)58

59 log(log_level, log_message)60

61 {:noreply, state}62 end63 end

Por ultimo, el modulo LogSystem.LogGenerator que tambien es un GenServer.

Se lanzara un LogGenerator por cada cola que tengamos en el sistema, en este caso

cinco.

Como podemos ver en la lınea 8, al ser llamado, este GenServer recibe un nivel de log

que sera debug, info, warn, error o all.

Al iniciar su ejecucion cada LogGenerator se suscribira a la cola que le corresponda, si

el log level con el que ha sido llamado es debug se suscribira a la cola debug queuede la que escuchara los mensajes.

Como se puede ver en la lınea 42 se reciben los mensajes de la cola, se responde con un

ACK al servidor para indicar que se va a tratar ese mensaje y el servidor puede eliminarlo

de la cola en la que estaba. Despues se deserializa el mensaje y se muestra por pantalla el

mensaje recibido.

Ejecucion del ejemplo

En este apartado se explicara como se ejecuta este pequeno ejemplo para poder ver el

resultado.

En primer lugar ejecutaremos un servidor de RabbitMQ, como la instalacion y ejecucion

es dependiente del sistema no se incluira.

Se usara iex, la shell interactiva de Elixir, cargando este proyecto. Se ejecutara el co-

mando iex -S mix desde la raız del proyecto.

Una vez abierto iex se debe ejecutar la funcion LogSystem.declare system para

declarar el exchange y las colas.

Dado que este sistema se puede distribuir y ejecutar en distintas maquinas se abriran cinco

iex mas para poder visualizar el resultado de este ejemplo correctamente.

14

Page 16: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

Se ejecutara en las cinco nuevas terminales LogSystem.LogReceiver.start link("level")siendo level cada uno de los siguientes: debug, info, warn, error y all.

Por ultimo ejecutaremos el Producer con LogSystem.LogGenerator.start link,

el resultado se puede ver en las siguientes figuras:

Figura 3: Producer y Consumer de todos los logs

En la figura 3 se ve en la mitad superior el Producer mostrando los mensajes que envıa y

en la mitad inferior el Consumer de la cola all queue.

15

Page 17: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

Figura 4: Consumers de los distintos niveles de log

En la figura 4, diferenciados cada uno con un color, se pueden ver el resto de Consumersrecibiendo unicamente el nivel de log al que esta suscrito.

4. DisenoEn esta seccion se explicara como se ha disenado una comunicacion de microservicios en

una empresa real llamada Coowry.

4.1. Caso de usoCoowry permite realizar micropagos de telefono movil a telefono movil gracias a un

sistema programado en Erlang y Elixir.

En este trabajo se ha transformado una parte de un servicio llamado coowry-core que,

ademas de ser la API de Coowry, provee las funcionalidades principales de todo el siste-

ma. Este servicio esta separado en modulos pero tiene una arquitectura monolıtica de la

que se han extraido algunas funcionalidades a microservicios externos.

En el funcionamiento original de coowry-core cuando un usuario realizaba una accion en

el sistema, por ejemplo enviar dinero a otro usuario, se ejecutaban las acciones pertinentes

para que se lleve a cabo dicha accion y se notificaba a los usuarios afectados por esta

accion mediante llamadas a funciones en modulos dedicados a la comunicacion con los

usuarios. Estos modulos envıan mensajes a traves de SMS, email o Telegram (aplicacion

de mensajerıa instantanea).

16

Page 18: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

Lo que se ha conseguido en este trabajo es que las acciones que se realizan sean notifica-

das a traves RabbitMQ a un servicio encargado de las comunicaciones llamado cwcomm,

este servicio escucha eventos que envıa coowry-core, y dependiendo del evento proce-

sado comunica a los usuarios de Coowry las acciones que se han realizado por las vıas

mencionadas anteriormente.

4.2. EventosPara determinar los eventos se han estudiado los distintos casos de uso del sistema. A

continuacion se muestran algunos de los eventos con una pequena descripcion:

Receipt requested: se crea cuando un usuario pide una factura de una transaccion

que ha realizado

Pin set: se crea cuando un usuario establece un pin de seguridad a traves de la

aplicacion movil o la web

Pin removed: se crea cuando un usuario deshabilita el pin de seguridad que ha

establecido anteriormente

Trade created: se produce cuando una transaccion se crea, esto puede ser una peti-

cion de saldo o un envıo de saldo

Email changed: se crea cuando un usuario cambia el email que ha dado a Coowry

a traves de la aplicacion movil o la web

Estos son algunos de los eventos que se envıan a traves de RabbitMQ serializados. Para

deserializar correctamente estos mensajes cwcomm dispone de un modulo donde guarda

los tipos de los distintos eventos en forma de struct, de esta manera cuando recibe un

mensaje lo deserializara al struct correspondiente, validando ası que el mensaje ha sido

enviado en el formato correcto.

A continuacion se muestra un fragmento del modulo Cwcomm.Event:

1 defmodule Cwcomm.Event do2 # Events from API (coowry-core)3 defmodule ReceiptRequested do4 defstruct [:trid, :to, :from]5

6 @type t :: %ReceiptRequested{7 trid: :cw_model.trid(),8 to: String.t(),9 from: String.t()

10 }11 end12

13 defmodule PinSet do14 defstruct [:user]15 @type t :: %PinSet{user: String.t()}16 end17

17

Page 19: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

18 defmodule PinRemoved do19 defstruct [:user]20 @type t :: %PinRemoved{user: String.t()}21 end22

23 defmodule TradeCreated do24 defstruct [:trid]25 @type t :: %TradeCreated{trid: :cw_model.trid()}26 end27

28 defmodule EmailChanged do29 defstruct [:to, :code, :action]30 @type t :: %EmailChanged{31 to: String.t(),32 code: String.t(),33 action: String.t()34 }35 end36 end

Como se puede ver en el codigo para cada evento se define un struct y los tipos de los

elementos de ese struct.

4.3. Listeners y ProcessorsPara escuchar y procesar los mensajes no se ha optado por un enfoce distribuido, aunque

gracias a la implementacion realizada es perfectamente viable distribuir los Consumersen distintas maquinas.

Se han creado dos elementos llamados Listeners y Processors que se describiran a conti-

nuacion.

4.3.1. Listeners

Los Listeners son GenServers a los que se les da la informacion necesaria para saber

a que colas tienen que escuchar y que eventos deben escuchar. Aunque se detallara su

implementacion en la seccion 6 a continuacion se muestra como se crea un Listener en

cwcomm:

1 defmodule Cwcomm.Listener.PinSet do2 alias Cwcomm.Event.PinSet3

4 use Cwcomm.Listener,5 queues: ["pin_set"],6 event_type: %PinSet{},7 primary_channels: %{8 "mail" => Cwcomm.Processor.Mail.PinSet,9 "push" => Cwcomm.Processor.Push.PinSet,

10 "telegram" => Cwcomm.Processor.Telegram.PinSet11 },

18

Page 20: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

12 secondary_channels: %{}13 end

Como se puede ver es algo muy sencillo, simplemente hay que usar la macro use Cw-comm.Listener, a la que pasamos los siguientes parametros:

queues: es una lista de strings que determina de que colas tiene que consumir este

Listener

event type: el tipo de evento que debe escuchar de las colas a las que este suscrito

primary channels: es un map (estructura clave/valor) en el que se ensena para cada

canal de comunicacion que modulo de cwcomm se encarga de procesarlo, se hablara

detenidamente de esto en la seccion 4.3.2

secondary channels: tiene la misma estructura que primary channels pero estos ca-

nales se usan en el caso de que ninguno los primary channels no esten disponibles

4.3.2. Processors

Un Processor es un modulo que se encarga de enviar un mensaje por un canal en concreto,

normalmente un Listener llama a la funcion process de varios Processors para notificar

a un usuario, a continuacion se muestra un Processor del evento PinSet para el canal

de Telegram:

1 defmodule Cwcomm.Processor.Telegram.PinSet do2 alias Cwcomm.Event.PinSet3 alias CwEcto.People.Telegram4

5 @behaviour Cwcomm.Processor6

7 require Logger8

9 @spec process(PinSet.t()) :: Cwcomm.Channel.response()10 def process(%{user: id}) do11 case Telegram.get_tguser_account(id) do12 [tg_user] ->13 params = %{14 to: tg_user.tg_id,15 locale: tg_user.language,16 action: :on17 }18

19 Cwcomm.Channel.Telegram.send("/api/pins", params)20

21 error ->22 Logger.debug("""23 Could not get telegram user,24 got instead:\n#{inspect(error)}25 """)26 end

19

Page 21: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

27 end28 end

Todos los modulos que sean Processors de un canal tienen que tener definida la funcion

process obligatoriamente, en la seccion 6 se vera como se consigue esto.

En el codigo anterior podemos ver que la funcion process obtiene los datos nece-

sarios del usuario que se va a notificar a traves de Telegram y se llama a la funcion

Cwcomm.Channel.Telegram.send para que el modulo del canal de Telegram se

ocupe de enviar la notificacion llamando a traves de una peticion HTTP a otro microser-

vicio que existe en Coowry llamado cwbottg el cual expone una API para poder operar

con el.

4.4. RabbitMQEn este proyecto se ha utilizado una estructura parecida a la creada en la seccion 3.2.2.

A continuacion se muestra un diagrama con los eventos mencionados en la seccion 4.2:

coowry-core cwexchange

receipt requested

pin set

pin removed

trade created

email changed

Pin Removed Listener

Pin Set Listener

Receipt Requested Listener

Trade Created Listener

Email Changed Listener

Figura 5: Estructura de RabbitMQ en Coowry

En este servidor de RabbitMQ la librerıa cwcore que usa coowry-core inicializa todos

los elementos necesarios, un exchange de tipo topic y una cola por cada evento con los

siguientes bindings:

20

Page 22: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

Queue Routing keyreceipt requested receipt.requested

pin set pin.set

pin removed pin.removed

trade created trade.created

email changed email.changed

Gracias al exchange de tipo topic podemos crear bindings como pin.set y pin.removed,

estos bindings ofrecen la posibilidad de tener en el futuro una cola con el binding pin.*que recibira todos los mensajes relacionados con el pin de seguridad del usuario. Esta

flexibilidad ha sido determinante a la hora de elegir el tipo de exchange.

5. Tecnologıas usadasEl sistema de Coowry estaba mayormente programado en Erlang, este lenguaje es usado

ampliamente en telecomunicaciones, empresas de banca y comercio electronico. Erlangbasa su diseno en gran parte en el modelo de actores.

Pero con el tiempo y despues de varias refactorizaciones de codigo, cada vez hay mas

servicios en Elixir, un lenguaje creado por Jose Valim que quiso aunar la comunidad y las

herramientas que ofrece un lenguaje como Ruby con la potencia y capacidades de Erlang,

creando ası un lenguaje funcional que permite programar sistemas altamente escalables y

potentes con herramientas que ayudan al desarrollador.

Para mantener todo el codigo se ha usado el sistema de control de versiones Git alojado

en Bitbucket.

6. Detalles de implementacionElixir ha ofrecido grandes funcionalidades para facilitar la implementacion del sistema, a

continuacion se veran las que mas se han aprovechado.

6.1. MetaprogramacionElixir da la posibilidad de extender el propio lenguaje mediante macros. Las macros son

funciones especiales que generan codigo que se inserta en la aplicacion en el proceso de

compilacion.

En este trabajo se ha usado la macro using para poder crear Listeners de manera sen-

cilla como se menciono en la seccion 4.3.1. Para crear un Listener se llama a la macro usecon unos parametros, la macro se define en otro modulo llamado Cwcomm.Listenery es la siguiente:

1 defmacro __using__(args) when is_list(args) do2 quote do

21

Page 23: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

3 def start_link(connection) do4 # Add channel, and process function to the state5 args =6 [7 connection: connection,8 event_module: __MODULE__9 ] ++ unquote(args)

10 Cwcomm.Listener.start_link(args)11 end12 def child_spec(args \\ []) do13 %{14 id: __MODULE__,15 start: {__MODULE__, :start_link, args},16 type: :worker17 }18 end19 end20 end

Definiendo esta macro, Elixir nos permite inyectar codigo en la fase de compilacion como

el que vemos en el ejemplo anterior, en el se crean dos funciones: start link en la que

se construye una keyword list con los argumentos que se han especificado al usar la

macro use, y child spec que es una funcion que define como tiene que ser ejecutado

ese GenServer.

6.2. BehavioursPara asegurar que los Processors tienen el mismo comportamiento siempre que se pro-

grame uno se han utilizado behaviours, una funcionalidad que ofrece Elixir que permite

definir una serie de funciones que tienen que ser implementadas por los modulos que usen

un behaviour.

A continuacion se muestra el behaviour que define un Processor:

1 defmodule Cwcomm.Processor do2 @callback process(struct()) ::3 Cwcomm.Channel.response()4 | Cwcomm.Channel.callback_response()5 end

Para crear un processor solo tendremos que crear la funcion process como se especifica

en el codigo anterior. Este es un ejemplo de un Processor:

1 defmodule Cwcomm.Processor.Telegram.PinRemoved do2 alias Cwcomm.Event.PinRemoved3 alias Cwcomm.Channel.Telegram4 import CwEcto.People.Telegram5

6 @behaviour Cwcomm.Processor7

22

Page 24: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

8 require Logger9

10 @doc """11 Gets the Telegram user id from de DB and sends a PinRemoved message12 """13 @spec process(PinRemoved.t()) :: Cwcomm.Channel.response()14 def process(%{user: id}) do15 case get_tguser_account(id) do16 [tg_user] ->17 params = %{18 to: tg_user.tg_id,19 locale: tg_user.language,20 action: :off21 }22

23 Telegram.send("/api/pins", params)24

25 error ->26 Logger.debug("""27 Could not get telegram user,28 got instead:\n#{inspect(error)}29 """)30 end31 end32 end

7. ConclusionesEn este trabajo se ha puesto en practica la transformacion de una arquitectura monolıtica

a una orientada a microservicios y se han podido aprovechar las ventajas que esta ofrece.

Aunque se ha requerido un esfuerzo inicial por parte de todo el equipo de desarrollo de

software para aprender a usar RabbitMQ y aplicar tecnicas de metaprogramacion, final-

mente ha compensado el esfuerzo porque ahora se aprecian las ventajas del mismo.

El nuevo codigo es mas mantenible que su version monolıtica ya que se compone de

modulos mas pequenos y la desventaja de disenar un sistema de comunicacion no ha sido

tan grave dada la naturaleza de Erlang que usa procesos ligeros que hay que comunicar al

igual que los microservicios, aunque no se comunican de la misma manera el cambio no

ha sido tan drastico.

Otra ventaja que se ha apreciado ha sido la capacidad de hacer desarrollo independiente,

un desarrollador puede trabajar en cwcomm mientras otro trabaja en coowry-core o cwcorey no hay problemas de coordinacion con el control de versiones.

23

Page 25: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Trabajo Fin de Grado Cesar Guzman Alpızar

8. Referencias[1] C. Richardson, Microservices Patterns. Manning Publications Co., 2019, ISBN: 9781617294549.

[2] T. Ziade, Python Microservices Development. Packt Publishing Ltd., 2017, ISBN:

978-1-78588-111-4.

[3] Chris Richardson, ed. (). What are microservices?, direccion: https://microservices.io/.

[4] .NET Foundation, ed. (). Communication in a microservice architecture, direccion:

https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/architect-microservice-container-applications/communication-in-microservice-architecture#communication-types.

[5] OASIS®, ed. (). AMQP is the Internet Protocol for Business Messaging, direccion:

https://www.amqp.org/about/what.

[6] Pivotal Software, ed. (). Products and Success Stories, direccion: https://www.amqp.org/about/examples.

[7] Pivotal Software, ed. (). RabbitMQ is the most widely deployed open source messa-

ge broker., direccion: https://www.rabbitmq.com/.

[8] Cisco Systems et al, ed. (). AMQP Advanced Message Queuing Protocol, direccion:

https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf.

[9] Plataformatec, ed. (). GenServer, direccion: https://elixir-lang.org/getting-started/mix-otp/genserver.html.

24

Page 26: Graduado en Ingenier´ıa Informatica´ - Archivo Digital UPMoa.upm.es/55739/1/TFG_CESAR_GUZMAN_ALPIZAR.pdf · 2019-07-09 · ca de una empresa real para conseguir una arquitectura

Este documento esta firmado porFirmante CN=tfgm.fi.upm.es, OU=CCFI, O=Facultad de Informatica - UPM,

C=ES

Fecha/Hora Sun Jun 02 20:32:35 CEST 2019

Emisor delCertificado

[email protected], CN=CA Facultad deInformatica, O=Facultad de Informatica - UPM, C=ES

Numero de Serie 630

Metodo urn:adobe.com:Adobe.PPKLite:adbe.pkcs7.sha1 (AdobeSignature)