REST API Рекомендации: как принять список значений параметров в качестве входных данных [закрыто]

410

Мы запускаем новый REST API, и я хотел, чтобы сообщество внесло свой вклад в передовые практики, касающиеся того, как мы должны отформатировать входные параметры:

Прямо сейчас наш API очень ориентирован на JSON (возвращает только JSON). Дискуссия о том, хотим ли мы / нужно ли возвращать XML, является отдельной проблемой.

Поскольку наш выход API ориентирован на JSON, мы пошли по пути, где наши входы немного ориентированы на JSON, и я подумал, что это может быть удобно для некоторых, но в целом странно.

Например, чтобы получить несколько подробностей о продукте, по которым можно одновременно извлечь несколько продуктов, у нас есть:

http://our.api.com/Product?id=["101404","7267261"]

Должны ли мы упростить это как:

http://our.api.com/Product?id=101404,7267261

Или удобен ввод данных в формате JSON? Больше боли?

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

Более сложный случай, когда мы хотим предложить более сложные входные данные. Например, если мы хотим разрешить несколько фильтров для поиска:

http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}

Мы не обязательно хотим помещать типы фильтров (например, productType и color) в качестве имен запросов, например:

http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]

Потому что мы хотели сгруппировать все входные данные фильтра.

В конце концов, действительно ли это имеет значение? Вполне вероятно, что существует так много утилит JSON, что тип ввода не имеет большого значения.

Я знаю, что наши клиенты JavaScript, делающие AJAX-вызовы API, могут по достоинству оценить входные данные JSON, чтобы сделать их жизнь проще.

whatupwilly
источник

Ответы:

341

Шаг назад

Прежде всего, REST описывает URI как универсально уникальный идентификатор. Слишком много людей увлеклись структурой URI, и какие URI более "спокойны", чем другие. Этот аргумент так же нелеп, как если бы сказать, что называть кого-то «Бобом» лучше, чем называть его «Джо», - оба имени делают «идентификацию человека». URI - это не что иное, как универсально уникальное имя.

Таким образом, в глазах REST спор о том, ?id=["101404","7267261"]является ли он более успокоительным ?id=101404,7267261или \Product\101404,7267261бесполезным.

Сказав это, много раз, как создаются URI, обычно могут служить хорошим индикатором для других проблем в RESTful-сервисе. В ваших URI есть пара красных флажков и вопрос в целом.

Предложения

  1. Несколько URI для одного и того же ресурса и Content-Location

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

    URI идентифицируют ресурсы. Каждый ресурс должен иметь один канонический URI. Это не означает, что вы не можете иметь два URI, указывающих на один и тот же ресурс, но есть четко определенные способы сделать это. Если вы решите использовать форматы JSON и списки (или любой другой формат), вам необходимо решить, какой из этих форматов является основным каноническим URI. Все ответы на другие URI, которые указывают на один и тот же «ресурс», должны включать Content-Locationзаголовок .

    Придерживаясь аналогии с именами, наличие нескольких URI похоже на использование псевдонимов для людей. Это вполне приемлемо и часто очень удобно, однако, если я использую псевдоним, я все еще, вероятно, хочу знать его полное имя - «официальный» способ обращения к этому человеку. Таким образом, когда кто-то упоминает кого-то по имени, «Nichloas Telsa», я знаю, что он говорит о том же человеке, которого я называю «Ник».

  2. «Поиск» в вашем URI

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

    Мое общее правило: если ваш URI содержит глагол, это может указывать на то, что что-то не так. URI идентифицируют ресурс, однако они не должны указывать, что мы делаем с этим ресурсом. Это работа HTTP или, в спокойном смысле, наш «единый интерфейс».

    Чтобы опровергнуть аналогию с именем, использование глагола в URI похоже на изменение чьего-либо имени, когда вы хотите с ним взаимодействовать. Если я общаюсь с Бобом, его имя не становится «BobHi», когда я хочу сказать ему «Привет». Точно так же, когда мы хотим «искать» продукты, наша структура URI не должна изменяться с «/ Product / ...» на «/ Search / ...».

