Навіщо прагнути до проекту RESTful?
Принципи RESTful приводять функції, які спрощують веб-сайти (для випадкового користувача, щоб " користуватися ними") до дизайну API веб-служб , тому програмісту вони легко користуються. REST - це не добре, оскільки це REST, це добре, тому що це добре. І це здебільшого добре тому, що це просто .
Простота звичайного HTTP (без конвертів SOAP та перевантажених POSTслужб одноразового URI ), що деякі можуть назвати «відсутністю функцій» , насправді є його найбільшою силою . Безпосередньо HTTP просить вас мати адресність та без громадянства : два основні дизайнерські рішення, завдяки яким HTTP можна масштабувати до сьогоднішніх мега-сайтів (і мегапослуг).
Але REST - це не срібний буллет: Іноді RPC-стиль ("Віддалений виклик процедури" - наприклад SOAP) може бути доречним , а іноді інші потреби мають перевагу над чеснотами Мережі. Це добре. Те, що нам не дуже подобається, - це непотрібність . Занадто часто програміст або компанія надає послуги стилю RPC для роботи, з якою звичайний старий HTTP міг би справитись чудово. Ефект полягає в тому, що HTTP зводиться до транспортного протоколу для величезної корисної навантаження XML, яка пояснює, що відбувається "насправді" (не URI або метод HTTP не дають поняття про це). Отримана послуга є надто складною, її неможливо налагодити, і вона не працюватиме, якщо ваші клієнти не встановлять точну настройку, як планував розробник.
Само # код Java / C може бути НЕ об'єктно-орієнтованим, тільки з допомогою HTTP не робить дизайн RESTful. Хтось може потрапити в поспіх думати про свої послуги з точки зору дій та віддалених методів, які слід викликати. Недарма це в основному закінчиться послугою RPC-Style (або гібридом REST-RPC). Перший крок - думати інакше. RESTful дизайн може бути досягнутий багатьма способами. Один із способів - це продумати свою програму з точки зору ресурсів, а не з дій:
💡 Замість того, щоб думати з точки зору дій, які вона може виконувати ("зробіть пошук місць на карті") ...
... спробуйте продумати результати цих дій ("список місць на карті, що відповідають критеріям пошуку").
Я піду для прикладів нижче. (Інший ключовий аспект REST - це використання HATEOAS - я не наношу його тут, але швидко говорю про це на іншій посаді .)
Випуски першої конструкції
Давайте розглянемо запропонований дизайн:
ACTION http://api.animals.com/v1/dogs/1/
По-перше, ми не повинні розглянути можливість створення нового HTTP verb ( ACTION). Взагалі, це небажано з кількох причин:
- (1) Враховуючи лише URI служби, як "випадковий" програміст дізнається, чи
ACTIONіснує дієслово?
- (2) якщо програміст знає, що він існує, то як він буде знати його семантику? Що означає це дієслово?
- (3) які властивості (безпека, ідентифікація) слід очікувати від цього дієслова?
- (4) що робити, якщо програміст має дуже простий клієнт, який обробляє лише стандартні HTTP-дієслова?
- (5) ...
Тепер давайте розглянемо використанняPOST (я обговорю, чому нижче, просто візьміть моє слово зараз):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
Це може бути нормально ... але тільки якщо :
{"action":"bark"}був документ; і
/v1/dogs/1/був URI "процесор документування" (подібний до заводських). "Процесор документа" - це URI, який ви просто "кидаєте на речі" і "забудете" про них - процесор може перенаправити вас на новостворений ресурс після "кидання". Наприклад, URI для розміщення повідомлень у службі посередника повідомлень, яка після публікації перенаправить вас на URI, який показує стан обробки повідомлення.
Я мало знаю про вашу систему, але я вже ставлюся на те, що обидва не відповідають дійсності:
{"action":"bark"} це не документ , це фактично метод, який ви намагаєтеся ніндзя прокрастися до служби; і
/v1/dogs/1/URI являє собою «собака» ресурс (ймовірно, собака з id==1) , а не документ процесора.
Отже, все, що ми знаємо зараз, - це те, що дизайн вище не такий ВИПАДНИЙ, але що це саме? Що в цьому так поганого? В основному це погано, оскільки це складний URI зі складними значеннями. Ви нічого не можете зробити з цього. Як програміст знає, що собака має barkдії, які можна таємно влити POSTв неї?
Розробка API-викликів вашого запитання
Тож давайте зупинимось на погоні і спробуємо спроектувати ці гайки, подумавши з точки зору ресурсів . Дозвольте мені цитувати книгу "Відпочиваючі веб-служби" :
POSTЗапит є спробою створити новий ресурс з існуючого. Існуючий ресурс може бути батьківським новим у сенсі структури даних, так як корінь дерева є батьківським для всіх його листкових вузлів. Або існуючий ресурс може бути спеціальним "заводським"
ресурсом, єдиною метою якого є генерування інших ресурсів. Представлення, надіслане разом із POSTзапитом, описує початковий стан нового ресурсу. Як і у випадку PUT, POSTзапит взагалі не повинен містити представлення.
Після опису вище , ми можемо побачити , що barkможе бути змодельовані як в subresource зdog (так як barkміститься всередині собаки, тобто кора «вкорінення» від собаки).
З цього міркування ми вже отримали:
- Метод є
POST
- Ресурс є
/barks, субресурс собаки:, що /v1/dogs/1/barksпредставляє bark"фабрику". Цей URI унікальний для кожної собаки (оскільки вона знаходиться під /v1/dogs/{id}).
Тепер кожен випадок вашого списку має певну поведінку.
1. Кора просто надсилає електронний лист dog.emailі нічого не записує.
По-перше, чи гавкання (надсилання електронного листа) є синхронним чи асинхронним завданням? По-друге, чи barkвимагає запит будь-який документ (можливо, електронний лист) чи він порожній?
1.1 кора відправляє електронний лист dog.emailі нічого не записує (як синхронне завдання)
Цей випадок простий. Виклик на barksзаводський ресурс дає кору (електронний лист надіслано) одразу, і відповідь (якщо гаразд чи ні) надається відразу:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
Оскільки він нічого не записує (не змінює), 200 OKцього достатньо. Це показує, що все пройшло так, як очікувалося.
1.2 кора відправляє електронний лист dog.emailі нічого не записує (як асинхронне завдання)
У цьому випадку клієнт повинен мати спосіб відстежити barkзавдання. Тоді barkзавдання повинно бути ресурсом із власним URI:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Таким чином, кожен barkпростежується. Потім клієнт може GETнадати barkURI, щоб знати, що це поточний стан. Можливо, навіть скористайтеся а, DELETEщоб скасувати це.
2. Кора надсилає електронну пошту до, dog.emailа потім з кроком dog.barkCountна 1
Це може бути складніше, якщо ви хочете повідомити клієнту, що dogресурс змінюється:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
У цьому випадку locationзаголовок має намір повідомити клієнту, що він повинен поглянути dog. З HTTP RFC про303 :
Цей метод існує головним чином, щоб дозволити виводу
POSTактивованого сценарію перенаправляти агент користувача на обраний ресурс.
Якщо завдання асинхронне, потрібний barkсубресурс, як і 1.2ситуація, і він 303повинен бути повернутий у GET .../barks/Yмомент, коли завдання завершено.
3. Кора створює нову " bark" запис із bark.timestampзаписом, коли відбулася кора. Він також збільшується dog.barkCountна 1.
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Тут barkстворено завдяки запиту, тому застосовується статус 201 Created.
Якщо створення асинхронне, натомість 202 Acceptedпотрібно ( як говорить HTTP RFC ).
Збережена часова марка є частиною barkресурсу і її можна отримати за допомогою a GET. Оновлену собаку також можна «задокументувати» GET dogs/X/barks/Y.
4. Брай виконує системну команду, щоб витягнути останню версію собачого коду вниз від Github. Потім він надсилає текстове повідомлення, щоб dog.ownerповідомити їм, що новий код собаки знаходиться у виробництві.
Формулювання цього є складним, але це, як правило, є простим асинхронним завданням:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Потім клієнт видасть GETs, щоб /v1/dogs/1/barks/a65h44знати поточний стан (якщо код був витягнутий, його електронна пошта була надіслана власнику тощо). Щоразу, коли собака змінюється, а 303можна піддати.
Підведенню
Цитуючи Роя Філдінга :
Єдине, що REST вимагає від методів, це те, щоб вони були рівномірно визначені для всіх ресурсів (тобто, щоб посередники не знали тип ресурсу, щоб зрозуміти значення запиту).
У наведених вище прикладах POSTрозроблено рівномірно. Це зробить собаку " bark". Це не безпечно (означає, що кора впливає на ресурси), ні idempotent (кожен запит дає новий bark), що добре відповідає POSTдієслову.
Програміст знає: а POSTдо barksприбутків а bark. Коди статусу відповіді (також, якщо це необхідно, із суттю-органом та заголовками) виконують завдання пояснення того, що змінилося, і як клієнт може і повинен діяти.
Примітка. Основними використаними джерелами були: книга " Відпочиваючі веб-сервіси ", HTTP RFC та блог Роя Філдінга .
Редагувати:
Запитання, а отже, і відповідь досить сильно змінилися з моменту їх створення. Оригінальний питання поставлено питання про дизайн URI , як:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Нижче наведено пояснення, чому це не вдалий вибір:
Як клієнти говорять серверу, ЩО РОБИТИ з даними - це інформація про метод .
- RESTful веб-сервіси передають інформацію про метод у методі HTTP.
- Типові сервіси RPC-стилю та SOAP зберігають своє місце в тілі об'єкту та HTTP-заголовку.
НА ЯКІЙ ЧАСТИНИ даних (клієнт хоче, щоб сервер) працював, це інформація про скопіювання .
- RESTful сервіси використовують URI. Сервіси SOAP / RPC-стилю вкотре використовують заголовки тіла та тіла та HTTP.
Як приклад, візьміть URI Google http://www.google.com/search?q=DOG. Там інформація про метод є, GETа інформація про масштаби є /search?q=DOG.
Довга коротка історія:
- У архітектурах RESTful інформація про метод переходить у метод HTTP.
- У архітектурах , орієнтованих на ресурси , інформація про масштаби передається в URI.
І правило:
Якщо метод HTTP не відповідає інформації про метод, служба не RESTful. Якщо інформація про розміщення відсутня в URI, сервіс не орієнтований на ресурси.
Ви можете помістити "кора" "дії" в URL-адресу (або в тіло-сутність) і використовувати POST. Тут немає жодних проблем, це працює, і, можливо, це найпростіший спосіб зробити це, але це НЕ РЕЗУЛЬТАТИ .
Щоб ваша послуга була дійсно ВІДБУДОВОЮ, можливо, вам доведеться зробити крок назад і подумати про те, що ви насправді хочете зробити тут (які наслідки це матиме на ресурсах).
Я не можу говорити про ваші конкретні бізнес-потреби, але дозвольте навести приклад. Розгляньте послугу RESTful замовлення, де замовлення виконуються у таких URI example.com/order/123.
Тепер скажіть, що ми хочемо скасувати замовлення, як це робити? Можна спокуситись подумати, що це "скасування" "дії", і спроектувати це якPOST example.com/order/123?do=cancel .
Це не ВИПУСК, як ми говорили вище. Натомість ми можемо PUTнове представлення елемента orderз canceledелементом, надісланим true:
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
І це все. Якщо замовлення неможливо скасувати, певний код статусу можна повернути. (Дизайн субресурсів, як POST /order/123/canceledі суб'єкт-орган true, для простоти також може бути доступним.)
У вашому конкретному сценарії ви можете спробувати щось подібне. Таким чином, в той час як собака гавкає, наприклад, GETв /v1/dogs/1/можете включити цю інформацію (наприклад <barking>true</barking>) . Або ... якщо це занадто складно, послабте свою ВІДПОВІДНУ вимогу і дотримуйтесь POST.
Оновлення:
Я не хочу робити відповідь занадто великим, але потрібен певний час, щоб отримати змогу викрити алгоритм ( дію ) як набір ресурсів. Замість того, щоб думати з точки зору дій ( "виконайте пошук місць на карті" ), потрібно думати з точки зору результатів цієї дії ( "список місць на карті, що відповідають критеріям пошуку" ).
Ви можете повернутися до цього кроку, якщо виявите, що ваш дизайн не відповідає єдиному інтерфейсу HTTP.
Змінні запиту є інформацією про масштаби , але не позначають нові ресурси ( /post?lang=enце явно той самий ресурс, як /post?lang=jpпросто інше представлення). Швидше, вони використовуються для передачі стану клієнта (наприклад ?page=10, щоб цей стан не зберігався на сервері; ?lang=enтут також є прикладом) або вхідних параметрів до алгоритмічних ресурсів ( /search?q=dogs, /dogs?code=1). Знову ж таки, не окремі ресурси.
Властивості (методи) дієслів HTTP:
Ще один чіткий момент, який відображається ?action=somethingв URI, не є RESTful, - це властивості дієслів HTTP:
GETі HEADє безпечними (і ідентичними);
PUTі DELETEє лише ідентичними;
POST це не те.
Безпека : GETабо HEADзапит - це запит на читання деяких даних, а не запит на зміну будь-якого стану сервера. Клієнт може зробити GETабо HEADзапит 10 разів, і це те саме, що зробити його один раз, або взагалі ніколи не робити його .
Ідентифікація : Ідентифікаційна операція в тій, яка має той самий ефект, незалежно від того, застосовуєте ви її один раз або більше одного разу (в математиці множення на нуль - ідентичне). Якщо ви DELETEодин раз використовуєте ресурс, його видалення матиме такий же ефект (ресурс GONEвже є ).
POSTне є ні безпечним, ні безсильним. Надання двох однакових POSTзапитів на "заводський" ресурс, ймовірно, призведе до двох підпорядкованих ресурсів, що містять однакову інформацію. При перевантаженні (метод в URI або суті) POSTвсе ставки відміняються.
Обидва ці властивості були важливими для успіху протоколу HTTP (у ненадійних мережах!): Скільки разів ви оновлювали ( GET) сторінку, не чекаючи її повного завантаження?
Створення дії та розміщення її в URL-адресі явно порушує контракт методів HTTP. Знову ж таки, технологія дозволяє, ви можете це зробити, але це не RESTful дизайн.