Migración exitosa a una solución XMPP personalizada
Publicado: 2019-04-08Hablaré sobre los desafíos que enfrentamos al migrar de un chat de terceros a una solución de mensajería personalizada basada en XMPP para nuestro cliente, Forward Health, una solución de atención médica de mensajería con sede en el Reino Unido. Este artículo cubrirá los motivos de la migración, nuestras expectativas frente a las realidades de la implementación y el desafío de crear funcionalidades adicionales.
donde empezamos
Forward Health, nuestro cliente, quería crear una aplicación de comunicaciones móviles para trabajadores de la salud en el Reino Unido, incluida la función de chat. Como startup, querían mostrar su producto de trabajo rápidamente. Al mismo tiempo, la mensajería tenía que ser fiable, robusta y capaz de enviar datos confidenciales de los pacientes de forma segura. Para lograr esto, decidimos utilizar una de las soluciones de terceros disponibles para la función de chat .
La funcionalidad de chat no es algo trivial, especialmente cuando tiene como objetivo apoyar a la industria de la salud. A medida que la aplicación creció, encontramos más casos extremos y algunos errores en el lado de la biblioteca en los que el tercero no estaba dispuesto a trabajar. Además, Forward Health quería agregar nuevas funciones que no eran compatibles con la biblioteca de terceros. Cambiar a una solución personalizada fue el siguiente paso.
Fue entonces cuando empezamos a trabajar con MongooseIM . MIM es una solución de código abierto basada en el protocolo XMPP bien establecido. Trabajamos con una empresa externa, Erlang Solutions Limited, para configurar nuestro backend y brindar asistencia para implementar soluciones personalizadas.
Al principio, todo lo relacionado con la mensajería parecía diferente. Anteriormente, el SDK y su API REST satisfacían todas nuestras necesidades. Ahora, usando MongooseIM, tuvimos que tomarnos un tiempo para comprender la naturaleza de XMPP e implementar nuestro propio SDK . Resultó que el servidor XMPP básico solo pasaba estrofas (mensajes XML) entre clientes en tiempo real. Las estrofas pueden ser de diferentes tipos, es decir, mensajes de chat normales, presencia, solicitudes y respuestas. Se puede agregar una gran variedad de módulos al servidor para, por ejemplo, almacenar mensajes y permitir que los clientes los consulten.
En el lado del cliente (Android, iOS) había algunos SDK de bajo nivel. Desafortunadamente, solo actuaban como una capa que permitía la comunicación con MongooseIM y algunos de sus módulos conectables llamados XEP (protocolo de extensión XMPP responsable, entre otras cosas, de enviar notificaciones automáticas para cada mensaje). Nuestro equipo tuvo que implementar toda la arquitectura para el manejo, almacenamiento y consulta de mensajes.
Lo que vino a nuestro rescate fue la biblioteca de terceros que habíamos usado anteriormente. Tenía una API muy bien pensada, así que hicimos que nuestra solución funcionara de manera similar. Separamos el código específico de XMPP en nuestro SDK interno con la interfaz correspondiente a una de la solución anterior. Esto resultó en solo algunos cambios en el código de nuestra aplicación después de la migración.
Durante la implementación de MongooseIM, nos sorprendieron varias veces los elementos que pensábamos que serían estándar, pero que no estaban disponibles para nosotros, ni siquiera con XEP.
Implementación de características clave del chat basado en XMPP
Marcas de tiempo
Puede pensar, como lo hicimos nosotros, que las marcas de tiempo serían tan simples como "Recibo un mensaje, lo muestro en la interfaz de usuario con una marca de tiempo". No, no es tan fácil. De forma predeterminada, las secciones del mensaje no tienen un campo de marca de tiempo. Afortunadamente para nuestro equipo, XMPP es un protocolo fácilmente extensible. En el backend, implementamos una función personalizada, agregando una marca de tiempo a cada mensaje que pasó a través del servidor MongooseIM. Luego, el destinatario tendría la marca de tiempo adjunta al mensaje.
¿Por qué un remitente no puede agregar una marca de tiempo por sí mismo? Bueno, no sabemos si tienen la hora correcta configurada en su teléfono.
¿Por qué no hay ningún XEP para eso? Tal vez porque XMPP es un protocolo en tiempo real, por lo que, en teoría, todos los mensajes enviados se reciben de inmediato.
EDITAR: Como señaló Florian Schmaus: "En realidad hay uno, aunque puede pasarse por alto fácilmente debido a su confuso nombre: XEP-0203: Entrega retrasada". Agrega una marca de tiempo a un mensaje solo si su entrega se retrasa. De lo contrario, el mensaje fue enviado justo ahora.
Mensajes sin conexión
Cuando ambos usuarios inician sesión en la aplicación, pueden enviarse mensajes en tiempo real. Pero, ¿y si uno de ellos está desconectado? La respuesta rápida es: los mensajes deben almacenarse en el backend . La función de mensajes sin conexión maneja este trabajo y envía todas las estrofas almacenadas en búfer al usuario una vez que vuelve a iniciar sesión.
Pero entonces surgen varias preguntas:
- ¿Por cuánto tiempo se deben almacenar en búfer estos mensajes?
- ¿Cuantos de ellos?
- ¿Deberían volver a enviarse justo después de volver a iniciar sesión? Pero inundará al cliente con los mensajes, ¿no?
- ¿Qué sucede si un usuario solo inicia sesión, pero no ingresa al chat con los nuevos mensajes? ¿Se habrán ido todos?
- ¿Qué pasa si un usuario ha iniciado sesión en varios dispositivos?
Se hizo evidente que la función Mensaje sin conexión solo podía enviar mensajes al primer dispositivo que se volvía a conectar, y esos mensajes se perderían para todos los demás dispositivos. Decidimos descartar esta función y almacenar los mensajes en el backend XMPP de una manera diferente y persistente.

