Erfolgreiche Migration zu einer benutzerdefinierten XMPP-Lösung

Veröffentlicht: 2019-04-08

Ich werde Ihnen die Herausforderungen erläutern, mit denen wir bei der Migration von einem Drittanbieter-Chat zu einer benutzerdefinierten XMPP-basierten Messaging-Lösung für unseren Kunden Forward Health, eine in Großbritannien ansässige Messaging-Lösung für das Gesundheitswesen, konfrontiert waren. Dieser Artikel behandelt die Gründe für die Migration, unsere Erwartungen im Vergleich zu den Realitäten der Implementierung und die Herausforderung, zusätzliche Funktionen zu entwickeln.

Wo wir angefangen haben

Forward Health, unser Kunde, wollte eine mobile Kommunikationsanwendung für medizinisches Personal in Großbritannien entwickeln, einschließlich Chat-Funktion. Als Startup wollten sie ihr funktionierendes Produkt schnell zeigen. Gleichzeitig musste das Messaging zuverlässig und robust sein und sensible Patientendaten sicher versenden können. Um dies zu erreichen, haben wir uns entschieden, eine der verfügbaren Lösungen von Drittanbietern für die Chat-Funktionalität zu verwenden .

Die Chat-Funktionalität ist keine triviale Sache, insbesondere wenn sie darauf abzielt, die Gesundheitsbranche zu unterstützen. Als die App wuchs, stießen wir auf weitere Grenzfälle und einige Fehler auf der Bibliotheksseite, an denen der Drittanbieter nicht arbeiten wollte. Darüber hinaus wollte Forward Health neue Funktionen hinzufügen, die von der Bibliothek des Drittanbieters nicht unterstützt werden. Der Wechsel zu einer individuellen Lösung war der nächste Schritt.

Zu diesem Zeitpunkt begannen wir mit MongooseIM zu arbeiten. MIM ist eine Open-Source-Lösung, die auf dem etablierten XMPP-Protokoll basiert. Wir haben mit einem externen Unternehmen, Erlang Solutions Limited, zusammengearbeitet, um unser Backend einzurichten und Unterstützung bei der Implementierung kundenspezifischer Lösungen zu bieten.

MongooseIM-Dashboard

Am Anfang schien alles über Messaging anders zu sein. Zuvor wurden alle unsere Anforderungen vom SDK und seiner REST-API erfüllt. Jetzt mussten wir uns bei der Verwendung von MongooseIM etwas Zeit nehmen, um die Natur von XMPP zu verstehen und unser eigenes SDK zu implementieren . Es stellte sich heraus, dass der „Bare Bone“-XMPP-Server in Echtzeit nur Stanzas (XML-Nachrichten) zwischen Clients weitergab. Stanzas können unterschiedlicher Art sein, dh normale Chat-Nachrichten, Anwesenheit, Anfragen und Antworten. Dem Server kann eine Vielzahl von Modulen hinzugefügt werden, um beispielsweise Nachrichten zu speichern und von den Clients abfragen zu lassen.

Auf der Client-Seite (Android, iOS) gab es einige Low-Level-SDKs. Leider fungierten sie nur als eine Ebene, die die Kommunikation mit MongooseIM und einigen seiner Plug-in-Module namens XEPs (XMPP Extension Protocol, das unter anderem für das Senden von Push-Benachrichtigungen für jede Nachricht verantwortlich ist) ermöglichte. Die gesamte Architektur für die Nachrichtenbehandlung, das Speichern und Abfragen von Nachrichten musste von unserem Team implementiert werden.

Was uns zu Hilfe kam, war die Bibliothek eines Drittanbieters , die wir zuvor verwendet hatten. Es hatte eine sehr gut durchdachte API, also haben wir unsere Lösung auf ähnliche Weise funktionieren lassen. Wir haben XMPP-spezifischen Code in unser internes SDK getrennt, wobei die Schnittstelle einer der vorherigen Lösung entspricht. Dies führte nach der Migration zu nur wenigen Änderungen in unserem Anwendungscode.

Während der Implementierung von MongooseIM wurden wir mehrmals von Elementen überrascht, die wir für Standard hielten, die uns aber nicht einmal von XEP zur Verfügung standen.

Implementieren von Schlüsselfunktionen des XMPP-basierten Chats

Zeitstempel

