Estoy en pleno desarrollo de un producto para comercializar en el Grupo Ultra. Es primera vez que arquitecto una solución en la que interactúan el segmento consumidor por lo que incluye una gran variable nueva que nunca antes tuve que considerar: la complejidad de la red de acceso del usuario final; los diferentes mecanismos de seguridad con los que los usuarios se encuentran protegidos presenta un reto para la comunicación de eventos entre el servidor y los clientes.
Adicionalmente, como se verá en el desarrollo de este documento, hoy en día el paradigma de comunicación de mensajes entre sistemas ha evolucionado con el sutil cambio de nombre, de «Comunicación de mensajes» a «Comunicación de Eventos». Y la gran diferencia es la inmediatez en el acceso a los mensajes (perdón, Eventos) por parte del receptor.
Evalúo aquí una lista corta de tecnologías, considerando que cualquier otra es competencia de alguna de estas. Específicamente Pusher, RabbitMQ, Kafka, AWS IOT Core, AWS SQS y AWS SNS.
Por último, al inicio comparto una breve sección que he llamado «Prefacio» en la que describo la historia que respalda mis criterios para realizar esta evaluación. Siéntanse libres de saltársela e ir directo al grano en la sección de Arquitectura
Espero que compartir mi evaluación y conclusión sea de utilidad para la comunidad. Quedo pendiente de contestar cualquier pregunta que tengan a través del mecanismo de comentarios.
Prefacio
En el año 2002 creé mi primera plataforma de mensajería transaccional y fue para un emprendiemiento que fundé llamado Tedexis (inicialmente Twindivision) con el fin de intermediar mensajes de texto SMS entre aplicaciones y usuarios. Utilicé como infraestructura iMQ, el MOM (Message Oriented Middleware) de Sun Microsystems; actualmente es OpenMQ administrado por la fundación Eclipse.
Implementé una arquitectura con enrutamiento de colas que distribuían el trabajo a nodos especializados que manejaban distintas tecnologías cada uno; gozaban de la garantía de persistencia y seguridad transaccional que brindaba iMQ, y por arquitectura podía escalar horizontalmente pudiendo manejar millones y millones de mensajes agregando capacidad de cómputo de bajo costo.
Durante las primera dos décadas de este siglo diseñé muchas arquitecturas de integración empresarial de aplicaciones (EAI) que si bien estaban basados en servicios web (SOAP), siempre se planteaban sobre un transporte persistente como JMS (estándar Java para MOMs); ambientes con más de 200 servicios (hoy serían llamados microservicios) que gozaban de la persistencia de SOAP/JMS para desacoplarse.
Hoy 20 años después, me encuentro con la necesidad de definir el mecanismo y herramientas para la integración de potenciales miles de nodos «cliente» que deben comunicarse con un cluster de servidores que manejan lógica de negocio asociado a la operación de los clientes. Y como es de esperar, lo primero que me viene a la mente es aplicar la misma arquitectura de integración vía MOM que me me ha servido para ambientes de mucho volumen y misión crítica.
Sin embargo, estoy consciente de la evolución de la tecnología; han aparecido nuevas herramientas que más allá de desafiar las implementaciones de los MOM tradicionales, transforman las complejidades de operación y costos de su uso (por la existencia de soluciones PAAS) y hasta evolucionan el paradigma para ofrecer capacidades funcionales que no se tenían antes.
Entonces, me resulta necesario revisar el estado del arte respecto a tecnología de gestión de mensajes, para tener así la información necesaria para tomar una decisión sobre qué tecnología usar, y sobre todo tener claro para qué casos de uso requeriría cuál otra.
Arquitectura
El caso de uso es simple; se tienen tópicos (o colas) para los cuales existen productores, que publican mensajes en el tópico, y consumidores, que al suscribirse a algún tópico reciben cada mensaje en la medida que sean publicados.
Es importante resaltar que tanto los productores como los consumidores pueden encontrarse con acceso intermitente y escaso a la red; adicionalmente la red puede encontrarse filtrada por cortafuegos ya sean dispositivos de red o software en el equipo donde funcionan.
Un rasgo particular derivado de la forma como el producto debe ser configurado por los usuarios, es que varios clientes deben compartir la misma credencial.
Front-end de Websockets
Una de las cosas más importante de esta investigación es haber descubierto que la comunicación entre el servidor y los clientes DEBE ser a través de Websockets. Los websockets tienen las siguientes características que por un lado son FUNDAMENTALES para llegar de manera masiva al consumidor, y por el otro ninguna plataforma la soporta de manera nativa excepto AWS IoT Core:
- Establecen un canal de comunicación bi-direccional
- Al usar el protocolo HTTP como iniciador de la sesión, es inmune a filtros básicos de tráfico no web.
De usar un protocolo diferente al Websocket, habrá problemas con cliente que no podrán conectarse por filtros básicos de seguridad.
Criterios
Mi evaluación considera las siguientes variables:
Persistencia
Es fundamental que cualquier mensaje que sea publicado en la plataforma pueda permanecer en espera por largo tiempo en caso de que el consumidor (o consumidores) no puedan procesarlo. Y al decir “pueda” me refiero a que los productores puedan definir si este es el comportamiento esperado o no para cada mensaje publicado.
Este paradigma es llamado “Store and Forward”, del ingles “Almacena y reenvía”.
Instantaneidad
La principal diferencia que tienen las plataformas de mensajería modernas respecto a las existentes 20 años atrás es la dinámica de entrega.
Las plataformas actuales se basan en el paradigma de PUSH de eventos, lo cual consigue el consumo de mensajes de manera más instantánea.
El PUSH de eventos hace referencia a que la plataforma de gestión de eventos contacta e inicia el servicio de recepción del consumidor para cada evento.
El paradigma de PULL de eventos se refiere al modelo en el que el consumidor “busca” en la plataforma de gestión de eventos si existe algún evento nuevo para él.
Esto implica que el consumidor debe hace “la consulta” constantemente a través de iteraciones con algún tiempo de espera entre cada iteración (Polling). Esto retrasa el tiempo en el que el consumidor tiene acceso a los eventos.
Entonces, es necesario que la plataforma seleccionada soporte la entrega de mensajes a través del paradigma PUSH.
Protocolos de comunicación
En el pasado teníamos protocolos propietarios como RMI (de Java), SunRPC (de Ansi C POSIX), IIOP (de Corba) y finalmente SOAP de webservices.
Hoy existen nuevos protocolos como el Websocket para interacciones vía web, y otros especializados para comunicación con dispositivos de la Internet de las Cosas (IoT) como MQTT, AMQP, etc.
Simplicidad de operación
En el pasado usar cualquier tecnología implicaba comprar licencias, conseguir servidores, instalar, configurar y operar atendiendo mantenimientos, etc. Hoy en día este tipo de herramientas están disponibles bajo modelos de Software como Servicio, en el que uno usa la plataforma pagando por lo que usa, sin ninguna preocupación de lo antes indicado.
Personalización
La mayoría de las tecnologías no soportan la interacción con los clientes vía Websockets, lo cual es fundamental. Así que es necesario considerar para aquellas que no lo soportan, un esfuerzo de personalización que exponga o integre la plataforma con websockets.
Costo
Este criterio no requiere de presentación. Menos es mejor.
Tecnologías
Mi análisis se concentró en las siguientes plataformas:
- Pusher
- RabbitMQ
- Apache Kafka
- AWS IOT Core
- AWS SQS
- AWS SNS
Comparación
Adicionalmente a la comparación que resumo en el siguiente cuadro, realicé una prueba de concepto con cada una de ellas excepto Pusher, en Java. Implementé un agente «servidor» y otro «cliente», logrando la comunicación tal como cada tecnología promete.
| NOMBRE | PERSIST. | INSTANT. | PROT. | OPS | CUST | Costo | NOTA |
| Pusher | No | PUSH | Propietario | 3ero | No | $$ | Vendor lock-in |
| RabbitMQ | Si | PUSH | MQTT | Ambas opciones | Si | $$$ | |
| Kafka | Si | PULL | Propietario | Ambas opciones | No | $$ | Existen «conectores» que expone vía Websockets |
| AWS IoT Core | No | PUSH | Websockets | 3ero | No | $ | No permite más de 1 conexión con una misma credencial. |
| AWS SQS | Si | PULL | Propietario | 3ero | Si | $ | Requiere desarrollo del front-end Websockets |
| AWS SNS | No | PUSH | Propietario | 3ero | Si | $ | Requiere desarrollo del front-end Websockets |
Análisis
De partida elimino AWS IoT Core de la evaluación por tres (3) razones:
- No permite que más de una conexión compartan la misma credencial; restricción que sólo aplica a mi caso de uso.
- La arquitectura de seguridad que exige AWS para poner a funcionar IoT más allá de tener una complejidad importante, exige que de requerir un control de identificación y autenticación dinámicos sin uso de certificados (como mi caso) este control debe ser implementado por Cognito. Esto implica que un componente con lógica de negocio deje de estar del lado de la aplicación, y quizás para otro arquitecto sea tan importante como para mi. En la medida que lógica de negocio esté manejada por servicios de infraestructura, mayor la dependencia que se genera de ese proveedor.
- Es muy compleja la configuración de la arquitectura de seguridad. Esto lo hace costoso para administrar, lo cual quiero evitar.
Entonces, como primera conclusión, queda AWS IoT descartada.
De todas las plataformas restantes, el único que cumple con las características obligatorias de persistencia (PERSIST=Si) e instantaneidad (INSTANT=PUSH) es RabbitMQ.
Sin embargo, dado que en general habría que desarrollar el front-end de Websockets, ya que estamos integrando las plataformas con el exterior, podríamos incluir lógica de integración de más una plataforma. De la misma forma como en POSIX la premisa es de tener componentes simples que son integrados entre si para lograr un objetivo complejo (ej. «ls -ef | grep java»), se puede aplicar la misma filosofía a componentes como los servicios de AWS.
Entonces, ¿ayudaría de algo integrar SNS y SQS? Y la respuesta es SI!
El front-end de Websockets puede recibir los eventos PUSH desde SNS, y en caso de que el dispositivo cliente no esté disponible, guardarlo en una cola de SQS para reenviar posteriormente (Store and Forward). En este caso el modelo PULL de SQS deja de ser relevante puesto que la entrega de los mensajes almacenados en SQS serían realizados cuando el cliente se conecte, quien recibiría los mensajes pendientes de manera estructuralmente retrasados.
Entonces, queda la pregunta final: RabbitMQ? o SNS+SQS? Si uno ve el vector de costos, queda más que claro que el ganador sin discusión es SNS+SQS.
Conclusión
Vamos a usar SNS+SQS integrado por el front-end de websockets que vamos a desarrollar. Esta arquitectura nos dará lo mejor de ambos mundos, con la mejor escalabilidad, precio y facilidad de operación.
Espero que esta evaluación le sea de utilidad.
