Проектування apst REST за допомогою URI vs рядка запиту


73

Скажімо, у мене є три ресурси, пов'язані так:

Grandparent (collection) -> Parent (collection) -> and Child (collection)

Сказане зображує такі відносини між цими ресурсами так: Кожен бабуся і дідусь можуть подати карту одному або декільком батькам. Кожен з батьків може скласти карту до одного або кількох дітей. Я хочу можливість підтримувати пошук дочірніх ресурсів, але з критеріями фільтра:

Якщо мої клієнти передають мені посилання на бабусю і дідуся, я хочу шукати лише дітей, які є прямими нащадками цього бабусі та дідуся.

Якщо мої клієнти передають мені ідентифікаційне посилання на батьків, я хочу шукати лише дітей, які є прямими нащадками мого батька.

Я придумав щось подібне:

GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}

і

GET /myservice/api/v1/parents/{parentID}/children?search={text}

для вищезазначених вимог відповідно.

Але я також могла зробити щось подібне:

GET /myservice/api/v1/children?search={text}&grandparentID={id}&parentID=${id}

У цьому дизайні я міг би дозволити моєму клієнту передавати мені те чи інше в рядку запиту: або бабуся-дідусь, або батьківський ідентифікатор, але не обидва.

Мої запитання:

1) Який дизайн API більш ВІДКРИТИЙ і чому? Семантично вони означають і поводяться однаково. Останній ресурс в URI - це "діти", що фактично передбачає, що клієнт працює на дитячому ресурсі.

2) Які плюси і мінуси для кожного з точки зору зрозумілості з точки зору клієнта та ремонтопридатності з точки зору дизайнера.

3) Для чого насправді використовуються рядки запитів, окрім «фільтрації» на вашому ресурсі? Якщо перейти з першим підходом, параметр фільтра вбудовується в сам URI як параметр шляху замість параметра рядка запиту.

Дякую!


3
Назва вашого питання повинна бути надзвичайно заплутаною для тих, хто переглядає це. Дійсні сегменти URI визначаються як <scheme>: // <user>: <password> @ <host>: <port> / <path>; <params>? <query> / # <fragment> (хоча < пароль> застарілий) "Рядок запиту" є дійсним компонентом URI, тому ваш "проти" в заголовку - це божевільна розмова.
К. Алан Бейтс

Ви маєте на увазі I want to only search against children who are INdirect descendants of that grandparent.? За вашою структурою, у бабусі та дідуся немає прямих дітей.
null

Яка різниця між дитиною та батьком? Чи є батьком батько, якщо у нього немає дітей? Пахне помилкою дизайну
Pinoniq

re: potential design flawі якщо у вас є інформація про людину, але немає інформації про її батьків, чи можна їх кваліфікувати як child? (наприклад, Адам і Єва) :)
Джессі Чизгольм

Відповіді:


64

Спочатку

Відповідно до RFC 3986 §3.4 (Уніфіковані ідентифікатори ресурсів § (Синтаксичні компоненти) | Запит

3.4 Запит

Компонент запиту містить неієрархічні дані, які поряд із даними в компоненті шляху (Розділ 3.3) служать для ідентифікації ресурсу в межах схеми URI та органу іменування (якщо він є).

Компоненти запиту призначені для пошуку неієрархічних даних; є кілька речей, більш ієрархічних за своєю природою, ніж генеалогічне дерево! Ergo - незалежно від того, чи вважаєте ви це "REST-y" чи ні , щоб відповідати форматам, протоколам та структурам та для розробки систем в Інтернеті, ви не повинні використовувати рядок запитів для ідентифікації цієї інформації.

REST не має нічого спільного з цим визначенням.

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

Ваш рядок запиту може бути більш відповідним чином визначений як

?first_name={firstName}&last_name={lastName}&birth_date={birthDate} тощо.

Щоб відповісти на ваші конкретні запитання

1) Який дизайн API більш ВІДКРИТИЙ і чому? Семантично вони означають і поводяться однаково. Останній ресурс в URI - це "діти", що фактично передбачає, що клієнт працює на дитячому ресурсі.

Я не думаю, що це так чітко, як ви, здається, вірите.

