Siirry sisältöön

e

Luokkakomponentit, Sekalaista

Luokkakomponentit

Olemme käyttäneet kurssilla ainoastaan JavaScript-funktioina määriteltyjä React-komponentteja. Tämä ei ollut mahdollista ennen Reactiin versiossa 16.8. tullutta hook-toiminnallisuutta, tällöin esimerkiksi tilaa käyttävät komponentit oli pakko määritellä käyttäen JavaScriptin Class-syntaksia.

Class-, eli luokkakomponentit on syytä tuntea ainakin jossain määrin, sillä maailmassa on suuri määrä vanhaa React-koodia, mitä ei varmaankaan koskaan tulla kokonaisuudessaan uudelleenkirjoittamaan uudella syntaksilla.

Tutustutaan nyt luokkakomponenttien tärkeimpiin ominaisuuksiin toteuttamalla jälleen kerran jo niin tuttu anekdoottisovellus. Talletetaan anekdootit json-serveriä hyödyntäen tiedostoon db.json. Tiedoston sisältö otetaan täältä.

Luokkakomponentin ensimmäinen versio näyttää seuraavalta

import React from 'react'

class App extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <div>
        <h1>anecdote of the day</h1>
      </div>
    )
  }
}

export default App

Komponentilla on nyt konstruktori missä ei toistaiseksi tehdä mitään sekä metodi render. Kuten arvata saattaa, render määrittelee sen miten komponentti piirtyy ruudulle.

Määritellään komponenttiin tila anekdoottien listalle sekä näkyvissä olevalle anekdootille. Toisin kuin useState-hookia käytettäessä, luokkakomponenteilla on ainoastaan yksi tila. Eli jos tila koostuu useista "osista", tulee osat tallettaa tilan kenttiin. Tila alustetaan konstruktorissa:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {      anecdotes: [],      current: 0    }  }

  render() {
    if (this.state.anecdotes.length === 0 ) {      return <div>no anecdotes...</div>
    }

    return (
      <div>
        <h1>anecdote of the day</h1>
        <div>
          {this.state.anecdotes[this.state.current].content}        </div>
        <button>next</button>
      </div>
    )
  }
}

Komponentin tila on siis instanssimuuttujassa this.state. Tila on olio, jolla on kaksi kenttää. this.state.anecdotes on anekdoottien lista ja this.state.current on näytettävän anekdootin indeksi.

Funktionaalisille komponenteille oikea paikka hakea palvelimella olevaa dataa ovat effect hookit. Effect hookit suoritetaan aina komponentin renderöitymisen yhteydessä tai tarvittaessa harvemmin, esim. ainoastaan ensimmäisen renderöinnin yhteydessä.

Luokkakomponenttien elinkaarimetodit tarjoavat vastaavan toiminnallisuuden. Oikea paikka käynnistää tietojen haku palvelimelta on elinkaarimetodi componentDidMount, joka suoritetaan kertaalleen heti komponentin ensimmäisen renderöitymisen jälkeen:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      anecdotes: [],
      current: 0
    }
  }

  componentDidMount = () => {    axios.get('http://localhost:3001/anecdotes').then(response => {      this.setState({ anecdotes: response.data })    })  }
  // ...
}

HTTP-pyynnön takaisinkutsufunktio päivittää komponentin tilaa metodilla setState. Metodi toimii siten, että se koskee tilassa ainoastaan niihin avaimiin, mitä parametrina olevassa oliossa on määritelty. Avain current jää siis entiseen arvoonsa.

Metodin setState kutsuminen aiheuttaa aina luokkakomponentin uudelleenrenderöinnin, eli metodin render kutsun.

Viimeistellään vielä komponentti siten, että näytettävä anekdootti on mahdollista vaihtaa. Seuraavassa koko komponentin koodi, lisäys korostettuna:

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      anecdotes: [],
      current: 0
    }
  }

  componentDidMount = () => {
    axios.get('http://localhost:3001/anecdotes').then(response => {
      this.setState({ anecdotes: response.data })
    })
  }

  handleClick = () => {    const current = Math.floor(      Math.random() * (this.state.anecdotes.length - 1)    )    this.setState({ current })  }
  render() {
    if (this.state.anecdotes.length === 0 ) {
      return <div>no anecdotes...</div>
    }

    return (
      <div>
        <h1>anecdote of the day</h1>
        <div>{this.state.anecdotes[this.state.current].content}</div>
        <button onClick={this.handleClick}>next</button>      </div>
    )
  }
}

