Rest-02 REST API Zelfstudie

Bronnen:
http://www.restapitutorial.com/index.html
RESTful Service Best Practices

Auteur: Todd Fredrich

Nederlandse vertaling en bewerking: Peter Schalk

Creative Commons License
Dit document valt onder de Naamsvermelding-GelijkDelen 4.0 Internationaal (CC BY-SA 4.0) licentie.

0. Inleiding – Leer REST: een RESTful zelfstudie

REST staat voor “REpresentational State Transfer” – status-overdracht door middel van representatie.

REST is een software-architectuurstijl die bestaat uit een aantal richtlijnen en “best practices” voor schaalbare webservices. REST is een gecoördineerde verzameling constraints die worden toegepast op het ontwerp van componenten in een gedistribueerd hypermedia-systeem, die kan leiden tot een goed presterende en onderhoudbare architectuur.
 

Definitie van Hypermedia op en.wikipedia.org
Hypermedia, een uitbreiding van de term hypertext, is een niet-lineair informatiemedium dat afbeeldingen, audio, video, platte tekst en hyperlinks bevat. Dit staat naast de bredere term multimedia, die naast hypermedia ook kunnen bestaan uit niet-interactieve lineaire presentaties. De term hypermedia is ook gerelateerd aan het terrein van de elektronische literatuur. De term werd voor het eerst gebruikt in een artikel uit 1965 door Ted Nelson.
REST is breed geaccepteerd binnen het World Wide Web als een eenvoudig alternatief voor webservices die zijn gebaseerd op SOAP en WSDL. RESTful systemen communiceren meestal, maar niet altijd, via het Hypertext Transfer Protocol met dezelfde HTTP-werkwoorden (GET, POST, PUT, DELETE, enzovoort) die door webbrowsers worden gebruikt om webpagina’s op te halen en gegevens naar externe servers te verzenden.

De REST architectuurstijl werd ontwikkeld door de W3C Technical Architecture Group (TAG), parallel met HTTP 1.1, gebaseerd op het bestaande ontwerp van HTTP 1.0. Het World Wide Web is de grootste implementatie van een systeem dat voldoet aan de REST architectuurstijl.

Er zijn verschillende bronnen met best practices voor het creëren van RESTful webservices. Deze zijn vaak tegenstrijdig, mede afhankelijk van wanneer ze werden geschreven. Bovendien is het bijna niet te doen om een serie boeken over dit onderwerp te bestuderen, als je doel is om services binnen een korte doorlooptijd te implementeren. Deze zelfstudie van RestApiTutorial.com is bedoeld om je snel en zonder veel discussie wegwijs te maken in de belangrijkste best practices met betrekking tot REST.

REST is meer een verzameling principes dan een set standaards. Naast de overkoepelende zes constraints wordt niets gedicteerd. Er zijn “best practices” en “de facto standaards”, maar die zijn voortdurend in ontwikkeling.

Deze zelfstudie geeft in beknopte vorm een aantal aanbevelingen en achtergrondinformatie over veel van de vragen die rondom REST gesteld worden. Dit helpt je als ontwikkelaar bij de effectieve ontwikkeling van “real-world”, productie-rijpe, consistente RESTful services. Informatie uit andere bronnen wordt hier gebundeld en gecombineerd met ervaring die in praktijk is opgedaan.

Er is nog steeds veel discussie over de vraag of REST is beter dan SOAP (en vice versa), en misschien zijn er nog steeds redenen om SOAP services te ontwikkelen. Hoewel dit document enkele SOAP zaken aanstipt, wordt weinig tijd besteed aan het bespreken van de onderlinge voor- en nadelen. Omdat de technologie en de industrie oprukken, zullen we verder werken vanuit de veronderstelling dat gebruik maken van REST de huidige best practice is voor de ontwikkeling van webservices.

Het eerste deel van deze zelfstudie biedt een overzicht van wat REST is, de constraints en wat REST uniek maakt. Het tweede deel levert een aantal praktische tips en geheugensteuntjes met betrekking tot REST-service concepten. Latere hoofdstukken gaan meer de diepte in om de ontwikkelaar van webservices te helpen bij het maken van kwalitatief goede REST-services, die geschikt zijn om in een productie-omgeving te worden toegepast.

1. Wat is REST?

1.1. Zes constraints

De REST-architectuur beschrijft zes constraints (beperkingen, randvoorwaarden). Deze constraints, toegepast op de architectuur, werden oorspronkelijk gecommuniceerd in een proefschrift van Roy Fielding en definiëren de basis van de RESTful-architectuurstijl.

Deze zes voorwaarden zijn:
a. Uniforme interface.
b. Statusloos (“stateless”, toestandloos).
c. Cache-baar.
d. Client-Server.
e. Gelaagd systeem.
f. Code op aanvraag (optioneel).

1.2. Uniforme interface

De uniforme interface constraint definieert de interface tussen clients en servers. Deze constraint vereenvoudigt en ontkoppelt de architectuur, waardoor elk onderdeel onafhankelijk ontwikkeld kan worden. De vier basisprincipes van de uniforme interface zijn:

a. Gebaseerd op resources (“resource-based”)
Individuele resources (bronnen, middelen) worden in de requests geïdentificeerd met behulp van URI’s (Uniform Resource Identifiers) die dienst doen als resource-identificatie (“resource identifiers”). De resources zelf zijn conform het ontwerp gescheiden van de representaties die worden teruggestuurd naar de client. De server verzendt bijvoorbeeld niet een complete database of database-tabel, maar in plaats daarvan een stuk HTML, XML of JSON die één of meer database-records vertegenwoordigt, bijvoorbeeld in het Engels en gecodeerd in UTF-8, afhankelijk van de kenmerken van het request en de implementatie van de server.

b. Manipulatie van resources door middel van representaties
Wanneer een client beschikt over een representatie van een resource, met inbegrip van alle daaraan gekoppelde metadata, heeft hij genoeg informatie om het resource op de server te wijzigen of te verwijderen, op voorwaarde dat hij toestemming (autorisatie) heeft om dit te doen.

