Migrazione riuscita a una soluzione XMPP personalizzata

Pubblicato: 2019-04-08

Ti parlerò delle sfide che abbiamo dovuto affrontare migrando da una chat di terze parti a una soluzione di messaggistica personalizzata basata su XMPP per il nostro cliente, Forward Health, una soluzione sanitaria di messaggistica con sede nel Regno Unito. Questo articolo tratterà i motivi della migrazione, le nostre aspettative rispetto alla realtà dell'implementazione e la sfida della creazione di funzionalità aggiuntive.

Da dove abbiamo iniziato

Forward Health, il nostro cliente, desiderava creare un'applicazione di comunicazione mobile per gli operatori sanitari nel Regno Unito, inclusa la funzionalità di chat. Come startup, volevano mostrare rapidamente il loro prodotto funzionante. Allo stesso tempo, la messaggistica doveva essere affidabile, robusta e in grado di inviare in modo sicuro i dati sensibili dei pazienti. Per raggiungere questo obiettivo, abbiamo deciso di utilizzare una delle soluzioni di terze parti disponibili per la funzionalità di chat .

La funzionalità di chat non è una cosa banale, soprattutto quando mira a supportare il settore sanitario. Con la crescita dell'app, abbiamo riscontrato più casi limite e alcuni bug sul lato libreria su cui la terza parte non era disposta a lavorare. Inoltre, Forward Health voleva aggiungere nuove funzionalità che non erano supportate dalla libreria di terze parti. Il passaggio a una soluzione personalizzata è stato il passaggio successivo.

È allora che abbiamo iniziato a lavorare con MongooseIM . MIM è una soluzione open source basata sul consolidato protocollo XMPP. Abbiamo collaborato con una società esterna Erlang Solutions Limited per configurare il nostro back-end e fornire supporto con l'implementazione di soluzioni personalizzate.

Dashboard di MongooseIM

All'inizio, tutto ciò che riguardava la messaggistica sembrava diverso. In precedenza, tutte le nostre esigenze erano soddisfatte dall'SDK e dalla relativa API REST. Ora, usando MongooseIM, abbiamo dovuto prenderci del tempo per comprendere la natura di XMPP e implementare il nostro SDK . Si è scoperto che il server XMPP "bare bones" ha passato solo stanze (messaggi XML) tra i client in tempo reale. Le stanze possono essere di diverso tipo, ovvero normali messaggi di chat, presenza, richieste e risposte. È possibile aggiungere una vasta gamma di moduli al server, ad esempio, per archiviare messaggi e consentire ai client di interrogarli.

Sul lato client (Android, iOS) c'erano alcuni SDK di basso livello. Sfortunatamente, agivano solo come un livello che consentiva la comunicazione con MongooseIM e alcuni dei suoi moduli collegabili chiamati XEPs (XMPP Extension Protocol responsabile, tra le altre cose, dell'invio di notifiche push per ogni messaggio). L'intera architettura per la gestione dei messaggi, la memorizzazione e l'interrogazione dei messaggi doveva essere implementata dal nostro team.

Ciò che è venuto in nostro soccorso è stata la libreria di terze parti che avevamo utilizzato in precedenza. Aveva un'API molto ben congegnata, quindi abbiamo fatto in modo che la nostra soluzione funzionasse in modo simile. Abbiamo separato il codice specifico di XMPP nel nostro SDK interno con l'interfaccia corrispondente a quella della soluzione precedente. Ciò ha comportato solo poche modifiche al codice dell'applicazione dopo la migrazione.

Durante l'implementazione di MongooseIM, siamo stati sorpresi più volte da elementi che pensavamo sarebbero stati standard, ma non erano disponibili per noi, nemmeno da XEP.

Implementazione delle funzionalità chiave della chat basata su XMPP

Timestamp

