RESTful API. Должен ли я вернуть объект, который был создан / обновлен?


36

Я разрабатываю веб-сервис RESTful с использованием WebApi, и мне было интересно, какие HTTP-ответы и тела ответов нужно возвращать при обновлении / создании объектов.

Например, я могу использовать метод POST для отправки некоторого JSON в веб-сервис, а затем создать объект. Рекомендуется ли затем установить для HTTP-статуса статус созданный (201) или ok (200) и просто вернуть сообщение, например «Новый сотрудник добавлен», или вернуть объект, который был отправлен изначально?

То же самое касается метода PUT. Какой HTTP-статус лучше всего использовать и нужно ли возвращать созданный объект или просто сообщение? Учитывая тот факт, что пользователь в любом случае знает, какой объект он пытается создать / обновить.

Есть предположения?

Пример:

Добавить нового сотрудника:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

Обновить существующего сотрудника:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Ответы:

Ответ с созданным / обновленным объектом

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Ответ только с сообщением:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

Ответ только с кодом состояния:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT

2
Хороший вопрос, но использование термина «лучшие практики» является своего рода табу на этом сайте meta.programmers.stackexchange.com/questions/2442/… Возможно, вы просто захотите переформулировать вопрос. meta.programmers.stackexchange.com/questions/6967/…
Snoop

3
В качестве небольшого дополнения было бы хорошей идеей иметь флаг в запросе, чтобы (например) мобильное приложение могло вернуть весь объект, когда он находится в Wi-Fi, но только идентификатор при использовании сотовых данных? Есть ли заголовок, который следует использовать для этого, чтобы избежать загрязнения JSON?
Эндрю говорит восстановить Монику

@AndrewPiliser Интересная идея, хотя лично я думаю, что лучше всего выбрать один подход и придерживаться его. Затем, когда ваше приложение развивается или становится все более популярным, оптимизируйте его
iswinky

@AndrewPiliser Ваша идея очень похожа на UPDATE/INSERT ... RETURNINGвариант Postgresql для SQL. Это очень удобно, особенно потому, что сохраняет представление новых данных и запрос на обновленную версию на атомарном уровне.
Бельдаз

Ответы:


31

Как и в большинстве вещей, это зависит. Ваш компромисс - простота использования и размер сети. Для клиентов может быть очень полезно увидеть созданный ресурс. Он может включать в себя поля, заполненные сервером, такие как время последнего создания. Поскольку вы, кажется, включаете idвместо использования hateoas, клиенты, вероятно, захотят увидеть идентификатор для ресурса, который они только что POSTредактировали.

Если вы не включили созданный ресурс, не создавайте произвольное сообщение. Поля 2xx и Location являются достаточной информацией, чтобы клиенты могли быть уверены, что их запрос был правильно обработан.


+1 Цель hateoas - не дать клиенту составить URI, также может быть достигнута путем предоставления клиенту возможности заполнять предоставленные сервером шаблоны URL с определенными идентификаторами. Да, клиент "создает", но только способом "заполнить пробелы". Хотя он не является чистым HATEOAS, он достигает цели и делает работу с объектами, имеющими (большое) число «действий» uri, чуть менее чувствительной к пропускной способности, не говоря уже о том, когда вы помещаете эти объекты в (большой) список.
Марьян Венема

3
+1 к совету «пожалуйста, не создавайте произвольное сообщение»
HairOfTheDog

«Нет произвольного сообщения» фокусируется на строковых сообщениях или любых возвращаемых значениях, которые не являются созданным ресурсом? Я сосредотачиваюсь на случаях, когда мы возвращаем идентификатор созданного ресурса (но не сам ресурс), и
мне

12

Лично я всегда только возвращаюсь 200 OK.

Процитировать ваш вопрос

Учитывая тот факт, что пользователь в любом случае знает, какой объект он пытается создать / обновить.

Зачем добавлять дополнительную пропускную способность (за которую, возможно, придется платить), чтобы сообщить клиенту, что он уже знает?


1
Это то, о чем я думал: если он недействителен, вы можете вернуть проверочные сообщения, но если он действителен и создается / обновляется, проверьте код состояния HTTP и покажите пользователю сообщение, например, «Ура», на основании этого
iswinky

3
См. Stackoverflow.com/a/827045/290182 относительно 200/ 204 No Contentчтобы избежать путаницы jQuery и тому подобное.
Белдаз

10

@iswinky Я бы всегда отправлял полезные данные в случае как POST, так и PUT.

В случае POST вы можете создать объект с внутренним идентификатором или UUID. Следовательно, имеет смысл отправить обратно полезную нагрузку.

Точно так же в случае PUT вы можете игнорировать некоторые поля пользователя (скажем, неизменяемые значения), или в случае PATCH данные могут быть изменены и другими пользователями.

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

Только в случае УДАЛЕНИЯ я бы не отправил обратно полезную нагрузку и сделал бы либо 200 с содержимым ответа, либо 204 без содержимого ответа.