c. Zelfbeschrijvende berichten
Elk bericht bevat voldoende informatie om te beschrijven hoe het bericht moet worden verwerkt. Bijvoorbeeld, de parser die moet worden aangeroepen, kan worden gespecificeerd door een internet media-type (voorheen bekend als een MIME-type). Responses geven ook expliciet aan of ze “cache-baar” zijn, met andere woorden of ze in cache-geheugen mogen worden bewaard.

d. Hypermedia als de motor van de applicatie-status (Engels: Hypermedia as the Engine of Application State, afgekort “HATEOAS”)
Clients leveren een status (“state” of toestand) via de inhoud (“content”) van bodies, “query-string parameters” (zoek-sleutels of zoek-tekenreeksen), request-headers en de gevraagde URI (de resource-naam). Services leveren de status aan clients via de inhoud van bodies, response-codes en response-headers. Dit wordt in technische termen aangeduid als hypermedia (of hyperlinks binnen hypertext).

Naast de bovenstaande beschrijving betekent HATEOS ook dat, indien nodig, links worden opgenomen in de geretourneerde body (of headers) om de URI te leveren voor het ophalen van het object zelf of gerelateerde objecten. We zullen hier later in meer detail op terugkomen.

De uniforme interface die elke REST-service moet aanbieden is van fundamenteel belang voor zijn ontwerp.

1.3. Statusloos (“stateless”, toestandloos)

Aangezien REST een acroniem is voor “REpresentational State Transfer” – (gegevens-)overdracht door middel van representatie) – is statusloosheid een basisvoorwaarde. Dit betekent eigenlijk dat de benodigde status om het request te behandelen is opgenomen in het request zelf, als onderdeel van de URI, query-string parameters, body of header. De URI bevat een unieke identificatie van het resource en de body bevat de status (of statuswijziging) van dat resource. Nadat de server zijn verwerking doet, wordt de juiste status, of de onderdelen van de status die relevant zijn, teruggegeven via headers, status en response-body.
 

Definitie van statusloos protocol (“stateless protocol”) op en.wikipedia.org
In de informatica is een statusloos protocol een communicatieprotocol dat elk request behandelt als een onafhankelijke transactie die geen relatie heeft met eerdere requests, zodat de communicatie bestaat uit onafhankelijke paren van requests en responses. Een statusloos protocol vereist niet van de server dat deze over meerdere requests heen sessie-informatie of statussen bijhoudt van elke component die bij de communicatie betrokken is. In tegenstelling hiermee wordt een protocol dat de interne status op de server bij moet houden een statushoudend protocol (“stateful protocol”) genoemd.

Voorbeelden van statusloze protocollen zijn het Internet Protocol (IP), dat de basis is voor het internet, en het Hypertext Transfer Protocol (HTTP), dat de basis is van de datacommunicatie voor het World Wide Web.
De REST constraint van statusloosheid is gedefinieerd vanuit het perspectief van de server. De constraint zegt dat de server niet de status van de applicatie behoort bij te houden. De consequentie hiervan is dat de client alle informatie aan de server moet verstrekken om het request in te vullen, waarbij de status indien nodig opnieuw wordt verzonden als deze meerdere requests bestrijkt, aangezien de server geen informatie van eerdere requests kan gebruiken omdat hij deze niet onthouden heeft.
 
Voorbeeld
Als je door een verzameling foto’s bladert en de server heeft zojuist foto nummer 12 verzonde, kan de client niet eenvoudig tegen de server zeggen “geef me de volgende foto”. In plaats daarvan vraagt de client om foto 13 om verder te gaan in de verzameling. De client moet inderdaad alle informatie verstrekken om dit request uit te voeren, omdat de server niet onthouden heeft dat je foto 12 aan het bekijken bent.

De client hoeft echter ook niet te weten dat je precies deze foto aan het bekijken was. Het is namelijk mogelijk dat de server samen met de representatie van foto 12 (hyper)links stuurt met het label “vorige” en “volgende” die naar de aangegeven foto’s verwijzen. Is dit niet tegenstrijdig? Nee, want op het moment dat de server de representatie van foto 12 aan het genereren was, zat hij middenin de verwerking van je request, dus op dat moment wist hij welke foto je had opgevraagd en wat de vorige en volgende foto waren.
Het is belangrijk om te beseffen dat er twee soorten statussen bestaan. We hebben de applicatie-status waarover we het hiervoor gehad hebben, die op de client wordt bewaard. Daarnaast hebben we de resource-status die door servers wordt behandeld.

De applicatie-status is informatie over waar je je in de interactie bevindt. Deze wordt gebruikt tijdens je sessie met een applicatie. Bijvoorbeeld het feit dat je foto 12 aan het bekijken bent, of dat je bent ingelogd op Twitter, zijn beide applicatie-statussen. Wijzigingen op deze status zijn mogelijk door de besturingselementen in hypermedia-representaties. In het voorbeeld met de foto zijn het (hyper)links, bij Twitter zijn het bijvoorbeeld het Tweet-invoerveld en de bijbehorende “Tweeten” knop.

De resource-status betreft de (semi-)permanente gegevens die een server bewaart en die blijven bestaan na de tijdsduur van een enkele sessie met interacties. Een foto die je hebt ge-upload naar een database met een fotoverzameling en een Tweet die je hebt verzonden, zijn voorbeelden van resource-status. Dus hoewel HTTP een statusloos protocol is, kun je wel langdurige status op de server bewaren. De kortdurende status die tijdens je interactie met de server wordt gebruikt is echter volledig de verantwoordelijkheid van je client en moet met elk request meeverzonden worden.

Statusloosheid zorgt voor een grotere schaalbaarheid, omdat de server de sessie-status niet hoeft te bewaren, bij te werken of te communiceren. Daarnaast behoeven load-balancers zich bij statusloze systemen geen zorgen te maken over de relaties tussen sessies.

1.4. Cache-baar

Net zoals op het World Wide Web kunnen clients responses cachen. Responses moeten zichzelf daarom, impliciet of expliciet, definiëren als wel of niet cache-baar (“cachable”), om te voorkomen dat clients de status van onjuiste of verouderde gegevens gebruiken in antwoord op latere requests. Goed beheerde caching kan een deel van de client-server interacties geheel of gedeeltelijk elimineren, wat ten goede komt aan de schaalbaarheid en performance.

