Hypermedia & REST

Het REST Maturity Model definieert vier niveaus waarin een REST API zich kan bevinden. Het laatste niveau onderscheidt zich door het toevoegen van Hypermedia. Dit artikel omschrijft het concept Hypermedia en laat door middel van een voorbeeld zien dat het eenvoudig toe te voegen is aan onze API’s.

In 2000 publiceerde Roy Fielding zijn proefschrift[1] waarin hij voor het eerst REST als architectuurstijl beschreef. In zijn proefschrift benoemde hij de volgende eigenschappen: Client-server, Stateless, Caching, Uniform interface, Layered system. Vervolgens deed Fielding in 2008 de volgende uitspraak in zijn blog[2]: “What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint?”. Enkele weken later presenteerde Leonard Richardson voor het eerst het ‘REST Maturity Model’. Het model kent 4 niveaus:

 

 

Bron: http://martinfowler.com/articles/images/richardsonMaturityModel/overview.png

 

  • Level 0: The swamp of POX
    Één URI, één HTTP-methode, HTTP wordt enkel gebruikt als transportmechanisme.
  • Level 1: Resources
    Meerdere URI’s, één HTTP-methode, elke actie heeft een eigen URI.
  • Level 2: HTTP Verbs
    Meerdere URI’s, meerdere HTTP-methodes, samenhangende acties op dezelfde URI met verschillende methodes bepaald volgens de HTTP-specificatie.
  • Level 3: Hypermedia controls
    Meerdere URI’s, meerdere HTTP-methodes, Hypermedia As The Engine Of Application State.

 

De geestelijk vader van REST stelde dat een volwassen REST API gestuurd wordt door Hypermedia. Maar waarom en wat voegt dat toe? Daar komen we in dit artikel achter.

 

Hypermedia

Wikipedia omschrijft hypertekst als volgt: “Hypertekst is tekst met direct activeerbare hyperlinks (verwijzingen). De lezer hoeft voor het volgen van een verwijzing geen bladzijden, hoofdstukken of andere teksten op te zoeken en open te slaan, maar kan direct naar een specifieke tekst of een specifieke plek in de tekst doorspringen.”[3]

 

We kennen dit concept natuurlijk allemaal in de vorm van HTML. Dit is een representatie waar zowel de informatie en alle vervolgacties, door middel van hyperlinks, in één overzicht beschikbaar zijn. Met de tekst in de hyperlink geven we de relatie van de actie, of eigenlijk de resource achter de relatie, aan. Als we bij Google op ‘zoeken’ klikken, weten we niet naar welke URI we navigeren. We weten wel dat we hierna zoekresultaten te zien krijgen.

 

Waarom doen we niet hetzelfde als we een API ontwikkelen? Het lijken twee verschillende werelden, een persoon die navigeert door een webpagina en een applicatie die navigeert door een REST API. Immers, een applicatie beschikt niet over de intelligentie om de relaties in een resource te identificeren en te gebruiken. Maar elke applicatie heeft een ontwikkelaar of meer waarschijnlijk een heel team aan ontwikkelaars. Laten we ons daarom gaan richten tot hen als we een API ontwerpen; één URI zou voldoende moeten zijn om de API volledig te gebruiken.

 

Hoe geven we acties dan weer in een representatie van onze resource? Ook op het gebied van Hypermedia, of eigenlijk ‘Hypermedia As The Engine Of Application State’ (HATEOAS), is al een aantal standaarden ontwikkeld. HAL[4], JSON:API[5], Collection+JSON[6] etc. zijn allemaal voorbeelden van gestandaardiseerde Hypermedia formaten. Ter illustratie gebruiken we HAL en de resource ‘Rekening’. Deze heeft een aantal attributen zoals een rekeningnummer, een rekeninghouder en een saldo. Voor nu onderkennen we alleen even de acties storten en opnemen. De URI is https://nljug.org/accounts/{{accountnummer}} en het content-type application/json+hal.

 

Implementatie

Een GET request op deze URI retourneert een representatie van de rekening met de attributen zoals eerder beschreven. Maar dit is enkel de data. Hoe geven we hier de acties bij weer? HAL gebruikt daarvoor het attribuut “_links”. Elke link heeft een optionele naam, een URI en een relatie. De relatie omschrijft de actie die hiermee uitgevoerd kan worden. Er is een IETF[7]- standaard voorgesteld die een aantal standaardrelaties omschrijft[8]. In ons geval is er geen standaardrelatie te benoemen anders dan de self-relatie. Deze is altijd aanwezig in een HAL representatie en verwijst naar de URI van de huidige representatie. Daarnaast hebben we nog relaties nodig voor het storten en opnemen van geld, respectievelijk deposit en withdraw.

 


