RESTFul: дії, що змінюють стан


59

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

Скажімо, у нас є стаття як ресурс (api / article), як нам реалізовувати такі дії, як публікація, скасування публікації, активація чи деактивація тощо, але намагатися зберегти її якомога простіше?

1) Чи слід використовувати api / article / {id} / {action}, оскільки там може статися багато логіки резервного копіювання, наприклад, переміщення у віддалені місця чи зміна кількох властивостей. Напевно, найскладніше тут - те, що нам потрібно повернути всі дані статті назад в API, щоб робота над оновленням не могла бути реалізована. Наприклад, редактор міг надіслати на 5 секунд старіші дані та переписати виправлення, які робив якийсь інший журналіст 2 секунди тому, і я не можу пояснити це клієнтам, оскільки публікація статті насправді ніяк не пов'язана з оновленням вмісту.

2) Створення нового ресурсу також може бути варіантом, api / article- {action} / id, але потім повернутий ресурс буде не article- {action}, а статтею, в якій я не впевнений, чи правильно це. Також у класі статті на коді сервера обробляються актуальні роботи на обох ресурсах, і я не впевнений, чи це суперечить RESTfull think

Будь-які пропозиції вітаються ..


Цілком законно, щоб "дії" були частиною URI RESTful - якщо вони констатують дію / алгоритм, який слід виконувати. Що не так api/article?action=publish? Параметри запиту призначені для таких випадків, коли стан ресурсу залежить від згаданого вами алгоритму (або дії). Наприклад api/articles?sort=asc, діє
кандидат наук

1
Я пропоную вам ознайомитись із цією програмою, яка може надихнути вас ще більш РЕСТЕВНІМИ рішеннями.
Бенжол

Однією з проблем, які я бачу у програмі api / article? Action = публікувати, є те, що в додатку RESTfull слід надсилати ВСІ дані статті для публікації, тоді як я вважаю за краще робити саме це: api / article / 4545 / публікація / без нічого додаткового
Miro Svrtan

2
Відмінний ресурс з цього питання та багато іншого: REST API Design - Моделювання ресурсів
Іжакі

@PhD: хоча це абсолютно законно в протоколі, URI зазвичай мають бути іменниками, а не дієсловами. Наявність дієслова в URI зазвичай є ознакою поганого дизайну REST.
Лежи Райан

Відповіді:


49

Я вважаю, що описані тут практики є корисними:

Що з діями, які не вписуються у світ операцій CRUD?

Тут речі можуть розпливатися. Існує ряд підходів:

  1. Реструктуруйте дію, щоб вона виглядала як поле ресурсу. Це працює, якщо дія не приймає параметрів. Наприклад, дія активації може бути відображена в булевому activatedполі та оновлена ​​через PATCH до ресурсу.
  2. Ставтесь до цього як до підресурсу з принципами RESTful. Наприклад, API GitHub дозволяє вам зірку Сутності з PUT /gists/:id/starі зняти позначку з DELETE /gists/:id/star.
  3. Іноді у вас дійсно немає можливості зіставити дію на розумну структуру RESTful. Наприклад, пошук кількох ресурсів насправді не має сенсу застосовувати до кінцевої точки конкретного ресурсу. У цьому випадку це /searchмало б сенс, навіть якщо це не ресурс. Це все в порядку - просто робіть те, що правильно з точки зору споживача API, і переконайтеся, що це чітко задокументовано, щоб уникнути плутанини.

Я голосую за підхід 2. Хоча це може виглядати як незграбні штучні ресурси для абонентів API, насправді вони не знають, що відбувається на сервері. Якщо я закликаю POST /article/123/deactivationsстворити новий запит на дезактивацію статті 123, сервер може не тільки деактивувати запитуваний ресурс, але й фактично зберігати мій запит на дезактивацію, щоб я міг отримати його статус пізніше.
JustAMartin

2
чому PUT /gists/:id/star ні POST /gists/:id/star?
Філіп Бартузі

