Utilisation des méthodes PUT vs PATCH dans les scénarios réels de l'API REST

682

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 /usersavec 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/1reviendra {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/1avec "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?
Dmitry Kudryavtsev
la source
1
a) c'est RFC 2616, pas 2612. b) RFC 2616 est obsolète, la spécification actuelle de PUT est dans greenbytes.de/tech/webdav/rfc7231.html#PUT , c) je n'ai pas votre question; n'est-il pas assez évident que PUT peut être utilisé pour remplacer n'importe quelle ressource, pas seulement une collection, d) avant l'introduction de PATCH, les gens utilisaient généralement POST, e) enfin, oui, une demande de PATCH spécifique (selon le format du patch) peut être idempotent; c'est juste que ce n'est pas généralement.
Julian Reschke
si ça aide j'ai écrit un article sur le PATCH vs PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
equivalent8
5
Simple: POST crée un élément dans une collection. PUT remplace un élément. PATCH modifie un élément. Lors du POST, l'URL du nouvel élément est calculée et renvoyée dans la réponse, tandis que PUT et PATCH nécessitent une URL dans la demande. Droite?
Tom Russell
Ce message peut être utile: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Réponses:

943

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:

  1. Vous faites référence à une entité, pas à une collection.

  2. L'entité que vous fournissez est complète (l' entité entière ).

Regardons l'un de vos exemples.

{ "username": "skwee357", "email": "[email protected]" }

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": "[email protected]"
}

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": "[email protected]"       // new email address
}

Vous pouvez accomplir la même chose en utilisant PATCH. Cela pourrait ressembler à ceci:

PATCH /users/1
{
    "email": "[email protected]"       // 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": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // 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": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // 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": "[email protected]",
  "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": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // 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": "[email protected]",  // 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": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "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.

Dan Lowe
la source
2
Cette phrase n'est pas tout à fait correcte: "Mais elle est idempotente: chaque fois que A entre, B sort toujours". Par exemple, si vous deviez GET /users/1avant que le bureau de poste GET /users/1ne 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.
Jason Hoetger
@JasonHoetger GET est sûr (présumé ne provoquer aucun changement), mais n'est pas toujours idempotent. Il existe une différence. Voir RFC 2616 sec. 9.1 .
Dan Lowe
1
@DanLowe: GET est certainement garanti idempotent. Il indique exactement que dans la section 9.1.2 de la RFC 2616 et dans la spécification mise à jour, la RFC 7231 section 4.2.2 , que "des méthodes de demande définies par cette spécification, PUT, DELETE et les méthodes de demande sécurisées sont idempotentes". L'idempotence ne signifie tout simplement pas "vous obtenez la même réponse à chaque fois que vous faites la même demande". 7231 4.2.2 poursuit: "La répétition de la demande aura le même effet prévu, même si la demande d'origine a réussi, bien que la réponse puisse différer. "
Jason Hoetger
1
@JasonHoetger Je le concède, mais je ne vois pas ce que cela a à voir avec cette réponse, qui a discuté de PUT et PATCH et ne mentionne même jamais GET ...
Dan Lowe
1
Ah, le commentaire de @JasonHoetger l'a éclairci: seuls les états résultants, plutôt que les réponses, de plusieurs requêtes de méthode idempotente doivent être identiques.
Tom Russell
329

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 ):

Le terme idempotent est utilisé de manière plus complète pour décrire une opération qui produira les mêmes résultats si elle est exécutée une ou plusieurs fois [...] Une fonction idempotente est une fonction qui a la propriété f (f (x)) = f (x) pour toute valeur x.

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 /usersrenvoie une liste d'utilisateurs, actuellement:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" }]

Plutôt que PATCHing / users / {id}, comme dans l'exemple de l'OP, supposons que le serveur autorise PATCHing / users. Émettons cette demande PATCH:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "[email protected]" }]

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 /usersreviendrait:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" }]

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 /usersretourne:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" },
 { "id": 3, "username": "newuser", "email": "[email protected]" }]

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:

