Utilizzo dei metodi PUT vs PATCH negli scenari di vita reale dell'API REST


682

Prima di tutto, alcune definizioni:

PUT è definito nella Sezione 9.6 RFC 2616 :

Il metodo PUT richiede che l'entità inclusa sia archiviata nell'URI di richiesta fornito. Se l'URI della richiesta fa riferimento a una risorsa già esistente, l'entità inclusa DOVREBBE essere considerata come una versione modificata di quella residente sul server di origine . Se l'URI di richiesta non punta a una risorsa esistente e tale URI può essere definito come una nuova risorsa dall'agente utente richiedente, il server di origine può creare la risorsa con quell'URI.

PATCH è definito in RFC 5789 :

Il metodo PATCH richiede l'applicazione di una serie di modifiche descritte nell'entità richiesta alla risorsa identificata dall'URI della richiesta.

Anche secondo RFC 2616, la Sezione 9.1.2 PUT è idempotente, mentre PATCH no.

Ora diamo un'occhiata a un esempio reale. Quando eseguo il POST /userscon i dati {username: 'skwee357', email: 'skwee357@domain.com'}e il server è in grado di creare una risorsa, risponderà con 201 e posizione della risorsa (supponiamo /users/1) e /users/1tornerà qualsiasi chiamata successiva a GET {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}.

Ora diciamo che voglio modificare la mia email. La modifica dell'email è considerata "un insieme di modifiche" e quindi dovrei PATCH /users/1con " documento patch ". Nel mio caso sarebbe il documento JSON: {email: 'skwee357@newdomain.com'}. Il server quindi restituisce 200 (supponendo che le autorizzazioni siano ok). Questo mi porta alla prima domanda:

  • PATCH NON è idempotente. Lo diceva in RFC 2616 e RFC 5789. Tuttavia, se invio la stessa richiesta PATCH (con la mia nuova e-mail), otterrò lo stesso stato di risorsa (con la mia e-mail modificata al valore richiesto). Perché PATCH non è quindi idempotente?

PATCH è un verbo relativamente nuovo (RFC introdotto a marzo 2010), e arriva a risolvere il problema del "patching" o della modifica di una serie di campi. Prima dell'introduzione di PATCH, tutti utilizzavano PUT per aggiornare le risorse. Ma dopo che PATCH è stato introdotto, mi lascia confuso su ciò per cui viene utilizzato PUT. E questo mi porta alla mia seconda (e principale) domanda:

  • Qual è la vera differenza tra PUT e PATCH? Ho letto da qualche parte che PUT potrebbe essere usato per sostituire l' intera entità sotto una risorsa specifica, quindi si dovrebbe inviare l'intera entità (invece del set di attributi come con PATCH). Qual è il reale utilizzo pratico in questo caso? Quando desideri sostituire / sovrascrivere un'entità in un URI specifico della risorsa e perché un'operazione del genere non viene considerata aggiornando / rattoppando l'entità? L'unico caso d'uso pratico che vedo per PUT è l'emissione di un PUT su una raccolta, cioè /usersper sostituire l'intera raccolta. L'emissione di PUT su un'entità specifica non ha senso dopo l'introduzione di PATCH. Ho sbagliato?

1
a) è RFC 2616, non 2612. b) RFC 2616 è obsoleto, l'attuale specifica di PUT è in greenbytes.de/tech/webdav/rfc7231.html#PUT , c) non capisco la tua domanda; non è abbastanza ovvio che PUT può essere usato per sostituire qualsiasi risorsa, non solo una raccolta, d) prima dell'introduzione di PATCH, le persone usavano solitamente POST, e) infine, sì, una richiesta PATCH specifica (a seconda del formato della patch) può essere idempotente; è solo che non è generalmente.
Julian Reschke,

se mi aiuta, ho scritto un articolo su PATCH vs PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
equivalente8