Vertailun vuoksi sama sovellus funktionaalisena komponenttina:

const App = () => {
  const [anecdotes, setAnecdotes] = useState([])
  const [current, setCurrent] = useState(0)

  useEffect(() =>{
    axios.get('http://localhost:3001/anecdotes').then(response => {
      setAnecdotes(response.data)
    })
  },[])

  const handleClick = () => {
    setCurrent(Math.round(Math.random() * (anecdotes.length - 1)))
  }

  if (anecdotes.length === 0) {
    return <div>no anecdotes...</div>
  }

  return (
    <div>
      <h1>anecdote of the day</h1>
      <div>{anecdotes[current].content}</div>
      <button onClick={handleClick}>next</button>
    </div>
  )
}

Esimerkkimme tapauksessa erot eivät ole suuret. Suurin ero funktionaalisissa ja luokkakomponenteissa lienee se, että luokkakomponentin tila on aina yksittäinen olio, ja tilaa muutetaan metodin setState avulla kun taas funktionaalisessa komponentissa tila voi koostua useista muuttujista, joilla kaikilla on oma päivitysfunktio.

Hieman edistyneemmissä käyttöskenaarioissa effect hookit tarjoavat huomattavasti paremman mekanismin sivuvaikutusten hallintaan verrattuna luokkakomponenttien elinkaarimetodeihin.

Merkittävä etu funktionaalisten komponenttien käytössä on se, että paljon harmia tuottavaa JavaScriptin olioon itseensä viittaavaa this-viitettä ei tarvitse käsitellä ollenkaan.

Oman ja suuren enemmistön mielestä luokkakomponenteilla ei ole oikeastaan mitään etuja hookeilla rikastettuihin funktionaalisiin komponentteihin verrattuna, poikkeuksen tähän muodostaa ns. error boundary ‑mekanismi, joka ei ole toistaiseksi funktionaalisten komponenttien käytössä.

Kun kirjoitat uutta koodia, ei siis ole mitään rationaalista syytä käyttää luokkakomponentteja jos projektissa on käytössä Reactista vähintään versio 16.8. Toisaalta kaikkea vanhaa Reactia ei ole toistaiseksi mitään syytä uudelleenkirjoittaa funktionaalisina komponentteina.

React-sovelluksen koodin organisointi

Noudatimme useimmissa sovelluksissa periaatetta, missä komponentit sijoitettiin hakemistoon components, reducerit hakemistoon reducers ja palvelimen kanssa kommunikoiva koodi hakemistoon services. Tälläinen organisoimistapa riittää pienehköihin sovelluksiin, mutta komponenttien määrän kasvaessa tarvitaan muunlaisia ratkaisuja. Yhtä oikeaa tapaa ei ole, artikkeli The 100% correct way to structure a React app (or why there’s no such thing) tarjoaa näkökulmia aiheeseen.

Frontti ja backend samassa repositoriossa

Olemme kurssilla tehneet frontendin ja backendin omiin repositorioihinsa. Kyseessä on varsin tyypillinen ratkaisu. Teimme tosin deploymentin kopioimalla frontin bundlatun koodin backendin repositorion sisälle. Toinen, ehkä järkevämpi tilanne olisi ollut deployata frontin koodi erikseen.

Joskus voi kuitenkin olla tilanteita, missä koko sovellus halutaan samaan repositorioon. Tällöin yleinen ratkaisu on sijoittaa package.json ja webpack.config.js hakemiston juureen ja frontin sekä backendin koodi omiin hakemistoihinsa, esim. client ja server.

Palvelimella tapahtuvat muutokset

Jos palvelimella olevassa tilassa tapahtuu muutoksia, esim. blogilistapalveluun lisätään uusia blogeja muiden käyttäjien toimesta, tällä kurssilla tekemämme React-frontendit eivät huomaa muutoksia ennen sivujen uudelleenlatausta. Vastaava tilanne tulee eteen, jos frontendistä käynnistetään jotain kauemmin kestävää laskentaa backendiin, miten laskennan tulokset saadaan heijastettua frontendiin?

Eräs tapa on suorittaa frontendissa pollausta, eli toistuvia kyselyitä backendin APIin esim. setInterval-komennon avulla.

Edistyneempi tapa on käyttää WebSocketeja, joiden avulla on mahdollista muodostaa kaksisuuntainen kommunikaatiokanava selaimen ja palvelimen välille. Tällöin frontendin ei tarvitse pollata backendia, riittää määritellä takaisinkutsufunktiot tilanteisiin, joissa palvelin lähettää WebSocketin avulla tietoja tilan päivittämisestä.

