HATEOAS für einen Spring REST Service

REST Top

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs

1. Übersicht

Dieser Artikel konzentriert sich auf die Implementierung der Erkennbarkeit in einem Spring REST-Service und auf die Erfüllung der HATEOAS-Einschränkung.

Dieser Artikel konzentriert sich auf Spring MVC. Unser Artikel Eine Einführung in Spring HATEOAS beschreibt die Verwendung von HATEOAS in Spring Boot.

2. Entkopplung der Erkennbarkeit durch Ereignisse

Die Erkennbarkeit als separater Aspekt oder Anliegen der Webschicht sollte von dem Controller entkoppelt werden, der die HTTP-Anforderung verarbeitet. Zu diesem Zweck löst der Controller Ereignisse für alle Aktionen aus, die eine zusätzliche Manipulation der Antwort erfordern.

Zuerst erstellen wir die Ereignisse:

public class SingleResourceRetrieved extends ApplicationEvent { private HttpServletResponse response; public SingleResourceRetrieved(Object source, HttpServletResponse response) { super(source); this.response = response; } public HttpServletResponse getResponse() { return response; } } public class ResourceCreated extends ApplicationEvent { private HttpServletResponse response; private long idOfNewResource; public ResourceCreated(Object source, HttpServletResponse response, long idOfNewResource) { super(source); this.response = response; this.idOfNewResource = idOfNewResource; } public HttpServletResponse getResponse() { return response; } public long getIdOfNewResource() { return idOfNewResource; } }

Dann der Controller mit 2 einfachen Operationen - Suchen nach ID und Erstellen :

@RestController @RequestMapping(value = "/foos") public class FooController { @Autowired private ApplicationEventPublisher eventPublisher; @Autowired private IFooService service; @GetMapping(value = "foos/{id}") public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) { Foo resourceById = Preconditions.checkNotNull(service.findOne(id)); eventPublisher.publishEvent(new SingleResourceRetrieved(this, response)); return resourceById; } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void create(@RequestBody Foo resource, HttpServletResponse response) { Preconditions.checkNotNull(resource); Long newId = service.create(resource).getId(); eventPublisher.publishEvent(new ResourceCreated(this, response, newId)); } }

Wir können diese Ereignisse dann mit einer beliebigen Anzahl entkoppelter Listener behandeln. Jeder von ihnen kann sich auf seinen eigenen Fall konzentrieren und dazu beitragen, die allgemeine HATEOAS-Einschränkung zu erfüllen.

Die Listener sollten die letzten Objekte im Aufrufstapel sein, und es ist kein direkter Zugriff auf sie erforderlich. als solche sind sie nicht öffentlich.

3. Den URI einer neu erstellten Ressource erkennbar machen

Wie im vorherigen Beitrag zu HATEOAS erläutert, sollte beim Erstellen einer neuen Ressource der URI dieser Ressource im Location- HTTP-Header der Antwort zurückgegeben werden.

Wir werden dies mit einem Listener erledigen:

@Component class ResourceCreatedDiscoverabilityListener implements ApplicationListener{ @Override public void onApplicationEvent(ResourceCreated resourceCreatedEvent){ Preconditions.checkNotNull(resourceCreatedEvent); HttpServletResponse response = resourceCreatedEvent.getResponse(); long idOfNewResource = resourceCreatedEvent.getIdOfNewResource(); addLinkHeaderOnResourceCreation(response, idOfNewResource); } void addLinkHeaderOnResourceCreation (HttpServletResponse response, long idOfNewResource){ URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri(). path("/{idOfNewResource}").buildAndExpand(idOfNewResource).toUri(); response.setHeader("Location", uri.toASCIIString()); } }

In diesem Beispiel verwenden wir den ServletUriComponentsBuilder, der bei der Verwendung der aktuellen Anforderung hilft. Auf diese Weise müssen wir nichts weitergeben und können einfach statisch darauf zugreifen.

Wenn die API ResponseEntity zurückgeben würde, könnten wir auch die Standortunterstützung verwenden .

4. Eine einzelne Ressource erhalten

Beim Abrufen einer einzelnen Ressource sollte der Client in der Lage sein, den URI zu ermitteln, um alle Ressourcen dieses Typs abzurufen :

