AirbnbEng

Follow

11 novembre, 2013 – 10 min read

Di Spike Brehm

Questo post è stato pubblicato su VentureBeat.

A Airbnb, abbiamo imparato molto negli ultimi anni mentre costruivamo ricche esperienze web. Ci siamo tuffati nel mondo delle app a pagina singola nel 2011 con il nostro sito web mobile, e da allora abbiamo lanciato Wish List e la nostra nuova pagina di ricerca ridisegnata, tra le altre. Ognuna di queste è una grande app JavaScript, il che significa che la maggior parte del codice viene eseguito nel browser per supportare un’esperienza più moderna e interattiva.

Questo approccio è comune oggi, e librerie come Backbone.js, Ember.js e Angular.js hanno reso più facile per gli sviluppatori costruire queste ricche app JavaScript. Abbiamo scoperto, tuttavia, che questi tipi di app hanno alcune limitazioni critiche. Per spiegare perché, facciamo prima una breve deviazione attraverso la storia delle app web.

JavaScript Grows Up

Dagli albori del web, l’esperienza di navigazione ha funzionato così: un browser web richiedeva una particolare pagina (diciamo, “http://www.geocities.com/”), inducendo un server da qualche parte su Internet a generare una pagina HTML e inviarla indietro sul filo. Questo ha funzionato bene perché i browser non erano molto potenti e le pagine HTML rappresentavano documenti che erano per lo più statici e autocontenuti. JavaScript, creato per permettere alle pagine web di essere più dinamiche, non permetteva molto di più che slideshow di immagini e widget per scegliere la data.

Dopo anni di progressi nel personal computing, i tecnologi creativi hanno spinto il web ai suoi limiti e i browser web si sono evoluti per tenere il passo. Ora, il web è maturato in una piattaforma applicativa completa, e i runtime JavaScript veloci e gli standard HTML5 hanno permesso agli sviluppatori di creare applicazioni ricche che prima erano possibili solo su piattaforme native.

L’app a pagina singola

Non è passato molto tempo prima che gli sviluppatori iniziassero a costruire intere applicazioni nel browser usando JavaScript, approfittando di queste nuove capacità. Applicazioni come Gmail, il classico esempio di app a pagina singola, potevano rispondere immediatamente alle interazioni dell’utente, non avendo più bisogno di fare un round-trip al server solo per renderizzare una nuova pagina.

Librerie come Backbone.js, Ember.js e Angular.js sono spesso indicate come librerie MVC (Model-View-Controller) o MVVM (Model-View-ViewModel) lato client. La tipica architettura MVC lato client assomiglia a questa:

Il grosso della logica dell’applicazione (viste, modelli, controller, modelli, internazionalizzazione, ecc. Il server potrebbe essere scritto in qualsiasi linguaggio, come Ruby, Python o Java, e si occupa principalmente di servire una pagina iniziale di HTML. Una volta che i file JavaScript vengono scaricati dal browser, vengono valutati e l’applicazione lato client viene inizializzata, recuperando i dati dall’API e rendendo il resto della pagina HTML.

Questo è ottimo per l’utente perché una volta che l’applicazione è inizialmente caricata, può supportare la navigazione rapida tra le pagine senza aggiornare la pagina, e se fatto bene, può anche funzionare offline.

Questo è ottimo per lo sviluppatore perché l’app idealizzata a pagina singola ha una chiara separazione delle preoccupazioni tra il client e il server, promuovendo un bel flusso di lavoro di sviluppo ed evitando la necessità di condividere troppa logica tra i due, che sono spesso scritti in lingue diverse.

In pratica, tuttavia, ci sono alcuni difetti fatali con questo approccio che gli impediscono di essere giusto per molti casi d’uso.

SEO

Un’applicazione che può essere eseguita solo sul lato client non può servire HTML ai crawler, quindi avrà una scarsa SEO per impostazione predefinita. I web crawler funzionano facendo una richiesta a un web server e interpretando il risultato; ma se il server restituisce una pagina bianca, non ha molto valore. Ci sono soluzioni, ma non senza saltare attraverso alcuni cerchi.

Performance

Per lo stesso motivo, se il server non rende una pagina intera di HTML ma aspetta che lo faccia il JavaScript lato client, gli utenti sperimenteranno alcuni secondi critici di pagina bianca o di spinner di caricamento prima di vedere il contenuto della pagina. Ci sono molti studi che mostrano l’effetto drastico che un sito lento ha sugli utenti, e quindi sulle entrate. Amazon sostiene che ogni 100ms di riduzione del tempo di caricamento della pagina aumenta le entrate dell’1%. Twitter ha speso un anno e 40 ingegneri per ricostruire il loro sito per renderlo sul server invece che sul client, dichiarando un miglioramento di 5 volte nel tempo di caricamento percepito.

Manutenibilità

Mentre il caso ideale può portare ad una bella e pulita separazione delle preoccupazioni, inevitabilmente alcuni pezzi di logica dell’applicazione o di logica della vista finiscono per essere duplicati tra client e server, spesso in lingue diverse. Esempi comuni sono la formattazione della data e della valuta, le validazioni dei moduli e la logica di routing. Questo rende la manutenzione un incubo, specialmente per le applicazioni più complesse.

Alcuni sviluppatori, me compreso, si sentono morsi da questo approccio – spesso è solo dopo aver investito il tempo e lo sforzo per costruire un’applicazione a pagina singola che diventa chiaro quali sono gli svantaggi.

Un approccio ibrido

Alla fine della giornata, vogliamo davvero un ibrido tra il nuovo e il vecchio approccio: vogliamo servire HTML completamente formato dal server per le prestazioni e il SEO, ma vogliamo la velocità e la flessibilità della logica applicativa lato client.

A questo scopo, abbiamo sperimentato ad Airbnb le applicazioni “Isomorphic JavaScript”, che sono applicazioni JavaScript che possono essere eseguite sia lato client che lato server.

Un’app isomorfa potrebbe assomigliare a questa, soprannominata qui “Client-server MVC”:

In questo mondo, parte della tua applicazione e la logica della vista possono essere eseguite sia sul server che sul client. Questo apre ogni sorta di porte – ottimizzazione delle prestazioni, migliore manutenibilità, SEO-by-default, e applicazioni web più statiche.

Con Node.js, un runtime JavaScript veloce e stabile lato server, possiamo ora rendere questo sogno una realtà. Creando le astrazioni appropriate, possiamo scrivere la nostra logica applicativa in modo che venga eseguita sia sul server che sul client – la definizione di JavaScript isomorfo.

Isomorphic JavaScript in the Wild

Questa idea non è nuova – Nodejitsu ha scritto una grande descrizione dell’architettura JavaScript isomorfa nel 2011 – ma è stata adottata lentamente. Ci sono già stati alcuni framework isomorfi che sono spuntati fuori.

Mojito è stato il primo framework isomorfo open-source ad ottenere una certa stampa. È un framework avanzato e completo basato su Node.js, ma la sua dipendenza da YUI e le sue stranezze specifiche di Yahoo non hanno portato a molta popolarità nella comunità JavaScript da quando è stato reso open source nell’aprile 2012.

Meteor è probabilmente il progetto isomorfo più conosciuto oggi. Meteor è costruito da zero per supportare app in tempo reale, e il team sta costruendo un intero ecosistema intorno al suo gestore di pacchetti e agli strumenti di distribuzione. Come Mojito, è un framework Node.js grande e pieno di opinioni, tuttavia ha fatto un lavoro molto migliore coinvolgendo la comunità JavaScript, e il suo tanto atteso rilascio 1.0 è proprio dietro l’angolo. Meteor è un progetto da tenere d’occhio – ha un team all-star, e ha raccolto 11,2 milioni di dollari da Andreessen Horowitz – inaudito per una società interamente focalizzata sul rilascio di un prodotto open-source.

Asana, l’app di gestione delle attività fondata dal cofondatore di Facebook Dustin Moskovitz, ha una storia isomorfa interessante. Non male per i finanziamenti, considerando lo status di Moskovitz come il più giovane miliardario del mondo, Asana ha trascorso anni in R&D sviluppando il loro framework a codice chiuso Luna, uno dei più avanzati esempi di JavaScript isomorfo in giro. Luna, originariamente costruito su v8cgi nei giorni prima che Node.js esistesse, permette una copia completa dell’applicazione da eseguire sul server per ogni singola sessione utente. Esegue un processo server separato per ogni utente, eseguendo lo stesso codice dell’applicazione JavaScript sul server che è in esecuzione nel client, consentendo un’intera classe di ottimizzazioni avanzate, come un robusto supporto offline e veloci aggiornamenti in tempo reale.

A inizio anno abbiamo lanciato una nostra libreria isomorfa. Chiamata Rendr, permette di costruire un’app a pagina singola Backbone.js + Handlebars.js che può anche essere completamente resa sul lato server. Rendr è un prodotto della nostra esperienza nella ricostruzione della web app mobile di Airbnb per migliorare drasticamente i tempi di caricamento delle pagine, che è particolarmente importante per gli utenti su connessioni mobili ad alta latenza. Rendr si sforza di essere una libreria piuttosto che un framework, quindi risolve meno problemi rispetto a Mojito o Meteor, ma è facile da modificare ed estendere.

Abstraction, Abstraction, Abstraction

Il fatto che questi progetti tendano ad essere grandi framework web full-stack parla della difficoltà del problema. Il client e il server sono ambienti molto diversi, e quindi dobbiamo creare un insieme di astrazioni che disaccoppino la nostra logica applicativa dalle implementazioni sottostanti, in modo da poter esporre una singola API allo sviluppatore dell’applicazione.

Routing

Vogliamo un singolo insieme di rotte che mappiano i modelli URI ai gestori delle rotte. I nostri gestori di rotte devono essere in grado di accedere alle intestazioni HTTP, ai cookie e alle informazioni URI, e specificare i reindirizzamenti senza accedere direttamente a window.location (browser) o a req e res (Node.js).

Fetching e persistenza dei dati

Vogliamo descrivere le risorse necessarie per rendere una particolare pagina o componente indipendentemente dal meccanismo di fetching. Il descrittore delle risorse potrebbe essere un semplice URI che punta ad un endpoint JSON, o per applicazioni più grandi, potrebbe essere utile incapsulare le risorse in modelli e collezioni e specificare una classe di modello e una chiave primaria, che ad un certo punto verrebbe tradotta in un URI.

Rendering delle viste

Che si scelga di manipolare direttamente il DOM, di attenersi al template HTML basato su stringhe, o di optare per una libreria di componenti UI con un’astrazione DOM, dobbiamo essere in grado di generare markup in modo isomorfo. Dovremmo essere in grado di rendere qualsiasi vista sia sul server che sul client, a seconda delle necessità della nostra applicazione.

Costruire e impacchettare

Si è scoperto che scrivere codice isomorfo per applicazioni è solo metà della battaglia. Strumenti come Grunt e Browserify sono parti essenziali del flusso di lavoro per far funzionare l’applicazione. Ci può essere un certo numero di passi di compilazione: compilare i template, includere le dipendenze lato client, applicare le trasformazioni, la minificazione, ecc. Il caso più semplice è quello di combinare tutto il codice dell’applicazione, le viste e i modelli in un unico bundle, ma per le app più grandi, questo può risultare in centinaia di kilobyte da scaricare. Un approccio più avanzato è quello di creare bundle dinamici e introdurre il lazy-loading delle risorse, tuttavia questo diventa rapidamente complicato. Strumenti di analisi statica come Esprima possono permettere agli sviluppatori ambiziosi di tentare l’ottimizzazione avanzata e la metaprogrammazione per ridurre il codice boilerplate.

Comporre insieme piccoli moduli

Essere il primo a commercializzare un framework isomorfo significa dover risolvere tutti questi problemi in una volta. Ma questo porta a framework grandi e ingombranti che sono difficili da adottare e integrare in un’app già esistente. Man mano che più sviluppatori affrontano questo problema, vedremo un’esplosione di piccoli moduli riutilizzabili che possono essere integrati insieme per costruire applicazioni isomorfe.

Si è scoperto che la maggior parte dei moduli JavaScript possono già essere usati in modo isomorfo con poche o nessuna modifica. Per esempio, librerie popolari come Underscore, Backbone.js, Handlebars.js, Moment, e persino jQuery possono essere usate sul server.

Per dimostrare questo punto, ho creato un’app di esempio chiamata isomorphic-tutorial che potete controllare su GitHub. Combinando insieme alcuni moduli, ognuno dei quali può essere usato isomorficamente, è facile creare una semplice app isomorfa in poche centinaia di righe di codice. Utilizza Director per il routing basato su server e browser, Superagent per le richieste HTTP e Handlebars.js per i template, il tutto costruito sopra un’app Express.js di base. Naturalmente, man mano che un’applicazione cresce in complessità, si devono introdurre più livelli di astrazione, ma la mia speranza è che man mano che più sviluppatori sperimentano con questo, ci saranno nuove librerie e standard che emergeranno.

La vista da qui

Come più organizzazioni si sentono a proprio agio nell’eseguire Node.js in produzione, è inevitabile che sempre più applicazioni web inizieranno a condividere codice tra il loro codice client e server. È importante ricordare che il JavaScript isomorfo è uno spettro – può iniziare con la semplice condivisione di template, progredire fino ad essere il livello di visualizzazione di un’intera applicazione, fino alla maggior parte della logica di business dell’applicazione. Esattamente cosa e come il codice JavaScript viene condiviso tra gli ambienti dipende interamente dall’applicazione che viene costruita e dal suo unico insieme di vincoli.

Nicholas C. Zakas ha una bella descrizione di come immagina che le applicazioni cominceranno a tirare il loro strato UI giù al server dal client, permettendo ottimizzazioni di prestazioni e manutenibilità. Un’app non deve strappare il suo backend e sostituirlo con Node.js per usare JavaScript isomorfo, essenzialmente buttando via il bambino con l’acqua sporca. Invece, creando API sensate e risorse RESTful, il backend tradizionale può vivere accanto allo strato Node.js.

A Airbnb, abbiamo già iniziato a riorganizzare il nostro processo di costruzione lato client per usare strumenti basati su Node.js come Grunt e Browserify. La nostra app principale Rails potrebbe non essere mai completamente soppiantata da un’app Node.js, ma abbracciando questi strumenti diventa sempre più facile condividere certi bit di JavaScript e template tra gli ambienti.

L’avete sentito qui per primi – entro pochi anni, sarà raro vedere una web app avanzata che non stia eseguendo del JavaScript sul server.

Per saperne di più

Se questa idea ti eccita, vieni a vedere il workshop Isomorphic JavaScript che terrò al DevBeat martedì 12 novembre a San Francisco, o all’Assemblea Generale giovedì 21 novembre. Lavoreremo insieme sull’app campione Node.js isomorfo-tutorial che ho creato per dimostrare quanto sia facile iniziare a scrivere app isomorfe.

Tenete anche d’occhio l’evoluzione delle web app di Airbnb seguendo me su @spikebrehm e il team di Airbnb Engineering su @AirbnbEng.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.