Sie denken vielleicht, wie wir es getan haben, dass Zeitstempel so einfach wären wie „Ich bekomme eine Nachricht, ich zeige diese auf der Benutzeroberfläche mit einem Zeitstempel an“. Nö, nicht so einfach. Standardmäßig haben die Nachrichtenzeilen kein Zeitstempelfeld. Zum Glück für unser Team ist XMPP ein leicht erweiterbares Protokoll. Im Backend haben wir eine benutzerdefinierte Funktion implementiert, die jeder Nachricht, die den MongooseIM-Server passiert, einen Zeitstempel hinzufügt. Dann würde der Empfänger den Zeitstempel an die Nachricht anhängen.

Warum konnte ein Absender nicht selbst einen Zeitstempel hinzufügen? Nun, wir wissen nicht, ob sie die richtige Uhrzeit auf ihrem Telefon eingestellt haben.

Warum gibt es dafür kein XEP? Vielleicht, weil XMPP ein Echtzeitprotokoll ist, also theoretisch jede gesendete Nachricht sofort ankommt.

EDIT: Wie Florian Schmaus betonte: „Es gibt tatsächlich einen, obwohl er aufgrund seines verwirrenden Namens leicht übersehen werden kann: XEP-0203: Delayed Delivery.“ Es fügt einer Nachricht nur dann einen Zeitstempel hinzu, wenn sich ihre Zustellung verzögert. Andernfalls wurde die Nachricht gerade jetzt gesendet.

Offline-Nachrichten

Wenn beide Benutzer bei der Anwendung angemeldet sind, können sie sich gegenseitig Nachrichten in Echtzeit senden. Aber was ist, wenn einer von ihnen offline ist? Die schnelle Antwort lautet: Nachrichten müssen im Backend gepuffert werden . Die Offline-Nachrichtenfunktion übernimmt diese Arbeit und sendet alle gepufferten Zeilen an den Benutzer, sobald er sich wieder anmeldet.

Doch dann stellen sich mehrere Fragen:

  • Wie lange sollen diese Nachrichten gepuffert werden?
  • Wie viele davon?
  • Sollten sie direkt nach dem erneuten Einloggen erneut gesendet werden? Aber es wird den Client mit den Nachrichten überschwemmen, nicht wahr?
  • Was ist, wenn sich ein Benutzer nur anmeldet, aber den Chat nicht mit den neuen Nachrichten betritt? Werden sie alle weg sein?
  • Was passiert, wenn ein Benutzer auf mehreren Geräten angemeldet ist?

Es stellte sich heraus, dass die Offline-Nachrichtenfunktion nur Nachrichten an das erste Gerät senden konnte, das wieder online war, und diese Nachrichten dann für alle anderen Geräte verloren gingen. Wir haben uns entschieden, diese Funktion zu verwerfen und die Nachrichten auf dem XMPP-Backend auf eine andere, dauerhafte Weise zu speichern.

Nachrichtenarchivverwaltung (MAM)

MAM ist ein Serverspeicher für Nachrichten. Wenn ein Client angemeldet ist, kann er den Server nach Nachrichten abfragen. Sie können nach Seiten abfragen, Sie können nach Daten abfragen. Es ist flexibel – Sie können sogar vor oder nach einer Nachricht mit einer bestimmten ID nach einer Seite suchen und Filter für Nachrichten aus der genauen Konversation hinzufügen.

Aber hier ist der Haken. Normale Chat-Nachrichten werden in MAM-Nachrichten verpackt gespeichert, die ihre eigenen eindeutigen IDs haben. Wenn ein Benutzer eine Chat-Nachricht in einem Stream erhält, enthält diese nicht die MAM-ID. Sie müssen das MAM abfragen, um es zu bekommen.

Das Abrufen von MAM ist eine Netzwerkanfrage, was bedeutet, dass es relativ lange dauern kann. Wenn ein Benutzer einen Chat betritt, möchte er sofort Nachrichten sehen. Wir brauchen also auch eine lokale Datenbank .

Wenn ein Benutzer eine Nachricht in einem Stream erhält (eine Online-Nachricht), speichern wir sie in der lokalen Datenbank und zeigen sie dem Benutzer an. Auf diese Weise zeigen wir dem Benutzer schnell Nachrichten an, die in Echtzeit eintreffen.

Außerdem laden wir jedes Mal, wenn sie den Chat-Bildschirm betreten, alle Nachrichten von jetzt auf die neueste MAM-Nachricht herunter, die in der lokalen Datenbank für diese Konversation gespeichert ist, und legen sie in einer Datenbank ab, wobei Duplikate ignoriert werden.

So handhaben wir das Speichern alter Nachrichten. Außerdem sind wir sicher, dass in der Datenbank ein vollständiger Satz von Nachrichten für eine bestimmte Konversation zwischen der ersten und der letzten Nachricht von MAM vorhanden ist.