Potresti pensare, come abbiamo fatto noi, che i timestamp sarebbero semplici come "Ricevo un messaggio, lo visualizzo sull'interfaccia utente con un timestamp". No, non così facile. Per impostazione predefinita, le stanze dei messaggi non hanno un campo timestamp. Fortunatamente per il nostro team, XMPP è un protocollo facilmente estensibile. Sul back-end, abbiamo implementato una funzionalità personalizzata, aggiungendo un timestamp a ogni messaggio che è passato attraverso il server MongooseIM. Quindi il destinatario avrebbe il timestamp allegato al messaggio.

Perché un mittente non può aggiungere un timestamp da solo? Non sappiamo se hanno impostato l'ora corretta sul telefono.

Perché non c'è alcun XEP per quello? Forse perché XMPP è un protocollo in tempo reale, quindi teoricamente ogni messaggio inviato viene ricevuto immediatamente.

EDIT: Come ha sottolineato Florian Schmaus: "In realtà ce n'è uno, anche se può essere facilmente perso a causa del suo nome confuso: XEP-0203: Consegna ritardata". Aggiunge un timestamp a un messaggio solo se la sua consegna è ritardata. In caso contrario, il messaggio è stato inviato proprio ora.

Messaggi offline

Quando entrambi gli utenti hanno effettuato l'accesso all'applicazione, possono scambiarsi messaggi in tempo reale. Ma cosa succede se uno di loro è offline? La risposta rapida è: i messaggi devono essere memorizzati nel buffer sul back-end . La funzione dei messaggi offline gestisce questo lavoro e invia tutte le stanze memorizzate nel buffer all'utente una volta che ha eseguito nuovamente l'accesso.

Ma poi sorgono diverse domande:

  • Per quanto tempo devono essere memorizzati nel buffer questi messaggi?
  • Quanti di loro?
  • Devono essere rispediti subito dopo aver effettuato nuovamente l'accesso? Ma inonderà il client di messaggi, vero?
  • Cosa succede se un utente effettua solo l'accesso, ma non entra nella chat con i nuovi messaggi. Se ne andranno tutti?
  • Cosa succede se un utente ha effettuato l'accesso su più dispositivi?

È diventato evidente che la funzione Messaggi offline era in grado di inviare messaggi solo al primo dispositivo che tornava online e che quei messaggi sarebbero andati persi per tutti gli altri dispositivi. Abbiamo deciso di eliminare questa funzione e di archiviare i messaggi sul backend XMPP in un modo diverso e persistente.

Gestione Archivio messaggi (MAM)

MAM è l'archiviazione sul server per i messaggi. Quando un client è connesso, può interrogare il server per i messaggi. È possibile eseguire query per pagine, è possibile eseguire query per date. È flessibile: puoi persino cercare una pagina prima o dopo un messaggio con un ID specifico, aggiungendo filtri per i messaggi dalla conversazione esatta.

Ma ecco il trucco. I normali messaggi di chat vengono archiviati all'interno di messaggi MAM, che hanno i propri ID univoci. Quando un utente riceve un messaggio di chat in uno stream, non contiene l'ID MAM. Devono interrogare il MAM per ottenerlo.

Il recupero da MAM è una richiesta di rete, il che significa che può richiedere un tempo relativamente lungo. Quando un utente entra in una chat, vuole vedere i messaggi immediatamente. Quindi abbiamo anche bisogno di un database locale .

Quando un utente riceve un messaggio in uno stream (un messaggio online), lo salviamo nel database locale e lo mostriamo all'utente. In questo modo, visualizziamo messaggi che arrivano rapidamente in tempo reale all'utente.

Inoltre, ogni volta che entrano nella schermata della chat, scarichiamo tutti i messaggi da ora nel messaggio MAM più recente archiviato nel DB locale per quella conversazione e li inseriamo in un database, ignorando i duplicati.