Отвечая на ваш начальный вопрос

  1. Относительно ["101404","7267261"]vs 101404,7267261: Мое предложение здесь состоит в том, чтобы избежать простоты синтаксиса JSON для простоты (то есть не требуйте, чтобы ваши пользователи делали кодирование URL, когда вам это не нужно). Это сделает ваш API более удобным. Еще лучше, как рекомендовали другие, использовать стандартный application/x-www-form-urlencodedформат, поскольку он, вероятно, будет наиболее знаком вашим конечным пользователям (например ?id[]=101404&id[]=7267261). Это не может быть "довольно", но Pretty URIs не обязательно означают Используемые URI. Однако, чтобы повторить мою начальную точку, хотя, в конечном счете, когда речь идет о REST, это не имеет значения. Не зацикливайтесь на этом.

  2. Ваш пример URI сложного поиска может быть решен почти так же, как пример вашего продукта. Я бы порекомендовал application/x-www-form-urlencodedснова перейти на этот формат, поскольку это уже стандарт, с которым многие знакомы. Кроме того, я бы порекомендовал объединить два.

Ваш URI ...

/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}    

Ваш URI после кодирования URI ...

/Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D

Может быть преобразован в ...

/Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red

Помимо того, что отпадает требование кодирования URL-адресов и выглядит более стандартным, теперь он немного гомогенизирует API. Пользователь знает, что если он хочет получить продукт или список продуктов (оба рассматриваются как один «ресурс» в терминах RESTful), они заинтересованы в /Product/...URI.

nategood
источник
67
Хотелось продолжить и заметить, что []синтаксис не всегда поддерживается (и, несмотря на то, что он распространен, может даже нарушать спецификацию URI). Некоторые HTTP-серверы и языки программирования предпочитают просто повторять имя (например productType=value1&productType=value2).
Nategood
1
Начальный вопрос с этим запросом .. "/ Search? Term = pumas & filters = {" productType ": [" Clothing "," Bags "]," color ": [" Black "," Red "]}" переводится как .. . (productType == одежда || productType == сумки) && (color == black || color == red), НО ВАШЕ РЕШЕНИЕ: / Product? term = pumas & productType [] = Clothing & productType [] = Сумки и цвет [] = Черные и цветные [] = Красный, кажется, переводится на ... Либо (productType == одежда || productType == сумки || цвет == черный || цвет == красный), либо Либо (productType == одежда && productType == сумки && цвет == черный && color == красный) Что, кажется, немного отличается от меня. Или я неправильно понял?
Томас Ченг
2
А как насчет входов в почтовый запрос? Я хотел бы знать, если мы обновляем ресурс, это плохая практика, чтобы отправить запрос / фильтр и данные в теле в стандартном формате. например если я хочу , чтобы изменить данные , связанные с пользователем с помощью API /user/и в теле, я пришлю { q:{}, d: {} }с qв запросе, с пользователем , будут запрошены в БД и в dкачестве измененных данных.
молекула
1
Что вы делаете, когда список может быть очень большим? Длина URI ограничена в зависимости от браузера. Я, как правило, переключился на запрос по почте и отправил список в теле. Есть предложения?
Трой
4
Он должен быть ОЧЕНЬ большим (см. Stackoverflow.com/questions/417142/… ), но да, в самых крайних случаях вам может понадобиться использовать тело запроса. POSTing запросы для поиска данных - это одна из тех вещей, которые RESTafarians любят обсуждать.
nategood
234

Стандартный способ передать список значений в качестве параметров URL - это повторить их:

http://our.api.com/Product?id=101404&id=7267261

Большая часть кода сервера будет интерпретировать это как список значений, хотя у многих есть упрощения с одним значением, поэтому вам, возможно, придется поискать.

Значения с разделителями тоже в порядке.

