HATEOAS for en Spring REST Service

REST Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

Denne artikel vil fokusere på implementering af opdagelighed i en Spring REST-tjeneste og om at opfylde HATEOAS-begrænsningen.

Denne artikel fokuserer på Spring MVC. Vores artikel An Intro to Spring HATEOAS beskriver, hvordan du bruger HATEOAS i Spring Boot.

2. Afkobling af opdagelighed gennem begivenheder

Opdagbarhed som et separat aspekt eller bekymring for weblaget skal frakobles fra controlleren håndtering af HTTP-anmodningen. Til dette formål afbryder controlleren begivenheder for alle de handlinger, der kræver yderligere manipulation af svaret.

Lad os først oprette begivenhederne:

offentlig klasse SingleResourceRetrieved udvider ApplicationEvent {private HttpServletResponse svar; offentlig SingleResourceRetrieved (Objektkilde, HttpServletResponse-svar) {super (kilde); this.response = svar; } offentlig HttpServletResponse getResponse () {return response; }} offentlig klasse ResourceCreated udvider ApplicationEvent {private HttpServletResponse svar; privat lang idOfNewResource; public ResourceCreated (Objektkilde, HttpServletResponse-svar, lang idOfNewResource) {super (kilde); this.response = svar; this.idOfNewResource = idOfNewResource; } offentlig HttpServletResponse getResponse () {retur svar; } offentlig lang getIdOfNewResource () {return idOfNewResource; }}

Derefter, controlleren med 2 enkle betjeninger - find efter id og skab:

@RestController @RequestMapping (værdi = "/ foos") offentlig klasse FooController {@Autowired privat ApplicationEventPublisher eventPublisher; @Autowired privat IFooService-tjeneste; @GetMapping (værdi = "foos / {id}") offentlig Foo findById (@PathVariable ("id") Lang id, HttpServletResponse svar) {Foo resourceById = Preconditions.checkNotNull (service.findOne (id)); eventPublisher.publishEvent (ny SingleResourceRetrieved (dette, svar)); return resourceById; } @PostMapping @ResponseStatus (HttpStatus.CREATED) public void create (@RequestBody Foo resource, HttpServletResponse response) {Preconditions.checkNotNull (resource); Lang newId = service.create (ressource) .getId (); eventPublisher.publishEvent (new ResourceCreated (this, response, newId)); }}

Vi kan derefter håndtere disse begivenheder med et hvilket som helst antal afkoblede lyttere. Hver af disse kan fokusere på sin egen særlige sag og hjælpe med at opfylde den overordnede HATEOAS-begrænsning.

Lytterne skal være de sidste objekter i opkaldstakken, og det er ikke nødvendigt med direkte adgang til dem; som sådan er de ikke offentlige.

3. At gøre URI'en til en nyoprettet ressource synlig

Som diskuteret i det forrige indlæg på HATEOAS, handlingen med at oprette en ny ressource skal returnere URI for denne ressource i Beliggenhed HTTP-overskrift af svaret.

Vi håndterer dette ved hjælp af en lytter:

@Komponent klasse ResourceCreatedDiscoverabilityListener implementerer ApplicationListener {@Override public void onApplicationEvent (ResourceCreated resourceCreatedEvent) {Preconditions.checkNotNull (resourceCreatedEvent); HttpServletResponse respons = resourceCreatedEvent.getResponse (); lang idOfNewResource = resourceCreatedEvent.getIdOfNewResource (); addLinkHeaderOnResourceCreation (svar, idOfNewResource); } ugyldig addLinkHeaderOnResourceCreation (HttpServletResponse-svar, lang idOfNewResource) {URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri (). sti ("/ {idOfNewResource}"). buildAndExpand (idOfNewResource) .toUri (); respons.setHeader ("Placering", uri.toASCIIString ()); }}

I dette eksempel vi gør brug af ServletUriComponentsBuilder - som hjælper med at bruge den aktuelle anmodning. På denne måde behøver vi ikke videregive noget, og vi kan simpelthen få adgang til dette statisk.

Hvis API'en ville vende tilbage SvarEnhed - vi kunne også bruge Beliggenhed support.

4. Få en enkelt ressource

Når du henter en enkelt ressource, klienten skal være i stand til at finde URI for at få alle ressourcer af den type:

