API RESTful. Devrais-je retourner l'objet créé / mis à jour?

36

Je conçois un service Web RESTful utilisant WebApi et je me demandais quelles réponses HTTP et quels corps de réponse renvoyer lors de la mise à jour / de la création d'objets.

Par exemple, je peux utiliser la méthode POST pour envoyer du JSON au service Web, puis créer un objet. Est-il recommandé de définir ensuite le statut HTTP sur créé (201) ou ok (200) et de simplement renvoyer un message tel que "Nouvel employé ajouté" ou de renvoyer l'objet envoyé à l'origine?

Il en va de même pour la méthode PUT. Quel est le meilleur statut HTTP à utiliser et dois-je renvoyer l'objet créé ou juste un message? Considérant le fait que l'utilisateur sait quel objet il tente de créer / mettre à jour de toute façon.

Des pensées?

Exemple:

Ajouter un nouvel employé:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

Mettre à jour un employé existant:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Réponses:

Réponse avec objet créé / mis à jour

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Réponse avec juste un message:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

Réponse avec juste le code de statut:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT
Iswinky
la source
2
Bonne question, mais utiliser le terme "meilleure pratique" est en quelque sorte un tabou sur ce site meta.programmers.stackexchange.com/questions/2442/… Vous pourriez simplement reformuler la question. meta.programmers.stackexchange.com/questions/6967/…
Snoop
3
Pour faire un peu de suivi, serait-il judicieux d’avoir un indicateur dans la demande afin que (par exemple) une application mobile puisse obtenir l'objet entier renvoyé lorsqu'il est en WiFi, mais uniquement l'identifiant lors de l'utilisation de données cellulaires? Existe-t-il un en-tête à utiliser pour éviter de polluer le JSON?
Andrew dit Réintégrer Monica
@AndrewPiliser Idée intéressante, même si je pense personnellement qu'il est préférable de choisir une approche et de s'y tenir. Puis, au fur et à mesure que votre application se développe ou
gagne en
@AndrewPiliser votre idée est très similaire à la UPDATE/INSERT ... RETURNINGvariante Postgresql pour SQL. C'est extrêmement pratique, en particulier parce qu'il conserve la soumission de nouvelles données et demande la version mise à jour atomique.
Beldaz

Réponses:

31

Comme avec la plupart des choses, cela dépend. Votre compromis est la facilité d'utilisation par rapport à la taille du réseau. Il peut être très utile pour les clients de voir la ressource créée. Il peut inclure des champs renseignés par le serveur, tels que last-creation-time. Comme vous semblez inclure le idau lieu de l’utiliser hateoas, les clients voudront probablement voir l’identifiant de la ressource qu’ils viennent d’ POSTéditer.

Si vous n'incluez pas la ressource créée, veuillez ne pas créer de message arbitraire. Les champs 2xx et Emplacement fournissent suffisamment d'informations pour que les clients puissent être sûrs que leur demande a été traitée correctement.

Eric Stein
la source
+1 L'objectif hateoas de ne pas laisser le client composer les uri peut également être atteint en permettant au client de remplir les modèles d'URL fournis par le serveur avec des identifiants spécifiques. Oui, le client "compose", mais uniquement de manière "à remplir les blancs". Bien que ce ne soit pas pur HATEOAS, il permet d’atteindre l’objectif et rend le travail avec des objets qui ont un (grand) nombre d’actions "action" un peu moins sensible à la bande passante, sans parler du fait que ces objets sont placés dans une liste (plus longue).
Marjan Venema
3
+1 sur l'avis "s'il vous plaît, ne créez pas de message arbitraire"
HairOfTheDog
Le "pas de message arbitraire" se concentre-t-il sur les messages de chaîne ou sur toute valeur de retour qui n'est pas la ressource créée? Je me concentre sur les cas dans lesquels nous renvoyons l'identifiant de la ressource créée (mais pas la ressource elle-même) et nous nous demandions où cela s'inscrit.
Flater
12

Personnellement, je reviens toujours uniquement 200 OK.

Pour citer votre question