1.5. Client-Server

De uniforme interface scheidt clients van servers. Deze scheiding van verantwoordelijkheden (“separation of concerns”) betekent bijvoorbeeld dat clients zich niet bezig hoeven te houden met gegevensopslag, die intern blijft binnen elke server, waardoor de overdraagbaarheid (“portability”) van programmacode van clients verbetert. Servers zijn niet bezig met de gebruikers-interface of de gebruikers-status, zodat servers eenvoudiger en beter schaalbaar zijn. Servers en clients kunnen ook onafhankelijk worden vervangen en ontwikkeld, zolang de interface hetzelfde blijft.

1.6. Gelaagd systeem

Een client kan gewoonlijk niet zeggen of hij direct aan de eind-server is gekoppeld, of aan een intermediaire server. Intermediaire servers kunnen de schaalbaarheid van systemen verbeteren door load-balancing mogelijk te maken en door het aanbieden van gedeelde caches (voor het bewaren van veel-gebruikte gegevens). Ook kunnen “lagen” (Engels: layers) worden gebruikt om beveiligings-policies te implementeren.

1.7. Code op aanvraag (optioneel)

Servers zijn in staat om de functionaliteit van een client tijdelijk uit te breiden of aan te passen door logica over te dragen die de client kan uitvoeren. Voorbeelden hiervan zijn gecompileerde componenten, zoals Java-applets, en client-side scripts, zoals JavaScript.

Door te voldoen aan deze zes constraints, in overeenstemming met de REST architectuurstijl, kan elk type gedistribueerd hypermedia-systeem over de gewenste eigenschappen beschikken, zoals performance, schaalbaarheid, eenvoud, aanpasbaarheid, zichtbaarheid, overdraagbaarheid en betrouwbaarheid.
 

Opmerking: De enige optionele constraint van REST-architectuur is “Code op aanvraag”. Als een service één of meer andere constraints niet volgt, kan hij strikt genomen niet worden beschouwd als RESTful.

2. Tips voor toepassing van REST API

Ongeacht of services in technisch opzicht strikt RESTful zijn opgezet (volgens de zes eerder genoemde constraints), kan het zinvol zijn om de aanbevelingen hierna, voor begrippen die lijken op REST, op te volgen. Het toepassen van deze tips zal resulteren in betere, meer bruikbare services.

2.1. Gebruik HTTP werkwoorden die iets betekenen

Consumers (gebruikers) van API’s kunnen de werkwoorden GET, POST, PUT en DELETE gebruiken; deze werkwoorden verbeteren in hoge mate de duidelijkheid van wat een bepaald request doet. Daarnaast mogen GET requests geen onderliggende resource-gegevens wijzigen. Wel kunnen hiermee metingen en controles worden uitgevoerd, waarbij ook gegevens kunnen worden bijgewerkt, maar dit mogen geen gegevens zijn die worden geïdentificeerd door de URI.

In het algemeen worden de vier primaire HTTP werkwoorden als volgt gebruikt:

GET
Lees een specifiek resource (door middel van een identifier) of een verzameling van resources.

PUT
Wijzig (“update”) een specifiek resource (door middel van een identifier) of een verzameling van resources. Dit kan ook worden gebruikt om een specifiek resource te maken als de resource-identifier vooraf bekend is.

DELETE
Verwijder een specifiek resource door middel van een identifier.

POST
Maak/creëer een nieuw resource. Dit is tevens een algemeen bruikbaar (“catch-all”) werkwoord voor operaties die niet passen in de andere categorieën.

2.2. Zorg voor goed doordachte resource-namen

Voor een goede API is een URI-hiërarchie met goed opgezette resources nodig. Met geschikte resource-namen (die lijken op URI-paden, zoals /klanten/12345/orders) wordt het duidelijker wat een bepaald request doet.

Geschikte resource-namen bieden de samenhang (context) voor een service-request, wat de begrijpelijkheid van de API verbetert. Resources worden hiërarchisch weergegeven via hun URI-namen en bieden de gebruikers een makkelijke, begrijpelijke hiërarchie waarvan ze in hun applicaties kunnen profiteren.

Hier zijn enkele regels voor het ontwerp van URI-paden (resource-namen):

a. Gebruik identifiers in je URI’s in plaats van in de query-string. Het gebruik van query-string parameters in de URI is prima voor het filteren, maar niet voor resource-namen.
Goed: /klanten/12345
Slecht: /api?type=klant&id=12345

b. Maak gebruik van de hiërarchische opbouw van de URI om structuur in te bouwen.

c. Ontwerp voor je clients, niet voor je gegevens.

d. Resource-namen behoren zelfstandige naamwoorden te zijn; vermijd werkwoorden als resource-namen, dit maakt zaken duidelijker. Gebruik de HTTP-methoden om het werkwoord-gedeelte van het request te specificeren.

e. Gebruik meervoudsvormen in URI-componenten om je API-URI’s over alle HTTP-methoden consistent te houden, met behulp van de metafoor van de verzameling, zoals de verzameling klanten of de verzameling orders.
Aanbevolen: /klanten/33245/orders/8769/orderregels/1
Niet: /klant/33245/order/8769/orderregel/1

f. Vermijd in URI’s het gebruik van enkelvoudige woorden die een verzameling inhouden, zoals “klant_lijst” als een resource. Gebruik dus meervoudsvormen om verzamelingen aan te duiden (bijvoorbeeld “klanten” in plaats van “klant_lijst”).

g. Gebruik kleine letters in URI-componenten en scheid woorden met underscores (“_”) of koppeltekens (‘-‘). Sommige servers negeren hoofdletters, dus is het beter om eenduidig te zijn door het gebruik van hoofdletters te vermijden.

h. Houd URI’s zo kort mogelijk, met zo weinig mogelijk componenten als zinvol is.

2.3. Gebruik HTTP Response-statuscodes om de status aan te geven

