Нарушаю ли я RESTfulness при использовании POST как UPDATE OR CREATE?

Учитывая требования других отделов к нашему REST API, они хотели бы использовать POST не только для СОЗДАНИЯ, но и для ОБНОВЛЕНИЯ ИЛИ СОЗДАНИЯ. Я знаю, что в RESTful API для этого можно или нужно использовать PUT, но поскольку клиенты должны обновлять информацию, используемую для построения URI, мы не можем это использовать. Это изменит URI и сделает PUT больше не идемпотентным... (старый URI не будет существовать после первого PUT).
tl;dr мы не можем использовать PUT

В спецификациях HTTP/1.1 POST определяется как

Метод POST используется для запроса того, чтобы исходный сервер принял объект, включенный в запрос, в качестве нового подчиненного ресурса, идентифицированного Request-URI.

но также

Действие, выполняемое методом POST, может не привести к получению ресурса, который можно идентифицировать по URI.

Чтобы оставаться RESTful, я бы объяснил функциональность обновления как удаление старого элемента, а затем создание нового, что было бы приемлемой функциональностью для POST, я бы сказал.

Мы возвращали #201, когда создание было успешным, и #200, когда это было просто обновление.


В нашем API "возможно" обратиться к нужному элементу без URI (например, для его обновления с помощью POST), поскольку все части первичного ключа построения URI находятся в теле ресурса, поэтому API знает, какой элемент клиент хочет получить доступ.


Пример

(Это всего лишь пример поведения для POST. Не для структуры данных ресурса. Конечно, использование PUT было бы полностью правильным для /cars/)

POST /cars/ HTTP/1.1
<car>
    <id>7</id>
    <status>broken</status>
</car>

ответ: #201
и затем

POST /cars/ HTTP/1.1
<car>
    <id>7</id>
    <status>fine</status>
</car>

ответ: #200
теперь GET на /cars/7 вернет следующее:

<car>
    <id>7</id>
    <status>fine</status>
</car>

person Robert    schedule 18.09.2014    source источник
comment
Я только что удалил свой первоначальный комментарий после просмотра вашего примера, поскольку ваша транзакция кажется идемпотентной. Вы устанавливаете состояние автомобиля 7 как хорошее. Если бы вы повторяли этот метод снова и снова, вы бы все равно получили тот же результат. Поэтому нет веских причин, по которым этот конкретный пример не может быть методом PUT.   -  person ydaetskcoR    schedule 18.09.2014
comment
Этот ответ лучше всего описывает PUT v POST. Если ваше действие является идемпотентным, используйте PUT, в противном случае используйте POST.   -  person ydaetskcoR    schedule 18.09.2014
comment
@ydaetskcoR Я думаю, что ваш первый комментарий был просто замечательным. Пример был только для поведения (добавлено это к вопросу). Извините за неточность! :/ Я также видел ответ, на который вы ссылаетесь, но, к сожалению, в нем не используются хорошие источники (PDF не очень полезен). Btu, чтобы прокомментировать ваше последнее утверждение, мой POST (с обновлением или созданием) теперь является своего рода устойчивым, потому что вызов его снова и снова приведет к тому же набору данных. Создание этой идемпотентности с помощью PUT кажется невозможным для моего решения (потому что меняется uri)   -  person Robert    schedule 18.09.2014


Ответы (2)


«Нарушение RESTfulness» строго не определено.

Хорошей ссылкой на степени REST-подобного поведения в интерфейсе является Модель зрелости Ричардсона, которая разделяет степени поддержки идеалов REST на 4 уровня (где 0 означает «совсем не REST», а 3 означает «полностью REST, но вряд ли кто-то еще делает это»)

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

POST достаточно открыт, чтобы при желании он мог быть идемпотентным, но HTTP этого не требует. Таким образом, вы не порвали с HTTP, просто упустили возможность использовать более уместный метод PUT. Обратная ситуация с использованием PUT для неидемпотентной части API была бы более проблематичной.

Как личное мнение, я думаю, что если вы остаетесь самосогласованными при обработке различных методов и маршрутов HTTP и четко документируете это изменение в соответствии с ожиданиями, то вы можете разумно называть свой API «RESTful». Я думаю, что разделения кода ответа 200/201 достаточно, чтобы можно было написать самосогласованный клиент. Хотя я бы предпочел видеть здесь как create, так и update как PUT, это не то, что заставило бы меня задуматься больше, чем на мгновение.

Вы можете рассмотреть возможность поддержки обновить или создать на маршруте сбора (POST /cars в вашем примере) в соответствии с рекомендациями, а также обновить на маршруте элемента (POST /cars/7). Концепция «обновить или создать» достаточно распространена в базах данных и структурах ORM, поэтому ее легко понять. Клиент также может использовать последний маршрут только для обновления, будучи уверенным, что он случайно не создаст новую запись автоматически.

