Une introduction au printemps HATEOAS

Une introduction au printemps HATEOAS

1. Vue d'ensemble

Cet article explique le processus de création d'un service Web REST piloté par hypermédia à l'aide du projet Spring HATEOAS.

2. Printemps-HATEOAS

Le projet Spring HATEOAS est une bibliothèque d’API que nous pouvons utiliser pour créer facilement des représentations REST qui suivent le principe de HATEOAS (l’hypertexte en tant que moteur de l’état de l’application).

De manière générale, le principe implique que l'API doit guider le client à travers l'application en renvoyant des informations pertinentes sur les prochaines étapes potentielles, avec chaque réponse.

Dans cet article, nous allons créer un exemple utilisant Spring HATEOAS dans le but de découpler le client et le serveur, et en théorie permettant à l'API de modifier son schéma URI sans casser les clients.

3. Préparation

Tout d'abord, ajoutons la dépendance Spring HATEOAS:


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

Si nous n'utilisons pas Spring Boot, nous pouvons ajouter les bibliothèques suivantes à notre projet:


    org.springframework.hateoas
    spring-hateoas
    0.25.1.RELEASE


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

Comme toujours, nous pouvons rechercher les dernières versions des dépendancesstarter HATEOAS,spring-hateoas etspring-plugin-core dans Maven Central.

Ensuite, nous avons la ressourceCustomer sans le support Spring HATEOAS:

public class Customer {

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

    // standard getters and setters
}

Et nous avons une classe de contrôleur sans support 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);
    }
}

Enfin, la représentation des ressourcesCustomer:

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

4. Ajout de la prise en charge de HATEOAS

Dans un projet Spring HATEOAS, nous n'avons pas besoin de rechercher le contexte du servlet ni de concaténer la variable de chemin avec l'URI de base.

Au lieu de cela,Spring HATEOAS offers three abstractions for creating the URI – ResourceSupport, Link, and ControllerLinkBuilder. Nous pouvons les utiliser pour créer les métadonnées et les associer à la représentation des ressources.

4.1. Ajout de la prise en charge hypermédia à une ressource

Le projet fournit une classe de base appeléeResourceSupport dont hériter lors de la création d'une représentation de ressource:

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. Ainsi, une fois le lien créé, nous pouvons facilement définir cette valeur sur la représentation des ressources sans y ajouter de nouveaux champs.

Spring HATEOAS fournit un objetLink pour stocker les métadonnées (emplacement ou URI de la ressource).

Tout d'abord, nous allons créer un lien simple manuellement:

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

L'objetLink suit la syntaxe du lienhttps://en.wikipedia.org/wiki/Atom (standard) [Atom] _ et se compose d'unrel qui identifie la relation avec la ressource et de l'attributhref qui est le lien réel lui-même.

Voici à quoi ressemble la ressourceCustomer maintenant qu'elle contient le nouveau lien:

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

L'URI associé à la réponse est qualifié de lienself. La sémantique de la relationself est claire - il s’agit simplement de l’endroit canonique auquel la ressource est accessible.

Une autre abstraction très importante offerte par la bibliothèque estthe ControllerLinkBuilder – which simplifies building URIs en évitant les liens codés en dur.

L'extrait de code suivant montre la création du lien automatique client à l'aide de la classeControllerLinkBuilder:

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

Regardons:

  • la méthodelinkTo() inspecte la classe du contrôleur et obtient son mappage racine

  • la méthodeslash() ajoute la valeurcustomerId comme variable de chemin du lien

  • enfin, lewithSelfMethod() qualifie la relation comme un auto-lien

5. Rapports

Dans la section précédente, nous avons montré une relation d'auto-référencement. Cependant, des systèmes plus complexes peuvent également impliquer d'autres relations.

Par exemple, uncustomer peut avoir une relation avec les commandes. Modélisons également la classeOrder en tant que ressource:

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

    // standard getters and setters
}

À ce stade, nous pouvons étendre lesCustomerController avec une méthode qui renvoie toutes les commandes d'un client particulier:

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

Notre méthode renvoie un objetResources pour se conformer au type de retour HAL, ainsi qu'un lien «_self” pour chacune des commandes et la liste complète.

Une chose importante à noter ici est que le lien hypertexte pour les commandes client dépend du mappage de la méthodegetOrdersForCustomer(). Nous appellerons ces types de liens des liens de méthode et montrerons comment lesControllerLinkBuilderpeuvent aider à leur création.

LeControllerLinkBuilder offre un support complet pour les contrôleurs Spring MVC. L'exemple suivant montre comment créer des liens hypertexte HATEOAS basés sur la méthodegetOrdersForCustomer() de la 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 sur le contrôleur proxy et définit lescustomerId comme variable de chemin de l'URI.

7. Spring HATEOAS en action

Mettons ensemble la création de lien automatique et de lien de méthode dans une méthodegetAllCustomers():

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

Ensuite, appelons la méthodegetAllCustomers():

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

Et examinez le résultat:

{
  "_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"
    }
  }
}

Dans chaque représentation de ressource, il y a un lienself et le lienallOrders pour extraire toutes les commandes d'un client. Si un client n'a pas de commandes, le lien pour les commandes n'apparaîtra pas.

Cet exemple montre comment Spring HATEOAS favorise la découvrabilité des API dans un service Web de repos. 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. Conclusion

Dans ce didacticiel, nous avons expliqué commentbuild a hypermedia-driven Spring REST web service using the Spring HATEOAS project.

Dans l'exemple, nous voyons que le client peut avoir un seul point d'entrée dans l'application et que d'autres actions peuvent être entreprises en fonction des métadonnées dans la représentation de la réponse.

Cela permet au serveur de changer son schéma d'URI sans casser le client. En outre, l'application peut annoncer de nouvelles fonctionnalités en ajoutant de nouveaux liens ou URI dans la représentation.

Enfin, l'implémentation complète de cet article se trouve dansthe GitHub project.