RESTful-web-API-ontwerp

De meeste moderne webtoepassingen maken API's beschikbaar die clients kunnen gebruiken om te communiceren met de toepassing. Een goed ontworpen web-API moet erop gericht zijn om ondersteuning te bieden aan:

  • Platformonafhankelijkheid. Een client moet de API kunnen aanroepen, ongeacht hoe de API intern wordt geïmplementeerd. Dit vereist het gebruik van standaardprotocollen en een mechanisme waarbij de client en de webservice overeenstemming kunnen bereiken over de indeling van de gegevens voor de uitwisseling.

  • Service-evolutie. De web-API moet functionaliteit onafhankelijk van clienttoepassingen kunnen ontwikkelen en toevoegen. Als de API zich verder ontwikkelt, moeten bestaande clienttoepassingen zonder aanpassing blijven werken. Alle functionaliteit moet detecteerbaar zijn, zodat clienttoepassingen deze volledig kunnen gebruiken.

In deze richtlijnen worden problemen beschreven waarmee u rekening moet houden bij het ontwerpen van een web-API.

Wat is REST?

In 2000 stelde Roy Fielding Representational State Transfer (REST) voor als een architecturale benadering voor het ontwerpen van webservices. REST is een architecturale stijl voor het bouwen van gedistribueerde systemen op basis van hypermedia. REST is onafhankelijk van eventuele onderliggende protocollen en is niet noodzakelijkerwijs gekoppeld aan HTTP. De meest voorkomende REST API-implementaties maken echter gebruik van HTTP als het toepassingsprotocol. Deze handleiding is gericht op het ontwerpen van REST API's voor HTTP.

Een belangrijk voordeel van REST via HTTP is dat er open standaarden worden gebruikt en de implementatie van de API of de clienttoepassingen niet wordt gekoppeld aan een specifieke implementatie. Een REST-webservice kan bijvoorbeeld worden geschreven in ASP.NET, terwijl clienttoepassingen elke taal of toolset kunnen gebruiken die HTTP-aanvragen kan genereren en HTTP-antwoorden kan parseren.

