REST - вставлять ID в тело или нет?


103

Скажем, я хочу иметь ресурс RESTful для людей, которому клиент может назначать идентификатор.

Выглядит человек так: {"id": <UUID>, "name": "Jimmy"}

Теперь, как клиент должен сохранить (или «ПОСТАВИТЬ») его?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - теперь у нас есть это неприятное дублирование, которое мы должны постоянно проверять: совпадает ли идентификатор в теле с идентификатором в пути?
  2. Асимметричное представление:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID возвращается {"id": <UUID>, "name": "Jimmy"}
  3. В теле нет ID - ID только в локации:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID возвращается {"name": "Jimmy"}
  4. Это не POSTкажется хорошей идеей, поскольку идентификатор генерируется клиентом.

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

Ответы:


65

Нет ничего плохого в том, чтобы иметь разные модели чтения / записи: клиент может написать одно представление ресурса, где после того, как сервер может вернуть другое представление с добавленными / вычисленными элементами в нем (или даже совершенно другое представление - в любой спецификации нет ничего против этого , единственное требование - PUT должен создавать или заменять ресурс).

Поэтому я бы выбрал асимметричное решение в (2) и избегал "неприятной проверки дублирования" на стороне сервера при написании:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

2
И если вы применяете типизацию (статическую или динамическую), вы не можете безболезненно иметь модели без идентификатора ... Так что гораздо проще удалить идентификатор из URL для запросов PUT. Это не будет «успокаивающим», но будет правильным.
Иван Клешнин

2
Сохраняйте дополнительные TO без idTO с идентификаторами и объектами, дополнительными конвертерами и слишком большими накладными расходами для программистов.
Григорий Кислин

Что, если я получу идентификатор от BODY, например: PUT / person {"id": 1, "name": "Jimmy"}. Будет ли это плохой практикой?
Бруно Сантос

Вставить ID в тело было бы хорошо. Используйте GUID для идентификатора вместо целого числа, иначе вы рискуете получить повторяющиеся идентификаторы.
Йорн Вильдт,

Это не правильно. Смотрите мой ответ. PUT должен содержать весь ресурс. Используйте PATCH, если вы хотите исключить идентификатор и обновить только части записи.
CompEng88,

27

Если это общедоступный API, вы должны быть консервативны, когда отвечаете, но соглашайтесь свободно.

Под этим я подразумеваю, что вы должны поддерживать и 1, и 2. Я согласен, что 3 не имеет смысла.

Способ поддержки как 1, так и 2 состоит в том, чтобы получить идентификатор из URL-адреса, если он не указан в теле запроса, и если он находится в теле запроса, затем проверить, соответствует ли он идентификатору в URL-адресе. Если они не совпадают, вернуть ответ 400 Bad Request.

При возврате ресурса person будьте консервативны и всегда включайте идентификатор в json, даже если он не является обязательным в put.


3
Это должно быть приемлемое решение. API всегда должен быть удобным для пользователя. Это должно быть необязательно в теле. Я не должен получать идентификатор из POST, а затем делать его неопределенным в PUT. Кроме того, ответ на 400 пунктов верен.
Майкл

Примерно 400 кодов см. Также в обсуждении softwareengineering.stackexchange.com/questions/329229/… . Короче говоря, код 400 не является неподходящим, просто менее конкретным, по сравнению с 422.
Григорий Кислин

8

Одно из решений этой проблемы включает в себя несколько сбивающую с толку концепцию «Гипертекст как механизм состояния приложения» или «HATEOAS». Это означает, что ответ REST содержит доступные ресурсы или действия, которые необходимо выполнить, в виде гиперссылок. При использовании этого метода, который был частью первоначальной концепции REST, уникальные идентификаторы / идентификаторы ресурсов сами являются гиперссылками. Так, например, у вас может быть что-то вроде:

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

Затем, если вы хотите обновить этот ресурс, вы можете сделать (псевдокод):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

Одним из преимуществ этого является то, что клиент не должен иметь никакого представления о внутреннем представлении идентификаторов пользователей на сервере. Идентификаторы могут измениться, и даже сами URL-адреса могут измениться, если у клиента есть способ их обнаружить. Например, при получении коллекции людей вы можете вернуть такой ответ:

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(Вы можете, конечно, также вернуть полный объект person для каждого человека, в зависимости от потребностей приложения).

Используя этот метод, вы думаете о своих объектах больше с точки зрения ресурсов и местоположений, а не с точки зрения идентификатора. Таким образом, внутреннее представление уникального идентификатора отделено от вашей клиентской логики. Это было первоначальным стимулом для REST: создавать архитектуры клиент-сервер, которые более слабо связаны, чем системы RPC, которые существовали раньше, с использованием функций HTTP. Для получения дополнительной информации о HATEOAS, посмотрите статью в Википедии, а также эту короткую статью .


4

Во вставке вам не нужно добавлять идентификатор в URL-адрес. Таким образом, если вы отправляете идентификатор в PUT, вы можете интерпретировать его как UPDATE для изменения первичного ключа.

  1. ВСТАВИТЬ:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. ОБНОВИТЬ

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

JSON API использует этот стандарт и решают некоторые проблемы , возвращающихся вставленный или обновленный объект со ссылкой на новый объект. Некоторые обновления или вставки могут включать некоторую бизнес-логику, которая изменяет дополнительные поля.

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


3

Об этом уже спрашивали - стоит взглянуть на обсуждение:

Должен ли ответ RESTful GET возвращать идентификатор ресурса?

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

Как бы то ни было, я стараюсь мыслить категориями согласованных ресурсов и не менять их структуру между методами. Однако, IMHO, самое важное с точки зрения удобства использования - это то, что вы единообразны во всем API!


