HTTP PUT vs HTTP PATCH i et REST API

Oversigt

I denne hurtige artikel ser vi på forskellene mellem HTTP PUT- og PATCH-verberne og på semantikken i de to operationer.

Vi bruger Spring til at implementere to REST-endpoints, der understøtter disse to typer operationer, og til bedre at forstå forskellene og den rigtige måde at bruge dem på.

Hvornår skal man bruge Put og hvornår Patch?

Lad os starte med en simpel og lidt simpel erklæring.

Når en klient har brug for at erstatte en eksisterende ressource helt, kan de bruge PUT. Når de foretager en delvis opdatering, kan de bruge HTTP PATCH.

For eksempel, når de opdaterer et enkelt felt i ressourcen, kan det være besværligt at sende den komplette ressourcerepræsentation og bruge en masse unødvendig båndbredde. I sådanne tilfælde giver PATCHs semantik meget mere mening.

Et andet vigtigt aspekt at overveje her er idempotens; PUT er idempotent; PATCH kan være det, men er ikke forpligtet til det. Og så – afhængigt af semantikken for den operation, vi implementerer, kan vi også vælge det ene eller det andet baseret på denne egenskab.

Implementering af PUT- og PATCH-logik

Lad os sige, at vi ønsker at implementere REST API’et til opdatering af en HeavyResource med flere felter:

public class HeavyResource { private Integer id; private String name; private String address; // ...

Først skal vi oprette det endpoint, der håndterer en fuld opdatering af ressourcen ved hjælp af PUT:

@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}

Dette er et standard endpoint til opdatering af ressourcer.

Nu lad os sige, at adressefeltet ofte vil blive opdateret af klienten. I det tilfælde ønsker vi ikke at sende hele HeavyResource-objektet med alle felter, men vi ønsker muligheden for kun at opdatere adressefeltet – via PATCH-metoden.

Vi kan oprette en HeavyResourceAddressOnly DTO for at repræsentere en delvis opdatering af adressefeltet:

public class HeavyResourceAddressOnly { private Integer id; private String address; // ...}

Næst kan vi udnytte PATCH-metoden til at sende en delvis opdatering:

@PatchMapping("/heavyresource/{id}")public ResponseEntity<?> partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated");}

Med denne mere granulære DTO kan vi kun sende det felt, vi har brug for at opdatere – uden det overhead, der er forbundet med at sende hele HeavyResource.

Hvis vi har et stort antal af disse delvise opdateringsoperationer, kan vi også springe over oprettelsen af en brugerdefineret DTO for hver ud – og kun bruge et map:

@RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<?> partialUpdateGeneric( @RequestBody Map<String, Object> updates, @PathVariable("id") String id) { heavyResourceRepository.save(updates, id); return ResponseEntity.ok("resource updated");}

Denne løsning vil give os mere fleksibilitet i implementeringen af API’et; vi mister dog også et par ting – såsom validering.

Test af PUT og PATCH

Sluttelig, lad os skrive tests for begge HTTP-metoder. Først vil vi teste opdateringen af den fulde ressource via PUT-metoden:

mockMvc.perform(put("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResource(1, "Tom", "Jackson", 12, "heaven street"))) ).andExpect(status().isOk());

Udførelse af en delvis opdatering opnås ved at bruge PATCH-metoden:

mockMvc.perform(patch("/heavyrecource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString( new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk());

Vi kan også skrive en test for en mere generisk tilgang:

HashMap<String, Object> updates = new HashMap<>();updates.put("address", "5th avenue");mockMvc.perform(patch("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString(updates)) ).andExpect(status().isOk());

Håndtering af delvise anmodninger med nulværdier

Når vi skriver en implementering til en PATCH-metode, skal vi angive en kontrakt for, hvordan vi skal behandle tilfælde, hvor vi får nul som værdi for adressefeltet i HeavyResourceAddressOnly.

Sæt, at klienten sender følgende anmodning:

{ "id" : 1, "address" : null}

Så kan vi håndtere dette som at sætte en værdi for adressefeltet til null eller bare ignorere en sådan anmodning ved at behandle den som no-change.

Vi bør vælge én strategi til håndtering af null og holde os til den i hver PATCH-metodeimplementering.

Slutning

I denne hurtige vejledning fokuserede vi på at forstå forskellene mellem HTTP PATCH- og PUT-metoderne.

Vi implementerede en simpel Spring REST-controller til at opdatere en ressource via PUT-metoden og en delvis opdatering ved hjælp af PATCH.

Implementeringen af alle disse eksempler og kodestumper kan findes i GitHub-projektet – det er et Maven-projekt, så det burde være nemt at importere og køre som det er.

Kom godt i gang med Spring 5 og Spring Boot 2, gennem kurset Learn Spring :

>> CHECK OUT THE COURSE

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.