Если вам нужно отправить JSON на сервер, мне не нравится видеть его в URL (это другой формат). В частности, URL имеют ограничение по размеру (на практике, если не в теории).

Я видел, как некоторые выполняют сложный запрос RESTful, в два этапа:

  1. POST требования вашего запроса, получение идентификатора (по сути, создание ресурса критериев поиска)
  2. GET поиск, ссылающийся на вышеуказанный идентификатор
  3. При необходимости УДАЛИТЕ требования к запросу, но учтите, что они доступны для повторного использования.
Кэти Ван Стоун
источник
8
Спасибо Кэти. Я думаю, что я с вами и не очень люблю видеть JSON в URL. Тем не менее, я не фанат делать пост для поиска, который является неотъемлемой операцией GET. Можете ли вы указать на пример этого?
whatupwilly
1
Если запросы могут работать как простые параметры, сделайте это. Источник был из списка рассылки остальных обсуждений: tech.groups.yahoo.com/group/rest-discuss/message/11578
Кэти Ван Стоун,
2
Если вы просто хотите показать два ресурса, ответ Джеймса Вестгейта более типичный
Кэти Ван Стоун,
Это правильный ответ. Я уверен, что в ближайшем будущем мы увидим некоторый фильтр = id в (a, b, c, ...), поддерживаемый OData или что-то в этом роде.
Барт Каликсто
Так работает Akka HTTP
Джоан,
20

Первый:

Я думаю, что вы можете сделать это 2 способами

http://our.api.com/Product/<id> : если вы просто хотите одну запись

http://our.api.com/Product : если вы хотите все записи

http://our.api.com/Product/<id1>,<id2> : как предположил Джеймс, это может быть вариант, так как то, что следует после тега Product, является параметром

Или мне больше всего нравится:

Вы можете использовать Hypermedia как свойство состояния приложения (HATEOAS) для RestFul WS и выполнять вызов, http://our.api.com/Productкоторый должен возвращать эквивалентные URL-адреса http://our.api.com/Product/<id>и вызывать их после этого.

второй

Когда вам нужно делать запросы по URL-адресам. Я бы предложил снова использовать HATEOAS.

1) позвонить http://our.api.com/term/pumas/productType/clothing/color/black

2) позвонить http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

3) (Используя HATEOAS) Позвоните по адресу: http://our.api.com/term/pumas/productType/ -> получите URL-адреса всех возможных URL-адресов одежды -> позвоните по желанию (одежда и сумки) - > получить возможные цветовые URL -> назвать те, которые вы хотите

Диего Диас
источник
1
Несколько дней назад я попал в аналогичную ситуацию: мне пришлось настроить (HATEOAS) rest api, чтобы получить отфильтрованный (большой) список объектов, и я просто выбрал ваше второе решение. Не слишком ли вспомнить API снова и снова для каждого из них?
Самсон
Это действительно зависит от вашей системы .... Если это простая система с несколькими «опциями», то, вероятно, это будет излишним. Тем не менее, если у вас есть действительно большие списки, может быть очень сложно сделать все это одним большим вызовом, кроме того, если ваш API общедоступен, он может усложниться для пользователей (если он частный, это должно быть проще ... просто научите пользователей, которых вы знаете). В качестве альтернативы вы можете реализовать как стиль, так и вызов HATEOAS и вызов «non-restful array» для продвинутых пользователей
Диего Диас
Я создаю успокоительный веб-сервис API в rails, и мне нужно следовать той же структуре URL, что и выше ( our.api.com/term/pumas/productType/clothing/color/black ). Но я не уверен, как настроить маршруты соответственно.
rubyist
такое термин, productType и цвет ваших контроллеров? Если это так, вам просто нужно сделать: resources: term do resources: productType do resources: color end end
Диего Диас
productType и color являются параметрами. Так в Params из ProductType является одежда и PARAMS одежды черного цвета
rubyist
12

Вы можете проверить RFC 6570 . Эта спецификация шаблона URI показывает много примеров того, как URL могут содержать параметры.

