Eine Einführung zu Spring HATEOAS

Eine Einführung in Spring HATEOAS

1. Überblick

In diesem Artikel wird das Erstellen eines hypermediengesteuerten REST-Webdiensts mithilfe des Spring HATEOAS-Projekts erläutert.

2. Frühling-HATEOAS

Das Spring HATEOAS-Projekt ist eine Bibliothek von APIs, mit denen wir einfach REST-Repräsentationen erstellen können, die dem Prinzip von HATEOAS (Hypertext als Engine of Application State) folgen.

Im Allgemeinen impliziert das Prinzip, dass die API den Client durch die Anwendung führen sollte, indem relevante Informationen über die nächsten möglichen Schritte zusammen mit jeder Antwort zurückgegeben werden.

In diesem Artikel erstellen wir ein Beispiel mit Spring HATEOAS mit dem Ziel, Client und Server zu entkoppeln und der API theoretisch zu ermöglichen, ihr URI-Schema zu ändern, ohne Clients zu beschädigen.

3. Vorbereitung

Fügen wir zunächst die Spring HATEOAS-Abhängigkeit hinzu:


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

Wenn wir Spring Boot nicht verwenden, können wir unserem Projekt die folgenden Bibliotheken hinzufügen:


    org.springframework.hateoas
    spring-hateoas
    0.25.1.RELEASE


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

Wie immer können wir in Maven Central nach den neuesten Versionen der Abhängigkeitenstarter HATEOAS,spring-hateoas undspring-plugin-core suchen.

Als nächstes haben wir die RessourceCustomerohne Spring HATEOAS-Unterstützung:

public class Customer {

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

    // standard getters and setters
}

Und wir haben eine Controller-Klasse ohne Spring HATEOAS-Unterstützung:

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

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

Schließlich die Ressourcendarstellung vonCustomer:

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

4. Hinzufügen von HATEOAS-Unterstützung

In einem Spring HATEOAS-Projekt müssen wir weder den Servlet-Kontext nachschlagen noch die Pfadvariable mit dem Basis-URI verketten.

StattdessenSpring HATEOAS offers three abstractions for creating the URI – ResourceSupport, Link, and ControllerLinkBuilder. Wir können diese verwenden, um die Metadaten zu erstellen und sie der Ressourcendarstellung zuzuordnen.

4.1. Hinzufügen von Hypermedia-Unterstützung zu einer Ressource

Das Projekt bietet eine Basisklasse namensResourceSupport, von der beim Erstellen einer Ressourcendarstellung geerbt werden kann:

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. Sobald wir einen Link erstellt haben, können wir diesen Wert einfach auf die Ressourcendarstellung setzen, ohne neue Felder hinzufügen zu müssen.

Spring HATEOAS stellt einLink-Objekt zum Speichern der Metadaten (Speicherort oder URI der Ressource) bereit.

Zunächst erstellen wir manuell einen einfachen Link:

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

Das ObjektLink folgt der Link-Syntaxhttps://en.wikipedia.org/wiki/Atom (Standard) [Atom] _ und besteht aus einemrel, das die Beziehung zur Ressource identifiziert, und dem Attributhref, das die tatsächliche Verknüpfung darstellt selbst.

So sieht die RessourceCustomerjetzt aus, da sie den neuen Link enthält:

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

Der der Antwort zugeordnete URI wird alsself-Verbindung qualifiziert. Die Semantik derself-Relation ist klar - es ist einfach der kanonische Ort, an dem auf die Ressource zugegriffen werden kann.

Eine weitere sehr wichtige Abstraktion, die von der Bibliothek angeboten wird, istthe ControllerLinkBuilder – which simplifies building URIs, indem fest codierte Links vermieden werden.

Das folgende Snippet zeigt das Erstellen des Kunden-Self-Links mithilfe der KlasseControllerLinkBuilder:

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

Werfen wir einen Blick:

  • Die MethodelinkTo()überprüft die Controller-Klasse und erhält ihre Root-Zuordnung

  • Die Methodeslash() fügt den WertcustomerId als Pfadvariable der Verknüpfung hinzu

  • Schließlich qualifiziert daswithSelfMethod() die Beziehung als Selbstverbindung

5. Beziehungen

Im vorherigen Abschnitt haben wir eine selbstreferenzierende Beziehung gezeigt. Komplexere Systeme können jedoch auch andere Beziehungen beinhalten.

Zum Beispiel kann eincustomer eine Beziehung zu Aufträgen haben. Modellieren wir auch die KlasseOrderals Ressource:

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

    // standard getters and setters
}

Zu diesem Zeitpunkt können wirCustomerController mit einer Methode erweitern, die alle Bestellungen eines bestimmten Kunden zurückgibt:

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

Unsere Methode gibt einResources-Objekt zurück, um dem HAL-Rückgabetyp zu entsprechen, sowie einen "_self”-Link für jede Bestellung und die vollständige Liste.

Hierbei ist zu beachten, dass der Hyperlink für die Kundenbestellungen von der Zuordnung dergetOrdersForCustomer()-Methode abhängt. Wir werden diese Arten von Links als Methodenlinks bezeichnen und zeigen, wie dieControllerLinkBuilder bei ihrer Erstellung helfen können.

DieControllerLinkBuilder bieten umfassende Unterstützung für Spring MVC Controller. Das folgende Beispiel zeigt, wie HATEOAS-Hyperlinks basierend auf dergetOrdersForCustomer()-Methode derCustomerController-Klasse erstellt werden:

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

The methodOn() obtains the method mapping by making dummy invocation of the target method auf dem Proxy-Controller und legtcustomerId als Pfadvariable des URI fest.

7. Frühling HATEOAS in Aktion

Lassen Sie uns die Erstellung von Selbstverknüpfungen und Methodenverknüpfungen in einergetAllCustomers()-Methode zusammenfassen:

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

Rufen wir als nächstes die MethodegetAllCustomers()auf:

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

Und überprüfe das Ergebnis:

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

Innerhalb jeder Ressourcendarstellung gibt es einenself-Link und einenallOrders-Link, um alle Bestellungen eines Kunden zu extrahieren. Wenn ein Kunde keine Bestellungen hat, wird der Link für Bestellungen nicht angezeigt.

Dieses Beispiel zeigt, wie Spring HATEOAS die API-Erkennbarkeit in einem Rest-Webdienst fördert. 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. Fazit

In diesem Tutorial haben wir besprochen, wie manbuild a hypermedia-driven Spring REST web service using the Spring HATEOAS project macht.

Im Beispiel sehen wir, dass der Client einen einzelnen Einstiegspunkt für die Anwendung haben kann und weitere Aktionen basierend auf den Metadaten in der Antwortdarstellung durchgeführt werden können.

Dadurch kann der Server sein URI-Schema ändern, ohne den Client zu beschädigen. Die Anwendung kann auch für neue Funktionen werben, indem neue Links oder URIs in die Darstellung eingefügt werden.

Die vollständige Implementierung dieses Artikels finden Sie inthe GitHub project.