Зачем стремиться к дизайну 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 (или гибридом REST-RPC). Первый шаг - думать иначе. RESTful-дизайн может быть реализован разными способами, один из них - думать о своем приложении с точки зрения ресурсов, а не действий:
💡 Вместо того, чтобы думать о действиях, которые он может выполнять («поискать места на карте») ...
... попробуйте мыслить категориями результатов этих действий («список мест на карте, соответствующих критериям поиска»).
Я приведу примеры ниже. (Другой ключевой аспект REST - это использование HATEOAS - я не прикрашиваю его здесь, но я быстро расскажу об этом в другом посте .)
Проблемы первого дизайна
Посмотрим на предлагаемую конструкцию:
ACTION http://api.animals.com/v1/dogs/1/
Во-первых, нам не следует рассматривать создание новой HTTP-команды ( 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) , а не документ процессора.
Итак, все, что мы знаем сейчас, - это то, что приведенный выше дизайн не такой уж RESTful, но что это именно? Что в этом плохого? По сути, это плохо, потому что это сложный URI со сложным значением. Вы ничего не можете сделать из этого. Откуда программисту знать, что у собаки есть barkдействие, которое можно тайно вложить POSTв него?
Разработка вызовов API вашего вопроса
Итак, давайте перейдем к делу и попробуем спроектировать этот лай в режиме ОТДЫХА, думая о ресурсах . Позвольте мне процитировать книгу Restful Web Services :
POSTЗапрос является попыткой создать новый ресурс из существующего. Существующий ресурс может быть родителем нового в смысле структуры данных, как корень дерева является родителем всех его листовых узлов. Или существующий ресурс может быть специальным
ресурсом «фабрики» , единственная цель которого - генерировать другие ресурсы. Представление, отправленное вместе с POSTзапросом, описывает начальное состояние нового ресурса. Как и в случае с PUT, POSTзапрос вообще не должен включать представление.
После описания выше , мы можем увидеть , что barkможет быть смоделированы как в subresource изdog (так как barkсодержится внутри собаки, то есть кора «окорененная» от собаки).
Из этого рассуждения мы уже получили:
- Метод
POST
- Ресурс
/barks, подресурс dog:, /v1/dogs/1/barksпредставляет собой bark«фабрику». Этот URI уникален для каждой собаки (так как он находится ниже /v1/dogs/{id}).
Теперь каждый случай из вашего списка имеет определенное поведение.
1. bark просто отправляет электронное письмо dog.emailи ничего не записывает.
Во-первых, лай (отправка электронного письма) - это синхронная или асинхронная задача? Во-вторых, barkтребуется ли для запроса какой-либо документ (может быть, электронная почта) или он пустой?
1.1 bark отправляет электронное письмо dog.emailи ничего не записывает (как синхронная задача)
Это простой случай. Обращение к barksресурсу factory сразу же дает лай (отправленное электронное письмо), и сразу же выдается ответ (если все в порядке или нет):
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 bark отправляет электронное письмо 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. bark отправляет электронное письмо на адрес, 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должен быть возвращен в a, GET .../barks/Yкогда задача будет завершена.
3. bark создает новую " 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ресурса и может быть извлечена с помощью элемента GET. Обновленная собака также может быть «задокументирована» GET dogs/X/barks/Y.
4. bark запускает системную команду для получения последней версии кода собаки с 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узнать текущее состояние (если код был извлечен, электронное письмо было отправлено владельцу и т. Д.). Всякий раз, когда собака меняется, применяется 303a.
Подведение итогов
Цитата Роя Филдинга :
Единственное, что REST требует от методов, - это чтобы они были единообразно определены для всех ресурсов (т. Е. Чтобы посредникам не нужно было знать тип ресурса, чтобы понять смысл запроса).
В приведенных выше примерах POSTспроектирован единообразно. Это сделает собака " bark". Это небезопасно (это означает, что лай оказывает влияние на ресурсы) или идемпотентным (каждый запрос дает новый bark), что хорошо соответствует POSTглаголу.
Программист знал бы: от a POSTдо barksa bark. Коды состояния ответа (также с телом объекта и заголовками, если необходимо) объясняют, что изменилось и как клиент может и должен действовать.
Примечание. В качестве первичных источников использовались: книга « Restful Web Services », HTTP RFC и блог Роя Филдинга .
Редактировать:
Вопрос и, следовательно, ответ немного изменились с момента их создания. Оригинальный вопрос задан вопрос о дизайне URI , как:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Ниже приводится объяснение того, почему это не лучший выбор:
Как клиенты сообщают серверу, ЧТО ДЕЛАТЬ с данными - это информация о методе .
- Веб-службы RESTful передают информацию о методах в методе HTTP.
- Типичные службы стиля RPC и SOAP сохраняют свои в теле объекта и заголовке HTTP.
КАКАЯ ЧАСТЬ данных [клиент хочет, чтобы сервер] оперировал, является информацией об объеме .
- Службы RESTful используют URI. Сервисы в стиле SOAP / RPC снова используют заголовки entity-body и HTTP.
В качестве примера возьмем URI Google http://www.google.com/search?q=DOG. Там информация о методе и информация GETоб объеме /search?q=DOG.
Короче говоря:
- В архитектурах RESTful информация о методе передается в метод HTTP.
- В ресурсо-ориентированной архитектуре информация об области действия передается в URI.
И практическое правило:
Если метод HTTP не соответствует информации о методе, служба не является RESTful. Если информации об области нет в URI, служба не ориентирована на ресурсы.
Вы можете поместить «лай» «действие» в URL-адрес (или в тело объекта) и использовать POST. Нет проблем, это работает и, возможно, самый простой способ сделать это, но это не RESTful .
Чтобы ваш сервис оставался действительно RESTful, вам, возможно, придется сделать шаг назад и подумать о том, что вы действительно хотите здесь сделать (какое влияние это окажет на ресурсы).
Я не могу говорить о ваших конкретных бизнес-потребностях, но позвольте мне привести пример: рассмотрим службу заказов RESTful, где заказы размещаются по URI, например example.com/order/123.
Теперь предположим, что мы хотим отменить заказ, как мы это сделаем? У кого-то может возникнуть соблазн подумать, что это « действие» «отмены », и спроектировать его как POST example.com/order/123?do=cancel.
Это не RESTful, как мы говорили выше. Вместо этого мы могли бы 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.)
В вашем конкретном сценарии вы можете попробовать нечто подобное. Таким образом, когда собака лает, например, GETat /v1/dogs/1/может включать эту информацию (например <barking>true</barking>) . Или ... если это слишком сложно, ослабьте требования RESTful и придерживайтесь их 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 ни то, ни другое.
Безопасность : запрос GETor HEAD- это запрос на чтение некоторых данных, а не запрос на изменение состояния сервера. Клиент может сделать GETили HEADзапросить 10 раз, и это то же самое, что сделать это один раз или не сделать вообще .
Идемпотентность : идемпотентная операция в одном, которая имеет одинаковый эффект независимо от того, применяете ли вы ее один или более одного раза (в математике умножение на ноль идемпотентно). Если вы DELETEиспользуете ресурс один раз, повторное удаление будет иметь тот же эффект (ресурс GONEуже есть).
POSTне является ни безопасным, ни идемпотентным. Выполнение двух идентичных POSTзапросов к «фабричному» ресурсу, вероятно, приведет к тому, что два подчиненных ресурса будут содержать одинаковую информацию. При перегруженном (метод в URI или теле объекта) POSTвсе ставки отключены.
Оба эти свойства были важны для успеха протокола HTTP (в ненадежных сетях!): Сколько раз вы обновляли ( GET) страницу, не дожидаясь ее полной загрузки?
Создание действия и его размещение в URL-адресе явно нарушает контракт методов HTTP. Еще раз: технология позволяет, вы можете это делать, но это не RESTful-дизайн.