5
Semplice: POST crea un elemento in una raccolta. PUT sostituisce un oggetto. PATCH modifica un oggetto. Durante il POST, l'URL per il nuovo elemento viene calcolato e restituito nella risposta, mentre PUT e PATCH richiedono un URL nella richiesta. Giusto?
Tom Russell,

Questo post potrebbe essere utile: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Risposte:


943

NOTA : quando ho trascorso il tempo a leggere di REST, l'idempotenza era un concetto confuso per cercare di ottenere il giusto. Non ho ancora capito bene nella mia risposta originale, come hanno dimostrato ulteriori commenti (e la risposta di Jason Hoetger ). Per un po ', ho resistito ad aggiornare ampiamente questa risposta, per evitare di plagiare efficacemente Jason, ma la sto modificando ora perché, beh, mi è stato chiesto di (nei commenti).

Dopo aver letto la mia risposta, ti suggerisco di leggere anche l'eccellente risposta di Jason Hoetger a questa domanda, e cercherò di migliorare la mia risposta senza semplicemente rubare a Jason.

Perché il PUT è idempotente?

Come hai notato nella tua citazione RFC 2616, PUT è considerato idempotente. Quando metti una risorsa, sono in gioco questi due presupposti:

  1. Ti riferisci a un'entità, non a una collezione.

  2. L'entità che stai fornendo è completa (il intera entità).

Diamo un'occhiata a uno dei tuoi esempi.

{ "username": "skwee357", "email": "skwee357@domain.com" }

Se invii questo documento a /users , come suggerisci, potresti recuperare un'entità come

## /users/1

{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}

Se desideri modificare questa entità in un secondo momento, scegli tra PUT e PATCH. Un PUT potrebbe apparire così:

PUT /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // new email address
}

Puoi ottenere lo stesso risultato usando PATCH. Potrebbe apparire così:

PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

Noterai subito una differenza tra questi due. Il PUT includeva tutti i parametri su questo utente, ma PATCH includeva solo quello che veniva modificato ( email).

Quando si utilizza PUT, si presume che si invii l'entità completa e quell'entità completa sostituisce qualsiasi entità esistente in tale URI. Nell'esempio sopra, PUT e PATCH raggiungono lo stesso obiettivo: entrambi cambiano l'indirizzo e-mail di questo utente. Ma PUT lo gestisce sostituendo l'intera entità, mentre PATCH aggiorna solo i campi che sono stati forniti, lasciando gli altri soli.

Poiché le richieste PUT includono l'intera entità, se si emette ripetutamente la stessa richiesta, dovrebbe sempre avere lo stesso risultato (i dati inviati sono ora tutti i dati dell'entità). Pertanto PUT è idempotente.

Usando PUT sbagliato

Cosa succede se si utilizzano i dati PATCH sopra riportati in una richiesta PUT?

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PUT /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "email": "skwee357@gmail.com"      // new email address... and nothing else!
}

(Sto assumendo ai fini di questa domanda che il server non ha campi obbligatori specifici e consentirebbe che ciò accada ... ciò potrebbe non essere il caso nella realtà.)

Dal momento che abbiamo usato PUT, ma fornito solo email, ora questa è l'unica cosa in questa entità. Ciò ha comportato la perdita di dati.

Questo esempio è qui a scopo illustrativo: non farlo mai. Questa richiesta PUT è tecnicamente idempotente, ma ciò non significa che non sia un'idea terribile, rotta.

Come può PATCH essere idempotente?

Nell'esempio sopra, PATCH era idempotente. Hai apportato una modifica, ma se hai apportato la stessa modifica più e più volte, restituiresti sempre lo stesso risultato: hai modificato l'indirizzo e-mail con il nuovo valore.

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // email address was changed
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // nothing changed since last GET
}

Il mio esempio originale, corretto per la precisione

Inizialmente avevo esempi che pensavo mostrassero non idempotenza, ma erano fuorvianti / errati. Conserverò gli esempi, ma li userò per illustrare una cosa diversa: che più documenti PATCH contro la stessa entità, modificando attributi diversi, non rendono i PATCH non idempotenti.