Изменить: Благодаря некоторым комментариям ниже, я переписываю свой ответ. Я до сих пор придерживаюсь того, как я проектирую свои API и отправляю ответные ответы, но я думаю, что имеет смысл уточнить некоторые мои мысли о дизайне.

а) Когда я говорю «отправить обратно полезную нагрузку», я на самом деле хотел сказать «отправить обратно данные ресурса», а не ту же полезную нагрузку, которая пришла в запросе. Пример: если вы отправите полезную нагрузку create, я могу в бэкэнде создать другие объекты, такие как UUID и (возможно) временные метки и даже некоторые (графические) соединения. Я бы отправил все это обратно в ответ (а не только полезную нагрузку запроса как есть - что бессмысленно).

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

В случае, если объект очень большой или родительский объект обновляется, я бы отправил обратно вложенные структуры как hrefs.

Суть в том, что я бы определенно попытался отправить обратно данные, которые имеют смысл для потребителя / пользовательского интерфейса, чтобы немедленно обработать и выполнить с помощью атомарного действия API, вместо того, чтобы идти и получать еще 2-5 API, чтобы обновить пользовательский интерфейс после публикации. создание / обновление данных.

Интерфейсы между серверами API могут думать по-другому об этом. Я сосредотачиваюсь на API, которые управляют пользовательским опытом.


Я вижу много ситуаций, когда отправка обратно всей полезной нагрузки - плохая идея, когда полезная нагрузка велика.
Белдаз

2
@beldaz полностью согласен. YMMV основан на дизайне REST API. Я обычно избегаю очень больших объектов и разбиваю их на серию подресурсов / PUT. Если полезная нагрузка очень велика, есть лучшие способы сделать это, и в этом случае вы захотите сделать HATEOAS (как говорит Марьян выше), где вы возвращаете ссылку на объект вместо самого объекта.
кспрашу

@ksprashu: «Следовательно, имеет смысл отправить обратно полезную нагрузку» - я считаю, что это плохая идея, потому что таким образом ресурс можно получить разными способами: через GET, как ответ POST, как ответ PUT. Это означает, что клиент получает 3 ресурса с потенциально различной структурой. Где, как если бы вы возвращали только URI (местоположение) без тела, тогда единственный способ получить ресурс - это GET. Это гарантирует, что клиент всегда получает последовательные ответы.
Менталлург

@mentallurg Я понимаю, что, возможно, не сформулировал это право. Спасибо за указание на это. Я отредактировал свой ответ. Дайте мне знать, если это имеет больше смысла.
кспрашу

Пока вы реализуете услугу для домашней работы, это не имеет большого значения. Делай как хочешь. Экономьте свое время.
Менталлург

9

Ссылаясь на ссылку RFC стандартов , вы должны вернуть 201 (созданный) статус при успешном сохранении ресурса запроса с помощью Post. В большинстве приложений идентификатор ресурса генерируется самим сервером, поэтому рекомендуется возвращать идентификатор созданного ресурса. Возврат всего объекта - это накладные расходы для запроса Post. Идеальной практикой является возвращение URL-адреса вновь созданного ресурса.

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

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

Эта конечная точка отдыха возвратит ответ в виде:

Статус 201 - СОЗДАН

Расположение заголовка → http: // localhost: 8080 / employee / 1


Хороший и чистый - буду следить за этим сейчас и за его пределами
Хасан Тарек

0

Я бы сделал полезную нагрузку в возвращаемом теле условной к параметру HTTP.

Чаще всего лучше возвращать некоторый контент обратно потребителю API, чтобы избежать ненужных циклов (одна из причин существования GraphQL).

На самом деле, поскольку наши приложения становятся более интенсивными и распределенными, я стараюсь соблюдать это правило:

Мое руководство :

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

Как это делается и какой тип контента возвращать обратно из PUT или POST, зависит от конкретного приложения. Теперь было бы интересно, если бы приложение могло параметризовать тип «содержимого» в теле ответа (хотим ли мы просто местоположение нового объекта, или некоторых полей, или всего объекта и т. Д.)

Приложение может определить набор параметров, которые POST / PUT может получить для управления типом «содержимого», возвращаемого в теле ответа. Или он может закодировать какой-то запрос GraphQL в качестве параметра (я вижу, что это полезно, но также может стать кошмаром обслуживания).

В любом случае, мне кажется, что:

  1. Можно (и, скорее всего, желательно) возвращать что-либо в теле ответа POST / PUT.
  2. Как это сделать, зависит от конкретного приложения и практически невозможно обобщить.
  3. Вы не хотите возвращать «контекст» большого размера по умолчанию (шум трафика, который побеждает всю причину перехода от POST-follow-by-GETs.)

Итак, 1) сделай это, но 2) сделай это простым.

Другой вариант, который я видел, это люди, создающие альтернативные конечные точки (скажем, / customer для POST / PUT, которые ничего не возвращают в теле и / customer_with_details для POST / PUT для / клиентов, но которые возвращают что-то в теле ответа.)

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

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