Лучшая практика для частичных обновлений в RESTful-сервисе - PullRequest
199 голосов
/ 14 марта 2010

Я пишу сервис RESTful для системы управления клиентами и пытаюсь найти наилучшую практику для частичного обновления записей. Например, я хочу, чтобы вызывающая сторона могла читать полную запись с помощью запроса GET. Но для его обновления разрешены только определенные операции с записью, например, изменение статуса с ENABLED на DISABLED. (У меня есть более сложные сценарии, чем это)

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

Есть ли рекомендуемый способ построения URI? При чтении книг REST вызовы в стиле RPC кажутся недовольными.

Если следующий вызов возвращает полную запись о клиенте с идентификатором 123

GET /customer/123
<customer>
    {lots of attributes}
    <status>ENABLED</status>
    {even more attributes}
</customer>

как мне обновить статус?

POST /customer/123/status
<status>DISABLED</status>

POST /customer/123/changeStatus
DISABLED

...

Обновление : Дополнить вопрос. Как включить «вызовы бизнес-логики» в API REST? Есть ли согласованный способ сделать это? Не все методы CRUD по своей природе. Некоторые из них являются более сложными, например, ' sendEmailToCustomer (123) ', ' mergeCustomers (123, 456) ', ' countCustomers () '

POST /customer/123?cmd=sendEmail

POST /cmd/sendEmail?customerId=123

GET /customer/count 

Спасибо Frank

Ответы [ 10 ]

67 голосов
/ 14 марта 2010

У вас есть два варианта:

  1. Используйте PATCH (но учтите, что вы должны определить свой собственный тип носителя, который точно определяет, что произойдет)

  2. Используйте POST для подресурса и верните 303 См. Другое с заголовком Location, указывающим на основной ресурс. Цель 303 - сообщить клиенту: «Я выполнил ваш POST, и в результате был обновлен какой-то другой ресурс. См. Заголовок Location, для которого этот ресурс был». POST / 303 предназначен для итеративных дополнений к ресурсам для создания состояния некоторого основного ресурса и идеально подходит для частичных обновлений.

47 голосов
/ 14 марта 2010

Вы должны использовать POST для частичных обновлений.

Чтобы обновить поля для клиента 123, сделайте POST в /customer/123.

Если вы хотите обновить только статус, вы также можете PUT в /customer/123/status.

Как правило, запросы GET не должны иметь побочных эффектов, а PUT предназначен для записи / замены всего ресурса.

Это следует непосредственно из HTTP, как показано здесь: http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods

10 голосов
/ 03 января 2013

Вам следует использовать PATCH для частичных обновлений - либо с использованием документов json-patch (см. http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-08 или http://www.mnot.net/blog/2012/09/05/patch), либо структуры исправлений XML (см. http://tools.ietf.org/html/rfc5261). На мой взгляд, хотя, json-patch лучше всего подходит для ваших бизнес-данных.

PATCH с документами JSON / XML patch имеет очень прямолинейную семантику для частичных обновлений. Если вы начнете использовать POST с измененными копиями исходного документа, для частичных обновлений вы вскоре столкнетесь с проблемами, когда вы хотите, чтобы пропущенные значения (или, скорее, нулевые значения) представляли либо «игнорировать это свойство», либо «установите это свойство на «Пустое значение» - и это приводит к кроличьей норе взломанных решений, что в итоге приведет к вашему виду формата патчей.

Более подробный ответ вы можете найти здесь: http://soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.html.

6 голосов
/ 07 июня 2010

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

Вот два решения, о которых я могу подумать:

  1. сделать PUT со всем ресурсом. На стороне сервера определите семантику, согласно которой PUT со всем ресурсом игнорирует все значения, которые не изменились.

  2. сделать PUT с частичным ресурсом. На стороне сервера определите семантику этого как слияние.

2 - это просто оптимизация пропускной способности, равная 1. Иногда 1 - единственный вариант, если ресурс определяет, что некоторые поля являются обязательными полями (например, прото-буферы).

Проблема обоих этих подходов заключается в том, как очистить поле. Вам придется определить специальное нулевое значение (особенно для протобуферов, поскольку нулевые значения не определены для протобуферов), которое приведет к очистке поля.

Комментарии

5 голосов
/ 20 мая 2015

Для изменения статуса я думаю, что подход RESTful состоит в том, чтобы использовать логический подресурс, который описывает состояние ресурсов. Это IMO довольно полезно и чисто, когда у вас уменьшенный набор статусов. Это делает ваш API более выразительным, не заставляя существующие операции для вашего ресурса клиента.

Пример:

POST /customer/active  <-- Providing entity in the body a new customer
{
  ...  // attributes here except status
}

Служба POST должна вернуть вновь созданного клиента с идентификатором:

{
    id:123,
    ...  // the other fields here
}

GET для созданного ресурса будет использовать расположение ресурса:

GET /customer/123/active

GET / клиент / 123 / неактивный должен вернуть 404

Для операции PUT, без предоставления объекта Json, он просто обновит статус

PUT /customer/123/inactive  <-- Deactivating an existing customer

Предоставление объекта позволит вам обновлять содержимое клиента и одновременно обновлять статус.

PUT /customer/123/inactive
{
    ...  // entity fields here except id and status
}

Вы создаете концептуальный подресурс для своего клиентского ресурса. Это также согласуется с определением ресурса Роя Филдинга: «... ресурс - это концептуальное отображение набора сущностей, а не сущности, которая соответствует сопоставлению в любой конкретный момент времени ...». В этом случае концептуальное сопоставление активный клиент-клиент со статусом = ACTIVE.

Операция чтения:

GET /customer/123/active 
GET /customer/123/inactive

Если вы выполняете эти вызовы один раз после того, как другой из них должен вернуть статус 404, успешный вывод может не включать статус, поскольку он неявный. Конечно, вы все равно можете использовать GET / customer / 123? Status = ACTIVE | INACTIVE для прямого запроса ресурса клиента.

Операция DELETE интересна, так как семантика может сбивать с толку. Но у вас есть возможность не публиковать эту операцию для этого концептуального ресурса или использовать ее в соответствии с вашей бизнес-логикой.

DELETE /customer/123/active

Чтобы ваш клиент мог перейти в статус УДАЛЕНО / ОТКЛЮЧЕН или в противоположный статус (АКТИВНЫЙ / НЕАКТИВНЫЙ).

5 голосов
/ 08 июня 2010

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

почтовых отправлений


POST /customers/123/mails

payload:
{from: x@x.com, subject: "foo", to: y@y.com}

Реализация этого ресурса + POST будет отправлять почту. при необходимости вы можете предложить что-то вроде / customer / 123 / outbox, а затем предложить ссылки на ресурсы в /customer/mails/ndommailIdcasts.

количество клиентов

Вы можете обращаться с ним как с поисковым ресурсом (включая метаданные поиска с информацией подкачки и num-found информацией, которая подсчитывает количество клиентов).


GET /customers

response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}