Diciamo che in passato è stato aggiunto un utente. Questo è lo stato da cui inizi.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Dopo un PATCH, hai un'entità modificata:

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Se poi applichi ripetutamente PATCH, continuerai a ottenere lo stesso risultato: l'e-mail è stata cambiata con il nuovo valore. A entra, A esce, quindi questo è idempotente.

Un'ora dopo, dopo che sei andato a fare un caffè e fare una pausa, qualcun altro arriva con il proprio PATCH. Sembra che l'ufficio postale abbia apportato alcune modifiche.

PATCH /users/1
{"zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Poiché questo PATCH dell'ufficio postale non si occupa di e-mail, ma solo di un codice postale, se viene applicato ripetutamente, otterrà lo stesso risultato: il codice postale viene impostato sul nuovo valore. Un entra, esce A, quindi questo è anche idempotente.

Il giorno successivo, decidi di inviare di nuovo il PATCH.

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

La tua patch ha lo stesso effetto che ha avuto ieri: ha impostato l'indirizzo e-mail. A è entrato, A è uscito, quindi anche questo è idempotente.

Cosa ho sbagliato nella mia risposta originale

Voglio fare una distinzione importante (qualcosa che ho sbagliato nella mia risposta originale). Molti server risponderanno alle tue richieste REST inviando il nuovo stato dell'entità, con le tue modifiche (se presenti). Quindi, quando ricevi questa risposta , è diversa da quella che hai ricevuto ieri , perché il codice postale non è quello che hai ricevuto l'ultima volta. Tuttavia, la tua richiesta non riguardava il codice postale, ma solo l'e-mail. Quindi il tuo documento PATCH è ancora idempotente: l'e-mail che hai inviato in PATCH ora è l'indirizzo e-mail sull'entità.

Quindi, quando PATCH non è idempotente, allora?

Per un trattamento completo di questa domanda, ti rimando nuovamente alla risposta di Jason Hoetger . Lo lascerò solo perché, onestamente, non penso di poter rispondere a questa parte meglio di quanto abbia già fatto.


2
Questa frase non è del tutto corretta: "Ma è idempotente: ogni volta che entra, B esce sempre". Ad esempio, se GET /users/1prima dell'ufficio postale fosse necessario aggiornare il codice postale e quindi effettuare nuovamente la stessa GET /users/1richiesta dopo l'aggiornamento dell'ufficio postale, si otterrebbero due risposte diverse (codici postali diversi). La stessa "A" (richiesta GET) sta entrando, ma stai ottenendo risultati diversi. Eppure GET è ancora idempotente.
Jason Hoetger,

@JasonHoetger GET è sicuro (presumibilmente non causa alcun cambiamento), ma non è sempre idempotente. C'è una differenza. Vedi RFC 2616 sec. 9.1 .
Dan Lowe,

1
@DanLowe: OTTENERE sicuramente è idempotente. Dice esattamente che nella Sezione 9.1.2 di RFC 2616, e nella specifica aggiornata, RFC 7231 sezione 4.2.2 , che "Dei metodi di richiesta definiti da questa specifica, PUT, DELETE e metodi di richiesta sicuri sono idempotenti." Idempotenza non significa semplicemente "ottieni la stessa risposta ogni volta che fai la stessa richiesta". 7231 4.2.2 prosegue dicendo: "La ripetizione della richiesta avrà lo stesso effetto previsto, anche se la richiesta originale ha avuto esito positivo, anche se la risposta potrebbe essere diversa. "
Jason Hoetger,

1
@JasonHoetger Lo ammetterò, ma non vedo cosa abbia a che fare con questa risposta, che ha discusso di PUT e PATCH e non menziona nemmeno GET ...
Dan Lowe,

1
Ah, il commento di @JasonHoetger l'ha chiarito: solo gli stati risultanti, piuttosto che le risposte, di più richieste di metodi idempotenti devono essere identici.
Tom Russell,

329

Sebbene l'eccellente risposta di Dan Lowe abbia risposto in modo molto approfondito alla domanda del PO sulla differenza tra PUT e PATCH, la sua risposta alla domanda sul perché PATCH non è idempotente non è del tutto corretta.

Per mostrare perché PATCH non è idempotente, aiuta a iniziare con la definizione di idempotenza (da Wikipedia ):

Il termine idempotente è usato in modo più completo per descrivere un'operazione che produrrà gli stessi risultati se eseguita una o più volte [...] Una funzione idempotente è quella che ha la proprietà f (f (x)) = f (x) per qualsiasi valore x.

In un linguaggio più accessibile, un PATCH idempotente potrebbe essere definito come: Dopo il PATCH di una risorsa con un documento patch, tutte le successive chiamate PATCH alla stessa risorsa con lo stesso documento patch non cambieranno la risorsa.

Al contrario, un'operazione non idempotente è quella in cui f (f (x))! = F (x), che per PATCH potrebbe essere indicato come: Dopo PATCHing una risorsa con un documento patch, PATCH successivo chiama la stessa risorsa con il stesso documento patch di fare modificare la risorsa.

Per illustrare un PATCH non idempotente, supponiamo che ci sia una risorsa / users e supponiamo che la chiamata GET /usersrestituisca un elenco di utenti, attualmente:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]

