web-dev-qa-db-fra.com

Quelles sont les meilleures pratiques pour ajouter des métadonnées à une réponse JSON RESTful?

Contexte

Nous construisons une API reposante qui devrait renvoyer des objets de données au format JSON. Dans la plupart des cas, il suffit de renvoyer l'objet de données, mais dans certains cas, f.ex. pagination ou validation, nous devons ajouter des métadonnées à la réponse.

Ce que nous avons jusqu'à présent

Nous avons enveloppé toutes les réponses Json comme dans cet exemple:

{
    "metadata" :{
        "status": 200|500,
        "msg": "Some message here",
        "next": "http://api.domain.com/users/10/20"
        ...
    },
    "data" :{
        "id": 1001,
        "name": "Bob"
    }
}

Avantages

  • Nous pouvons ajouter des métadonnées utiles à la réponse

Les inconvénients

  • Dans la plupart des cas, nous n'avons pas besoin du champ de métadonnées, et cela ajoute de la complexité au format json
  • Comme ce n'est plus un objet de données, mais plutôt une réponse enveloppée, nous ne pouvons pas utiliser la réponse tout de suite dans f.ex backbone.js sans extraire l'objet de données.

Question

Quelles sont les meilleures pratiques pour ajouter des métadonnées à une réponse json?

METTRE À JOUR

Ce que j'ai si loin des réponses ci-dessous:

  • Supprimez le metadata.status et retournez le code de réponse http dans le protocole Http à la place (200, 500 ...)
  • Ajouter un message d'erreur au corps d'une réponse HTTP 500 
  • Pour la pagination, il est naturel de disposer de métadonnées indiquant la structure de la pagination et des données imbriquées dans cette structure.
  • Une petite quantité de métadonnées peut être ajoutée à l'en-tête http (X-quelque chose)
36
user920041

Vous avez plusieurs moyens de transmettre des métadonnées dans une API RESTful:

  1. Code de statut Http
  2. En-têtes
  3. Corps de réponse

Pour le fichier metadata.status, utilisez le code d’état Http, c’est le but de! Si métadonnées fait référence à la réponse entière, vous pouvez l’ajouter sous forme de champs d’en-tête . Si les métadonnées ne font référence qu’à une partie de la réponse, vous doivent incorporer les métadonnées dans l’objet. NE PAS FAIRE envelopper toute la réponse dans une enveloppe artificielle et diviser l’enveloppe en données et métadonnées.

Et enfin, soyez cohérent dans votre API avec les choix que vous faites.

Un bon exemple est un GET sur toute une collection avec pagination. GET /itemsVous pouvez renvoyer la taille de la collection et la page en cours dans des en-têtes personnalisés. Et des liens de pagination en en-tête de lien standard:

Link: <https://api.mydomain.com/v1/items?limit=25&offset=25>; rel=next

Le problème avec cette approche est lorsque vous devez ajouter des métadonnées référençant des éléments spécifiques dans la réponse. Dans ce cas, il suffit de l'intégrer à l'objet lui-même. Et pour avoir une approche cohérente ... ajoutez toujours toutes les métadonnées à la réponse. Pour revenir à l'élément GET/items, imaginons que chaque élément a créé et mis à jour des métadonnées:

{
  items:[
    {
      "id":"w67e87898dnkwu4752igd",
      "message" : "some content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    },
    ......
    {
      "id":"asjdfiu3748hiuqdh",
      "message" : "some other content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    }
  ],
  "_total" :133,
  "_links" :[
     {
        "next" :{
           href : "https://api.mydomain.com/v1/items?limit=25&offset=25"
         } 
   ]
}

Notez qu'une réponse à la collecte est un cas particulier. Si vous ajoutez des métadonnées à une collection, celle-ci ne peut plus être renvoyée sous forme de tableau. Il doit s'agir d'un objet contenant un tableau. Pourquoi un objet? parce que vous voulez ajouter des attributs de métadonnées.

Comparez avec les métadonnées dans les éléments individuels. Rien n'est proche d'envelopper l'entité. Vous ajoutez juste quelques attributs à la ressource.

Une convention consiste à différencier les champs de contrôle ou de métadonnées. Vous pouvez préfixer ces champs avec un trait de soulignement.

9
Daniel Cerecedo

Dans le sens du commentaire de @ Charlie: pour la partie relative à la pagination de votre question, vous devez toujours intégrer les métadonnées dans la réponse, mais les attributs status et message sont quelque peu redondants, car ils sont déjà couverts par le protocole HTTP200 - modèle trouvé, 404 - modèle non trouvé, 403 - nombre de privilèges insuffisant, vous avez une idée) (voir spec ). Même si votre serveur renvoie une condition d'erreur, vous pouvez toujours envoyer la partie message en tant que corps de la réponse. Ces deux champs couvriront une grande partie de vos besoins en métadonnées.

Personnellement, j'ai tendance à utiliser (ab) des en-têtes HTTP personnalisés pour des métadonnées plus petites (avec un préfixe X-), mais j'imagine que la limite lorsque cela devient impraticable est assez basse.

J'ai développé un peu à ce sujet dans une question avec une portée plus petite, mais je pense que les points sont toujours valables pour cette question.

4
Jacob Oscarson

Nous avions le même cas d'utilisation, dans lequel nous devions ajouter des métadonnées de pagination à une réponse JSON. Nous avons fini par créer un type de collection dans Backbone capable de gérer ces données, ainsi qu'un wrapper léger côté Rails. Cet exemple ajoute simplement les métadonnées à l'objet collection pour référence par la vue.

Nous avons donc créé une classe Backbone Collection qui ressemble à ceci

// Example response:
// { num_pages: 4, limit_value: 25, current_page: 1, total_count: 97
//   records: [{...}, {...}] }

PageableCollection = Backbone.Collection.extend({
  parse: function(resp, xhr)  {
    this.numPages = resp.num_pages;
    this.limitValue = resp.limit_value;
    this.currentPage = resp.current_page;
    this.totalCount = resp.total_count;
    return resp.records;
  }  
});

Et puis nous avons créé cette classe simple du côté de Rails, pour émettre les métadonnées quand paginé avec Kaminari

class PageableCollection
  def initialize (collection)
    @collection = collection
  end
  def as_json(opts = {})
    {
      :num_pages => @collection.num_pages 
      :limit_value => @collection.limit_value 
      :current_page => @collection.current_page,
      :total_count => @collection.total_count
      :records => @collection.to_a.as_json(opts)
    }
  end
end

Vous l'utilisez dans un contrôleur comme celui-ci

class ThingsController < ApplicationController
  def index 
    @things = Thing.all.page params[:page]
    render :json => PageableCollection.new(@things)
  end
end

Prendre plaisir. J'espère que vous le trouverez utile.

0
maxl0rd