REST – ny vin på gamle flasker

Mange toolkits, frameworks og arkitekturparadigmer ender med at være en klods om foden mere end noget der fremmer målet. Der er dog undtagelser; Med REST principperne får man virkelig foræret en stak der bare giver god mening.

Teknologien er ikke ny, alligevel ser vi gang på gang implementationer der hævder at være REST-baserede, men alligevel ignorerer nogle af de basale principper.

Da vi i halvfemserne opdagede XML ville begejstringen ingen ende tage. Det var et godt alternativ til de flade CSV-filer og obskure binære hierarkiske filstrukturerer. Teknologien i sig selv havde næsten hele tiden ligget lige for næsen af os og var så oplagt at vi fik svært ved at se brugsscenarie hvor den rent faktisk var uegnet.

Med XML bølgen opstod idéen om at kunne bruge det til at lave service-kald fra maskine til maskine og SOAP blev født – en protokol der var så abstraheret at den kunne overføres med brevdue.

Netop denne abstraktion var også SOAPs svaghed. Alt det som man allerede opfundet i forbindelse med HTTP blev ignoreret og med det eksploderede kompleksiteten.

Ud af frustrationen, formaliserede Roy Fielding i hans Ph.d. i 2000 begrebet REST.

Representational State Transfer

Med REST lød der i udviklerkredse et lettelsens suk, væk var den komplekse og debug-uvenlige XML-wrapping. Væk var de utallige ws-* protokoller og tilbage sad vi med fortidslævnet HTTP og dens stak af teknologier.

Problemet opstod da vi begyndte at kalde en simpel JSON-over-HTTP-grænseflade for REST og dermed glemte/ignorerede nogle af de arkitekturmæssige principper som Fielding havde sat op for netop at sikre at den enkelte service’s levetid og selvstændighed.

Dette har medført en findeling af principperne i en række modenhedsniveauer og endda et nyt term – RESTful, der så skal indikere rigtig-REST. Her ville jeg foretrække at vi kaldte det hele for et WEB API indtil vi overholder principperne:

1. Identifikation af ressourcer

En adresse identificerer hvilken ressource vi ønsker (ikke en streng gemt i et dokument som med SOAP):

http://eksempel.dk/kunde/1

http://eksempel.dk/kunde/1/adresse

http://eksempel.dk/kunder?fra-navn=peter

Det betyder at vi for alvor kan lagdele vores arkitektur og infrastruktur – det giver synlighed i arbejdet med servicen. Det kan være svært at ramme helt rigtigt, generelt skal man kigge efter navneord fra ens domæne. En anden god konvention er at undlade valgfrie argumenter fra adressen og lade dem være en del af query-strengen.

2. Metoder betyder det samme for alle ressourcer

Med HTTP får man også foræret en oplagt funktonel grænseflade bestående af en række udsagnsord – her de mest basale (RFC 2616):

OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE og CONNECT

Konventionen er at man bruger POST, GET, PUT og DELETE til de basale CRUD operationer, opret, hent, opdater og slet og normalt begrænser man sig til disse funktioner – der også alle er understøttet af browserne. Står man og mangler udsagnsord for at kunne udtrykke en service’ funktionaliet er det typisk et spørgsmål om at tage et nyt kig på ens ressourcer – her bør man bruge CRUD-operationerne til at opdatere en tilstand.

Hvis vi for eksempel har en funktion der skal aktivere en bruger, kunne det være fristende at lave en POST til /user/123/activate-user men dermed har vi to udsagnsord i samme forespørgsel. En mere rigtig konstruktion ville være at lave en PUT/POST til /user/123 eller /user/123/status med en besked der indeholder et aktiveringsflag.

3. Manipulation af ressourcer gennem repræsentationer

Endnu en afvigelse fra SOAP, man ændrer altså ikke tilstand ved at kalde funktioner, men ved hjælp af den repræsentation man allerede har modtaget.

