Именование сложных действий в REST API
Во всех руководствах в описаниях REST дают простые примеры, типа вот вам пользователи, они будут ресурсом /users, вот вам один пользователь, он будет /users/[id] и действия с ним добавить\удалить\изменить.
А что если действия сложные или комплексные и не вписываются в GET\POST\DELETE?
Пытаясь разобраться, задал вопрос на тостере и продолжил дальнейшие раскопки.
Нашел исчерпывающую статью, но полный перевод не осилю, так что сведу сделанные выводы в короткую заметку.
Вкратце о проблеме:
Главное отличие REST от RPC заключается в подходе, что URL — адрес ресурса, действия с которым выполняются с помощью стандартных HTTP-методов: GET\POST\DELETE\PATCH\… Если кратко — ресурс это существительное, а не глагол.
Т.е. если вы видите что-то типа /api/users/do_some_cool_staff или GET /users/1?action=delete, знайте — это не REST!
Предположим, мы пишем приложение «ядерный чемоданчик» с REST интерфейсом. Функционал будет такой:
1. Действия с ракетой [id]: запуск по стране, продажа в страну, обслуживание, списать в утиль
Логично использовать ресурс /missiles/[id], но для этих действий просто использовать PUT или PATCH некорректно и неинформативно.
Неправильные варианты:
1. Можно расширять список стандартных HTTP-методов. Но могут быть сложности с поддержкой у разных клиентов, готовых библиотек и фильтрации настройками сервера.
2. /missiles/[id]?action=launch лишает смысла весь REST подход, т.к. бОльшая часть функционала будет в действиях.
3. /missiles/[id]/launch — тащить глагол в адрес ресурса неправильно.
4. LINK /missiles/[id]/countries/[country_id]/ — непонятно что именно происходит — продажа или ядерный удар.
Правильный вариант
Правильно обращаться к свойствам ресурса. Т.е. не действие "/users/[id]/disable", а ресурс "/users/[id]/disabled", к которому логично применимы стандартные методы GET\POST\…
Продажа ракеты: POST /countries/[country_id]/missiles-bought/[id] или POST /missiles/[id]/owner/[country_id]
Обслуживание: POST /missiles/[id]/on-service
UPD:
Запуск ракеты: POST /missiles/[id]/launcher {target: [country_id]}
Продажа ракеты: PATCH /missiles/[id] {owner: [country_id]}
Обслуживание: PUT /missiles/[id]/on-service или если есть отдельный сервис обслуживания ракет POST /missiles_service/ {missile: [id]}
Списать в утиль: DELETE /missiles/[id] или если могут быть другие варианты «удаления» PUT /missiles/[id]/utilized
2. Запустить любую подходящую ракету по стране [country_id]
Предположим, что ракет очень дофига и делать сначала поиск, а потом запуск накладно и долго, нужен один запрос на моментальное исполнение.
Неправильные варианты:
1. Выполнить «запуск» на всю коллекцию ракет ([id] конкретной ракеты мы не знаем).
2. Действие «получить ракетой» у выбранной страны
Правильный вариант
Если действие комплексное и не может быть выражено через атомарные действия над ресурсом-объектом, создается ресурс-процесс. Аналогично ресурс-процесс создается в случае, если требуется произвести составное действие со сложной бизнес-логикой (состоящее из нескольких зависимых действий), например банковская транзакция.
Мысль более развернуто
Let us relook at what Roy Fielding’s dissertation says about resource: “...any concept that might be the target of an author's hypertext reference must fit within the definition of a resource….”. Business capabilities / processes can neatly fit the definition of resources. In other words, for complex business processes spanning multiple resources, we can consider the business process as a resource itself. For example, the process of setting up a new customer in a banking domain can be modeled as a resource. CRUD is just a minimal business process applicable to almost any resource. This allows us to model business processes as true resources which can be tracked in their own right.
Т.е. правильным будет создание ресурса «Запуск ракеты».
Тогда POST будет запускать ракету, GET узнавать состояние процесса, DELETE отменять его. При этом сохраняется консистентность, т.е. ракета уже летит, а статус страны «еще существует».
Бонусом:
Оказалось, что многие крупные компании вообще не имеют представления, что такое REST, при этом в описании используют именно это название. Так что нельзя доверять всему, что написано
Пара примеров ахтунгов:
-
Twitter
POST friendships/create
POST friendships/destroy
POST friendships/update
GET friendships/show
GET friends/list
-
Mail.ru
Все вызовы методов API — это GET или POST HTTP-запросы к URL www.appsmail.ru/platform/api с некоторым набором параметров. [...] На данный момент, API не делает различий между GET- и POST-запросами.
Единственное приличное REST API, которое я смог обнаружить GitHub. Аминь.