Response-statuscodes maken deel uit van de HTTP-specificatie. Er bestaat een flink aantal codes om de meest voorkomende situaties aan te duiden. Conform de wens om onze RESTful services te laten aansluiten bij de HTTP-specificatie, moeten onze web-API’s relevante HTTP-statuscodes teruggeven. Wanneer bijvoorbeeld een resource succesvol is aangemaakt (bijvoorbeeld via een POST request), moet de API HTTP-statuscode 201 teruggeven. Er is een lijst met geldige HTTP-statuscodes (Engels) beschikbaar met een gedetailleerde beschrijving van elke code.

De aanbevolen “Top 10” lijst van HTTP-statuscodes bestaat uit de volgende response-codes:
 

HTTP-statuscodes
200 OK Algemene statuscode. De meest voorkomende code die wordt gebruikt om een succesvol uitgevoerde operatie aan de duiden.
201 CREATED (gecreëerd/gemaakt) Resource is succesvol gecreëerd (via POST of PUT). Stel de Location header in zodat deze een link naar het nieuw gecreëerde resource bevat (na POST). De Response Body kan al of niet aanwezig zijn.
204 NO CONTENT (geen inhoud) Geeft een succesvolle operatie aan, maar zonder inhoud in de Response Body; vaak gebruikt voor DELETE en PUT operaties.
400 BAD REQUEST (ongeldig request/verzoek) Algemene fout in het geval dat het uitvoeren van het request een ongeldige status zou veroorzaken. Enkele voorbeelden hiervan zijn domeinvalidatie-fouten of ontbrekende gegevens.
401 UNAUTHORIZED (niet geautoriseerd/ niet toegestaan) Response met foutcode in het geval van ontbrekend of ongeldig bewijs van geldige verificatie/autorisatie.
403 FORBIDDEN (verboden toegang) Foutcode in het geval dat de gebruiker niet bevoegd/geautoriseerd is om de operatie uit te voeren, of wanneer het resource om één of andere reden niet beschikbaar is, bijvoorbeeld vanwege tijdslimieten (time constraints).
404 NOT FOUND (niet gevonden) Wordt gebruikt wanneer het opgevraagde resource niet is gevonden, omdat dit resource niet bestaat, of omdat de service vanwege veiligheidsredenen een 401 of 403 wil verbergen/maskeren.
405 METHODE NOT ALLOWED (methode niet toegestaan) Wordt gebruikt om aan te geven dat de gevraagde URI wel bestaat, maar dat de gevraagde HTTP-methode niet van toepassing is. Bijvoorbeeld, POST /klanten/12345 waarbij de API de creatie van het resource niet op deze manier ondersteunt (met een meegeleverde ID). De Allow HTTP header moet bij het retourneren van een 405 worden ingesteld, om duidelijk te maken welke HTTP-methoden wel worden ondersteund. In het vorige geval zou de header er zo uitzien: “Allow: GET, PUT, DELETE”.
409 CONFLICT Wordt teruggegeven wanneer een resource-conflict zou worden veroorzaakt door het request uit te voeren. Voorbeelden zijn dubbele vermeldingen, zoals een poging om twee klanten met dezelfde informatie te creëren, en het verwijderen van het bovenste object in een boomstructuur van objecten terwijl “cascade-delete” niet wordt ondersteund.
500 INTERNAL SERVER ERROR (interne server-fout) Geef deze foutcode nooit opzettelijk terug. Dit is een algemene “catch-all” foutcode wanneer de server-kant een exceptie teruggeeft. Gebruik dit alleen voor fouten die de consumer aan zijn kant niet kan behandelen.

2.4. Bied zowel JSON als XML aan

Geef de voorkeur aan JSON ondersteuning, tenzij je werkzaam bent in een sterk gestandaardiseerde en gereguleerde bedrijfstak of omgeving die XML, schema-validatie en namespaces vereist. Eventueel kun je zowel JSON als XML aanbieden, als de extra kosten geen bezwaar zijn. Idealiter laat je consumers wisselen tussen het gebruik van de HTTP Accept header, of door alleen het veranderen van de extensie in de URI van .xml naar .json.

Hier is wel een waarschuwing op zijn plaats; zodra we beginnen over XML-ondersteuning, hebben we het over validatieschema’s, namespaces, enzovoort. Tenzij de bedrijfstak waarvoor je werkt dat vereist, kun je het ondersteunen van die complexiteit in eerste instantie beter vermijden. JSON is ontworpen met het doel om eenvoudig, beknopt en functioneel zijn. Probeer indien mogelijk je XML daarop te laten lijken.

Met andere woorden, laat de XML die wordt teruggegeven op JSON lijken: eenvoudig en makkelijk te lezen, zonder de aanwezigheid van het schema en de namespace-gegevens – alleen gegevens en links. Als het resultaat complexer wordt dan dit, zullen de kosten van XML snel omhoog gaan.

NB: http://json-schema.org/ biedt validatiemogelijkheden voor schema-stijlen, als je daar behoefte aan hebt.

2.5. Maak “fijnmazige” resources

Wanneer je begint is het eenvoudiger om API’s te bouwen die het onderliggende applicatiedomein of de database-architectuur van je systeem nabootsen. Uiteindelijk wil je wellicht geaggregeerde services maken – services die meerdere onderliggende resources gebruiken – om versnippering te verminderen. Maar het is veel makkelijker om later grotere resources te creëren uit individuele resources, dan om fijnmazige of individuele resources te maken uit grotere aggregaten. Maak het jezelf makkelijk en begin met kleine, gemakkelijk te definiëren resources, die CRUD-functionaliteit leveren (Create, Read, Update, Delete, de vier basisoperaties die op gegevens kunnen worden toegepast). Later kun je use-case georiënteerde resources maken om versnippering te voorkomen.

2.6. Overweeg het aanbieden van koppelingen (links)

Eén van de principes van REST is “gekoppeldheid”, via hypermedia links (denk aan HATEOAS). Hoewel services ook nuttig zijn zonder links, worden API’s meer zelf-beschrijvend en beter vindbaar wanneer in de response links worden geretourneerd. Op zijn minst kan een “zelf” link clients informeren hoe de gegevens kunnen worden opgehaald. Bovendien kun je gebruik maken van de HTTP Location header om een link op te nemen over het creëren van het resource via POST (of PUT). Voor verzamelingen die worden geretourneerd in een response die paginering (bladeren) ondersteunt, kunnen “eerste”, “laatste”, “volgende” en “vorige” links erg handig zijn.

