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:
Ti riferisci a un'entità, non a una collezione.
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.