WebSocketit ovat selaimen tarjoama rajapinta, jolla ei kuitenkaan ole kaikille selaimille vielä täyttä tukea:

fullstack content

WebSocket API:n suoran käyttämisen sijaan onkin suositeltavaa käyttää Socket.io-kirjastoa, joka tarjoaa erilaisia automaattisia fallback-mahdollisuuksia, jos käytettävässä selaimessa ei ole täyttä WebSocket-tukea.

Osan 8 aiheena GraphQL, joka tarjoaa valmiin toiminnallisuuden palvelimella tapahtuvien muutosten synkronoimiseksi muutoksista kiinnostuneihin frontendeihin.

Virtual DOM

Reactin yhteydessä mainitaan usein käsite Virtual DOM. Mistä oikein on kyse? Kuten osassa 0 mainittiin, selaimet tarjoavat DOM API:n, jota hyväksikäyttäen selaimessa toimiva JavaScript voi muokata sivun ulkoasun määritteleviä elementtejä.

Reactia käyttäessä ohjelmoija ei koskaan (tai parempi sanoa yleensä) manipuloi DOM:ia suoraan. React-komponentin määrittelevä funktio palauttaa joukon React-elementtejä. Vaikka osa elementeistä näyttää normaaleilta HTML-elementeiltä

const element = <h1>Hello, world</h1>

eivät nekään ole HTML:ää vaan pohjimmiltaan JavaScriptia olevia React-elementtejä.

Sovelluksen komponenttien ulkoasun määrittelevät React-elementit muodostavat Virtual DOM:in, joka pidetään suorituksen aikana keskusmuistissa.

ReactDOM-kirjaston avulla komponenttien määrittelevä virtuaalinen DOM renderöidään oikeaksi DOM:iksi eli DOM API:n avulla selaimen näytettäväksi:

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

Kun sovelluksen tila muuttuu, määrittyy komponenttien renderöinnin takia uusi virtuaalinen DOM. Reactilla on edellinen versio virtual DOM:ista muistissa ja sen sijaan että uusi virtuaalinen DOM renderöitäisiin suoraviivaisesti DOM API:n avulla, React laskee mikä on optimaalisin tapa tehdä DOM:iin muutoksia (eli poistaa, lisätä ja muokata DOM:issa olevia elementtejä) siten, että DOM saadaan vastaamaan uutta Virtual DOM:ia.

Reactin roolista sovelluksissa

Materiaalissa ei ole tuotu ehkä riittävän selkeästi esille sitä, että React on ensisijaisesti tarkoitettu näkymien luomisesta huolehtivaksi kirjastoksi. Jos ajatellaan perinteistä Model View Controller ‑jaottelua, on Reactin toimialaa juurikin View. React on siis sovellusalueeltaan suppeampi kuin esim. Angular, joka on kaiken tarjoava Frontendin MVC-sovelluskehys. Reactia ei kutsutakaan sovelluskehykseksi (framework) vaan kirjastoksi (library).

Pienissä sovelluksissa React-komponenttien tilaan talletetaan sovelluksen käsittelemää dataa, eli komponenttien tilan voi näissä tapauksissa ajatella vastaavan MVC:n modeleita.

React-sovellusten yhteydessä ei kuitenkaan yleensä puhuta MVC-arkkitehtuurista ja jos käytössä on Redux niin silloin sovellukset noudattavat Flux-arkkitehtuuria ja Reactin rooliksi jää entistä enemmän pelkkä näkymien muodostaminen. Varsinainen sovelluslogiikka hallitaan Reduxin tilan ja action creatorien avulla. Jos käytössä on osasta 6 tuttu redux thunk, on sovelluslogiikka mahdollista eristää lähes täysin React-koodista.

Koska sekä React että Flux ovat Facebookilla syntyneinä, voi ajatella, että Reactin pitäminen ainoastaan käyttöliittymästä huolehtivana kirjastona on sen oikeaoppista käyttöä. Flux-arkkitehtuurin noudattaminen tuo sovelluksiin tietyn overheadin ja jos on kyse pienestä sovelluksesta tai prototyypistä, saattaa Reactin "väärinkäyttäminen" olla järkevää, sillä myöskään overengineering ei yleensä johda optimaaliseen tulokseen.