Det betyder at vi kan lade den oprindelige ressource forblive skjult bag ved grænsefladen og dermed senere frit ændre på den underliggende datastruktur m.v.

4. Selv-beskrivende beskeder

Beskeder indeholder meta-data om hvordan de skal behandles og caches. Typisk bruges mime-typer til at beskrive data formater:

application/json, image/png osv.

Mime-typen kan så præciseres yderligere for at klient og server eksplicit kan definere forventede formater:

application/vnd.eksempel.kunde+json

Her er vnd for leverandør(vendor)-specifik besked-type, eksempel for leverandøren, kunde for typen.

Resultatet er at det er nemmere at udvide servicen med nye typer og nye klienter i fremtiden – her har HTTP en god mekanisme til forhandling af en passende mine-type gennem headers.

5. Hypermedier som motor for applikationstilstand

Det vil sige at links som vi kender dem fra browsing også er vigtige for services. Lige som vi skifter tilstand når vi browser ned gennem et website, kan klienten skifte tilstand gennem flere service-kald. I praksis betyder det at man kun lader nogle få adresser være kendte på forhånd af klienterne, disse adresser bruger de så til at opdage nye adresser gennem forespørgsler og manipulationer.

Det betyder også at servicen bliver mere selv-beskrivende – står man med en kunde, bør kunden beskrive hvilke handlinger der kan udføres samt hvordan man kan få flere relevante oplysninger.

Resultatet er at klienten har tilstanden. Hvis en operation mislykkedes giver det ikke en ugyldig tilstand og så betyder det at adresser til ens service ikke bliver hårdt kodet i tilfældige e-mails eller lange dokumenter og at vi dermed ikke kan ændre på opbygningen i fremtiden.

I de fleste implementationer jeg har set og lavet har det gået fint med de første principper, men knebet med de sidste der primært handler om klientens antagelser om serveren; lav kobling og sen binding. Software har det med at ændre sig og med mekanismer som hyperlinks, redirects og kanoniske adresser er HTTP og REST et perfekt match til en agil arkitektur.

Vælger vi at holde os til REST-principperne, sikrer vi os en service-API-arkitektur der kan skalere og ændre sig i fremtiden. Alt (helt ned til fejlkoder) er standardiseret og det gør det let at kommunikere om. Det er ikke regler men fornuftige principper som kan give os en rettesnor.

