De nyeste sonene til Erik

Erik M. har ikke anbefalt noen soner ennå. Vi viser derfor de nyeste sonene Erik M. har blitt medlem i.

Nye i Eriks krets

Jørgen W.
Morten H.
Trym B. A.
Jakob V. N.
Trygve L.Trygve kjenner jeg fra hans tid som utvikler hos Escenic. Kul fyr!
Simen G. J.Überaltmuligmann som fikser det meste hos A-pressen

Eriks bidrag

Lagdelt arkitektur

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:

  1. 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()?
  2. 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
  3. 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 “)” 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 “)” 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!

Å bruke web arkitektur i ikke-web-programvare

Skalerbarhet i tradisjonell softwareutvikling innebærer ofte at man gjør seg mindre avhengig av databasen, og introduserer en objektcache i minnet til en eller flere servere. Problemer oppstår når dataene forandrer seg under skjørtene på serveren. Da kan man låne noen teknikker fra webben: HTTP har nemlig særdeles gode mekanismer for å kunne skalere.

Først må man akseptere at ikke alle nodene i en server vil til enhver tid se de aller siste dataene. Hvis du lager en børsapplikasjon som skal tjene penger på day-trading er det kanskje greit å ha de siste tallene, og ikke en 60 sekunder gammel kurs. Kan man akseptere litt elde (noen sekunder) er mulighetene mange.

Ok punkt en er å putte objektene inn i cache, men det er jo ikke så spennende. Noen ganger vet man at objektet forandrer seg et spesielt klokkeslett. Slikt kan man nytte seg av: Man tar HTTP’s Cache-Control: max-age og gjør noe tilsvarende med objektene når de puttes i en cache. Hvis man vet at objektet ikke er gyldig etter et visst klokkeslett er det bare å fortelle cache’n dette.

Hvis objektene fakisk brukes til videre oppdatering mot databasen (f.eks. du forandrer en liten ting og O/R mappingen tar og lagrer hele greia) så låner man If-Unmodified-Since eller enda bedre: If-Match fra HTTP og "gjør det selv. Når man får et objekt som skal sendes ned til databasen, så sjekker man versjonsnummeret til objektet man fikk er det samme som versjonsnummeret i databasen. Versjonsnummeret må selvfølgelig ikke være noe som klientkoden kan forandre på. Modifiseringsdato funker også om man ikke liker versjonsnummer.

Hva om dataene har forandret seg? Jo, det er jo HTTP 412 Precondition Failed som får en liknende vri i koden din. Slikt kan stort sett unngås ved å gjøre noe som tilsvarer en unconditional GET—nemlig å forbigå cachen når man henter data for oppdatering. Ingen kan garantere at andre holder seg unna dataene om ikke man har en form for pessimistisk lås da. Utrolig nok har ikke Wikipedia noen entry for Pessimistic locking, bare Optimistic locking.

Hvis objektene forandrer seg av ymse årsaker, så kan man låne mnot sin lovende Cache Channels slik: Med jevne mellomrom (et par ganger i minuttet) henter cachen ut en liste med endringer fra databasen, og fjerner så utdaterte objektene fra cachen. Finnes det flere cacher vil de alle hente samme liste med jevne mellomrom, og er det virkelig mange cacher kan jo listen over endringer caches den også.

Nettoresultat:

  • Cachen kan holde på objekter i laang tid. Hvis dette er en memcached kan dette være gunstig :-)
  • Cachen fjerner objekter som går ut på tid til riktig tid
  • Cachen fjerner objekter som har forandret seg innen 30 sekunder, i snitt etter 15 sekunder (eller slik du konfigurerer)
  • Oppdateringer på “gamle” objekter nektes, så man beholder dataintegritet
  • Det skalerer: Du kan ha så mange cacher i systemet som du ønsker

Ganske stilig.

Motto? Les HTTP spec’en (igjen)!

Metadata i HTML

HTTP handler jo om hypermedia: Du mottar et dokument som har lenker til andre dokumenter. Men visste du at det er mulig å lenke til andre dokumenter uten å putte lenkene inni dokumenter?

HTTP deler jo responsen i data og metadata, nemlig i selve dokumentet og headers (“hoder”?). Header’ene beskriver selve dokumentet sett utenifra; f.eks. Last-Modified forteller når dokumentet sist ble endret. Content-Length forteller hvor mange bytes det er. Cache-Control forteller deg om hvor lenge du bør anse dokumentet som gyldig. Allow forteller deg hva du kan gjøre med dokumentet. Ad kjedsommelig. Men det finnes en HTTP header som har vært med siden 1990 men som ble tatt ut av selve HTTP spec’en fordi den ikke var særlig i bruk1. Jeg snakker om Link header’en.

