Какой наилучший метод RESTful возвращает общее количество элементов в объекте?

StackOverflow https://stackoverflow.com/questions/3715981

  •  02-10-2019
  •  | 
  •  

Вопрос

Я разрабатываю сервис REST API для крупной социальной сети, в которой я участвую.Пока что все работает отлично.Я могу выдать GET, POST, PUT и DELETE запросы на объектные URL-адреса влияют на мои данные.Однако эти данные распределяются по страницам (не более 30 результатов одновременно).

Однако, каков был бы наилучший RESTful способ получить общее количество, скажем, участников, через мой API?

В настоящее время я отправляю запросы к структуре URL, подобной следующей:

  • /api/участники—Возвращает список участников (30 одновременно, как указано выше)
  • /api/участники/1— Влияет на одного участника, в зависимости от используемого метода запроса

Мой вопрос заключается в следующем:как бы я тогда использовал аналогичную структуру URL-адресов, чтобы получить общее количество участников в моем приложении?Очевидно, запрашивая только id поле (аналогично Facebook Graph API) и подсчет результатов были бы неэффективны, если бы был возвращен только фрагмент из 30 результатов.

Это было полезно?

Решение

Хотя ответ на /API/users разбивается на страницы и возвращает только 30 записей, ничто не мешает вам включить в ответ также общее количество записей и другую соответствующую информацию, такую как размер страницы, номер страницы / смещение и т.д.

API StackOverflow является хорошим примером такого же дизайна.Вот документация для метода Users - https://api.stackexchange.com/docs/users

Другие советы

Я предпочитаю использовать HTTP-заголовки для такого рода контекстуальной информации.

Для общего количества элементов я использую X-total-count заголовок.
Для ссылок на следующую, предыдущую страницу и т.д.Я использую http Link заголовок:
http://www.w3.org/wiki/LinkHeader

Github делает это таким же образом: https://developer.github.com/v3/#pagination

На мой взгляд, это чище, поскольку его можно использовать также при возврате контента, который не поддерживает гиперссылки (например, двоичные файлы, картинки).

В последнее время я провел несколько обширных исследований по этому и другим вопросам, связанным с подкачкой REST, и счел конструктивным добавить некоторые из моих выводов сюда.Я немного расширяю вопрос, чтобы включить мысли о подкачке по страницам, а также о количестве, поскольку они тесно связаны.

Заголовки

Метаданные подкачки включаются в ответ в виде заголовков ответа.Большим преимуществом такого подхода является то, что сама полезная нагрузка ответа - это всего лишь фактические данные, которые запрашивал запрашивающий.Упрощение обработки ответа для клиентов, которые не заинтересованы в информации о подкачке.

Существует множество (стандартных и пользовательских) заголовков, используемых в wild для возврата информации, связанной с подкачкой, включая общее количество.

X-Общее количество

X-Total-Count: 234

Это используется в некоторые API - интерфейсы Я нашел его в дикой природе.Существуют также Пакеты NPM для добавления поддержки этого заголовка, например, вОбратная связь.Некоторые Статьи рекомендую также установить этот заголовок.

Он часто используется в сочетании с Link заголовок, который является довольно хорошим решением для подкачки по страницам, но в нем отсутствует информация об общем количестве.

Ссылка

Link: </TheBook/chapter2>;
      rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
      </TheBook/chapter4>;
      rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

Много читая на эту тему, я чувствую, что общее мнение заключается в том, чтобы использовать Link заголовок предоставлять ссылки на подкачку клиентам, используя rel=next, rel=previous и т.д.Проблема с этим заключается в том, что ему не хватает информации о том, сколько всего существует записей, именно поэтому многие API объединяют это с X-Total-Count заголовок.

В качестве альтернативы, некоторые API и, например,тот самый JsonApi стандартно используйте Link отформатируйте, но добавьте информацию в конверт ответа, а не в заголовок.Это упрощает доступ к метаданным (и создает место для добавления информации об общем количестве) за счет увеличения сложности доступа к самим фактическим данным (путем добавления конверта).

Контент-Диапазон

Content-Range: items 0-49/234