24 comments for “REST – ny vin på gamle flasker

  1. “Hvis vi for eksempel har en funktion der skal aktivere en bruger, kunne det være fristende at lave en POST til /user/123/activate-user men dermed har vi to udsagnsord i samme forespørgsel. En mere rigtig konstruktion ville være at lave en PUT til /user/123 (…)”

    Ved opdatering af en entity impliciterer PUT verbet at body erstatter entitien: “If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server.”. Dette vil være unødig brug af båndbredde for en request, der basalt kun drejer sig om at flippe en bit.

    “(…) eller /user/123/status med en besked der indeholder et aktiveringsflag.” Et PUT til en sådan URL vil være direkte forkert, da /user/123/status så skulle være en entitet i sig selv, i stedet for en status på /user/123.

    IMHO vil det mest korrekte være et POST til /user/123/status med værdien af status i body, ex. {“status”: true} ved en JSON request.

    Samme URL ville så også kunne bruges med en GET request som forespørgsel på status.

    • Tak for kommentaren.

      Ja hvis vi opdaterer brugeren for at sætte flaget, så mener jeg jo at PUT er det rigtige valg – min tanke var at der var tale om en status _opdatering_ derfor skulle det være PUT.

      Om det så betyder noget i båndbredderegnskabet – måske skulle man i såfald over i en PATCH?

      Men jeg kan godt se din pointe med at det bør være POST til /status hvis det virkelig kun er et flag vi ændrer og status er en del af rod-aggregaten user.

      • “Om det så betyder noget i båndbredderegnskabet – måske skulle man i såfald over i en PATCH?”

        Til opdatéring af f.ex. status (hvad det så end må være for en status) vil jeg stadig bruge POST, da jeg ser det som en opdatering af metadata – ikke selve entititen/objektet/resourcen, men det er vist mest udviklerens fortolkning af RFCer, der afgør det 😉

        For reelle opdateringer bruger jeg PATCH. Et tænkt eksempel, hvor du ændrer brugerens fornavn og sletter mellemnavnet kunne have en body med: {“fname”:”Per-Ole”, “mname”:null}.
        Og så bruger jeg application/json-merge-patch som Content-Type[*].

        [*] I senere drafts er det blevet ændret til application/merge-patch+json, hvilket egentlig lyder ganske fornuftigt, da det åbner for brug af andet end JSON

        http://tools.ietf.org/html/draft-snell-merge-patch-08
        http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-05

    • Har tilføjet “POST” i indlægget, da selve metoden ikke var pointen med eksemplet …

      • Et PUT request til /user/123/status vil stadig være semantisk forkert medmindre “status” ses som en resource under /user/123, hvilket nok ville være at strække den lidt 😉

  2. Hvad er det i brugen af SOAP, du synes er komplekst?

    Personligt synes jeg, at det er en ret simpel standard. Jeg medgiver, at nogle af de tillægsstandarderne (som eksempelvis WSDL) er komplekse at arbejde med, hvis man skal kode det hele fra bunden, men med moderne udviklingsværktøjer og frameworks har det da ikke voldt mig alverden problemer.

    • Vi har to fulde stakke af teknologier i stedet for kun én. Jeg ved ikke hvor jeg skulle starte hvis jeg skulle kalde en SOAP service fra fx JavaScript – med REST gør jeg det hele tiden …

      Sidst jeg arbejdede med SOAP (og ja det er noget tid siden) foregik kaldet gennem en masse proxy klasser og andet genereret kode, noget jeg helt slipper for ved REST, det er komplekst i min bog.

      Når jeg laver services til mobile enheder er SOAP heller ikke svaret, Google og Apple har ikke gidet understøtte det … det gør det så komplekst, da man selv skal lave alt arbejdet omkring et http bibliotek.

      • At det er svært at kalde SOAP fra JavaScript er vel næppe i sig selv et argument for at SOAP er komplekst.

        Det er rigtigt at man i eksempelvis .NET kan foretage SOAP kald igennem genererede proxy-agtige klasser, men det plejer jeg nu ikke at gøre. Man kan – i modsætning til hvad mange tror – sagtens kalde services via WCF uden at benytte genererede proxy-agtige klasser. At man vælger, at kalde SOAP på nogle bestemte måder kan vel heller ikke i sig selv være et argument for, at SOAP er kompleks.

        Jeg synes til gengæld, at det eksempelvis er helt validt at foretrække WEB APIer frem for SOAP med argumentet, at SOAP ikke er (godt) understøttet på en given platform/i givne værktøjer.

        • Nu er kompleksitet jo relativt, men i forhold til REST mener jeg faktisk at SOAP er komplekst.

          Uanset hvilket værktøj man vælger tilføjer SOAP et lag jeg hellere ville være foruden.

          Hvis vi vælger et værktøj med god SOAP understøttelse (abstraktion) er værktøjet komplekst, hvis vi vælger et værktøj med ringe SOAP understøttelse bliver det mere bøvlet at kalde – jeg kunne ikke skrive en curl SOAP-request uden at skulle google.

          Hele udtryksmodellen er mere kompleks, husker fx. hvordan der i sin tid blevet lavet et hack oven på SOAP for at kunne putte en binær payload i enden af en besked. Med klassisk HTTP giver det lidt sig selv hvordan man gør den slags.

  3. Hvad er det i brugen af SOAP, du synes er komplekst?

    Personligt synes jeg, at det er en ret simpel standard. Jeg medgiver, at nogle af de tillægsstandarderne (som eksempelvis WSDL) er komplekse at arbejde med, hvis man skal kode det hele fra bunden, men med moderne udviklingsværktøjer og frameworks har det da ikke voldt mig alverden problemer.

    • Vi har to fulde stakke af teknologier i stedet for kun én. Jeg ved ikke hvor jeg skulle starte hvis jeg skulle kalde en SOAP service fra fx JavaScript – med REST gør jeg det hele tiden …

      Sidst jeg arbejdede med SOAP (og ja det er noget tid siden) foregik kaldet gennem en masse proxy klasser og andet genereret kode, noget jeg helt slipper for ved REST, det er komplekst i min bog.

      Når jeg laver services til mobile enheder er SOAP heller ikke svaret, Google og Apple har ikke gidet understøtte det … det gør det så komplekst, da man selv skal lave alt arbejdet omkring et http bibliotek.

      • At det er svært at kalde SOAP fra JavaScript er vel næppe i sig selv et argument for at SOAP er komplekst.

        Det er rigtigt at man i eksempelvis .NET kan foretage SOAP kald igennem genererede proxy-agtige klasser, men det plejer jeg nu ikke at gøre. Man kan – i modsætning til hvad mange tror – sagtens kalde services via WCF uden at benytte genererede proxy-agtige klasser. At man vælger, at kalde SOAP på nogle bestemte måder kan vel heller ikke i sig selv være et argument for, at SOAP er kompleks.

        Jeg synes til gengæld, at det eksempelvis er helt validt at foretrække WEB APIer frem for SOAP med argumentet, at SOAP ikke er (godt) understøttet på en given platform/i givne værktøjer.

        • Nu er kompleksitet jo relativt, men i forhold til REST mener jeg faktisk at SOAP er komplekst.

          Uanset hvilket værktøj man vælger tilføjer SOAP et lag jeg hellere ville være foruden.

          Hvis vi vælger et værktøj med god SOAP understøttelse (abstraktion) er værktøjet komplekst, hvis vi vælger et værktøj med ringe SOAP understøttelse bliver det mere bøvlet at kalde – jeg kunne ikke skrive en curl SOAP-request uden at skulle google.

          Hele udtryksmodellen er mere kompleks, husker fx. hvordan der i sin tid blevet lavet et hack oven på SOAP for at kunne putte en binær payload i enden af en besked. Med klassisk HTTP giver det lidt sig selv hvordan man gør den slags.

  4. Når du skriver: “Hvis vi vælger et værktøj med god SOAP understøttelse (abstraktion) er værktøjet komplekst” lyder det for mig, som om dit argument går på, at abstraktioner i sig selv medfører kompleksitet. Hvis jeg ikke misforstår dig, så er vi fundamentalt uenige.

    Grunden til at man laver abstraktioner er meget ofte, at man netop vil undgå underliggende kompleksitet.

    Naturligvis er det ikke alle abstraktioner der giver mindre kompleksitet (der er masser af eksempler på det modsatte), men personligt kan jeg godt lide abstraktioner både i min egen kode og i det værktøjer samt frameworks, som jeg benytter mig af. Jeg længes for eksempel ikke specielt meget tilbage til assemblerprogrammeringens velsignelser, om end det da er ganske fornøjeligt at lade som om, at man længes tilbage. Den slags skrøner trives dog bedst over en god øl eller to.

    • Dér tror jeg dog at vi er enige – dog ser jeg mange misbrug af kode-mæssige abstraktioner.

      Men abstraktionen SOAP oven på HTTP er jeg stadig ikke vild med, primært fordi at den abstraktionsfrie grænseflade løser problemerne så naturligt.

  5. Når du skriver: “Hvis vi vælger et værktøj med god SOAP understøttelse (abstraktion) er værktøjet komplekst” lyder det for mig, som om dit argument går på, at abstraktioner i sig selv medfører kompleksitet. Hvis jeg ikke misforstår dig, så er vi fundamentalt uenige.

    Grunden til at man laver abstraktioner er meget ofte, at man netop vil undgå underliggende kompleksitet.

    Naturligvis er det ikke alle abstraktioner der giver mindre kompleksitet (der er masser af eksempler på det modsatte), men personligt kan jeg godt lide abstraktioner både i min egen kode og i det værktøjer samt frameworks, som jeg benytter mig af. Jeg længes for eksempel ikke specielt meget tilbage til assemblerprogrammeringens velsignelser, om end det da er ganske fornøjeligt at lade som om, at man længes tilbage. Den slags skrøner trives dog bedst over en god øl eller to.

    • Dér tror jeg dog at vi er enige – dog ser jeg mange misbrug af kode-mæssige abstraktioner.

      Men abstraktionen SOAP oven på HTTP er jeg stadig ikke vild med, primært fordi at den abstraktionsfrie grænseflade løser problemerne så naturligt.

  6. Fint indlæg.

    Må dog indrømme at jeg har mest held med at benytte “extension” til at repræsentere mime-type. altså “products/123.json” eller “products/123.xml”. Det er vigtigt med tilgængeligheden af dine resourcer gennem en browser, curl, etc.. og skal http resourcen repræsenteres som en fil, giver filnavnet “products-123.xml” fin mening.

    Generelt så har http allerede alt, hvad man skal bruge. cache-headers, compression, fejl koder, resource-redirects, last-modified, range/paging, osv.. Så det virker tåbeligt at implementere et lag ovenover.

    Problemet med SOAP er at det tilføjer flere problemer end det løser. Protokollen er alt for tung og kompliseret, det er lavet til maskiner ( kode ) og ikke lavet til mennesker skal læse det. Jeg vil mene at 80% en en given rest service’s resourcer skal være læsbar for “Ikke involverede IT folk”. Vi kan jo alle blive enige om at mennesker er de dyre her. så lad maskinerne udveksle noget som er menneskeligt.

    Processen med Proxy’erne som skal skabes ud fra wsdl’er er ofte fyldt med fejl. Bare se på den store forskel mellem Java og .Net når det kommer til denne process. Man kan som oftest argumentere for at det er folkene bag den implementerede SOAP service, som har fejlet i deres design. Men det faktum at jeg kan lave en SOAP Service med fuldt valideret WSDL i et framework som ikke kan benyttes i et andet framework grundet forskellig måder at tolke en WSDL på, får “sæbe boblen” til at brase.

    Det er vigtigt at man holder sig til de simple data typer og repræsenterer dem efter standarderne i xml/json. Altså “strenge”, “tal” og “datoer”, og ikke begynde at opfinde for meget selv. De fleste data strukture burde kunne repræsenteres, og er det ikke tilfældet kan det være at man gør noget forkert. De binære data typer så som billeder, film, osv.. holder jeg som regel helt væk fra mine Rest services.

    Men når alt kommer til alt, så er det største problem med services som regel mangel på “brugervenlighed”. “internal server error” eller “unexpected error occured” burde være forbudt. Mangler jeg noget authentication, må min værdi i querystring ikke være negativ, er der grænse på, hvor mange resourcer jeg kan poste, osv.? Så skriv det ud, lav bruger venlige fejlbeskeder, fordi at du som udvikler kan få din egen service til at virke, betyder det ikke at den gør 🙂

    • Tak for kommentaren.

      Tror det med at bruge extension er en diskussion der deler vandene. Jeg kender mange der også foretrækker det men bryder mig ikke selv om løsningen. Jeg er fan at kunne servere det bedste indhold til den klient der nu end måtte kigge forbi. Og i nogen tilfælde kan det bedste indhold godt være binært – i form at et billede eller lign.

      På en service jeg lavde for nyligt har jeg ladet svaret, der under normale omstændigheder er i json være i atom hvis det er en feed reader der henter. Det er meget behageligt at kunne holde øje med service output i ens almindelige feed reader.

      Jeg ved ikke om jeg helt forstår din kommentar om simple data typer – så vidt jeg ved indeholder json kun de typer der er tilgængelige i javascript – altså dem du nævner?

      Og ja – fejl håndtering er fantastisk vigtigt – rart at kunne ligge sig op af http-fejlkoderne og så bygge nogle gode beskrivelser på …

  7. Fint indlæg.

    Må dog indrømme at jeg har mest held med at benytte “extension” til at repræsentere mime-type. altså “products/123.json” eller “products/123.xml”. Det er vigtigt med tilgængeligheden af dine resourcer gennem en browser, curl, etc.. og skal http resourcen repræsenteres som en fil, giver filnavnet “products-123.xml” fin mening.

    Generelt så har http allerede alt, hvad man skal bruge. cache-headers, compression, fejl koder, resource-redirects, last-modified, range/paging, osv.. Så det virker tåbeligt at implementere et lag ovenover.

    Problemet med SOAP er at det tilføjer flere problemer end det løser. Protokollen er alt for tung og kompliseret, det er lavet til maskiner ( kode ) og ikke lavet til mennesker skal læse det. Jeg vil mene at 80% en en given rest service’s resourcer skal være læsbar for “Ikke involverede IT folk”. Vi kan jo alle blive enige om at mennesker er de dyre her. så lad maskinerne udveksle noget som er menneskeligt.

    Processen med Proxy’erne som skal skabes ud fra wsdl’er er ofte fyldt med fejl. Bare se på den store forskel mellem Java og .Net når det kommer til denne process. Man kan som oftest argumentere for at det er folkene bag den implementerede SOAP service, som har fejlet i deres design. Men det faktum at jeg kan lave en SOAP Service med fuldt valideret WSDL i et framework som ikke kan benyttes i et andet framework grundet forskellig måder at tolke en WSDL på, får “sæbe boblen” til at brase.

    Det er vigtigt at man holder sig til de simple data typer og repræsenterer dem efter standarderne i xml/json. Altså “strenge”, “tal” og “datoer”, og ikke begynde at opfinde for meget selv. De fleste data strukture burde kunne repræsenteres, og er det ikke tilfældet kan det være at man gør noget forkert. De binære data typer så som billeder, film, osv.. holder jeg som regel helt væk fra mine Rest services.

    Men når alt kommer til alt, så er det største problem med services som regel mangel på “brugervenlighed”. “internal server error” eller “unexpected error occured” burde være forbudt. Mangler jeg noget authentication, må min værdi i querystring ikke være negativ, er der grænse på, hvor mange resourcer jeg kan poste, osv.? Så skriv det ud, lav bruger venlige fejlbeskeder, fordi at du som udvikler kan få din egen service til at virke, betyder det ikke at den gør 🙂

    • Tak for kommentaren.

      Tror det med at bruge extension er en diskussion der deler vandene. Jeg kender mange der også foretrækker det men bryder mig ikke selv om løsningen. Jeg er fan at kunne servere det bedste indhold til den klient der nu end måtte kigge forbi. Og i nogen tilfælde kan det bedste indhold godt være binært – i form at et billede eller lign.

      På en service jeg lavde for nyligt har jeg ladet svaret, der under normale omstændigheder er i json være i atom hvis det er en feed reader der henter. Det er meget behageligt at kunne holde øje med service output i ens almindelige feed reader.

      Jeg ved ikke om jeg helt forstår din kommentar om simple data typer – så vidt jeg ved indeholder json kun de typer der er tilgængelige i javascript – altså dem du nævner?

      Og ja – fejl håndtering er fantastisk vigtigt – rart at kunne ligge sig op af http-fejlkoderne og så bygge nogle gode beskrivelser på …

Skriv et svar til Poul Foged Annuller svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *