Навіщо прагнути до проекту 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
надати bark
URI, щоб знати, що це поточний стан. Можливо, навіть скористайтеся а, 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
Потім клієнт видасть GET
s, щоб /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 дизайн.