Продвигается статьей в блоге под названием Заголовок диапазона, я выбираю вас (для разбивки на страницы)!.Автор приводит убедительные доводы в пользу использования Range и Content-Range заголовки для разбивки на страницы.Когда мы внимательно читаем тот самый RFC по этим заголовкам мы обнаруживаем, что расширение их значения за пределы диапазонов байтов фактически ожидалось RFC и явно разрешено.При использовании в контексте items вместо того, чтобы bytes, заголовок Range фактически дает нам способ как запросить определенный диапазон элементов, так и указать, к какому диапазону общего результата относятся элементы ответа.Этот заголовок также предоставляет отличный способ показать общее количество.И это настоящий стандарт, который в основном соотносит "один к одному" с подкачкой.Это также используется в дикой природе.

Конверт

Множество API, включая тот, что с нашего любимого сайта вопросов и ответов используйте конверт, оболочка вокруг данных, которая используется для добавления метаинформации о данных.Также, OData ( ОДата ) и JsonApi в обоих стандартах используется конверт ответа.

Большим недостатком этого (имхо) является то, что обработка данных ответа становится более сложной, поскольку фактические данные должны быть найдены где-то в конверте.Кроме того, существует много различных форматов для этого конверта, и вы должны использовать правильный.Это говорит о том, что конверты ответов от OData и JsonApi сильно отличаются, причем OData смешивает метаданные в нескольких точках ответа.

Отдельная конечная точка

Я думаю, что это было достаточно рассмотрено в других ответах.Я не стал подробно разбираться в этом, потому что согласен с комментариями о том, что это сбивает с толку, поскольку теперь у вас есть несколько типов конечных точек.Я думаю, что лучше всего, если каждая конечная точка представляет собой (коллекцию) ресурсов.

Дальнейшие размышления