Considérant le fait que l'utilisateur sait quel objet il tente de créer / mettre à jour de toute façon.

Pourquoi ajouter de la bande passante supplémentaire (qui pourrait être payante) pour dire au client ce qu'il sait déjà?

Mawg
la source
1
C'est ce que je pensais. S'il est invalide, vous pouvez renvoyer des messages de validation, mais s'il est valide et qu'il est créé / mis à jour, vérifiez le code de statut HTTP et montrez à l'utilisateur un message, par exemple "Hourra", basé sur cela
iswinky
3
Voir stackoverflow.com/a/827045/290182 en ce qui concerne 200/ 204 No Contentpour éviter de confondre jQuery et autres.
Beldaz
10

@ iswinky Je renvoie toujours la charge utile en cas de POST et de PUT.

Dans le cas du POST, vous pouvez créer une entité avec un ID interne ou un UUID. Il est donc logique de renvoyer la charge utile.

De même, dans le cas de PUT, vous pouvez ignorer certains champs de l'utilisateur (des valeurs immuables, par exemple) ou, dans le cas d'un PATCH, les données peuvent également avoir été modifiées par d'autres utilisateurs.

L'envoi des données en tant que données persistantes est toujours une bonne idée et ne nuit définitivement pas. Si l'appelant n'a pas besoin des données retournées, il ne les traitera pas mais utilisera simplement le code de statut. Sinon, ils peuvent utiliser ces données pour mettre à jour l'interface utilisateur.

Ce n'est que dans le cas d'un DELETE que je ne renverrais pas la charge utile et que je ferais un 200 avec un contenu de réponse ou un 204 sans contenu de réponse.

Edit: Merci à quelques commentaires ci-dessous, je reformule ma réponse. Je continue de concevoir les API et de renvoyer les réponses, mais je pense qu'il est logique de qualifier certaines de mes idées de conception.

a) Lorsque je dis à renvoyer la charge utile, je voulais en fait dire à renvoyer les données de la ressource, pas la même charge utile fournie dans la demande. Ex: si vous envoyez une charge de création, je peux créer dans le backend d'autres entités telles que UUID et (peut-être) des horodatages et même certaines connexions (graphiques). Je renverrais tout cela dans la réponse (pas seulement la charge de la requête telle quelle - ce qui est inutile).

b) Je ne renverrais pas les réponses au cas où la charge utile est très grande. J'en ai déjà parlé dans les commentaires, mais ce que je tiens à préciser, c'est que je ferais de mon mieux pour concevoir mes API ou mes ressources de manière à ce que les charges utiles ne soient pas très grandes. J'essayerais de diviser les ressources en entités plus petites et gérables de telle sorte que chaque ressource soit définie par 15 à 20 attributs JSON et non plus grande.

Si l'objet est très volumineux ou si l'objet parent est mis à jour, je vous renverrais les structures imbriquées sous forme de hrefs.

En bout de ligne, je voudrais certainement essayer de renvoyer les données qui ont un sens pour que le consommateur / l'interface utilisateur traite immédiatement et se fassent avec une action d'API atomique plutôt que d'avoir à aller chercher 2 à 5 autres API simplement pour mettre à jour l'interface utilisateur après la création / mise à jour des données.

Les API de serveur à serveur peuvent penser différemment à ce sujet. Je me concentre sur les API qui génèrent une expérience utilisateur.