9
@FilipBartuzi Оскільки PUT ідентичний, тобто, незалежно від того, скільки разів ви виконуєте дію з тими ж параметрами, результат завжди однаковий (наприклад, якщо вимкнути світло на вимкнено, він змінюється. Якщо ви спробуєте увімкнути він знову, нічого не змінюється - він уже включений). POST не є ідентичним, тобто кожен раз, коли ви виконуєте дію, навіть із одними і тими ж параметрами, дія має різний результат (наприклад, якщо ви надіслали листа комусь, ця особа отримала лист. Якщо ви надіслали ідентичний лист на та сама людина, у них зараз 2 букви).
Рафаель

9

Операції, що призводять до значних змін стану та поведінки на стороні сервера, як описана вами дія «опублікувати», важко чітко моделювати в REST. Я часто бачу рішення - керувати такою складною поведінкою неявно через дані.

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

Замість складної операції з купівлі такий API може дозволяти вам створити новий ресурс, замовлення на купівлю. На початку ви можете внести будь-які зміни до нього: додати або видалити товари, змінити адресу доставки, вибрати інший варіант оплати або скасувати замовлення взагалі. Ви можете зробити все це, тому що ви ще нічого не купили, ви просто маніпулюєте деякими даними на сервері.

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

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


Ідея змінити весь ресурс від неопублікованої статті до проекту відповідатиме саме цій справі, але не відповідає всім іншим діям, які існують на ресурсі загалом. Чи REST навіть повинен впоратися з цим? Можливо, я зловживаю цим і повинен використовувати його лише як CRUD та нічого іншого, як SQL запити, де я не очікую, що будь-яка логіка буде всередині самого запиту.
Міро Свртан

Чому я все це прошу? Отже, оскільки деякий час тому веб-додатки починають багатоплатформуватись, і я вважаю за краще зберігати багато бізнес-логіки на сервері, оскільки оновлення логіки бізнесу на iOS, Android, Інтернеті, настільному ПК або будь-якій іншій платформі приходить на розум - це зробити неможливо. швидко, і я хотів би уникнути всіх проблем зворотної сумісності при зміні якогось невеликого шматка BL.
Міро Свртан

2
Я думаю, що REST може справлятись із діловою логікою просто чудово, але це не так підходить для викриття існуючої бізнес-логіки, написаної без пам'яті про REST. Ось чому багато компаній, таких як Microsoft, SAP та інші, часто виставляють лише дані операцій CRUD, як ви вже говорили. Подивіться рекламний протокол відкритих даних, щоб побачити, як вони це роблять.
Ferenc Mihaly

7

нам потрібно повернути всі статті статті назад в API, щоб робота з оновленнями не могла бути реалізована. Наприклад, редактор міг надіслати на 5 секунд старіші дані та переписати виправлення, які робив якийсь інший журналіст 2 секунди тому, і я не можу пояснити це клієнтам, оскільки публікація статті насправді ніяк не пов'язана з оновленням вмісту.

Така річ є проблемою, незалежно від того, що ви робите, це дуже схожа проблема з розподіленим контролем джерел (mercurial, git тощо), і рішення, написане в HTTP / ReST, виглядає дещо схожим.

Припустимо, у вас є два користувачі, Аліса та Боб, які працюють над обома /articles/lunch. (для наочності відповідь є жирним шрифтом)

По-перше, Аліса створює статтю.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