person Neil Slater    schedule 18.09.2014
comment
Я прочитал вашу ссылку и думаю, что мне очень полезно объяснить отклонение от стандарта в документации по разработке API. Хотя я знаю, что очень трудно дать определение Restful, ваши мысли и объяснения охватывают ход моих мыслей. Большое спасибо за ваш ответ! (Я подожду, если кто-то другой добавит что-то новое, а затем отметит вас как ответ :)) - person Robert; 18.09.2014
comment
отклонение от стандарта - REST не является стандартом. Или если да, то какой у него каталожный номер? :) Это архитектурный образец. Вы выбираете, насколько вы будете следовать ему. Вы также можете выбрать моделирование объекта. Следование CRUD не является обязательным. - person pcjuzer; 22.12.2016
comment
да. Но для лучшей практики разделите POST как создание и PUT как обновление. - person hien711; 01.10.2019
comment
@hien711: Это не всегда так. PUT имеет смысл для создания, когда идентификатор известен и имеет смысл. Возможно, задайте об этом отдельный вопрос, если непонятно. - person Neil Slater; 01.10.2019

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

Методы также могут иметь свойство «идемпотентности» в том смысле, что (помимо ошибок или проблем с истечением срока действия) побочные эффекты N> 0 идентичных запросов такие же, как и для одного запроса.

Таким образом, идемпотентность связана с побочными эффектами на стороне сервера. Ваш второй PUT не имеет других побочных эффектов (из-за 404), чем первый, поэтому использование PUT здесь идемпотентно.

Метод POST используется для запроса того, чтобы исходный сервер принял объект, включенный в запрос, в качестве нового подчиненного ресурса, идентифицированного Request-URI.

В этом случае вы не создаете новый ресурс, вы просто добавляете новый идентификатор ресурса и удаляете старый идентификатор ресурса. Так что POST неуместен.

Действие, выполняемое методом POST, может не привести к получению ресурса, который можно идентифицировать по URI.

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

В нашем API «возможно» обратиться к нужному элементу без URI (например, для его обновления с помощью POST), потому что все части первичного ключа построения URI находятся в теле ресурса, поэтому API знает, к какому элементу клиент хочет получить доступ. .

Вы должны использовать IRI (URL) для идентификации ресурсов, если вы не хотите нарушать ограничение универсального интерфейса (идентификация ресурсов).

Вы должны использовать гиперссылки, если вы не хотите нарушать ограничение универсального интерфейса (гипермедиа как механизм состояния приложения). Структура IRI не несет никакой семантики для клиентов, поэтому может быть чем угодно, если на их усмотрение. Вы должны использовать отношения ссылок или какой-либо атрибут из словаря, чтобы добавить семантику.

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

По определению веб-служба REST соответствует всем ограничениям REST. . Термин RESTful был создан, когда люди, которые понятия не имели о REST, начали называть свои веб-сервисы REST. Итак, после этого ppl создал термин RESTful, что означает, что так называемая служба REST не нарушает ни одно из ограничений REST. После этого срок RESTful тоже был исчерпан. В настоящее время мы различаем API гипермедиа и веб-API. API гипермедиа означает, что веб-служба соответствует ограничению HATEOAS (гипермедиа как механизм состояния приложения). Веб-API может быть любым, который претендует на роль REST.

person inf3rno    schedule 18.09.2014
comment
Если я принимаю PUT для обновления этих объектов, я должен принимать запросы, в которых URI отличается от тела, что мне не нравится, но, возможно, это необоснованно. Но как мой API должен реагировать на второй PUT? Номер 404 не подходит, потому что это законный звонок. Клиент хочет обновить или создать ресурс, API не может отказать в этом, потому что нет элемента с таким URI. Вызовы PUT должны создать ресурс, если его нет. НО ТОГДА... URI становится неактуальным для любого вызова PUT, потому что я просто игнорирую его и работаю с телом. Это действительно будет hypermedia API !? - person Robert; 18.09.2014
comment
И большое спасибо за развернутый и подробный ответ! Мое понимание идемпотента было неправильным. Было бы очень хорошо, если бы вы включили в свой ответ мои опасения из последнего комментария. - person Robert; 18.09.2014
comment
"If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI." - Вам не нужно создавать новый ресурс с помощью PUT, если вы не хотите... can create != must create - person inf3rno; 18.09.2014
comment
If I accept PUT for updating these entitys, I have to accept requests where the URI is different from the body, which feels not good for me, but maybe thats unfounded. Можете привести пример? - person inf3rno; 18.09.2014
comment
Хм, да. stackoverflow.com/a/25642689/2630337 мы уже обсуждали это несколько дней назад... Вопрос для меня в том, как API должен себя вести? Игнорировать URI и просто сделать ОБНОВЛЕНИЕ/СОЗДАНИЕ данного ресурса из тела? Или просто посмотреть, есть ли под URI ресурс и попробовать его обновить? Но что, если под данным URI нет ресурса: создать новый с первичным ключом, указанным в теле, или с первичным ключом, указанным в URI? Это кажется мне очень непоследовательным... | Реальные объекты, с которыми мы работаем, выглядят примерно так: /parameter/<id>/<validFrom> и мы меняем дату - person Robert; 18.09.2014
comment
Вы никогда не игнорируете URL-адрес, поскольку он идентифицирует ресурс, над которым вы хотите работать. URL-адреса могут прерываться во времени, в этом нет ничего плохого. Вы должны принять тот факт, что клиент не всегда синхронизирован с сервером. Вот почему вы отправляете свои запросы, чтобы обновить клиент или обновить сервер. Все остальное зависит от ваших потребностей. Вы можете сохранить старый URL-адрес, если хотите, и использовать PUT на том же ресурсе. Вы можете удалить старый URL-адрес, если хотите, и отправить обратно 404, поскольку для URL-адреса не назначен ресурс. так далее... - person inf3rno; 18.09.2014