2

Хотя можно иметь разные представления для разных операций, общая рекомендация для PUT - содержать ВСЕ полезные данные . Значит, idэто тоже должно быть. В противном случае вам следует использовать PATCH.

Сказав это, я думаю, что PUT в основном следует использовать для обновлений, и его также idследует всегда передавать в URL-адресе. В результате использование PUT для обновления идентификатора ресурса - плохая идея. Это оставляет нас в нежелательной ситуации, когда idURL-адрес может отличаться от URL-адреса idв теле.

Итак, как нам разрешить такой конфликт? В основном у нас есть 2 варианта:

  • выбросить исключение 4XX
  • добавить заголовок WarningX-API-Warnт. д.).

Это настолько близко, насколько я могу ответить на этот вопрос, потому что в целом тема - это вопрос мнения.


1

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

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

он в основном используется таким образом, даже если структура сущностей использует этот метод, когда сущность добавляется в dbContext, класс без сгенерированного идентификатора - это идентификатор, созданный по ссылке в Entity Framework.


1

Я смотрю на это с точки зрения JSON-LD / Semantic Web, потому что это хороший способ добиться реального соответствия REST, как я обрисовал на этих слайдах . Глядя на это с этой точки зрения, нет никаких сомнений в том, чтобы выбрать вариант (1.), поскольку идентификатор (IRI) веб-ресурса всегда должен быть равен URL-адресу, который я могу использовать для поиска / разыменования ресурса. Я думаю, что верификацию нетрудно реализовать, и она не требует больших вычислений; поэтому я не считаю это веской причиной для выбора варианта (2.). Я думаю, что вариант (3.) на самом деле не вариант, поскольку POST (создать новый) имеет другую семантику, чем PUT (обновление / замена).


1

Просто к вашему сведению, ответы здесь неправильные.

Увидеть:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

СТАВИТЬ

Используйте PUT API в первую очередь для обновления существующего ресурса (если ресурс не существует, API может решить создать новый ресурс или нет). Если новый ресурс был создан с помощью PUT API, исходный сервер ДОЛЖЕН проинформировать пользовательский агент с помощью ответа HTTP с кодом 201 (Создан) ответ, а если существующий ресурс изменен, либо 200 (ОК), либо 204 (Нет содержимого) коды ответа ДОЛЖНЫ быть отправлены, чтобы указать на успешное выполнение запроса.

Если запрос проходит через кеш и Request-URI идентифицирует один или несколько кешированных в данный момент объектов, эти записи СЛЕДУЕТ рассматривать как устаревшие. Ответы на этот метод не кэшируются.

Используйте PUT, если вы хотите изменить отдельный ресурс, который уже является частью коллекции ресурсов. PUT заменяет ресурс полностью. Используйте PATCH, если запрос обновляет часть ресурса.

ПАТЧ

Запросы HTTP PATCH предназначены для частичного обновления ресурса. Если вы видите, что запросы PUT также изменяют объект ресурса, чтобы было понятнее, метод PATCH является правильным выбором для частичного обновления существующего ресурса, а PUT следует использовать только в том случае, если вы полностью заменяете ресурс.

Итак, вы должны использовать его таким образом:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

Практика RESTful показывает, что не имеет значения, что вы ПОСТАВЛЯЕТЕ в / {id} - содержимое записи должно быть обновлено до того, которое предоставляется полезной нагрузкой, - но GET / {id} все равно должен ссылаться на тот же ресурс.

Другими словами, PUT / 3 может обновить идентификатор полезной нагрузки до 4, но GET / 3 должен по-прежнему связываться с той же полезной нагрузкой (и возвращать тот, у которого идентификатор установлен на 4).

Если вы решаете, что вашему API требуется один и тот же идентификатор в URI и полезной нагрузке, ваша задача - убедиться, что он совпадает, но обязательно используйте PATCH вместо PUT, если вы исключаете идентификатор в полезной нагрузке, которая должна быть там полностью . Вот где принятый ответ ошибся. PUT должен заменить весь ресурс, в то время как исправление может быть частичным.


В вашем абзаце: «Другими словами, PUT / 3 может обновить идентификатор полезной нагрузки до 4, но GET / 3 должен по-прежнему связываться с той же полезной нагрузкой (и возвращать тот, у которого идентификатор установлен на 4 )». - в последнем id вы имели в виду 3 вместо 4 ?
mlst

Re: «используйте PATCH вместо PUT, если вы исключаете идентификатор» . Это плохо информировано. 2014 в RFC 7231 (один из РЛКА , что устаревший 1999 год в RFC 2616) упоминает здесь , что сервер может иметь transformation applied to the body. Есть даже механизм для клиента allows a user agent to know when the representation body it has in memory remains current, которым является сервер MUST NOT send ... an ETag or Last-Modified ... unless the request's representation was saved without any transformation.
Славомир Бжезинский

Также см. Этот вопрос SO + ответ других пользователей, содержащий обоснование целесообразности использования PUT. В нем красиво резюмируется: «Важно понять, в чем заключалась цель клиентского запроса. Клиент намеревался полностью заменить содержимое ресурса переданными значениями».
Славомир Бжезинский,

0

Возможно, вам придется изучить типы запросов PATCH / PUT.

Запросы PATCH используются для частичного обновления ресурса, тогда как в запросах PUT вы должны отправить весь ресурс, где он будет переопределен на сервере.

Что касается наличия идентификатора в URL-адресе, я думаю, вы всегда должны иметь его, поскольку это стандартная практика для идентификации ресурса. Так работает даже Stripe API.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.