Découverte de l’API REST et HATEOAS

Découvrabilité de l'API REST et HATEOAS

1. Vue d'ensemble

Cet article se concentrera sur lesDiscoverability of the REST API, HATEOAS et les scénarios pratiques pilotés par des tests.

2. Pourquoi rendre l'API détectable

La découverte d'une API est un sujet qui ne reçoit pas suffisamment d'attention bien méritée. En conséquence, très peu d'API réussissent. C'est aussi quelque chose qui, s'il est fait correctement, peut rendre l'API non seulement RESTful et utilisable, mais aussi élégante.

Pour comprendre la découvrabilité, nous devons comprendre la contrainte Hypermedia en tant que moteur d'état d'application (HATEOAS). This constraint of a REST API is about full discoverability of actions/transitions on a Resource from Hypermedia (Hypertext really), as the only driver of application state.

Si l'interaction doit être pilotée par l'API via la conversation elle-même, concrètement via Hypertext, il ne peut y avoir de documentation. Cela contraindrait le client à émettre des hypothèses qui seraient en dehors du contexte de l'API.

En conclusion,the server should be descriptive enough to instruct the client how to use the API via Hypertext uniquement. Dans le cas d'une conversation HTTP, nous pourrions y parvenir via l'en-têteLink.

3. Scénarios de découvrabilité (pilotés par des tests)

Alors, qu'est-ce que cela signifie pour un service REST d'être découvrable?

Tout au long de cette section, nous testerons des caractéristiques individuelles de découvrabilité à l'aide de Junit,rest-assured etHamcrest. Depuisthe REST Service has been previously secured, chaque test doit d'abord avoirauthenticate avant de consommer l'API.

3.1. Découvrez les méthodes HTTP valides

Lorsqu'un service REST est utilisé avec une méthode HTTP non valide, la réponse doit être une méthode 405 NON AUTORISÉE.

L'API doit également aider le client à découvrir les méthodes HTTP valides autorisées pour cette ressource particulière. Pour cela,we can use the Allow HTTP Header in the response:

@Test
public void
  whenInvalidPOSTIsSentToValidURIOfResource_thenAllowHeaderListsTheAllowedActions(){
    // Given
    String uriOfExistingResource = restTemplate.createResource();

    // When
    Response res = givenAuth().post(uriOfExistingResource);

    // Then
    String allowHeader = res.getHeader(HttpHeaders.ALLOW);
    assertThat( allowHeader, AnyOf.anyOf(
      containsString("GET"), containsString("PUT"), containsString("DELETE") ) );
}

3.2. Découvrez l'URI de la ressource nouvellement créée

The operation of creating a new Resource should always include the URI of the newly created resource in the response. Pour cela, nous pouvons utiliser l'en-tête HTTPLocation.

Maintenant, si le client effectue un GET sur cet URI, la ressource doit être disponible:

@Test
public void whenResourceIsCreated_thenUriOfTheNewlyCreatedResourceIsDiscoverable() {
    // When
    Foo newResource = new Foo(randomAlphabetic(6));
    Response createResp = givenAuth().contentType("application/json")
      .body(unpersistedResource).post(getFooURL());
    String uriOfNewResource= createResp.getHeader(HttpHeaders.LOCATION);

    // Then
    Response response = givenAuth().header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
      .get(uriOfNewResource);

    Foo resourceFromServer = response.body().as(Foo.class);
    assertThat(newResource, equalTo(resourceFromServer));
}

Le test suit un scénario simple:creating a new Foo resource, then using the HTTP response to discover the URI where the Resource is now available. Il effectue également un GET sur cet URI pour récupérer la ressource et la comparer à l'original. Cela permet de s’assurer qu’il a été correctement enregistré.

3.3. Découvrez l'URI pour OBTENIR toutes les ressources de ce type

When we GET any particular Foo resource, we should be able to discover what we can do next: we can list all the available Foo resources. Ainsi, l'opération de récupération d'une ressource doit toujours inclure dans sa réponse l'URI où obtenir toutes les ressources de ce type.

Pour cela, nous pouvons à nouveau utiliser l'en-têteLink:

@Test
public void whenResourceIsRetrieved_thenUriToGetAllResourcesIsDiscoverable() {
    // Given
    String uriOfExistingResource = createAsUri();

    // When
    Response getResponse = givenAuth().get(uriOfExistingResource);

    // Then
    String uriToAllResources = HTTPLinkHeaderUtil
      .extractURIByRel(getResponse.getHeader("Link"), "collection");

    Response getAllResponse = givenAuth().get(uriToAllResources);
    assertThat(getAllResponse.getStatusCode(), is(200));
}

Notez que le code de bas niveau complet pourextractURIByRel - responsable de l'extraction des URI parrel relationis shown here.

Ce test couvre le sujet épineux des relations de lien dans REST: l'URI pour récupérer toutes les ressources utilise la sémantique derel=”collection”.

Ce type de relation de lien n'a pas encore été standardisé, mais est déjàin use par plusieurs microformats et proposé à la standardisation. L'utilisation de relations de liens non standard ouvre la discussion sur les microformats et une sémantique plus riche dans les services Web RESTful.

4. Autres URI et microformats potentiellement découvrables

Other URIs could potentially be discovered via the Link header, mais les types de relations de lien existants ne le permettent pas plus sans passer à un balisage sémantique plus riche tel quedefining custom link relations, lesAtom Publishing Protocol oumicroformats, qui seront le sujet d'un autre article.

Par exemple, le client doit être capable de découvrir l'URI pour créer de nouvelles ressources lors de l'exécution d'unGET sur une ressource spécifique. Malheureusement, il n'y a pas de relation de lien avec la sémantique du modèlecreate.

Heureusement, il est courant que l’URI de création soit le même que l’URI pour GET toutes les ressources de ce type, la seule différence étant la méthode HTTP POST.

5. Conclusion

Nous avons vuhow a REST API is fully discoverable from the root and with no prior knowledge - ce qui signifie que le client est capable de le parcourir en effectuant un GET à la racine. À l'avenir, tous les changements d'état sont pilotés par le client à l'aide des transitions disponibles et détectables que l'API REST fournit dans les représentations (d'oùRepresentational State Transfer).

Cet article couvrait quelques-uns des traits de la découvrabilité dans le contexte d'un service Web REST, abordant la découverte de méthode HTTP, la relation entre créer et obtenir, la découverte de l'URI pour obtenir toutes les ressources, etc.

L'implémentation de tous ces exemples et extraits de code est disponibleover on GitHub. Ceci est un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.