Kolme kuukautta MeteorJS:ää

Menneen kolmen kuukauden aikana Summer @ Vincity -tiimi on toteuttanut web-sovellusta käyttäen MeteorJS-websovelluskehystä. Kesän aikana tiimi on saanut paljon kokemusta Meteorin moninaisista ominaisuuksista ja ongelmista. Tässä artikkelissa on tarkoitus jakaa tärkeimmät näistä opetuksista.

Summer @ Vincity -projekti

Projekti lähti käyntiin toukokuun viimeisellä viikolla lähinnä projektin aiheeseen tutustumisella ja sovelluksen toiminnallisuuden suunnittelulla. Projektin ennakkosuunnittelussa oli päädytty käyttämään uudenkarheaa Meteor-sovelluskehystä sen tarjoamien ominaisuuksien takia. Käytimme alussa paljon aikaa tutustuaksemme tähän tiimin jäsenille tuntemattomaan ympäristöön.

Sovelluksen kehitys lähti nopeasti täyteen vauhtiin. Huomasimme, että Meteorilla on erittäin nopea toteuttaa ominaisuuksia ja käyttöliittymiä helposti käytettävien tietokanta- ja template-rajapintojen avulla. Kesäkuun lopussa olimme jo julkaisseet ensimmäisen version julkiseen testikäyttöön.

Mikä on Meteor?

Meteor on web-sovellusten kehittämiseen tarkoitettu avoimen lähdekoodin sovelluskehys. Se on niin sanottu full stack framework eli se haluaa tarjota kaiken tärkeän toiminnallisuuden web-sovelluksen kehittämiseen. Verrattuna moniin muihin samantyyppisiin sovelluskehyksiin, Meteorilla on nopea lähteä kehittämään SPA-sovellusta.

Tällaisen sovelluksen hyötynä on, että käyttöliittymä vastaa käyttäjän syötteeseen ilman viivettä. Selaimen ei tarvitse odottaa palvelimen lähettäessä uutta sivua, vaan toiseen näkymään voidaan siirtyä viiveettä. Tämä parantaa sovelluksen käyttökokemusta huomattavasti. Tiedonsiirrossa ei myöskään tarvitse olla mukana HTML-markuppia, joten tiedon siirtäminenkin tapahtuu nopeammin. Asiakkaalle siirretään vain HTML-dokumentin runko, jota asiakkaan selain muokkaa tarpeen mukaan.

Meteor tuo tähän pakkaan vielä useita lisäominaisuuksia. Palvelinyhteyden viive saadaan piilotettua client-side-tietokantarajapinnan ansiosta. Sekä asiakaspään että palvelimen koodi voidaan kirjoittaa JavaScriptillä. Reaktiiviset rajapinnat mahdollistavat näkymien automaattisen päivittymisen heti, kun datassa tapahtuu muutoksia. Meteorissa on myös helppo käyttää ulkopuolisia kirjastoja paketinhallinnan avulla. Lisäksi kehityksen aikana Meteor tarkkailee koodikannassa tapahtuvia muutoksia ja automaattisesti päivittää ajettavaa järjestelmää, joten testaaminen on nopeaa.

Todellisuus iskee

Projektin lähdettyä liikkeelle, tilanne ei kuitenkaan säilynyt ruusuisena. Ennen pitkää uusien ominaisuuksien toteuttamisesta tuli vaikeaa. Eri ominaisuuksien riippuvuudet toisistaan aiheuttivat usein ylimääräistä työtä.

Vaikka projekti edelleen eteni hyvää vauhtia, rupesimme huomaamaan nopean aloituksen huonot puolet. Hyvien arkkitehtuuriesimerkkien puutteen vuoksi suunnittelu ja toteutus tapahtui lähinnä yrityksen ja erehdyksen kautta. Koodissa ei ollut juurikaan rakennetta, jonka takia asiat olivat vahvasti riippuvaisia toisistaan. Pikkuhiljaa aloimme selvittelemään miten tämän tyyppiseen ja kokoiseen projektiin saisi lisättyä modulaarisuutta.

Mitä opimme

Sovelluksen rakenne tulisi pyrkiä ottamaan huomioon alusta alkaen. Tämä pätee tietenkin kaikkeen ohjelmistokehitykseen, mutta Meteorilla on erittäin helppo alkaa tehdä nopeasti huonoa koodia. Erityisesti seuraamalla Meteorin omien esimerkkien viitoittamaa tietä törmää ennen pitkää riippuvuusongelmiin.