3 голосов
/ 17 июля 2015

Используйте PUT для обновления неполного / частичного ресурса.

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

Ниже приведена функция, которую вы можете использовать для справки:

public IHttpActionResult Put(int id, JObject partialObject)
{
    Dictionary<string, string> dictionaryObject = new Dictionary<string, string>();

    foreach (JProperty property in json.Properties())
    {
        dictionaryObject.Add(property.Name.ToString(), property.Value.ToString());
    }

    int id = Convert.ToInt32(dictionaryObject["id"]);
    DateTime startTime = Convert.ToDateTime(orderInsert["AppointmentDateTime"]);            
    Boolean isGroup = Convert.ToBoolean(dictionaryObject["IsGroup"]);

    //Call function to update resource
    update(id, startTime, isGroup);

    return Ok(appointmentModelList);
}
1 голос
/ 22 июля 2017

Относительно вашего обновления.

Концепция CRUD, по-моему, вызвала некоторую путаницу в отношении дизайна API. CRUD - это общая концепция низкого уровня для базовых операций, выполняемых с данными, а HTTP-глаголы - это просто методы запроса (, созданный 21 год назад ), которые могут отображаться или не отображаться в операции CRUD. На самом деле, попробуйте найти наличие аббревиатуры CRUD в спецификации HTTP 1.0 / 1.1.

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

Базовая концепция здесь (и та, которая вызывает много путаницы) - это отображение между "методами" и глаголами HTTP. Одна вещь состоит в том, чтобы определить, какие «операции» (методы) будет выполнять ваш API над какими типами ресурсов (например, получить список клиентов или отправить электронное письмо), а другой - глаголами HTTP. Должно быть определение обоих методов и глаголов, которые вы планируете использовать, и отображение между ними .

В нем также говорится, что когда операция не отображается точно стандартным методом (List, Get, Create, Update, Delete в этом случае), можно использовать «Пользовательские методы». ", например BatchGet, который извлекает несколько объектов на основе ввода нескольких идентификаторов объектов, или SendEmail.

1 голос
/ 14 марта 2010

Выезд http://www.odata.org/

Он определяет метод MERGE, поэтому в вашем случае это будет примерно так:

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

Обновляется только свойство status, остальные значения сохраняются.

0 голосов
/ 17 июля 2015

Это не имеет значения. С точки зрения REST, вы не можете сделать GET, потому что он не кэшируется, но это не имеет значения, если вы используете POST или PATCH или PUT или что-то еще, и не имеет значения, как выглядит URL. Если вы выполняете REST, важно, чтобы, когда вы получаете представление своего ресурса с сервера, это представление могло предоставить опции перехода состояния клиента.

Если в вашем ответе GET произошли переходы состояний, клиенту просто нужно знать, как их читать, и сервер может изменить их при необходимости. Здесь обновление выполняется с использованием POST, но если оно было изменено на PATCH или URL-адрес изменился, клиент все еще знает, как сделать обновление:

{
  "customer" :
  {
  },
  "operations":
  [
    "update" : 
    {
      "method": "POST",
      "href": "https://server/customer/123/"
    }]
}

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

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

"email":
{
  "method": "POST",
  "href": "http://server/emailservice/send?customer=1234"
}

Вот несколько хороших видео и пример архитектуры REST докладчика. Stormpath использует только GET / POST / DELETE, что хорошо, поскольку REST не имеет никакого отношения к тому, какие операции вы используете или как должны выглядеть URL-адреса (за исключением того, что GET должен кэшироваться):

https://www.youtube.com/watch?v=pspy1H6A3FM,
https://www.youtube.com/watch?v=5WXYw4J4QOU,
http://docs.stormpath.com/rest/quickstart/

...