Различные спокойные представления одного и того же ресурса


Мое приложение имеет ресурс в /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, поэтому я не могу использовать его для тестирования.
  • пользовательский заголовок HTTP: 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 45

    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 являются различными идентификаторами. Но они могут быть связаны в пределах одного ресурса, будучи сопоставлены различным представлениям.

    Код HTTP 404 означает, что сервер не смог найти ничего, к чему можно было бы привязать URL, а не то, что URL не связан ни с каким ресурсом.

    Функциональность

    Ни один браузер или кэш никогда не будет иметь проблем с косыми чертами или дефисы.

    На самом деле это зависит от функциональности ресурса. Если, например, ресурс представляет сущность:

    /customers/5

    Здесь '5' представляет идентификатор клиента

    Ответ:

    {
       "id": 5,
       "name": "John",
       "surename": "Doe",
       "marital_status": "single",
       "sex": "male",
       ...
    }
    
    Таким образом, если мы рассмотрим его внимательно, каждое свойство json фактически представляет собой поле записи на экземпляре ресурса клиента. Предположим, что потребитель хотел бы получить частичный ответ, то есть часть полей . Мы можем смотреть на это как потребитель хочет иметь возможность выбирать через запрос различные поля, которые ему интересны, но не более (чтобы сэкономить трафик или производительность, если часть полей трудно вычислить).

    Я думаю, что в этой ситуации наиболее читаемым и правильным 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 /foo?sections=a,b,d, поскольку в этом случае вы примените фильтр к коллекции foo. В противном случае,если a, b и c являются сингулярным ресурсом коллекции foo, то способ, который следует за этим, состоит в выполнении серии GET запросов /foo/a /foo/b /foo/c. Этот подход, как вы сказали, имеет высокую полезную нагрузку для запроса, но это правильный способ следовать подходу Restfull. Я бы не стал пользоваться вторым предложение, сделанное вами, потому что плюс символ в url имеет особое значение.

    Другое предложение состоит в том, чтобы отказаться от использования GET и POST и создать действие для коллекции foo, например: /foo/filter или /foo/selection или любой глагол, представляющий действие над коллекцией. Таким образом, имея тело запроса post, вы можете передать список JSON ресурса, который вы хотите.

    Вы можете использовать второй тип носителя поставщика в заголовке запроса application/vnd. com.mycompany.resource. rep2, вы не можете добавить это в закладки, однако параметры запроса не кэшируются (/foo?разделы=a, b, c) вы можете взглянуть на matrix-parameters, однако в этом вопросе они должны быть кэшируемыми параметры матрицы URL в сравнении с параметрами запроса