REMARQUE : lorsque j'ai passé du temps à lire sur REST, l'idempotence était un concept déroutant pour essayer de bien faire. Je n'ai toujours pas bien compris dans ma réponse d'origine, comme l' ont montré d' autres commentaires (et la réponse de Jason Hoetger ). Pendant un certain temps, j'ai résisté à la mise à jour complète de cette réponse, pour éviter de plagier efficacement Jason, mais je le modifie maintenant parce que, bien, on m'a demandé de le faire (dans les commentaires).
Après avoir lu ma réponse, je vous suggère également de lire l'excellente réponse de Jason Hoetger de à cette question, et je vais essayer d'améliorer ma réponse sans simplement voler de Jason.
Pourquoi PUT est-il idempotent?
Comme vous l'avez noté dans votre citation RFC 2616, PUT est considéré comme idempotent. Lorsque vous METTEZ une ressource, ces deux hypothèses sont en jeu:
Vous faites référence à une entité, pas à une collection.
L'entité que vous fournissez est complète (l' entité entière ).
Regardons l'un de vos exemples.
{ "username": "skwee357", "email": "skwee357@domain.com" }
Si vous POSTEZ ce document à /users
, comme vous le suggérez, vous pourriez récupérer une entité telle que
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
Si vous souhaitez modifier cette entité ultérieurement, vous choisissez entre PUT et PATCH. Un PUT pourrait ressembler à ceci:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // new email address
}
Vous pouvez accomplir la même chose en utilisant PATCH. Cela pourrait ressembler à ceci:
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
Vous remarquerez immédiatement une différence entre ces deux. Le PUT incluait tous les paramètres de cet utilisateur, mais PATCH ne comprenait que celui qui était en cours de modification (email
).
Lorsque vous utilisez PUT, il est supposé que vous envoyez l'entité complète et cette entité complète remplace toute entité existante à cet URI. Dans l'exemple ci-dessus, le PUT et le PATCH atteignent le même objectif: ils modifient tous les deux l'adresse e-mail de cet utilisateur. Mais PUT le gère en remplaçant l'entité entière, tandis que PATCH ne met à jour que les champs qui ont été fournis, laissant les autres seuls.
Étant donné que les demandes PUT incluent l'entité entière, si vous émettez la même demande à plusieurs reprises, elle devrait toujours avoir le même résultat (les données que vous avez envoyées sont désormais toutes les données de l'entité). Par conséquent, PUT est idempotent.
Utiliser PUT mal
Que se passe-t-il si vous utilisez les données PATCH ci-dessus dans une demande 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!
}
(Je suppose aux fins de cette question que le serveur n'a pas de champs obligatoires spécifiques et permettrait que cela se produise ... ce n'est peut-être pas le cas en réalité.)
Depuis que nous avons utilisé PUT, mais seulement fourni email
, c'est maintenant la seule chose dans cette entité. Cela a entraîné une perte de données.
Cet exemple est ici à des fins d'illustration - ne faites jamais vraiment cela. Cette demande PUT est techniquement idempotente, mais cela ne signifie pas que ce n'est pas une idée terrible et cassée.
Comment PATCH peut-il être idempotent?
Dans l'exemple ci-dessus, PATCH était idempotent. Vous avez fait un changement, mais si vous faisiez le même changement encore et encore, cela donnerait toujours le même résultat: vous avez changé l'adresse e-mail pour la nouvelle valeur.
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
}
Mon exemple d'origine, corrigé pour la précision
Au départ, j'avais des exemples qui, à mon avis, montraient la non-idempotence, mais ils étaient trompeurs / incorrects. Je vais garder les exemples, mais les utiliser pour illustrer une chose différente: que plusieurs documents PATCH contre la même entité, modifiant différents attributs, ne rendent pas les PATCH non idempotents.
Disons qu'à un certain moment, un utilisateur a été ajouté. C'est l'état à partir duquel vous partez.
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
Après un PATCH, vous avez une entité modifiée:
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"
}
Si vous appliquez ensuite votre PATCH à plusieurs reprises, vous continuerez à obtenir le même résultat: l'e-mail a été remplacé par la nouvelle valeur. A entre, A sort, donc c'est idempotent.
Une heure plus tard, après que vous soyez allé faire du café et faire une pause, quelqu'un d'autre vient avec son propre PATCH. Il semble que le bureau de poste ait apporté quelques modifications.
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
}
Étant donné que ce PATCH du bureau de poste ne concerne pas l'e-mail, uniquement le code postal, s'il est appliqué à plusieurs reprises, il obtiendra également le même résultat: le code postal est défini sur la nouvelle valeur. A entre, A sort, donc c'est aussi idempotent.
Le lendemain, vous décidez d'envoyer à nouveau votre 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"
}
Votre patch a le même effet qu'hier: il a défini l'adresse e-mail. A est entré, A est sorti, donc c'est aussi idempotent.
Ce que je me suis trompé dans ma réponse d'origine
Je veux faire une distinction importante (quelque chose que je me suis trompé dans ma réponse originale). De nombreux serveurs répondront à vos demandes REST en renvoyant le nouvel état d'entité, avec vos modifications (le cas échéant). Ainsi, lorsque vous obtenez cette réponse , elle est différente de celle que vous avez reçue hier , car le code postal n'est pas celui que vous avez reçu la dernière fois. Cependant, votre demande ne concernait pas le code postal, mais uniquement l'e-mail. Votre document PATCH est donc toujours idempotent - l'e-mail que vous avez envoyé dans PATCH est désormais l'adresse e-mail de l'entité.
Alors, quand PATCH n'est-il pas idempotent, alors?
Pour un traitement complet de cette question, je vous renvoie à nouveau à la réponse de Jason Hoetger . Je vais en rester là, car je ne pense vraiment pas pouvoir répondre à cette partie mieux qu'il ne l'a déjà fait.