Questo è il modo in cui gestiamo la memorizzazione dei vecchi messaggi. Inoltre, siamo sicuri che nel database sia presente un set completo di messaggi per una specifica conversazione tra il primo e l'ultimo messaggio di MAM.

Per tenere traccia dei messaggi scaricati da MAM, abbiamo aggiunto due proprietà alle entità di conversazione:

  1. ID MAM del messaggio MAM più recente nel database
  2. ID MAM del messaggio MAM più vecchio nel database

La gestione di insiemi frantumati di messaggi MAM in un database locale sarebbe molto problematica.

Inoltre, avere queste due proprietà per ogni conversazione ci consente di archiviare i normali messaggi di chat nel database ignorando il wrapper - messaggio MAM. E quando l'utente entra nella chat, possiamo mostrare gli ultimi messaggi dal database e in background recuperare i messaggi mancanti da MAM.

Posta in arrivo

Ogni app basata su chat ha bisogno di una schermata con un elenco di chat, un luogo in cui puoi vedere i nomi, gli ultimi messaggi e il conteggio dei messaggi non letti. Ci deve essere una soluzione a questo!

In realtà, non c'è... C'è qualcosa chiamato Roster: può contenere un elenco di utenti etichettati come "amici". Sfortunatamente, non c'è l'ultimo messaggio, né il conteggio dei messaggi non letti allegati. Certo, puoi ottenere le informazioni necessarie dal back-end a pezzi. All'inizio volevamo farlo in quel modo, ma avrebbe funzionato lentamente e sarebbe stato complicato da fare. È stato allora che abbiamo iniziato a lavorare con Erlang Solutions sulla funzione Inbox, che si sta facendo strada anche verso l'open source.

Chat basata su XMPP - schermata della posta in arrivo
Schermata Posta in arrivo: tutti i dati delle chat sono forniti dalla funzione Posta in arrivo

Quando un utente si connette al back-end XMPP, l'app recupera la posta in arrivo, che contiene tutte le conversazioni di quell'utente, sia le chat individuali che quelle di gruppo. Ognuno di loro ha l'ultimo messaggio allegato e un conteggio di messaggi non letti. L'applicazione salva l'intera posta in arrivo nel database locale. Quando un utente è nell'app e arriva un nuovo messaggio, aggiorniamo lo stato della posta in arrivo in locale. In questo modo l'app non ha bisogno di recuperare la posta in arrivo per ogni nuovo messaggio.

Riepilogo

Alcune soluzioni di chat di terze parti forniscono un alto livello di astrazione. Questo va bene se vuoi creare una semplice applicazione di chat. Implementando la nostra soluzione basata su XMPP nell'app Forward, siamo stati in grado di ottenere un accesso di basso livello molto migliore, il che ha reso molto più semplice la risoluzione dei problemi. Certo, ci è voluto del tempo, ma ora sappiamo che possiamo fornire qualsiasi funzionalità personalizzata per aiutare i medici nel Regno Unito a comunicare in modo semplice e sicuro approvato dal SSN.

La messaggistica è tutta una questione di alte prestazioni, comunicazione in tempo reale. Passando a MIM siamo stati in grado di ottimizzare ogni parte della soluzione per migliorare la velocità, l'affidabilità e, in definitiva, la fiducia. Al momento, abbiamo l'intero codice, quindi è facile rintracciarli. Inoltre, siamo alla fine della fase di stabilizzazione e un numero di segnalazioni legate alla messaggistica è drasticamente diminuito. Gli utenti sono felici di potersi fidare della piattaforma.

Progettare e scrivere il nostro SDK è stato un compito impegnativo e ci è piaciuto. Era qualcosa di diverso dalle semplici applicazioni in cui è necessario recuperare i dati da un server e mostrarli sullo schermo. Durante l'implementazione, abbiamo compreso molte scelte di progettazione dell'API della libreria di terze parti che abbiamo utilizzato in precedenza. Come mai? Perché abbiamo riscontrato gli stessi problemi.