HTML blir jo ofte hyllet som det ultimate hypermediaformatet, på grunn av a og link taggene. Faktisk anbefaler Sam Ruby et al at man bruker HTML så langt det lar seg gjøre hvis man skal lage en RESTful web service. Skal man lage en to-do-liste web service så burde man returnere application/xhtml med head og body og en HTML-liste for hver to-do-liste…

Men å si at dette andre dokumentet er en i en rekke av dokumenter (next/previous), eller at atom feeden til dokumentet er her: Det er jo metadata! Jeg ønsker ikke å måtte forkludre dokumentet mitt. Hva om jeg returnerer et dokument som ikke har rom for lenker? Putt dem i HTTP header’ene istedenfor i HTML header’en:

Link: <http://my/other/reference.txt>; rel="alternate"; type="text/plain"
Link: <http://mypage/me>; rev="made"; title="John Doe"

Mark Nottingham har forsøkt å beskrive Link headeren i en egen Internet Draft2, og diskusjonen fortsetter hos w3c3. Om man bruker Link header’en istedenfor lenker inni dokumentet så trenger ikke klienten forstå dokumentet. Om jeg serverer et WORD dokument eller en PDF så har de vidt forskjellig metamodeller; Jeg kunne hente ut forfatter, men da må jeg ha en WORD parser eller en PDF parser. Om de lå i header’en ville jobben min som klient være betydelig lettere, om jobben består i å finne dokumenter av en viss forfatter.

Personlig anser jeg at metadata hører hjemme i HTTP header’en, og ikke i dokumentet. Metadata i dokumentet er jo plutselig data. At HTTP og HTML begge støtter både data og metadata er litt kunstig, og speiler kanskje det faktum at webben stammer fra fil-servere som ikke hadde noen særlig rik meta-modell. Både Last-Modified, Content-Length og Allow støttes jo av ethvert filsystem, og HTML fikk jo tilogmed meta http-equiv tags som skulle erstatte HTTP headers. Det er jo helt sykt! Tiden er kanskje moden for å putte metadata der det hører hjemme, nemlig i HTTP!

1 Link Header esw.w3.org

2 Mark Nottinghams Draft

3 Diskusjon om temaet

Eksempler på Hypermedia

Den lille CSS-saken som Alexander postet her om dagen fikk meg til å tenke:

Hvorfor er det bare html som hylles som det store hypermediaformatet? CSS er jo vel så bra, og brukes i nesten like stor grad

Så her sitter jeg og skriver igjen! Hypermedia er jo egentlig bare media (tekst, bilder, video) med hyperlenker til annet media (mer tekst, bilder, video). Når man sier hypermedia er det jo HTML man tenker på: HyperText Markup Language. Naturlig, ikke sant? Men hva med f.eks. CSS? Er ikke det er hypermediaspråk? Sier man ikke i CSS at man ønsker det bakgrunnsbildet på dette elementet? Og da refererer man jo til bildet ved hjelp av en URI - altså er det en hyperlenke. I alle fall på lik linje med img eller @object@-taggene i HTML.

Og da tenker jeg: Er det andre opplagte hypermediaformat man bør vurdere når man skal gi fra seg representasjoner?

JSON blir ofte disset fordi det sies at det ikke er et hypermediaformat, og det har man jo rett i. CSS har en egen “uri” funksjon for å uttrykke hypertekstlenker. JSON har ikke det.

XML blir ofte hypet opp som et veldig bra hypermediaformat, men det er jo helt feil. XML har heller ingen “uri” funksjon for å si at dette attributtet eller denne strengen her er faktisk en URI som en UA bør bruke til noe.

Slikt må (for både XML og JSON) uttrykkes i andre dokumenter som beskriver strukturen i dokumentet. Ironisk nok gjøres dette i XML ved hjelp av URI’er:

xmlns:foo=“urn:foo”

xsi:schemaLocation=“urn:foo http://bar”
xmlns:xsi=“.”

Her sier man at dokumentet ligger i namespacet foo. Neste linje sier at namespacet foo identifiseres ved hjelp av URI’en urn:foo. Dernest sier man at namespacet urn:foo har et XML schema som ligger klart til henting på http://bar—en XML prossessor kan hente denne for å sikre seg dokumentet er gyldig. Til slutt må man si til prosessoren at prefixet xsi faktisk er XML Schema Instance namespacet, som igjen blir identifisert ved hjelp av nok en URI. XML parseren slipper å hente skjemadefinisjonen for xsi fordi parseren skal kjenne igjen denne bare på bakgrunn av URI’en.

