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


409

Ми запускаємо новий API REST, і я хотів би отримати деякі відомості про найкращі практики щодо того, як у нас повинні бути відформатовані вхідні параметри:

Зараз наш 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"]}

Ми не обов'язково хочемо розміщувати типи фільтрів (наприклад, тип товару та колір) як такі назви запитів:

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

Тому що ми хотіли згрупувати весь вхід фільтра разом.

Зрештою, це насправді має значення? Можливо, там є стільки утиліт JSON, що тип введення просто не має великого значення.

Я знаю, що наші клієнти JavaScript, які здійснюють дзвінки AJAX до API, можуть оцінити входи JSON, щоб полегшити їхнє життя.

Відповіді:


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 - це як зміна когось імені, коли ви хочете взаємодіяти з ними. Якщо я взаємодію з Бобом, ім'я Боба не стає «БобХі», коли я хочу сказати Привіт йому. Так само, коли ми хочемо "шукати" продукти, наша структура URI не повинна змінюватися з "/ продукту / ..." на "/ пошуку / ...".

Відповідаючи на ваше початкове запитання

  1. Щодо ["101404","7267261"]vs 101404,7267261: Моя пропозиція тут - уникати синтаксису JSON заради простоти (тобто не вимагати від ваших користувачів кодування URL-адреси, коли вам це не потрібно). Це зробить ваш API непридатнішим для використання. Ще краще, як рекомендували інші, перейдіть до стандартного application/x-www-form-urlencodedформату, оскільки він, мабуть, буде найбільш знайомий вашим кінцевим користувачам (наприклад ?id[]=101404&id[]=7267261). Це може бути не "симпатичним", але досить URI не мають значення 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.


67
Хотіли відстежити і зазначити, що []синтаксис не завжди підтримується (і незважаючи на те, що він є загальним, може навіть порушити специфікацію URI). Деякі сервери HTTP та мови програмування віддають перевагу просто повторенню назви (наприклад productType=value1&productType=value2).
nategood

1
Початкове запитання з цим запитом .. "/ Search? Term = pumas & filters = {" productType ": [" Одяг "," Сумки "]," color ": [" Чорний "," Червоний "]}" перекладається на .. . (productType == одяг || productType == сумки) && (колір == чорний || колір == червоний) АЛЕ ВАШЕ РІШЕННЯ: / Product? term = pumas & productType [] = Одяг та productType [] = Сумки та колір [] = Чорний та колір [] = Червоний, здається, перекладається на ... Або (productType == одяг || productType == сумки || колір == чорний || колір == червоний), або будь-який (productType == одяг і& productType == сумки && color == чорний && color == червоний) Що здається мені дещо іншим. Або я неправильно зрозумів?
Томас Ченг

2
Що з введеннями у запиті на пост? Мені хотілося знати, чи ми оновлюємо ресурс, то чи погана практика надсилати запит / фільтр та дані в тілі у стандартному форматі. напр. якщо я хочу , щоб змінити дані , пов'язані з користувачем з допомогою API /user/і в тілі, я пришлю { q:{}, d: {} }з qв запиті, з користувачем , будуть запитані в БД і в dякості змінених даних.
молекула

1
Що ви робите, коли список може бути дуже великим? URI обмежена по довжині залежно від браузера. Я, як правило, перейшов на запит на публікацію та надіслав цей список у своєму органі. Будь-які пропозиції є?
Troy Cosentino

4
Це повинно бути ДУЖЕ великим (див. Stackoverflow.com/questions/417142/… ), але так, в крайніх випадках вам може знадобитися використовувати тіло запиту. POSTing-запити щодо пошуку даних - одна з тих речей, про які RESTafarians люблять обговорювати.
nategood

233

Стандартний спосіб передавати список значень як параметрів URL - це повторити їх:

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

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

Розмежовані значення також нормально.

Якщо вам потрібно надіслати JSON на сервер, мені не подобається бачити його в URL-адресі (який має інший формат). Зокрема, URL-адреси мають обмеження розміру (на практиці якщо не в теорії).

Те, як я бачив, як деякі справляють складний запит НАЗАД, це у два етапи:

  1. POST ваші вимоги до запиту, отримання ідентифікатора (фактично створення ресурсу критеріїв пошуку)
  2. GET пошук, посилаючись на вищевказаний ідентифікатор
  3. при необхідності ВИДАЛИТИ вимоги запиту, якщо це потрібно, але зауважте, що вони вимагаються для повторного використання.

8
Дякую Кеті. Я думаю, що я з тобою, і мені не дуже подобається бачити JSON в URL-адресі. Однак я не прихильник займатися публікацією пошуку, яка є притаманною операцією GET. Чи можете ви вказати на приклад цього?
whatupwilly

1
Якщо запити можуть працювати як прості параметри, зробіть це. Джерело було зі списку розсилки для інших обговорень: tech.groups.yahoo.com/group/rest-discuss/message/11578
Кеті Ван Стоун

2
Якщо ви просто хочете показати два ресурси, відповідь Джеймса Вестгейта є більш типовою
Кеті Ван Стоун

Це правильна відповідь. Найближчим часом я впевнений, що ми побачимо якийсь filter = id в (a, b, c, ...), підтримуваний OData або щось подібне.
Барт Калікто

Так працює HTTP Akka
Джоан

20

Спочатку:

Я думаю, ви можете це зробити двома способами

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) решту api, щоб отримати відфільтрований (великий) список об'єктів, і я просто обрав ваше друге рішення. Хіба не згадуючи api знову і знову для кожної з них трохи надмірно?
Самсон

Це дійсно залежить від вашої системи .... Якщо це звичайний варіант з кількома "параметрами", він, ймовірно, повинен бути надмірним. Однак, якщо у вас є кілька дійсно великих списків, це може стати справді клопітким зробити все це за один великий дзвінок, крім того, якщо ваш API публічний, він може стати складним для користувачів (якщо це приватний, він повинен бути простішим ... просто навчіть користувачів, яких ви знаєте). Як альтернатива, ви можете реалізувати обидва стилі, HATEOAS та "неспокійний масив" для досвідчених користувачів
Diego Dias

Я будую спокійну веб-службу api в рейках, і мені потрібно слідувати тій же структурі URL, що і вище ( our.api.com/term/pumas/productType/clothing/color/black ). Але я не впевнений, як правильно настроїти маршрути.
рубіст

є термін, тип товару та колір ваших контролерів? Якщо це так, вам просто потрібно зробити: ресурси: термін "ресурси": продукт "Введіть ресурси": кольоровий кінець
Дієго Діас

Тип товару та колір - це параметри. Тож парами productType - це одяг, а парами одягу чорні
рубіст

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.

EDIT: Після роздуму, я думаю, що цей підхід підходить для об'єкту зі складним ключем, але не запитом для декількох об'єктів.


3
Зрозуміло, в обох прикладах сліду /не повинно бути, оскільки URI звертається до ресурсу, а не до колекції.
Лоуренс Дол

2
Я завжди, хоч дієслова HTTP, у використанні REST повинен був робити конкретні дії, і це був рядок керівництва: GET: отримати / прочитати об'єкт, POST створити об'єкт, PUT оновити існуючий об'єкт і DELETE видалити об'єкт. Тому я б не використовував POST, щоб щось витягнути. Якщо я хочу, зокрема, перелік об’єктів (фільтр), я б зробив GET зі списком у параметрах url (розділений комою, здається, добре)
Alex

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=

Звичайно, це моя думка, оскільки вона не нав'язується.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.