Жоден із цих ресурсних інтерфейсів не є RESTful. Основним умовою для RESTful архітектурного стилю є те , що стан додатки переходи повинні бути передані з сервера в гіпермедіа. Люди трудилися над структурою URI, щоб зробити їх якось "RESTful URI", але формальна література, що стосується REST, насправді мало що про це говорить. Моя особиста думка полягає в тому, що значна частина мета-дезінформації про REST була опублікована з наміром порушити старі шкідливі звички. (Побудова справді "RESTful" системи - це фактично велика робота. Галузь перейшла на "REST" і заповнила деякі ортогональні проблеми з безглуздими кваліфікаціями та обмеженнями.)

Що говорить література REST, це те, що якщо ви збираєтеся використовувати протокол HTTP в якості протоколу програми, ви повинні дотримуватися формальних вимог специфікацій протоколу, і ви не можете "створювати http, коли ви йдете, і все ще заявляєте, що використовуєте http" ; якщо ви збираєтеся використовувати URI для ідентифікації своїх ресурсів, ви повинні дотримуватися формальних вимог специфікацій щодо URI / URL-адрес.

На ваше питання безпосередньо звертається RFC3986 §3.4, яку я зв'язав вище. Суть у цьому питанні полягає в тому, що, хоча відповідний URI недостатній, щоб вважати API "RESTful", якщо ви хочете, щоб ваша система фактично була "RESTful" і ви використовуєте HTTP та URI, ви не можете ідентифікувати ієрархічні дані через рядок запиту, оскільки:

3.4 Запит

Компонент запиту містить неієрархічні дані

... це так просто.

2) Які плюси і мінуси для кожного з точки зору зрозумілості з точки зору клієнта та ремонтопридатності з точки зору дизайнера.

«Плюси» перших двох у тому, що вони на правильному шляху . «Мінуси» третього полягає в тому, що він, здається, вирівнюється неправильно.

Що стосується вашої зрозумілості та ремонтопридатності, вони, безумовно, суб'єктивні і залежать від рівня розуміння клієнта розробника та дизайнерських відсіків дизайнера. Специфікація URI - це остаточна відповідь щодо форматування URI. Ієрархічні дані повинні бути представлені на шляху та з параметрами шляху. Неієрархічні дані повинні бути представлені у запиті. Фрагмент є складнішим, оскільки його семантика конкретно залежить від типу носія запитуваного представлення. Отже, щоб вирішити "зрозумілий" компонент вашого запитання, я спробую точно перекласти те, що говорять ваші перші два URI. Потім я спробую представити те, що ви говорите, що намагаєтеся виконати з дійсними URI.

Переклад дословних URI на їх семантичне значення /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text} Це говорить про батьків бабусь і дідусів і бабусь, знайдіть їхню дитину. search={text} Те, що ви сказали зі своїм URI, узгоджується лише тоді, коли шукаєте братів та сестер. З вашими "бабусями та дідусями, батьками, дітьми" ви виявили, що "бабуся і дідусь" піднімалися на покоління до їх батьків, а потім поверталися до "бабусь і дідусів", дивлячись на дітей батьків.

/myservice/api/v1/parents/{parentID}/children?search={text} Це говорить про те, що для батьків , ідентифікованих {ParentID}, знайти їх дитина , яка має ?search={text}це ближче , щоб виправити те, що ви бажаєте , і являє собою відносини між батьками> дитиною , який , ймовірно , може бути використаний для моделювання всього API. Щоб моделювати це таким чином, на клієнта покладається тягар, який визнає, що якщо у них є "бабуся і дід", то між ідентифікатором, який вони мають, і тією частиною сімейного графіка, яку вони бажають бачити, існує шар непрямості. Щоб знайти "дитину" за допомогою "grandparentId", ви можете зателефонувати у вашу /parents/{parentID}/childrenслужбу, а потім пророкувати дитину, яка повертається, шукати їх дітей за ідентифікатором вашої особи.

Реалізація ваших вимог як URI Якщо ви хочете моделювати більш розширюваний ідентифікатор ресурсу, який може ходити по дереву, я можу придумати декілька способів, як ви це зможете досягти.

