Descoberta da API REST e HATEOAS

Descoberta da API REST e HATEOAS

1. Visão geral

Este artigo se concentrará emDiscoverability of the REST API, HATEOASe cenários práticos conduzidos por testes.

2. Por que tornar a API detectável

A descoberta de uma API é um tópico que não recebe a atenção merecida o suficiente. Como conseqüência, poucas APIs acertam. É também algo que, se feito corretamente, pode tornar a API não apenas RESTful e utilizável, mas também elegante.

Para entender a capacidade de descoberta, precisamos entender a restrição Hipermídia como o mecanismo do estado do aplicativo (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.

Se a interação deve ser conduzida pela API através da própria conversa, concretamente via Hypertext, não pode haver documentação. Isso forçaria o cliente a fazer suposições que estão de fato fora do contexto da API.

Em conclusão,the server should be descriptive enough to instruct the client how to use the API apenas via hipertexto. No caso de uma conversa HTTP, podemos fazer isso por meio do cabeçalhoLink.

3. Cenários de descoberta (orientados por testes)

Então, o que significa que um serviço REST pode ser descoberto?

Ao longo desta seção, testaremos características individuais de descoberta usando Junit,rest-assuredeHamcrest. Desdethe REST Service has been previously secured, cada teste precisa primeiroauthenticate antes de consumir a API.

3.1. Descubra os métodos HTTP válidos

Quando um serviço REST é consumido com um método HTTP inválido, a resposta deve ser 405 MÉTODO NÃO PERMITIDO.

A API também deve ajudar o cliente a descobrir os métodos HTTP válidos permitidos para esse Recurso específico. Para isso,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. Descubra o URI do recurso recém-criado

The operation of creating a new Resource should always include the URI of the newly created resource in the response. Para isso, podemos usar o cabeçalho HTTPLocation.

Agora, se o cliente fizer um GET nesse URI, o recurso deverá estar disponível:

@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));
}

O teste segue um cenário simples:creating a new Foo resource, then using the HTTP response to discover the URI where the Resource is now available. Ele também faz um GET nesse URI para recuperar o recurso e o compara ao original. Isso é para garantir que ele foi salvo corretamente.

3.3. Descubra o URI para obter todos os recursos desse tipo

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. Assim, a operação de recuperação de um recurso deve sempre incluir em sua resposta o URI de onde obter todos os recursos desse tipo.

Para isso, podemos usar novamente o cabeçalhoLink:

@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));
}

Observe que o código de baixo nível completo paraextractURIByRel - responsável por extrair os URIs pela relaçãorelis shown here.

Este teste cobre o assunto espinhoso de Relações de Link em REST: o URI para recuperar todos os recursos usa a semânticarel=”collection”.

Esse tipo de relação de link ainda não foi padronizado, mas já éin use por vários microformatos e proposto para padronização. O uso de relações de link não padrão abre a discussão sobre microformatos e semântica mais rica em serviços da web RESTful.

4. Outros URIs e microformatos potenciais detectáveis

Other URIs could potentially be discovered via the Link header, mas há apenas o limite que os tipos existentes de relações de ligação permitem sem passar para uma marcação semântica mais rica, comodefining custom link relations,Atom Publishing Protocol oumicroformats, que será o tópico de outro artigo.

Por exemplo, o cliente deve ser capaz de descobrir o URI para criar novos Recursos ao fazerGET em um Recurso específico. Infelizmente, não há relação de link para a semântica do modelocreate.

Felizmente, é uma prática padrão que o URI para a criação seja o mesmo que o URI para OBTER todos os recursos desse tipo, com a única diferença sendo o método POST HTTP.

5. Conclusão

Vimoshow a REST API is fully discoverable from the root and with no prior knowledge - o que significa que o cliente é capaz de navegar fazendo um GET na raiz. Seguindo em frente, todas as mudanças de estado são conduzidas pelo cliente usando as transições disponíveis e detectáveis ​​que a API REST fornece nas representações (portanto,Representational State Transfer).

Este artigo abordou algumas das características de descoberta no contexto de um serviço da Web REST, discutindo a descoberta de método HTTP, a relação entre criar e obter, descoberta do URI para obter todos os recursos etc.

A implementação de todos esses exemplos e trechos de código está disponívelover on GitHub. Este é um projeto baseado em Maven, portanto, deve ser fácil importar e executar como está.