Jos sovelluksen tarvitsee manipuloida dataa usein, kannattaa tutustua Underscore-kirjaston tarjoamiin apufunktioihin. Nämä erittäin usein helpottavat listojen, objektien ja funktioiden kanssa toimimista. Underscore on aina saatavilla Meteor-projektissa, koska Meteor käyttää sitä myös sisäisesti.

Template-instanssit

Yksi tapa saada rakennetta koodiin on käyttää template-instansseja tietyn näkymän tarvitsemien funktioiden ja datan säilyttämiseen. Template-instanssiin pääsee käsiksi ns. lifecycle callbackeissä nimiltään created, rendered ja destroyed avainsanan this avulla:

Template.mainView.created = function() {
    this.buttonHasBeenClicked = false;
}

Sama onnistuu myös templaten tapahtumankäsittelijöissä lisäämällä niihin tapahtumaobjektin lisäksi toisen parametrin:

'click button': function(event, template) {
    template.buttonHasBeenClicked = true;
}

Template-instanssien käyttämisen ongelmana (ainakin nykyisessä versiossa) on, että niihin ei pääse käsiksi templaten helper-funktioista. Tämän ongelman korjaamiseen voisi yrittää esimerkiksi käyttää Underscore-kirjaston bind-funktiota, joka luo funktiosta version, jossa annettu objekti on liitetty funktion kontekstiksi. Tämän tyyppinen ratkaisu kuitenkin monimutkaistaa koodia merkittävästi eikä sen takia ole yleensä hyödyllinen.

Reaktiivisuus

Reaktiivisuus on yksi Meteorin tärkeimmistä ominaisuuksista, ja se vaikuttaa koodin rakenteeseen todella paljon. Reaktiivisuus ei ole vain sitä, että datan muuttuessa palvelimella käyttöliittymä automaattisesti päivitetään vastaamaan sitä dataa. Se tarkoittaa myös, että kun asiakaspäässä muutetaan sovelluksen sisäistä tilaa, nämä muutokset päivittyvät käyttöliittymään.

Meteorin omat esimerkit kannustavat käyttämään sessiomuuttujia erittäin vapaasti. Ne ovat erittäin käteviä silloin kun tarvitaan reaktiivisuutta, mutta aiheuttavat helposti paljon riippuvuuksia, jos niitä käytetään liian usein.

Sessiomuuttujien käyttämisen tehokkuutta voidaan parantaa erittäin helposti. Usein sessiomuuttuja haetaan tarkasteltavaksi, jotta voidaan tietää onko siinä jokin tietty arvo. Meteorin sessiomuuttujien dokumentaatiosta voidaan huomata, että

Session.equals('primaryView', 'mainView')

on parempi vaihtoehto kuin

Session.get('primaryView') === 'mainView'

Kun käytetään Session.equals, sitä käyttävä reaktiivinen funktio invalidoidaan vain kun equals-funktion palauttama tulos muuttuu, kun taas get-funktiota käytettäessä, reaktiivinen funktio invalidoidaan aina kun sessiomuuttujan arvo muuttuu.

Kun reaktiivisia rajapintoja käytetään esimerkiksi templateista, ne rekisteröivät riippuvuuksia. Nämä rajapinnat sitten ilmoittavat muutoksista, jolloin reaktiivista koodia käyttänyt funktio invalidoidaan ja ajetaan uudelleen; tässä tapauksessa template renderöidään uudestaan. Reaktiivisesti uudelleen ajettavaa koodia voi templateiden lisäksi olla Meteorin tarjoamaan Deps.autorun-funktioon lisättynä, joka helposti aiheuttaa lisää invalidointeja ja koodia ajetaan tämän takia turhaan useaan kertaan.

Pieni ongelma turhassa sessiomuuttujien käytössä on koodin tehokkuus, isompi ongelma on niiden toimiminen globaaleina muuttujina. Lyhyesti sanottuna, sessiomuuttujia kannattaa käyttää vain kun tarvitaan reaktiivisuutta ja se yksinkertaistaa koodia.

Publish/subscribe

