HATEOAS voor een Spring REST-service

REST Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

Dit artikel gaat over de implementatie van vindbaarheid in een Spring REST Service en om te voldoen aan de HATEOAS-beperking.

Dit artikel richt zich op Spring MVC. In ons artikel An Intro to Spring HATEOAS wordt beschreven hoe je HATEOAS in Spring Boot kunt gebruiken.

2. Ontkoppeling van vindbaarheid door middel van gebeurtenissen

Vindbaarheid als een afzonderlijk aspect of zorg van de weblaag moet worden losgekoppeld van de controller het afhandelen van het HTTP-verzoek. Voor dit doel zal de controller gebeurtenissen afvuren voor alle acties die aanvullende manipulatie van de respons vereisen.

Laten we eerst de evenementen maken:

openbare klasse SingleResourceRetrieved breidt ApplicationEvent {privé HttpServletResponse-antwoord uit; openbare SingleResourceRetrieved (objectbron, HttpServletResponse-reactie) {super (bron); this.response = antwoord; } openbare HttpServletResponse getResponse () {antwoord antwoord; }} openbare klasse ResourceCreated breidt ApplicationEvent {privé HttpServletResponse-antwoord uit; privé lange idOfNewResource; openbare ResourceCreated (objectbron, HttpServletResponse-antwoord, lange idOfNewResource) {super (bron); this.response = antwoord; this.idOfNewResource = idOfNewResource; } openbare HttpServletResponse getResponse () {antwoord antwoord; } openbare lange getIdOfNewResource () {retourneer idOfNewResource; }}

Dan, de controller, met 2 eenvoudige bewerkingen - zoeken op id en creëren:

@RestController @RequestMapping (value = "/ foos") openbare klasse FooController {@Autowired privé ApplicationEventPublisher eventPublisher; @Autowired privé IFooService-service; @GetMapping (waarde = "foos / {id}") openbare Foo findById (@PathVariable ("id") Lange id, HttpServletResponse-reactie) {Foo resourceById = Preconditions.checkNotNull (service.findOne (id)); eventPublisher.publishEvent (nieuwe SingleResourceRetrieved (this, response)); terug resourceById; } @PostMapping @ResponseStatus (HttpStatus.CREATED) public void create (@RequestBody Foo resource, HttpServletResponse response) {Preconditions.checkNotNull (resource); Lange newId = service.create (resource) .getId (); eventPublisher.publishEvent (nieuwe ResourceCreated (this, response, newId)); }}

We kunnen deze gebeurtenissen vervolgens afhandelen met een willekeurig aantal ontkoppelde luisteraars. Elk van deze kan zich richten op zijn eigen specifieke geval en helpen bij het voldoen aan de algemene HATEOAS-beperking.

De listeners moeten de laatste objecten in de call-stack zijn en er is geen directe toegang toe nodig; als zodanig zijn ze niet openbaar.

3. De URI van een nieuw gecreëerde bron vindbaar maken

Zoals besproken in het vorige bericht op HATEOAS, de bewerking van het maken van een nieuwe resource moet de URI van die resource in het Plaats HTTP-header van het antwoord.

We zullen dit afhandelen met behulp van een luisteraar:

@Component klasse ResourceCreatedDiscoverabilityListener implementeert ApplicationListener {@Override public void onApplicationEvent (ResourceCreated resourceCreatedEvent) {Preconditions.checkNotNull (resourceCreatedEvent); HttpServletResponse respons = resourceCreatedEvent.getResponse (); lang idOfNewResource = resourceCreatedEvent.getIdOfNewResource (); addLinkHeaderOnResourceCreation (antwoord, idOfNewResource); } void addLinkHeaderOnResourceCreation (HttpServletResponse-antwoord, lange idOfNewResource) {URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri (). pad ("/ {idOfNewResource}"). buildAndExpand (idOfNewResource) .toUri (); response.setHeader ("Locatie", uri.toASCIIString ()); }}

In dit voorbeeld we maken gebruik van de ServletUriComponentsBuilder - wat helpt bij het gebruik van het huidige verzoek. Op deze manier hoeven we niets door te geven en hebben we hier eenvoudig statisch toegang toe.

