В REST, как реагировать на POST дочерней сущности, из которой может быть только одна, и одна уже существует?

#api #rest #api-design #aggregateroot

#API #rest #api-дизайн #aggregateroot

Вопрос:

У меня есть vehicles ресурс в качестве корневой сущности агрегата. Каждое транспортное средство может иметь ноль или единицу engines в качестве суб-ресурса / объекта.

Если транспортное средство № 947 существует, но у него нет двигателя, я могу сказать:

 POST /vehicles/947/engines         /* create an engine with id=0 */

GET /vehicles/947/engines          /* read an array bearing a single engine at index 0 with id=0*/

GET /vehicles/947/engines/0        /* read the engine explicitly by id */
  

Что я должен вернуть, если вышеупомянутое было выполнено, но затем впоследствии это выполняется?

 POST vehicles/947/engines
  

У меня уже есть движок для vehicle 947, и я не могу разрешить другой. Какой статус должен быть возвращен?

Некоторые возможности, которые я рассмотрел:

  • «403 Запрещено» — в этой статье в Википедии указывается, что должно быть выдано «403 запрещено». Однако на этой странице MDN, похоже, указано, что «403 Forbidden» касается проблем с полномочиями.
  • «406 неприемлемо» — согласно MDN, это, по-видимому, больше ориентировано на ситуации, вызванные ограничениями заголовка.
  • «Конфликт 409» — за MDN, хммм, может быть.

Должен ли я структурировать путь REST по-другому? Я думаю, что показанная структура имеет смысл. Engines действительно является самостоятельной сущностью и привязана к транспортному средству. У каждого движка есть множество атрибутов, которые я действительно не хочу просто добавлять в транспортное средство; атрибуты принадлежат движку.

Мысли?

Комментарии:

1. Если у вас может быть только ровно 1 какой-то ресурс, не PUT будет ли здесь более уместным использовать стабильный URL-адрес?

2. @Evert, я уверен, что вы знаете об этом, но ради других читателей: традиционно PUT не создает, а обновляет. Таким образом, здесь необходимы как POST, так и PUT . Однако меня беспокоит, если кто-то невольно опубликует сообщение о движке там, где он уже существует. Очевидно, что они должны были выполнить PUT (после получения движка), но если вместо этого они слепо выполняют POST, что должен вернуть мой сервис?

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

4. PUT это лучший выбор для этого случая, я бы пригласил вас прочитать соответствующие фрагменты спецификации HTTP, поскольку она будет охватывать это.

5. С точки зрения структуры URL-адреса я бы просто в вашем случае поддержал GET и PUT включил /vehicles/947/engine . Очень странно, что вы поместили сюда коллекцию «движков», если она когда-либо будет только одна.

Ответ №1:

Первый момент: в реестре кода состояния IANA перечислены авторитетные ссылки на семантику каждого кода состояния. Большинство распространенных из них определены последней спецификацией HTTP; сегодня это означает RFC 7231.

Второй момент: коды состояния — это метаданные, дающие подсказки о семантике ответа компонентам общего назначения (например, веб-браузерам или кэшам). Использование кодов состояния стандартными способами означает, что наши ресурсы API выглядят так же, как любая другая страница в Интернете.

Другими словами, мы можем ответить на подобные вопросы, рассмотрев «что будет делать веб-сервер?» или «как код состояния изменяет поведение компонентов общего назначения?»

Разумный выбор для вашего примера:

  • 403 «Я понял ваш запрос, но я отказываюсь его санкционировать».
  • 405 «Этот ресурс сейчас не поддерживает этот метод»
  • 409 «Это редактирование конфликтует с текущим состоянием ресурса»

405 «кэшируется по умолчанию», что является одним из способов отличить их друг от друга; кроме того, для этого кода состояния требуется заголовок Allow для описания поддерживаемых в настоящее время методов.

В противном случае между этими кодами нет большой разницы — браузеры не будут обрабатывать их по-разному, то же самое с кэшами.

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

Должен ли я структурировать путь REST по-другому?

Возможно. REST не заботится о сущностях, он заботится о документах (ресурсы, являющиеся обобщением документов, по большей части).

Итак, является ли документ о двигателе тем же документом, что и документ о транспортном средстве, или они должны быть двумя отдельными документами (возможно, связанными ссылкой)? Должны ли они кэшироваться вместе (это означает, что аннулирование одного документа также делает недействительным другой) или по отдельности (один документ часто меняется, другой редко)?

Это тот же компромисс, который мы делаем, когда решаем, помещать ли java script в тег script в HTML-документе или иметь ссылку из HTML-документа на javascript. Любой ответ может иметь смысл при правильных условиях.

Варианты написания URI в порядке. Если когда-либо будет только один ресурс движка, вы можете рассмотреть возможность использования идентификатора /vehicles/947/engine . Также может иметь смысл хранить документы движка в коллекции документов движка : /engines/947 . Если вы ожидаете использовать точечные сегменты для идентификации других документов, то может быть удобнее иметь идентификаторы в той же иерархии.

Комментарии:

1. Хорошая обратная связь @VoiceOfUnreason. Вы и @Evert, похоже, согласны с тем, чтобы сделать /vehicles/947/engine сингулярный. Более того, вы оба предлагаете рассмотреть возможность перехода на /engines/947 . Я обязательно рассмотрю. Большое спасибо.

2. VoiceOfUnreason и @Evert, я хотел бы услышать ваши дальнейшие мысли по этому поводу: одна из трудностей при переходе к /engines/947 структуре заключается в том, что для движка, связанного с vehicle 947, я все еще не могу POST /engines , но, скорее, я должен сначала GET /engines/947 , а затем PUT /engines/947 . То есть запрашивающая сторона должна контролировать идентификатор и гарантировать отсутствие непреднамеренной перезаписи. Структура GET /vehicles/947/engine и PUT /vehicles/947/engine кажется немного более естественной. Я ценю вашу помощь.