Anziché PATCHing / users / {id}, come nell'esempio dell'OP, supponiamo che il server consenta PATCHing / users. Emettiamo questa richiesta PATCH:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]

Il nostro documento di patch indica al server di aggiungere un nuovo utente chiamato newuserall'elenco di utenti. Dopo averlo chiamato la prima volta, GET /userssarebbe tornato:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" }]

Ora, se emettiamo la stessa richiesta PATCH come sopra, cosa succede? (Per il bene di questo esempio, supponiamo che la risorsa / users consenta nomi utente duplicati.) "Op" è "aggiungi", quindi un nuovo utente viene aggiunto all'elenco e un successivo GET /usersritorna:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" },
 { "id": 3, "username": "newuser", "email": "newuser@example.org" }]

La risorsa / users è cambiata di nuovo , anche se abbiamo emesso lo stesso PATCH esattamente sullo stesso endpoint. Se il nostro PATCH è f (x), f (f (x)) non è uguale a f (x), e quindi questo particolare PATCH non è idempotente .

Sebbene PATCH non sia garantito come idempotente, non c'è nulla nella specifica PATCH che ti impedisca di eseguire idempotenti tutte le operazioni PATCH sul tuo particolare server. RFC 5789 anticipa anche i vantaggi delle richieste PATCH idempotenti:

Una richiesta PATCH può essere emessa in modo tale da essere idempotente, il che aiuta anche a prevenire risultati errati da collisioni tra due richieste PATCH sulla stessa risorsa in un arco temporale simile.

Nell'esempio di Dan, la sua operazione PATCH è, in effetti, idempotente. In quell'esempio, l'entità / users / 1 è cambiata tra le nostre richieste PATCH, ma non a causa delle nostre richieste PATCH; è stato in realtà il diverso documento patch dell'ufficio postale a causare la modifica del codice postale. Il diverso PATCH dell'ufficio postale è un'operazione diversa; se il nostro PATCH è f (x), il PATCH dell'ufficio postale è g (x). L'idempotenza lo afferma f(f(f(x))) = f(x), ma non fa garanzie f(g(f(x))).


11
Supponendo che il server consenta anche l'emissione di PUT su /users, questo renderebbe PUT anche non idempotente. Tutto ciò si riduce a come il server è progettato per gestire le richieste.
Uzair Sajid,

13
Quindi, potremmo creare un'API solo con operazioni PATCH. Quindi, quale diventa il principio REST dell'utilizzo di http VERBS per effettuare azioni CRUD sulle risorse? Non stiamo forse complottando troppo i signori dei confini PATCH qui?
bohr

