Local-first: appene som ikke venter på serveren

Gammelt nytt! Æsj! Dette innlegget er ment for de som ennå ikke har fått med seg hva local-first/sync-motor er og hvordan det i hovedsak fungerer.

Sindre Kvalsgård om local-first/sync-motor.
Publisert

✍ leserinnlegg

Dette er et leserinnlegg fra en ekstern skribent, som betyr at innholdet ikke nødvendigvis speiler kode24s meninger. Vil du også bidra? Send oss en epost på [email protected], eller les mer her!

Du kjenner situasjonen. Standup, ni på ti, noen deler skjerm og klikker på en sak i Jira. Så står dere der, åtte voksne mennesker, og ser på et hjul som snurrer.

Så bytter teamet til Linear, og appen føles virkelig responsiv og snappy. Det er lett å tenke at Linear bare har raskere servere, men forklaringen er en annen: Klikket ditt går ikke til noen server i det hele tatt. Arbeidsområdet ditt ligger allerede på maskinen din, og grensesnittet leser derfra. Synkroniseringen skjer i bakgrunnen, når det passer.

Et manifest fra 2019

Våren 2019 publiserte forskningslaben Ink & Switch essayet «Local-first software: You own your data, in spite of the cloud». Blant forfatterne var Martin Kleppmann, mannen bak boka «Designing Data-Intensive Applications». Essayet leste som et manifest, med sju idealer for god programvare. Det første: ingen spinnere, arbeidet ditt skal være under fingertuppene dine.

Poenget var at skyen ga oss samarbeid og tilgjengelighet, men at vi betalte med både eierskap og responstid. Appene våre ble terminaler mot andres databaser, og hvert tastetrykk en nettverksforespørsel. Local-first snur det på hodet. Dataene bor lokalt hos deg. Skyen er fortsatt der, men degradert fra sjef til budbringer.

Ideen var ikke engang ny i 2019. Lotus Notes synkroniserte dokumenter mellom frakoblede maskiner allerede på nittitallet, og offline-first-bølgen rundt CouchDB prøvde igjen for et tiår siden. Den strandet fordi det vanskeligste ble dyttet over på utvikleren: Når to brukere hadde endret det samme, måtte du skrive fletteloggikken selv. Det som har endret seg, er at akkurat det problemet har fått en løsning som virker.

Matematikk i stedet for krangling

Løsningen heter CRDT, conflict-free replicated data types. Så hvordan fungerer egentlig dette?

Tenk deg at du og en kollega har den samme handlelista lokalt, begge uten nett. Du legger til melk. Hun stryker brød. Problemet med en vanlig liste er at endringene beskriver posisjoner: sett inn på plass tre, slett plass fem. I det øyeblikket dere har gjort hver deres endring, peker posisjonene på forskjellige ting, og den som skal flette må gjette.

En CRDT angriper akkurat det. Hvert element får en unik identitet når det skapes, i praksis en kombinasjon av en klient-id og et løpenummer, og den identiteten endres aldri.

Endringene sier ikke lenger «sett inn på plass tre», men «sett inn melk etter elementet med id 47».

Sletting fjerner heller ikke noe, elementet merkes bare som dødt, slik at en endring som ankommer sent og refererer til det, fortsatt har noe å peke på. Og når to endringer treffer nøyaktig samme verdi, kåres vinneren av en regel alle følger, for eksempel at nyeste tidsstempel vinner, og står det likt, avgjør klient-id-en.

Poenget er at reglene er laget slik at rekkefølgen slutter å bety noe. Uansett hvilken rekkefølge endringene kommer inn i, ender alle maskiner opp med nøyaktig samme resultat, og dermed trengs ingen server til å megle. Serveren vet ikke noe klientene ikke kan regne ut selv.

CRDT-er garanterer at alle blir enige, ikke at resultatet er det vi tenker er intuitivt riktig. Redigerer du og kollegaen samme setning samtidig, får dere begge den samme flettede setningen, og den kan bli rar. Matematikken løser koordineringsproblemet, ikke meningsproblemet.

Det mest brukte verktøyet er Yjs. 

Slik kan handlelista se ut i praksis:

import * as Y from 'yjs'

import { WebsocketProvider } from 'y-websocket'

import { IndexeddbPersistence } from 'y-indexeddb'

 

const doc = new Y.Doc()

 

// lagres i nettleseren, virker uten nett

new IndexeddbPersistence('handleliste', doc)

 

// synkroniseres med andre når nettet er der

new WebsocketProvider('wss://din-server.no', 'handleliste', doc)

 

const varer = doc.getArray('varer')

varer.observe(() => console.log(varer.toArray()))

 

varer.push(['melk'])

 

Mister du nettet, fortsetter appen som om ingenting har skjedd. Endringene flettes inn igjen når forbindelsen er tilbake. 

Sync-motor som ble hyllevare

CRDT-er løser sammenslåingen, men du trenger fortsatt grunnarbeidet: hvilke data skal ned til hvilken klient, og hvordan. Det er her sync-motor kommer inn. Du forteller den hva klienten skal se, så holder den lokal og sentral tilstand i takt begge veier. Potensielt kan den erstatte: REST-endepunkter, cache-invalidering, loading-tilstander, custom optimistiske oppdateringer.

Linear og Figma bygde slike motorer selv. Nå kan du hente dem ferdige, og en av de mer kjente er Electric (tidligere ElectricSQL), som strømmer utvalgte deler av Postgres-databasen du allerede har ut til klientene.

Interessen rundt local-first/sync-motorer økende. Første søndag i februar hadde local-first sitt eget spor på FOSDEM i Brussel, og 12. til 14. juli samles miljøet til egen konferanse i Berlin. PGlite, en utgave av Postgres som kjører i nettleseren, rundet nettopp ti millioner ukentlige nedlastinger.

Nedsiden

Noe må det koste. Tilgangsstyring blir vanskeligere, for har du først sendt data ned til en klient, kan du ikke trekke dem tilbake. Lagringsplassen på enheten setter grenser. Økosystemet er ungt, mange av verktøyene er yngre enn tre år, og å feilsøke tilstand som lever på tusen enheter er en annen sport enn å lese serverlogger.

Og noen domener er sentraliserte av natur. Når to personer kjøper den siste billetten samtidig, finnes det ingen CRDT som kan flette de to virkelighetene. Og banken din bør fortsatt ha én fasit.

Local-first er derfor ikke et enten-eller, men en ende av en skala. Spørsmålet er hvilke deler av appen din som ikke trenger en rundtur innom datasenteret i Amsterdam for hvert tastetrykk. Og i et land der brukerne jevnlig sitter på ferga, i en tunnel eller på ei hytte uten dekning, er ikke det spørsmålet spesielt teoretisk.

Det forfriskende med local-first er at det ikke handler om å legge til enda et lag, men om å fjerne noen. Færre endepunkter, mindre tilstandshåndtering (muligens), ingen spinnere. 

«Data is never late. Nor is it early. It arrives precisely when it means to.»

Foretrekk oss i Google Discover

Ved å legge oss til som foretrukket kilde i Google vil du blant annet få opp flere av sakene våre i Google Discover. Tusen takk for støtten!

Foretrekk oss 😻
Bygget med Labrador CMS