Uma Introdução à Primavera HATEOAS

Uma Introdução à Primavera HATEOAS

1. Visão geral

Este artigo explica o processo de criação de serviço da Web REST controlado por hipermídia usando o projeto Spring HATEOAS.

2. Spring-HATEOAS

O projeto Spring HATEOAS é uma biblioteca de APIs que podemos usar para criar facilmente representações REST que seguem o princípio do HATEOAS (hipertexto como o mecanismo do estado do aplicativo).

De modo geral, o princípio implica que a API deve guiar o cliente através do aplicativo, retornando informações relevantes sobre as próximas etapas possíveis, junto com cada resposta.

Neste artigo, vamos construir um exemplo usando Spring HATEOAS com o objetivo de desacoplar o cliente e o servidor e, teoricamente, permitir que a API mude seu esquema de URI sem quebrar os clientes.

3. Preparação

Primeiro, vamos adicionar a dependência Spring HATEOAS:


    org.springframework.boot
    spring-boot-starter-hateoas
    2.1.4.RELEASE

Se não estivermos usando Spring Boot, podemos adicionar as seguintes bibliotecas ao nosso projeto:


    org.springframework.hateoas
    spring-hateoas
    0.25.1.RELEASE


    org.springframework.plugin
    spring-plugin-core
    1.2.0.RELEASE

Como sempre, podemos pesquisar as versões mais recentes das dependênciasstarter HATEOAS,spring-hateoasespring-plugin-core no Maven Central.

Em seguida, temos o recursoCustomer sem suporte Spring HATEOAS:

public class Customer {

    private String customerId;
    private String customerName;
    private String companyName;

    // standard getters and setters
}

E nós temos uma classe de controlador sem suporte ao Spring HATEOAS:

@RestController
@RequestMapping(value = "/customers")
public class CustomerController {
    @Autowired
    private CustomerService customerService;

    @GetMapping("/{customerId}")
    public Customer getCustomerById(@PathVariable String customerId) {
        return customerService.getCustomerDetail(customerId);
    }
}

Finalmente, a representação de recursosCustomer:

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company"
}

4. Adicionando Suporte HATEOAS

Em um projeto Spring HATEOAS, não precisamos pesquisar o contexto do Servlet nem concatenar a variável de caminho para o URI de base.

Em vez disso,Spring HATEOAS offers three abstractions for creating the URI – ResourceSupport, Link, and ControllerLinkBuilder. Podemos usá-los para criar os metadados e associá-los à representação de recursos.

4.1. Adicionando Suporte Hipermídia a um Recurso

O projeto fornece uma classe base chamadaResourceSupport para herdar ao criar uma representação de recurso:

public class Customer extends ResourceSupport {
    private String customerId;
    private String customerName;
    private String companyName;

    // standard getters and setters
}

The Customer resource extends from the ResourceSupport class to inherit the add() method. Assim, quando criamos um link, podemos facilmente definir esse valor para a representação do recurso sem adicionar novos campos a ele.

Spring HATEOAS fornece um objetoLink para armazenar os metadados (localização ou URI do recurso).

Primeiro, vamos criar um link simples manualmente:

Link link = new Link("http://localhost:8080/spring-security-rest/api/customers/10A");

O objetoLink segue a sintaxe do linkhttps://en.wikipedia.org/wiki/Atom (padrão) [Atom] _ e consiste em umrel que identifica a relação com o recurso e o atributohref que é o link real em si.

Esta é a aparência do recursoCustomer agora que contém o novo link:

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company",
    "_links":{
        "self":{
            "href":"http://localhost:8080/spring-security-rest/api/customers/10A"
         }
    }
}

O URI associado à resposta é qualificado como um linkself. A semântica da relaçãoself é clara - é simplesmente a localização canônica em que o recurso pode ser acessado.

Outra abstração muito importante oferecida pela biblioteca éthe ControllerLinkBuilder – which simplifies building URIs, evitando os links embutidos em código.

O snippet a seguir mostra a construção do self-link do cliente usando a classeControllerLinkBuilder:

linkTo(CustomerController.class).slash(customer.getCustomerId()).withSelfRel();

Vamos dar uma olhada:

  • o métodolinkTo() inspeciona a classe do controlador e obtém seu mapeamento de raiz

  • o métodoslash() adiciona o valorcustomerId como a variável de caminho do link

  • finalmente, owithSelfMethod() qualifica a relação como um link próprio

5. Relações

