Различные спокойные представления одного и того же ресурса
Мое приложение имеет ресурс в /foo
. Как правило, она представлена полезная нагрузка HTTP-ответ такой:
{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}
Клиенту не всегда нужны все четыре элемента этого объекта. Каков спокойный семантический способ для клиента сообщить серверу, что ему нужно в представлении? например, если он хочет:
{"a": "some text", "b": "some text", "d": "some text"}
Как это должно быть GET
? Некоторые возможности (я ищу исправления, если я неправильно понял Отдых):
-
GET /foo?sections=a,b,d
.- Строка запроса (называемая в конце концов строкой запроса ), по-видимому, означает "найти ресурсы, соответствующие этому условию, и рассказать мне о них", а не "представить мне этот ресурс в соответствии с этой настройкой".
GET /foo/a+b+d
мой любимый Если семантика REST не охватывает эту проблему, из-за ее простоты.
- нарушает непрозрачность URI, нарушая HATEOAS.
- кажется, разрывает различие между ресурс (единственное значение URI-идентификация одного ресурса) и представление. Но это спорно, потому что это согласуется с
/widgets
, представляющим презентабельный список/widget/<id>
ресурсов, с которым у меня никогда не было проблем.
GET /foo/a
и т. д., И пусть клиент сделает запрос на компонент /foo
, который он хочет.
- умножает накладные расходы, которые могут превратиться в кошмар, если
/foo
имеет сотни компонентов и клиенту нужно 100 из них. те. - Если я хочу поддерживать HTML-представление
/foo
, я должен использовать Ajax, что проблематично, если я просто хочу одну HTML-страницу, которую можно обойти, визуализировать минималистскими браузерами и т. д. - чтобы поддерживать HATEOAS, он также требует, чтобы ссылки на эти "подресурсы" существовали в других представлениях, вероятно, в
/foo
:{"a": {"url": "/foo/a", "content": "some text"}, ...}
GET /foo
, Content-Type: application/json
и {"sections": ["a","b","d"]}
в теле запроса.
- непримечательный и недоступный.
- HTTP делает не определять семантику тела для
GET
. Это законный HTTP, но как я могу гарантировать, что прокси-сервер какого-то пользователя не лишит тело запросаGET
? - мойклиент REST не позволяет мне поместить тело на запрос
GET
, поэтому я не могу использовать его для тестирования.
Sections-Needed: a,b,d
- я бы предпочел по возможности избегать пользовательских заголовков.
- непримечательный и недоступный.
POST /foo/requests
, Content-Type: application/json
и {"sections": ["a","b","d"]}
в теле запроса. Получите 201
С Location: /foo/requests/1
. Затем GET /foo/requests/1
получить желаемое представление /foo
- неуклюжий; требуется туда-сюда и какой-то странный код.
- Unbookmarkable и uncachable, так как
/foo/requests/1
- это просто псевдоним, который будет использоваться только один раз и сохраняться только до тех пор, пока он не будет запрошен.
5 ответов:
Я бы предложил решение querystring (ваше первое). Ваши аргументы против других альтернатив-хорошие аргументы (и те, с которыми я сталкивался на практике, пытаясь решить ту же проблему). В частности, решение" ослабить ограничения/ответить на
foo/a
" может работать в ограниченных случаях, но вносит много сложности в API как от реализации, так и от потребления и, по моему опыту, не стоило усилий.Я буду слабо противодействовать вашим аргумент" по-видимому, означает " с общим примером: рассмотрим ресурс, представляющий собой большой список объектов (
GET /Customers
). Вполне разумно расположить эти объекты на странице, и для этого обычно используется строка запроса:GET /Customers?offset=100&take=50
в качестве примера. В этом случае строка запроса не фильтрует ни одно свойство перечисленного объекта, а предоставляет параметры для подвида объекта.Более конкретно, я бы сказал, что вы можете поддерживать последовательность и HATEOAS через эти критерии для использования строка запроса:
- возвращаемый объект должен быть той же сущностью, что и возвращаемый из Url-адреса без строки запроса.
- Uri без строки запроса должен возвращать полный объект - надмножество любого доступного представленияс строкой запроса в том же Uri. Таким образом, если вы кэшируете результат недекорированного Uri, вы знаете, что у вас есть полная сущность.
- результат, возвращаемый для данной строки запроса, должен быть детерминированным, так что URI с querystrings легко кэшируются
Однако, что вернуть для этих URI может иногда ставить более сложные вопросы:
- возврат другого типа сущности для URI, отличающегося только строкой запроса, может быть нежелательным (
/foo
является сущностью, ноfoo/a
является строкой); альтернативой является возврат частично заполненной сущности- Если вы используете различные типы сущностей для подзапросов, то, если ваш
Возвращение частично заполненной сущности может быть нежелательным, но возвращение части сущности может быть невозможным или может быть более запутанным/foo
не имеетa
, статус404
вводит в заблуждение (/foo
существует ли!), но пустой ответ может быть столь же запутанным- возврат частично заполненной сущности может быть невозможен, если у вас есть сильная схема (если
a
является обязательным, но клиент запрашивает толькоb
, вы вынуждены возвращать либо ненужное значение дляa
, либо недопустимый объект)В прошлом, Я попытался решить эту проблему, определив конкретные именованные "представления" требуемых сущностей и разрешив строку запроса, такую как
?view=summary
или?view=totalsOnly
- ограничение числа перестановок. Это также позволяет определить подмножество сущности, которая "имеет смысл" для потребителя услуги и может быть задокументирована.В конечном счете, я думаю, что это сводится к проблеме согласованности больше, чем что-либо: вы можете встретить руководство HATEOAS с помощью строки запроса относительно легко, но выбор вы можете сделать сами. вам нужно быть последовательным в вашем API и, я бы сказал, хорошо документированным.
Я решил следующее:
Поддержка нескольких комбинаций членов : я придумаю название для каждой комбинации. например, если статья содержит элементы для автора, даты и тела,
/article/some-slug
вернет все это, а/article/some-slug/meta
просто вернет автора и дату.Поддерживая множество комбинаций: я разделю имена членов дефисами:
/foo/a-b-c
.В любом случае, я верну
404
, если комбинация не поддерживается.Архитектурный ограничение
Отдых
Идентификация ресурсов
Из определения покоя:
Ресурс R является временно изменяющейся функцией принадлежности M R(t ), которая для времени T соответствует множеству эквивалентных сущностей или значений. Значения в наборе могут быть представлениями ресурсов и / или идентификаторами ресурсов.
Представление, являющееся телом HTTP и идентификатором быть URL-адресом.
Это очень важно. Идентификатор - это просто значение, связанное с другими идентификаторами и представлениями. Это отличается от сопоставления идентификатора→представления. Сервер может сопоставить любой идентификатор, который он хочет, с любым представлением, если оба связаны одним и тем же ресурсом.
Именно разработчик должен придумать определения ресурсов, которые разумно описывают бизнес, думая о категориях вещей, таких как" пользователи " и "посты".
HATEOAS
Если бы я действительно заботился о совершенных HATEOAS, я мог бы поместить гиперссылку где-нибудь в представлении/foo
к/foo/members
, и это представление просто содержало бы гиперссылку на каждую поддерживаемую комбинацию членов.HTTP
Из определения URL:
Компонент запроса содержит неиерархические данные, которые, наряду с данными в компоненте пути, служат для идентификации ресурса в пределах области Схема Ури и полномочия по присвоению имен (если таковые имеются).
Таким образом,
/foo?sections=a,b,d
и/foo?sections=b
являются различными идентификаторами. Но они могут быть связаны в пределах одного ресурса, будучи сопоставлены различным представлениям.Функциональность
Ни один браузер или кэш никогда не будет иметь проблем с косыми чертами или дефисы.
На самом деле это зависит от функциональности ресурса. Если, например, ресурс представляет сущность:
/customers/5
Здесь '5' представляет идентификатор клиента
Ответ:
Таким образом, если мы рассмотрим его внимательно, каждое свойство json фактически представляет собой поле записи на экземпляре ресурса клиента. Предположим, что потребитель хотел бы получить частичный ответ, то есть часть полей . Мы можем смотреть на это как потребитель хочет иметь возможность выбирать через запрос различные поля, которые ему интересны, но не более (чтобы сэкономить трафик или производительность, если часть полей трудно вычислить).{ "id": 5, "name": "John", "surename": "Doe", "marital_status": "single", "sex": "male", ... }
Я думаю, что в этой ситуации наиболее читаемым и правильным API будет (например, получить только name и surename)
/customers/5?fields=name,surename
Ответ:
{ "name": "John", "surename": "Doe" }
HTTP/1.1
- если запрашивается незаконное имя поля-404 (не Найдено) возвращается
- если запрашиваются разные имена полей-генерируются разные ответы, что также согласуется с кэшированием.
- Минусы: если запрашиваются одни и те же поля, но порядок между полями различен (скажем:
fields=id,name
илиfields=name,id
), хотя ответ один и тот же, эти ответы будут кэшироваться отдельно.HATEOAS
На мой взгляд, чистый HATEOAS не подходит для решения этой конкретной проблемы. Потому что в для этого вам нужен отдельный ресурс для каждой перестановки комбинацийfield , что является излишним, так как сильно раздувает API (скажем, у вас есть 8 полей в ресурсе, вам понадобятся перестановки !).
- если вы моделируете ресурсы только для полей , но не для всех перестановок, это имеет последствия для производительности, например, вы хотите свести количество циклов к минимуму.
Если a, b, c являются свойствами ресурса, например свойства admin for role, то правильным будет использовать первый предложенный вами способ
Другое предложение состоит в том, чтобы отказаться от использования GET и POST и создать действие для коллекцииGET /foo?sections=a,b,d
, поскольку в этом случае вы примените фильтр к коллекцииfoo
. В противном случае,если a, b и c являются сингулярным ресурсом коллекцииfoo
, то способ, который следует за этим, состоит в выполнении серииGET
запросов/foo/a /foo/b /foo/c
. Этот подход, как вы сказали, имеет высокую полезную нагрузку для запроса, но это правильный способ следовать подходу Restfull. Я бы не стал пользоваться вторым предложение, сделанное вами, потому что плюс символ в url имеет особое значение.foo
, например:/foo/filter
или/foo/selection
или любой глагол, представляющий действие над коллекцией. Таким образом, имея тело запроса post, вы можете передать список JSON ресурса, который вы хотите.
Вы можете использовать второй тип носителя поставщика в заголовке запроса application/vnd. com.mycompany.resource. rep2, вы не можете добавить это в закладки, однако параметры запроса не кэшируются (/foo?разделы=a, b, c) вы можете взглянуть на matrix-parameters, однако в этом вопросе они должны быть кэшируемыми параметры матрицы URL в сравнении с параметрами запроса