Airbnb:ssä olemme oppineet paljon viime vuosina rakentaessamme rikkaita verkkokokemuksia. Sukelsimme yhden sivun sovellusten maailmaan vuonna 2011 mobiiliverkkosivustollamme, ja sen jälkeen olemme lanseeranneet muun muassa Wish Listsin ja äskettäin uudistetun hakusivumme. Jokainen näistä on suuri JavaScript-sovellus, mikä tarkoittaa, että suurin osa koodista suoritetaan selaimessa nykyaikaisemman, vuorovaikutteisemman käyttökokemuksen tukemiseksi.
Tämä lähestymistapa on nykyään yleinen, ja Backbone.js:n, Ember.js:n ja Angular.js:n kaltaiset kirjastot ovat helpottaneet kehittäjien työtä tällaisten rikkaiden JavaScript-sovellusten rakentamisessa. Olemme kuitenkin havainneet, että tämäntyyppisillä sovelluksilla on joitakin kriittisiä rajoituksia. Selittääksemme miksi, tehdään ensin nopea kiertoajelu web-sovellusten historiaan.
- JavaScript kasvaa
- Yksisivuinen sovellus
- SEO
- Suorituskyky
- Kunnossapidettävyys
- Hybridi lähestymistapa
- Isomorfinen JavaScript luonnossa
- Abstraktio, abstraktio, abstraktio
- Reititys
- Datan noutaminen ja säilyttäminen
- Näkymän renderöinti
- Rakentaminen ja pakkaaminen
- Pienten moduulien yhteen kokoaminen
- Näkymä täältä
- Learn More
JavaScript kasvaa
Webin alusta lähtien selailukokemus on toiminut näin: web-selain pyysi tiettyä sivua (vaikkapa ”http://www.geocities.com/”), jolloin palvelin jossain päin Internetiä loi HTML-sivun ja lähetti sen takaisin langan välityksellä. Tämä on toiminut hyvin, koska selaimet eivät olleet kovin tehokkaita ja HTML-sivut edustivat enimmäkseen staattisia ja itsenäisiä asiakirjoja. JavaScript, joka luotiin, jotta verkkosivut voisivat olla dynaamisempia, ei mahdollistanut juuri muuta kuin kuvien diaesitykset ja päivämääränvalitsinwidgetit.
Vuosien kuluessa henkilökohtaisen tietojenkäsittelyn kehityksestä luovat teknologiatutkijat ovat vieneet verkon äärirajoilleen, ja verkkoselaimet ovat kehittyneet pysyäkseen mukana. Nyt verkko on kypsynyt täysimittaiseksi sovellusalustaksi, ja nopeiden JavaScript-ajoaikojen ja HTML5-standardien ansiosta kehittäjät voivat luoda monipuolisia sovelluksia, jotka ennen olivat mahdollisia vain natiiveilla alustoilla.
Yksisivuinen sovellus
Ei kestänyt kauan, ennen kuin kehittäjät alkoivat rakentaa kokonaisia sovelluksia selaimessa JavaScriptin avulla ja hyödyntää näitä uusia mahdollisuuksia. Gmailin kaltaiset sovellukset, klassinen esimerkki yhden sivun sovelluksesta, saattoivat reagoida välittömästi käyttäjän vuorovaikutukseen, eikä niiden tarvinnut enää tehdä kierrosta palvelimelle vain uuden sivun renderöimiseksi.
Backbone.js:n, Ember.js:n ja Angular.js:n kaltaisia kirjastoja kutsutaan usein asiakaspuolen MVC- (Model-View-Controller) tai MVVM-kirjastoiksi (Model-View-ViewModel). Tyypillinen asiakaspuolen MVC-arkkitehtuuri näyttää jotakuinkin tältä:
Valtaosa sovelluksen logiikasta (näkymiä, malleja, kontrollereita, malleja, kansainvälistämistä yms.) asuu asiakasohjelmassa, ja se keskustelee API:n kanssa saadakseen tietoja. Palvelin voidaan kirjoittaa millä tahansa kielellä, kuten Rubylla, Pythonilla tai Javalla, ja se huolehtii lähinnä alkuperäisen HTML-sivun tarjoilusta. Kun selain on ladannut JavaScript-tiedostot, ne arvioidaan ja asiakassovellus alustetaan, jolloin se hakee tietoja API:sta ja renderöi loput HTML-sivusta.
Tämä on käyttäjän kannalta hienoa, koska kun sovellus on ladattu aluksi, se voi tukea nopeaa navigointia sivujen välillä päivittämättä sivua, ja jos se tehdään oikein, se voi toimia jopa offline-tilassa.
Tämä on hienoa kehittäjän kannalta, koska idealisoidussa yhden sivun sovelluksessa on selkeä huolenaiheiden erottelu asiakkaan ja palvelimen välillä, mikä edistää mukavaa kehitystyönkulkua ja estää tarpeen jakaa liikaa logiikkaa näiden kahden, usein eri kielillä kirjoitetun sovelluksen välillä.
Käytännössä tässä lähestymistavassa on kuitenkin muutamia kohtalokkaita puutteita, jotka estävät sitä soveltumasta moniin käyttötapauksiin.
SEO
Sovellus, joka voi toimia vain asiakaspuolella, ei voi tarjoilla HTML:ää indeksoijille, joten sen SEO on oletusarvoisesti huono. Web-matkailijat toimivat tekemällä pyynnön web-palvelimelle ja tulkitsemalla tuloksen; mutta jos palvelin palauttaa tyhjän sivun, siitä ei ole paljon hyötyä. On olemassa kiertoteitä, mutta ei ilman, että joutuu hyppäämään joidenkin renkaiden läpi.
Suorituskyky
Jos palvelin ei renderöi täyttä HTML-sivua, vaan odottaa sen sijaan asiakaspuolen JavaScriptiä, käyttäjät kokevat muutaman kriittisen sekunnin tyhjää sivua tai lastauskehrääjää, ennen kuin näkevät sivun sisällön. On paljon tutkimuksia, jotka osoittavat hitaan sivuston dramaattisen vaikutuksen käyttäjiin ja siten tuloihin. Amazon väittää, että jokainen 100 ms:n vähennys sivun latausajassa lisää liikevaihtoa 1 %. Twitter käytti vuoden ja 40 insinööriä rakentaakseen sivustonsa uudelleen siten, että se renderöityy palvelimella eikä asiakkaalla, ja väitti, että havaittu latausaika parani viisi kertaa.
Kunnossapidettävyys
Ideaalitapauksessa huolenaiheet voidaan erottaa toisistaan hienosti ja siististi, mutta väistämättä osa sovelluslogiikasta tai näkymälogiikasta jää päällekkäisiksi asiakkaan ja palvelimen välille, ja ne ovat usein eri kielillä. Yleisiä esimerkkejä ovat päivämäärien ja valuuttojen muotoilu, lomakkeiden validointi ja reitityslogiikka. Tämä tekee ylläpidosta painajaismaista, erityisesti monimutkaisemmissa sovelluksissa.
Jotkut kehittäjät, minä mukaan lukien, kokevat tämän lähestymistavan purevan – usein vasta kun on panostanut aikaa ja vaivaa yksisivuisen sovelluksen rakentamiseen, tulee selväksi, mitkä ovat sen haitat.
Hybridi lähestymistapa
Loppujen lopuksi haluamme oikeastaan uuden ja vanhan lähestymistavan hybridin: haluamme tarjoilla palvelimelta täysin muotoiltua HTML:ää suorituskyvyn ja hakukoneoptimoinnin vuoksi, mutta haluamme asiakaspuolen sovelluslogiikan nopeuden ja joustavuuden.
Tätä varten olemme Airbnb:ssä kokeilleet ”isomorfisia JavaScript-sovelluksia”, jotka ovat JavaScript-sovelluksia, jotka voivat toimia sekä asiakas- että palvelinpuolella.
Isomorfinen sovellus voi näyttää tältä, ja sitä kutsutaan tässä ”Client-server MVC:ksi”:
Tässä maailmassa osa sovelluksen ja näkymän logiikasta voidaan suorittaa sekä palvelimella että asiakkaalla. Tämä avaa monenlaisia ovia – suorituskyvyn optimointia, parempaa ylläpidettävyyttä, SEO-by-default ja tilallisempia verkkosovelluksia.
Node.js:n, nopean ja vakaan palvelinpuolen JavaScript-ajoaikajärjestelmän, avulla voimme nyt tehdä tästä unelmasta totta. Luomalla sopivia abstraktioita voimme kirjoittaa sovelluslogiikkamme niin, että se toimii sekä palvelimella että asiakkaalla – isomorfisen JavaScriptin määritelmä.
Isomorfinen JavaScript luonnossa
Ajatus ei ole uusi – Nodejitsu kirjoitti loistavan kuvauksen isomorfisesta JavaScript-arkkitehtuurista vuonna 2011 – mutta sen omaksuminen on ollut hidasta. Muutamia isomorfisia kehyksiä on jo syntynyt.
Mojito oli ensimmäinen avoimen lähdekoodin isomorfinen kehys, joka sai lehdistöä. Se on edistynyt, täysimittainen Node.js-pohjainen kehys, mutta sen riippuvuus YUI:sta ja Yahoo!-spesifiset oikut eivät ole johtaneet suureen suosioon JavaScript-yhteisössä sen jälkeen, kun he avasivat sen huhtikuussa 2012.
Meteor on luultavasti tunnetuin isomorfinen hanke nykyään. Meteor on rakennettu alusta alkaen tukemaan reaaliaikaisia sovelluksia, ja tiimi rakentaa kokonaista ekosysteemiä sen paketinhallinta- ja käyttöönottotyökalujen ympärille. Kuten Mojito, se on suuri, mielipiteitä jakava Node.js-kehys, mutta se on tehnyt paljon parempaa työtä JavaScript-yhteisön sitouttamisessa, ja sen odotettu 1.0-julkaisu on aivan nurkan takana. Meteor on hanke, jota kannattaa seurata – sillä on huipputiimi, ja se on kerännyt 11,2 miljoonaa dollaria Andreessen Horowitzilta, mikä on ennenkuulumatonta yritykseltä, joka keskittyy täysin avoimen lähdekoodin tuotteen julkaisemiseen.
Asana, Facebookin perustajan Dustin Moskovitzin perustama tehtävienhallintasovellus, on mielenkiintoinen isomorfinen tarina. Koska Moskovitz on maailman nuorin miljardööri, Asana vietti vuosia R&D:ssä kehittäen suljetun lähdekoodin Luna-kehystä, joka on yksi edistyneimmistä esimerkeistä isomorfisesta JavaScriptistä. Luna, joka alun perin rakennettiin v8cgi:n varaan ennen Node.js:n olemassaoloa, mahdollistaa sovelluksen täydellisen kopion suorittamisen palvelimella jokaisen käyttäjän istunnon ajan. Se ajaa erillistä palvelinprosessia kullekin käyttäjälle ja suorittaa palvelimella samaa JavaScript-sovelluskoodia kuin asiakkaalla, mikä mahdollistaa koko luokan kehittyneitä optimointeja, kuten vankan offline-tuen ja nopeat reaaliaikaiset päivitykset.
Luovutimme oman isomorfisen kirjastomme aiemmin tänä vuonna. Sen nimi on Rendr, ja sen avulla voit rakentaa Backbone.js + Handlebars.js -sovelluksen yhdellä sivulla, joka voidaan renderöidä kokonaan myös palvelinpuolella. Rendr perustuu kokemukseemme Airbnb:n mobiiliverkkosovelluksen uudelleenrakentamisesta, jotta sivujen latausajat paranisivat huomattavasti, mikä on erityisen tärkeää käyttäjille, jotka käyttävät korkean viiveen mobiiliyhteyksiä. Rendr pyrkii olemaan pikemminkin kirjasto kuin kehys, joten se ratkaisee vähemmän ongelmia puolestasi verrattuna Mojitoon tai Meteoriin, mutta sitä on helppo muokata ja laajentaa.
Abstraktio, abstraktio, abstraktio
Mutta se, että nämä projektit ovat yleensä isoja, täysimittaisia web-kehyksiä, kertoo ongelman vaikeudesta. Asiakas ja palvelin ovat hyvin erilaisia ympäristöjä, joten meidän on luotava joukko abstraktioita, jotka irrottavat sovelluslogiikkamme taustalla olevista toteutuksista, jotta voimme tarjota sovelluskehittäjälle yhden ainoan API:n.
Reititys
Haluamme yhden ainoan joukon reittejä, jotka kartoittavat URI-malleja reittikäsittelijöille. Reittikäsittelijöidemme on voitava käyttää HTTP-otsikoita, evästeitä ja URI-tietoja sekä määritellä uudelleenohjauksia käyttämättä suoraan window.location (selain) tai req ja res (Node.js).
Datan noutaminen ja säilyttäminen
Tahdomme kuvata resurssit, joita tarvitaan tietyn sivun tai komponentin renderöintiin noutomekanismista riippumatta. Resurssikuvaaja voi olla yksinkertainen URI, joka osoittaa JSON-päätepisteeseen, tai suuremmissa sovelluksissa voi olla hyödyllistä kapseloida resurssit malleihin ja kokoelmiin ja määritellä malliluokka ja primääriavain, jotka jossain vaiheessa käännetään URI:ksi.
Näkymän renderöinti
Valitsemmeko suoran DOM-käsittelyn, pitäydymmekö merkkijonoon perustuvassa HTML-tapauksessa vai valitsemmeko UI-komponenttikirjaston, jossa on mukana DOM-abstraktio, meidän on pystyttävä tuottamaan merkintäkuvaus isomorfisesti. Meidän pitäisi pystyä renderöimään mikä tahansa näkymä joko palvelimella tai asiakkaalla sovelluksemme tarpeista riippuen.
Rakentaminen ja pakkaaminen
Kävi ilmi, että isomorfisen sovelluskoodin kirjoittaminen on vain puolet taistelusta. Gruntin ja Browserifyn kaltaiset työkalut ovat olennainen osa työnkulkua, jotta sovellus saadaan oikeasti toimimaan. Rakennusvaiheita voi olla useita: mallien kääntäminen, asiakaspuolen riippuvuuksien sisällyttäminen, muunnosten soveltaminen, pienentäminen jne. Yksinkertaisimmillaan kaikki sovelluskoodi, näkymät ja mallit yhdistetään yhteen nippuun, mutta suuremmissa sovelluksissa tämä voi johtaa satojen kilotavujen lataamiseen. Edistyneempi lähestymistapa on luoda dynaamisia niputuksia ja ottaa käyttöön asset lazy-loading, mutta tästä tulee nopeasti monimutkaista. Espriman kaltaiset staattisen analyysin työkalut voivat antaa kunnianhimoisille kehittäjille mahdollisuuden kokeilla kehittynyttä optimointia ja metaohjelmointia boilerplate-koodin vähentämiseksi.
Pienten moduulien yhteen kokoaminen
Ensimmäisenä markkinoille pääseminen isomorfisen kehyksen kanssa tarkoittaa sitä, että sinun on ratkaistava kaikki nämä ongelmat kerralla. Tämä johtaa kuitenkin suuriin, kömpelöihin kehyksiin, joita on vaikea ottaa käyttöön ja integroida jo olemassa olevaan sovellukseen. Kun yhä useammat kehittäjät tarttuvat tähän ongelmaan, tulemme näkemään räjähdysmäisen määrän pieniä, uudelleenkäytettäviä moduuleja, jotka voidaan integroida toisiinsa isomorfisten sovellusten rakentamiseksi.
On käynyt ilmi, että useimpia JavaScript-moduuleja voidaan jo nyt käyttää isomorfisesti vain vähän tai ei lainkaan muuttamalla. Esimerkiksi suosittuja kirjastoja, kuten Underscorea, Backbone.js:ää, Handlebars.js:ää, Momentia ja jopa jQueryä voidaan käyttää palvelimella.
Tämän asian havainnollistamiseksi olen luonut esimerkkisovelluksen nimeltä isomorphic-tutorial, johon voit tutustua GitHubissa. Yhdistämällä yhteen muutamia moduuleja, joista jokaista voidaan käyttää isomorfisesti, on helppo luoda yksinkertainen isomorfinen sovellus vain muutamalla sadalla rivillä koodia. Se käyttää Directoria palvelin- ja selainpohjaiseen reititykseen, Superagentia HTTP-pyyntöihin ja Handlebars.js:ää templatointiin, ja kaikki tämä on rakennettu perus Express.js-sovelluksen päälle. Sovelluksen monimutkaisuuden kasvaessa on tietysti otettava käyttöön lisää abstraktiokerroksia, mutta toivon, että kun yhä useammat kehittäjät kokeilevat tätä, syntyy uusia kirjastoja ja standardeja.
Näkymä täältä
Kun yhä useammat organisaatiot alkavat käyttää Node.js:ää tuotannossa, on väistämätöntä, että yhä useammat web-sovellukset alkavat jakamaan koodia asiakas- ja palvelinkoodin välillä. On tärkeää muistaa, että isomorfinen JavaScript on spektri – se voi alkaa pelkkien mallien jakamisesta, edetä koko sovelluksen näkymäkerrokseksi aina sovelluksen suurimpaan osaan liiketoimintalogiikasta. Se, mitä ja miten JavaScript-koodia jaetaan ympäristöjen välillä, riippuu täysin rakennettavasta sovelluksesta ja sen ainutlaatuisista rajoitteista.
Nicholas C. Zakasilla on hieno kuvaus siitä, miten hän näkee sovellusten alkavan vetää käyttöliittymäkerroksensa palvelimelle asiakkaalta, mikä mahdollistaa suorituskyvyn ja ylläpidettävyyden optimoinnin. Sovelluksen ei tarvitse repiä backendiä irti ja korvata sitä Node.js:llä käyttääkseen isomorfista JavaScriptiä, jolloin lapsi heitetään pois pesuveden mukana. Sen sijaan luomalla järkeviä API:ita ja RESTful-resursseja perinteinen backend voi elää Node.js-kerroksen rinnalla.
Airbnb:ssä olemme jo alkaneet muokata asiakaspuolen rakentamisprosessiamme käyttämään Node.js-pohjaisia työkaluja, kuten Gruntia ja Browserifyta. Pääasiallinen Rails-sovelluksemme ei ehkä koskaan korvaudu kokonaan Node.js-sovelluksella, mutta ottamalla nämä työkalut käyttöön on yhä helpompaa jakaa tiettyjä JavaScriptin osia ja malleja ympäristöjen välillä.
Olet kuullut sen täältä ensimmäisenä – muutaman vuoden sisällä on harvinaista nähdä kehittynyttä web-sovellusta, jossa ei käytetä jonkin verran JavaScriptiä palvelimella.
Learn More
Jos tämä ajatus innostaa sinua, tule tutustumaan Isomorphic JavaScript -työpajaan, jota opetan DevBeatissa tiistaina 12. marraskuuta San Franciscossa, tai General Assemblyssä torstaina 21. marraskuuta. Työskentelemme yhdessä luomani Node.js-isomorfisen tutorial-sovelluksen esimerkin parissa osoittaaksemme, kuinka helppoa isomorfisten sovellusten kirjoittaminen on.
Seuraa myös Airbnb:n web-sovellusten kehitystä seuraamalla minua osoitteessa @spikebrehm ja Airbnb:n insinööritiimiä osoitteessa @AirbnbEng.