Зачем стремиться к дизайну 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
в bark
URI , чтобы знать это текущее состояние. Может быть, даже использовать, 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
Затем клиент выдаст GET
s, чтобы /v1/dogs/1/barks/a65h44
узнать текущее состояние (если код был извлечен, электронное письмо было отправлено владельцу и т. Д.). Всякий раз, когда собака меняется, применяется 303
a.
Подведение итогов
Цитата Роя Филдинга :
Единственное, что REST требует от методов, - это чтобы они были единообразно определены для всех ресурсов (т. Е. Чтобы посредникам не нужно было знать тип ресурса, чтобы понять смысл запроса).
В приведенных выше примерах POST
спроектирован единообразно. Это сделает собака " bark
". Это небезопасно (это означает, что лай оказывает влияние на ресурсы) или идемпотентным (каждый запрос дает новый bark
), что хорошо соответствует POST
глаголу.
Программист знал бы: от a POST
до barks
a 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
.)
В вашем конкретном сценарии вы можете попробовать нечто подобное. Таким образом, когда собака лает, например, GET
at /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
ни то, ни другое.
Безопасность : запрос GET
or HEAD
- это запрос на чтение некоторых данных, а не запрос на изменение состояния сервера. Клиент может сделать GET
или HEAD
запросить 10 раз, и это то же самое, что сделать это один раз или не сделать вообще .
Идемпотентность : идемпотентная операция в одном, которая имеет одинаковый эффект независимо от того, применяете ли вы ее один или более одного раза (в математике умножение на ноль идемпотентно). Если вы DELETE
используете ресурс один раз, повторное удаление будет иметь тот же эффект (ресурс GONE
уже есть).
POST
не является ни безопасным, ни идемпотентным. Выполнение двух идентичных POST
запросов к «фабричному» ресурсу, вероятно, приведет к тому, что два подчиненных ресурса будут содержать одинаковую информацию. При перегруженном (метод в URI или теле объекта) POST
все ставки отключены.
Оба эти свойства были важны для успеха протокола HTTP (в ненадежных сетях!): Сколько раз вы обновляли ( GET
) страницу, не дожидаясь ее полной загрузки?
Создание действия и его размещение в URL-адресе явно нарушает контракт методов HTTP. Еще раз: технология позволяет, вы можете это делать, но это не RESTful-дизайн.