Даррел Миллер
источник
1
Раздел 3.2.8, похоже, применим. Хотя стоит отметить, что это всего лишь предлагаемый стандарт, и, похоже, он не вышел за рамки этого.
Майк Пост
3
@MikePost Теперь, когда IETF перешел на двухступенчатый процесс подготовки документов для отслеживания стандартов, я ожидаю, что 6570 останется таким же еще несколько лет, прежде чем перейти к «Интернет-стандарту». tools.ietf.org/html/rfc6410 Спецификация чрезвычайно стабильна, имеет много реализаций и широко используется.
Даррел Миллер
Ах, я не знал об этом изменении. (Или TIL IETF теперь более разумный.) Спасибо!
Майк Пост
8

Первый случай:

Нормальный поиск продукта будет выглядеть так

http://our.api.com/product/1

Так что я думаю, что лучшая практика будет для вас, чтобы сделать это

http://our.api.com/Product/101404,7267261

Второй случай

Поиск с параметрами строки запроса - хорошо, как это. Я хотел бы объединить термины с AND и OR вместо использования [].

PS Это может быть субъективно, поэтому делайте то, что вам удобно.

Причиной размещения данных в URL является то, что ссылка может быть вставлена ​​на сайт / разделена между пользователями. Если это не проблема, во всех случаях используйте вместо этого JSON / POST.

РЕДАКТИРОВАТЬ: Если подумать, я думаю, что этот подход подходит для сущности с составным ключом, но не для запроса для нескольких сущностей.

Джеймс Вестгейт
источник
3
Конечно, в обоих примерах /не должно быть конца, поскольку URI обращается к ресурсу, а не к коллекции.
Лоуренс Дол
2
Я всегда думал, что глаголы HTTP, при использовании REST, должны были выполнять определенные действия, и это было руководством: GET: получить / прочитать объект, POST создать объект, PUT обновить существующий объект и DELETE удалить объект. Поэтому я бы не использовал POST для получения чего-либо. Если я хочу получить список объектов, в частности (фильтр), я бы сделал GET со списком в параметрах URL (разделенных запятой кажется хорошим)
Алекс
1

Я согласен с ответом nategood, так как он завершен, и он, кажется, удовлетворяет ваши потребности. Тем не менее, я хотел бы добавить комментарий об идентификации нескольких (1 или более) ресурсов таким образом:

http://our.api.com/Product/101404,7267261

При этом вы:

Сложите клиентов , заставив их интерпретировать ваш ответ как массив, что для меня будет нелогично, если я сделаю следующий запрос:http://our.api.com/Product/101404

Создайте избыточные API с одним API для получения всех продуктов и одним выше для получения 1 или многих. Поскольку вам не следует показывать пользователю более 1 страницы подробностей ради UX, я считаю, что более 1 идентификатора будет бесполезным и будет использоваться исключительно для фильтрации продуктов.

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

пример

Я хочу заказать книгу из Amazing . Я точно знаю, что это за книга, и вижу ее в списке при переходе по книгам ужасов:

  1. 10 000 удивительных линий, 0 удивительных испытаний
  2. Возвращение удивительного монстра
  3. Давайте дублировать удивительный код
  4. Удивительное начало конца

После выбора второй книги, я перенаправлен на страницу с подробным описанием книжной части списка:

--------------------------------------------
Book #1
--------------------------------------------
    Title: The return of the amazing monster
    Summary:
    Pages:
    Publisher:
--------------------------------------------

Или на странице, дающей мне полную информацию только об этой книге?

---------------------------------
The return of the amazing monster
---------------------------------
Summary:
Pages:
Publisher:
---------------------------------

Мое мнение

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

/products/{id}
/products/{id}/specs/{name}

В тот момент, когда вам нужно более 1 ресурса, я бы предложил отфильтровать большую коллекцию. Для того же примера:

/products?ids=

Конечно, это мое мнение, поскольку оно не навязано.

gumol
источник