By Spike Brehm
This post has been cross-posted on VentureBeat.
Az Airbnb-nél sokat tanultunk az elmúlt évek során, miközben gazdag webes élményeket építettünk. Az egyoldalas alkalmazások világába 2011-ben vetettük bele magunkat a mobil weboldalunkkal, azóta pedig többek között elindítottuk a kívánságlistákat és az újonnan áttervezett keresőoldalunkat. Ezek mindegyike egy-egy nagy JavaScript-alkalmazás, ami azt jelenti, hogy a kód nagy része a böngészőben fut a modernebb, interaktívabb élmény támogatása érdekében.
Ez a megközelítés ma már általános, és az olyan könyvtárak, mint a Backbone.js, az Ember.js és az Angular.js megkönnyítették a fejlesztők számára az ilyen gazdag JavaScript-alkalmazások létrehozását. Azt tapasztaltuk azonban, hogy az ilyen típusú alkalmazásoknak van néhány kritikus korlátja. Hogy elmagyarázzuk, miért, először tegyünk egy gyors kitérőt a webes alkalmazások történetén keresztül.
- JavaScript Grows Up
- Az egyoldalas alkalmazás
- SEO
- Teljesítmény
- Fenntarthatóság
- Hibrid megközelítés
- Izomorf JavaScript a vadonban
- Abstrahálás, absztrakció, absztrakció, absztrakció
- Routing
- Adatok lekérése és tárolása
- Nézet megjelenítés
- Építés és csomagolás
- Kis modulok összeillesztése
- A kilátás innen
- Tudj meg többet
JavaScript Grows Up
A web hajnala óta a böngészési élmény a következőképpen működött: a webböngésző egy adott oldalt (mondjuk “http://www.geocities.com/”) kért, aminek hatására egy szerver valahol az interneten létrehozott egy HTML-oldalt, és visszaküldte azt a vezetéken keresztül. Ez azért működött jól, mert a böngészők nem voltak túl erősek, és a HTML-oldalak többnyire statikus és önálló dokumentumokat jelentettek. A JavaScript, amelyet azért hoztak létre, hogy a weboldalak dinamikusabbak legyenek, nem tett lehetővé sokkal többet, mint a képi diavetítéseket és a dátumválasztó widgeteket.
A személyi számítógépek fejlődésének évei után a kreatív technológusok a web határait feszegették, és a webböngészők is fejlődtek, hogy lépést tartsanak vele. Mára a web teljes körű alkalmazásplatformmá érett, a gyors JavaScript-futtatási idők és a HTML5-szabványok pedig lehetővé tették a fejlesztők számára, hogy olyan gazdag alkalmazásokat hozzanak létre, amelyek korábban csak natív platformokon voltak lehetségesek.
Az egyoldalas alkalmazás
Nem kellett sokáig várni arra, hogy a fejlesztők ezeket az új képességeket kihasználva teljes alkalmazásokat építsenek ki a böngészőben JavaScript segítségével. Az olyan alkalmazások, mint a Gmail, az egyoldalas alkalmazás klasszikus példája, azonnal reagálhattak a felhasználói interakciókra, nem kellett többé körutazást tenniük a szerverre egy új oldal megjelenítéséhez.
A Backbone.js, Ember.js és Angular.js könyvtárakat gyakran nevezik kliensoldali MVC (Model-View-Controller) vagy MVVM (Model-View-ViewModel) könyvtáraknak. A tipikus kliensoldali MVC architektúra valahogy így néz ki:
Az alkalmazáslogika nagy része (nézetek, sablonok, vezérlők, modellek, nemzetköziesítés stb.) a kliensben él, és az adatokért egy API-val beszél. A kiszolgáló bármilyen nyelven írható, például Ruby, Python vagy Java, és többnyire egy kezdeti, csupasz HTML-oldal kiszolgálásával foglalkozik. Miután a JavaScript-fájlokat a böngésző letöltötte, azok kiértékelésre kerülnek, és a kliensoldali alkalmazás inicializálódik, adatokat hív le az API-ból, és megjeleníti a HTML-oldal többi részét.
Ez nagyszerű a felhasználó számára, mert az alkalmazás kezdeti betöltése után az oldal frissítése nélkül támogatja a gyors navigációt az oldalak között, és ha jól csinálják, akár offline is működhet.
Ez azért nagyszerű a fejlesztő számára, mert az idealizált egyoldalas alkalmazásban a kliens és a szerver között egyértelműen elkülönülnek a problémák, ami elősegíti a szép fejlesztési munkafolyamatot, és megakadályozza, hogy túl sok logikát kelljen megosztani a kettő között, amelyek gyakran különböző nyelveken íródnak.
A gyakorlatban azonban van néhány végzetes hibája ennek a megközelítésnek, ami megakadályozza, hogy sok felhasználási esetre megfelelő legyen.
SEO
Egy olyan alkalmazás, amely csak a kliensoldalon futhat, nem tud HTML-t szolgáltatni a lánctalpasoknak, így alapból rossz lesz a SEO-ja. A webkúszók úgy működnek, hogy kérést intéznek egy webkiszolgálóhoz, és értelmezik az eredményt; de ha a kiszolgáló egy üres oldalt küld vissza, az nem sokat ér. Vannak megoldási lehetőségek, de nem anélkül, hogy átugranánk néhány karikát.
Teljesítmény
Ugyanígy, ha a kiszolgáló nem egy teljes HTML-oldalt renderel, hanem a kliensoldali JavaScriptre vár, a felhasználók néhány kritikus másodpercig üres oldalt vagy betöltő spinnert fognak tapasztalni, mielőtt meglátnák az oldalon lévő tartalmat. Rengeteg tanulmány mutatja, hogy egy lassú oldal milyen drasztikus hatással van a felhasználókra, és ezáltal a bevételekre. Az Amazon azt állítja, hogy az oldal betöltési idejének minden 100 ms-os csökkenése 1%-kal növeli a bevételt. A Twitter egy évet és 40 mérnököt töltött azzal, hogy a kliens helyett a szerveren renderelje az oldalát, és azt állította, hogy az érzékelt betöltési idő 5-szörösére javult.
Fenntarthatóság
Míg az ideális esetben a gondok szép, tiszta szétválasztása valósulhat meg, elkerülhetetlen, hogy az alkalmazási logika vagy a nézeti logika egyes részei duplikálódjanak a kliens és a szerver között, gyakran különböző nyelveken. Gyakori példák erre a dátum- és pénznemformázás, űrlapérvényesítés és útválasztási logika. Ez rémálommá teszi a karbantartást, különösen az összetettebb alkalmazások esetében.
Egyes fejlesztők, köztük én is, úgy érzik, hogy megharapott ez a megközelítés – gyakran csak azután, hogy időt és energiát fektettek egy egyoldalas alkalmazás létrehozásába, válik világossá, hogy mik a hátrányai.
Hibrid megközelítés
A nap végén valójában az új és a régi megközelítés hibridjét szeretnénk: a teljesítmény és a SEO érdekében teljesen formázott HTML-t szeretnénk kiszolgálni a szerverről, de szeretnénk a kliensoldali alkalmazáslogika sebességét és rugalmasságát.
Ezért az Airbnb-nél “izomorfikus JavaScript” alkalmazásokkal kísérleteztünk, amelyek olyan JavaScript alkalmazások, amelyek kliens- és szerveroldalon egyaránt futtathatók.
Egy izomorfikus alkalmazás így nézhet ki, amit itt “kliens-szerver MVC”-nek nevezünk:
Ebben a világban az alkalmazás és a nézeti logika egy része a szerveren és a kliensen is végrehajtható. Ez mindenféle ajtót megnyit – teljesítményoptimalizálás, jobb karbantarthatóság, SEO-by-default és több állapotú webalkalmazások.
A Node.js, egy gyors, stabil szerveroldali JavaScript-futtatási idővel most már valóra válthatjuk ezt az álmot. A megfelelő absztrakciók létrehozásával úgy írhatjuk meg az alkalmazáslogikánkat, hogy az mind a szerveren, mind a kliensen fut – ez az izomorf JavaScript definíciója.
Izomorf JavaScript a vadonban
Az ötlet nem új – a Nodejitsu 2011-ben írt egy nagyszerű leírást az izomorf JavaScript architektúráról -, de lassan terjedt el. Néhány izomorf keretrendszer már felbukkant.
A Mojito volt az első nyílt forráskódú izomorf keretrendszer, amely kapott némi sajtót. Ez egy fejlett, full-stack Node.js alapú keretrendszer, de a YUI-tól való függősége és a Yahoo!-specifikus furcsaságai nem vezettek nagy népszerűséghez a JavaScript közösségben, mióta 2012 áprilisában nyílt forráskódúvá tették.
A Meteor valószínűleg a legismertebb izomorf projekt ma. A Meteor az alapoktól kezdve a valós idejű alkalmazások támogatására épül, és a csapat egy egész ökoszisztémát épít a csomagkezelő és a telepítési eszközök köré. A Mojitóhoz hasonlóan ez is egy nagy, véleményes Node.js keretrendszer, azonban sokkal jobban sikerült bevonni a JavaScript közösséget, és a várva várt 1.0-s kiadása már a küszöbön áll. A Meteor egy olyan projekt, amelyet érdemes figyelemmel kísérni – egy sztárcsapattal rendelkezik, és 11,2 millió dollárt gyűjtött az Andreessen Horowitz-tól – ami hallatlan egy olyan cég esetében, amely teljes mértékben egy nyílt forráskódú termék kiadására összpontosít.
Asana, a Facebook társalapítója, Dustin Moskovitz által alapított feladatkezelő alkalmazás érdekes izomorf történettel rendelkezik. Mivel Moskovitz a világ legfiatalabb milliárdosa, az Asana éveket töltött az R&D-ben a zárt forráskódú Luna keretrendszerük fejlesztésével, amely az izomorf JavaScript egyik legfejlettebb példája. A Luna, amely eredetileg a v8cgi-re épült a Node.js létezése előtti időkben, lehetővé teszi, hogy az alkalmazás egy teljes másolata fusson a szerveren minden egyes felhasználói munkamenethez. Minden egyes felhasználó számára külön szerverfolyamatot futtat, ugyanazt a JavaScript-alkalmazáskódot hajtja végre a szerveren, ami a kliensben is fut, lehetővé téve a fejlett optimalizációk egész osztályát, például a robusztus offline támogatást és a gyors valós idejű frissítéseket.
Ez év elején elindítottunk egy saját izomorf könyvtárat. Rendr néven lehetővé teszi egy Backbone.js + Handlebars.js egyoldalas alkalmazás készítését, amely a szerveroldalon is teljes mértékben megjeleníthető. A Rendr az Airbnb mobil webes alkalmazásának átépítésével szerzett tapasztalatainkból származik, hogy drasztikusan javítsuk az oldalletöltési időt, ami különösen fontos a nagy késleltetésű mobilkapcsolatokat használó felhasználók számára. A Rendr inkább könyvtár, mint keretrendszer igyekszik lenni, így a Mojitóhoz vagy a Meteorhoz képest kevesebb problémát old meg helyettünk, de könnyen módosítható és bővíthető.
Abstrahálás, absztrakció, absztrakció, absztrakció
Az, hogy ezek a projektek általában nagy, full-stack webes keretrendszerek, a probléma nehézségéről árulkodik. A kliens és a szerver nagyon különböző környezetek, ezért olyan absztrakciókat kell létrehoznunk, amelyek szétválasztják az alkalmazási logikánkat a mögöttes implementációktól, így egyetlen API-t tehetünk közzé az alkalmazásfejlesztő számára.
Routing
Egyetlen útvonalkészletet akarunk, amely az URI-mintákat útvonalkezelőkhöz képezi le. Az útvonalkezelőinknek képesnek kell lenniük a HTTP fejlécekhez, a cookie-khoz és az URI-információkhoz való hozzáférésre, valamint az átirányítások megadására anélkül, hogy közvetlenül hozzáférnének a window.location (böngésző) vagy a req és res (Node.js) adataihoz.
Adatok lekérése és tárolása
A lekérési mechanizmustól függetlenül szeretnénk leírni egy adott oldal vagy komponens megjelenítéséhez szükséges erőforrásokat. Az erőforrás-leíró lehet egy egyszerű URI, amely egy JSON végpontra mutat, vagy nagyobb alkalmazások esetében hasznos lehet az erőforrásokat modellekbe és gyűjteményekbe kapszulázni, és megadni egy modellosztályt és egy elsődleges kulcsot, amelyet egy bizonyos ponton lefordítanak egy URI-ra.
Nézet megjelenítés
Mindegy, hogy a DOM-ot közvetlenül manipuláljuk, ragaszkodunk a karakterlánc-alapú HTML templatinghoz, vagy egy DOM-absztrakcióval rendelkező UI-komponenskönyvtárat választunk, a jelölést izomorf módon kell tudnunk generálni. Képesnek kell lennünk bármilyen nézetet megjeleníteni akár a kiszolgálón, akár a kliensen, az alkalmazásunk igényeitől függően.
Építés és csomagolás
Kiderült, hogy az izomorf alkalmazáskód megírása csak a csata fele. Az olyan eszközök, mint a Grunt és a Browserify nélkülözhetetlen részei a munkafolyamatnak, hogy az alkalmazás ténylegesen működőképes legyen. Számos build lépés lehet: sablonok fordítása, kliensoldali függőségek bevonása, transzformációk alkalmazása, minifikálás stb. Egyszerű esetben az összes alkalmazáskódot, nézetet és sablont egyetlen csomagba kell egyesíteni, de nagyobb alkalmazások esetében ez több száz kilobájtos letöltést eredményezhet. Egy fejlettebb megközelítés a dinamikus csomagok létrehozása és az eszközök lusta betöltésének bevezetése, ez azonban gyorsan bonyolulttá válik. Az olyan statikus elemző eszközök, mint az Esprima, lehetővé tehetik az ambiciózus fejlesztők számára, hogy fejlett optimalizálással és metaprogramozással próbálkozzanak a boilerplate kód csökkentése érdekében.
Kis modulok összeillesztése
Az izomorf keretrendszerrel elsőként piacra lépni azt jelenti, hogy egyszerre kell megoldani ezeket a problémákat. Ez azonban nagy, nehézkes keretrendszerekhez vezet, amelyeket nehéz elfogadni és integrálni egy már meglévő alkalmazásba. Ahogy egyre több fejlesztő foglalkozik ezzel a problémával, a kis, újrafelhasználható modulok robbanásszerű terjedését fogjuk látni, amelyek egymással integrálva izomorf alkalmazásokat hozhatnak létre.
Kiderült, hogy a legtöbb JavaScript modul már most is izomorfikusan használható kevés módosítással. Például az olyan népszerű könyvtárak, mint az Underscore, a Backbone.js, a Handlebars.js, a Moment és még a jQuery is használhatóak a szerveren.
Azért, hogy ezt demonstráljam, létrehoztam egy izomorfikus-tutorial nevű mintaalkalmazást, amelyet a GitHubon megnézhetsz. Néhány modul kombinálásával, amelyek mindegyike izomorfikusan használható, könnyen létrehozható egy egyszerű izomorfikus alkalmazás mindössze néhány száz sornyi kóddal. A Director-t használja a szerver- és böngészőalapú útválasztáshoz, a Superagent-et a HTTP-kérésekhez és a Handlebars.js-t a templatinghez, mindezt egy alap Express.js alkalmazás tetejére építve. Természetesen, ahogy egy alkalmazás összetettsége növekszik, egyre több absztrakciós réteget kell bevezetni, de reményeim szerint, ahogy egyre több fejlesztő kísérletezik ezzel, új könyvtárak és szabványok fognak megjelenni.
A kilátás innen
Amint egyre több szervezet kezdi el kényelmesen futtatni a Node.js-t a termelésben, elkerülhetetlen, hogy egyre több webes alkalmazás kezdjen kódot megosztani a kliens- és a szerverkód között. Fontos megjegyezni, hogy az izomorf JavaScript egy spektrum – kezdődhet csupán sablonok megosztásával, fejlődhet egy teljes alkalmazás nézeti rétegévé, egészen az alkalmazás üzleti logikájának nagy részéig. Az, hogy pontosan mit és hogyan osztunk meg JavaScript-kódot a környezetek között, teljes mértékben az épülő alkalmazástól és annak egyedi korlátaitól függ.
Nicholas C. Zakas szépen leírja, hogy elképzelései szerint az alkalmazások a kliensről a szerverre kezdik lefelé húzni a felhasználói felület rétegüket, ami lehetővé teszi a teljesítmény és a karbantarthatóság optimalizálását. Egy alkalmazásnak nem kell kitépnie a backendjét és Node.js-re cserélnie ahhoz, hogy izomorf JavaScriptet használjon, lényegében a fürdővízzel együtt kidobva a gyereket is. Ehelyett ésszerű API-k és RESTful erőforrások létrehozásával a hagyományos backend a Node.js réteg mellett élhet.
Az Airbnb-nél már elkezdtük átalakítani az ügyféloldali építési folyamatunkat, hogy olyan Node.js alapú eszközöket használjunk, mint a Grunt és a Browserify. A fő Rails-alkalmazásunkat talán soha nem fogja teljesen kiszorítani egy Node.js alkalmazás, de ezeknek az eszközöknek a használatával egyre könnyebbé válik a JavaScript és a sablonok bizonyos részeinek megosztása a környezetek között.
Itt hallottad először – néhány éven belül ritka lesz az olyan fejlett webes alkalmazás, amelyben nem fut JavaScript a szerveren.
Tudj meg többet
Ha ez az elképzelés izgat, gyere el az Isomorphic JavaScript workshopra, amelyet november 12-én, kedden San Franciscóban a DevBeat-en, vagy november 21-én, csütörtökön a General Assembly-n tartok. Együtt fogunk hackelni az általam készített Node.js izomorfikus mintaalkalmazáson, hogy bemutassuk, milyen egyszerű elkezdeni izomorfikus alkalmazásokat írni.
Az Airbnb webes alkalmazásainak fejlődését is nyomon követheted, ha követsz engem a @spikebrehm és az Airbnb Engineering csapatát a @AirbnbEng címen.