Men spør du meg, er det mye magi bare for å gjøre XML til et überhypermediaformat. XML Schema Instance er også et lite dyr, men med litt øvelse kan man sikkert definere at et attributt er av type anyURI. Dermed kan hvemsomhelst som leser dokumentet og skjemaet ditt, kjenne igjen URIer. Men hva skal man bruke det til? Huff :-)

JSON tiltaler meg. Det er Lists og Maps i skjønn harmoni. Et rent datautvekslingsformat som ikke forsøker å spesifisere datatypen utover noen enkle primitiver. Det er ikke noe problem å uttrykke en URI i et JSON dokument; Det er jo bare en streng som alltid. Så blir det opp til spesifikasjonen av JSON-dokumentet (som kanskje ligger i prosatekst slik som hele Atom Publishing Protocol - APP har ingen XML skjema) å angi hvilke strenger som er URIer osv. JSON er da like bra hypermediaformat som XML???

Misforstått: URI

URIer er en av hjørnestenene i webbens arkitektur. Alle websider har en adresse, en oppskrift for å hente en spesifik webside. Opprinnelig het disse URLer fordi de lokaliserte websider på nettet.

F.eks. denne sonen har URI’en http://web-arkitektur.origo.no/ Det er sonens kanoniske identifikator. Jeg kan gi URIen til andre og de vil kjenne den igjen som en URL og skjønne at den peker på noe: URIen er også sonens URL—man kan skrive det inn i en nettlos, og så forsøker den å hente innholdet. Voilá.

Men arkitekturen bygger også på at man kan identifisere hva som helst - ikke bare websider.

En vanlig misforståelse er at URIer angir nettopp websider. Det gjør de ikke. De fleste URIer på webben i dag gjør det (stort sett websider, bilder, CSS og javascript), men noen URIer gjør det ikke.

Min oppfattning av URIer er at de kan identifisere hva som helst: For eksempel bygger XML Namespaces på URIer. Et XML Namespace er ikke en webside eller en skjemadefinisjon, men et abstrakt konsept om et unikt navneområde for XML tagger. Det er ingen forventning om at man skal kunne putte inn en namespace URI i en braoser og få f.eks. et skjemadokument. Namespace URIen er primært for å identifisere namespacet fra andre namespaces. Det er en identifikator.

JSP Taglib URIer har samme funksjon. De er der kun for å identifisere et tag library fra et annet tag library. Her forventes det heller ikke (strengt tatt) at man må eller kan hente noe hvis man går til et taglib URI. Taglib URIer er ikke nødvendigvis URLer.

På denne måten kan URIer brukes til å identifisere ting uten at man må sette opp ett nettsted for å servere innhold. Ting som ikke kan servere innhold. For eksempel mennesker, bygninger, produkter, lyspærer, og stoler kan i teorien gis egne URIer og refereres til.

Andre som kjenner til de samme tingene kjenner også disse tingenes URIer og kan dermed kjenne igjen URIen hvis vi ser den. XML prosessorer som kjenner igjen et xml namespace URIer. JSP prosessorer som kjenner igjen taglib URIer.

RDF er et språk som har tatt dette til sitt hjerte. RDF er et språk som lar deg uttrykke kunnskap om relasjoner mellom ressurser. URIer brukes her for å identifisere personer, firmaer, blogger og blogginnlegg, og attributter på disse: navn, overskrifter, forfattere osv. Man kan uttrykke at en person er forfatter til en bloggentry, at personen har navnet sånn og sånn, og at han kjenner disse andre personene, og at de har disse navnene. Her og der kommer det også “rdfs:seeAlso” som sier at her er en URI som forteller deg mer om denne ressursen.

Hva er poenget med URIer som ikke lar seg “hente”? For en webutvikler kan det være meningsløst. Det skjønner jeg. Men i softwareutvikling kan det brukes som en generisk måte å referere til ting. Primærnøkler? Hvorfor ikke? Sannsynligvis vil alle dataene leve med samme scheme (http har grei semantikk…) og et hostnavn hvor dataene “lever” (i kanonisk forstand) Det som gjenstår er “path” til dataene. På samme måte som på en web server er pathene dine en uendelig stor tomt som skal deles opp.

Poenget er at det gir universell adresserbarhet. Hvemsomhelst i verden kan referere til tingene dine, presist og nøyaktig.

Hvorfor velge en annen identifikator enn URIer?

Annonse