@Component class SingleResourceRetrievedDiscoverabilityListener implements ApplicationListener{ @Override public void onApplicationEvent(SingleResourceRetrieved resourceRetrievedEvent){ Preconditions.checkNotNull(resourceRetrievedEvent); HttpServletResponse response = resourceRetrievedEvent.getResponse(); addLinkHeaderOnSingleResourceRetrieval(request, response); } void addLinkHeaderOnSingleResourceRetrieval(HttpServletResponse response){ String requestURL = ServletUriComponentsBuilder.fromCurrentRequestUri(). build().toUri().toASCIIString(); int positionOfLastSlash = requestURL.lastIndexOf("/"); String uriForResourceCreation = requestURL.substring(0, positionOfLastSlash); String linkHeaderValue = LinkUtil .createLinkHeader(uriForResourceCreation, "collection"); response.addHeader(LINK_HEADER, linkHeaderValue); } }

Beachten Sie, dass die Semantik der Verknüpfungsbeziehung den Beziehungstyp "Sammlung" verwendet, der in mehreren Mikroformaten angegeben und verwendet wird, jedoch noch nicht standardisiert ist.

Der Link- Header ist einer der am häufigsten verwendeten HTTP-Header, um die Erkennbarkeit zu gewährleisten. Das Dienstprogramm zum Erstellen dieses Headers ist einfach genug:

public class LinkUtil { public static String createLinkHeader(String uri, String rel) { return "; rel=\"" + rel + "\""; } }

5. Auffindbarkeit an der Wurzel

Die Wurzel ist der Einstiegspunkt in den gesamten Service - damit kommt der Client in Kontakt, wenn er die API zum ersten Mal verwendet.

Wenn die HATEOAS-Einschränkung berücksichtigt und implementiert werden soll, ist dies der Ausgangspunkt. Daher müssen alle Haupt-URIs des Systems von der Wurzel aus erkennbar sein.

Schauen wir uns nun den Controller dafür an:

@GetMapping("/") @ResponseStatus(value = HttpStatus.NO_CONTENT) public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) { String rootUri = request.getRequestURL().toString(); URI fooUri = new UriTemplate("{rootUri}{resource}").expand(rootUri, "foos"); String linkToFoos = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection"); response.addHeader("Link", linkToFoos); }

Dies ist natürlich eine Illustration des Konzepts, das sich auf eine einzelne Beispiel-URI für Foo Resources konzentriert. Eine echte Implementierung sollte in ähnlicher Weise URIs für alle auf dem Client veröffentlichten Ressourcen hinzufügen.

5.1. Bei der Erkennbarkeit geht es nicht darum, URIs zu ändern

Dies kann ein kontroverser Punkt sein - einerseits besteht der Zweck von HATEOAS darin, dass der Client die URIs der API erkennt und sich nicht auf fest codierte Werte verlässt. Auf der anderen Seite - so funktioniert das Web nicht: Ja, URIs werden erkannt, aber sie werden auch mit Lesezeichen versehen.

Eine subtile, aber wichtige Unterscheidung ist die Entwicklung der API - die alten URIs sollten weiterhin funktionieren, aber jeder Client, der die API erkennt, sollte die neuen URIs erkennen -, wodurch sich die API dynamisch ändern kann und gute Clients auch dann gut funktionieren, wenn die API-Änderungen.

Nur weil alle URIs des RESTful-Webdienstes als coole URIs betrachtet werden sollten (und sich coole URIs nicht ändern), bedeutet dies nicht, dass die Einhaltung der HATEOAS-Einschränkung bei der Weiterentwicklung der API nicht besonders nützlich ist.

6. Vorbehalte zur Auffindbarkeit

Wie in einigen Diskussionen zu den vorherigen Artikeln festgestellt wurde , besteht das erste Ziel der Auffindbarkeit darin, die Dokumentation nur minimal oder gar nicht zu verwenden und den Kunden über die erhaltenen Antworten lernen und verstehen zu lassen, wie die API verwendet wird.

Tatsächlich sollte dies nicht als ein so weit hergeholtes Ideal angesehen werden - so konsumieren wir jede neue Webseite - ohne Dokumentation. Wenn das Konzept im Kontext von REST problematischer ist, muss es sich um eine technische Implementierung handeln, nicht um die Frage, ob es möglich ist oder nicht.

Technisch gesehen sind wir jedoch noch weit von einer voll funktionsfähigen Lösung entfernt - die Spezifikation und die Framework-Unterstützung entwickeln sich weiter, und aus diesem Grund müssen wir einige Kompromisse eingehen.

7. Fazit

Dieser Artikel befasste sich mit der Implementierung einiger Merkmale der Auffindbarkeit im Kontext eines RESTful Service mit Spring MVC und ging auf das Konzept der Auffindbarkeit an der Wurzel ein.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie auf GitHub - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.

REST unten

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs