Scopul acestui articol urmărește descrierea unei modalități eficiente de a implementa un sistem de tip tracker. Într-un sistem obișnuit lucrurile decurg în mod firesc, utilizatorul se loghează pe o aplicație client, după care face un request la un server, backendul face o serie de calcule sau își execută logica, scrie în baza de date și returnează ceva spre afișare în client. Dar există și alte posibilități de abordare a problemei. Cu ajutorul sistemului de notificări în timp real Firebase, putem decupla clientul de server, cele două părți urmând a comunica prin baza de date. Cu toate că sună un pic ciudat, există o serie de aplicații la care o astfel de abordare ar putea fi avantajoasă. Se prezintă în continuare un caz particular și componentele cheie care permit realizarea acestuia.
Vrem să implementăm un tracker generic care urmărește evoluția valorilor în timp a unui element din cadrul unui site. Pentru aceasta vom pune la dispoziția logicii de backend următoarele date de input: URL al paginii web, un xpath al elementului și un interval de timeout pentru verificare. În maniera cea mai simplă, selecția elementului se va putea face printr-o extensie de browser sau ceva asemănător. Aceste date vor identifica în mod unic, un obiect care va fi asociat în baza de date utilizatorului curent. Pe partea de backend, vom avea un scheduler care va efectua periodic verificările propriu-zise ale elementelor trimise de către diverși clienți.
În continuare, expunem o prezentare foarte sintetică a toolurilor folosite:
Un avantaj major al faptului că se folosește Firebase este faptul că aceasta permite elaborarea unui set de reguli (rules) pentru acces la baza de date. Putem securiza accesul la baza de date astfel încât fiecare utilizator să se poate loga și lucra pe setul său de date sau să vizualizeze date la care se permite accesul. Dat fiind model clasic:
Client -> HTTP request la un REST API -> backend execută proces și scrie în baza de date -> HTTP response -> Client
Putem face altfel:
Client -> scrie în baza de date prin Firebase Auth SDK
Backend -> ascultă și primește notificări de la baza de date prin Firebase Admin SDK
Se observă clar că clientul nu are legătură directă cu serverul, ba mai mult, nici măcar nu știe unde se află acesta sau câte instanțe sunt pornite. Putem eventual să-i oferim acces la un proxy care să-i spună dacă există vreun worker activ dar nu mai mult. Deci din punct de vedere al securității, sistemul nostru este destul de bine ascuns față de eventualii atacatori.
Fig. 1 Arhitectura unui sistem decuplat cu Firebase
Vom analiză împreună particularitățile unui astfel de sistem.
Pe partea de client se folosește plain-old JavaScript cu HTML și câteva linii de CSS, fără alte tooluri sau frameworkuri adiționale. De ce această combinație relativ simplă? Pentru că la nivel de client funcționalitatea este destul de elementară, permițând următoarele acțiuni:
Adăugarea elementelor:
URL
XPath
Pentru a folosi sistemul, utilizatorii se vor loga prin OAuth (ex. cu cont de Google), după care vor putea adăuga un element de test, să verifice că funcționează. În cazul în care rezultatul e satisfăcător și vor dori mai multe elemente, se pune la dispoziție posibilitatea achiziționării unui API key.
La nivel de cod, lucrurile sunt destul de simple: se folosește Firebase Web SDK. Acesta se configurează prin apelarea funcției firebase.initializeApp(), căreia i se pasează un obiect JSON obținut din pagină de configurări a proiectului [1].
Fig. 2 Adăugare element prin intermediul aplicației web (client)
Fig. 3 Afisarea elementelor adăugate și evoluției valorilor acestora
Un feature interesant și foarte util este posibilitatea de deployment a aplicației web direct pe Firebase Cloud prin intermediul Firebase CLI[2]. Dacă totul decurge cu succes, aplicația va fi servită de pe un CDN global, accesibilă printr-un URL de forma http://
Am descris sistemul de reguli Firebase. Pe lângă faptul că forțăm autentificarea utilizatorilor, vom spori gradul de securizare a datelor prin folosirea sistemului de autorizare (rules)[3].
{
"rules": {
"exemplu" : {
".read": true,
".write": false
}
}
}
Un nod exemplu
poate fi citit dar nu scris de catre utilizatori
Bineînțeles că prin folosirea de expresii, variabile predefinite și chei dinamice, se pot descrie reguli complexe care te scutesc de validări extra pe partea de server[4].
Cu toate că regulile reușesc să curețe mare parte din necesarul de validări, uneori poate fi nevoie de mai mult (ex. diverse acțiuni, notificări, housekeeping noduri, etc.) Folosind o serie de triggere[5] (ex. onCreate(), onWrite(), onUpdate() etc.), acestea pot fi îndeplinite cu ajutorul Firebase Functions[6].
exports.trackVser = functions.auth
.user()
.onCreate(result =>
const {uid} = result.data,
now = new Date{) .getTime{)
return admin.database()
.ref('/users/${uid}')
.set ( {
created: now,
lastActive: now,
activeQuestions: O,
totalQuestions: O,
} )
} )
La crearea unui utilizator nou se inițializează un nod nou sub calea /users/$uid în baza de date
Partea cea mai stufoasă a aplicației se află pe backend, acolo unde o aplicație de tip scheduler primește notificări de la baza de date Firebase legate de elementele nou adăugate. Pentru fiecare element, se menține în baza de date o structură pe baza căreia se face tracking atât la acțiunile utilizatorului cât și la evoluție (ex. preț, grade, diverse numere sau texte). În cazul în care un element este șters (de către utilizatorul care l-a adăugat spre procesare sau de către site), acesta se mută într-o altă locație (istorizare).
Un scheduled process din cadrul containerului (ex. de Spring Boot) iterează peste elemente și în funcție de intervalul specificat face actualizări ale valorilor identificate pe site. Se poate menține astfel și un cache local al elementelor în lucru. Acest proces rulează neîntrerupt și fără a mai interacționa cu clientul.
Cu toate că există multe avantaje ale folosirii Firebase în cadrul unui sistem decuplat pentru un astfel de use-case, există totuși o serie de mici dezavantaje:
Mesajele de eroare (validări) pe partea de Firebase Functions nu pot fi personalizate.
Cotele de limitare pentru Functions sunt destul de mici.
Nu se pot apela servicii de la API-uri externe (nici măcar din platforma Google) din Functions.
Majoritatea acestor dezavantaje pot fi anulate în cazul în care se înregistrează un card de credit în contul de Firebase. Pentru toate celelalte furnizorul încurajează utilizarea platformei succesor Firestore.
Se pot spune multe despre posibilitățile pe care le oferă un design decuplat. Nu am vorbit foarte mult despre performanță în cadrul unui astfel de sistem. Se înțelege totuși că prin folosirea unui astfel de serviciu de date (care face parte din platforma Google Cloud), astfel de teme sunt mult mai ușor de abordat, fiind configurabile prin schemă pay-on-demand.
Adăugare Firebase la un proiect Web (https://firebase.google.com/docs/web/setup)
Deployment aplicație Web prin Firebase Hosting (https://firebase.google.com/docs/hosting/deploying)
Reguli de acces în bazele de date Firebase (https://firebase.google.com/docs/database/security)
Securizarea datelor în Firebase (https://firebase.google.com/docs/database/security/securing-data)
Realtime database triggers (Firebase Functions) (https://firebase.google.com/docs/functions/database-events)