Ksprashu
la source
Je peux voir de nombreuses situations dans lesquelles renvoyer la totalité de la charge utile est une mauvaise idée, lorsque la charge utile est importante.
Beldaz
2
@beldaz complètement d'accord. YMMV basé sur la conception de l'API REST. J'évite généralement les très gros objets et les décompose en une série de sous-ressources / PUT. Si la charge utile est très grande, il existe de meilleures façons de le faire, et vous voudrez faire HATEOAS (comme Marjan l’a dit plus haut) dans lequel vous renvoyez la référence à l’objet au lieu de l’objet lui-même.
ksprashu
@ksprashu: "Il est donc logique de renvoyer la charge utile" - Je trouve que c'est une mauvaise idée, car une ressource peut être obtenue de différentes manières: via GET, en réponse à POST, en réponse à PUT. Cela signifie que le client obtient 3 ressources avec une structure potentiellement différente . Où comme si vous retourniez uniquement l'URI (emplacement), sans corps, le seul moyen d'obtenir une ressource serait alors GET. Cela ferait en sorte que le client obtienne toujours des réponses cohérentes.
Mentallurg
@mentallurg Je réalise que je n'ai peut-être pas formulé ce droit. Merci de l'avoir signalé. J'ai édité ma réponse. Faites-moi savoir si cela a plus de sens.
ksprashu
Tant que vous implémentez un service pour votre travail à domicile, peu importe. Fais-le comme tu veux. Économisez votre temps.
mentallurg
9

En vous référant aux normes RFC de lien , vous devez renvoyer le statut 201 (créé) lors du stockage correct de la ressource de demande à l'aide de Post. Dans la plupart des applications, l'identifiant de la ressource est généré par le serveur lui-même. Il est donc recommandé de renvoyer l'identifiant de la ressource créée. Renvoyer l'intégralité de l'objet est la surcharge de la requête Post. La pratique idéale est de renvoyer l’adresse URL de la ressource nouvellement créée.

Par exemple, vous pouvez vous reporter à l'exemple suivant qui enregistre l'objet Employé dans la base de données et renvoie l'URL du nouvel objet ressource créé en réponse.

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

Ce noeud final restituera la réponse sous la forme:

Statut 201 - CRÉÉ

Header Location → http: // localhost: 8080 / employés / 1

Shubham Bondarde
la source
Sympa et propre - je vais suivre ça maintenant et au
Hassan Tareq
0

Je ferais une charge utile dans le corps de retour conditionnel à un paramètre HTTP.

Plus souvent qu'autrement, il est préférable de renvoyer une sorte de contenu au consommateur d'API pour éviter les allers-retours inutiles (une des raisons de l'existence de GraphQL.)

En fait, au fur et à mesure que nos applications utilisent de plus en plus de données et plus de distribution, j’essaie de respecter ces consignes:

Ma ligne directrice :

Chaque fois qu'un cas d'utilisation exige un GET immédiatement après un POST ou un PUT, il est préférable de simplement renvoyer quelque chose dans la réponse POST / PUT.

Comment cela est fait, et quel type de contenu renvoyer hors d'un PUT ou d'un POST, c'est spécifique à l'application. Maintenant, il serait intéressant que l'application puisse paramétrer le type de "contenu" dans le corps de la réponse (voulons-nous seulement l'emplacement du nouvel objet, de certains champs ou de l'objet entier, etc.)

Une application peut définir un ensemble de paramètres qu'un POST / PUT peut recevoir pour contrôler le type de "contenu" à renvoyer dans le corps de la réponse. Ou bien, il pourrait coder une sorte de requête GraphQL en tant que paramètre (je peux voir cela utile mais aussi devenir un cauchemar de maintenance.)

De toute façon, il me semble que:

  1. Il est correct (et probablement souhaitable) de renvoyer quelque chose dans un corps de réponse POST / PUT.
  2. Comment cela est fait est spécifique à l'application et presque impossible à généraliser.
  3. Vous ne voulez pas renvoyer de "contexte" de grande taille par défaut (bruit de la circulation qui élimine toute la raison de l’abandon de la méthode POST-suivie-par-GET).

Alors, 1) fais-le, mais 2) reste simple.

Une autre option que j'ai vue consiste à créer des points de terminaison alternatifs (par exemple, / customers pour POST / PUT qui ne renvoie rien dans le corps et / customer_with_details pour POST / PUT à / clients, mais qui renvoie quelque chose dans le corps de la réponse.)

Je voudrais éviter cette approche, cependant. Que se passe-t-il lorsque vous devez légitimement renvoyer un type de contenu différent? Un point de terminaison par type de contenu? Non évolutif ou maintenable.

luis.espinal
la source