Hier volgen enkele van de belangrijkste principes van RESTful-API's via HTTP:

  • REST-API's zijn ontworpen rond resources, oftewel elk soort object, gegevens of service die voor de client toegankelijk is.

  • Een resource heeft een id, een URI die een unieke identificatie van die resource is. Bijvoorbeeld: de URI voor een bepaalde klantorder kan zijn:

    https://adventure-works.com/orders/1
    
  • Clients communiceren met een service door het uitwisselen van weergaven van resources. Veel web-API's gebruiken JSON als de indeling voor uitwisseling. Een GET-aanvraag voor de hierboven vermelde URI kan bijvoorbeeld deze antwoordtekst retourneren:

    {"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}
    
  • REST-API's gebruiken een uniforme interface waarmee ze de client en de service-implementaties kunnen loskoppelen. Voor REST API's die zijn gebouwd op HTTP, bevat de uniforme interface het gebruik van standaard HTTP-werkwoorden voor het uitvoeren van bewerkingen op resources. De meest voorkomende bewerkingen zijn GET, POST, PUT, PATCH en DELETE.

  • REST-API's gebruiken een staatloos aanvraagmodel. HTTP-aanvragen moeten onafhankelijk zijn en kunnen in elke volgorde plaatsvinden, zodat het niet haalbaar is om tijdelijke statusinformatie tussen aanvragen te bewaren. De enige plaats waar informatie wordt opgeslagen is in de resources zelf en elke aanvraag moet een atomische bewerking zijn. Door deze beperking kunnen webservices zeer schaalbaar zijn, omdat er geen vereiste is voor het bewaren van affiniteit tussen clients en specifieke servers. Een willekeurige server kan elke aanvraag vanaf elke client afhandelen. Er zijn echter wel andere factoren die de schaalbaarheid kunnen beperken. Veel webservices schrijven bijvoorbeeld naar een back-endgegevensarchief. Dit kan lastig zijn om uit te schalen. Zie Horizontaal, verticaal en functioneel partitioneren voor meer informatie over strategieën voor het uitschalen van een gegevensarchief.

  • REST-API's worden aangestuurd door hypermedia-koppelingen die zijn opgenomen in de weergave. Hieronder ziet u bijvoorbeeld een JSON-weergave van een order. Deze bevat koppelingen naar ophalen of bijwerken van de klant die is gekoppeld aan de order.

    {
      "orderID":3,
      "productID":2,
      "quantity":4,
      "orderValue":16.60,
      "links": [
        {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" },
        {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" }
      ]
    }
    

In 2008 stelde Leonard Richardson het volgende vervaldatummodel voor web-API's voor:

  • Niveau 0: Definieer een URI en alle bewerkingen zijn POST-aanvragen voor deze URI.
  • Niveau 1: Maak aparte URI's voor afzonderlijke resources.
  • Niveau 2: Gebruik HTTP-methoden voor het definiëren van bewerkingen op resources.
  • Niveau 3: Gebruik hypermedia (HATEOAS, hieronder beschreven).

Niveau 3 komt overeen met een echt RESTful-API volgens de definitie van Fielding. In de praktijk vallen veel gepubliceerde web-API's ergens rond niveau 2.

Het API-ontwerp organiseren rond resources

Richt u op de zakelijke entiteiten die de web-API beschikbaar maakt. De primaire entiteiten kunnen in een e-commerce-systeem bijvoorbeeld klanten en orders zijn. Het maken van een order kan worden bereikt via een HTTP POST-aanvraag die de ordergegevens bevat. Het HTTP-antwoord geeft aan of de order wel of niet is geplaatst. Indien mogelijk moeten de bron-URI's worden gebaseerd op zelfstandige naamwoorden (de resource) en niet op werkwoorden (de bewerkingen op de bron).

https://adventure-works.com/orders // Good

https://adventure-works.com/create-order // Avoid

Een resource hoeft niet te zijn gebaseerd op één fysiek gegevensitem. Een order-resource kan bijvoorbeeld intern worden geïmplementeerd als meerdere tabellen in een relationele database, maar aan de client worden gepresenteerd als één entiteit. Vermijd het maken van API's die niet meer doen dan de interne structuur van een database spiegelen. Het doel van REST is om een model te maken van entiteiten en de bewerkingen die een toepassing op deze entiteiten kan uitvoeren. Een client moet niet worden blootgesteld aan de interne uitvoering.

Entiteiten worden vaak samen in verzamelingen gegroepeerd (orders, klanten). Een verzameling is een afzonderlijke resource van het item binnen de verzameling en moet een eigen URI hebben. De volgende URI vertegenwoordigt bijvoorbeeld de verzameling van orders:

https://adventure-works.com/orders

Door een HTTP GET-aanvraag te verzenden naar de verzameling-URI, haalt u een lijst met items op in de verzameling. Elk item in de verzameling heeft ook een eigen unieke URI. Een HTTP GET-aanvraag naar de URI van het artikel retourneert de gegevens van dat item.

Neem een consistente naamgeving in URI's aan. In het algemeen is het nuttig om meervoudwoorden te gebruiken voor URI's die verwijzen naar verzamelingen. Het is raadzaam om URI's voor verzamelingen en objecten in een hiërarchie te organiseren. Bijvoorbeeld: /customers is het pad naar de verzameling klanten en /customers/5 is het pad naar de klant waarvan de id gelijk is aan 5. Deze aanpak helpt om de web-API intuïtief te houden. Ook versturen veel web-API-frameworks aanvragen op basis van geparameteriseerde URI-paden, zodat u een route kunt definiëren voor het pad /customers/{id}.

Denk ook na over de relaties tussen de verschillende soorten resources en hoe u deze koppelingen mogelijk beschikbaar kunt maken. Voorbeeld: de /customers/5/orders geeft mogelijk alle orders voor klant 5 weer. U kunt ook de andere richting op gaan en de koppeling van een order terug uitdrukken naar de klant met een URI, bijvoorbeeld /orders/99/customer. Het te ver uitbreiden van dit model, kan echter lastig worden om te implementeren. Een betere oplossing is het aanbieden van navigeerbare koppelingen naar de bijbehorende resources in de hoofdtekst van het HTTP-antwoordbericht. Dit mechanisme wordt uitgebreid beschreven in de sectie HATEOAS gebruiken om navigatie naar gerelateerde resources mogelijk te maken.

In complexere systemen kan het verleidelijk zijn om URI's te geven waarmee een client door verschillende niveaus van relaties kan navigeren, bijvoorbeeld /customers/1/orders/99/products. Dit niveau van complexiteit is echter moeilijk te onderhouden en is star als de relaties tussen resources in de toekomst wijzigen. Probeer in plaats daarvan URI's relatief eenvoudig te houden. Wanneer een toepassing eenmaal een verwijzing naar een resource bevat, moet het mogelijk zijn deze naslaginformatie te gebruiken om items te vinden die betrekking hebben op deze resource. De voorgaande query kan worden vervangen door de URI /customers/1/orders om te zoeken naar alle bestellingen voor klant 1 en vervolgens door /orders/99/products om de producten in deze volgorde te vinden.

Tip

Voorkom dat de bron-URI's complexer zijn dan verzameling/item/verzameling.

Een andere factor is dat alle webaanvragen een belasting op de webserver opleggen. Hoe meer aanvragen, hoe groter de belasting. Probeer daarom veeleisende web-API's te vermijden die een groot aantal resources beschikbaar maken. Een dergelijke API vereist mogelijk een clienttoepassing om meerdere aanvragen te verzenden om alle gegevens te vinden die hiervoor nodig zijn. Mogelijk wilt u in plaats daarvan de gegevens denormaliseren en gerelateerde gegevens in grotere resources combineren, die kunnen worden opgehaald met één aanvraag. U moet deze benadering echter balanceren met de overhead voor het ophalen van gegevens die de client niet nodig heeft. Het ophalen van grote objecten kan de latentie van een aanvraag verhogen en extra bandbreedtekosten veroorzaken. Zie voor meer informatie over deze prestatie-antipatronen Intensieve I/O en Overbodige ophaalbewerking.

Vermijd de introductie van afhankelijkheden tussen de web-API en de onderliggende gegevensbronnen. Als uw gegevens bijvoorbeeld worden opgeslagen in een relationele database, hoeft de web-API niet elke tabel als een verzameling resources beschikbaar te maken. Dat is in feite eerder een slecht ontwerp. Zie in plaats daarvan de web-API als een abstractie van de database. Introduceer indien nodig een toewijzingslaag tussen de database en de web-API. Op die manier zijn clienttoepassingen geïsoleerd van wijzigingen aan het onderliggende database-schema.

Ten slotte is het misschien niet mogelijk om elke bewerking die is geïmplementeerd door een web-API op een specifieke resource, toe te wijzen. U kunt zulke niet-resource-scenario's verwerken via HTTP-aanvragen die een functie aanroepen en de resultaten retourneren als een HTTP-antwoordbericht. Een web-API die eenvoudige rekenmachine-bewerkingen implementeert, zoals optellen en aftrekken, kan bijvoorbeeld URI's leveren die deze bewerkingen als pseudo-resources beschikbaar maken, en de queryreeks gebruiken om de vereiste parameters op te geven. Een GET-aanvraag voor de URI /add?operand1=99&operand2=1 retourneert bijvoorbeeld een antwoordbericht met de hoofdtekst met de waarde 100. Wees echter spaarzaam in het gebruik van deze soorten URI's.

API-bewerkingen definiëren in termen van HTTP-methoden

Het HTTP-protocol definieert een aantal methoden die semantische betekenis toewijzen aan een aanvraag. De gebruikelijke HTTP-methoden die worden gebruikt door de meeste RESTful-web-API's zijn:

  • GET haalt een weergave van de resource op bij de opgegeven URI. De hoofdtekst van het antwoordbericht bevat de details van de aangevraagde resource.
  • POST maakt een nieuwe resource bij de opgegeven URI. De hoofdtekst van het aanvraagbericht bevat de details van de nieuwe resource. Houd er rekening mee dat POST ook kan worden gebruikt voor het activeren van bewerkingen die niet daadwerkelijk resources maken.
  • PUT maakt of vervangt de resource op de opgegeven URI. De hoofdtekst van het aanvraagbericht bevat de bron die moet worden gemaakt of bijgewerkt.
  • PATCH voert een gedeeltelijke update uit van een resource. De aanvraagtekst geeft de set van wijzigingen toe om toe te passen op de resource.
  • DELETE verwijdert de resource bij de opgegeven URI.

Het effect van een specifieke aanvraag moet ervan afhangen of de resource een verzameling of een afzonderlijk item is. De volgende tabel bevat een overzicht van de algemene conventies die door de meeste RESTful-implementaties zijn aangenomen met behulp van het voorbeeld van e-commerce. Niet al deze aanvragen kunnen worden geïmplementeerd. Dit is afhankelijk van het specifieke scenario.

Resource POST GET PUT DELETE
/klanten Een nieuwe klant maken Alle klanten ophalen Klanten bulksgewijs bijwerken Alle klanten verwijderen
/klanten/1 Error Ophalen van de gegevens voor klant 1 De details van klant 1 bijwerken als deze bestaat Klant 1 verwijderen
/klanten/1/orders Een nieuwe order voor klant 1 maken Ophalen van alle orders voor klant 1 Orders voor klant 1 bulksgewijs bijwerken Alle orders voor klant 1 verwijderen

De verschillen tussen POST, PUT en PATCH kunnen verwarrend zijn.

  • Een POST-aanvraag maakt een resource. De server wijst een URI toe voor de nieuwe resource en stuurt deze URI naar de client. In het REST-model past u vaak POST-aanvragen toe bij verzamelingen. De nieuwe resource wordt toegevoegd aan de verzameling. Een POST-aanvraag kan ook worden gebruikt om gegevens voor verwerking naar een bestaande resource te sturen, zonder dat een nieuwe resource wordt gemaakt.

  • Een PUT-aanvraag maakt een resource of werkt een bestaande resource bij. De client geeft de URI voor de resource. De aanvraagtekst bevat een volledige weergave van de resource. Als een resource met deze URI al bestaat, wordt deze vervangen. Anders wordt een nieuwe resource gemaakt, als de server dit ondersteunt. PUT-aanvragen worden meestal toegepast op resources die afzonderlijke items zijn, zoals een specifieke klant, in plaats van verzamelingen. Een server ondersteunt mogelijk updates, maar niet maken via PUT. Of u maken wilt ondersteunen via PUT hangt ervan af of de client op een nuttige manier een URI aan een resource kan toewijzen voordat deze bestaat. Als dit niet het geval is, gebruik dan POST voor het maken van resources en PUT of PATCH om bij te werken.

  • Een PATCH-aanvraag voert een gedeeltelijke update uit aan een bestaande resource. De client geeft de URI voor de resource. De aanvraagtekst bevat een reeks wijzigingen om toe te passen op de resource. Dit kan efficiënter zijn dan het gebruik van PUT, omdat de client alleen de wijzigingen verzendt en niet de volledige weergave van de resource. Technisch gesproken kan PATCH ook een nieuwe resource maken (door het opgeven van een set van updates aan een 'null'-resource), als de server dit ondersteunt.

PUT-aanvragen moeten idempotent zijn. Als een client dezelfde PUT-aanvraag meerdere keren verzendt, moeten de resultaten altijd hetzelfde zijn (dezelfde resources worden gewijzigd met dezelfde waarden). POST- en PATCH-aanvragen zijn niet gegarandeerd idempotent.

Voldoen aan de semantiek voor HTTP

Deze sectie beschrijft enkele typische overwegingen voor het ontwerpen van een API die aan de HTTP-specificaties voldoet. De sectie behandelt echter niet elk mogelijke detail of scenario. Raadpleeg bij twijfel de HTTP-specificaties.

Mediatypen

Zoals eerder gezegd, wisselen clients en servers representaties van resources uit. In een POST-aanvraag bevat de aanvraagtekst bijvoorbeeld een weergave van de resource die moet worden gemaakt. In een GET-aanvraag bevat de antwoordtekst een weergave van de opgehaalde resource.

In het HTTP-protocol worden indelingen gespecificeerd met behulp van mediatypen, ook wel MIME-typen genoemd. Voor niet-binaire gegevens ondersteunen de meeste web-API's JSON (mediatype = application/json) en mogelijk XML (mediatype = application/xml).

De Content-Type-header in een aanvraag of antwoord geeft de indeling van de weergave op. Hier volgt een voorbeeld van een POST-aanvraag met JSON-gegevens:

POST https://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57

{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}

Als de server geen ondersteuning voor het mediatype biedt, moet deze de HTTP-statuscode 415 (Mediatype niet ondersteund) retourneren.

Een aanvraag van een client kan een Accept-header bevatten met in het antwoordbericht een lijst van media die de client accepteert van de server. Voorbeeld:

GET https://adventure-works.com/orders/2 HTTP/1.1
Accept: application/json

Als de server niet overeenkomt met een van de vermelde mediatypen, moet de HTTP-statuscode 406 (niet acceptabel) worden geretourneerd.

GET-methoden

Een geslaagde GET-methode retourneert doorgaans een HTTP-statuscode 200 (OK). Als de resource niet kan worden gevonden, moet de methode 404 (Niet gevonden) retourneren.

Als aan de aanvraag is voldaan, maar er geen antwoordtekst is opgenomen in het HTTP-antwoord, moet de HTTP-statuscode 204 (geen inhoud) worden geretourneerd; Een zoekbewerking die bijvoorbeeld geen overeenkomsten oplevert, kan worden geïmplementeerd met dit gedrag.

POST-methoden

Als een POST-methode een nieuwe resource maakt, retourneert die de HTTP-statuscode 201 (Aangemaakt). De URI van de nieuwe resource is opgenomen in de Location-header van het antwoord. De antwoordtekst bevat een weergave van de opgehaalde resource.

Als de methode enige verwerking doet, maar geen nieuwe resource maakt, kan de methode HTTP-statuscode 200 retourneren en het resultaat van de bewerking opnemen in de hoofdtekst van de reactie. Als er geen resultaten zijn om te retourneren, kan de methode ook HTTP-statuscode 204 (Geen inhoud) zonder antwoordtekst retourneren.

Als de client ongeldige gegevens in de aanvraag plaatst, moet de server HTTP-statuscode 400 (Foute aanvraag) retourneren. De antwoordtekst kan aanvullende informatie bevatten over de fout, of een koppeling naar een URI die meer details biedt.

PUT-methoden

Als een PUT-methode een nieuwe resource maakt, retourneert deze de HTTP-statuscode 201 (Aangemaakt), zoals bij een POST-methode. Als de methode een bestaande resource bijwerkt, wordt het resultaat 200 (OK) of 204 (Geen inhoud). In sommige gevallen kan het niet mogelijk zijn om een bestaande resource bij te werken. Overweeg in dat geval het retourneren van HTTP-statuscode 409 (Conflict).

Overweeg de implementatie van bulksgewijze HTTP PUT-bewerkingen, die updates aan meerdere resources in een verzameling kunnen samenvoegen. De PUT-aanvraag moet de URI van de verzameling opgeven en de hoofdtekst van de aanvraag moet de details opgeven van de resources die worden gewijzigd. Deze aanpak kan helpen de intensiviteit te verminderen en de prestaties te verbeteren.

PATCH-methoden

Met een PATCH-aanvraag verzendt de client een set van updates naar een bestaande resource, in de vorm van een patch-document. De server verwerkt het patch-document om de update uit te voeren. Het patch-document beschrijft niet de hele resource, alleen een reeks wijzigingen om toe te passen. De specificatie voor de PATCH-methode (RFC 5789) bevat geen definitie van een bepaalde opmaak voor patch-documenten. De indeling moet worden afgeleid van het mediatype in de aanvraag.

JSON is waarschijnlijk de meest voorkomende gegevensindeling voor web-API's. Er zijn twee belangrijke patch-indelingen gebaseerd op JSON. Deze heten JSON-patch en JSON-samenvoegen-patch .

De JSON-samenvoegen-patch is iets eenvoudiger. Het patch-document heeft dezelfde structuur als de oorspronkelijke JSON-resource, maar bevat alleen de subset met velden die moeten worden gewijzigd of toegevoegd. Bovendien kan een veld worden verwijderd door null op te geven als waarde van het veld in het patch-document. (Dit betekent dat de samenvoegen-patch niet geschikt is als de oorspronkelijke bron expliciete null-waarden kan hebben.)

Stel bijvoorbeeld dat de oorspronkelijke bron de volgende JSON-weergave heeft:

{
    "name":"gizmo",
    "category":"widgets",
    "color":"blue",
    "price":10
}

Er is een mogelijke JSON-samenvoegen-patch voor deze bron:

{
    "price":12,
    "color":null,
    "size":"small"
}

Dit vertelt de server dat de server moet bijwerken price, verwijderen coloren toevoegen size, terwijl name en category niet worden gewijzigd. Zie voor de exacte details van de JSON-samenvoegen-patch RFC 7396. Het mediatype voor JSON-samenvoegpatch is application/merge-patch+json.

De samenvoegen-patch is niet geschikt als de oorspronkelijke bron null-waarden kan hebben, als gevolg van de speciale betekenis van null in het patch-document. Het patch-document geeft ook niet de volgorde op waarin de updates moeten worden toegepast door de server. Dat kan al dan niet van belang zijn, afhankelijk van de gegevens en het domein. JSON-patch als gedefinieerd in RFC 6902 biedt meer flexibiliteit. Het geeft de wijzigingen op als een reeks bewerkingen om toe te passen. Bewerkingen zijn onder meer toevoegen, verwijderen, vervangen, kopiëren en testen (voor het valideren van waarden). Het mediatype voor JSON-patch is application/json-patch+json.

Hier volgen enkele typische fouten die mogelijk worden aangetroffen bij het verwerken van een PATCH-aanvraag, samen met de juiste HTTP-statuscode.

Foutvoorwaarde HTTP-statuscode
De patch-documentindeling wordt niet ondersteund. 415 (Mediatype niet ondersteund)
Misvormd patch-document. 400 (Foute aanvraag)
Het patch-document is geldig, maar de wijzigingen kunnen niet worden toegepast op de resource in de huidige status. 409 (Conflict)

DELETE-methoden

Als de verwijderbewerking is geslaagd, moet de webserver reageren met HTTP-statuscode 204 (Geen inhoud), waarmee wordt aangegeven dat het proces is verwerkt, maar dat de hoofdtekst van het antwoord geen verdere informatie bevat. Als de resource niet bestaat, kan de webserver HTTP 404 (Niet gevonden) retourneren.

Asynchrone bewerkingen

Soms is voor een POST-, PUT-, PATCH- of DELETE-bewerking mogelijk verwerking vereist die enige tijd in beslag neemt. Als u wacht op voltooiing voordat u een antwoord naar de client verzendt, kan dit een onaanvaardbare latentie veroorzaken. Als dit het geval is, kunt u overwegen de bewerking asynchroon te maken. Retourneer HTTP-statuscode 202 (Aanvaard) om aan te geven dat de aanvraag is geaccepteerd voor verwerking, maar niet is voltooid.

U moet een eindpunt beschikbaar maken dat de status van een asynchrone aanvraag retourneert, zodat de client de status kan controleren door het eindpunt van de status te peilen. Neem de URI van het eindpunt van de status op in de Location-header van het 202-antwoord. Voorbeeld:

HTTP/1.1 202 Accepted
Location: /api/status/12345

Als de client een GET-aanvraag naar dit eindpunt verzendt, moet de huidige status van de aanvraag in het antwoord zijn opgenomen. Dit kan eventueel ook een geschatte tijd voor voltooiing bevatten, of een link om de bewerking te annuleren.

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"In progress",
    "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}

Als de asynchrone bewerking een nieuwe resource maakt, moet het eindpunt van de status de statuscode 303 (Zie andere) retourneren nadat de bewerking is voltooid. Neem in het 303-antwoord een Location-header op die de URI van de nieuwe resource geeft:

HTTP/1.1 303 See Other
Location: /api/orders/12345

Zie Asynchrone ondersteuning bieden voor langlopende aanvragen en het Asynchrone aanvraag-antwoordpatroon voor meer informatie over het implementeren van deze benadering.

Lege sets in berichtteksten

Telkens wanneer de hoofdtekst van een geslaagd antwoord leeg is, moet de statuscode 204 (Geen inhoud) zijn. Voor lege sets, zoals een reactie op een gefilterde aanvraag zonder items, moet de statuscode nog steeds 204 (Geen inhoud), niet 200 (OK) zijn.

Gegevens filteren en pagineren

Een verzameling resources via een enkele URI beschikbaar maken, kan leiden tot toepassingen die grote hoeveelheden gegevens ophalen, terwijl slechts een subset van de informatie vereist is. Stel bijvoorbeeld dat een clienttoepassing moet zoeken naar alle bestellingen met kosten boven een bepaalde waarde. De toepassing kan alle orders uit de /orders-URI ophalen en vervolgens deze orders aan de clientzijde filteren. Dit proces is duidelijk zeer inefficiënt. Het verspilt netwerkbandbreedte en verwerkingskracht op de server die als host fungeert voor de web-API.

In plaats daarvan kan de API een filter doorgeven in de queryreeks van de URI, zoals /orders?minCost=n. De web-API is vervolgens verantwoordelijk voor het parseren en verwerken van de minCost parameter in de querytekenreeks en het retourneren van de gefilterde resultaten aan de serverzijde.

GET-aanvragen via de verzameling resources kunnen mogelijk een groot aantal items retourneren. U moet een web-API ontwerpen om de hoeveelheid gegevens te beperken die zijn geretourneerd door elke afzonderlijke aanvraag. Overweeg ondersteunende queryreeksen die het maximum aantal items opgeven om op te halen, en een begin-offset in de verzameling. Voorbeeld:

/orders?limit=25&offset=50

Overweeg ook een bovengrens op te leggen voor het aantal geretourneerde items, om Denial of Service-aanvallen te voorkomen. Om te helpen met clienttoepassingen, moeten GET-aanvragen die gepagineerde gegevens retourneren ook een vorm van metagegevens bevatten die wijzen op het totale aantal resources dat beschikbaar is in de verzameling.

U kunt een vergelijkbare strategie gebruiken om gegevens te sorteren zodra deze worden opgehaald, door een sorteerparameter op te geven die een veldnaam als waarde gebruikt, zoals /orders?sort=ProductID. Deze aanpak kan wel een negatief effect hebben op opslaan in cache, omdat queryreeksparameters deel uitmaken van de resource-id die door veel implementaties van de cache gebruikt worden als de sleutel tot gegevens in de cache.

U kunt deze benadering uitbreiden om de geretourneerde velden voor elk item te beperken, als elk item een grote hoeveelheid gegevens bevat. U kunt bijvoorbeeld een queryreeksparameter gebruiken die een door komma's gescheiden lijst met velden accepteert, zoals /orders?fields=ProductID,Quantity.

Geef alle optionele parameters in queryreeksen zinvolle standaardwaarden. Stel bijvoorbeeld de limit-parameter in op 10 en de offset-parameter op 0 bij het implementeren van paginering. Stel de sorteerparameter in op de sleutel van de resource als u ordening implementeert en de fields-parameter op alle velden in de resource als u projecties ondersteunt.

Gedeeltelijke antwoorden voor grote binaire resources ondersteunen

Een resource kan grote binaire velden bevatten, zoals bestanden of installatiekopieën. Overweeg het mogelijk te maken dat deze resources in segmenten kunnen worden opgehaald, om problemen op te lossen die worden veroorzaakt door onbetrouwbare en onregelmatige verbindingen, en om reactietijden te verbeteren. Om dit te doen moet de web-API de Accept-Ranges-header ondersteunen voor GET-aanvragen voor grote resources. Deze header geeft aan dat de GET-bewerking gedeeltelijke aanvragen ondersteunt. De clienttoepassing kan GET-aanvragen verzenden die resulteren in een subset van een resource die is opgegeven als een bereik van bytes.

Overweeg bovendien het implementeren van HTTP HEAD-aanvragen voor deze resources. Een HEAD-aanvraag is vergelijkbaar met een GET-aanvraag, behalve dat deze alleen de HTTP-headers retourneert die de resource beschrijven, met een lege berichttekst. Een clienttoepassing kan een HEAD-verzoek uitgeven om te bepalen of een bron moet worden opgehaald met behulp van gedeeltelijke GET-aanvragen. Voorbeeld:

HEAD https://adventure-works.com/products/10?fields=productImage HTTP/1.1

Hier volgt een voorbeeld-antwoordbericht:

HTTP/1.1 200 OK

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580

De Content-Length-header geeft de totale grootte van de bron en de Accept-Ranges-header geeft aan dat de bijbehorende GET-bewerking ondersteuning biedt voor gedeeltelijke resultaten. De clienttoepassing kan deze informatie gebruiken om de afbeelding in kleinere chunks op te halen. De eerste aanvraag haalt de eerste 2500 bytes op met behulp van de Range-header:

GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499

Het antwoordbericht geeft aan dat dit een gedeeltelijk antwoord is door HTTP-statuscode 206 te retourneren. De Content-Length-header geeft het werkelijke aantal bytes dat wordt geretourneerd op in de berichthoofdtekst (niet de grootte van de resource) en de Content-Range-header geeft aan welk deel van de resource dit is (0-2499 bytes van 4580):

HTTP/1.1 206 Partial Content

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580

[...]

Een volgende aanvraag van de clienttoepassing kan de rest van de resource ophalen.

Een van de primaire motivaties achter REST is dat het mogelijk moet zijn de volledige set van resources te navigeren zonder kennis vooraf van het URI-schema. Elke HTTP GET-aanvraag als resultaat moet de informatie retourneren die nodig is om naar de resources te zoeken, die rechtstreeks verband houden met het gevraagde object via hyperlinks opgenomen in het antwoord, en moet ook worden voorzien van informatie over de bewerkingen die beschikbaar zijn op elk van deze resources. Dit principe staat bekend als HATEOAS of Hypertext as the Engine of Application State. Het systeem is in feite een eindige statusmachine en het antwoord op elke aanvraag bevat de informatie die nodig is om van een status naar een andere te bewegen. Er zou geen andere informatie nodig moeten zijn.

Notitie

Er zijn momenteel geen algemene standaarden die bepalen hoe het HATEOAS-principe moet worden gemodelleerd. In de voorbeelden in deze sectie ziet u één mogelijke, bedrijfseigen oplossing.

Om bijvoorbeeld de relatie af te handelen tussen een order en een klant, bevat de weergave van een order links die de beschikbare bewerkingen voor de klant van de order identificeren. Hier is een mogelijke weergave:

{
  "orderID":3,
  "productID":2,
  "quantity":4,
  "orderValue":16.60,
  "links":[
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"DELETE",
      "types":[]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"DELETE",
      "types":[]
    }]
}

In dit voorbeeld heeft de matrix links een set van links. Elke link vertegenwoordigt een bewerking op een gerelateerde entiteit. De gegevens voor elke koppeling bevatten de relatie ('klant'), de URI (https://adventure-works.com/customers/3), de HTTP-methode en de ondersteunde MIME-typen. Dit is alle informatie die een clienttoepassing nodig heeft om de bewerking aan te kunnen roepen.

De matrix links bevat ook naar zichzelf verwijzende informatie over de resource zelf die is opgehaald. Deze hebben de relatie self.

De set van links die worden geretourneerd kan worden gewijzigd, afhankelijk van de status van de resource. Dit is wat wordt bedoeld met hypertext als de 'engine van de toepassingsstatus'.

Versiebeheer van een RESTful-web-API

Het is zeer onwaarschijnlijk dat een web-API ongewijzigd blijft. Als bedrijfsvereisten veranderen, kunnen nieuwe verzamelingen van resources worden toegevoegd, kunnen de relaties tussen resources veranderen en kan de structuur van de gegevens in de resources worden gewijzigd. Hoewel het bijwerken van een web-API voor het afhandelen van nieuwe of verschillende behoeften een relatief eenvoudig proces is, moet u rekening houden met de effecten die dergelijke wijzigingen hebben op clienttoepassingen die de web-API gebruiken. Het probleem is dat hoewel de ontwikkelaar die een web-API ontwerpt en implementeert, volledige controle over die API heeft, de ontwikkelaar niet dezelfde mate van controle heeft over clienttoepassingen, die kunnen worden gebouwd door externe organisaties die extern werken. De primaire noodzaak is het zorgen dat bestaande clienttoepassingen ongewijzigd blijven werken, terwijl nieuwe clienttoepassingen kunnen profiteren van nieuwe functies en resources.

Versiebeheer zorgt dat een web-API aan kan geven welke functies en resources beschikbaar worden gemaakt en een clienttoepassing kan aanvragen verzenden die worden doorgestuurd naar een specifieke versie van een functie of resource. De volgende secties beschrijven enkele verschillende benaderingen, die allemaal hun eigen voor- en nadelen hebben.

Geen versiebeheer

Dit is de eenvoudigste manier en mogelijk acceptabel voor een aantal interne API's. Belangrijke wijzigingen kunnen worden weergegeven als nieuwe resources of nieuwe koppelingen. Het toevoegen van inhoud aan bestaande resources kan mogelijk geen belangrijke wijziging opleveren, omdat clienttoepassingen die deze inhoud niet verwachten te zien deze inhoud negeert.

Een aanvraag naar de URI https://adventure-works.com/customers/3 moet bijvoorbeeld de details retourneren van één klant met id, nameen address velden die worden verwacht door de clienttoepassing:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

Notitie

Voor het gemak zijn de voorbeeldantwoorden die worden weergegeven in deze sectie geen HATEOAS-links.

Als het veld DateCreated is toegevoegd aan het schema van de klant-resource, ziet het antwoord er als volgt uit:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}

Bestaande clienttoepassingen blijven mogelijk goed werken als ze in staat zijn om niet-herkende velden te negeren, terwijl nieuwe clienttoepassingen kunnen worden ontworpen voor het afhandelen van dit nieuwe veld. Als echter steeds ingrijpender wijzigingen in het schema van de resources optreden (zoals verwijderen of wijzigen van veldnamen) of de relaties tussen resources wijzigen, kan dit inhouden dat dit grote veranderingen zijn die verhinderen dat bestaande clienttoepassingen correct werken. In deze situaties moet u een van de volgende benaderingen overwegen.

URI-versiebeheer

Telkens wanneer u de web-API wijzigt of het schema van resources aanpast, voegt u een uniek versienummer toe aan de URI voor elke resource. De bestaande URI's moeten blijven functioneren als voorheen en resources retourneren die voldoen aan hun oorspronkelijke schema.

Als het vorige voorbeeld wordt uitgebreidstreetAddressstatecityzipCode, kan deze versie van de address resource worden weergegeven via een URI met een versienummer, zoals:https://adventure-works.com/v2/customers/3

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Dit mechanisme voor versiebeheer is zeer eenvoudig, maar is afhankelijk van de server die de aanvraag naar het juiste eindpunt doorstuurt. Het kan echter onhandig worden als de web-API via verschillende iteraties groeit en de server een aantal verschillende versies moet ondersteunen. Vanuit het oogpunt van een purist worden in alle gevallen de clienttoepassingen dezelfde gegevens opgehaald (klant 3), dus de URI mag niet echt verschillen, afhankelijk van de versie. Implementatie van HATEOAS wordt door dit schema ook ingewikkelder, omdat alle koppelingen het versienummer moeten opnemen in hun URI's.

Versiebeheer voor querytekenreeks

In plaats van meerdere URI's op te geven, kunt u de versie van de resource opgeven met behulp van een parameter in de queryreeks die is toegevoegd aan de HTTP-aanvraag, zoals https://adventure-works.com/customers/3?version=2. De versieparameter moet standaard terugvallen op een zinvolle waarde zoals 1, als deze door oudere clienttoepassingen wordt weggelaten.

Hiervan is het semantische voordeel dat dezelfde resource altijd wordt opgehaald uit dezelfde URI, maar dit is afhankelijk van de code die verantwoordelijk is voor de aanvraag voor het parseren van de queryreeks en het terugsturen van het juiste HTTP-antwoord. Deze methode heeft ook dezelfde problemen voor het implementeren van HATEOAS als het mechanisme voor URI-versiebeheer.

Notitie

Sommige oudere webbrowsers en webproxy's slaan antwoorden voor aanvragen met een queryreeks in de URI niet op in de cache. Dit kan de prestaties verminderen voor webtoepassingen die gebruikmaken van een web-API en die worden uitgevoerd vanuit een dergelijke webbrowser.

Header-versiebeheer

In plaats van het versienummer als een queryreeksparameter toe te voegen, kunt u een aangepaste header implementeren die de versie van de resource aangeeft. Deze aanpak vereist dat de clienttoepassing de juiste header toevoegt aan alle aanvragen, hoewel de code voor het verwerken van de clientaanvraag een standaardwaarde (versie 1) kan gebruiken als de versie-header wordt weggelaten. In de volgende voorbeelden wordt een aangepaste header met de naam Custom-Header gebruikt. De waarde van deze header geeft de versie van de web-API aan.

Versie 1:

GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

Versie 2:

GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Net als bij de vorige twee benaderingen vereist het implementeren van HATEOAS het opnemen van de juiste aangepaste header in koppelingen.

Mediatype-versiebeheer

Wanneer een clienttoepassing een HTTP GET-aanvraag naar een webserver verzendt, moet deze de indeling van de inhoud bepalen die ze kan verwerken met behulp van een Accept-header, zoals eerder in deze richtlijnen beschreven. Vaak is het doel van de Accept-header ervoor zorgen dat de clienttoepassing op kan geven of de hoofdtekst van het antwoord XML, JSON of een andere algemene indeling moet zijn, die door de client kan worden geparseerd. Het is echter mogelijk aangepaste mediatypen te definiëren met informatie voor het inschakelen van de clienttoepassing, om aan te geven welke versie van een resource wordt verwacht.

In het volgende voorbeeld ziet u een aanvraag die een Accept-header opgeeft met de waarde application/vnd.adventure-works.v1+json. Het vnd.adventure-works.v1-element geeft bij de webserver aan dat versie 1 van de resource moet worden geretourneerd, terwijl het json-element aangeeft dat de indeling van de antwoordtekst JSON moet zijn:

GET https://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json

De code voor het verwerken van de aanvraag is verantwoordelijk voor de verwerking van de Accept-header en het zo veel mogelijk naleven ervan (de clienttoepassing kan verschillende indelingen opgeven in de Accept-header. In dat geval kan de webserver de meest geschikte indeling voor de antwoordtekst kiezen). De webserver bevestigt de indeling van de gegevens in de antwoordtekst met behulp van de Content-Type-header:

HTTP/1.1 200 OK
Content-Type: application/vnd.adventure-works.v1+json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

Als de Accept-header geen bekende mediatypen opgeeft, kan de webserver een antwoordbericht HTTP 406 (Niet aanvaardbaar) genereren, of een bericht retourneren met een standaard-mediatype.

Deze aanpak is weliswaar de zuiverste van de mechanismen voor versiebeheer en is van nature geschikt voor HATEOAS, dat het MIME-type van gerelateerde gegevens in links naar resources kan opnemen.

Notitie

Wanneer u een strategie voor versiebeheer selecteert, moet u ook de gevolgen voor de prestaties overwegen, met name opslaan in cache op de webserver. De schema's voor URI-versiebeheer en de Queryreeks-versiebeheer zijn cache-vriendelijk, aangezien dezelfde combinatie van de URI-querytekenreeks telkens naar dezelfde gegevens verwijst.

De mechanismen voor Header-versiebeheer en Mediatype-versiebeheer vereisen normaal extra logica voor het onderzoeken van de waarden in de aangepaste header of de Accept-header. In een grootschalige omgeving kan het gebruik van verschillende versies van een web-API door veel clients leiden tot een aanzienlijke hoeveelheid gedupliceerde gegevens in een cache aan serverzijde. Dit probleem kan acuut worden als een clienttoepassing met een webserver communiceert via een proxy die opslaan in cache implementeert en die alleen een aanvraag naar de webserver verzendt als deze niet op dit moment een kopie van de aangevraagde gegevens in de cache bevat.

Open API Initiative

Het Open API Initiative is gemaakt door een brancheconsortium om REST-API-beschrijvingen bij leveranciers te standaardiseren. Als onderdeel van dit initiatief kreeg de Swagger 2.0-specificatie de nieuwe naam OpenAPI Specification (OAS) en werd deze onder het Open API Initiative gebracht.

Mogelijk wilt u OpenAPI gebruiken voor uw web-API's. Enkele punten om in overweging te nemen:

  • De OpenAPI Specification wordt geleverd met een set van bevooroordeelde richtlijnen over hoe een REST-API moet worden ontworpen. Dit heeft voordelen voor interoperabiliteit, maar vereist meer zorg bij het ontwerpen van uw API om te voldoen aan de specificatie.

  • OpenAPI draagt bij aan een contract-eerst-benadering, in plaats van een implementatie-eerst-benadering. Contract-eerst betekent dat u het API-contract (de interface) eerst ontwerpt en vervolgens de code schrijft die het contract implementeert.

  • Hulpprogramma's zoals Swagger kunnen clientbibliotheken of documentatie van de API-contracten genereren. Zie bijvoorbeeld ASP.NET Help-pagina's voor web-API's met behulp van Swagger.

Volgende stappen

  • Richtlijnen voor Microsoft Azure REST API. Gedetailleerde aanbevelingen voor het ontwerpen van REST API's in Azure.

  • Controlelijst voor web-API. Een handige lijst met items waarmee u rekening moet houden bij het ontwerpen en implementeren van een web-API.

  • Open API Initiative. Documentatie en implementatie-informatie over Open API.