java - sirve - ¿Cuáles son las mejores prácticas para agregar metadatos a una respuesta RESTful JSON?
qué es una api rest y para qué sirve (3)
Fondo
Estamos construyendo una API reposada que debería devolver objetos de datos como JSON. En la mayoría de los casos, está bien devolver el objeto de datos, pero en algunos casos, f.ex. paginación o validación, necesitamos agregar algunos metadatos a la respuesta.
Lo que tenemos hasta ahora
Hemos envuelto todas las respuestas json como este ejemplo:
{
"metadata" :{
"status": 200|500,
"msg": "Some message here",
"next": "http://api.domain.com/users/10/20"
...
},
"data" :{
"id": 1001,
"name": "Bob"
}
}
Pros
- Podemos agregar metadatos útiles a la respuesta
Contras
- En la mayoría de los casos, no necesitamos el campo de metadatos, y agrega complejidad al formato json
- Dado que ya no es un objeto de datos, sino más bien una respuesta envolvente, no podemos usar la respuesta de inmediato en f.ex backbone.js sin extraer el objeto de datos.
Pregunta
¿Cuáles son las mejores prácticas para agregar metadatos a una respuesta json?
ACTUALIZAR
Lo que tengo hasta ahora de las respuestas a continuación:
- Elimine
metadata.status
su lugar, devuelva el código de respuesta http en el protocolo http (200, 500 ...) - Agregar mensaje de error al cuerpo de una repsonse http 500
- Para la paginación es natural que algunos metadatos me cuenten sobre la estructura de paginación, y los datos anidados en esa estructura
- Se puede agregar una pequeña cantidad de metadatos al encabezado http (X-something)
En la línea del comentario de @ Charlie: para la parte de paginación de su pregunta, todavía necesita hornear los metadatos en la respuesta somhow, pero los atributos de status
y message
aquí son un tanto redundantes, ya que están cubiertos por el protocolo HTTP
sí (estado 200
- modelo encontrado, 404
- modelo no encontrado, 403
- privs insuficientes, se entiende la idea) (ver spec ). Incluso si su servidor devuelve una condición de error, puede enviar la parte del message
como el cuerpo de la respuesta. Estos dos campos cubrirán gran parte de sus necesidades de metadatos.
Personalmente, he tendido hacia (ab) el uso de encabezados HTTP personalizados para piezas más pequeñas de metadatos (con un prefijo X-
), pero creo que el límite donde eso se vuelve poco práctico es bastante bajo.
He expanded un poco sobre esto en una pregunta con un alcance menor, pero creo que los puntos siguen siendo válidos para esta pregunta.
Teníamos el mismo caso de uso, en el que necesitábamos agregar metadatos de paginación a una respuesta JSON. Terminamos creando un tipo de colección en Backbone que podría manejar esta información, y una envoltura liviana en el lado de Rails. Este ejemplo simplemente agrega los metadatos al objeto de colección para referencia de la vista.
Así que creamos una clase Backbone Collection algo como esto
// 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;
}
});
Y luego creamos esta clase simple en el lado de Rails, para emitir los metadatos cuando se paginó con 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
Lo usas en un controlador como este
class ThingsController < ApplicationController
def index
@things = Thing.all.page params[:page]
render :json => PageableCollection.new(@things)
end
end
Disfrutar. Esperamos que te sea útil.
Tiene varios medios para pasar metadatos en una API RESTful:
- Código de estado de Http
- Encabezados
- Cuerpo de respuesta
Para metadata.status, use el código de estado Http, ¡para eso sirve! Si los metadatos se refieren a la respuesta completa, puede agregarlos como campos de encabezado. Si los metadatos se refieren solo a parte de la respuesta, deberá incrustar los metadatos como parte del objeto. NO envuelva toda la respuesta en un sobre artificial y divida el envoltorio en datos y metadatos.
Y, finalmente, sea coherente en su API con las elecciones que haga.
Un buen ejemplo es un GET en una colección completa con paginación. GET / items Puede devolver el tamaño de la colección y la página actual en encabezados personalizados. Y enlaces de paginación en el encabezado de enlace estándar:
Link: <https://api.mydomain.com/v1/items?limit=25&offset=25>; rel=next
El problema con este enfoque es cuando necesita agregar metadatos que hagan referencia a elementos específicos en la respuesta. En ese caso simplemente incrústelo en el objeto mismo. Y para tener un enfoque coherente ... agregue siempre todos los metadatos a la respuesta. De modo que, volviendo a los elementos GET /, imagine que cada elemento ha creado y actualizado metadatos:
{
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"
}
]
}
Tenga en cuenta que una respuesta de recopilación es un caso especial. Si agrega metadatos a una colección, la colección ya no se puede devolver como una matriz, debe ser un objeto con una matriz. ¿Por qué un objeto? porque desea agregar algunos atributos de metadatos.
Compare con los metadatos en los elementos individuales. Nada cerca de envolver a la entidad. Simplemente agrega algunos atributos al recurso.
Una convención es diferenciar campos de control o metadatos. Podría prefijar esos campos con un guión bajo.