Pour inclure un ID de ressource dans la charge utile ou pour dériver de l'URI

13

En concevant une API, nous nous sommes heurtés à la question de savoir si une charge utile PUT doit contenir l'ID de la ressource mise à jour.

Voici ce que nous avons actuellement:

PUT /users/123 Payload: {name: "Adrian"}

Notre code d'itinéraire extrait l'ID de l'URI et continue la mise à jour.

Les premiers utilisateurs de notre API se demandent pourquoi nous n'autorisons pas l'ID dans la charge utile:

PUT /users/123 Payload: {id: 123, name: "Adrian"}

La raison pour laquelle nous ne l'avons pas autorisé est que l'ID est dupliqué, dans la charge utile et l'URI.

En y réfléchissant un peu plus, nous couplons la ressource à l'URI.

Si l'URI n'a pas l'ID, la charge utile devra être modifiée:

PUT /no/id/here Payload: {name: "Adrian"} < What user???

Y a-t-il des raisons de ne pas le faire?

Adrian Lynch
la source

Réponses:

14

Vous êtes censé coupler l'identificateur de ressource uniforme à la ressource .

Lorsque REST est implémenté avec HTTP, vous utilisez GET pour récupérer la valeur actuelle de la ressource et PUT pour définir une nouvelle valeur. Le GET n'a pas de charge utile, la ressource doit donc être identifiée par l'URI. Et le PUT est logiquement effectué sur le même URI et la charge utile doit ressembler exactement à ce que vous voulez que le prochain GET renvoie.

Vous pouvez utiliser POST pour différents URI, mais cela n'aurait que moins de sens car il serait inutilement asymétrique par rapport à GET. Le POST vers l'URI commun ne peut avoir de sens que pour créer de nouvelles ressources ( POST /users/new, charge utile:, {name: "Adrian"}réponse {id: 345, name: "Adrian"}), mais ce n'est pas idempotent et doit donc être évité si vous cherchez REST¹. Au lieu de cela, vous devez réserver l'ID avec un seul appel, puis utiliser PUT pour définir le nouvel ID; qui est tolérant aux pannes, car si la première demande échoue, la réservation d'ID peut éventuellement expirer et PUTest idempotente. Ou utilisez l'UUID généré par le client.


¹ La définition de REST ne dit rien sur l'idempotence, donc je ne peux pas vraiment affirmer que ce n'est pas REST si vous avez des opérations non idempotentes. Cela ne change rien au fait que s'en tenir aux demandes idempotentes rend les choses plus fiables sans les compliquer et est donc recommandé.

Jan Hudec
la source
3
Pour autant que je sache, une demande POST ne doit pas être idempotente. Je ne vois donc aucun problème avec la publication sur /users(pas besoin d'ajouter «nouveau»).
lex82
Merci pour votre réponse. Je vois que c'est une fonctionnalité intéressante d'avoir l'idempotence pour toutes les demandes, mais qui dit que cela est requis pour une API REST? De nombreuses API qui s'appellent RESTful autorisent certainement des requêtes POST non idempotentes (en particulier lorsque le serveur génère des identifiants pour de nouvelles ressources). Mais même si vous appliquez une définition très stricte de REST, je ne vois pas quelle contrainte architecturale est violée avec des POST non idempotents comme l'exemple ci-dessus.
lex82
Je peux certainement imaginer des requêtes POST qui violent une contrainte. Je ne vois tout simplement pas pourquoi publier une ressource dans une collection et laisser le serveur décider de son identifiant est une violation des contraintes REST.
lex82
Continuons cette discussion dans le chat .
lex82
3
L'idée de POST n'est pas d'être idempotente ....
EralpB
2

En y réfléchissant un peu plus, nous couplons la ressource à l'URI.

Si l'URI n'a pas l'ID, la charge utile devra être modifiée:

PUT / no / id / here Charge utile: {nom: "Adrian"} <Quel utilisateur ???

Y a-t-il des raisons de ne pas le faire?

La réponse à cette question dépend de si vous souhaitez autoriser le client à modifier l'ID?

Si le client peut modifier l'ID, via un PUT, l'URI de la ressource changera et vous devez fournir un 301 déplacé de façon permanente à chaque fois qu'une ressource accède à l'ancien URI.

Ainsi, par exemple, vous commencez avec une ressource à

/users/123

et le client met ce qui suit sur la ressource

{id: 222, name: "Adrian"}

la ressource a été mise à jour et son URI est maintenant

/users/222

Le Locationchamp dans la réponse PUT doit contenir le nouvel URI, et si vous allez à /users/123vous devriez obtenir une 301réponse avec le champ Emplacement pointant vers la nouvelle /users/222ressource.

Dans la plupart des cas, vous ne voulez pas que le client puisse changer l'ID, car cela peut devenir assez compliqué assez rapidement. Dans ce cas, l'ID est quelque chose que seul le serveur peut changer, et vous devez le laisser hors du corps PUT, car le client ne peut pas mettre à jour cet état.

Si vous METTRE un besoin à un URI différent sur la même ressource, dites

/users/adian_lync

alors si cette ressource n'existe pas, le serveur doit la créer et créer et ID quand il le fait

Cormac Mulhall
la source
1
La raison pour laquelle nous remettons en question le placement de l'ID dans la charge utile était due au fait que Backbone.js transmettait l'ID dans une demande PUT par défaut. Nous pouvons l'empêcher de se produire, mais maintenant je veux savoir pourquoi c'est le comportement par défaut.
Adrian Lynch
1
Peur que je ne connais pas Backbone.js. Semble inutile si l'ID est également inclus dans l'URL. Peut-être un oubli de la part des développeurs
Cormac Mulhall