1) Перше, що я вже згадував. Представити графік "Люди" у вигляді складеної структури. Кожна людина має посилання на покоління над нею через шлях батьків і на покоління під ним через свій Дітячий шлях.

/Persons/Joe/Parents/Mother/Parents був би способом захопити Джо-бабусю та дідуся по матері.

/Persons/Joe/Parents/Parents був би способом схопити всіх бабусь і дідусів Джо.

/Persons/Joe/Parents/Parents?id={Joe.GrandparentID} схопив би дідуськи Джо і мав ідентифікатор, який у вас в руках.

і все це матиме сенс (зауважте, що тут може бути покарання за ефективність залежно від завдання, вимушуючи dfs на сервер через відсутність ідентифікації гілки в шаблоні "Батьки / батьки / батьки"). можливість підтримувати будь-яку довільну кількість поколінь. Якщо ви чомусь хочете шукати 8 поколінь, ви могли б це уявити

/Persons/Joe/Parents/Parents/Parents/Parents/Parents/Parents/Parents/Parents?id={Joe.NotableAncestor}

але це призводить до другого домінуючого варіанту представлення цих даних: через параметр шляху.


2) Використовуйте параметри шляху для "запиту на ієрархію". Ви можете розробити наступну структуру, щоб полегшити навантаження на споживачів і все ще мати API, який має сенс.

Оглянути 147 поколінь, що представляє цей ідентифікатор ресурсу з параметрами шляху

/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor}

Щоб знайти Джо у його Великого Дідуся, Ви можете переглянути графік відомої кількості поколінь для ІД Джо. /Persons/JoesGreatGrandparent/Children;generations=3?id={Joe.Id}

Головне, що слід звернути увагу на ці підходи, - це те, що без додаткової інформації в ідентифікаторі та запиті слід очікувати, що перший URI витягує особу 147 поколінь від Джо з ідентифікатором Joe.NotableAncestor. Ви повинні очікувати, що другий поверне Джо. Припустимо, що ви дійсно хочете, щоб ваш клієнт, що дзвонить, міг отримати весь набір вузлів та їх зв’язки між кореневою Особою та кінцевим контекстом вашого URI. Ви можете зробити це з тим самим URI (з деяким додатковим оформленням) та встановивши Accept text/vnd.graphvizна ваш запит, який є носієм, зареєстрованим IANA для .dotпредставлення графіків. За допомогою цього змініть URI на

/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor.Id}#directed

з заголовком запиту HTTP, Accept: text/vnd.graphviz і ви можете мати клієнтів досить чітко повідомляти про те, що вони хочуть спрямований графік ієрархії поколінь між Джо та 147 поколіннями до того, коли це 14-е покоління родовідника містить людину, ідентифіковану як "Помітний прародитель" Джо.

Я не впевнений, що в тексті / vnd.graphviz є якийсь заздалегідь визначений семантичний фрагмент для його фрагменту, я не міг знайти жодного в пошуку інструкції. Якщо цей тип носія насправді має заздалегідь визначену фрагментну інформацію, то для створення відповідного URI слід дотримуватися його семантики. Але якщо ці семантики не визначені заздалегідь, специфікація URI зазначає, що семантика ідентифікатора фрагмента не обмежена і замість цього визначена сервером, що робить це використання дійсним.


3) Для чого насправді використовуються рядки запитів, окрім «фільтрації» на вашому ресурсі? Якщо перейти з першим підходом, параметр фільтра вбудовується в сам URI як параметр шляху замість параметра рядка запиту.

Я вважаю, що я вже ретельно побив це до смерті, але рядки запитів не для "фільтрації" ресурсів. Вони призначені для ідентифікації вашого ресурсу з неієрархічних даних. Якщо ви прорахували свою ієрархію своїм шляхом, перейшовши, /person/{id}/children/і ви хочете визначити конкретну дитину або певний набір дітей, ви скористаєтесь деяким атрибутом, який стосується набору, який ви ідентифікуєте, і включите його до запиту.


1
RFC стосується лише ієрархії, якщо він визначає синтаксис та алгоритм для вирішення відносних посилань на URI. Чи можете ви розробити або навести деякі джерела, пояснюючи, чому приклади в оригінальній публікації не відповідають?
user2313838

