Udana migracja do niestandardowego rozwiązania XMPP
Opublikowany: 2019-04-08Opowiem Ci o wyzwaniach, z jakimi musieliśmy się zmierzyć podczas migracji z czatu innej firmy do niestandardowego rozwiązania komunikacyjnego opartego na XMPP dla naszego klienta, Forward Health, rozwiązania do przesyłania wiadomości z siedzibą w Wielkiej Brytanii. W tym artykule omówimy przyczyny migracji, nasze oczekiwania a realia wdrożeniowe oraz wyzwanie budowania dodatkowych funkcjonalności.
Gdzie zaczęliśmy
Forward Health, nasz klient, chciał zbudować aplikację do komunikacji mobilnej dla pracowników służby zdrowia w Wielkiej Brytanii, w tym funkcję czatu. Jako startup chcieli szybko pokazać działający produkt. Jednocześnie komunikaty musiały być niezawodne, solidne i umożliwiać bezpieczne przesyłanie poufnych danych pacjenta. Aby to osiągnąć, postanowiliśmy skorzystać z jednego z dostępnych rozwiązań innych firm do obsługi czatu .
Funkcjonalność czatu nie jest rzeczą trywialną, zwłaszcza gdy ma wspierać branżę opieki zdrowotnej. Wraz z rozwojem aplikacji napotkaliśmy więcej przypadków brzegowych i kilka błędów po stronie biblioteki, nad którymi firma zewnętrzna nie chciała pracować. Ponadto firma Forward Health chciała dodać nowe funkcje, które nie były obsługiwane przez bibliotekę innej firmy. Kolejnym krokiem było przejście na rozwiązanie niestandardowe.
Właśnie wtedy rozpoczęliśmy współpracę z MongooseIM . MIM to rozwiązanie typu open source oparte na uznanym protokole XMPP. Współpracowaliśmy z zewnętrzną firmą Erlang Solutions Limited, aby skonfigurować nasz backend i zapewnić wsparcie przy wdrażaniu niestandardowych rozwiązań.
Na początku wszystko, co dotyczyło przesyłania wiadomości, wydawało się inne. Wcześniej wszystkie nasze potrzeby spełniał SDK i jego REST API. Teraz, używając MongooseIM, musieliśmy poświęcić trochę czasu na zrozumienie natury XMPP i zaimplementowanie naszego własnego SDK . Okazało się, że serwer XMPP „nagich kości” przekazywał tylko sekcje (komunikaty XML) między klientami w czasie rzeczywistym. Zwrotki mogą być różnego rodzaju, tj. zwykłe wiadomości na czacie, obecność, prośby i odpowiedzi. Do serwera można dodać szeroką gamę modułów, na przykład w celu przechowywania wiadomości i umożliwienia klientom wysyłania do nich zapytań.
Po stronie klienta (Android, iOS) było kilka niskopoziomowych SDK. Niestety, działały one jedynie jako warstwa, która umożliwiała komunikację z MongooseIM i niektórymi jego wtykanymi modułami zwanymi XEPs (XMPP Extension Protocol odpowiedzialny m.in. za wysyłanie powiadomień push dla każdej wiadomości). Cała architektura obsługi wiadomości, przechowywania i odpytywania wiadomości musiała zostać wdrożona przez nasz zespół.
Na ratunek przyszła nam biblioteka innej firmy, z której korzystaliśmy wcześniej. Posiadało bardzo przemyślane API, dlatego sprawiliśmy, że nasze rozwiązanie działało w podobny sposób. Oddzieliliśmy kod specyficzny dla XMPP w naszym wewnętrznym SDK z interfejsem odpowiadającym temu z poprzedniego rozwiązania. Spowodowało to jedynie kilka zmian w kodzie naszej aplikacji po migracji.
Podczas wdrażania MongooseIM kilka razy byliśmy zaskoczeni elementami, które uważaliśmy za standardowe, ale nie były dla nas dostępne, nawet przez XEP.
Wdrażanie kluczowych funkcji czatu opartego na XMPP
Znaczniki czasu
Możesz pomyśleć, tak jak my, że znaczniki czasu byłyby tak proste, jak „Otrzymuję wiadomość, wyświetlam to w interfejsie użytkownika ze znacznikiem czasu”. Nie, nie takie proste. Domyślnie sekcje wiadomości nie mają pola sygnatury czasowej. Na szczęście dla naszego zespołu XMPP jest protokołem łatwo rozszerzalnym. Na zapleczu wdrożyliśmy niestandardową funkcję, dodając znacznik czasu do każdej wiadomości, która przeszła przez serwer MongooseIM. Wtedy odbiorca będzie miał dołączony znacznik czasu do wiadomości.
Dlaczego nadawca nie mógł samodzielnie dodać znacznika czasu? No cóż, nie wiemy, czy mają na telefonie ustawioną poprawną godzinę.
Dlaczego nie ma na to żadnego XEP? Może dlatego, że XMPP jest protokołem czasu rzeczywistego, więc teoretycznie każda wysłana wiadomość jest odbierana od razu.
EDYCJA: Jak zauważył Florian Schmaus: „Tak naprawdę jest jeden, chociaż można go łatwo przeoczyć ze względu na mylącą nazwę: XEP-0203: Opóźniona dostawa”. Dodaje znacznik czasu do wiadomości tylko wtedy, gdy jej dostarczenie jest opóźnione. W przeciwnym razie wiadomość została wysłana właśnie teraz.
Wiadomości offline
Gdy obaj użytkownicy są zalogowani do aplikacji, mogą wysyłać do siebie wiadomości w czasie rzeczywistym. Ale co, jeśli jeden z nich jest offline? Szybka odpowiedź brzmi: wiadomości muszą być buforowane na zapleczu . Funkcja wiadomości offline obsługuje tę pracę i wysyła wszystkie buforowane sekcje do użytkownika po ponownym zalogowaniu.
Ale wtedy pojawia się kilka pytań:
- Jak długo te wiadomości powinny być buforowane?
- Ilu z nich?
- Czy należy je ponownie wysłać zaraz po ponownym zalogowaniu? Ale zaleje klienta wiadomościami, prawda?
- Co się stanie, jeśli użytkownik tylko się zaloguje, ale nie dołączy do czatu z nowymi wiadomościami. Czy wszyscy znikną?
- Co się stanie, jeśli użytkownik jest zalogowany na wielu urządzeniach?
Stało się jasne, że funkcja wiadomości offline była w stanie wysyłać wiadomości tylko do pierwszego urządzenia, które wróciło do trybu online, a te wiadomości zostałyby następnie utracone dla wszystkich innych urządzeń. Zdecydowaliśmy się odrzucić tę funkcję i przechowywać wiadomości na backendzie XMPP w inny, trwały sposób.

Zarządzanie archiwum wiadomości (MAM)
MAM to pamięć masowa na serwerze dla wiadomości. Gdy klient jest zalogowany, może wysyłać zapytania do serwera o wiadomości. Możesz wyszukiwać według stron, możesz wyszukiwać według dat. Jest elastyczny — możesz nawet wyszukiwać strony przed lub po wiadomości o określonym identyfikatorze, dodając filtry dla wiadomości z konwersacji.
Ale tu jest haczyk. Zwykłe wiadomości czatu są przechowywane w wiadomościach MAM, które mają własne unikalne identyfikatory. Gdy użytkownik otrzymuje wiadomość czatu w strumieniu, nie zawiera ona identyfikatora MAM. Muszą zapytać MAM, aby to uzyskać.
Pobieranie z MAM jest żądaniem sieciowym, co oznacza, że może zająć stosunkowo dużo czasu. Gdy użytkownik wchodzi na czat, chce od razu widzieć wiadomości. Potrzebujemy więc również lokalnej bazy danych .
Gdy użytkownik otrzymuje wiadomość w strumieniu (wiadomość online), zapisujemy ją w lokalnej bazie danych i pokazujemy użytkownikowi. W ten sposób wyświetlamy wiadomości, które szybko docierają do użytkownika w czasie rzeczywistym.
Dodatkowo, za każdym razem, gdy wchodzą na ekran czatu, pobieramy wszystkie wiadomości od teraz do najnowszej wiadomości MAM przechowywanej w lokalnej bazie danych dla tej rozmowy i umieszczamy je w bazie danych, ignorując duplikaty.
W ten sposób radzimy sobie z przechowywaniem starych wiadomości. Mamy też pewność, że w bazie danych znajduje się komplet wiadomości dla konkretnej rozmowy pomiędzy pierwszą a ostatnią wiadomością od MAM.
Aby śledzić wiadomości pobierane z MAM, dodaliśmy dwie właściwości do encji konwersacji:
- Identyfikator MAM najnowszej wiadomości MAM w bazie danych
- Identyfikator MAM najstarszej wiadomości MAM w bazie danych
Obsługa rozbitych zestawów wiadomości MAM w lokalnej bazie danych byłaby bardzo problematyczna.
Dodatkowo posiadanie tych dwóch właściwości dla każdej rozmowy pozwala nam przechowywać w bazie danych normalne wiadomości czatu, ignorując opakowanie — wiadomość MAM. A gdy użytkownik wejdzie na czat, możemy pokazać najnowsze wiadomości z bazy danych, a w tle pobrać brakujące wiadomości z MAM.
W pudełku
Każda aplikacja oparta na czacie potrzebuje ekranu z listą czatów — miejsca, w którym można zobaczyć nazwiska, ostatnie wiadomości i liczbę nieprzeczytanych wiadomości. Musi być na to rozwiązanie!
Właściwie nie ma… Jest coś, co nazywa się Roster — może zawierać listę użytkowników oznaczonych jako „przyjaciele”. Niestety nie ma do nich dołączonej ostatniej wiadomości ani liczby nieprzeczytanych wiadomości. Jasne, możesz uzyskać potrzebne informacje z zaplecza w kawałkach. Na początku chcieliśmy to zrobić w ten sposób, ale działałoby to powoli i byłoby skomplikowane. Właśnie wtedy rozpoczęliśmy współpracę z Erlang Solutions nad funkcją skrzynki odbiorczej, która również trafia do open source.
Gdy użytkownik łączy się z backendem XMPP, aplikacja pobiera jego skrzynkę odbiorczą, która zawiera wszystkie konwersacje tego użytkownika — zarówno indywidualne, jak i zespołowe. Do każdego z nich dołączona jest ostatnia wiadomość oraz liczba nieprzeczytanych wiadomości. Aplikacja zapisuje całą skrzynkę w lokalnej bazie danych. Gdy użytkownik jest w aplikacji i pojawia się nowa wiadomość, lokalnie aktualizujemy stan skrzynki odbiorczej. W ten sposób aplikacja nie musi pobierać skrzynki odbiorczej dla każdej nowej wiadomości.
Streszczenie
Niektóre rozwiązania czatu innych firm zapewniają wysoki poziom abstrakcji. To jest w porządku, jeśli chcesz stworzyć prostą aplikację do czatu. Wdrażając własne rozwiązanie oparte na XMPP w aplikacji Forward, uzyskaliśmy znacznie lepszy dostęp na niskim poziomie, co znacznie ułatwiło rozwiązywanie problemów. Jasne, zajęło to trochę czasu, ale teraz wiemy, że możemy zapewnić dowolną niestandardową funkcję, aby pomóc lekarzom w Wielkiej Brytanii komunikować się w bezpieczny i łatwy sposób zatwierdzony przez NHS.
Wiadomości to przede wszystkim wysoka wydajność komunikacji w czasie rzeczywistym. Przechodząc na MIM byliśmy w stanie zoptymalizować każdą część rozwiązania, aby poprawić szybkość, niezawodność i ostatecznie zaufanie. Obecnie mamy cały kod, więc łatwo je wyśledzić. Poza tym jesteśmy już po fazie stabilizacji, a liczba zgłoszeń związanych z wiadomościami drastycznie spadła. Użytkownicy są zadowoleni, że mogą zaufać platformie.
Zaprojektowanie i napisanie własnego SDK było trudnym zadaniem i podobało nam się. To było coś innego niż proste aplikacje, w których trzeba pobrać dane z serwera i pokazać je na ekranie. Podczas implementacji zrozumieliśmy wiele wyborów projektowych zewnętrznego API biblioteki, z którego korzystaliśmy wcześniej. Czemu? Ponieważ napotkaliśmy te same problemy.