Um die von MAM heruntergeladenen Nachrichten zu verfolgen, haben wir zwei Eigenschaften zu Konversationsentitäten hinzugefügt:

  1. MAM-ID der neuesten MAM-Nachricht in der Datenbank
  2. MAM-ID der ältesten MAM-Nachricht in der Datenbank

Die Handhabung zersplitterter Sätze von MAM-Nachrichten in einer lokalen Datenbank wäre sehr problematisch.

Wenn wir diese beiden Eigenschaften für jede Konversation haben, können wir außerdem normale Chat-Nachrichten in der Datenbank speichern, während wir die Wrapper-MAM-Nachricht ignorieren. Und wenn der Benutzer den Chat betritt, können wir die neuesten Nachrichten aus der Datenbank anzeigen und im Hintergrund die fehlenden Nachrichten von MAM abrufen.

Posteingang

Jede Chat-basierte App benötigt einen Bildschirm mit einer Liste von Chats – einen Ort, an dem Sie Namen, letzte Nachrichten und die Anzahl der ungelesenen Nachrichten sehen können. Dafür muss es doch eine Lösung geben!

Eigentlich gibt es nicht ... Es gibt etwas namens Roster - es kann eine Liste von Benutzern enthalten, die als "Freunde" gekennzeichnet sind. Leider ist ihnen weder die letzte Nachricht noch die Anzahl der ungelesenen Nachrichten beigefügt. Sicher, Sie können die benötigten Informationen in Stücken aus dem Backend abrufen. Anfangs wollten wir es so machen, aber es würde langsam funktionieren und kompliziert sein. Zu diesem Zeitpunkt begannen wir mit Erlang Solutions an der Inbox-Funktion zu arbeiten, die sich ebenfalls auf dem Weg zu Open Source befindet.

XMPP-basierter Chat - Posteingangsbildschirm
Posteingangsbildschirm – Alle Chatdaten werden von der Posteingangsfunktion bereitgestellt

Wenn sich ein Benutzer mit dem XMPP-Backend verbindet, ruft die App seinen Posteingang ab, der alle Konversationen dieses Benutzers enthält – sowohl Einzel- als auch Team-Chats. Jedem von ihnen ist die letzte Nachricht angehängt und die Anzahl der ungelesenen Nachrichten. Die Anwendung speichert den gesamten Posteingang in der lokalen Datenbank. Wenn sich ein Benutzer in der App befindet und eine neue Nachricht eintrifft, aktualisieren wir den Posteingangsstatus lokal. Auf diese Weise muss die App nicht für jede neue Nachricht den Posteingang abrufen.

Zusammenfassung

Einige Chat-Lösungen von Drittanbietern bieten ein hohes Maß an Abstraktion. Dies ist in Ordnung, wenn Sie eine einfache Chat-Anwendung erstellen möchten. Durch die Implementierung unserer eigenen XMPP-basierten Lösung in der Forward-App konnten wir einen weitaus besseren Low-Level-Zugriff erhalten, was die Lösung von Problemen erheblich vereinfachte. Sicher, es hat einige Zeit gedauert, aber jetzt wissen wir, dass wir jede benutzerdefinierte Funktion bereitstellen können, die Ärzten in Großbritannien dabei hilft, auf sichere und einfache Weise zu kommunizieren, die vom NHS genehmigt wurde.

Bei Messaging dreht sich alles um Hochleistungskommunikation in Echtzeit. Durch den Wechsel zu MIM konnten wir jeden Teil der Lösung optimieren, um Geschwindigkeit, Zuverlässigkeit und letztendlich Vertrauen zu verbessern. Derzeit haben wir den gesamten Code, sodass wir sie leicht aufspüren können. Außerdem haben wir die Stabilisierungsphase hinter uns und eine Reihe von Meldungen im Zusammenhang mit Messaging sind drastisch zurückgegangen. Die Benutzer sind zufrieden damit, der Plattform vertrauen zu können.

Unser eigenes SDK zu entwerfen und zu schreiben war eine herausfordernde Aufgabe und wir mochten es. Es war etwas anderes als einfache Anwendungen, bei denen Sie Daten von einem Server abrufen und auf dem Bildschirm anzeigen müssen. Während der Implementierung haben wir viele Designentscheidungen der Bibliotheks-API von Drittanbietern verstanden, die wir zuvor verwendet haben. Wieso den? Weil wir auf die gleichen Probleme gestoßen sind.