@Komponent klasse SingleResourceRetrievedDiscoverabilityListener implementerer ApplicationListener {@Override public void onApplicationEvent (SingleResourceRetrieved resourceRetrievedEvent) {Preconditions.checkNotNull (resourceRetrievedEvent); HttpServletResponse respons = resourceRetrievedEvent.getResponse (); addLinkHeaderOnSingleResourceRetrieval (anmodning, svar); } ugyldigt addLinkHeaderOnSingleResourceRetrieval (HttpServletResponse svar) {String anmodningURL = ServletUriComponentsBuilder.fromCurrentRequestUri (). build (). toUri (). toASCIIString (); int positionOfLastSlash = requestURL.lastIndexOf ("/"); String uriForResourceCreation = requestURL.substring (0, positionOfLastSlash); String linkHeaderValue = LinkUtil .createLinkHeader (uriForResourceCreation, "samling"); respons.addHeader (LINK_HEADER, linkHeaderValue); }}

Bemærk, at semantikken i linkrelationen gør brug af "kollektion" relationstype, specificeret og anvendt i flere mikroformater, men endnu ikke standardiseret.

Det Link header er et af de mest anvendte HTTP-headeremed henblik på at finde. Værktøjet til at oprette dette overskrift er enkelt nok:

offentlig klasse LinkUtil {offentlig statisk streng createLinkHeader (String uri, String rel) {return "; rel = \" "+ rel +" \ ""; }}

5. Opdagelsesevne ved roden

Roten er indgangspunktet i hele tjenesten - det er hvad klienten kommer i kontakt med, når han bruger API for første gang.

Hvis HATEOAS-begrænsningen skal overvejes og implementeres overalt, er dette stedet at starte. Derfor alle de vigtigste URI'er i systemet skal kunne findes fra roden.

Lad os nu se på controlleren for dette:

@GetMapping ("/") @ResponseStatus (værdi = HttpStatus.NO_CONTENT) offentlig ugyldig adminRoot (endelig HttpServletRequest anmodning, endelig HttpServletResponse svar) {String rootUri = request.getRequestURL (). ToString (); URI fooUri = nyt UriTemplate ("{rootUri} {ressource}"). Udvid (rootUri, "foos"); String linkToFoos = LinkUtil.createLinkHeader (fooUri.toASCIIString (), "samling"); respons.addHeader ("Link", linkToFoos); }

Dette er naturligvis en illustration af konceptet, der fokuserer på en enkelt prøve URI til Foo Ressourcer. En reel implementering skal ligeledes tilføje URI'er for alle de ressourcer, der offentliggøres til klienten.

5.1. Opdagbarhed handler ikke om at ændre URI'er

Dette kan være et kontroversielt punkt - på den ene side er formålet med HATEOAS at få klienten til at opdage API'ets URI'er og ikke stole på hardkodede værdier. På den anden side - sådan fungerer internettet ikke: ja, URI'er opdages, men de er også bogmærket.

En subtil, men vigtig skelnen er udviklingen af ​​API'et - de gamle URI'er skal stadig fungere, men enhver klient, der opdager API'et, skal opdage de nye URI'er - som gør det muligt for API'et at ændre sig dynamisk, og gode klienter kan arbejde godt, selv når API-ændringer.

Afslutningsvis - bare fordi alle URI'er i RESTful-webservicen skal betragtes som kølige URI'er (og seje URI'er ændres ikke) - betyder det ikke, at overholdelse af HATEOAS-begrænsningen ikke er ekstremt nyttig, når man udvikler API'en.

6. Advarsler om opdagelighed

Som det fremgår af nogle af diskussionerne omkring de tidligere artikler, det første mål med opdagelighed er at gøre minimal eller ingen brug af dokumentation og få klienten til at lære og forstå, hvordan man bruger API'en via de svar, den får.

Faktisk skal dette ikke betragtes som et så langt hentet ideal - det er sådan, vi forbruger hver ny webside - uden nogen dokumentation. Så hvis konceptet er mere problematisk i forbindelse med REST, skal det være et spørgsmål om teknisk implementering, ikke et spørgsmål om, hvorvidt det er muligt.

Når det er sagt, er vi teknisk set stadig langt fra en fuldt fungerende løsning - specifikationen og rammestøtten er stadig under udvikling, og derfor er vi nødt til at indgå kompromiser.

7. Konklusion

Denne artikel dækkede implementeringen af ​​nogle af trækene ved opdagelighed i forbindelse med en RESTful Service med Spring MVC og berørte begrebet opdagelighed ved roden.

Implementeringen af ​​alle disse eksempler og kodestykker findes på GitHub - dette er et Maven-baseret projekt, så det skal være let at importere og køre som det er.

REST bunden

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

$config[zx-auto] not found$config[zx-overlay] not found