Migrare cu succes la o soluție XMPP personalizată
Publicat: 2019-04-08Vă voi vorbi despre provocările cu care ne-am confruntat migrând de la un chat terță parte la o soluție de mesagerie personalizată bazată pe XMPP pentru clientul nostru, Forward Health, o soluție de asistență medicală de mesagerie din Marea Britanie. Acest articol va acoperi motivele migrării, așteptările noastre față de realitățile implementării și provocarea de a construi funcționalități suplimentare.
De unde am început
Forward Health, clientul nostru, a dorit să creeze o aplicație de comunicații mobile pentru lucrătorii din domeniul sănătății din Marea Britanie, inclusiv funcționalitate de chat. Ca startup, au vrut să-și arate rapid produsul care funcționează. În același timp, mesajele trebuiau să fie fiabile, robuste și capabile să trimită în siguranță date sensibile ale pacientului. Pentru a realiza acest lucru, am decis să folosim una dintre soluțiile disponibile terță parte pentru funcționalitatea de chat .
Funcționalitatea chat nu este un lucru banal, mai ales atunci când are scopul de a sprijini industria de sănătate. Pe măsură ce aplicația a crescut, am întâlnit mai multe cazuri de margine și unele erori pe partea bibliotecii la care terțul nu era dispus să lucreze. În plus, Forward Health a dorit să adauge noi funcții care nu au fost acceptate de biblioteca terță parte. Trecerea la o soluție personalizată a fost următorul pas.
Atunci am început să lucrăm cu MongooseIM . MIM este o soluție open source bazată pe protocolul XMPP bine stabilit. Am lucrat cu o companie externă Erlang Solutions Limited pentru a ne configura backend-ul și pentru a oferi suport pentru implementarea soluțiilor personalizate.
La început, totul despre mesagerie părea diferit. Anterior, ne-am îndeplinit toate nevoile de SDK și API-ul REST al acestuia. Acum, folosind MongooseIM, a trebuit să luăm ceva timp pentru a înțelege natura XMPP și a implementa propriul nostru SDK . S-a dovedit că serverul XMPP „bare bones” a transmis doar strofe (mesaje XML) între clienți în timp real. Strofele pot fi de diferite tipuri, adică mesaje de chat normale, prezență, solicitări și răspunsuri. O mare varietate de module pot fi adăugate la server, de exemplu, pentru a stoca mesaje și a permite clienților să le interogheze.
Pe partea de client (Android, iOS) existau câteva SDK-uri de nivel scăzut. Din păcate, aceștia acționau doar ca un strat care permitea comunicarea cu MongooseIM și unele dintre modulele sale conectabile numite XEP (XMPP Extension Protocol responsabil, printre altele, de trimiterea de notificări push pentru fiecare mesaj). Întreaga arhitectură pentru gestionarea mesajelor, stocarea și interogarea mesajelor, a trebuit să fie implementată de echipa noastră.
Ceea ce ne-a salvat a fost biblioteca terță parte pe care am folosit-o anterior. Avea un API foarte bine gândit, așa că am făcut ca soluția noastră să funcționeze într-un mod similar. Am separat codul specific XMPP în SDK-ul nostru intern cu interfața corespunzătoare uneia din soluția anterioară. Acest lucru a dus la doar câteva modificări în codul aplicației noastre după migrare.
În timpul implementării MongooseIM, am fost surprinși de mai multe ori de elementele despre care credeam că ar fi standard, dar nu ne erau disponibile, nici măcar de XEP.
Implementarea caracteristicilor cheie ale chat-ului bazat pe XMPP
Marcaje temporale
S-ar putea să credeți, așa cum am făcut și noi, că marcajele de timp ar fi la fel de simple ca „Primesc un mesaj, îl afișez pe interfața de utilizare cu un marcaj de timp”. Nu, nu atât de ușor. În mod implicit, strofele mesajului nu au un câmp de marcaj temporal. Din fericire pentru echipa noastră, XMPP este un protocol ușor de extensibil. Pe backend, am implementat o caracteristică personalizată, adăugând un marcaj de timp la fiecare mesaj care a trecut prin serverul MongooseIM. Apoi, destinatarul va avea marcajul de timp atașat mesajului.
De ce nu ar putea un expeditor să adauge el însuși un marcaj de timp? Ei bine, nu știm dacă au setat ora corectă pe telefon.
De ce nu există niciun XEP pentru asta? Poate pentru că XMPP este un protocol în timp real, deci teoretic fiecare mesaj trimis este primit imediat.
EDIT: După cum a subliniat Florian Schmaus: „Există de fapt unul, deși poate fi ușor ratat din cauza numelui său confuz: XEP-0203: Livrare întârziată.” Acesta adaugă un marcaj de timp la un mesaj numai dacă livrarea acestuia este întârziată. Altfel, mesajul a fost trimis chiar acum.
Mesaje offline
Când ambii utilizatori sunt conectați la aplicație, ei își pot trimite mesaje unul altuia în timp real. Dar dacă unul dintre ei este offline? Răspunsul rapid este: mesajele trebuie să fie stocate pe backend . Funcția de mesaje offline se ocupă de această activitate și trimite utilizatorului toate strofele din buffer odată ce acesta se conectează din nou.
Dar apoi apar mai multe întrebări:
- Cât timp ar trebui să fie stocate aceste mesaje în tampon?
- Câți dintre ei?
- Ar trebui să fie retrimise imediat după reconectare? Dar îl va inunda pe client cu mesaje, nu-i așa?
- Ce se întâmplă dacă un utilizator se conectează doar, dar nu intră în chat cu noile mesaje. Vor dispărea cu toții?
- Ce se întâmplă dacă un utilizator este conectat pe mai multe dispozitive?
A devenit evident că funcția Mesaj offline era capabilă să trimită mesaje doar către primul dispozitiv care revine online, iar acele mesaje se vor pierde apoi pentru toate celelalte dispozitive. Am decis să renunțăm la această funcție și să stocăm mesajele pe backend-ul XMPP într-un mod diferit și persistent.

Gestionarea arhivei mesajelor (MAM)
MAM este stocarea pe server pentru mesaje. Când un client este conectat, acesta poate interoga serverul pentru mesaje. Puteți interoga după pagini, puteți interoga după date. Este flexibil – puteți chiar să căutați o pagină înainte sau după un mesaj cu un anumit ID, adăugând filtre pentru mesajele din conversația exactă.
Dar aici este prinderea. Mesajele de chat obișnuite sunt stocate împachetate în mesajele MAM, care au propriile lor ID-uri unice. Când un utilizator primește un mesaj de chat într-un flux, acesta nu conține ID-ul MAM. Ei trebuie să interogheze MAM pentru a-l obține.
Preluarea din MAM este o solicitare de rețea, ceea ce înseamnă că poate dura un timp relativ lung. Când un utilizator intră într-un chat, vrea să vadă mesajele imediat. Deci avem nevoie și de o bază de date locală .
Când un utilizator primește un mesaj într-un flux (un mesaj online), îl salvăm în baza de date locală și îl arătăm utilizatorului. Astfel, afișăm mesaje care ajung rapid în timp real utilizatorului.
În plus, de fiecare dată când intră în ecranul de chat, descarcăm toate mesajele de acum în cel mai nou mesaj MAM stocat în DB local pentru acea conversație și le punem într-o bază de date, ignorând duplicatele.
Acesta este modul în care gestionăm stocarea mesajelor vechi. De asemenea, suntem siguri că în baza de date există un set complet de mesaje pentru o anumită conversație între primul și ultimul mesaj de la MAM.
Pentru a urmări mesajele descărcate din MAM, am adăugat două proprietăți la entitățile de conversație:
- ID MAM al celui mai nou mesaj MAM din baza de date
- ID-ul MAM al celui mai vechi mesaj MAM din baza de date
Gestionarea seturilor sparte de mesaje MAM într-o bază de date locală ar fi foarte problematică.
În plus, având aceste două proprietăți pentru fiecare conversație, ne permite să stocăm mesaje de chat obișnuite în baza de date, ignorând în același timp pachetul - mesaj MAM. Iar atunci când utilizatorul intră în chat, putem afișa cele mai recente mesaje din baza de date și în fundal putem prelua mesajele lipsă de la MAM.
Inbox
Fiecare aplicație bazată pe chat are nevoie de un ecran cu o listă de chat-uri – un loc unde puteți vedea numele, ultimele mesaje și un număr de mesaje necitite. Trebuie să existe o soluție la asta!
De fapt, nu există... Există ceva numit Lista - poate conține o listă de utilizatori etichetați ca „prieteni”. Din păcate, nu li se atașează ultimul mesaj și nici un număr de mesaje necitite. Sigur, puteți obține informațiile necesare de la backend în bucăți. La început, am vrut să o facem așa, dar va funcționa încet și va fi complicat de făcut. Atunci am început să lucrăm cu Erlang Solutions pe funcția Inbox, care se îndreaptă și spre open source.
Când un utilizator se conectează la backend-ul XMPP, aplicația își preia căsuța de e-mail, care conține toate conversațiile utilizatorului respectiv - atât conversații individuale, cât și chat-uri în echipă. Fiecare dintre ele are atașat ultimul mesaj și un număr de mesaje necitite. Aplicația salvează întreaga inbox în baza de date locală. Când un utilizator se află în aplicație și sosește un mesaj nou, actualizăm starea căsuței de e-mail la nivel local. În acest fel, aplicația nu trebuie să preia căsuța de e-mail pentru fiecare mesaj nou.
rezumat
Unele soluții de chat terțe oferă un nivel ridicat de abstractizare. Acest lucru este ok dacă doriți să creați o aplicație simplă de chat. Implementând propria noastră soluție bazată pe XMPP în aplicația Forward, am reușit să obținem un acces la nivel scăzut mult mai bun, ceea ce a făcut rezolvarea problemelor mult mai ușoară. Sigur, a durat ceva timp, dar acum știm că putem oferi orice caracteristică personalizată pentru a ajuta medicii din Marea Britanie să comunice într-un mod sigur și ușor, aprobat de NHS.
Mesageria se referă la performanță înaltă, comunicare în timp real. Trecând la MIM, am reușit să optimizăm fiecare parte a soluției pentru a îmbunătăți viteza, fiabilitatea și, în cele din urmă, încrederea. În prezent, avem întregul cod, așa că este ușor să le găsiți. De asemenea, suntem după faza de stabilizare și un număr de rapoarte legate de mesagerie au scăzut drastic. Utilizatorii sunt mulțumiți că pot avea încredere în platformă.
Proiectarea și scrierea propriului SDK a fost o sarcină dificilă și ne-a plăcut. Era ceva diferit de aplicațiile simple în care trebuie să preluați date de pe un server și să le afișați pe ecran. În timpul implementării, am înțeles multe opțiuni de design ale API-ului de bibliotecă terță parte pe care le-am folosit anterior. De ce? Pentru că ne-am confruntat cu aceleași probleme.