Сервер не створив ресурс, оскільки до запиту не додалася "версія" (припускаючи ідентифікатор /articles/{id}/{version}. Для здійснення створення Аліса була перенаправлена ​​на URL-адресу статті / версії, яку вона створюватиме. Користувач Аліси Потім агент повторно подасть запит за новою адресою.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

І ось стаття створена. далі, Боб розглядає статтю:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Боб дивиться туди:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

Він вирішує додати власну зміну.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

Як і у випадку з Алісою, Боб перенаправляють туди, де він буде створювати нову версію.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Нарешті, Аліса вирішує, що їй хотілося б додати до власної статті:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

Замість того, щоб переадресовуватись як звичайне, клієнту повертається інший код статусу 409, який повідомляє Алісі, що версія, з якої вона намагалася відключитися, вже розгалужена. Нові ресурси були створені в будь-якому випадку (як показано в Locationзаголовку), і відмінності між ними були включені в орган відповіді. Аліса тепер знає, що запит, який вона щойно зробила, потрібно якось об'єднати.


Все це перенаправлення пов'язане з семантикою PUT, яка вимагає створення нових ресурсів саме там, де запитує рядок запиту. це також може зберегти цикл запитів, використовуючи POSTнатомість, але тоді номер версії повинен був би бути закодований в запиті якоюсь іншою магією, яка для ілюстрації для мене здавалася менш очевидною, але, ймовірно, все-таки буде віддана перевагу в реальному API щоб мінімізувати цикли запиту / відповіді.


1
Версонінг тут не був проблемою, я просто сказав це як приклад можливих проблем, якщо використовувати статтю як ресурс для публікації дій
Миро Свртан,

3

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

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

Який тут найресторантний підхід? Команда POST / service? Команда = перезапуск, наприклад? Або POST / service / state з органом, скажімо, "працює"?

Тут було б непогано зашифрувати найкращі практики та чи REST - це правильний підхід до подібної ситуації.

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

GET / звіт може бути способом отримання копії звіту самостійно; але що робити, якщо ми хочемо підштовхнути до сервера подальші дії, такі як електронне повідомлення, як я вже говорив вище. Або запис у базу даних.

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


2

REST орієнтований на дані і тому такі ресурси найкраще працюють як "речі", а не дії. Неявна семантика методів http; GET, PUT, DELETE тощо слугують для посилення орієнтації. POST, звичайно, є винятком.

Ресурс може бути сумішшю даних, тобто. зміст статті; і метадані, тобто. опубліковано, заблоковано, перегляд. Існує багато інших можливих способів розрізати дані, але вам доведеться пройти те, як буде виглядати потік даних спочатку, щоб визначити найбільш оптимальний (якщо такий є). Наприклад, може статися, що зміни статті повинні бути власним ресурсом відповідно до статті, як пропонує TokenMacGuy.

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


1

Не думайте про це, як безпосередньо маніпулюючи станом статті. Натомість ви вводите порядок змін із проханням створити статтю.

Можна ввести порядок змін як створення нового ресурсу порядку замовлення (POST). Переваг багато. Наприклад, ви можете вказати майбутню дату та час, коли стаття має бути опублікована як частина порядку зміни, і дозволити серверу хвилюватися, як це реалізовано.

Якщо публікація не є миттєвим процесом, вам не доведеться чекати завершення, перш ніж повернутися до клієнта. Ви просто визнаєте, що замовлення на зміну створено та повертаєте ідентифікатор замовлення на зміну. Потім ви можете використовувати URL-адресу, відповідну цьому порядку зміни, щоб поділитися статусом порядку замовлення.

Ключовим розумінням для мене було визнання цієї метафори порядку зміни - це ще один спосіб описати об'єктно-орієнтоване програмування. Замість ресурсів ми називаємо тоді об'єкти. Замість зміни замовлень ми називаємо їх повідомленнями. Один із способів надіслати повідомлення від A до B в OO - це метод A для виклику на B. Ще один спосіб зробити це, особливо коли A і B знаходяться на різних комп'ютерах, - це створити A новий об'єкт, M і відправити його на B. REST просто формалізує цей процес.


Я б насправді вважав це ближче до моделі Актора, ніж ОО. Розглядаючи REST-запити як Повідомлення (якими вони є), вони дуже чітко вирівнюють їх до Sourcing подій для управління державою. REST акуратно розподіляє свою взаємодію по лініях CQRS. Отримати повідомлення - запит, POST, PUT, PATCH, DELETE потрапляють у область Command.
WillD

0

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

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

У всіх перерахованих вище випадках вашій службі потрібно виконати встановлені вами бізнес-правила. Таким чином, ви можете зателефонувати до служби за параметрами: userid, стаття, версія, дія (де версія необов’язкова, це знову залежить від правил вашого бізнесу).


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