Gestión de archivos de mensajes (MAM)
MAM es almacenamiento en el servidor para mensajes. Cuando un cliente inicia sesión, puede consultar el servidor en busca de mensajes. Puede consultar por páginas, puede consultar por fechas. Es flexible: incluso puede consultar una página antes o después de un mensaje con una identificación específica, agregando filtros para mensajes de la conversación exacta.
Pero aquí está la trampa. Los mensajes de chat normales se almacenan envueltos dentro de los mensajes MAM, que tienen sus propias identificaciones únicas. Cuando un usuario recibe un mensaje de chat en una secuencia, no contiene el ID de MAM. Tienen que consultar el MAM para obtenerlo.
La recuperación de MAM es una solicitud de red, lo que significa que puede llevar un tiempo relativamente largo. Cuando un usuario ingresa a un chat, quiere ver los mensajes de inmediato. Así que también necesitamos una base de datos local .
Cuando un usuario recibe un mensaje en un flujo (un mensaje en línea), lo guardamos en la base de datos local y se lo mostramos al usuario. De esa manera, mostramos mensajes que llegan en tiempo real rápidamente al usuario.
Además, cada vez que ingresan a la pantalla de chat, descargamos todos los mensajes desde ahora al mensaje MAM más nuevo almacenado en la base de datos local para esa conversación y los colocamos en una base de datos, ignorando los duplicados.
Así es como manejamos el almacenamiento de mensajes antiguos. Además, estamos seguros de que en la base de datos hay un conjunto completo de mensajes para una conversación específica entre el primer y el último mensaje de MAM.
Para realizar un seguimiento de los mensajes descargados de MAM, hemos agregado dos propiedades a las entidades de conversación:
- ID de MAM del mensaje MAM más reciente en la base de datos
- MAM id del mensaje MAM más antiguo en la base de datos
El manejo de conjuntos fragmentados de mensajes MAM en una base de datos local sería muy problemático.
Además, tener estas dos propiedades para cada conversación nos permite almacenar mensajes de chat normales en la base de datos mientras ignoramos el contenedor: mensaje MAM. Y cuando el usuario ingresa al chat, podemos mostrar los últimos mensajes de la base de datos y, en segundo plano, recuperar los mensajes faltantes de MAM.
Bandeja de entrada
Cada aplicación basada en chat necesita una pantalla con una lista de chats, un lugar donde puede ver los nombres, los últimos mensajes y un recuento de mensajes no leídos. ¡Debe haber una solución para eso!
En realidad, no hay... Hay algo llamado Roster: puede contener una lista de usuarios etiquetados como "amigos". Desafortunadamente, no hay un último mensaje ni un número de mensajes no leídos adjuntos. Claro, puede obtener la información necesaria del backend en partes. Al principio, queríamos hacerlo de esa manera, pero funcionaría lentamente y sería complicado de hacer. Fue entonces cuando comenzamos a trabajar con Erlang Solutions en la función Bandeja de entrada, que también se está abriendo camino hacia el código abierto.
Cuando un usuario se conecta al backend XMPP, la aplicación obtiene su bandeja de entrada, que contiene todas las conversaciones de ese usuario, tanto individuales como de equipo. Cada uno de ellos tiene el último mensaje adjunto y un recuento de mensajes no leídos. La aplicación guarda toda la bandeja de entrada en la base de datos local. Cuando un usuario está en la aplicación y llega un nuevo mensaje, actualizamos el estado de la bandeja de entrada localmente. De esa manera, la aplicación no necesita buscar la bandeja de entrada para cada mensaje nuevo.
Resumen
Algunas soluciones de chat de terceros proporcionan un alto nivel de abstracción. Esto está bien si desea crear una aplicación de chat simple. Al implementar nuestra propia solución basada en XMPP en la aplicación Forward, pudimos obtener un acceso de bajo nivel mucho mejor, lo que facilitó mucho la resolución de problemas. Claro, tomó algún tiempo, pero ahora sabemos que podemos proporcionar cualquier función personalizada para ayudar a los médicos en el Reino Unido a comunicarse de una manera segura y fácil aprobada por el NHS.
La mensajería tiene que ver con la comunicación de alto rendimiento en tiempo real. Al cambiar a MIM, pudimos optimizar cada parte de la solución para mejorar la velocidad, la confiabilidad y, en última instancia, la confianza. Actualmente, tenemos el código completo, por lo que es fácil rastrearlos. Además, estamos tras la fase de estabilización y una cantidad de informes relacionados con la mensajería han disminuido drásticamente. Los usuarios están contentos de poder confiar en la plataforma.
Diseñar y escribir nuestro propio SDK fue una tarea desafiante y nos gustó. Era algo diferente de las aplicaciones simples en las que necesita obtener datos de un servidor y mostrarlos en la pantalla. Durante la implementación, comprendimos muchas opciones de diseño de la API de biblioteca de terceros que usamos anteriormente. ¿Por qué? Porque nos hemos encontrado con los mismos problemas.