Концепції API REST


10

У мене є три питання щодо дизайну API REST, на які я сподіваюся, що хтось може пролити трохи світла. Я невпинно шукав багато годин, але ніде не знайшов відповіді на мої запитання (можливо, я просто не знаю, що шукати?).

питання 1

Перше моє запитання стосується дій / RPC. Я деякий час розробляв API REST і звик думати про речі з точки зору колекцій та ресурсів. Однак я натрапив на кілька випадків, коли парадигма, схоже, не застосовується, і мені цікаво, чи є спосіб узгодити це з парадигмою REST.

Зокрема, у мене є випадок, коли зміна ресурсу спричиняє генерацію електронного листа. Однак пізніше користувач може конкретно вказати, що хоче повторно надіслати електронний лист, надісланий раніше. Під час повторного надсилання електронної пошти жоден ресурс не змінюється. Жоден стан не змінюється. Це просто дія, яка повинна відбутися. Дія прив’язана до конкретного типу ресурсу.

Чи доречно змішати якийсь виклик дії з URI ресурсу (наприклад /collection/123?action=resendEmail)? Було б краще вказати дію та передати їй ідентифікатор ресурсу (наприклад /collection/resendEmail?id=123)? Це неправильний шлях про це? Традиційно (принаймні, із HTTP) дія, що виконується, - це метод запиту (GET, POST, PUT, DELETE), але вони насправді не дозволяють виконувати спеціальні дії з ресурсом.

Питання 2

Я використовую частину URL-адреси запиту для фільтрування набору ресурсів, повернутих під час запиту колекції (наприклад /collection?someField=someval). Потім у своєму контролері API я визначаю, яке порівняння він буде робити з цим полем і значенням. Я виявив, що це справді не працює. Мені потрібен спосіб дозволити користувачеві API вказати тип порівняння, який вони хочуть виконати.

