Wow. Lenge siden sist. Jeg er blitt et barn rikere, og har lært masse om hvordan verdensveven er tenkt fra et arkitekturståsted. Tidligere innlegg her har jo med å forklare Representational State Transfer, og hvordan man kan anvende denne arkitekturstilen i systemer som ikke er verdensveven. Siden sist har vi på jobben (Escenic AS) kommet mye lengre med vår tykke klient, som snakker med en server over HTTP, hvor vi har forsøkt å bruke REST slik det var tiltenkt.
Det jeg vil dele med dere i dag er tanken om vevens lagdeling. Lagdeling er en av Roy Fielding sine begrensninger. Konkret er det slik at man utnytter det enhetlige settet med verb (i HTTP, GET, PUT, POST, DELETE osv) og selvforklarende meldinger. Lagdeling illustreres best med en proxy server, som kanskje din internettleverandør har, for at de skal spare båndbredde. Kina har en hel mur med slike proxy servere, men ikke for å spare båndbredde, kan dere tro. Det innlegget du ser på nå har antakelig også gått gjennom noen lag før den når dine øyne:
- Kanskje arbeidsplassen har en proxy, for å filtrere bort facebook?
- Kanskje din ISP har en proxy, som cacher og sparer båndbredde?
- Origo har helt sikkert en såkalt reverse proxy
- som gjør at noen ting ikke treffer databasen
Jeg vil også våge å tro at netleseren har en proxy, nemlig dens innebygde browser cache.
Denne lagdelingen er innebygget i HTTP, og det gjøres ofte helt usynlig for (mesteparten av) selve nettleseren din. Men denne lagdelingen stopper gjerne i applikasjonene som bruker HTTP for å snakke med serverene. Klientkoden er gjerne en grøt med objekter som sender meldinger til hverandre, tråder som sparker igang arbeid, og av og til noe som gjør et kall til HTTP PUT for å sende den greia over til serveren.
En ting vi har skjønt (helt nylig) er at man kan ta lagdelingen et hakk videre, nemlig å ta arkitekturen til webben (REST) og implementasjonen (HTTP), og tilby et nytt lag inni applikasjonen. Vi skjønte tidlig at vi måtte ha abstraksjoner over HTTP, og beholdt metoder i koden vår for f.eks. å ta en URI og returnere et flott objekt. Men det vi ikke skjønte da vi gjorde det var at vi egentlig implementerte et nytt “lag” i ånden til vevens arkitektur. Hadde vi skjønt det, hadde vi antakelig dette laget sett mer fullstendig ut, og ikke litt “hullete” som det er i dag.
Et eksempel. Du har en URI som du er interessert i å se på. Koden din vil ikke vite om HTTP, men vil gjøre et funksjonskall og få tilbake et objekt som du så kan spørre ut. Ikke noe rå byte-strøm. Så man lager en metode som ser omtrent ut som slik:
read(URI :: string) :: object
Den tar en streng og gir tilbake et objekt.
Og man hacker det sammen slik at det virker, for de forskjellige media typene man har. Vips så har man abstrahert bort HTTP, og programmerer’ne får et simpelt read(“http://api.mysite.com/v3/23432”).
Dog er det er flere problemer:
- abstraksjonen har huller. URI’en skinner gjennom og programmerer’ne må forholde seg til dette. Ikke noe enormt problem, men i verdensveven brukes URI’en til å identifisere ting, i eksemplet vårt er det bare en streng, som alle andre strenger. Hvordan skal en programmerer vite at denne strengen kan brukes i read()?
- HTTP GET er jo så enormt mye mer kraftig enn “hent hva det nå enn er som skjuler seg bak denne adressen”. Ja. Å lese er hovedhensikten men HTTP tilbyr alt fra å hente deler av en stor fil, til å velge en spesifikk type fil, eller å la vær å hente den om den ikke har forandret seg. Den enkle funksjonen mister alt dette
- Autentisering er helt abstrahert vekk. Antakelsen er vel at “den som har adgang til funksjonen får automatisk de autentiseringen som funksjonen legger på”.
Rådet jeg gir i dette innlegget er å fullfør abstraksjonen. Tilby (semantisk) de samme tingene som HTTP tilbyr, men gjerne i abstrahert form. Ikke abstraher vekk optimistisk låsing, cache validering og andre juveler i HTTP.
Det kan hende at Restlet API’et har noe for seg på klientsiden også. Vi syntes det ikke helt fungerte den gangen da vi så på det for et par år siden, og vi endte da opp med en hullete abstraksjon over HTTP, hvor vi hele tiden må lappe for å tilby det og det aspektet ved HTTP. Til nå har vi tatt med
- Identifikator
- Koden vår bryr seg ikke med URIer. Vi har en egen klasse som definerer hva identitet er. At den faktisk inneholder en URI er vanligvis ikke så viktig. URIen er også tilgjengelig, men om vi hadde designet det på nytt ville den kanskje vært innkapslet.
- Cache validering
- Koden får vite hvilken “versjon” av objektet det har (innkapslet i et objekt) og den kan be HTTP om å få objektet på ny “hvis det har forandret seg”. Det vil da si at den får tilbake ingenting dersom versjonen fortsatt er gjeldende.
- Enhetlige grensesnitt
- Programmerer’ne forholder seg til et relativt greit
- OPTIONS
- Hva har jeg lov til å gjøre på dette objektet. Et vanlig spørsmål fra klientkode er: “Har jeg skrivetilgang til denne?” Vi har noen greie “isReadOnly()” funksjoner som ordner dette.
- HEAD
- Er man bare interessert i metainformasjon, kan man få det.
- GET
- som nevnt over, en bitteliten metode som henter et objekt basert på en identifikator
- Content-Type
- Klienten velger automatisk koden som tolker dataene basert utelukkende på Content-Type header’n. Koden får aldri se denne; den ser bare et objekt av en gitt type.
- Conneg
- Content Negotiation, eller innholdsforhandling på godt norsk, er en måte for klienter og servere å bli enige om den beste representasjonen å sende over linja. Dette er abstrahert vekk ved at klientkoden sender en liste med klasser i foretrukket rekkefølge. Dette blir så gjort om til “Accept: foo/bar” når forespørselen sendes til serveren.
- PUT
- en enkel og grei måte å lagre et objekt på.
- Optimistisk låsing
- Koden som gjør endringer på en objektstruktur kan så be om å sende denne tilbake til serveren for lagring, da med HTTP PUT. Versjonen sendes automatisk som “If-Match”. En spesifikk feil sendes tilbake dersom det er en 412 PRECONDITION FAILED, og koden kan da fange dette opp og komme seg ut av det, gjerne ved å prøve på ny etter å ha gjort en eller annen form for fletting
- POST
- den utskjelte. Vi har kalt den “process()” siden vi ikke aner hva den kan gjøre. Men om man får
- redirects
- systemet følger automatisk omdirigeringer, men det ville kanskje vært en idé å eksponere dem for klientkoden, slik at de kunne vurdere i hver tilfelle, og evt. ta vare på URIene (identifikatorene) på veien.
Poenget her er at vi er godt på veit til å lage en java-abstraksjon som tilbyr alle (viktige) deler av HTTP. Hvis vi hadde tatt den helt ut ville vi kunne også gjort flere kule ting med mellommenn (proxy):
- Caching
- Vi har en form for browser cache, som cacher de rå dataene som gikk over linja, men det hadde vært gøy å automatisk cache ferdige objekter, for å slippe å laste dem fra disk hele tiden. En “gjennomsiktig objekt cache” ville vært smart, spesielt siden den ville (fra serveren) fått instrukser på hvor lenge ting skal leve i cachen.
- Testkode kunne kjørt mot en rent objektorientert mellommann som bare simulerte alt.
- En debug-mellommann kunne bare logget alt som skjedde til en fil, istedenfor å putte masse debug hooks i koden.
- Oppfrisking
- En mellommann som sniffet på alt som skjedde og brukt Publish/Subscribe eller Observable for å informere interesserte om hva brukeren egentlig holder på med (f.eks. for å holde deler av et GUI oppdatert).
- Tenk, kanskje hele GUI’et kunne vært en stor (pluggbar) mellommann. (“uuuurgh… My brain hurts!”)
Jeg lukter et eller annet REST klientrammeverk i dette her, noe som hjelper programmerer’ne lage gode RESTful klientapplikasjoner uten å måtte dille så mye med RFC2616, men allikevel få en applikasjon som følger vevens arkitektur.
God programmeringshøst!
Kommentarer
Sett Rest-* som blei lansert i går?
@trygve Nei. Men det ser ut som om det er noe man gjør hvis man ikke er i et distribuert miljø. Man kan per definisjon ikke ha to-fase transaksjoner om det ikke er en sentral eier, som de selv sier i kommentarene til Rest-transactions.
Når det gjelder Topic spec’en virker den mer interessant. Vi har fakisk kommet frem til noe liknende i versjon 5 av produktet vårt; bortsett fra at det er read only. I Vizrt har vi også kommet frem til en måte å bruke atom som overføringsformatet, samt relasjonstyper og ressursdesign som er cachebart, distribuerbart og stateless. De burde kanskje formaliseres; vi får ta det når det materialiserer seg.
Kanskje noen burde ta for seg området og lage en bok om REST patterns… hmmmm…
Jeg sier meg enig i “Avoid Envelope formats”, og er enig i karakteristikken at Atom er et envelope format.