6
Se PUT è implementato su una raccolta (ad es. /users), Qualsiasi richiesta PUT dovrebbe sostituire il contenuto di quella raccolta. Quindi un PUT /usersdovrebbe aspettarsi una raccolta di utenti ed eliminare tutti gli altri. Questo è idempotente. Non è probabile che faresti una cosa del genere su un endpoint / utenti. Ma qualcosa del genere /users/1/emailspuò essere una collezione e può essere perfettamente valido per consentire di sostituire l'intera collezione con una nuova.
Vectorjohn,

5
Sebbene questa risposta fornisca un ottimo esempio di idempotenza, credo che ciò possa confondere le acque in scenari tipici di REST. In questo caso hai una richiesta PATCH con opun'azione aggiuntiva che sta innescando una logica lato server specifica. Ciò richiederebbe che server e client siano consapevoli dei valori specifici da trasmettere affinché il opcampo attivi i flussi di lavoro lato server. In scenari REST più semplici, questo tipo di opfunzionalità è una cattiva pratica e dovrebbe probabilmente essere gestita direttamente tramite i verbi HTTP.
ivandov

7
Non prenderei mai in considerazione l'emissione di un PATCH, solo POST e DELETE, contro una collezione. È davvero mai stato fatto? PATCH può quindi essere considerato idempotente per tutti gli scopi pratici?
Tom Russell,

72

Ero curioso anche di questo e ho trovato alcuni articoli interessanti. Potrei non rispondere alla tua domanda nella sua massima misura, ma questo almeno fornisce alcune ulteriori informazioni.

http://restful-api-design.readthedocs.org/en/latest/methods.html

HTTP RFC specifica che PUT deve assumere una rappresentazione di risorse completamente nuova come entità richiesta. Ciò significa che se, ad esempio, vengono forniti solo determinati attributi, questi dovrebbero essere rimossi (ovvero impostati su null).

Detto questo, quindi un PUT dovrebbe inviare l'intero oggetto. Per esempio,

/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.com'}

Ciò aggiornerebbe efficacemente l'e-mail. Il motivo per cui PUT potrebbe non essere troppo efficace è che la modifica di un solo campo e l'inclusione del nome utente sono praticamente inutili. Il prossimo esempio mostra la differenza.

/users/1
PUT {id: 1, email: 'newemail@domain.com'}

Ora, se il PUT è stato progettato secondo le specifiche, il PUT imposterà il nome utente su null e si otterrà il seguente indietro.

{id: 1, username: null, email: 'newemail@domain.com'}

Quando usi un PATCH, aggiorni solo il campo specificato e lascia il resto da solo come nel tuo esempio.

La seguente interpretazione di PATCH è un po 'diversa da quella che non avevo mai visto prima.

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

La differenza tra le richieste PUT e PATCH si riflette nel modo in cui il server elabora l'entità inclusa per modificare la risorsa identificata dall'URI di richiesta. In una richiesta PUT, l'entità inclusa viene considerata una versione modificata della risorsa memorizzata sul server di origine e il client richiede la sostituzione della versione archiviata. Con PATCH, tuttavia, l'entità racchiusa contiene una serie di istruzioni che descrivono come modificare una risorsa attualmente residente sul server di origine per produrre una nuova versione. Il metodo PATCH influenza la risorsa identificata dall'URI di richiesta e può anche avere effetti collaterali su altre risorse; cioè, nuove risorse possono essere create, o esistenti, modificate, mediante l'applicazione di un PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "new.email@example.org" }
]

Stai trattando più o meno il PATCH come un modo per aggiornare un campo. Quindi invece di inviare l'oggetto parziale, stai inviando l'operazione. ad es. sostituire la posta elettronica con valore.

L'articolo termina con questo.

Vale la pena ricordare che PATCH non è realmente progettato per API REST autentiche, poiché la tesi di Fielding non definisce alcun modo per modificare parzialmente le risorse. Ma lo stesso Roy Fielding ha affermato che PATCH era qualcosa [che] ha creato per la proposta iniziale HTTP / 1.1 perché il PUT parziale non è mai RESTful. Sicuramente non stai trasferendo una rappresentazione completa, ma REST non richiede comunque che le rappresentazioni siano complete.