Er zijn veel formaten voor links. De HTTP Web Linking Specificatie (RFC5988) geeft de volgende uitleg van een link.
Een link is een koppeling van een bepaald relatie-type tussen twee resources die worden geïdentificeerd door “Internationalised Resource Identifiers” (IRI’s) [RFC3987], en bestaat uit:
– een context IRI,
– een link relatie-type, dat de betekenis van de link aangeeft
– een doel IRI (“target IRI”), en
– optionele doel-attributen (“target attributes”).

Een link kan worden beschouwd als een statement in de vorm “{context IRI} heeft een {relatie-type} resource op {doel IRI}, die {doel-attributen} heeft.”

Op zijn minst moet je links in de HTTP Link header plaatsen, zoals aanbevolen in de specificatie. Je kunt ook een JSON representatie van deze HTTP link-stijl (zoals Atom-stijl links, zie: RFC4287) in je JSON representaties opnemen. Later kun je meer complexe link-stijlen toevoegen, zoals HAL+JSON, Sirene, Collection+JSON en/of JSON-LD, enzovoort, wanneer je REST API’s meer volwassen worden.

3. HTTP methoden gebruiken voor RESTful services

De HTTP werkwoorden omvatten een groot deel van onze uniforme interface constraint en zij leveren ons de acties voor de resources die met zelfstandige naamwoorden zijn aangeduid. De primaire of meest gangbare HTTP werkwoorden (of methoden, zoals ze genoemd behoren te worden) zijn POST, GET, PUT en DELETE. Deze komen overeen met creëren, lezen, wijzigen en verwijderen (of CRUD). Er bestaan ook andere werkwoorden, maar die worden minder vaak gebruikt. Van die minder frequente methoden worden OPTIONS en HEAD vaker gebruikt dan anderen.

Hieronder staat een tabel met een overzicht van de aanbevolen waarden van response-statuscodes (“return values”) van de primaire HTTP-methoden in combinatie met de resource URI’s:
 

HTTP werkwoord Volledige verzameling (bijvoorbeeld /klanten) Specifiek item (bijvoorbeeld /klanten/id)
GET 200 (OK), lijst met klanten. Gebruik paginering, sortering en filteren om door lange lijsten te navigeren. 200 (OK), enkele klant.
404 (niet gevonden), als ID niet gevonden wordt of ongeldig is.
PUT 404 (niet gevonden), tenzij je elke resource in de volledige verzamelingen wilt wijzigen/vervangen. 200 (OK) or 204 (geen inhoud).
404 (niet gevonden) als ID niet gevonden wordt of ongeldig is.
UPDATE 201 (gecreëerd), Location header met link naar /klanten/{id} met een nieuw ID. 404 (niet gevonden).
DELETE 404 (niet gevonden), tenzij je de volledige verzameling wilt verwijderen (niet vaak gewenst). 200 (OK).
404 (niet gevonden) als ID niet gevonden wordt of ongeldig is.

Hierna worden de belangrijkste HTTP-methoden meer in detail besproken:

GET
De HTTP GET methode wordt gebruikt voor het ophalen (of lezen) van een representatie van een resource. In de “happy flow” (dat wil zeggen, zonder foutmelding), geeft GET een representatie in JSON of XML en een HTTP response-statuscode 200 (OK). In een foutsituatie is het meestal een 404 (niet gevonden) of 400 (ongeldig request/verzoek).

Volgens het ontwerp van de HTTP specificatie worden GET requests (samen met HEAD) alleen gebruikt om gegevens te lezen en niet om te wijzigen. Daarom worden ze, wanneer ze op deze manier worden gebruikt, als veilig beschouwd. Dat wil zeggen dat ze kunnen worden aangeroepen zonder het risico van wijziging van gegevens of gegevenscorruptie – eenmaal aanroepen heeft hetzelfde effect als tienmaal aanroepen, of geen enkele aanroep. Bovendien is GET (evenals HEAD) “idempotent”, wat betekent dat het uitvoeren van meerdere identieke requests eindigt met hetzelfde resultaat als een enkel request.

Laat geen onveilige operaties uitvoeren via GET – deze operatie mag nooit een resource wijzigen op de server.
 

Opmerking:In onderstaande voorbeelden is een niet daadwerkelijk bestaand topleveldomein “comnl” gebruikt.

Voorbeelden:
GET http://www.voorbeeld.comnl/klanten/12345
GET http://www.voorbeeld.comnl/klanten/12345/orders
GET http://www.voorbeeld.comnl/gloeilampen/steekproef


PUT
PUT wordt meestal gebruikt voor update-mogelijkheden. Het request wordt gestuurd naar een bekende resource-URI, waarbij de Request Body de nieuw bijgewerkte representatie van het originele resource bevat.

PUT kan echter ook worden gebruikt om een resource te creëren in het geval waarin het resource-ID wordt gekozen door de client in plaats van door de server. Met andere woorden, als de PUT naar een URI gaat die de waarde van een niet-bestaande resource-ID bevat. Nogmaals, de Request Body bevat een resource-representatie. Velen vinden dit ingewikkeld en verwarrend, daarom dient deze methode voor het creëren van resources met mate te worden gebruikt, of helemaal niet.

Je kunt ook gebruik maken van POST om nieuwe resources te creëren en het door de client gedefinieerde ID in de Body representatie opnemen – waarschijnlijk naar een URI die niet het ID van het resource bevat (zie POST hieronder).

Geef na een succesvolle update een 200 statuscode terug uit een PUT (of 204 als de Body geen inhoud bevat). Als PUT wordt gebruikt voor de creatie van een resource, geef dan na een succesvolle creatie HTTP-statuscode 201 terug. Een Body in de response is optioneel – het zorgt voor meer bandbreedte bij de consumers. Het is na een succesvolle creatie niet noodzakelijk om via een Location header een link terug te geven omdat de client het resource ID al heeft ingesteld.