Als de API zou terugkeren ResponseEntity - we kunnen ook de Plaats ondersteuning.

4. Verkrijgen van een enkele bron

Bij het ophalen van een enkele bron, de client moet de URI kunnen vinden om alle bronnen te krijgen van dat type:

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

Merk op dat de semantiek van de linkrelatie gebruik maakt van de "verzameling" relatietype, gespecificeerd en gebruikt in verschillende microformats, maar nog niet gestandaardiseerd.

De Koppeling header is een van de meest gebruikte HTTP-headersmet het oog op vindbaarheid. Het hulpprogramma om deze koptekst te maken is eenvoudig genoeg:

openbare klasse LinkUtil {openbare statische String createLinkHeader (String uri, String rel) {return "; rel = \" "+ rel +" \ ""; }}

5. Vindbaarheid bij de wortel

De root is het toegangspunt in de hele service - het is waarmee de klant in contact komt wanneer hij de API voor het eerst gebruikt.

Als de HATEOAS-beperking overal in overweging moet worden genomen en geïmplementeerd, dan is dit de plek om te beginnen. Daarom alle hoofd-URI's van het systeem moeten detecteerbaar zijn vanaf de root.

Laten we nu hiervoor naar de controller kijken:

@GetMapping ("/") @ResponseStatus (waarde = HttpStatus.NO_CONTENT) public void adminRoot (laatste HttpServletRequest-verzoek, laatste HttpServletResponse-reactie) {String rootUri = request.getRequestURL (). ToString (); URI fooUri = nieuwe UriTemplate ("{rootUri} {resource}"). Expand (rootUri, "foos"); String linkToFoos = LinkUtil.createLinkHeader (fooUri.toASCIIString (), "collectie"); response.addHeader ("Link", linkToFoos); }

Dit is natuurlijk een illustratie van het concept, gericht op een enkele voorbeeld-URI, voor Foo Middelen. Een echte implementatie zou op dezelfde manier URI's moeten toevoegen voor alle bronnen die aan de client zijn gepubliceerd.

5.1. Vindbaarheid gaat niet over het wijzigen van URI's

Dit kan een controversieel punt zijn - enerzijds is het doel van HATEOAS om de klant de URI's van de API te laten ontdekken en niet te vertrouwen op hardcoded waarden. Aan de andere kant - dit is niet hoe het web werkt: ja, URI's worden ontdekt, maar ze zijn ook voorzien van een bladwijzer.

Een subtiel maar belangrijk onderscheid is de evolutie van de API - de oude URI's zouden nog steeds moeten werken, maar elke client die de API ontdekt, zou de nieuwe URI's moeten ontdekken - waardoor de API dynamisch kan veranderen en goede clients goed kunnen werken, zelfs als de API API-wijzigingen.

Concluderend - alleen omdat alle URI's van de RESTful-webservice als coole URI's moeten worden beschouwd (en coole URI's veranderen niet) - wil dat nog niet zeggen dat het naleven van de HATEOAS-beperking niet erg nuttig is bij het ontwikkelen van de API.

6. Voorbehoud van vindbaarheid

Zoals sommige discussies rond de vorige artikelen aangeven, het eerste doel van vindbaarheid is om minimaal of geen gebruik te maken van documentatie en laat de klant leren en begrijpen hoe hij de API moet gebruiken via de reacties die hij krijgt.

In feite moet dit niet als zo'n vergezocht ideaal worden beschouwd - het is hoe we elke nieuwe webpagina consumeren - zonder enige documentatie. Dus als het concept problematischer is in de context van REST, dan moet het een kwestie zijn van technische implementatie, niet van de vraag of het al dan niet mogelijk is.

Dat gezegd hebbende, technisch gezien zijn we nog ver verwijderd van een volledig werkende oplossing - de specificatie en framework-ondersteuning zijn nog in ontwikkeling, en daarom moeten we een aantal compromissen sluiten.

7. Conclusie

Dit artikel behandelde de implementatie van enkele kenmerken van vindbaarheid in de context van een RESTful Service met Spring MVC en ging in op het concept van vindbaarheid bij de wortel.

De implementatie van al deze voorbeelden en codefragmenten is te vinden op GitHub - dit is een op Maven gebaseerd project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.

REST onder

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS