REST дизайн для обновления / добавления / удаления элемента из списка подресурсов
Я хотел бы знать, что является наилучшей практикой, когда у вас есть ресурс, который содержит список подресурсов. Например, у вас есть ресурс Author, в котором есть информация, такая как имя, идентификатор, день рождения и список книг. Этот список книг существует только в связи с Автором. Итак, у вас есть следующий сценарий:
- Вы хотите добавить новую книгу в список книг
- Вы хотите обновить название книги из списка
- Вы хотите удалить книгу из списка
РЕШЕНИЕ 1
Я искал, что является правильным дизайном, и я нашел несколько подходов. Я хочу знать, есть ли стандартный способ разработки этого. Я думаю, что дизайн книги говорит о следующих методах:
- Добавить:
POST /authors/{authorId}/book/
- Обновлять:
PUT /authors/{authorId}/book/{bookId}
- Удалить:
DELETE /authors/{authorId}/book/{bookId}
РЕШЕНИЕ 2
Мое решение состоит в том, чтобы иметь только один метод PUT, который делает все эти 3 вещи, потому что список книг существует только внутри автора объекта, и вы фактически обновляете автора. Что-то вроде:
PUT /authors/{authorId}/updateBookList
(и отправить весь обновленный список книг внутри объекта автора)
Я нахожу несколько ошибок в моем сценарии. Например, отправка большего количества данных от клиента, наличие некоторой логики на клиенте, больше проверки на API, а также полагание, что у клиента есть последняя версия Book List.
Мой вопрос: это анти-шаблон, чтобы сделать это?
СИТУАЦИЯ 1. В моей ситуации мой API использует другой API, а не базу данных. Используемый API имеет только один метод "updateBookList", поэтому я предполагаю, что проще повторить это поведение и в моем API. Это тоже правильно?
СИТУАЦИЯ 2. Но если предположить, что мой API будет использовать базу данных, было бы более целесообразно использовать РЕШЕНИЕ 1?
Кроме того, если бы вы могли предоставить некоторые статьи, книги, где вы можете найти аналогичную информацию. Я знаю, что этот вид дизайна не написан на камне, но некоторые рекомендации помогут. (Пример: REST API Design Rulebook)
2 ответа
Решение 2 очень похоже на RPC старого стиля, где вызывается метод, выполняющий некоторую обработку. Это похоже на антипаттерн REST, поскольку REST фокусируется на ресурсах, а не на методах. Операции, которые вы можете выполнять над ресурсом, определяются базовым протоколом (в вашем случае HTTP), и поэтому REST должен придерживаться семантики базового протокола (одно из немногих ограничений).
Кроме того, REST не заботится о том, как вы настраиваете ваши URI, следовательно, на самом деле нет RESTful URL. Для автоматизированной системы URI, следующий за определенной структурой, имеет ту же семантику, что и случайно сгенерированная строка, действующая как URI. Мы, люди, вкладываем смысл в эту строку, хотя приложение должно использовать rel
атрибут, который дает URI какое-то логическое имя, которое может использовать приложение. Приложение, которое ожидает определенную логическую композицию URL, уже тесно связано с API и, следовательно, нарушает принципы, которые пытается решить REST, а именно отделение клиентов от серверных API.
Если вы хотите обновить (под) ресурсы через PUT RESTful, вы должны следовать семантике пут, которая в основном утверждает, что полученная полезная нагрузка заменяет полезную нагрузку, доступную по данному URI перед обновлением.
Метод PUT запрашивает, чтобы состояние целевого ресурса было создано или заменено состоянием, определенным представлением, заключенным в полезную нагрузку сообщения запроса.
...
Целевой ресурс в запросе POST предназначен для обработки вложенного представления в соответствии с собственной семантикой ресурса, тогда как вложенное представление в запросе PUT определяется как замена состояния целевого ресурса. Следовательно, цель PUT является идемпотентной и видимой для посредников, хотя точный эффект известен только серверу происхождения.
В отношении частичных обновлений RFC 7231 утверждает, что частичные обновления возможны либо с использованием PATCH
как предложено @Alexandru или выпуская PUT
запрашивать непосредственно у подресурса, где полезные данные заменяют содержимое подресурса содержимым в полезных данных. Для ресурса, содержащего субресурс, это влияет на частичное обновление.
Частичные обновления содержимого возможны путем нацеливания на отдельно идентифицированный ресурс с состоянием, которое перекрывает часть большего ресурса, или с помощью другого метода, который был специально определен для частичных обновлений (например, метод PATCH, определенный в [RFC5789]).
Поэтому в вашем случае вы можете отправить обновленную коллекцию книг напрямую через PUT
операция что-то вроде .../author/{authorId}/books
ресурс, который заменяет старую коллекцию. Поскольку это может не подходить для авторов, которые написали много публикаций PATCH
вероятно предпочтительнее. Обратите внимание, однако, что PATCH
требует атомарного и транзакционного поведения. Либо все действия успешны, либо нет. Если во время действий возникает ошибка, вы должны отыграть все уже выполненные шаги.
Что касается вашего запроса на дополнительную литературу, SO не является подходящим местом, чтобы спросить об этом, так как для этого есть собственная причина закрытия / пометки не по теме.
Я бы пошел с первым вариантом и имел бы отдельные методы вместо того, чтобы втиснуть всю логику в общий PUT
, Даже если вы полагаетесь на API, а не на базу данных, это всего лишь сторонняя зависимость, которую вы сможете переключать в любой момент, без необходимости слишком много реорганизовывать свой код.
При этом, если вы собираетесь разрешить обновление большого количества книг одновременно, то PATCH
может быть вашим другом
Если посмотреть на RFC 6902 (который определяет стандарт Patch), с точки зрения клиента API можно назвать как
PATCH /authors/{authorId}/book
[
{ "op": "add", "path": "/ids", "value": [ "24", "27", "35" ]},
{ "op": "remove", "path": "/ids", "value": [ "20", "30" ]}
]
Технически, решение 1 руками вниз.
URL REST API состоят из ресурсов (а также идентификаторов и имен / значений атрибутов фильтра). Он не должен содержать действия (глаголы). Использование глаголов поощряет создание глупых API.
Например, я знаю реальный производственный API, который хочет, чтобы вы
- делать
POST
на/getrecords
чтобы получить все записи - делать
POST
на/putrecords
добавить новую запись
Причины выбора решения 2 не будут техническими.
Можно использовать семантику PATCH, но для создания URL используйте семантику HTTP PATCH (tools.ietf.org/html/rfc5789), а не семантику JSON PATCH, как это предложено Александру Маркулеску.
Семантика JSON PATCH, конечно, может использоваться для разработки тела запроса PATCH.