web-dev-qa-db-fra.com

REST API - Création ou mise à jour en bloc en une seule requête

Supposons qu'il existe deux ressources Binder et Doc avec une relation d'association, ce qui signifie que les variables Doc et Binder sont autonomes. Doc peut appartenir ou non à Binder et Binder peut être vide.

Si je veux concevoir une API REST qui permet à un utilisateur d'envoyer une collection de Docs, IN A SINGLE REQUEST, comme suit:

{
  "docs": [
    {"doc_number": 1, "binder": 1}, 
    {"doc_number": 5, "binder": 8},
    {"doc_number": 6, "binder": 3}
  ]
}

Et pour chaque doc dans la docs,

  • Si la doc existe, assignez-la à Binder
  • Si le doc n'existe pas, créez-le puis affectez-le

Je suis vraiment confus quant à la façon dont cela devrait être mis en œuvre:

  • Quelle méthode HTTP utiliser?
  • Quel code de réponse doit être renvoyé?
  • Est-ce même qualifié pour le repos?
  • A quoi ressemblerait l'URI? /binders/docs?
  • Traitement des demandes groupées, que se passe-t-il si quelques éléments génèrent une erreur alors que les autres passent? Quel code de réponse doit être renvoyé? L'opération en bloc doit-elle être atomique?
73
norbertpy

Je pense que vous pourriez utiliser une méthode POST ou PATCH pour gérer cela, car ils ont été conçus pour cela.

  • L'utilisation d'une méthode POST est généralement utilisée pour ajouter un élément lorsqu'elle est utilisée sur une ressource de liste, mais vous pouvez également prendre en charge plusieurs actions pour cette méthode. Voir cette réponse: Comment mettre à jour une collection de ressources REST . Vous pouvez également prendre en charge différents formats de représentation pour l'entrée (s'ils correspondent à un tableau ou à un seul élément).

    Dans ce cas, il n'est pas nécessaire de définir votre format pour décrire la mise à jour.

  • L'utilisation d'une méthode PATCH convient également, car les requêtes correspondantes correspondent à une mise à jour partielle. Selon RFC5789 ( http://tools.ietf.org/html/rfc5789 ):

    Plusieurs applications étendant le protocole HTTP (Hypertext Transfer Protocol) nécessitent une fonctionnalité permettant de modifier partiellement les ressources. La méthode HTTP PUT existante ne permet que le remplacement complet d'un document. Cette proposition ajoute une nouvelle méthode HTTP, PATCH, pour modifier une ressource HTTP existante.

    Dans ce cas, vous devez définir votre format pour décrire la mise à jour partielle.

Je pense que dans ce cas, POST et PATCH sont assez similaires car vous n'avez pas vraiment besoin de décrire l'opération à effectuer pour chaque élément. Je dirais que cela dépend du format de la représentation à envoyer.

Le cas de PUT est un peu moins clair. En fait, lorsque vous utilisez une méthode PUT, vous devez fournir la liste complète. En fait, la représentation fournie dans la requête remplacera la ressource de liste.

Vous pouvez avoir deux options concernant les chemins de ressources.

  • Utilisation du chemin de ressource pour la liste de documents

Dans ce cas, vous devez indiquer explicitement le lien de la documentation avec un classeur dans la représentation fournie dans la demande.

Voici un exemple de route pour ce /docs.

Le contenu de cette approche pourrait être pour la méthode POST:

[
    { "doc_number": 1, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 2, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 3, "binder": 5, (other fields in the case of creation) },
    (...)
]
  • Utilisation du chemin de sous-ressource de l'élément de reliure

En outre, vous pouvez également utiliser des sous-routes pour décrire le lien entre la documentation et les classeurs. Les astuces concernant l'association entre un document et un classeur n'ont pas à être spécifiées dans le contenu de la requête.

Voici un exemple de route pour ce /binder/{binderId}/docs. Dans ce cas, l'envoi d'une liste de documents avec une méthode POST ou PATCH l'attachera au classeur avec l'identificateur binderId après avoir créé le document s'il n'existe pas.

Le contenu de cette approche pourrait être pour la méthode POST:

[
    { "doc_number": 1, (other fields in the case of creation) },
    { "doc_number": 2, (other fields in the case of creation) },
    { "doc_number": 3, (other fields in the case of creation) },
    (...)
]

En ce qui concerne la réponse, il vous appartient de définir le niveau de réponse et les erreurs à renvoyer. Je vois deux niveaux: le niveau de statut (niveau global) et le niveau de charge utile (niveau plus fin). C'est également à vous de définir si toutes les insertions/mises à jour correspondant à votre demande doivent être atomiques ou non.

  • Atomique

Dans ce cas, vous pouvez utiliser le statut HTTP. Si tout se passe bien, vous obtenez le statut 200. Sinon, un autre statut comme 400 si les données fournies ne sont pas correctes (par exemple, l'ID du classeur n'est pas valide) ou quelque chose d'autre.

  • Non atomique

Dans ce cas, un statut 200 sera renvoyé et il appartient à la représentation de la réponse de décrire ce qui a été fait et où des erreurs se produisent. ElasticSearch a un noeud final dans son API REST pour la mise à jour en bloc. Cela pourrait vous donner quelques idées à ce niveau: http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/bulk.html .

  • asynchrone

Vous pouvez également implémenter un traitement asynchrone pour gérer les données fournies. Dans ce cas, le statut HTTP renvoyé sera 202. Le client doit extraire une ressource supplémentaire pour voir ce qui se passe.

Avant de terminer, je voudrais également noter que la spécification OData répond au problème des relations entre entités avec la fonctionnalité nommée des liens de navigation . Peut-être pourriez-vous jeter un coup d'œil à ceci ;-)

Le lien suivant peut également vous aider: https://templth.wordpress.com/2014/12/15/designing-a-web-api/ .

J'espère que ça vous aide, Thierry

50
Thierry Templier

Vous devrez probablement utiliser POST ou PATCH, car il est peu probable qu'une seule demande mettant à jour et créant plusieurs ressources soit idempotente.

Faire PATCH /docs est définitivement une option valide. L'utilisation des formats de correctifs standard peut s'avérer délicate pour votre scénario. Pas sûr de ça.

Vous pouvez utiliser 200. Vous pouvez également utiliser 207 - Statut multiple

Cela peut être fait de manière reposante. La clé, à mon avis, est d’avoir une ressource conçue pour accepter un ensemble de documents à mettre à jour/créer.

Si vous utilisez la méthode PATCH, je penserais que votre opération devrait être atomique. c’est-à-dire que je n’utiliserais pas le code de statut 207 pour ensuite signaler les succès et les échecs dans le corps de la réponse. Si vous utilisez l'opération POST, l'approche 207 est viable. Vous devrez concevoir votre propre corps de réponse pour indiquer quelles opérations ont abouti et lesquelles ont échoué. Je ne suis pas au courant d'un standard.

33
Darrel Miller

PUT ing

PUT /binders/{id}/docs Créer ou mettre à jour et associer un seul document à un classeur

par exemple.:

PUT /binders/1/docs HTTP/1.1
{
  "docNumber" : 1
}

PATCH ing

PATCH /docs Créer des documents s'ils n'existent pas et les lier aux classeurs

par exemple.:

PATCH /docs HTTP/1.1
[
    { "op" : "add", "path" : "/binder/1/docs", "value" : { "doc_number" : 1 } },
    { "op" : "add", "path" : "/binder/8/docs", "value" : { "doc_number" : 8 } },
    { "op" : "add", "path" : "/binder/3/docs", "value" : { "doc_number" : 6 } }
] 

J'inclurai d'autres informations plus tard, mais en attendant, jetez un œil à RFC 5789 , RFC 6902 et à William Durand s'il vous plaît. Don. pas patch comme un idiot entrée de blog.

17
Mauricio Morales

Dans un projet auquel j'ai travaillé, nous avons résolu ce problème en implémentant ce que nous appelions des requêtes "batch". Nous avons défini un chemin /batch où nous avons accepté json au format suivant:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 5,
         binder: 8
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      }
   },
]

La réponse a le code de statut 207 (Multi-Status) et ressemble à ceci:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
      status: 200
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         error: {
            msg: 'A document with doc_number 5 already exists'
            ...
         }
      },
      status: 409
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      },
      status: 200
   },
]

Vous pouvez également ajouter un support pour les en-têtes dans cette structure. Nous avons implémenté quelque chose qui s'est avéré utile: les variables à utiliser entre les requêtes d'un lot, ce qui signifie que nous pouvons utiliser la réponse d'une requête comme entrée d'une autre.

Facebook et Google ont des implémentations similaires:
https://developers.google.com/gmail/api/guides/batch
https://developers.facebook.com/docs/graph-api/making-multiple-requests

Lorsque vous souhaitez créer ou mettre à jour une ressource avec le même appel, j'utilise soit POST, soit PUT, selon le cas. Si le document existe déjà, voulez-vous que tout le document soit:

  1. Remplacé par le document que vous avez envoyé (c’est-à-dire que les propriétés manquantes dans la demande seront supprimées et déjà remplacées)?
  2. Fusionné avec le document que vous envoyez (les propriétés manquantes dans la demande ne seront pas supprimées et les propriétés déjà existantes seront écrasées)?

Si vous souhaitez utiliser le comportement de l'alternative 1, vous devez utiliser un POST et si vous souhaitez adopter le comportement de l'alternative 2, vous devez utiliser PUT.

http://restcookbook.com/HTTP%20Methods/put-vs-post/

Comme les gens l'ont déjà suggéré, vous pouvez également opter pour PATCH, mais je préfère garder les API simples et ne pas utiliser de verbes supplémentaires s'ils ne sont pas nécessaires.

9
David Berg