Osan 6 viimeinen luku käsittelee Reactin tilanhallinnan uudempia virtauksia. Reactin suoraan tarjoamat hook-funktiot useReducer ja useContext tarjoavat eräänlaisen kevytversion Reduxille. React Query taas on kirjasto joka ratkaisee monia palvelimella olevan tilan käsittelyyn liittyviä pulmia, eliminoiden tarvetta sille, että React-sovelluksen tarvitsee tallettaa suoraan frontendin tilaan palvelimelta haettua dataa.

React/node-sovellusten tietoturva

Emme ole vielä maininneet kurssilla kuin muutaman sanan tietoturvaan liittyen. Kovin paljon ei nytkään ole aikaa, ja onneksi Helsingin Yliopistolla on MOOC-kurssi Securing Software tähän tärkeään aihepiiriin.

Katsotaan kuitenkin muutamaa kurssispesifistä seikkaa.

The Open Web Application Security Project eli OWASP julkaisee vuosittain listan Websovellusten yleisimmistä turvallisuusuhista. Tuorein lista on täällä. Samat uhat ovat listalla vuodesta toiseen.

Listaykkösenä on injection, joka tarkoittaa sitä, että sovellukseen esim. lomakkeen avulla lähetettävä teksti tulkitaankin aivan eri tavalla kun sovelluskehittäjä on tarkoittanut. Kuuluisin injektioiden muoto lienevät SQL-injektiot.

Esim. jos ei-turvallisessa koodissa tehtäisiin seuraavasti muotoiltu SQL-kysely:

let query = "SELECT * FROM Users WHERE name = '" + userName + "';"

Oletetaan että hieman ilkeämielinen käyttäjä Arto Hellas nyt määrittelisi nimekseen


Arto Hell-as'; DROP TABLE Users; --

eli nimi sisältäisi hipsun ', joka on SQL:ssä merkkijonon aloitus/lopetusmerkki. Tämän seurauksena tulisi suoritetuksi kaksi SQL-operaatiota, joista jälkimmäinen tuhoaisi tietokantataulun Users

SELECT * FROM Users WHERE name = 'Arto Hell-as'; DROP TABLE Users; --'

SQL-injektiot estetään parametrisoiduilla kyselyillä. Niissä käyttäjän syötettä ei lisätä lainkaan SQL-kyselyn sekaan, vaan tietokanta itse lisää syötearvot turvallisesti kyselyssä olevien paikkamerkkien (yleensä ?) paikalle.

execute("SELECT * FROM Users WHERE name = ?", [userName])

Myös NoSQL-kantoihin tehtävät injektiohyökkäykset ovat mahdollisia. Mongoose kuitenkin estää ne sanitoimalla kyselyt. Lisää aiheesta esim. täällä.

Cross-site scripting eli XSS on hyökkäys, missä sovellukseen on mahdollista injektoida suoritettavaksi vihollismielistä JavaScript-koodia. Jos kokeilemme injektoida esim. muistiinpanosovellukseen seuraavan

<script>
  alert('Evil XSS attack')
</script>

koodia ei suoriteta, vaan koodi renderöityy sivulle 'tekstinä':

fullstack content

sillä React huolehtii muuttujissa olevan datan sanitoinnista. Reactin jotkut versiot ovat mahdollistaneet XSS-hyökkäyksiä, aukot on toki korjattu, mutta mikään ei takaa etteikö niitä voisi vielä löytyä.

Käytettyjen kirjastojen suhteen tuleekin olla tarkkana, jos niihin tulee tietoturvapäivityksiä, on kirjastot syytä päivittää omissa sovelluksissa. Expressin tietoturvapäivitykset löytyvät kirjaston dokumentaatiosta ja Nodeen liittyvät blogista.

Riippuvuuksien ajantasaisuuden voi testata komennolla

npm outdated --depth 0

Vuoden vanha osassa 9 käytetty projekti sisältää jo aika paljon vanhentuneita riippuvuuksia:

fullstack content

Riippuvuudet saa ajantasaistettua päivittämällä tiedostoa package.json. Sitä helpottaa npm-check-updates-niminen työkalu, joka asennetaan globaalisti komennolla

npm install -g npm-check-updates

Tämän työkalun avulla tarkistetaan riippuvuuksien ajantasaisuus seuraavasti:

$ npm-check-updates
Checking ...\ultimate-hooks\package.json
[====================] 9/9 100%

 @testing-library/react       ^13.0.0  →  ^13.1.1
 @testing-library/user-event  ^14.0.4  →  ^14.1.1
 react-scripts                  5.0.0  →    5.0.1