Na seção anterior, mostramos uma relação de autorreferência. No entanto, sistemas mais complexos também podem envolver outras relações.

Por exemplo, acustomer pode ter um relacionamento com pedidos. Vamos modelar a classeOrder como um recurso também:

public class Order extends ResourceSupport {
    private String orderId;
    private double price;
    private int quantity;

    // standard getters and setters
}

Neste ponto, podemos estender oCustomerController com um método que retorna todos os pedidos de um cliente específico:

@GetMapping(value = "/{customerId}/orders", produces = { "application/hal+json" })
public Resources getOrdersForCustomer(@PathVariable final String customerId) {
    List orders = orderService.getAllOrdersForCustomer(customerId);
    for (final Order order : orders) {
        Link selfLink = linkTo(methodOn(CustomerController.class)
          .getOrderById(customerId, order.getOrderId())).withSelfRel();
        order.add(selfLink);
    }

    Link link = linkTo(methodOn(CustomerController.class)
      .getOrdersForCustomer(customerId)).withSelfRel();
    Resources result = new Resources(orders, link);
    return result;
}

Nosso método retorna um objetoResources para cumprir o tipo de retorno HAL, bem como um link “_self” para cada um dos pedidos e a lista completa.

Uma coisa importante a notar aqui é que o hyperlink para os pedidos do cliente depende do mapeamento do métodogetOrdersForCustomer(). Vamos nos referir a esses tipos de links como links de método e mostrar como osControllerLinkBuilder podem ajudar em sua criação.

OControllerLinkBuilder oferece suporte avançado para controladores Spring MVC. O exemplo a seguir mostra como construir hiperlinks HATEOAS com base no métodogetOrdersForCustomer() da classeCustomerController:

Link ordersLink = linkTo(methodOn(CustomerController.class)
  .getOrdersForCustomer(customerId)).withRel("allOrders");

The methodOn() obtains the method mapping by making dummy invocation of the target method no controlador de proxy e definecustomerId como a variável de caminho do URI.

7. Spring HATEOAS em ação

Vamos colocar o self-link e a criação do link de método juntos em um métodogetAllCustomers():

@GetMapping(produces = { "application/hal+json" })
public Resources getAllCustomers() {
    List allCustomers = customerService.allCustomers();

    for (Customer customer : allCustomers) {
        String customerId = customer.getCustomerId();
        Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
        customer.add(selfLink);
        if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
            Link ordersLink = linkTo(methodOn(CustomerController.class)
              .getOrdersForCustomer(customerId)).withRel("allOrders");
            customer.add(ordersLink);
        }
    }

    Link link = linkTo(CustomerController.class).withSelfRel();
    Resources result = new Resources(allCustomers, link);
    return result;
}

A seguir, vamos invocar o métodogetAllCustomers():

curl http://localhost:8080/spring-security-rest/api/customers

E examine o resultado:

{
  "_embedded": {
    "customerList": [{
        "customerId": "10A",
        "customerName": "Jane",
        "companyName": "ABC Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
          }
        }
      },{
        "customerId": "20B",
        "customerName": "Bob",
        "companyName": "XYZ Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B/orders"
          }
        }
      },{
        "customerId": "30C",
        "customerName": "Tim",
        "companyName": "CKV Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/30C"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers"
    }
  }
}

Em cada representação de recurso, há um linkself e o linkallOrders para extrair todos os pedidos de um cliente. Se um cliente não tiver pedidos, o link para pedidos não aparecerá.

Este exemplo demonstra como o Spring HATEOAS promove a descoberta da API em um serviço da Web restante. If the link exists, the client can follow it and get all orders for a customer:

curl http://localhost:8080/spring-security-rest/api/customers/10A/orders
{
  "_embedded": {
    "orderList": [{
        "orderId": "001A",
        "price": 150,
        "quantity": 25,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/001A"
          }
        }
      },{
        "orderId": "002A",
        "price": 250,
        "quantity": 15,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/002A"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
    }
  }
}

8. Conclusão

Neste tutorial, discutimos comobuild a hypermedia-driven Spring REST web service using the Spring HATEOAS project.

No exemplo, vemos que o cliente pode ter um único ponto de entrada para o aplicativo e outras ações podem ser executadas com base nos metadados na representação de resposta.

Isso permite que o servidor altere seu esquema de URI sem interromper o cliente. Além disso, o aplicativo pode anunciar novos recursos colocando novos links ou URIs na representação.

Finalmente, a implementação completa deste artigo pode ser encontrada emthe GitHub project.