Ora, non so se sono particolarmente d'accordo con l'articolo, come molti commentatori sottolineano. L'invio su una rappresentazione parziale può essere facilmente una descrizione delle modifiche.

Per me, sono confuso nell'uso di PATCH. Per la maggior parte, tratterò PUT come un PATCH poiché l'unica vera differenza che ho notato finora è che PUT "dovrebbe" impostare i valori mancanti su null. Potrebbe non essere il modo "più corretto" per farlo, ma buona fortuna è la codifica perfetta.


7
Potrebbe valere la pena aggiungere: nell'articolo di William Durand (e rfc 6902) ci sono esempi in cui "op" è "add". Questo ovviamente non è idempotente.
Johannes Brodwall,

2
Oppure puoi semplificare e utilizzare invece la RFC 7396 Merge Patch ed evitare di creare patch JSON.
Piotr Kula,

per le tabelle nosql, le differenze tra patch e put sono importanti, perché nosql non ha colonne
stackdave

18

La differenza tra PUT e PATCH è che:

  1. PUT deve essere idempotente. Per raggiungere questo obiettivo, devi inserire l'intera risorsa completa nel corpo della richiesta.
  2. PATCH può essere non idempotente. Ciò implica che può anche essere idempotente in alcuni casi, come i casi che hai descritto.

PATCH richiede un "linguaggio di patch" per indicare al server come modificare la risorsa. Il chiamante e il server devono definire alcune "operazioni" come "aggiungi", "sostituisci", "elimina". Per esempio:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "abc@myemail.com"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "abc@myemail.com",
  "state": "NY",
  "address": "123 main street",
}

Invece di utilizzare espliciti campi di "operazione", il linguaggio delle patch può renderlo implicito definendo convenzioni come:

nel corpo della richiesta PATCH:

  1. L'esistenza di un campo significa "sostituisci" o "aggiungi" quel campo.
  2. Se il valore di un campo è nullo, significa eliminare quel campo.

Con la convenzione di cui sopra, il PATCH nell'esempio può assumere la forma seguente:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "abc@myemail.com",
  "zip":
}

Che sembra più conciso e facile da usare. Ma gli utenti devono essere consapevoli della convenzione di base.

Con le operazioni che ho menzionato sopra, PATCH è ancora idempotente. Ma se definisci operazioni come: "increment" o "append", puoi facilmente vedere che non sarà più idempotente.


7

TLDR - Versione ridotta

PUT => Imposta tutti i nuovi attributi per una risorsa esistente.

PATCH => Aggiorna parzialmente una risorsa esistente (non tutti gli attributi richiesti).


3

Vorrei citare e commentare più da vicino la sezione 4.2.2 di RFC 7231 , già citata nei commenti precedenti:

Un metodo di richiesta è considerato "idempotente" se l'effetto previsto sul server di più richieste identiche con quel metodo è lo stesso dell'effetto per una singola di tali richieste. Dei metodi di richiesta definiti da questa specifica, PUT, DELETE e i metodi di richiesta sicuri sono idempotenti.

(...)

I metodi indipendenti si distinguono perché la richiesta può essere ripetuta automaticamente se si verifica un errore di comunicazione prima che il client sia in grado di leggere la risposta del server. Ad esempio, se un client invia una richiesta PUT e la connessione sottostante viene chiusa prima di ricevere qualsiasi risposta, il client può stabilire una nuova connessione e ritentare la richiesta idempotente. Sa che ripetere la richiesta avrà lo stesso effetto previsto, anche se la richiesta originale ha avuto esito positivo, sebbene la risposta potrebbe essere diversa.

