În ultima perioadă se vorbește foarte mult despre serverless architecture. Aceasta pare să fie abordarea momentului pentru dezvoltarea software. În acest articol voi încerca să ofer o descriere a ceea ce înseamnă acest concept și voi oferi un exemplu concret de implementare utilizând serviciile de cloud furnizate de Amazon.
Voi încerca în următoarele rânduri să explic ce înseamnă acest nou concept prin plasarea sa în contextul de cloud computing actual. Imaginea de mai jos ilustrează modul în care serviciile cloud au simplificat procesul de dezvoltare a unei aplicații.
Acestea sunt principalele servicii puse la dispoziție de furnizorii de cloud computing:
Infrastructure as a Service (IaaS)- În cazul modelului clasic de dezvoltare software serverele sunt stocate fizic în cadrul companiei care beneficiază de aplicația dezvoltată. Primul pas pentru a pune în funcțiune aplicația este achiziționarea și configurarea componentelor hardware. De asemenea, vor trebui gândite cu atenție cerințele privind puterea de calcul și de stocare necesare pentru ca aplicația să funcționeze în parametri așteptați. Mai mult decât atât, în cazul în care este nevoie de resurse suplimentare, procesul trebuie repetat. Cel mai adesea, însă, este nevoie de resurse suplimentare doar pentru perioade scurte de timp, astfel că în marea majoritate a timpului resursele nu sunt utilizate în totalitate. IaaS reduce practic timpul de livrare și configurare a componentelor hardware de la câteva săptămâni sau chiar luni până la câteva minute. În plus, noi resurse pot fi alocate foarte ușor pentru a asigura scalabilitatea aplicației.
Platform as a Service (PaaS) - Următorul pas este reprezentat de instalarea unui sistem de operare și configurarea mediului de execuție necesar rulării aplicației. De exemplu, pentru o aplicație Java web, o versiune de JRE va trebui instalată. Alte cerințe ar putea cuprinde instalarea unui web server, application container, server de baze de date etc. . PaaS simplifică acest pas furnizând un mediu de execuție deja configurat. Totuși, la acest nivel încă există noțiunea de server (sau o abstractizare a sa) și programatorii sunt responsabili de scalarea aplicației.
În acest context serverless architecture ar putea fi considerat un nivel superior al PaaS. Cea mai importantă diferență constă în faptul că dispare în totalitate conceptul de server. Astfel, programatorul este responsabil să furnizeze codul ce trebuie executat, iar gestionarea mediului de execuție și scalabilitatea aplicației sunt asigurate automat. Ca idee generală, serverless architecture se referă la acea abordare în care codul e rulat în mod similar unei funcții a cărei execuții e declanșată de diferite evenimente (un request HTTP, un mesaj primit de la un alt sistem etc.). Nu există un server capabil să proceseze în mod continuu requesturile, astfel că funcția e încărcată și executată doar atunci când e nevoie. Voi utiliza în continuare termenul funcție pentru a numi acea bucată de cod executată ca reacție la un eveniment, aceasta fiind denumirea utilizată și în [1] (se utilizează chiar noțiunea de Function as a Service (FaaS)), iar cea mai avansată tehnologie în această direcție este AWS Lambda Function.
Serverless architecture permite programatorilor să se concentreze pe singura parte a aplicației capabilă să genereze profit: dezvoltarea de noi funcționalități. Celelalte niveluri sunt doar mijloace ajutătoare pentru a rula aplicația și a oferi utilizatorilor funcționalitățile de care au nevoie. În momentul în care poți face abstracție de celelalte niveluri (componentele hardware și mediul de execuție) și te poți concentra doar pe scrierea codului, atunci putem vorbi de serverless. Tot ce trebuie să faceți este să încărcați codul undeva, iar acesta e executat la momentul potrivit și alocarea de resurse suplimentare pentru a asigura scalabilitatea se realizează în mod automat. Această abordare vă poate ajuta să ajungeți foarte rapid de la idee la realizare pentru că nu aveți nevoie să pierdeți timpul cu configurări de servere sau instalarea unor componente hardware. Costul unor astfel de funcții este mai mic decât cel al execuției unor servere, deoarece costul implică doar resursele alocate pe durata execuției. Pe perioada în care aplicația nu recepționează niciun eveniment care să determine execuția unei funcții costul este zero.
Sună foarte promițător și probabil acesta e visul oricărui programator. Cu toate că cele expuse mai sus sunt adevărate, lucrurile nu sunt chiar atât de simple precum sunt adesea prezentate atunci când o nouă tehnologie iese la rampă. Chiar dacă nivelul de configurare necesar este mult mai redus față de modelele existente de dezvoltare, tot este necesar un aport semnificativ din partea programatorului pentru ca funcția să fie executată corect. O astfel de funcție reprezintă doar o secvență de cod, o parte a unei funcționalități a aplicației. Dar dacă nu e configurată corect, aceasta nu va fi executată niciodată. De asemenea, implementarea unei astfel de funcții trebuie să respecte un contract impus de furnizorul serviciului. Astfel va fi destul de greu ca acest cod să poată fi portat într-un alt mediu de execuție decât cel oferit de furnizorul serviciului de cloud. În plus, frameworkurile serverless actuale nu sunt încă mature, astfel că în cazul adoptării acestei abordări într-un proiect mai complex, trebuie realizată o analiză atentă.
Rezumând ideile anterioare, o aplicație serverless oferă mai multe avantaje:
necesită mult mai puține cunoștințe și efort din partea programatorilor în ceea ce privește nivelurile inferioare ale unei aplicații (se pot concentra doar pe implementarea funcționalităților);
scalabilitatea aplicației este realizată în mod automat;
această abordare este mult mai ieftină decât cea standard (costul reflectă perioada în care codul este executat, nefiind nevoie ca un server să fie tot timpul activ);
chiar dacă în acest moment tehnologia prezentată are încă multe lacune, dezvoltarea în este foarte accelerată (de exemplu, aproape în fiecare lună Amazon adaugă îmbunătățiri serviciului AWS Lambda Function), însă vine la pachet și cu o serie de dezavantaje ce trebuie luate în considerare:
codul și configurările sunt adesea specifice platformei pe care rulează, fiind astfel dificilă portarea aplicației într-un alt mediu;
această abordare nu e potrivită pentru acțiuni a căror execuție este de lungă durată, adesea existând un timp maxim de execuție pentru o funcție (de exemplu, pentru AWS Lambda timpul maxim de execuție pentru o funcție este de 5 minute);
funcțiile trebuie să fie stateless deoarece, în general, codul este re-încărcat la fiecare execuție;
Cu siguranță, această abordare nu este potrivită pentru orice tip de aplicație, astfel că e nevoie să urmărim câteva criterii pentru a decide dacă se potrivește cazurilor noastre de utilizare. Proiectul pe care lucrez folosește o abordare mixtă, în sensul în care majoritatea serviciilor sunt aplicații web ce rulează pe un server, însă există și numeroase funcții serverless. Iată câteva dintre cazurile în care utilizăm abordarea serverless:
microservicii a căror funcționalitate e mai mult bazată pe acțiuni simple de tip CRUD la baza de date. O altă condiție e ca această funcționalitate să fie utilizată destul de rar și să fie executată rapid;
acțiuni care se execută o singură dată. De exemplu, pentru migrarea sau actualizarea datelor dintr-o tabelă, se creează un script care trebuie executat o singură dată după care funcția poate fi ștearsă;
acțiuni executate în mod regulat. În locul utilizării unui job clasic, poate fi creată o funcție care se execută automat la intervale stabilite de timp. Logica ce trebuie executată poate fi implementată în cadrul funcției, fie funcția doar apelează un alt endpoint care realizează logica efectivă;
Pentru a vedea exact ce înseamnă dezvoltarea unei aplicații serverless, voi descrie în rândurile următoare un exemplu concret. Am ales un exemplu cât se poate de simplu pentru a utiliza doar servicii furnizate de Amazon care sunt cel mai des utilizate în dezvoltarea aplicațiilor serverless.
În acest scurt tutorial vom dezvolta o aplicație web care returnează al n-lea număr din șirul lui Fibonacci. Nu îmi propun să prezint aici fiecare pas din dezvoltarea aplicației pentru că astfel articolul ar deveni mult prea lung, însă sper să surprind punctele esențiale. Pentru informații mai detaliate despre fiecare serviciu Amazon vă sugerez să apelați la documentația oficială, care este foarte bine pusă la punct și vă pune la dispoziție multe exemple relevante.
Imaginea de mai jos surprinde structura generală a aplicației și interacțiunea dintre serviciile Amazon utilizate. Ca idee generală, accesând dintr-un browser URL-ul aplicației noastre se va încărca o pagină HTML care e stocată de serviciul S3. Din pagina HTML se realizează apeluri la un anumit endpoint REST definit cu ajutorul API Gateway. Acesta este configurat ca atunci când primește un request să apeleze mai departe serviciul Lambda pentru a calcula numărul căutat din șirul lui Fibonacci. Rezultatul este returnat pe același traseu pană la browser și este afișat utilizatorului. Parcurgând rândurile următoare veți avea o imagine mai clară asupra interacțiunii dintre serviciile Amazon descrise în această imagine.
Pe parcursul acestui exemplu, voi utiliza consola Amazon (interfața web pusă la dispoziție de Amazon pentru a interacționa cu serviciile). Există și alte posibilități de a interacționa cu serviciile Amazon (command line, Amazon SDK), însă aceasta este cea mai simplă și ne previne în a fi distrași de alte detalii irelevante în acest context.
Probabil cea mai simplă aplicație serverless la care ne putem gândi ar fi o aplicație web cuprinzând doar pagini statice HTML. În dezvoltarea aplicației noastre vom începe de la acest nivel construind o aplicație AngularJS foarte simplă. Interfața grafică este cea din imaginea alăturată, însă nu vă voi plictisi cu cod pentru că ceea ce face poate fi găsit în orice tutorial de AngularJS pentru începători.
Pentru a face accesibilă această pagină HTML, fișierul va fi stocat cu ajutorul serviciului S3. Acesta este un serviciu de la Amazon pentru stocarea fișierelor, dar poate funcționa și în calitate de Content Delivery Network (CDN). Pentru a realiza acest lucru este nevoie să creăm un bucket și să încărcăm fișierul HTML la această localizare. În mod automat se va genera un URL ce poate fi accesat direct din browser.
Următorul serviciu pe care îl vom utiliza este AWS Lambda Function. Acesta este un serviciu capabil să ruleze o secvență de cod într-un context specificat: ca urmare a interceptării unor evenimente (de exemplu, încărcarea unui fișier în S3 poate declanșa execuția unei funcții Lambda); ca urmare a unui request HTTP; invocarea în mod direct a unei funcții prin intermediul unui API pus la dispoziție de Amazon.
Navigând la pagina de configurare a serviciului AWS Lambda Function o nouă funcție va fi creată prin specificarea numelui funcției și furnizarea codului ce va fi executat. Există mai multe modalități pentru furnizarea codului: inline, prin introducerea codului în interfața Web; încărcarea unei arhive zip conținând codul și dependințele; introducerea unei referințe la un fișier stocat în S3. Am ales prima variantă pentru că e cea mai simplă și în acest caz nu avem nevoie de o altă abordare. Pentru o funcție Lambda se va specifica un handler (în imaginea de mai jos handlerul este dat de funcția exports.handler) ce va fi apelat atunci când este invocată funcția. Handlerul trebuie să respecte anumite convenții, lucru valabil pentru toate limbajele suportate de AWS Lambda. Conform documentației, limbajele de programare disponibile sunt: Node.js (JavaScript), Python, Java (compatibil cu Java 8) și C#. Pentru acest exemplu am ales Node.js. După creare, tot prin intermediul interfeței web, noua funcție poate fi testată apelând-o cu diferiți parametri. În acest moment avem o funcție implementată conform așteptărilor, însă codul scris de noi nu va fi executat niciodată.
exports.handler = (event, context, callback) => {
if(event.number === undefined) {
callback("No number provided.");
} else if(typeof event.number !== "number") {
callback("The provided number is"+
" invalid.");
} else
if(event.number < 1 || event.number > 1000) {
callback("Please provide a number"+
" between 1 and 1000.");
} else {
callback(null, fibonacci(event.numer));
}
};
function fibonacci(n) {
return n < 2 ? n : (fibonacci(n-1) + fibonacci(n-2));
}
Ultimul pas în dezvoltarea aplicației propuse este dat de crearea unui endpoint REST care să execute funcția definită anterior. Acest lucru se realizează cu ajutorul serviciului API Gateway. Definirea unui nou endpoint poate fi realizată tot prin intermediul consolei AWS. Din interfața serviciului API Gateway va fi creat mai întâi un nou API, iar în cadrul acestei componente se definește endpointul GET "/numbers/{number}", unde {number} reprezintă un placeholder pentru numărul furnizat de clientul endpointului. Acesta va fi furnizat mai departe ca parametru pentru funcția Lambda anterior definită.
Așa cum am văzut în acest articol, serverless este o versiune mai avansată a PaaS, în sensul în care programatorii nu mai trebuie să se concentreze pe configurarea mediului de execuție și scalarea aplicației, aceste facilități fiind furnizate în mod automat. Arhitectura de tip serverless este încă într-o fază de început, însă dezvoltarea este accelerată și este de așteptat ca noi soluții să apară în perioada următoare. Cu toate că există o serie de dezavantaje asociate acesteia, totuși pot fi găsite numeroase cazuri de utilizare pentru care serverless este abordarea cea mai potrivită.
Am încercat apoi să vă arăt ce presupune dezvoltarea unei aplicații serverless utilizând serviciile de la Amazon. Chiar dacă am omis mulți pași din acest proces pentru a păstra articolul cât mai scurt cu putință, sper că v-ați putut face o idee și că v-am stârnit curiozitatea pentru a încerca o implementare proprie. Pentru mai multe detalii, vă sugerez să apelați la documentația oficială. De asemenea, aici puteți găsi cel mai detaliat articol pe care l-am citit până acum despre serverless architecture.