PUT is geen veilige operatie, aangezien hij de status op de server wijzigt (of creëert), maar hij is wel idempotent. Met andere woorden, als je met behulp van PUT een resource creëert of wijzigt en daarna dezelfde aanroep doet, zal het resource er nog steeds zijn en dezelfde status hebben als na de eerste aanroep.

Als een aanroep van een PUT op een resource zorgt voor het ophogen van een teller in het resource, is de aanroep niet langer idempotent. Soms gebeurt dat en kan het voldoende zijn om te documenteren dat de oproep niet idempotent is. Het is echter aan te bevelen om PUT requests wel idempotent te houden. Het wordt sterk aanbevolen om voor niet-idempotente requests POST te gebruiken.

Voorbeelden:
PUT http://www.voorbeeld.comnl/klanten/12345
PUT http://www.voorbeeld.comnl/klanten/12345/orders/98765
PUT http://www.voorbeeld.comnl/gloeilampen/specificaties


POST
Het werkwoord POST wordt het meest gebruikt voor het creëren van nieuwe resources. Het wordt vooral gebruikt om ondergeschikte resources te creëren, dat wil zeggen, ondergeschikt aan een ander resource zoals een “parent” resource (ouder-resource). Met andere woorden, POST bij het maken van een nieuw resource naar het parent resource en de service verzorgt het koppelen van het nieuwe resource aan de parent, het toewijzen van een ID (nieuw resource-URI), enzovoort.

Geef na een succesvolle creatie HTTP-statuscode 201 terug, met een Location header met een link naar het nieuw gemaakte resource met de 201 HTTP-status.

POST is niet veilig of idempotent, deze operatie wordt daarom aanbevolen voor niet-idempotente resource-requests. Het uitvoeren van twee identieke POST requests zal hoogstwaarschijnlijk resulteren in twee resources met dezelfde informatie.

Voorbeelden:
POST http://www.voorbeeld.comnl/klanten
POST http://www.voorbeeld.comnl/klanten/12345/orders


DELETE
DELETE is vrij eenvoudig te begrijpen. Deze operatie wordt gebruikt om een resource, dat wordt geïdentificeerd door een URI, te verwijderen.

Geef na een succesvolle verwijdering HTTP-statuscode 200 (OK) terug, samen met een Response Body, en eventueel de representatie van het verwijderde item (maar vaak kost dit teveel bandbreedte), of een “gemaskeerde response” (zie de tabel met return-statuscodes hiervoor). Een alternatief is het retourneren van HTTP-statuscode 204 (geen inhoud) zonder Response Body. Met andere woorden, de aanbevolen responses zijn een 204 statuscode zonder Body of een response in JSEND-stijl en HTTP-statuscode 200.

Volgens de HTTP-specificaties zijn DELETE operaties idempotent. Als je een resource DELETE, wordt het verwijderd. Herhaaldelijk aanroepen van DELETE op dat resource heeft hetzelfde eindresultaat: het resource is verdwenen. Als de aanroep van een DELETE bijvoorbeeld een teller verlaagt (binnen het resource), is de DELETE aanroep niet langer idempotent. Zoals eerder vermeld, kunnen gebruiksstatistieken en metingen worden bijgewerkt, terwijl de service nog steeds als idempotent kan worden beschouwd. zolang er maar geen resource-gegevens worden gewijzigd. Het gebruik van POST wordt aanbevolen voor niet-idempotente resource-requests.

Er is echter een voorbehoud van toepassing op DELETE idempotentie. Het voor de tweede maal aanroepen van DELETE op een resource zal vaak resulteren in een 404 (niet gevonden), omdat het resource al werd verwijderd en daarom niet meer vindbaar is. Dat maakt DELETE operaties niet meer idempotent, maar dit is een geschikt compromis als resources daadwerkelijk uit de database zijn verwijderd, in plaats van dat ze gemarkeerd worden als verwijderd.

Voorbeelden:
DELETE http://www.voorbeeld.comnl/klanten/12345
DELETE http://www.voorbeeld.comnl/klanten/12345/orders
DELETE http://www.voorbeeld.comnl/gloeilampen/steekproef

4. Naamgeving van resources

4.1. Werkwoorden en zelfstandige naamwoorden

Naast het adequaat gebruiken van de HTTP-werkwoorden is de naamgeving van resources misschien wel het meest besproken en belangrijkste concept om rekening mee te houden bij het maken van een begrijpelijke, eenvoudig toe te passen webservice-API. Wanneer de resources goed worden vermeld, is een API intuïtief en makkelijk te gebruiken. Als dit op een verkeerde manier gebeurt, kan dezelfde API moeilijk te gebruiken en te begrijpen zijn. Hieronder volgen enkele tips die je op weg helpen bij het maken van de resource-URI’s voor je nieuwe API.

Uiteindelijk is een RESTful API een verzameling van URI’s, HTTP-aanroepen naar die URI’s en een aantal JSON en/of XML representaties van de resources, waarvan vele relationele links bevatten. Het REST principe van adresseerbaarheid wordt ingevuld door de URI’s. Elk resource heeft zijn eigen adres of URI – elk relevante stukje informatie dat de server kan aanbieden wordt ontsloten als een resource. De constraint van de uniforme interface wordt gedeeltelijk opgelost door de combinatie van URI’s en HTTP werkwoorden, en door ze te gebruiken in overeenstemming met de standaards en conventies.

Om te bepalen welke resources binnen je systeem aanwezig zijn, benoem je ze als zelfstandige naamwoorden in plaats van werkwoorden of acties. Met andere woorden, een RESTful URI verwijst naar een resource dat een “ding” is, in plaats van dat hij naar een actie verwijst. Zelfstandige naamwoorden hebben eigenschappen die werkwoorden niet hebben, dit is een andere onderscheidende factor.

Enkele voorbeeld van resources zijn:

  • Gebruikers van een systeem.
  • Cursussen waarop een student is ingeschreven.
  • Tijdlijn van berichten van een gebruiker.
  • Gebruikers die andere gebruikers volgen.
  • Een artikel over paardrijden.

 
