Ten wpis sponsoruje literka P. P jak Protokół HTTP, który oferuje, między innymi, trzy metody używane przy tworzeniu przyzwoitego API typu REST: POST, PUT i PATCH . Przyjrzyjmy się ich funkcją różnicom między nimi.
Gdy implementujemy operacje z rodziny CRUD to pojawia się tam zwykle POST i PUT. Pierwszy jako Create, a drugi jako Update.
POST
Post służy do tworzenia nowych obiektów domenowych. Dane, jakie są przesłane w ciele żądania HTTP są zwykle bezpośrednio mapowane na obiekt Javowy i utrwalane w bazie danych.
W odpowiedzi powinniśmy wysłać status CREATED, albo OK wraz z informacjami o nowo stworzonym obiekcie, albo tylko o jego ID. Nie ma tu jednoznacznie, bez kontekstowo dobrych/złych odpowiedzi.
Żądania z metodą POST kierowane są na główny adres, gdzie dostępny jest dany zasób np. /guests
Przykładowa implementacja w Spring Framework
@PostMapping("/guests") @ResponseStatus(HttpStatus.CREATED) public void createGuest(@RequestBody Guest guest) { guestService.createNewGuest(guest); }I przykładowy request
$ curl -i -X POST --data '{"firstName":"Pawel","lastName":"Cwik","age":34}' -H "Content-Type: application/json" localhost:8080/api/guestsPUT
Put standardowo służy to aktualizacji bądź stworzenia nowego obiektu. Podobnie jak Post wymaga, by w ciele żądania znajdował się komplet danych umożliwiających utworzenie całego obiektu po stronie serwera. jeżeli takie obiekt już istnieje, to aktualizowane są wszystkie dane, jeżeli nie to na podstawie przesłanych informacji tworzony jest nowy obiekt.
W odpowiedzi serwer powinien wysłać odpowiedź CREATED lub w przypadku aktualizacji, NO_CONTENT. Ponownie, czy wysyłać w odpowiedzi reprezentacje nowo utworzonego (lub zaktualizowanego) obiektu to już kwestia wyboru.
Żądania metody PUT kierowane są na adres z parametrem ścieżki określającym ID obiektu, który chcemy zaktualizować np. /guests/2. Należy pamiętać, iż jeżeli w naszym systemie nie znajduje się obiekt z ID 2, to nowo utworzony obiekt nie będzie miał z góry określonego ID na 2. To baza danych zwykle odpowiada za nadawanie unikalnych identyfikatorów, nigdy użytkownik.
Przykładowa implementacja w Spring Framework
@PutMapping("/guests/{id}") public ResponseEntity<Void> updateGuest(@PathVariable Long id, @RequestBody Guest guest) { Long updatedId = this.guestService.fullUpdateGuest(id, guest); if(updatedId==id) { return ResponseEntity.noContent().build(); } else { return ResponseEntity.created(URI.create("guests/"+updatedId)).build(); } }Oraz żądanie. Przy nieistniejącym obiekcie http_code będzie miał wartość 201, a przy aktualizacji istniejącego 204
curl -w " ---- RSP CODE: %{http_code}" -X PUT --data '{"firstName":"Pawel","lastName":"Cwik","age":37}' -H "Content-Type: application/json" localhost:8080/api/guests/2PATCH
Metoda Patch podobnie jak Put służy do aktualizacji danych o obiekcie, jednak wymaga ona, by dany obiekt istniał. A to dlatego, iż nie przesyła ona w żądaniu kompletu danych, a jedynie te dane, które mają zostać zaktualizowane.
W odpowiedzi powinniśmy wysyłać NO_CONTENT, bądź OK i w odpowiedzi wysłać dane o zaktualizowanym obiekcie.
Żądania typu PATCH kierowane są na adres wraz z ID obiektu, który chcemy zaktualizować np. /guests/2
Implementacja w Spring Framework
@PatchMapping("/guests/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void patchGuests(@RequestBody Map<String, Object> updates, @PathVariable Long id) { this.guestService.partialUpdate(id, updates); }Przykładowe zapytanie
curl -w " ---- RSP CODE: %{http_code}" -X PATCH --data '{"age":40}' -H "Content-Type: application/json" localhost:8080/api/guests/2Na koniec jeszcze mała uwaga w temacie odpowiedzi HTTP. Czy zwracamy tylko odpowiedni kod, czy ogólne OK, ale z dodatkiem reprezentacji nowo utworzonego/zaktualizowanego obiektu w ciele odpowiedzi. Nie ma jednoznacznych dobrych i złych praktyk.
Kiedy POST, Kiedy PATCH, Kiedy PUT
Skoro mamy trzy bardzo zbliżone rzeczy, to kiedy należy której metody użyć? Na dobrą sprawę PUT może nam załatwić i Update, i Create.
Kiedy chcemy tylko i wyłącznie utworzyć nowy obiekt używamy POST, jest to metoda dokładnie po to stworzona.
Kiedy chcemy zaktualizować obiekt, to używamy PUT. Czasem, by uniknąć „niejednoznaczności” blokuje się po stronie serwera możliwość tworzenia nowych obiektów dzięki PUT, tak by używać go tylko i wyłącznie do aktualizacji stanu.
PATCH ma nieco większy stopień skomplikowania, zarówno po stronie UI jak i serwerowej. Powinno się go używać albo w momencie, gdy pracujemy na naprawdę bardzo dużych obiektach i różnica w czasie i zasobach między pełną aktualizacją a częściową jest odczuwalna, albo gdy zależy nam na ograniczeniu do minimum ilości danych przesyłanych przez sieć.