Нам нужно не только передавать метаинформацию о подкачке, связанную с ответом, но и разрешать клиенту запрашивать определенные страницы / диапазоны.Интересно также рассмотреть этот аспект, чтобы в конечном итоге получить согласованное решение.Здесь мы также можем использовать заголовки (the Range заголовок кажется очень подходящим) или другие механизмы, такие как параметры запроса.Некоторые люди выступают за то, чтобы рассматривать страницы результатов как отдельные ресурсы, что может иметь смысл в некоторых случаях использования (например /books/231/pages/52.В итоге я выбрал широкий диапазон часто используемых параметров запроса, таких как pagesize, page[size] и limit и т.д. в дополнение к поддержке Range заголовок (а также в качестве параметра запроса).

Вы могли бы вернуть счетчик в виде пользовательского HTTP-заголовка в ответ на запрос HEAD.Таким образом, если клиенту нужно только количество, вам не нужно возвращать фактический список, и нет необходимости в дополнительном URL.

(Или, если вы находитесь в контролируемой среде от конечной точки к конечной, вы могли бы использовать пользовательский HTTP-глагол, такой как COUNT .)

Альтернатива, когда вам не нужны реальные товары

Ответ Франси Пенова это, безусловно, лучший способ сделать так, чтобы вы всегда возвращали элементы вместе со всеми дополнительными метаданными о запрашиваемых ваших объектах.Именно так это и должно быть сделано.

но иногда возвращать все данные не имеет смысла, потому что они могут вам вообще не понадобиться.Возможно, все, что вам нужно, - это метаданные о запрошенном вами ресурсе.Например, общее количество, или количество страниц, или что-то еще.В таком случае вы всегда можете попросить URL-запрос сообщить вашей службе, чтобы она возвращала не элементы, а просто метаданные, такие как:

/api/members?metaonly=true
/api/members?includeitems=0

или что-то подобное...

Я бы рекомендовал добавить заголовки для того же самого, например:

HTTP/1.1 200

Pagination-Count: 100
Pagination-Page: 5
Pagination-Limit: 20
Content-Type: application/json

[
  {
    "id": 10,
    "name": "shirt",
    "color": "red",
    "price": "$23"
  },
  {
    "id": 11,
    "name": "shirt",
    "color": "blue",
    "price": "$25"
  }
]

Для получения подробной информации обратитесь к:

https://github.com/adnan-kamili/rest-api-response-format

Для файла swagger:

https://github.com/adnan-kamili/swagger-response-template

Начиная с "X-"-префикс устарел.(см .: https://tools.ietf.org/html/rfc6648)

Мы сочли, что "Accept-Ranges" является лучшим выбором для отображения диапазона разбивки на страницы: https://tools.ietf.org/html/rfc7233#section-2.3 В качестве "единиц измерения диапазона" могут использоваться либо "байты", либо "токены".Оба они не представляют пользовательский тип данных.(см .: https://tools.ietf.org/html/rfc7233#section-4.2) Тем не менее, утверждается, что

Реализации HTTP /1.1 МОГУТ игнорировать диапазоны, указанные с использованием других единиц измерения.

Что указывает на:использование пользовательских единиц измерения диапазона не противоречит протоколу, но МОЖЕТ быть проигнорировано.

Таким образом, нам пришлось бы установить для Accept-Ranges значение "участники" или любой другой тип подразделения дальнего действия, который мы ожидали бы.И, кроме того, также установите Content-Range равным текущему диапазону.(см .: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12)

В любом случае, я бы придерживался рекомендации RFC7233 (https://tools.ietf.org/html/rfc7233#page-8) отправить 206 вместо 200:

Если все предварительные условия выполнены, сервер поддерживает диапазон
поле заголовка для целевого ресурса и указанный диапазон (диапазоны) являются
допустимый и выполнимый (как определено в разделе 2.1), сервер ДОЛЖЕН
отправьте ответ 206 (с частичным содержимым) с полезной нагрузкой, содержащей один
или более частичные представления, соответствующие выполнимым
запрашиваемые диапазоны, как определено в разделе 4.

Итак, в результате мы получили бы следующие поля HTTP-заголовка:

Для Частичного содержания:

206 Partial Content
Accept-Ranges: members
Content-Range: members 0-20/100

Для получения полного контента:

200 OK
Accept-Ranges: members
Content-Range: members 0-20/20

Как насчет новой конечной точки > /api/members/count, которая просто вызывает Members.Count() и возвращает результат

Кажется, проще всего просто добавить

GET
/api/members/count

и верните общее количество участников

Иногда фреймворкам (например, $resource / AngularJS) требуется массив в качестве результата запроса, и вы не можете получить ответ, подобный {count:10,items:[...]} в этом случае я сохраняю "count" в responseHeaders.

P.S.На самом деле вы можете сделать это с помощью $resource / AngularJS, но для этого нужны некоторые настройки.

При запросе данных с разбивкой по страницам вы знаете (по явному значению параметра размера страницы или значению размера страницы по умолчанию) размер страницы, поэтому вы знаете, получили ли вы все данные в ответ или нет.Когда в ответе меньше данных, чем размер страницы, то вы получаете целые данные.Когда будет возвращена полная страница, вам придется снова запросить другую страницу.

Я предпочитаю иметь отдельную конечную точку для count (или ту же конечную точку с параметром countOnly).Потому что вы могли бы подготовить конечного пользователя к длительному / трудоемкому процессу, показав правильно запущенную панель прогресса.

Если вы хотите возвращать datasize в каждом ответе, там также должны быть указаны pageSize и offset.Честно говоря, лучший способ - это тоже повторить запрос фильтров.Но реакция стала очень сложной.Итак, я предпочитаю, чтобы выделенная конечная точка возвращала количество.

<data>
  <originalRequest>
    <filter/>
    <filter/>
  </originalReqeust>
  <totalRecordCount/>
  <pageSize/>
  <offset/>
  <list>
     <item/>
     <item/>
  </list>
</data>

Насколько я понимаю, предпочитаю параметр countOnly существующей конечной точке.Таким образом, при указании ответ содержит только метаданные.

конечная точка?фильтр= значение

<data>
  <count/>
  <list>
    <item/>
    ...
  </list>
</data>

конечная точка?filter= значение иcountOnly=true

<data>
  <count/>
  <!-- empty list -->
  <list/>
</data>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top