Quindi, quale dovrebbe essere "lo stesso" dopo una ripetuta richiesta di un metodo idempotente? Non lo stato del server, né la risposta del server, ma l'effetto desiderato . In particolare, il metodo dovrebbe essere idempotente "dal punto di vista del cliente". Ora, penso che questo punto di vista mostri che l'ultimo esempio nella risposta di Dan Lowe , che non voglio qui plagiare, mostra in effetti che una richiesta PATCH può essere non idempotente (in modo più naturale dell'esempio in La risposta di Jason Hoetger ).

Effettivamente, rendiamo l'esempio leggermente più preciso rendendo esplicito un possibile intento per il primo cliente. Diciamo che questo client passa attraverso l'elenco degli utenti con il progetto per controllare le loro e-mail e e codici postali. Inizia con l'utente 1, nota che lo zip è giusto ma l'e-mail è sbagliata. Decide di correggerlo con una richiesta PATCH, che è pienamente legittima, e invia solo

PATCH /users/1
{"email": "skwee357@newdomain.com"}

poiché questa è l'unica correzione. Ora, la richiesta ha esito negativo a causa di alcuni problemi di rete e viene reinviata automaticamente un paio d'ore dopo. Nel frattempo, un altro client ha (erroneamente) modificato lo zip dell'utente 1. Quindi, l'invio della stessa richiesta PATCH una seconda volta non ottiene il effetto previsto del client, poiché si finisce con uno zip errato. Quindi il metodo non è idempotente nel senso della RFC.

Se invece il client utilizza una richiesta PUT per correggere l'e-mail, inviando al server tutte le proprietà dell'utente 1 insieme all'e-mail, l'effetto previsto verrà raggiunto anche se la richiesta deve essere inviata nuovamente in seguito e l'utente 1 è stato modificato nel frattempo --- poiché la seconda richiesta PUT sovrascriverà tutte le modifiche dalla prima richiesta.


2

Secondo la mia modesta opinione, idempotenza significa:

  • METTERE:

Mando una definizione della risorsa competitiva, quindi - lo stato della risorsa risultante è esattamente come definito dai parametri PUT. Ogni volta che aggiorno la risorsa con gli stessi parametri PUT - lo stato risultante è esattamente lo stesso.

  • PATCH:

Ho inviato solo una parte della definizione della risorsa, quindi potrebbe accadere che altri utenti stiano aggiornando i parametri ALTRI di questa risorsa nel frattempo. Di conseguenza - patch consecutive con gli stessi parametri e i loro valori potrebbero risultare con stato di risorsa diverso. Per esempio:

Presumi un oggetto definito come segue:

AUTO: - colore: nero, - tipo: berlina, - posti a sedere: 5

Lo patch con:

{colore rosso'}

L'oggetto risultante è:

AUTO: - colore: rosso, - tipo: berlina, - posti: 5

Quindi, alcuni altri utenti correggono questa macchina con:

{type: 'hatchback'}

quindi, l'oggetto risultante è:

AUTO: - colore: rosso, - tipo: berlina, - posti: 5

Ora, se rattoppo di nuovo questo oggetto con:

{colore rosso'}

l'oggetto risultante è:

AUTO: - colore: rosso, - tipo: berlina, - posti: 5

Ciò che è DIVERSO da quello che ho in precedenza!

Questo è il motivo per cui PATCH non è idempotente mentre PUT è idempotente.


1

Per concludere la discussione sull'idempotenza, dovrei notare che si può definire l'idempotenza nel contesto REST in due modi. Prima formalizziamo alcune cose:

Una risorsa è una funzione con il suo codice è la classe di stringhe. In altre parole, una risorsa è un sottoinsieme di String × Any, dove tutte le chiavi sono uniche. Chiamiamo la classe delle risorse Res.

Un'operazione REST su risorse, è una funzione f(x: Res, y: Res): Res. Due esempi di operazioni REST sono:

  • PUT(x: Res, y: Res): Res = x, e
  • PATCH(x: Res, y: Res): Res, che funziona come PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Questa definizione è specificamente progettata per discutere PUTe POST, ad esempio, non ha molto senso su GETe POST, poiché non si preoccupa della persistenza).

Ora, risolvendo x: Res(informalmente parlando, usando il curry), PUT(x: Res)e PATCH(x: Res)sono funzioni univariate di tipo Res → Res.

  1. Una funzione g: Res → Resè chiamato idempotente globalmente , quando g ○ g == g, cioè per ogni y: Res, g(g(y)) = g(y).

  2. Lascia x: Resuna risorsa e k = x.keys. Una funzione g = f(x)è chiamata idempotente , quando per ciascuno di y: Resnoi abbiamo g(g(y))|ₖ == g(y)|ₖ. Fondamentalmente significa che il risultato dovrebbe essere lo stesso, se guardiamo le chiavi applicate.

Quindi, PATCH(x)non è idempotente a livello globale, ma è lasciato idempotente. E l'idempotenza di sinistra è la cosa che conta qui: se patchamo alcune chiavi della risorsa, vogliamo che quelle chiavi siano uguali se la ripariamo di nuovo, e non ci preoccupiamo del resto della risorsa.

E quando RFC parla di PATCH non essendo idempotente, parla di idempotenza globale. Bene, è un bene che non sia globalmente idempotente, altrimenti sarebbe stata un'operazione fallita.


Ora, la risposta di Jason Hoetger sta cercando di dimostrare che PATCH non è nemmeno rimasto idempotente, ma sta rompendo troppe cose per farlo:

  • Prima di tutto, PATCH viene utilizzato su un set, sebbene PATCH sia definito per funzionare su mappe / dizionari / oggetti valore-chiave.
  • Se qualcuno vuole davvero applicare PATCH ai set, allora c'è una traduzione naturale che dovrebbe essere usata:, t: Set<T> → Map<T, Boolean>definita con x in A iff t(A)(x) == True. Usando questa definizione, il patching è lasciato idempotente.
  • Nell'esempio, questa traduzione non è stata utilizzata, invece PATCH funziona come un POST. Innanzitutto, perché viene generato un ID per l'oggetto? E quando viene generato? Se l'oggetto viene prima confrontato con gli elementi dell'insieme e se non viene trovato alcun oggetto corrispondente, viene generato l'ID, quindi il programma dovrebbe funzionare in modo diverso ( {id: 1, email: "me@site.com"}deve corrispondere a {email: "me@site.com"}, altrimenti il ​​programma viene sempre interrotto e il PATCH non può eventualmente patch). Se l'ID viene generato prima di verificare con il set, il programma viene nuovamente interrotto.

Si possono fare esempi di PUT come non idempotente con la rottura di metà delle cose che sono rotte in questo esempio:

  • Un esempio con funzioni aggiuntive generate sarebbe il controllo delle versioni. Si può tenere traccia del numero di modifiche su un singolo oggetto. In questo caso, PUT non è idempotente: PUT /user/12 {email: "me@site.com"}risulta {email: "...", version: 1}la prima volta e {email: "...", version: 2}la seconda volta.
  • In disordine con gli ID, è possibile generare un nuovo ID ogni volta che l'oggetto viene aggiornato, risultando in un PUT non idempotente.

Tutti gli esempi sopra sono esempi naturali che si possono incontrare.


Il mio ultimo punto è che PATCH non dovrebbe essere globalmente idempotente , altrimenti non ti darà l'effetto desiderato. Vuoi cambiare l'indirizzo email del tuo utente, senza toccare il resto delle informazioni e non vuoi sovrascrivere le modifiche di un'altra parte che accedono alla stessa risorsa.


-1

Un'ulteriore informazione che devo aggiungere è che una richiesta PATCH utilizza meno larghezza di banda rispetto a una richiesta PUT poiché solo una parte dei dati viene inviata non l'intera entità. Quindi basta usare una richiesta PATCH per gli aggiornamenti di record specifici come (1-3 record) mentre la richiesta PUT per l'aggiornamento di una maggiore quantità di dati. Ecco, non pensarci troppo o preoccuparti troppo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.