AirbnbEng

Follow

11 noiembrie, 2013 – 10 min citește

De Spike Brehm

Această postare a fost postată încrucișat pe VentureBeat.

La Airbnb, am învățat multe în ultimii ani în timp ce construiam experiențe web bogate. Ne-am scufundat în lumea aplicațiilor cu o singură pagină în 2011 cu site-ul nostru web mobil, iar de atunci am lansat, printre altele, Wish Lists și pagina noastră de căutare recent reproiectată. Fiecare dintre acestea este o aplicație JavaScript mare, ceea ce înseamnă că cea mai mare parte a codului se execută în browser pentru a susține o experiență mai modernă și mai interactivă.

Această abordare este obișnuită astăzi, iar biblioteci precum Backbone.js, Ember.js și Angular.js au facilitat dezvoltarea acestor aplicații JavaScript bogate de către dezvoltatori. Cu toate acestea, am constatat că aceste tipuri de aplicații au unele limitări critice. Pentru a explica de ce, să facem mai întâi un scurt ocol prin istoria aplicațiilor web.

JavaScript Grows Up

De la începuturile Web-ului, experiența de navigare a funcționat astfel: un browser web solicita o anumită pagină (să zicem, „http://www.geocities.com/”), determinând un server undeva pe Internet să genereze o pagină HTML și să o trimită înapoi pe fir. Acest lucru a funcționat bine deoarece browserele nu erau foarte puternice, iar paginile HTML reprezentau documente care erau în mare parte statice și autonome. JavaScript, creat pentru a permite ca paginile web să fie mai dinamice, nu a permis mult mai mult decât prezentări de imagini și widget-uri de selectare a datei.

După ani de progrese în domeniul calculatoarelor personale, tehnologii creativi au împins web-ul la limitele sale, iar browserele web au evoluat pentru a ține pasul. Acum, Web-ul s-a maturizat într-o platformă de aplicații cu funcționalități complete, iar timpii de execuție JavaScript rapizi și standardele HTML5 au permis dezvoltatorilor să creeze aplicații bogate care înainte erau posibile doar pe platforme native.

Aplicația cu o singură pagină

Nu a trecut mult timp până când dezvoltatorii au început să construiască aplicații întregi în browser folosind JavaScript, profitând de aceste noi capacități. Aplicații precum Gmail, exemplul clasic al aplicației cu o singură pagină, puteau răspunde imediat la interacțiunile utilizatorilor, nemaifiind nevoie să facă un drum dus-întors la server doar pentru a reda o nouă pagină.

Biblioteci precum Backbone.js, Ember.js și Angular.js sunt adesea denumite biblioteci MVC (Model-View-Controller) sau MVVM (Model-View-ViewModel) pe partea clientului. Arhitectura MVC tipică de tip client-side arată cam așa:

Majoritatea logicii aplicației (vizualizări, șabloane, controlori, modele, internaționalizare etc.) locuiește în client, iar acesta vorbește cu un API pentru date. Serverul poate fi scris în orice limbaj, cum ar fi Ruby, Python sau Java, și se ocupă în principal de servirea unei pagini inițiale de HTML. Odată ce fișierele JavaScript sunt descărcate de către browser, acestea sunt evaluate și aplicația de pe partea clientului este inițializată, preluând datele de la API și redând restul paginii HTML.

Acest lucru este excelent pentru utilizator deoarece, odată ce aplicația este încărcată inițial, poate suporta navigarea rapidă între pagini fără a reîmprospăta pagina și, dacă este făcută corect, poate funcționa chiar și offline.

Acest lucru este grozav pentru dezvoltator, deoarece aplicația idealizată cu o singură pagină are o separare clară a preocupărilor între client și server, promovând un flux de lucru de dezvoltare plăcut și prevenind necesitatea de a partaja prea multă logică între cele două, care sunt adesea scrise în limbaje diferite.

În practică, însă, există câteva defecte fatale ale acestei abordări care o împiedică să fie potrivită pentru multe cazuri de utilizare.

SEO

O aplicație care poate rula doar în partea de client nu poate servi HTML către crawlere, deci va avea un SEO slab în mod implicit. Web crawlerele funcționează prin efectuarea unei cereri către un server web și interpretarea rezultatului; dar dacă serverul returnează o pagină goală, nu are prea multă valoare. Există soluții de rezolvare, dar nu fără a sări prin niște cercuri.

Performanță

În aceeași ordine de idei, dacă serverul nu redă o pagină completă de HTML, dar în schimb așteaptă ca JavaScript-ul din partea clientului să facă acest lucru, utilizatorii vor experimenta câteva secunde critice de pagină goală sau de rotiță de încărcare înainte de a vedea conținutul de pe pagină. Există o mulțime de studii care arată efectul drastic pe care un site lent îl are asupra utilizatorilor și, prin urmare, asupra veniturilor. Amazon susține că fiecare reducere cu 100 ms a timpului de încărcare a paginii crește veniturile cu 1%. Twitter a petrecut un an și 40 de ingineri reconstruindu-și site-ul pentru a fi redat pe server în loc de client, susținând o îmbunătățire de 5 ori a timpului de încărcare perceput.

Maintainability

În timp ce cazul ideal poate duce la o separare frumoasă și curată a preocupărilor, în mod inevitabil, unele fragmente de logică a aplicației sau de logică de vizualizare ajung să fie duplicate între client și server, adesea în limbaje diferite. Exemple comune sunt formatarea datei și a monedei, validările de formulare și logica de rutare. Acest lucru face ca întreținerea să fie un coșmar, în special pentru aplicațiile mai complexe.

Câțiva dezvoltatori, inclusiv eu însumi, se simt mușcați de această abordare – de multe ori, abia după ce au investit timp și efort pentru a construi o aplicație cu o singură pagină, devine clar care sunt dezavantajele.

O abordare hibridă

În cele din urmă, ne dorim cu adevărat un hibrid între noua și vechea abordare: dorim să servim HTML complet format de pe server pentru performanță și SEO, dar ne dorim viteza și flexibilitatea logicii aplicației de pe partea clientului.

În acest scop, am experimentat la Airbnb cu aplicații „Isomorphic JavaScript”, care sunt aplicații JavaScript care pot rula atât pe partea de client, cât și pe partea de server.

O aplicație izomorfă ar putea arăta astfel, denumită aici „Client-server MVC”:

În această lume, o parte din logica aplicației și a vizualizării dvs. poate fi executată atât pe server, cât și pe client. Acest lucru deschide tot felul de uși – optimizări de performanță, o mai bună mentenabilitate, SEO-by-default și aplicații web cu mai multă stare.

Cu Node.js, un timp de execuție JavaScript rapid și stabil pe server, putem acum să transformăm acest vis în realitate. Prin crearea abstracțiilor adecvate, putem scrie logica aplicației noastre astfel încât să ruleze atât pe server, cât și pe client – definiția JavaScript izomorfic.

Isomorphic JavaScript in the Wild

Această idee nu este nouă – Nodejitsu a scris o descriere excelentă a arhitecturii JavaScript izomorfice în 2011 – dar a fost lent de adoptat. Au fost câteva cadre izomorfe care au apărut deja.

Mojito a fost primul cadru izomorf cu sursă deschisă care a primit ceva presă. Este un cadru avansat, full-stack, bazat pe Node.js, dar dependența sa de YUI și ciudățeniile specifice Yahoo! nu au dus la prea multă popularitate în comunitatea JavaScript de când au deschis sursa în aprilie 2012.

Meteor este probabil cel mai cunoscut proiect izomorfic de astăzi. Meteor este construit de la zero pentru a susține aplicații în timp real, iar echipa construiește un întreg ecosistem în jurul managerului său de pachete și a instrumentelor de implementare. La fel ca Mojito, este un cadru Node.js mare și plin de opinii, însă a făcut o treabă mult mai bună în ceea ce privește implicarea comunității JavaScript, iar mult-așteptata sa versiune 1.0 este chiar după colț. Meteor este un proiect de urmărit – are o echipă de vedete și a strâns 11,2 milioane de dolari de la Andreessen Horowitz – lucru nemaiîntâlnit pentru o companie axată în întregime pe lansarea unui produs open-source.

Asana, aplicația de gestionare a sarcinilor fondată de cofondatorul Facebook Dustin Moskovitz, are o poveste izomorfă interesantă. Neavând probleme de finanțare, având în vedere statutul lui Moskovitz de cel mai tânăr miliardar din lume, Asana a petrecut ani de zile în R&D dezvoltându-și cadrul Luna, cu sursă închisă, unul dintre cele mai avansate exemple de JavaScript izomorfic existente. Luna, construit inițial pe v8cgi în zilele de dinaintea existenței Node.js, permite ca o copie completă a aplicației să ruleze pe server pentru fiecare sesiune de utilizator. Rulează un proces de server separat pentru fiecare utilizator, executând pe server același cod de aplicație JavaScript care rulează în client, permițând o întreagă clasă de optimizări avansate, cum ar fi un suport robust pentru offline și actualizări rapide în timp real.

Am lansat o bibliotecă izomorfă proprie la începutul acestui an. Numită Rendr, aceasta vă permite să construiți o aplicație Backbone.js + Handlebars.js cu o singură pagină care poate fi, de asemenea, redată complet pe partea serverului. Rendr este un produs al experienței noastre de a reconstrui aplicația web mobilă Airbnb pentru a îmbunătăți drastic timpii de încărcare a paginilor, ceea ce este deosebit de important pentru utilizatorii cu conexiuni mobile cu latență ridicată. Rendr se străduiește să fie o bibliotecă mai degrabă decât un cadru, așa că rezolvă mai puține probleme pentru dumneavoastră în comparație cu Mojito sau Meteor, dar este ușor de modificat și extins.

Abstracție, Abstracție, Abstracție

Că aceste proiecte tind să fie cadre web mari și complete vorbește despre dificultatea problemei. Clientul și serverul sunt medii foarte diferite și, prin urmare, trebuie să creăm un set de abstracțiuni care să decupleze logica aplicației noastre de implementările subiacente, astfel încât să putem expune un singur API dezvoltatorului de aplicații.

Routing

Vrem un singur set de rute care să mapeze modelele URI către gestionarii de rute. Manipulatorii noștri de rute trebuie să fie capabili să acceseze antetele HTTP, cookie-urile și informațiile URI și să specifice redirecționări fără a accesa direct window.location (browser) sau req și res (Node.js).

Fetching și persistența datelor

Vrem să descriem resursele necesare pentru a reda o anumită pagină sau componentă, independent de mecanismul de preluare. Descriptorul de resurse ar putea fi un simplu URI care indică un endpoint JSON sau, pentru aplicații mai mari, poate fi util să încapsulăm resursele în modele și colecții și să specificăm o clasă de model și o cheie primară, care la un moment dat ar fi traduse într-un URI.

Rendarea vizualizării

Dacă alegem să manipulăm direct DOM, rămânem la template-uri HTML bazate pe șiruri de caractere sau optăm pentru o bibliotecă de componente UI cu o abstracție DOM, trebuie să putem genera marcaje izomorfe. Ar trebui să fim capabili să redăm orice vizualizare fie pe server, fie pe client, în funcție de nevoile aplicației noastre.

Constituirea și împachetarea

Se pare că scrierea codului isomorfic al aplicației este doar jumătate din bătălie. Instrumente precum Grunt și Browserify sunt părți esențiale ale fluxului de lucru pentru a pune efectiv aplicația în funcțiune. Pot exista o serie de etape de compilare: compilarea șabloanelor, includerea dependențelor din partea clientului, aplicarea transformărilor, minificarea etc. Cazul simplu este de a combina tot codul aplicației, vizualizările și șabloanele într-un singur pachet, dar pentru aplicațiile mai mari, acest lucru poate duce la sute de kiloocteți de descărcat. O abordare mai avansată este de a crea pachete dinamice și de a introduce încărcarea leneșă a activelor, însă acest lucru se complică rapid. Instrumentele de analiză statică, cum ar fi Esprima, pot permite dezvoltatorilor ambițioși să încerce optimizarea avansată și metaprogramarea pentru a reduce codul boilerplate.

Compunerea împreună a unor module mici

Să fiți primul pe piață cu un cadru izomorfic înseamnă că trebuie să rezolvați toate aceste probleme deodată. Dar acest lucru duce la cadre mari, greoaie, care sunt greu de adoptat și de integrat într-o aplicație deja existentă. Pe măsură ce mai mulți dezvoltatori abordează această problemă, vom asista la o explozie de module mici, reutilizabile, care pot fi integrate împreună pentru a construi aplicații izomorfe.

Se pare că majoritatea modulelor JavaScript pot fi deja utilizate izomorfic cu puține sau deloc modificări. De exemplu, biblioteci populare precum Underscore, Backbone.js, Handlebars.js, Moment și chiar jQuery pot fi utilizate pe server.

Pentru a demonstra acest aspect, am creat o aplicație de probă numită isomorphic-tutorial pe care o puteți verifica pe GitHub. Combinând împreună câteva module, fiecare dintre ele putând fi utilizat izomorfic, este ușor să creați o aplicație izomorfică simplă în doar câteva sute de linii de cod. Folosește Director pentru rutarea bazată pe server și browser, Superagent pentru cererile HTTP și Handlebars.js pentru template-uri, toate construite pe o aplicație Express.js de bază. Desigur, pe măsură ce o aplicație crește în complexitate, trebuie să se introducă mai multe straturi de abstractizare, dar speranța mea este că, pe măsură ce mai mulți dezvoltatori experimentează acest lucru, vor apărea noi biblioteci și standarde.

The View From Here

Pe măsură ce mai multe organizații se acomodează să ruleze Node.js în producție, este inevitabil ca tot mai multe aplicații web să înceapă să împartă codul între codul lor de client și cel de server. Este important să ne amintim că JavaScript izomorfic este un spectru – poate începe doar cu schimbul de șabloane, poate progresa până la a fi un întreg strat de vizualizare al unei aplicații, până la majoritatea logicii de afaceri a aplicației. Ce anume și cum anume este partajat codul JavaScript între medii depinde în întregime de aplicația care se construiește și de setul său unic de constrângeri.

Nicholas C. Zakas are o descriere frumoasă a modului în care preconizează că aplicațiile vor începe să își tragă stratul de interfață utilizator de la client la server, permițând optimizări de performanță și mentenabilitate. O aplicație nu trebuie să își smulgă backend-ul și să îl înlocuiască cu Node.js pentru a folosi JavaScript izomorfic, aruncând în esență copilul cu apa din baie. În schimb, prin crearea unor API-uri sensibile și a unor resurse RESTful, backend-ul tradițional poate trăi alături de stratul Node.js.

La Airbnb, am început deja să ne retehnologizăm procesul de construire pe partea de client pentru a utiliza instrumente bazate pe Node.js, cum ar fi Grunt și Browserify. Este posibil ca aplicația noastră principală Rails să nu fie niciodată înlocuită în întregime de o aplicație Node.js, dar prin adoptarea acestor instrumente devine din ce în ce mai ușor să partajăm anumite bucăți de JavaScript și șabloane între medii.

Ai auzit-o aici primul – în câțiva ani, va fi rar să vezi o aplicație web avansată care să nu ruleze ceva JavaScript pe server.

Învățați mai mult

Dacă această idee vă entuziasmează, veniți să vedeți atelierul Isomorphic JavaScript pe care îl voi preda la DevBeat marți, 12 noiembrie, în San Francisco, sau la Adunarea Generală de joi, 21 noiembrie. Vom hackui împreună pe exemplul de aplicație isomorfă-tutorial Node.js pe care l-am creat pentru a demonstra cât de ușor este cu adevărat să începi să scrii aplicații izomorfe.

De asemenea, urmăriți evoluția aplicațiilor web Airbnb urmărindu-mă pe mine la @spikebrehm și pe echipa de inginerie Airbnb la @AirbnbEng.

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.