{
  "accountNumber": "1234",
  "accountHolder": "Willem van Oranje",
  "balance": 10125.0,
  "_links": [
    {
      "rel": "self",
      "href": "https://nljug.org/hypermedia/account/1234"
    },
    {
      "rel": "deposit",
      "href": "https://nljug.org/hypermedia/account/deposit/1234"
    },
    {
      "rel": "withdraw",
      "href": "https://nljug.org/hypermedia/account/withdraw/1234"
    }
  ]
}

 

Een afnemer kan nu aan de hand van de resource URI alle acties uitvoeren die we onderkend hebben. In het response staan de relaties voor alle acties die uitgevoerd kunnen worden.

 


@Path("account")
public class AccountResource {
 
    private final Map<String, Account> resources ...
    @Inject
    private RepresentationFactory representationFactory;
 
    @GET
    @Path("/{accountNumber}")
    @Produces(RepresentationFactory.HAL_JSON)
    public Representation get(@PathParam("accountNumber") String accountNumber) {
        final Account account = resources.get(accountNumber);
       
        return representationFactory
.newRepresentation(getSelf(accountNumber))
.withLink("deposit", "/account/deposit/" + accountNumber)
.withLink("withdraw", "/account/withdraw/" + accountNumber)
.withBean(account);
    }
}

 

HAL definieert bij een link niet welke HTTP-methode gebruikt moet worden. Met het oog op de eigenschap ‘Uniform Interface’ is dit ook geen probleem. We streven er immers naar om het hoogste niveau van het REST Maturity Model te bereiken en daarom gebruiken we netjes de HTTP-methodes, zoals ze bedoeld zijn. Eventueel kan de ontwikkelaar die onze API wil gebruiken het HTTP OPTIONS-commando gebruiken om te controleren welke methoden ondersteund worden bij een bepaalde URI.

 

Om een storting te doen, kan de afnemer de link aanroepen met de relatie deposit. Volgens de HTTP-standaarden moet een POST[9] gebruikt worden om de resource bij te werken. En het gewenste bedrag moet meegestuurd worden in het request. Dan zul je wellicht denken: “Hoe weet de ontwikkelaar dat het gewenste bedrag verwacht wordt of wat voor formaat de data moet hebben?”

 

Hier heeft HAL curies voor. Dit zijn voorgedefinieerde documentatie-resources waarbij een URI template wordt gedefinieerd met een rel placeholder. Door deze placeholder te vervangen door de daadwerkelijke relatie, kan de documentatie hiervan opgevraagd worden. HAL gebruikt dus niet altijd een rel-attribuut; een attribuut op het _links-object mag ook gebruikt worden om de relatie aan te geven.

 


"_links": {
"curies": [
{
"name": "doc",
"href": "https://nljug.org/hypermedia/account/docs/{rel}",
"templated": true
}
],

"doc:deposit": {
"href": "https://nljug.org/hypermedia/deposit/1234"
}
}

De definitie van _links en curies is maar één onderdeel van HAL. De implementatie kent nog meer aspecten om het voor een afnemer mogelijk te maken de REST API te verkennen. Dit gaat echter te ver voor dit artikel. We zijn al een grote stap dichterbij een volwassen REST API als we links aan elke representatie toevoegen. Zie de HAL-specificatie[10] voor meer informatie over de overige aspecten.

 

Conclusie

Mobiele apparaten en betere browsers hebben een verschuiving op het web geïntroduceerd. We bouwen steeds vaker op JavaScript gebaseerde applicaties die een groot deel van de functionaliteit in de browser uitvoeren. Samen met de opmars van native Android/iOS apps is de noodzaak voor API’s gecreëerd.

 

De REST-architectuur is hier uitermate geschikt voor, maar deze API’s ontwikkelen we vaak vanuit ons eigen perspectief met de domeinkennis waarover we beschikken. Hierbij creëren we een sterke afhankelijkheid tussen de API en alle afnemers die onderkend zijn. Een deel van deze API’s is publiekelijk beschikbaar via het web, maar we maken het derden moeilijk om gebruik te maken van de functionaliteit die we bieden.

 

Als we onze API’s, door middel van Hypermedia, ontkoppelen van de gebruikersinterface creëren we een ingang naar onze bedrijfsprocessen die niet enkel beschikbaar is voor een bepaalde afnemer. We kunnen nu meerdere zelfstandige applicaties ontwikkelen die volledig ontkoppeld zijn. Zolang de API vasthoudt aan de gedefinieerde links, kan de implementatie gewijzigd worden zonder dat een afnemer hier hinder van zal ondervinden.

 

Github: https://github.com/quachc/nljug-hypermedia

 

[1]                http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm

[2]                http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

[3]                http://nl.wikipedia.org/wiki/Hypertekst

[4]                http://stateless.co/hal_specification.html

[5]                http://jsonapi.org/

[6]                http://www.amundsen.com/media-types/collection/

[7]                http://tools.ietf.org/html/rfc5988

[8]                https://www.iana.org/assignments/link-relations/link-relations.xhtml

[9]                http://roy.gbiv.com/untangled/2009/it-is-okay-to-use-post

[10]              http://stateless.co/hal_specification.html