Tout d'abord, quelques définitions:
PUT est défini dans la section 9.6 RFC 2616 :
La méthode PUT demande que l'entité incluse soit stockée sous l'URI de demande fourni. Si l'URI de demande fait référence à une ressource déjà existante, l'entité incluse DEVRAIT être considérée comme une version modifiée de celle résidant sur le serveur d'origine . Si l'URI de demande ne pointe pas vers une ressource existante et que cet URI peut être défini comme nouvelle ressource par l'agent utilisateur demandeur, le serveur d'origine peut créer la ressource avec cet URI.
PATCH est défini dans RFC 5789 :
La méthode PATCH demande qu'un ensemble de modifications décrites dans l'entité de demande soit appliqué à la ressource identifiée par l'URI de demande.
Toujours selon RFC 2616 Section 9.1.2 PUT est Idempotent tandis que PATCH ne l'est pas.
Voyons maintenant un exemple réel. Lorsque je fais un POST /users
avec les données {username: 'skwee357', email: '[email protected]'}
et que le serveur est capable de créer une ressource, il répondra avec 201 et l'emplacement de la ressource (supposons /users/1
) et tout prochain appel à GET /users/1
reviendra {id: 1, username: 'skwee357', email: '[email protected]'}
.
Disons maintenant que je souhaite modifier mon adresse e-mail. La modification des e-mails est considérée comme "un ensemble de changements" et je dois donc CORRIGER /users/1
avec "le document de correction ". Dans mon cas , ce serait le document JSON: {email: '[email protected]'}
. Le serveur renvoie alors 200 (en supposant que les autorisations sont correctes). Cela m'amène à la première question:
- PATCH n'est PAS idempotent. Il l'a dit dans RFC 2616 et RFC 5789. Cependant, si j'émets la même demande PATCH (avec mon nouvel e-mail), j'obtiendrai le même état de ressource (avec mon e-mail étant modifié à la valeur demandée). Pourquoi PATCH n'est-il pas alors idempotent?
PATCH est un verbe relativement nouveau (RFC introduit en mars 2010), et il vient résoudre le problème du "patching" ou de la modification d'un ensemble de champs. Avant l'introduction de PATCH, tout le monde utilisait PUT pour mettre à jour les ressources. Mais après l'introduction de PATCH, cela me laisse perplexe quant à l'utilisation de PUT. Et cela m'amène à ma deuxième (et principale) question:
- Quelle est la vraie différence entre PUT et PATCH? J'ai lu quelque part que PUT pourrait être utilisé pour remplacer l' entité entière sous une ressource spécifique, donc on devrait envoyer l'entité complète (au lieu d'un ensemble d'attributs comme avec PATCH). Quelle est la véritable utilisation pratique d'un tel cas? Quand souhaitez-vous remplacer / écraser une entité à un URI de ressource spécifique et pourquoi une telle opération n'est-elle pas considérée comme mettant à jour / corrigeant l'entité? Le seul cas d'utilisation pratique que je vois pour PUT est l'émission d'un PUT sur une collection, c'est-
/users
à- dire pour remplacer la collection entière. L'émission de PUT sur une entité spécifique n'a aucun sens après l'introduction de PATCH. Ai-je tort?
Réponses:
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.
Si vous POSTEZ ce document à
/users
, comme vous le suggérez, vous pourriez récupérer une entité telle queSi vous souhaitez modifier cette entité ultérieurement, vous choisissez entre PUT et PATCH. Un PUT pourrait ressembler à ceci:
Vous pouvez accomplir la même chose en utilisant PATCH. Cela pourrait ressembler à ceci:
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?
(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.
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.
Après un PATCH, vous avez une entité modifiée:
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.
É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.
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.
la source
GET /users/1
avant que le bureau de posteGET /users/1
ne mette à jour le code postal et que vous fassiez à nouveau la même demande après la mise à jour du bureau de poste, vous obtiendriez deux réponses différentes (différents codes postaux). Le même "A" (demande GET) entre, mais vous obtenez des résultats différents. Pourtant, GET est toujours idempotent.Bien que l'excellente réponse de Dan Lowe ait répondu de manière très approfondie à la question du PO concernant la différence entre PUT et PATCH, sa réponse à la question de savoir pourquoi PATCH n'est pas idempotent n'est pas tout à fait correcte.
Pour montrer pourquoi PATCH n'est pas idempotent, il est utile de commencer par la définition de l'idempotence (de Wikipedia ):
Dans un langage plus accessible, un PATCH idempotent pourrait être défini comme suit: Après avoir PATCHé une ressource avec un document de correctif, tous les appels PATCH ultérieurs à la même ressource avec le même document de correctif ne changeront pas la ressource.
Inversement, une opération non idempotente est une opération où f (f (x))! = F (x), qui pour PATCH pourrait être indiqué comme suit: Après avoir PATCHé une ressource avec un document de patch, les appels PATCH ultérieurs à la même ressource même document patch faire modifier la ressource.
Pour illustrer un PATCH non idempotent, supposons qu'il existe une ressource / users et supposons que l'appel
GET /users
renvoie une liste d'utilisateurs, actuellement:Plutôt que PATCHing / users / {id}, comme dans l'exemple de l'OP, supposons que le serveur autorise PATCHing / users. Émettons cette demande PATCH:
Notre document de patch demande au serveur d'ajouter un nouvel utilisateur appelé
newuser
à la liste des utilisateurs. Après avoir appelé cela la première fois,GET /users
reviendrait:Maintenant, si nous émettons exactement la même demande PATCH que ci-dessus, que se passe-t-il? (Pour les besoins de cet exemple, supposons que la ressource / users autorise les noms d'utilisateur en double.) L'op est "add", donc un nouvel utilisateur est ajouté à la liste et un
GET /users
retourne:La ressource / users a encore changé , même si nous avons émis exactement le même PATCH contre exactement le même point de terminaison. Si notre PATCH est f (x), f (f (x)) n'est pas le même que f (x), et par conséquent, ce PATCH particulier n'est pas idempotent .
Bien que PATCH ne soit pas garanti idempotent, rien dans la spécification PATCH ne vous empêche d'effectuer toutes les opérations PATCH sur votre serveur particulier idempotent. La RFC 5789 anticipe même les avantages des requêtes PATCH idempotentes:
Dans l'exemple de Dan, son opération PATCH est, en fait, idempotente. Dans cet exemple, l'entité / users / 1 a changé entre nos demandes PATCH, mais pas à cause de nos demandes PATCH; c'est en fait le document de patch différent du bureau de poste qui a fait changer le code postal. Le PATCH différent du bureau de poste est une opération différente; si notre PATCH est f (x), le PATCH du bureau de poste est g (x). Idempotence déclare cela
f(f(f(x))) = f(x)
, mais ne fait aucune garantief(g(f(x)))
.la source
/users
, cela rendrait également PUT non idempotent. Tout cela se résume à la façon dont le serveur est conçu pour gérer les demandes./users
), toute demande PUT doit remplacer le contenu de cette collection. Un PUT/users
doit donc s'attendre à une collection d'utilisateurs et supprimer tous les autres. C'est idempotent. Il est peu probable que vous fassiez une telle chose sur un point de terminaison / users. Mais quelque chose comme/users/1/emails
peut être une collection et il peut être parfaitement valide de permettre de remplacer la collection entière par une nouvelle.op
action supplémentaire qui déclenche une logique côté serveur spécifique. Cela nécessiterait que le serveur et le client soient conscients des valeurs spécifiques à transmettre pour que leop
champ déclenche des workflows côté serveur. Dans des scénarios REST plus simples, ce type deop
fonctionnalité est une mauvaise pratique et devrait probablement être géré directement via des verbes HTTP.J'étais également curieux à ce sujet et j'ai trouvé quelques articles intéressants. Je ne répondrai peut-être pas à votre question dans son intégralité, mais cela fournit au moins quelques informations supplémentaires.
http://restful-api-design.readthedocs.org/en/latest/methods.html
Étant donné que, alors un PUT devrait envoyer l'objet entier. Par exemple,
Cela mettrait effectivement à jour l'e-mail. La raison pour laquelle PUT n'est peut-être pas trop efficace est que vous ne modifiez vraiment qu'un seul champ et y compris le nom d'utilisateur est un peu inutile. L'exemple suivant montre la différence.
Maintenant, si le PUT a été conçu selon les spécifications, alors le PUT définirait le nom d'utilisateur sur null et vous obtiendriez ce qui suit.
Lorsque vous utilisez un PATCH, vous ne mettez à jour que le champ que vous spécifiez et laissez le reste comme dans votre exemple.
La prise suivante sur le PATCH est un peu différente de ce que je n'ai jamais vu auparavant.
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Vous traitez plus ou moins le PATCH comme un moyen de mettre à jour un champ. Donc, au lieu d'envoyer sur l'objet partiel, vous envoyez sur l'opération. c'est-à-dire remplacer l'email par une valeur.
L'article se termine par ceci.
Maintenant, je ne sais pas si je suis particulièrement d'accord avec l'article comme le soulignent de nombreux commentateurs. L'envoi sur une représentation partielle peut facilement être une description des changements.
Pour moi, je suis mitigée sur l'utilisation de PATCH. Pour la plupart, je traiterai PUT comme un PATCH car la seule vraie différence que j'ai remarquée jusqu'à présent est que PUT "devrait" mettre les valeurs manquantes à null. Ce n'est peut-être pas la façon la plus correcte de le faire, mais bonne chance, le codage est parfait.
la source
La différence entre PUT et PATCH est que:
PATCH nécessite un "langage de correctif" pour indiquer au serveur comment modifier la ressource. L'appelant et le serveur doivent définir certaines "opérations" telles que "ajouter", "remplacer", "supprimer". Par exemple:
Au lieu d'utiliser des champs "opération" explicites, le langage de patch peut le rendre implicite en définissant des conventions comme:
dans le corps de demande PATCH:
Avec la convention ci-dessus, le PATCH dans l'exemple peut prendre la forme suivante:
Ce qui semble plus concis et convivial. Mais les utilisateurs doivent être conscients de la convention sous-jacente.
Avec les opérations que j'ai mentionnées ci-dessus, le PATCH est toujours idempotent. Mais si vous définissez des opérations comme: "incrémenter" ou "ajouter", vous pouvez facilement voir que ce ne sera plus idempotent.
la source
TLDR - Version simplifiée
PUT => Définir tous les nouveaux attributs d'une ressource existante.
PATCH => Mettre à jour partiellement une ressource existante (tous les attributs ne sont pas requis).
la source
Permettez-moi de citer et de commenter de plus près la section 4.2.2 de la RFC 7231 , déjà citée dans les commentaires précédents:
Alors, qu'est-ce qui devrait être "le même" après une demande répétée d'une méthode idempotente? Pas l'état du serveur, ni la réponse du serveur, mais l'effet escompté . En particulier, la méthode doit être idempotente "du point de vue du client". Maintenant, je pense que ce point de vue montre que le dernier exemple de la réponse de Dan Lowe , que je ne veux pas plagier ici, montre en effet qu'une demande PATCH peut être non idempotente (d'une manière plus naturelle que l'exemple de Réponse de Jason Hoetger ).
En effet, rendons l'exemple un peu plus précis en rendant explicite un destin possible pour le premier client. Disons que ce client parcourt la liste des utilisateurs avec le projet pour vérifier leurs emails et codes postaux. Il commence par l'utilisateur 1, remarque que le zip est correct mais que l'e-mail est incorrect. Il décide de corriger cela avec une demande PATCH, qui est pleinement légitime, et envoie uniquement
car c'est la seule correction. Maintenant, la demande échoue en raison d'un problème de réseau et est soumise de nouveau automatiquement quelques heures plus tard. En attendant, un autre client a (à tort) modifié le zip de l'utilisateur 1. Ensuite, envoyer la même demande PATCH une deuxième fois n'atteint pas l' effet escompté escompté du client, car nous nous retrouvons avec un zip incorrect. Par conséquent, la méthode n'est pas idempotente au sens de la RFC.
Si, à la place, le client utilise une demande PUT pour corriger l'e-mail, envoyant au serveur toutes les propriétés de l'utilisateur 1 avec l'e-mail, l'effet escompté sera atteint même si la demande doit être renvoyée plus tard et l'utilisateur 1 a été modifié en attendant --- puisque la deuxième demande PUT écrasera toutes les modifications depuis la première demande.
la source
À mon humble avis, l'idempotence signifie:
J'envoie une définition de ressource concurrente, donc - l'état de ressource résultant est exactement tel que défini par les paramètres PUT. Chaque fois que je mets à jour la ressource avec les mêmes paramètres PUT - l'état résultant est exactement le même.
Je n'ai envoyé qu'une partie de la définition de la ressource, il peut donc arriver que d'autres utilisateurs mettent à jour les autres paramètres de cette ressource dans l'intervalle. Par conséquent, des correctifs consécutifs avec les mêmes paramètres et leurs valeurs peuvent résulter avec un état de ressource différent. Par exemple:
Supposons un objet défini comme suit:
VOITURE: - couleur: noir, - type: berline, - sièges: 5
Je le patche avec:
{La couleur rouge'}
L'objet résultant est:
VOITURE: - couleur: rouge, - type: berline, - sièges: 5
Ensuite, certains autres utilisateurs corrigent cette voiture avec:
{type: 'hayon'}
ainsi, l'objet résultant est:
VOITURE: - couleur: rouge, - type: hayon, - sièges: 5
Maintenant, si je corrige à nouveau cet objet avec:
{La couleur rouge'}
l'objet résultant est:
VOITURE: - couleur: rouge, - type: hayon, - sièges: 5
Qu'est-ce qui est différent de ce que j'ai précédemment!
C'est pourquoi PATCH n'est pas idempotent tandis que PUT est idempotent.
la source
Pour conclure la discussion sur l'idempotence, je dois noter que l'on peut définir l'idempotence dans le contexte REST de deux manières. Formalisons d'abord quelques éléments:
Une ressource est une fonction dont le domaine de codage est la classe de chaînes. En d'autres termes, une ressource est un sous-ensemble de
String × Any
, où toutes les clés sont uniques. Appelons la classe des ressourcesRes
.Une opération REST sur les ressources, est une fonction
f(x: Res, y: Res): Res
. Voici deux exemples d'opérations REST:PUT(x: Res, y: Res): Res = x
, etPATCH(x: Res, y: Res): Res
, qui fonctionne commePATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}
.(Cette définition est spécifiquement conçue pour discuter de
PUT
etPOST
, et par exemple n'a pas beaucoup de sens surGET
etPOST
, car elle ne se soucie pas de la persistance).Maintenant, en fixant
x: Res
(de manière informelle, en utilisant le curry),PUT(x: Res)
et cePATCH(x: Res)
sont des fonctions univariées de typeRes → Res
.Une fonction
g: Res → Res
est appelée globalement idempotent , lorsqueg ○ g == g
, par exemple pour toutey: Res
,g(g(y)) = g(y)
.Soit
x: Res
une ressource, etk = x.keys
. Une fonctiong = f(x)
est appelée gauche idempotente , alors que pour chacuney: Res
, nous l'avonsg(g(y))|ₖ == g(y)|ₖ
. Cela signifie essentiellement que le résultat devrait être le même, si nous regardons les clés appliquées.Donc,
PATCH(x)
n'est pas globalement idempotent, mais est laissé idempotent. Et l'idempotence gauche est la chose qui importe ici: si nous corrigeons quelques clés de la ressource, nous voulons que ces clés soient les mêmes si nous la corrigeons à nouveau, et nous ne nous soucions pas du reste de la ressource.Et lorsque RFC parle que PATCH n'est pas idempotent, il parle d'idempotence globale. Eh bien, c'est bien que ce ne soit pas globalement idempotent, sinon cela aurait été une opération cassée.
Maintenant, la réponse de Jason Hoetger tente de démontrer que PATCH n'est même pas laissé idempotent, mais il brise trop de choses pour le faire:
t: Set<T> → Map<T, Boolean>
définie avecx in A iff t(A)(x) == True
. En utilisant cette définition, le patch est laissé idempotent.{id: 1, email: "[email protected]"}
doit correspondre avec{email: "[email protected]"}
, sinon le programme est toujours cassé et le PATCH ne peut pas éventuellement pièce). Si l'ID est généré avant vérification par rapport à l'ensemble, le programme est de nouveau interrompu.On peut faire des exemples de PUT non-idempotent avec casser la moitié des choses qui sont cassées dans cet exemple:
PUT /user/12 {email: "[email protected]"}
résulte pour{email: "...", version: 1}
la première fois, et{email: "...", version: 2}
la deuxième fois.Tous les exemples ci-dessus sont des exemples naturels que l'on peut rencontrer.
Mon dernier point est que PATCH ne devrait pas être globalement idempotent , sinon il ne vous donnera pas l'effet souhaité. Vous souhaitez modifier l'adresse e-mail de votre utilisateur, sans toucher au reste des informations, et vous ne voulez pas écraser les modifications d'une autre partie accédant à la même ressource.
la source
Une information supplémentaire que je viens d'ajouter est qu'une demande PATCH utilise moins de bande passante qu'une demande PUT car seule une partie des données est envoyée et non l'entité entière. Il suffit donc d'utiliser une demande PATCH pour les mises à jour d'enregistrements spécifiques comme (1-3 enregistrements) tandis que la demande PUT pour mettre à jour une plus grande quantité de données. Voilà, n'y pensez pas trop et ne vous en faites pas trop.
la source