Une demande PATCH peut être émise de manière à être idempotente, ce qui permet également d'éviter les mauvais résultats des collisions entre deux demandes PATCH sur la même ressource dans un délai similaire.

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 garantie f(g(f(x))).

Jason Hoetger
la source
11
En supposant que le serveur autorise également l'émission de PUT at /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.
Uzair Sajid
13
Donc, nous ne pouvions construire une API qu'avec des opérations PATCH. Alors, quel est le principe REST d'utiliser http VERBS pour effectuer des actions CRUD sur les ressources? Ne sommes-nous pas en train de trop complexifier les frontières de PATCH messieurs ici?
bohr
6
Si PUT est implémenté sur une collection (par exemple /users), toute demande PUT doit remplacer le contenu de cette collection. Un PUT /usersdoit 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/emailspeut être une collection et il peut être parfaitement valide de permettre de remplacer la collection entière par une nouvelle.
Vectorjohn
5
Bien que cette réponse fournisse un excellent exemple d'idempotence, je pense que cela peut brouiller les eaux dans des scénarios REST typiques. Dans ce cas, vous avez une demande PATCH avec une opaction 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 le opchamp déclenche des workflows côté serveur. Dans des scénarios REST plus simples, ce type de opfonctionnalité est une mauvaise pratique et devrait probablement être géré directement via des verbes HTTP.
ivandov
7
Je n'envisagerais jamais d'émettre un PATCH, seulement POST et DELETE, contre une collection. Est-ce vraiment jamais fait? PATCH peut-il donc être considéré comme idempotent à toutes fins pratiques?
Tom Russell
72

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

Le HTTP RFC spécifie que PUT doit prendre une nouvelle représentation de ressource complète comme entité de demande. Cela signifie que si, par exemple, seuls certains attributs sont fournis, ceux-ci doivent être supprimés (c'est-à-dire définis sur null).

Étant donné que, alors un PUT devrait envoyer l'objet entier. Par exemple,

/users/1
PUT {id: 1, username: 'skwee357', email: '[email protected]'}

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.

/users/1
PUT {id: 1, email: '[email protected]'}

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.

{id: 1, username: null, email: '[email protected]'}

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/

La différence entre les demandes PUT et PATCH se reflète dans la façon dont le serveur traite l'entité incluse pour modifier la ressource identifiée par l'URI de demande. Dans une demande PUT, l'entité incluse est considérée comme une version modifiée de la ressource stockée sur le serveur d'origine et le client demande que la version stockée soit remplacée. Avec PATCH, cependant, l'entité jointe contient un ensemble d'instructions décrivant comment une ressource résidant actuellement sur le serveur d'origine doit être modifiée pour produire une nouvelle version. La méthode PATCH affecte la ressource identifiée par l'URI de demande, et elle PEUT également avoir des effets secondaires sur d'autres ressources; c'est-à-dire que de nouvelles ressources peuvent être créées ou des ressources existantes modifiées par l'application d'un PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "[email protected]" }
]

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.

Il convient de mentionner que PATCH n'est pas vraiment conçu pour les API véritablement REST, car la dissertation de Fielding ne définit aucun moyen de modifier partiellement les ressources. Mais, Roy Fielding lui-même a dit que PATCH était quelque chose [qu'il] avait créé pour la proposition HTTP / 1.1 initiale car le PUT partiel n'est jamais RESTful. Bien sûr, vous ne transférez pas une représentation complète, mais REST n'exige pas que les représentations soient complètes de toute façon.

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.

Kalel Wade
la source
7
Il peut être utile d'ajouter: dans l'article de William Durand (et rfc 6902), il existe des exemples où "op" est "add". Ce n'est évidemment pas idempotent.
Johannes Brodwall
2
Ou vous pouvez simplifier et utiliser le correctif de fusion RFC 7396 à la place et éviter de créer le correctif JSON.
Piotr Kula
pour les tables nosql, les différences entre patch et put sont importantes, car nosql n'ont pas de colonnes
stackdave
18