Найкраща ідея, яку я придумав поки що, - дозволити користувачеві API вказати його як додаток до назви поля (наприклад, /collection?someField:gte=someval- вказати, що він повинен повертати ресурси там, де someFieldбільше або дорівнює (> =), що б somevalне було . Це гарна ідея?

Питання 3

Я часто бачу URI, які виглядають щось на зразок /person/123/dogsотримання persons dogs. Я, як правило, уникав подібного, тому що, врешті-решт, я розумію, що, створивши подібний URI, ви насправді просто отримуєте доступ до dogsколекції, відфільтрованої за певним personідентифікатором. Це було б рівнозначно /dogs?person=123. Чи є коли-небудь дійсно вагома причина, щоб URI REST був глибше, ніж два рівні ( /collection/resource_id)?


10
У вас є три питання. Чому б не розмістити їх окремо?
anaximander

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

2
Я думаю, що вони всі пов'язані. Заголовок трохи на високому рівні, але це питання допоможе багатьом людям, і його легко знайти під час пошуку в SE. Це питання має стати спільнотою Wiki після того, як буде додано достатньо голосів і суті. На дослідження цього матеріалу знадобилися мені тижні.
Ендрю Т Фіннелл

1
Можливо, було б краще розмістити їх окремо, IDK. Однак, як згадував @AndrewFinnell, я вважав, що було б гарною ідеєю зберегти питання разом, оскільки це були найскладніші питання щодо REST, які у мене виникли, і іншим людям було б добре знайти відповіді. разом.
Джастін Варкентін

Відповіді:


11

Чи доречно змішати якийсь виклик дії з URI ресурсу (наприклад /collection/123?action=resendEmail)? Було б краще вказати дію та передати їй ідентифікатор ресурсу (наприклад /collection/resendEmail?id=123)? Це неправильний шлях про це? Традиційно (принаймні, із HTTP) дія, що виконується, - це метод запиту (GET, POST, PUT, DELETE), але вони насправді не дозволяють виконувати спеціальні дії з ресурсом.

Я вважаю за краще моделювати це по-іншому, зі збіркою ресурсів, що представляють електронні листи, які потрібно надіслати; відправлення буде своєчасно оброблено внутрішніми службами, після чого відповідний ресурс буде видалений. (Або користувач міг рано ВИДАЛИТИ ресурс, викликаючи скасування запиту про надсилання.)

Що б ви не робили, не вкладайте дієслова у назву ресурсу! Це іменник (а запитна частина - це набір прикметників). Іменникові дієслова weirds REST!

Я використовую частину URL-адреси запиту для фільтрування набору ресурсів, повернутих під час запиту колекції (наприклад /collection?someField=someval). Потім у своєму контролері API я визначаю, яке порівняння він буде робити з цим полем і значенням. Я виявив, що це справді не працює. Мені потрібен спосіб дозволити користувачеві API вказати тип порівняння, який вони хочуть виконати.

Найкраща ідея, яку я придумав поки що, - дозволити користувачеві API вказати його як додаток до назви поля (наприклад, /collection?someField:gte=someval- вказати, що він повинен повертати ресурси там, де деякийField більший або рівний ( >=), що б там somevalне було. Це хороша ідея? Погана ідея? Якщо так, то чому? Чи є кращий спосіб дозволити користувачеві вказати тип порівняння для даного поля та значення?

Я вважаю за краще вказати загальний пункт фільтра і мати його як необов'язковий параметр запиту в будь-якому запиті для отримання вмісту колекції. Потім клієнт може точно вказати, як обмежити повернутий набір будь-яким способом. Я також хотів би трохи потурбуватися про виявлення фільтра / мови запитів; чим багатшим ви це зробите, тим важче виявити довільних клієнтів. Альтернативний підхід, який, принаймні теоретично, стосується цього питання виявлення, полягає в тому, щоб дозволити внесення обмежувальних підресурсів колекції, які клієнти отримують шляхом розміщення документа, що описує обмеження на ресурс колекції. Це все-таки незначне зловживання, але принаймні це одне, якого ви можете чітко зробити відкритим!

Цей вид виявлення - одна з речей, які я вважаю найменш сильними з REST.

Я часто бачу URI, які схожі на те, /person/123/dogsщоб доставити людей собак. Я, як правило, уникав подібного, тому що, врешті-решт, я розумію, що, створюючи подібний URI, ви насправді просто отримуєте доступ до колекції собак, відфільтрованої за конкретною особою. Це було б рівнозначно /dogs?person=123. Чи є коли-небудь дійсно вагома причина, щоб URI REST був глибше, ніж два рівні ( /collection/resource_id)?

Коли вкладена колекція справді є підфункцією сутності учасників зовнішньої колекції, доцільно структурувати їх як підресурс. Під "підфункцією" я маю на увазі щось на зразок співвідношення композиції UML, де знищення зовнішнього ресурсу природно означає знищення внутрішньої колекції.

Інші типи колекції можуть моделюватися як перенаправлення HTTP; таким чином, /person/123/dogsдійсно можна відповісти, зробивши 307, який перенаправляє на /dogs?person=123. У цьому випадку колекція насправді не є композицією UML, а скоріше UML-агрегацією. Різниця має значення; це важливо!


2
У вас загальні бали. Однак, хоча resendEmailдію можна впоратися, створивши колекцію та розмістивши її, це здається менш природним. Насправді я нічого не зберігаю в базі даних, коли повідомлення електронної пошти відновлюється (не потрібно). Жоден ресурс не модифікується, таким чином, це просто дія, яка або успішна, або невдача. Я не міг повернути ідентифікатор ресурсу, який існує поза терміном виклику, зробивши таку реалізацію злому, а не RESTful. Це просто не CRUD-операція.
Джастін Варкентін

3

Зрозуміло, що слід трохи заплутатися в тому, як правильно використовувати REST на основі всіх способів, якими я бачив, як великі компанії проектували свої REST API.

Ви вірні в тому, що REST - це система збору ресурсів. Він розшифровується як державна передача представництва. Не чудове визначення, якщо ви запитаєте мене. Але основні поняття - це 4 HTTP VERB і вони без громадянства.

Важливим елементом є те, що у вас є лише 4 СЕРБИ з REST. Це GET, POST, PUT та DELETE. У вашому resendприкладі буде додавання нового дієслова до REST. Це повинен бути червоний прапор.

питання 1

Важливо усвідомити, що абонент вашого API REST не повинен знати, що виконання PUTвашої колекції призведе до створення електронної пошти. Це пахне мені витік. Те, що вони могли знати, - це те, що виконання ними PUTможе призвести до додаткових завдань, про які вони можуть пізніше запитувати. Вони могли б знати це, виконуючи GETнещодавно створений ресурс. Це GETповерне ресурс і всі Taskпов'язані з ним ідентифікатори ресурсу. Потім ви можете запитувати ці завдання, щоб визначити їх статус і навіть подати нове Task.

У вас є кілька варіантів.

REST - підхід на основі ресурсних задач

Створіть tasksресурс, на якому ви зможете подавати конкретні завдання у вашу систему для виконання дій. Потім ви можете GETвиконати завдання на основі IDповернутого документа, щоб визначити його статус.

Або ви можете змішати SOAP over HTTPвеб-сервіс, щоб додати трохи RPC до своєї архітектури.

запит на всі завдання для певного ресурсу

GET http://server/api/myCollection/123/tasks

{ "tasks" :
    [ { "22333" : "http://server/api/tasks/223333" } ] 
}

Приклад ресурсного завдання

PUT http://server/api/tasks

{ 
    "type" : "send-email" , 
    "parameters" : 
    { 
         "collection-type" : "foo" , 
         "collection-id" : "123" 
    } 
}

==> повертає ідентифікатор завдання

223334

GET http://server/api/tasks/223334

{ 
    "status" : "complete" , 
    "date" : "whenever" 
}

REST - використання POST для запуску дій

Ви завжди можете POSTдодати дані до ресурсу. На мою думку, це порушило б дух REST, але все-таки було б поступливим.

Ви можете зробити POST, подібний до цього:

POST http://server/api/collection/123

{ "action" : "send-email" }

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

Проблема у мене полягає в тому, що a GETна ресурсі поверне ці оновлені дані. Однак це вирішило би ваші вимоги та все-таки буде ВІДБУДАТИ.

SOAP - Веб-сервіс, який приймає ресурси, отримані від REST

Створіть нову WebService, в якій ви можете надсилати електронні листи на основі попереднього ідентифікатора ресурсу з API REST. Я не буду вникати в деталі про SOAP тут, оскільки початковий питання стосується REST, і ці два поняття / технології не слід порівнювати, оскільки це яблука та апельсини .

Питання 2

Тут також є кілька варіантів:

Здається, багато великих компаній, які публікують REST API, виставляють searchколекцію, яка насправді є лише способом передачі параметрів запиту для повернення ресурсів.

GET http://server/api/search?q="type = myCollection & someField >= someval"

Що поверне колекцію повністю кваліфікованих ресурсів REST, таких як:

{
    "results" : 
       { [ 
             "location" : "http://server/api/myCollection/1",
             "location" : "http://server/api/myCollection/9",
             "location" : "http://server/api/myCollection/56"
         ]
       }
}

Або ви можете дозволити щось на зразок MVEL як параметр запиту.

Питання 3

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

Примітки

Я не погоджуюся з коментарями читабельності інших. Незважаючи на те, що деякі можуть подумати, REST все ще не для споживання людиною. Це для споживання машини. Якщо я хочу побачити свої твіти, я використовую звичайний веб-сайт Twitters. Я не виконую REST GET зі своїм API. Якщо я хочу програмно щось робити зі своїми твітами, тоді я використовую їх REST API. Так, API повинні бути зрозумілими, але gteце не так вже й погано, це просто не інтуїтивно.

Інше головне, що стосується REST, - це те, що ви повинні мати можливість запуститись у будь-якій точці надання API та перейти до всіх інших пов’язаних ресурсів БЕЗ знати достроково URL-адреси інших ресурсів. Результати GETVERB в REST повинні повертати повну REST URL-адресу ресурсів, на які вона посилається. Таким чином, замість запиту, що повертає ідентифікатор Personоб'єкта, він повертав би повністю кваліфіковану URL-адресу, наприклад http://server/api/people/13. Тоді ви завжди можете програмно орієнтуватися в результатах, навіть якщо URL-адреса була змінена.

Відповідь на коментар

У реальному світі насправді трапляються речі, які не створюють, читають, оновлюють та не видаляють (CRUD) ресурс.

Додаткові дії можуть бути вжиті щодо ресурсів. Типові реляційні бази даних підтримують концепцію збережених процедур. Це додаткові команди, які можна виконати на наборі даних. REST не має такої концепції. І немає підстав для цього. Ці типи дій ідеально підходять для веб-служб RPC або SOAP.

Це загальна проблема, яку я бачу з API REST. Розробникам не подобаються концептуальні обмеження, які оточують REST, тому вони адаптують його до того, щоб вони хотіли. Це відриває його від того, що вона є РЕЙТЕЛЬНОЮ службою. По суті, ці URL-адреси стають GETвикликами псевдо-REST-подібних сервлетів.

У вас є кілька варіантів:

  • Створіть ресурс завдання
  • Підтримка POSTдодаткових даних до ресурсу для виконання дії.
  • Додайте додаткові команди через веб-сервіс SOAP.

Якщо ви використовували параметр запиту, який HTTP VERB використовували б для повторного надсилання електронної пошти?

  • GET- Це повторно надсилає електронну пошту І повертає дані ресурсу? Що робити, якщо система кешувала цю URL-адресу і розглядала її як унікальну URL-адресу для цього ресурсу. Кожен раз, коли вони потрапляють на URL-адресу, він надсилатиме електронний лист.
  • POST - Ви фактично не надсилали на ресурс ніяких нових даних, а лише додатковий параметр запиту.

Виходячи з усіх заданих вимог, завдання POSTна ресурсі з action fieldданими POST вирішить проблему.


3
Хоча REST, реалізований за допомогою HTTP, дає вам ці 4 дієслова, я не впевнений, що ці дієслова повинні бути його кінцем. У реальному світі насправді трапляються речі, які не створюють, читають, оновлюють та не видаляють (CRUD) ресурс. Повторне повідомлення електронної пошти - одна з таких речей. Мені не потрібно нічого зберігати чи змінювати в базі даних. Це просто дія, яка або вдається, або зазнає невдачі.
Джастін Варкентін

@JustinWarkentin Я розумію, які твої потреби. Але це не робить REST чимось це не так. Додавання нового дієслова до URL суперечить архітектурі REST. Я оновлю свою відповідь, щоб запропонувати іншу альтернативу, яка була б ВІДПОВІДНОЮ.
Ендрю Т Фіннелл

@JustinWarkentin Ознайомтеся з моєю відповіддю "REST - Використання POST для запуску дій".
Ендрю Т Фіннелл

0

Питання 1: Чи доречно змішати якийсь виклик дії з URI ресурсу [чи], чи було б краще вказати дію та передати їй ідентифікатор ресурсу?

Хороше питання. У цьому випадку я б порадив вам скористатися останнім підходом, а саме вказати дію та передати їй ідентифікатор ресурсу. Таким чином, коли ваш ресурс вперше модифікований, він, в свою чергу, викликає /sendEmailдію (бічна примітка: не потрібно називати його "повторно") як окремий запит RESTful (який ви можете пізніше і знову викликати, незалежно від зміненого ресурсу ).

Питання 2: щодо використання оператора порівняння, як-от так:/collection?someField:gte=someval

Хоча це технічно нормально, це, мабуть, погана ідея. Одним із ключових принципів REST є читабельність. Я б запропонував вам просто передати оператор порівняння як інший параметр, наприклад: /collection?someField=someval&operator=gteі, звичайно, спроектувати свій API так, щоб він відповідав випадку за замовчуванням (у випадку, якщо operatorпараметр не залишиться з URI).

Питання 3: Чи коли-небудь дійсно є вагома причина, щоб URI REST був глибше, ніж два рівні?

Так; для абстракції. Я бачив пару API REST, які використовують шари абстракції через декілька рівнів URI, наприклад: /vehicles/cars/123або /vehicles/bikes/123що, в свою чергу, дозволяє вам працювати з корисною інформацією, що стосується /vehiclesі /vehicles/bikesколекцій, і колекцій. Сказавши це, я не є великим прихильником такого підходу; вам рідко потрібно буде це робити на практиці, і є ймовірність, що ви могли б переробити API для використання лише двох рівнів.

І так, як підказують вищезазначені коментарі, в майбутньому найкраще буде розділити свої питання на окремі пости;)


Я думаю, що мій приклад для питання №2 був надто спрощеним. Мені потрібно вказати оператор порівняння для кожного поля, яке використовується для фільтрації колекції, а не лише одного, тому у вашому прикладі це повинно бути щось подібне /collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq.
Джастін Варкентін

0

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

скажемо, у вас є контейнер "Пошук", там ви робите POST /api/searches/специфікацію запиту на вміст. це може бути JSON, XML або навіть документ SQL, що б вам було простіше. Якщо запит розбирається правильно, новий пошук створюється як новий ресурс із власним URI, скажімо/api/searches/q123/

Тоді клієнт може просто GET /api/searches/q123/отримати результати запиту.

Нарешті, ви можете або попросити клієнта видалити запит, або очистити його після закриття сеансу.


0

Чи доречно змішувати якийсь виклик дії з URI ресурсу (наприклад, / collection / 123? Action = resendEmail)? Було б краще вказати дію та передати їй ідентифікатор ресурсу (наприклад, / collection / resendEmail? Id = 123)? Це неправильний шлях про це? Традиційно (принаймні, із HTTP) дія, що виконується, - це метод запиту (GET, POST, PUT, DELETE), але вони насправді не дозволяють виконувати спеціальні дії з ресурсом.

Ні, це не доцільно, оскільки IRI призначені для ідентифікації ресурсів, а не для операцій (однак, ppl використовує цей метод на певний час замінює підхід , у випадках, коли використання не POST та GET методів не підтримується). Що ви можете зробити, це шукати відповідний метод HTTP або створити новий. POST може бути вашим другом у цих випадках (ppl використовує його, якщо вони не можуть знайти відповідний метод і запит не є пошуком). Ще один підхід до отримання ресурсів для надсилання електронної пошти, і таким чином POST /emailsможе надсилати пошту, не створюючи реального ресурсу. Btw. Структура URI не має семантики, тому з точки зору REST це не має значення, який тип URI ви використовуєте. Важливими є метадані (наприклад, відношення посилань ), присвоєні посиланням, які ви надіслали клієнтам.

Найкраща ідея, яку я придумав поки що, - дозволити користувачеві API вказати його як додаток до імені поля (наприклад, / collection? SomeField: gte = someval - щоб вказати, що він повинен повертати ресурси там, де деяке поле більше, ніж чи дорівнює (> =) що-небудь someval. Це хороша ідея? погана ідея? Якщо так, чому? Чи є кращий спосіб дозволити користувачеві визначити тип порівняння для виконання даного поля та значення?

Вам не потрібно створювати власну мову запитів. Я б скоріше скористався вже наявним і додав опис запиту до мета-даних посилання. Для цього слід використовувати, мабуть, тип мультимедійного файлу RDF (наприклад, JSON-LD) або використовувати користувальницький тип MIME (афаік немає формату, що не підтримує RDF). Використовуючи існуючі стандарти, від'єднуйте свого клієнта від сервера, ось в чому полягає єдине обмеження інтерфейсу.

Це було б рівнозначно / собак? Людина = 123. Чи є коли-небудь справді вагома причина для того, щоб URI REST був глибше двох рівнів (/ collection / resource_id)?

Як я вже згадував раніше, структура URI не має значення з точки зору REST. Ви можете використовувати, /x71fd823df2наприклад. Це все ще має сенс для клієнтів, оскільки вони перевіряють метадані, присвоєні посиланням, а не структурі URI. Основна мета URI - визначення ресурсів. У стандарті URI вони заявляють, що шлях містить ієрархічні дані, а запит містить неієрархічні дані. Але це може бути дуже суб’єктивно тим, що є ієрархічним. Ось чому ви зустрічаєте багаторівневі глибокі URI та URI з довгими запитами.

Я невпинно шукав багато годин, але ніде не знайшов відповіді на мої запитання (можливо, я просто не знаю, що шукати?).

Вам слід прочитати принаймні обмеження REST з дисертації Fielding , стандарт HTTP та, напевно, веб-API 3-го покоління від Markus.

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