2
Це насправді генеалогічне дерево не графік, а не дерево, і зовсім не ієрархічне. зважаючи на багато батьків, розлучення та повторний шлюб тощо
Містер

1
@RobertoAloi Мені здається непротиправним передавати власний інтерфейс "Не знайдено елементів" через порожній набір, коли HTTP вже має для цього визначення. Основний принцип полягає в тому, що ви просите сервер повернути "речі (речі)", і якщо немає "речі (ів)" для повернення, сервер повідомляє про це з "404 - Не знайдено". Що в цьому суперечить?
К. Алан Бейтс

1
Я завжди вважав 404, щоб вказати, що коренева URL-адреса ресурсу не знайдена, тобто колекція в цілому. Отже, якщо ви запитували / книги? Author = Jim, а книг від Jim не було, ви отримаєте порожній набір []. Але якщо ви запитували / статті? Автор = Джим, але ресурс статей навіть не існував як колекція 404, це допоможе вказати, що взагалі немає користі шукати будь-які статті.
adjenks

1
@adjenks Ви цього не дізналися з офіційних специфікацій. Структурно складові URL-адреси можна вважати такими, що містять "корінь", якщо це допомагає вам міркувати про цілі для складових частин, але в кінцевому підсумку рядок запиту не є фільтром відображення проти ресурсу, ідентифікованого через шлях. Рядок запиту - це громадянин першого класу за адресою URL. Якщо ви не знайдете на сервері ресурсів, які б відповідали вашій URL-адресі (включаючи рядок запиту) 404, це засоби, визначені в http для повідомлення про це. Модель взаємодії, яку ви створюєте, вносить відмінність без різниці.
К. Алан Бейтс

14

Тут ви помиляєтесь:

Якщо мої клієнти передають мені ідентифікаційний номер

У системах REST клієнт ніколи не повинен турбуватися з ідентифікаторами. Єдиними ідентифікаторами ресурсів, про які повинен знати клієнт, повинні бути URI. Це принцип "рівномірного інтерфейсу".

Подумайте, як клієнти взаємодіють з вашою системою. Скажімо, користувач починає переглядати список бабусь і дідусів, він вибрав одну з дітей бабусі і дідуся, яка приводить його до /grandparent/123. Якщо клієнт повинен мати змогу здійснювати пошук дітей /grandparent/123, відповідно до "HATEOAS", те, що повертається під час запиту, /grandparent/123має повертати URL-адресу в пошуковий інтерфейс. У цій URL-адресі вже повинні бути дані, необхідні для фільтрації за поточними вбудованими в неї дідами.

Якщо посилання виглядає /grandparent/123?search={term}або /parent?grandparent=123&search={term}або /parent?grandparentTerm=someterm&someothergplocator=blah&search={term}несуттєві в відповідно до REST. Зауважте, як усі ці URL-адреси мають однакову кількість параметрів, що є {term}, навіть якщо вони використовують різні критерії. Ви можете перемикатися між будь-якими з цих URL-адрес або можете змішати їх залежно від конкретних бабусь і дідусів, і клієнт не порушиться, оскільки логічна залежність між ресурсами однакова, навіть якщо основна реалізація може суттєво відрізнятися.

Якщо б ви створили службу такою, яка вимагає, /grandparent/{grandparentID}?search={term}коли ви йдете в одну сторону, але /children?parent={parentID}&search={term}a}, коли ви йдете іншим шляхом, це занадто багато зв'язку, оскільки клієнт повинен знати, щоб інтерполювати різні речі на різні відносини, які концептуально схожі.

Незалежно від того, чи дійсно ви їдете, /grandparent/123?search={term}чи /parent?grandparent=123&search={term}це питання смаку і залежно від того, яка реалізація простіша для вас зараз. Важливо - не вимагати зміни клієнта, якщо ви змінюєте стратегію URL-адрес або використовуєте різні стратегії щодо різних відносин батьки-діти.


10

Я не впевнений, чому люди думають, що введення значень ідентифікатора в URL означає, що це якось REST API, REST - це обробка дієслів, передача ресурсів.