La différence entre PUT et PATCH est que:

  1. PUT doit être idempotent. Pour ce faire, vous devez placer l'intégralité de la ressource complète dans le corps de la demande.
  2. PATCH peut être non idempotent. Ce qui implique qu'il peut également être idempotent dans certains cas, tels que les cas que vous avez décrits.

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:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "[email protected]"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "address": "123 main street",
}

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:

  1. L'existence d'un champ signifie «remplacer» ou «ajouter» ce champ.
  2. Si la valeur d'un champ est nulle, cela signifie supprimer ce champ.

Avec la convention ci-dessus, le PATCH dans l'exemple peut prendre la forme suivante:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "[email protected]",
  "zip":
}

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.

Bin Ni
la source
7

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).

Bijan
la source
3

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:

Une méthode de requête est considérée comme "idempotente" si l'effet prévu sur le serveur de plusieurs requêtes identiques avec cette méthode est le même que l'effet pour une seule requête de ce type. Parmi les méthodes de demande définies par cette spécification, PUT, DELETE et les méthodes de demande sécurisées sont idempotentes.

(...)

Les méthodes idempotentes sont distinguées car la demande peut être répétée automatiquement si une défaillance de communication se produit avant que le client ne puisse lire la réponse du serveur. Par exemple, si un client envoie une demande PUT et que la connexion sous-jacente est fermée avant toute réponse, le client peut établir une nouvelle connexion et réessayer la demande idempotente. Il sait que la répétition de la demande aura le même effet souhaité, même si la demande d'origine a réussi, bien que la réponse puisse différer.

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

PATCH /users/1
{"email": "[email protected]"}

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.

Rolvernew
la source
2

À mon humble avis, l'idempotence signifie:

  • METTRE:

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.

  • PIÈCE:

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.

Zbigniew Szczęsny
la source
1

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 ressources Res.

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, et
  • PATCH(x: Res, y: Res): Res, qui fonctionne comme PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Cette définition est spécifiquement conçue pour discuter de PUTet POST, et par exemple n'a pas beaucoup de sens sur GETet POST, 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 ce PATCH(x: Res)sont des fonctions univariées de type Res → Res.

  1. Une fonction g: Res → Resest appelée globalement idempotent , lorsque g ○ g == g, par exemple pour toute y: Res, g(g(y)) = g(y).

  2. Soit x: Resune ressource, et k = x.keys. Une fonction g = f(x)est appelée gauche idempotente , alors que pour chacune y: Res, nous l'avons g(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:

  • Tout d'abord, PATCH est utilisé sur un ensemble, bien que PATCH soit défini pour fonctionner sur des cartes / dictionnaires / objets valeur-clé.
  • Si quelqu'un veut vraiment appliquer PATCH à des ensembles, alors il y a une traduction naturelle qui devrait être utilisée:, t: Set<T> → Map<T, Boolean>définie avec x in A iff t(A)(x) == True. En utilisant cette définition, le patch est laissé idempotent.
  • Dans l'exemple, cette traduction n'a pas été utilisée, à la place, le PATCH fonctionne comme un POST. Tout d'abord, pourquoi un ID est-il généré pour l'objet? Et quand est-il généré? Si l'objet est d'abord comparé aux éléments de l'ensemble, et si aucun objet correspondant n'est trouvé, l'ID est généré, puis à nouveau le programme devrait fonctionner différemment ( {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:

  • Un exemple avec des fonctionnalités supplémentaires générées serait la gestion des versions. On peut garder une trace du nombre de changements sur un seul objet. Dans ce cas, PUT n'est pas idempotent: PUT /user/12 {email: "[email protected]"}résulte pour {email: "...", version: 1}la première fois, et{email: "...", version: 2} la deuxième fois.
  • En jouant avec les ID, on peut générer un nouvel ID chaque fois que l'objet est mis à jour, ce qui entraîne un PUT non idempotent.

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.

Mohammad-Ali A'RÂBI
la source
-1

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.

Benjamin
la source