Elk resource in een systeem met services heeft tenminste één URI die hem identificeert. En het is het beste als die URI zinvol is en het resource adequaat beschrijft. URI’s moeten een voorspelbare, hiërarchische structuur volgen om de duidelijkheid te verbeteren en daarmee ook de bruikbaarheid. Voorspelbaar in de zin dat ze consistent zijn, hiërarchisch in de zin dat gegevens structuur hebben – relaties. Dit is geen REST regel of constraint, maar deze aanwijzingen verbeteren de API.

REST API’s zijn geschreven voor de clients. De naam en de structuur van URI’s moeten betekenis hebben voor deze clients. Het is vaak moeilijk om te weten wat de begrenzingen van de gegevens moeten zijn, maar met kennis van je gegevens ben je hoogstwaarschijnlijk in staat om een geschikte representatie aan je clients aan te bieden. Ontwerp voor je clients, niet voor je gegevens.

Laten we als voorbeeld een ordersysteem beschrijven met klanten, orders, orderregels, artikelen, enzovoort. De URI’s die je nodig hebt voor het beschrijven van de resources in deze verzameling van services zouden er uit kunnen zien zoals in de volgende paragraaf is beschreven.

4.2. Voorbeelden van resource-URI’s

Om een nieuwe klant in het systeem toe te voegen (te creëren), kunnen we gebruik maken van:
POST http://www.voorbeeld.comnl/klanten

Om een klant met klant-ID 33245 te lezen:
GET http://www.voorbeeld.comnl/klanten/33245
Dezelfde URI kan worden gebruikt voor PUT en DELETE, respectievelijk voor het wijzigen en verwijderen.

Dit zijn de voorgestelde URI’s voor artikelen:
POST http://www.voorbeeld.comnl/artikelen
voor het creëren van een nieuw artikel.

GET | PUT | DELETE http://www.voorbeeld.comnl/artikelen/66432
respectievelijk voor het lezen, wijzigen en verwijderen van artikel-ID 66432.

Nu wordt het interessant … hoe zit het met het creëren van een nieuwe order voor een klant? Een optie zou kunnen zijn:
POST http://www.voorbeeld.comnl/order
Dit zou kunnen werken om een order te maken, maar je zou kunnen stellen dat het buiten de context van een klant ligt.

Omdat we een order voor een klant willen maken (let op de relatie), is deze URI niet zo intuïtief als hij zou kunnen zijn. De volgende URI is duidelijker:
POST http://www.voorbeeld.comnl/klanten/33245/orders
Nu weten we dat we een order voor klant-ID 33245 maken.

Wat zou de volgende operatie teruggeven?
GET http://www.voorbeeld.comnl/klanten/33245/orders
Waarschijnlijk een lijst met orders die klant-ID 33245 besteld heeft of bezit. Let op: we kunnen ervoor kiezen om geen DELETE of PUT voor deze URI aan te bieden, omdat hij werkt op een verzameling.

Om verder te gaan met de hiërarchische opbouw, wat vind je van de volgende URI?
POST http://www.voorbeeld.comnl/klanten/33245/orders/8769/orderregels
Dit zou een orderregel toevoegen aan order 8769 (die bestemd is voor klant-ID 33245). Een GET voor die URI zou alle orderregels voor die order teruggeven. Echter, als orderregels niet alleen betekenis zouden hebben binnen de context van de klant maar ook daarbuiten, zouden we deze URI aanbieden:
POST www.voorbeeld.comnl/orders/8769/orderregels

Omdat er meerdere URI’s voor een bepaald resource kunnen zijn, kunnen we ook deze aanbieden:
GET http://www.voorbeeld.comnl/orders/8769
Deze URI biedt de mogelijkheid om een order via het ordernummer op te halen zonder dat je het klantnummer hoeft te kennen.

We gaan een laag dieper in de hiërarchie:
GET http://www.voorbeeld.comnl/klanten/33245/orders/8769/orderregels/1
Dit zou alleen de eerste orderregel in die order teruggeven.

Je kunt nu zien hoe de hiërarchische opzet werkt. Er zijn geen vaste regels, maar zorg er wel voor dat de vastgestelde structuur zinvol is voor consumers van je services. Zoals met alles in het vak van software-ontwikkeling, is naamgeving cruciaal voor succes.

Bekijk eens een aantal veel gebruikte API’s om hier wat meer gevoel voor te krijgen en maak gebruik van de ideeën van je teamgenoten om de resource-URI’s van je API te verfijnen. Enkele voorbeelden van API’s zijn:

  • Twitter: https://dev.twitter.com/docs/api
  • Facebook: http://developers.facebook.com/docs/reference/api/
  • LinkedIn: https://developer.linkedin.com/apis

4.3. Anti-patronen voor resource-namen

Hoewel we een aantal voorbeelden van geschikte resource-namen hebben besproken, is het soms ook informatief om een aantal “anti-patronen” te bekijken. Hieronder vind je enkele voorbeelden van slechte RESTful resource-URI’s die we gezien hebben. Dit zijn voorbeelden van hoe het niet moet!

Om te beginnen gebruiken services soms een enkele URI om de service-interface te specificeren, waarbij gebruik wordt gemaakt van query-string parameters om de gevraagde operatie en/of het HTTP-werkwoord op te geven. Bijvoorbeeld, om de klant met ID 12345 te wijzigen, kan het request voor een JSON Body als volgt zijn opgebouwd:
GET http://api.voorbeeld.comnl/services?op=wijzig_klant&id=12345&format=json

Inmiddels weet je dat je dit niet moet doen. Ook al is de “services” aanduiding in de URI een zelfstandig naamwoord, deze URI is niet zelfbeschrijvend aangezien de URI-hiërarchie voor alle requests hetzelfde is. Bovendien maakt hij gebruik van GET als HTTP werkwoord, terwijl we een wijziging uitvoeren. Dit gaat tegen het gevoel in en is lastig (zelfs foutgevoelig) om te gebruiken als je een client moet ontwikkelen.

Hier is een ander voorbeeld met dezelfde operatie voor het wijzigen van een klant:
GET http://api.voorbeeld.comnl/wijzig_klant/12345