Отже, якщо ви хочете встановити нового користувача, вам доведеться надіслати неабиякий фрагмент даних, і POST http-запит ідеальний, тому, хоча ви можете надіслати ключ (наприклад, ідентифікатор користувача), ви надішлете дані користувача (наприклад, ім'я, адреса) як дані POST.

Зараз загальна ідіома - вводити ідентифікатор ресурсу в URI, але це скоріше умова, ніж будь-яка форма канонічного "його не REST, якщо його немає в URI". Пам'ятайте, що оригінальна теза про REST насправді взагалі не згадує http, її ідіому для обробки даних на клієнт-сервері, а не те, що є розширенням до http (хоча, очевидно, http - це наша основна форма реалізації REST) .

Наприклад, Філдінг використовує запит академічної роботи як приклад. Ви хочете отримати ресурс "Папір доктора Джона про пиття пива", але ви, можливо, також хочете початкову версію або останню версію, тож ідентифікатор ресурсу може бути не тим, на що легко посилається як на єдиний ідентифікатор, який може бути поміщений в URI. REST дозволяє це, і за цим заявляється наступний намір:

REST натомість покладається на автора, вибираючи ідентифікатор ресурсу, який найкраще відповідає характеру ідентифікованої концепції

Тож ніщо не заважає вам використовувати статичний URI для отримання батьків, вводячи пошуковий термін у рядок запиту, щоб визначити точного користувача, якого ви шукаєте. У цьому випадку "ресурс", який ви ідентифікуєте, - це набір бабусь і дідусів (і тому URI містить "бабусь і дідусів" як частину URI. REST включає в себе поняття "контрольні дані", призначені для визначення того, яке представлення вашого ресурс повинен бути знайдений - приклад, наведений у цьому, - кеш керування, але також контроль версій - тому мій запит на відмінний папір доктора Джона можна уточнити, передавши версію як контрольні дані, а не частину URI.

Я думаю, що прикладом інтерфейсу REST, який зазвичай не згадується, є SMTP . Створюючи поштове повідомлення, ви надсилаєте дієслова (ВІД, ДО тощо) з даними про ресурси для кожної частини поштового повідомлення. Це RESTful, навіть якщо він не використовує http дієслова, він використовує власний набір.

Отже ... хоча вам потрібно мати деяку ідентифікацію ресурсів у своєму URI, це не повинно бути вашим ідентифікаційним ідентифікатором. Це може бути щасливо надіслано як контрольні дані, у рядку запиту або навіть у даних POST. Те, що ви насправді ідентифікуєте у своєму API REST, - це те, що ви після дитини, яку ви вже маєте у своєму URI.

Отже, на мій погляд, читаючи визначення REST, ви запитуєте дочірній ресурс, передаючи контрольні дані (у формі запиту), щоб визначити, який з них ви хочете повернути. Як результат, ви не можете зробити запит на URI для бабусі та дідуся чи батьків. Ви хочете, щоб діти повернулися, тому термін батько / бабуся чи дідусь або ідентичний однозначно не повинен бути таким URI. Діти повинні бути.


Власне, URI як концепція є центральним у REST. Однак це не так важливо, як синтаксис URI. Правильний інтерфейс REST повинен ідентифікувати ресурси із загальним синтаксисом. На основі принципу REST, не обов’язково погано ототожнювати складний ресурс з об'єктом JSON замість URI RFC 3986, хоча, якщо ви це зробите, вам слід використовувати JSON RI для всього API.
Лі Лі Райан

4

Багато людей вже говорили про те, що означає REST, і т. Д. І т.д. Але, схоже, ніхто не вирішує справжню проблему: ваш дизайн.

Чому бабуся і дідусь відрізняються від батька? вони обоє мають дітей, які, можливо, можуть мати дітей, які можуть ...

Врешті-решт, всі вони "люди". Напевно, це також є у вашому коді. Тому використовуйте це:

GET /<api-endpoint>/humans/<ID>

Поверне трохи корисної інформації про людину. (Ім'я та інше)

GET /<api-endpoint>/humans/<ID>/children

явно поверне масив дітей. Якщо дітей немає, порожній масив - це, мабуть, шлях.

Щоб зробити це легше, ви можете, наприклад, додати прапор "hasChildren". або 'hasGrandChildren'.

Думати розумнішим не важче


1

Далі є більш RESTfull, тому що кожен бабуся та дідусь отримують власну URL-адресу. Таким чином ресурс ідентифікується унікальним чином.

GET /myservice/api/v1/grandparents/{grandparentID}
GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}

Пошук параметрів запиту - це хороший спосіб виконати пошук із контексту цього ресурсу.

Коли сім'я стає дуже великою, ви можете використовувати start / limit як параметри запиту, наприклад:

GET /myservice/api/v1/grandparents/{grandparentID}/children?start=1&limit=50

Як розробник добре мати різні ресурси з унікальною URL-адресою / URI. Я думаю, ви повинні використовувати параметр запиту лише тоді, коли вони також можуть бути залишені.

Можливо, це добре прочитати http://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling та в іншому випадку оригінальну кандидатську дисертацію Роя Т Філдінга https://www.ics.uci.edu /~fielding/pubs/dissertation/fielding_dissertation.pdf, що пояснює поняття дуже добре і повно.


1

Я піду

/myservice/api/v1/people/{personID}/descendants/2?search={text}

У цьому випадку кожен префікс є дійсним ресурсом:

  • /myservice/api/v1/people: все населення
  • /myservice/api/v1/people/{personID}: одна людина з повною інформацією (включаючи предків, братів і сестер, нащадків)
  • /myservice/api/v1/people/{personID}/descendants: нащадки однієї людини
  • /myservice/api/v1/people/{personID}/descendants/2: онуки однієї людини

Можливо, це не найкраща відповідь, але принаймні має сенс для мене.


-1

багато до мене вже вказували, що формат URL не має значення в контексті служби RESTful ... два мої центи були б ... важливим аспектом REST, про який заявляли в оригінальному написанні, було поняття "ресурси". .при розробці URL-адреси один аспект, який вам може знадобитися мати на увазі, це те, що "Ресурс" не є рядком в одній таблиці (хоча це могло б бути). .і може використовуватися для внесення змін до стану цього представництва (і фактично до нижнього середовища зберігання) .. а даний ресурс може мати значення лише у вашому бізнес-контексті ... наприклад;

У вашому прикладі / myservice / api / v1 / бабусі та дідусі / {grandparentID} / батьки / діти? Search = {text}

Може бути більш значущим ресурсом, якщо скоротити цей / myservice / api / v1 / siblingsOfGrandParents? Search = ..

у цьому випадку ви можете оголосити "siblingsOfGrandParents" як ресурс. Це спрощує URL-адресу

Як зазначають інші, існує широко розповсюджене уявлення про те, що вам потрібно вписатись у кожен тип ієрархічних відносин між об'єктами доменів у більш чіткому вигляді в URL-адресі .. Не існує жодного жорсткого і швидкого правила, щоб мати 1-до1-зіставлення між об’єктами домену та ресурсів і представляють усі їх відносини ... питання, скоріше, я повинен вважати ... чи є необхідність розкривати такі відносини через URL-адреси ... конкретно сегменти шляху ... можливо, ні.


коли ви витрачаєте свій дорогоцінний час, принижуючи відповідь, було б корисно, якщо ви зможете поговорити і пояснити свої причини.
Senthu Sivasambu

Я не зовсім впевнений, чому зволікають на основі читання вашої відповіді. Я погоджуюсь з тобою. Кілька негайних речей, які вискакують мені, - це те, що ви подали дещо дотичний приклад, згадуючи про «братів і сестер», коли ОП запитували про ієрархічні стосунки. Можливо, також вдасться вдосконалити ваш стиль письма. Ви використовуєте багато "...", ми, як правило, не використовуємо багато "..." у формальному, професійному чи навчальному написанні. Також є трохи надмірності (наприклад, ви згадуєте, наприклад, потім говорите у своєму прикладі). Можливо, ваша відповідь могла б бути більш стислою і безпосередньо стосуватися питання ОП.
KennyCason
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.