Run ncu -u to upgrade package.json

Tiedosto package.json päivitetään suorittamalla komento ncu -u.

$ ncu -u
Upgrading ...\ultimate-hooks\package.json
[====================] 9/9 100%

 @testing-library/react       ^13.0.0  →  ^13.1.1
 @testing-library/user-event  ^14.0.4  →  ^14.1.1
 react-scripts                  5.0.0  →    5.0.1

Run npm install to install new versions.

Seuraavaksi ajantasaistetaan riippuvuudet suorittamalla komento npm install. Riippuvuuksien vanhat versiot eivät tietenkään välttämättä ole tietoturvariski.

Riippuvuuksien turvallisuus voidaan tarkistaa npm:n audit-komennolla, joka vertaa käytettyjen riippuvuuksien versioita keskitetyssä virhetietokannassa listattuihin tietoturvauhan sisältäviin riippuvuuksien versioihin.

Komennon npm audit suorittaminen samalle projektille antaa pitkän listan valituksia ja korjausehdotuksia. Seuraavassa osa raportista:

$ npm audit

... many lines removed ...

url-parse  <1.5.2
Severity: moderate
Open redirect in url-parse - https://github.com/advisories/GHSA-hh27-ffr2-f2jc
fix available via `npm audit fix`
node_modules/url-parse

ws  6.0.0 - 6.2.1 || 7.0.0 - 7.4.5
Severity: moderate
ReDoS in Sec-Websocket-Protocol header - https://github.com/advisories/GHSA-6fc8-4gx4-v693
ReDoS in Sec-Websocket-Protocol header - https://github.com/advisories/GHSA-6fc8-4gx4-v693
fix available via `npm audit fix`
node_modules/webpack-dev-server/node_modules/ws
node_modules/ws

120 vulnerabilities (102 moderate, 16 high, 2 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Reilun vuoden ikäinen koodi on siis täynnä pieniä tietoturvauhkia, kriittisiä uhkia on onneksi ainoastaan 2. Suoritetaan raportin suosittelema operaatio npm audit fix:

$ npm audit fix

104 vulnerabilities (93 moderate, 9 high, 2 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Haavoittuvuuksia jää vielä 104, sillä audit fix ei tee oletusarvoisesti versiopäivitystä kirjastolle, jonka major-versionumero on kasvanut. Tälläisen riippuvuuden päivitys saattaa aiheuttaa sovelluksen hajoamisen.

Kriittisten virheiden lähde on kirjasto immer

immer  <9.0.6
Severity: critical
Prototype Pollution in immer - https://github.com/advisories/GHSA-33f9-j839-rf8h
fix available via `npm audit fix --force`
Will install react-scripts@5.0.0, which is a breaking change

Komento npm audit fix --force päivittäisi käytetyn kirjastoversion, mutta emme nyt uskalla tehdä päivistystä sillä se aiheuttaisi react-scripts-kirjaston uuden version asennuksen ja se taas todennäköisesti hajottaisi sovelluskehitysympäristön. Jätämme siis tällä kertaa mahdollisten haavoittuvuuksien korjaamisen myöhempään kertaan.

Eräs OWASP:in listan mainitsemista uhista on Broken Authentication ja siihen liittyvä Broken Access Control. Käyttämämme token-perustainen autentikointi on kohtuullisen robusti, jos sovellusta käytetään tietoliikenteen salaavalla HTTPS-protokollalla. Access Controlin eli pääsynhallinnan toteuttamisessa on aina syytä muistaa tehdä esim. käyttäjän identiteetin tarkastus selaimen lisäksi myös palvelimella. Huonoa tietoturvaa olisi estää jotkut toimenpiteet ainoastaan piilottamalla niiden suoritusmahdollisuus selaimessa olevasta koodista.

Mozillan MDN:n erittäin hyvä Website security ‑guide nostaakin esiin tämän tärkeän seikan:

fullstack content

Expressin dokumentaatio sisältää tietoturvaa käsittelevän osan Production Best Practices: Security joka kannattaa lukea läpi. Erittäin suositeltavaa on ottaa backendissa käyttöön Helmet-kirjasto, joka sisältää joukon Express-sovelluksista tunnettuja turvallisuusriskejä eliminoivia middlewareja.

Myös ESlintin security-pluginin käyttöönotto kannattaa.

Tämän päivän trendejä

Katsotaan vielä lopuksi muutamaa huomisen tai oikeastaan jo tämän päivän tekniikkaa, ja suuntia joihin Web-sovelluskehitys on kulkemassa.

JavaScriptin tyypitetyt versiot

JavaScriptin muuttujien dynaaminen tyypitys aiheuttaa välillä ikäviä bugeja. Osassa 5 käsittelimme lyhyesti PropTypejä, eli mekanismia, jonka avulla React-komponenteille välitettäville propseille on mahdollista tehdä tyyppitarkastuksia.

Viime aikoina on ollut havaittavissa nousevaa kiinnostusta staattiseen tyypitykseen. Tämän hetken suosituin JavaScriptin tyypitetty versio on Microsoftin kehittämä TypeScript, joka on aiheena kurssin osassa 9.

Server side rendering, isomorfiset sovellukset ja universaali koodi

Selain ei ole ainoa paikka missä Reactilla määriteltyjä komponentteja voidaan renderöidä. Renderöinti on mahdollista tehdä myös palvelimella. Tätä hyödynnetäänkin nykyään enenevissä määrin siten, että kun sovellukseen tullaan ensimmäistä kertaa, lähettää palvelin selaimeen jo valmiiksi renderöimänsä Reactilla muodostetun sivun. Tämän jälkeen sovelluksen toiminta jatkuu normaaliin tapaan, eli selain suorittaa Reactia, joka manipuloi selaimen näyttämää DOM:ia. Palvelimella tapahtuvasta renderöinnistä käytetään englanninkielistä nimitystä server side rendering.

Eräs motivaatio server side renderingille on Search Engine Optimization eli SEO. Hakukoneet ovat ainakin perinteisesti olleet huonoja tunnistamaan selaimessa Javascriptillä renderöityä sisältöä, ajat saattavat tosin olla muuttumassa, ks. esim. tämä ja tämä.

Server side rendering ei tietenkään ole mikään React- tai edes JavaScript-spesifi asia, saman ohjelmointikielen käyttö kaikkialla koodissa tekee konseptista teoriassa helpommin toteutettavan, sillä samaa koodia voidaan suorittaa sekä backendissä että frontendissä.

Palvelimella tapahtuvaan renderöintiin liittyen on alettu puhua isomorfisista sovelluksista ja universaalista koodista, termien määritelmistä on kiistelty. Joidenkin määritelmien mukaan isomorfinen web-sovellus on sellainen, joka suorittaa renderöintiä sekä selaimessa että backendissa. Universaalinen koodi taas on koodia, joka voidaan suorittaa useimmissa ympäristöissä eli sekä selaimessa että backendissä.

React ja Node tarjoavatkin varteenotettavan vaihtoehdon isomorfisten sovellusten toteuttamiseen universaalina koodina.

Universaalin koodin kirjoittaminen suoraan Reactin avulla on vielä toistaiseksi melko vaivalloista. Viime aikoina paljon huomiota saanut Reactin päälle toteutettu Next.js-kirjasto on hyvä vaihtoehto universaalien sovellusten tekemiseen.

Progressive web apps

Viime aikona on myös ruvettu käyttämään Googlen lanseeraamaa termiä progressive web app (PWA). Googlen sivuilla oleva määritelmä kuulostaa markkinapuheelta ja sen perusteella on hankala saada selkeää käsitystä mistä on kyse. Checklista tuo mukaan konkretiaa.

Tiivistäen kyse on web-sovelluksista, jotka toimivat mahdollisimman hyvin kaikilla alustoilla ottaen jokaisesta alustasta irti sen parhaat puolet. Mobiililaitteiden pienempi näyttö ei saa heikentää sovellusten käytettävyyttä. PWA-sovellusten tulee myös toimia offline-tilassa tai hitaalla verkkoyhteydellä moitteettomasti. Mobiililaitteilla ne tulee pystyä asentamaan normaalien sovellusten tavoin. Kaiken PWA-sovellusten käyttämän verkkoliikenteen tulee olla salattua.

Mikropalveluarkkitehtuuri

Tällä kurssilla olemme tehneet palvelinpuolelle ainoastaan matalan pintaraapaisun. Sovelluksissamme on ollut korkeintaan muutaman API-endpointin tarjoava monoliittinen eli yhdellä palvelimella pyörivä kokonaisuuden muodostava backend.

Sovelluksen kasvaessa suuremmaksi monoliittisen backendin malli alkaa muuttua ongelmalliseksi niin suorituskyvyn kuin jatkokehitettävyydenkin kannalta.

Mikropalveluarkkitehtuurilla (microservice) tarkoitetaan tapaa koostaa sovelluksen backend useista erillisistä autonomisista palveluista, jotka kommunikoivat keskenään verkon yli. Yksittäisen mikropalvelun on tarkoituksena hoitaa tietty looginen toiminnallinen kokonaisuus. Puhdasoppisessa mikropalveluarkkitehtuurissa palvelut eivät käytä jaettua tietokantaa.

Esim. blogilistasovelluksen voisi koostaa kahdesta palvelusta, toinen huolehtisi käyttäjistä ja toinen blogeista. Käyttäjäpalvelun vastuulla olisi käyttäjätunnusten luominen ja käyttäjien autentikointi, blogipalvelu taas huolehtisi blogeihin liittyvistä toimista.

Seuraava kuva havainnollistaa mikropalveluarkkitehtuuriin perustuvan sovelluksen rakennetta perinteiseen monoliittiseen rakenteeseen verrattuna:

fullstack content

Frontendin (kuvassa neliöitynä) rooli ei välttämättä poikkea malleissa kovinkaan paljoa, mikropalveluiden ja frontendin välissä on usein API gateway joka tarjoaa frontendille perinteisen kaltaisen, "yhdessä palvelimessa" olevan näkymän backendiin, esim. Netflix käyttää tätä ratkaisua.

Mikropalveluarkkitehtuurit ovat syntyneet ja kehittyneet suurten internetskaalan sovellusten tarpeisiin. Trendin aloitti Amazon jo kauan ennen termin microservice lanseeraamista. Tärkeä lähtölaukaus oli CEO Jeff Bezosin vuonna 2002 kaikille työntekijöille lähettämä email:

All teams will henceforth expose their data and functionality through service interfaces.

Teams must communicate with each other through these interfaces.

There will be no other form of inter-process communication allowed: no direct linking, no direct reads of another team’s data store, no shared-memory model, no back-doors whatsoever. The only communication allowed is via service interface calls over the network.

It doesn’t matter what technology you use.

All service interfaces, without exception, must be designed from the ground up to be externalize-able. That is to say, the team must plan and design to be able to expose the interface to developers in the outside world.

No exceptions.

Anyone who doesn’t do this will be fired. Thank you; have a nice day!

Nykyään eräs vahvimmista suunnannäyttäjistä mikropalveluiden suhteen on Netflix.

Mikropalveluista on pikkuhiljaa tullut hype, tämän ajan silver bullet, jota yritetään tarjota ratkaisuiksi lähes kaikkiin ongelmiin. Mikropalveluarkkitehtuurin soveltamiseen liittyy kuitenkin suuri määrä haasteita ja voi olla järkevämpi lähteä liikeelle monolith first, eli tehdä aluksi perinteinen kaiken sisältävä backend. Tai sitten ei. Mielipiteitä on monenlaisia. Molemmat linkit johtavat Martin Fowlerin sivuille, eli viisaimmatkaan eivät ole ihan varmoja kumpi näistä oikeista tavoista on oikeampi.

Emme voi valitettavasti syventyä tällä kurssilla tähän tärkeään aihepiiriin tämän tarkemmin. Jo pintapuolinenkin käsittely vaatisi ainakin 5 viikkoa lisää aikaa.

Katso täältä Sympa Oy:n Panu Vartiaisen pitämä mikropalveluita käsittelevä vierailuluento.

Serverless

Loppuvuodesta 2014 tapahtuneen Amazonin lambda-palvelun julkaisun jälkeen alkoi web-sovellusten kehittämiseen nousta jälleen uusi trendi, serverless.

Kyse on siitä, että lambda ja nyttemmin Googlen Cloud functions ja Azuren vastaava toiminnallisuus mahdollistavat yksittäisten funktioiden suorittamisen pilvessä, kun ennen tätä pienin pilvessä suoritettava yksikkö oli käytännössä yksittäinen prosessi, eli esim. Node-backendiä suorittava ajoympäristö.

Esim. Amazonin API-gateway:n avulla on mahdollista tehdä "palvelimettomia" sovelluksia, missä määritellyn HTTP API:n kutsuihin vastataan suoraan pilvifunktioilla. Funktiot yleensä operoivat jo valmiiksi pilvipalvelun tietokantoihin talletetun datan avulla.

Serverlessissä ei siis ole kyse siitä että sovelluksissa ei olisi palvelinta, vaan tavasta määritellä palvelin. Sovelluskehittäjät voivat siirtyä ohjelmoinnissa korkeammalle abstraktiotasolle, ei ole enää tarvetta määritellä ohjelmallisesti HTTP-kutsujen reitityksiä, tietokantayhteyksiä ym., pilvi-infrastruktuuri tarjoaa kaiken tämän. Pilvifunktioilla on myös mahdollista saada helposti aikaan hyvin skaalautuvia järjestelmiä, esim. Amazon Lambda pystyy suorittamaan massiivisen määrän pilvifunktioita sekunnissa. Kaikki tämä tapahtuu infrastruktuurin toimesta automaattisesti, ei ole tarvetta käynnistellä uusia palvelimia ym.

Hyödyllisiä kirjastoja ja mielenkiintoisia linkkejä

Javasciptin kehittäjäyhteisö on tuottanut valtavan määrän erilaisia hyödyllisiä kirjastoja ja jos olet koodaamassa jotain vähänkin isompaa, kannattaa etsiä mitä valmista kalustoa on jo tarjolla. Seuraavassa listataan muutamia luotettavien tahojen hyväksi havaitsemia kirjastoja.

Jos sovelluksessa on tarve operoida hieman monimutkaisemman datan kanssa, on jo osassa 4 suositeltu kirjasto lodash hyvä lisä. Jos olet mieltynyt funktionaaliseen ohjelmointityyliin, kannattaa harkita ramda:n käyttöä.

Jos sovelluksessa käsitellään aikaa, esimerkiksi date-fns-kirjasto tarjoaa siihen hyvän välineistön. Jos sovellus sisältää monimutkaisia lomakkeita, voi React Hook Form olla hyvä valinta. Jos sovelluksessa tulee piirtää graafeja, on vaihtoehtoja lukuisia, sekä recharts että highcharts ovat hyviksi havaittuja.

Immer tarjoaa muutamista tietorakenteista muuttumattomia toteutuksia. Kirjastosta voi olla hyötyä Reduxia käytettäessä, sillä kuten osasta 6 muistamme reducerien on oltava puhtaita funktioita eli ne eivät saa muuttaa storen tilaa vaan niiden on korvattava se muutostilanteissa uudella.

Redux-saga tarjoaa osassa 6 käsitellylle redux thunkille vaihtoehtoisen tavan tehdä asynkronisia actioneja. Jotkut hypettää ja tykkää, itse en.

Single page ‑sovelluksissa analytiikkatietojen kerääminen käyttäjien sivuston kanssa käymästä vuorovaikutuksesta on haastavampaa kuin perinteisissä, kokonaiseen sivun lataamiseen perustuvissa web-sovelluksissa. React Google Analytics 4 ‑kirjasto tuo tähän avun.

Voit hyödyntää React-osaamistasi myös mobiilisovellusten toteuttamiseen Facebookin erittäin suositun React Native ‑kirjaston avulla. Kurssin osa 10 käsittelee React Nativea.

JavaScript-projektien projektinhallintaan ja bundlaamiseen käytettyjen työkalujen rintamalla on ollut tuulista, best practicet ovat vaihdelleet nopeasti (vuosiluvut ovat suuntaa-antavia, kukaan ei enää muista noin kauas menneisyyteen):

Hipsterien suurin into työkalukehitykseen näytti pysähtyneen Webpackin vallattua markkinat. Muutama vuosi sitten markkinoille ilmestyi uusi tulokas Parcel, joka markkinoi olevansa yksinkertainen, sitähän Webpack ei missään nimessä ole, ja paljon nopeampi kuin Webpack. Lupaavan alun jälkeen Parcel ei kuitenkaan ole jatkanut nostettaan, ja siitä ei ollut Webpackin tappajaksi. Viime aikoina esbuild on ollut suhteellisen kovassa nosteessa ja haastaa jo tosissaan Webpackia.

Sivu https://reactpatterns.com/ tarjoaa tiiviissä muodossa listan parhaita react-käytänteitä, joista osa on jo tältäkin kurssilta tuttuja. Toinen samankaltainen lista on react bits.

Reactiflux taas on suuri englanninkielinen React-kehittäjien keskusteluyhteisö Discordissa. Se voi olla yksi mahdollinen paikka saada tukea kurssin päättymisen jälkeen. Esimerkiksi useille kirjastoille löytyy kokonaan omat keskustelukanavat.

Jos tiedät jotain suositeltavia linkkejä tai kirjastoja, tee pull request!