API RESTful, когда несколько действий на одном и том же URI

Насколько я знаю, в RESTful API используются четыре типа методов:

GET для получения ресурса.
POST для обновления ресурса.
PUT для создания или замены ресурса.
УДАЛИТЬ для удаления ресурса.

Предположим, что у нас есть ресурс с именем apple, и мы можем его "обновить" несколькими способами. Например, попробуйте его, нарежьте или сделайте яблочный сок.
Каждое из этих трех различных действий по обновлению принимает разные аргументы и их API, общая часть будет:

POST /apple HTTP/1.1
Host: www.example.com

<different combination of arguments>

В этой ситуации три API-интерфейса имеют один и тот же URI и один и тот же метод запросов, единственными их отличиями являются аргументы. Я думаю, что это заставляет бэкэнд быть готовым принять набор объединений этих аргументов и различать, какое действие действительно запрашивается, бэкэнд должен проверить комбинацию аргументов. Это так сложно и не изящно.

Итак, мой вопрос:
В этих случаях Apple, как разработать элегантный набор RESTful API, которые делают бэкэнд легко справиться с ним.

Ответ 1

Прежде всего, постарайтесь избежать объединения HTTP-методов в операции CRUD. Я считаю, что основным источником путаницы в REST. Методы HTTP не переводят на операции CRUD чисто так. У меня есть подробный ответ здесь:

S3 REST API и метод POST

Короче.

  • POST - это метод, используемый для любой операции, которая не стандартизирована по протоколу HTTP и подвергает полезную нагрузку целевому URI.
  • PUT используется для полной замены ресурса в настоящем URI и подвергает полезную нагрузку самой службе.
  • PATCH предназначен для частичных идемпотентных обновлений с разницей между текущим и желаемым состоянием.
  • DELETE используется для удаления ресурса.
  • GET используется для извлечения ресурса.

Теперь, на стороне сервера, попробуйте подумать о ресурсах REST, как о машине состояний, где вы можете использовать методы для принудительного перехода, а не объекта с помощью методов. Таким образом, вы фокусируете реализацию на самом ресурсе, а не на взаимодействии с протоколом. Например, вы можете легко изменить атрибуты объекта из полезной нагрузки метода, а затем вызвать метод, который вызывается для определения того, какой переход необходим.

Например, вы можете думать о том, что яблоко имеет три состояния, целые, обрезанные, нарезанные и сочные. Вы переходите между состояниями, используя стандартизованное поведение методов.

Например:

GET /apple 

{"state": "whole",
 "self": "/apple"}

Затем вы хотите разрезать его. Вы можете сделать что-то вроде:

PUT /apple

{"state": "sliced"}

Или вы можете сделать что-то вроде:

PATCH /apple

{"from_state": "whole", "to_state": "sliced"}

Или даже что-то вроде:

POST /apple

{"transition": "slice"}

Идея состоит в том, что реализации могут быть достаточно обобщенными, и вам не нужно слишком беспокоиться о связи ресурса с методами HTTP.

  • Версия PUT является идемпотентной, поэтому ваши клиенты могут использовать ее, когда им нужна идемпотенция.
  • Версия PATCH гарантирует, что клиент знает текущее состояние и пытается выполнить действительный переход.
  • Версия POST является самой гибкой, вы можете делать все, что хотите, но ее необходимо документировать подробно. Вы не можете просто предположить, что ваши клиенты будут знать, как работает этот метод.

Пока ваша реализация ресурса понимает, что когда apple.state изменено на что-то еще, он должен определить, какое изменение произошло и выполнить адекватный переход, вы полностью отделены от протокола. Неважно, какой метод был использован.

Я считаю, что это самое элегантное решение и упрощает управление с внутренней стороны. Вы можете реализовать свои объекты, не слишком беспокоясь о протоколе. Пока объекты могут быть преобразованы между состояниями, они могут использоваться любым протоколом, который может влиять на эти переходы.

Ответ 2

Мой RESTful HTTP API отличается от вашего. У меня есть:

GET для получения ресурса.
POST для добавления нового ресурса в коллекцию.
PUT для замены ресурса (включая обрезку коллекций).
УДАЛИТЬ для удаления ресурса.
PATCH для обновления ресурса.
LINK для указания отношения между двумя ресурсами.
UNLINK для устранения связи между двумя ресурсами.

Ресурс листьев можно также рассматривать как коллекцию.

Например, скажем, что у вас есть /fruits и POST a apple для этого ресурса коллекции, который возвращает

201 Created
Location: /fruits/apple

Таким же образом вы можете рассматривать /fruits/apple как набор его свойств, поэтому:

GET /fruits/apple
->
colour=red&diameter=47mm

GET /fruits/apple/colour
->
red

GET /fruits/apple/diameter
->
47mm

и поэтому:

PUT /fruits/apple/slices
"12"
->
201 Created

GET /fruits/apple
->
colour=red&diameter=47mm&slices=12

Итак, я бы рекомендовал представить ваши действия как существительные и найти эти существительные в качестве под-ресурсов ресурса, к которому вы хотите применить действие.

Ответ 3

Подумайте о ресурсах. Здесь Apple является ресурсом.

Чтобы добавить одно или несколько яблок в список "/apples", используйте POST. Стиль REST позволяет размещать массив.

POST /apples HTTP/1.1
Host: www.example.com

Теперь предположим, что у вас есть яблоко с ID 123. Вы можете получить детали, используя метод GET на "/apple/123".

GET /apples/123 HTTP/1.1
Host: www.example.com

Чтобы внести какие-либо изменения в apple 123, просто отправьте его прямо к нему.

PUT /apples/123 HTTP/1.1
Host: www.example.com

Обманите его, нарежьте его или сделайте из него яблочный сок - все это в основном меняет некоторые атрибуты яблока. 123. Как вы говорили (правильно), используйте разные комбинации атрибутов.

Ответ 4

Я думаю, что это решать разработчику, но я вижу два подхода. Строго с точки зрения единой ответственности может иметь смысл предоставлять отдельные услуги для этих различных операций.

Однако, если вы настаиваете на единственном сервисе, я думаю, вы можете передать объект с квалификатором типа действия, чтобы упростить делегирование запроса другому коду в службе. Затем один объект может иметь другие необязательные параметры для поддержки потребностей в данных каждой операции.