Meteorin tietokantarajapinnan hienoutena on, että sitä voi käyttää samalla tavalla sekä asiakaspäästä että palvelimelta. Meteorissa tietokantadata julkaistaan palvelimelta ja tilataan asiakkaalla. Asiakaspäässä pidetään paikallinen kopio palvelimelta tilatusta datasta, ja siihen voi tehdä tietokantakyselyitä samalla tavalla kuin palvelimellakin.

Meteor oletusarvoisesti julkaisee ja tilaa kaiken datan automaattisesti. Tämä toiminnallisuus kannattaa ottaa pois päältä poistamalla projektista autopublish-paketti.

Asiakkaalle tulevan datan rajoittaminen on tärkeä osa sovelluksen pitämistä helposti ja nopeasti käytettävänä. Kun sovellus on ladattu asiakkaalle, sovellus alkaa ladata tilattua dataa. Tässä kestävä aika vaikuttaa merkittävästi siihen kuinka nopeasti sovellus on käyttökelpoinen.

Datan rajoittaminen

Sovelluksessamme datamäärät ovat niin suuria, että ei ole edes mahdollista saada kaikkea dataa mahtumaan asiakkaalle. Reitit pitävät sisällään perustietojen lisäksi myös kaikki reitin muodostavat pisteet, joita voi reitistä riippuen olla muutamista kymmenistä useisiin satoihin. Kun reittejä voi olla palvelussa jopa tuhansia, tarvitsee datan tilaamiseen käyttää erittäin tarkkaa rajausta.

Käytämme MongoDB:n $geoWithin- ja $geoIntersects-operaattoreita lähetettävän datan rajoittamiseen palvelimen julkaisufunktiossa. Kaikille reiteille on tallennettu niiden kattama alue ja paikoille on tallennettu niiden sijainti. Kun asiakas tilaa tämän julkaistun datajoukon, se antaa tilauksen mukana sillä hetkellä omalla ruudullaan näkyvän kartan alueen, jota käytetään datan rajaamiseen.

Reittien rajaaminen julkaisufunktiossa tapahtuu yksinkertaisuudessaan näin:

Meteor.publish('routes', function(geoJson) {
    return Sites.find({area: {$geoIntersects: {$geometry: geoJson}}});
});

Muuttuja geoJson on asiakkaan antama GeoJSON-polygoni, joka kuvaa nyt ruudulla näkyvää aluetta. Lisäksi kannattaa aina rajata julkaistavat kentät sen mukaan mitä kyseisessä tilanteessa tarvitaan. Sama julkaisufunktio kenttärajauksella:

Meteor.publish('routes', function(geoJson) {
    return Sites.find({area: {$geoIntersects: {$geometry: geoJson}}},
        {fields: {routeData: true, location: true, area: true, categories: true}});
});

Tämä julkaisu on tarkoitettu vain kartalla näkyvän datan tuomiseen asiakkaalle. Kun halutaan näyttää muita tietoja jostain kohteesta, kuten nimi tai kuvaus, tilataan toinen julkaisu vain yhdelle kohteelle joka on valittuna käyttöliittymässä. Tämä rajoitus vähentää tiedonsiirtoa ja asiakkaan selaimen käyttämää muistia radikaalisti.

Käyttämällä tällaista taktiikkaa kaikkeen dataan, voidaan sovelluksesta saada erittäin nopeasti käynnistyvä.

Yhteenveto

Meteor on vielä varhaisessa kehitysvaiheessa, mutta useimpien websovellusten toteuttaminen sillä onnistuu aivan mainiosti. Tämän projektin aikana on tietenkin tullut vastaan jonkin verran ongelmia, mutta kaikki on saatu ratkottua järkevässä ajassa.

Vaikka monesti Meteor-sovellusta kehittäessä huomaa, että jää kaipaamaan joitain ominaisuuksia, tästä projektista on jäänyt hyvä maku suuhun. Pääosin Meteorin kanssa kehittäminen on erittäin antoisaa puuhaa monien käytännön työtä helpottavien ominaisuuksien myötä.

Voimme suositella Meteoria single-page-sovellusten toteuttamiseen, koska se nopeuttaa kehittämistä erittäin paljon, kunhan muistaa ottaa huomioon laajuuden ja rakenteen projektia suunniteltaessa.

aleksigron

1 kommentti

Liity keskusteluun