En zijn eveneens slechte tegenhanger:
GET http://api.voorbeeld.comnl/klanten/12345/wijzig

Je zult dit vaak zien als je de service-specificaties van andere ontwikkelaars bekijkt. De ontwikkelaar probeert RESTful resource-namen te creëren en heeft enige vooruitgang geboekt. Maar je kunt het beter anders doen – je herkent nu het werkwoord-deel in de URI. Merk op dat we het “wijzig” werkwoord niet nodig hebben in de URI, want we kunnen vertrouwen op het HTTP-werkwoord om die operatie aan te duiden. Om dit te illustreren geven we nog een voorbeeld waarin de resource-URI onnodige redundantie bevat:
PUT http://api.voorbeeld.comnl/klanten/12345/wijzig

Als zowel PUT als “wijzig” in het request voorkomen, vraag je om verwarring bij de gebruikers van de service. Is “wijzig” het resource? We hebben nu voldoende tijd besteed aan dit onderwerp, we denken dat het punt duidelijk is.

4.4. Het gebruik van meervoudsvormen

We bespreken nu de vraag of URI-componenten in de hiërarchie met enkelvoud of meervoud dienen te worden aangeduid. Hoe moet bijvoorbeeld je URI voor het ophalen van een representatie van een klant er uitzien, zo:
GET http://www.voorbeeld.comnl/klant/33245
of zo:
GET http://www.voorbeeld.comnl/klanten/33245

Er zijn goede argumenten voor beide alternatieven, maar de algemeen aanvaarde best practice is om in URI-componenten gebruik maken van meervoudsvormen om je API URI’s over alle HTTP-methoden consistent te houden. Deze redenering is gebaseerd op het idee dat de klanten een verzameling vormen binnen het geheel aan services en de ID (bijvoorbeeld 33245) verwijst naar één van die klanten in de verzameling.

Gebaseerd op deze regel geven we een voorbeeld met meerdere URI-componenten, dat er met gebruik van meervoudsvormen als volgt uit zou zien:

GET http://www.voorbeeld.comnl/klanten/33245/orders/8769/orderregels/1
waarbij “klanten”, “orders” en ‘orderregels” als URI-componenten allemaal in de meervoudsvorm staan.

Dit houdt in dat je eigenlijk kunt volstaan met twee basis-URI’s voor elk resource op het hoogste niveau (elk “root resource”). Eén voor de creatie van het resource binnen een verzameling en een tweede voor het lezen, wijzigen en verwijderen van het resource via zijn identifier. Bijvoorbeeld, het creëren wordt in het geval van klanten afgehandeld door de volgende URI:
POST http://www.voorbeeld.comnl/klanten

Lezen, wijzigen en verwijderen worden behandeld door de volgende:
GET | PUT | DELETE http://www.voorbeeld.comnl/klanten/{id}

Zoals eerder vermeld, kunnen er meerdere URI’s voor een bepaald resource zijn, maar de volledige CRUD mogelijkheden die je vaak als minimum nodig hebt, kunnen doeltreffend worden afgehandeld met twee eenvoudige URI’s.

Je vraagt je misschien af of er een geval bestaat waarbij de meervoudsvorm geen zin heeft. Zo’n geval bestaat inderdaad, namelijk in de situaties dat er geen sprake is van een verzameling met meerdere exemplaren. Met andere woorden, het is acceptabel om een resourcenaam in enkelvoud te gebruiken wanneer slechts één geval (exemplaar, “occurrence”) van het resource kan bestaan – dit noemen we een singleton resource. Als er bijvoorbeeld een enkel, overkoepelend configuratie-resource is, zou je een enkelvoudig naamwoord gebruiken om dit weer te geven:
GET | PUT | DELETE http://www.voorbeeld.comnl/configuratie

Je ziet dat een configuratie-ID en het werkwoord POST ontbreken. Als er één (en niet meer dan één) configuratie per klant is, kan de URI als volgt zijn opgebouwd:
GET | PUT | DELETE http://www.voorbeeld.comnl/klanten/12345/configuratie

Ook in dit geval is er geen ID voor de configuratie en wordt werkwoord POST niet gebruikt. Hoewel, we zijn er zeker van dat er in beide gevallen argumenten kunnen zijn om POST wel te gebruiken.

5. Idempotentie

Idempotentie is een niet-gangbaar woord dat mensen soms verwarrend vinden, zeker de academische definitie.

Met betrekking tot RESTful services mag een operatie (of service-aanroep) idempotent genoemd worden als clients dezelfde aanroep herhaaldelijk kunnen doen, met hetzelfde resultaat tot gevolg. Met andere woorden, het maken van meerdere identieke requests heeft hetzelfde effect als het maken van een enkel request. Hou er rekening mee dat idempotente operaties weliswaar hetzelfde resultaat op de server produceren (zonder bij-effecten), maar dat de response zelf wel kan verschillen (de status van een resource kan bijvoorbeeld tussen requests gewijzigd worden).

De PUT en DELETE zijn gedefinieerd als zijnde idempotent. Er is echter een addertje onder het gras met DELETE. Een succesvolle DELETE zal gewoonlijk een response-statuscode 200 (OK) of 204 (geen inhoud) teruggeven, maar opeenvolgende aanroepen zullen vaak een 404 (niet gevonden) retourneren, tenzij de service zo is geconfigureerd dat hij een resource “markeert” voor verwijdering zonder deze daadwerkelijk te verwijderen. Wanneer de service het resource echter daadwerkelijk verwijdert, zal het resource bij de volgende aanroep niet gevonden worden en een 404 teruggeven. De status op de server is hetzelfde na elke DELETE aanroep, maar de response is verschillend.

De operaties GET, HEAD, OPTIONS en TRACE zijn gedefinieerd als “veilig”, wat betekent dat ze zijn uitsluitend zijn bedoeld voor het ophalen van gegevens. Dit maakt ze bovendien idempotent want meerdere, identieke requests zullen zich hetzelfde gedragen.

Gratis Nederlandstalige training en cursus ICT / Informatie- en Communicatie-Technologie