Каковы наилучшие методы добавления метаданных в ответ RESTful JSON?
Фон
Мы создаем Restful API, который должен возвращать объекты данных как JSON. В большинстве случаев это просто, чтобы вернуть объект данных, но в некоторых случаях f.ex. pagination или validation, нам нужно добавить некоторые метаданные в ответ.
Что мы до сих пор
Мы завернули все ответы json, как этот пример:
{
"metadata" :{
"status": 200|500,
"msg": "Some message here",
"next": "http://api.domain.com/users/10/20"
...
},
"data" :{
"id": 1001,
"name": "Bob"
}
}
Pros
- Мы можем добавить полезные ответы на ответ
против
- В большинстве случаев нам не нужно поле метаданных, и это добавляет сложности в формат json
- Поскольку это не объект данных больше, а скорее похож на окутанный ответ, мы не можем сразу использовать ответ в f.ex backbone.js без извлечения объекта данных.
Вопрос
Каковы наилучшие методы добавления метаданных в ответ json?
UPDATE
Что у меня осталось от ответов ниже:
- Удалите
metadata.status
, чтобы вернуть код ответа http в
http protocol (200, 500...)
- Добавить ошибку msg в тело ответа 500 500
- Для pagination я естественно иметь некоторые метаданные, рассказывающие о структуре разбиения на страницы, и данные, вложенные в эту структуру
- Небольшое количество метаданных может быть добавлено в заголовок http (X-something)
Ответы
Ответ 1
У вас есть несколько способов передать метаданные в RESTful API:
- Код состояния Http
- Заголовки
- Тело ответа
Для metadata.status используйте код состояния Http, для чего!
Если метаданные относятся ко всему ответу, вы можете добавить его как поля заголовка.
Если метаданные относятся только к части ответа, вам нужно будет внедрить метаданные как часть объекта. НЕ НЕ обернуть весь ответ в искусственном конверте и разделить обертку на данные и метаданные.
И, наконец, будет согласован в вашем API с вашими вариантами.
Хорошим примером является GET для целой коллекции с разбиением на страницы. GET/items
Вы можете вернуть размер коллекции и текущую страницу в пользовательские заголовки. И ссылки на страницы в стандартном заголовке заголовка:
Link: <https://api.mydomain.com/v1/items?limit=25&offset=25>; rel=next
Проблема с этим подходом заключается в том, когда вам нужно добавить метаданные, ссылающиеся на определенные элементы в ответе. В этом случае просто вставьте его в сам объект. И иметь последовательный подход... добавлять всегда все метаданные к ответу. Поэтому, возвращаясь к GET/items, представьте, что каждый элемент создал и обновил метаданные:
{
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"
}
]
}
Обратите внимание, что ответ коллекции является частным случаем. Если вы добавляете метаданные в коллекцию, сбор больше не может быть возвращен как массив, это должен быть объект с массивом в нем. Почему объект? потому что вы хотите добавить некоторые атрибуты метаданных.
Сравните с метаданными в отдельных элементах. Ничто не близко к обертыванию объекта. Вы просто добавляете некоторые атрибуты в ресурс.
Одно из условий - отличать поля управления или метаданных. Вы можете префикс этих полей с подчеркиванием.
Ответ 2
Вдоль строк комментария @Charlie: для фрагментации вашего вопроса вам все равно нужно выпекать метаданные в ответ так или иначе, но атрибуты status
и message
здесь несколько избыточны, так как они уже покрыты по самому протоколу HTTP
(статус 200
- найденная модель, 404
- модель не найдена, 403
- недостаточно привилегий, вы получаете эту идею) (см. spec). Даже если ваш сервер возвращает условие ошибки, вы все равно можете отправить часть message
в качестве тела ответа. Эти два поля будут покрывать большую часть ваших потребностей в метаданных.
Лично я склонялся к (ab) с помощью пользовательских заголовков HTTP для небольших фрагментов метаданных (с префиксом X-
), но я предполагаю, что предел, где это становится непрактичным, довольно низок.
несколько расширился в вопросе с меньшим объемом, но я думаю, что пункты по-прежнему актуальны для этого вопроса.
Ответ 3
У нас был тот же вариант использования, в котором нам нужно было добавить метаданные разбиения на страницы на ответ JSON. Мы закончили создание типа коллекции в Backbone, который мог бы обрабатывать эти данные, и легкую обертку на стороне Rails. Этот пример просто добавляет метаданные в объект коллекции для ссылки в представлении.
Итак, мы создали класс Backbone Collection что-то вроде этого
// 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;
}
});
И затем мы создали этот простой класс на стороне Rails, чтобы генерировать метаданные при разбиении на страницы с 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
Вы используете его в таком контроллере
class ThingsController < ApplicationController
def index
@things = Thing.all.page params[:page]
render :json => PageableCollection.new(@things)
end
end
Enjoy. Надеюсь, вы сочтете это полезным.