REST API - массовое создание или обновление за один запрос [закрыто]

94

Предположим, есть два ресурса Binderи Docс отношениями ассоциации, что означает, что Docи Binderстоят сами по себе. Docможет принадлежать или не принадлежать Binderи Binderможет быть пустым.

Если я хочу разработать REST API, который позволяет пользователю отправлять коллекцию Docs, ЗА ОДИН ЗАПРОС , как показано ниже:

{
  "docs": [
    {"doc_number": 1, "binder": 1}, 
    {"doc_number": 5, "binder": 8},
    {"doc_number": 6, "binder": 3}
  ]
}

И для каждого РОУ в docs,

  • Если docсуществует, назначьте егоBinder
  • Если docне существует, создайте его, а затем назначьте

Я действительно не понимаю, как это должно быть реализовано:

  • Какой метод HTTP использовать?
  • Какой код ответа нужно вернуть?
  • Подходит ли это вообще для REST?
  • Как бы выглядел URI? /binders/docs?
  • Обработка массового запроса: что делать, если несколько элементов вызывают ошибку, но другие обрабатываются. Какой код ответа нужно вернуть? Должна ли массовая операция быть атомарной?
Сэм Р.
источник

Ответы:

59

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

  • Использование POSTметода обычно используется для добавления элемента при использовании в ресурсе списка, но вы также можете поддерживать несколько действий для этого метода. См. Этот ответ: Как обновить коллекцию ресурсов REST . Вы также можете поддерживать различные форматы представления для ввода (если они соответствуют массиву или отдельным элементам).

    В этом случае нет необходимости определять ваш формат для описания обновления.

  • Использование PATCHметода также подходит, поскольку соответствующие запросы соответствуют частичному обновлению. Согласно RFC5789 ( http://tools.ietf.org/html/rfc5789 ):

    Некоторым приложениям, расширяющим протокол передачи гипертекста (HTTP), требуется функция частичной модификации ресурсов. Существующий метод HTTP PUT позволяет только полную замену документа. Это предложение добавляет новый метод HTTP, PATCH, для изменения существующего ресурса HTTP.

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

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

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

У вас может быть два варианта путей к ресурсам.

  • Использование пути к ресурсу для списка документов

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

Вот примерный маршрут для этого /docs.

Содержание такого подхода могло бы быть для метода POST:

[
    { "doc_number": 1, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 2, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 3, "binder": 5, (other fields in the case of creation) },
    (...)
]
  • Использование пути субресурсов связующего элемента

Кроме того, вы также можете рассмотреть возможность использования подмаршрутов для описания связи между документами и связующими. Подсказки относительно связи между документом и подшивкой теперь не нужно указывать в содержимом запроса.

Вот примерный маршрут для этого /binder/{binderId}/docs. В этом случае отправка списка документов с помощью метода POSTили PATCHприкрепление документов к связыванию с идентификатором binderIdпосле создания документа, если он не существует.

Содержание такого подхода могло бы быть для метода POST:

[
    { "doc_number": 1, (other fields in the case of creation) },
    { "doc_number": 2, (other fields in the case of creation) },
    { "doc_number": 3, (other fields in the case of creation) },
    (...)
]

Что касается ответа, вам решать, какой уровень ответа и какие ошибки нужно вернуть. Я вижу два уровня: уровень статуса (глобальный уровень) и уровень полезной нагрузки (более тонкий уровень). Также вам решать, должны ли все вставки / обновления, соответствующие вашему запросу, быть атомарными или нет.

  • Атомный

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

  • Неатомный

В этом случае 200будет возвращен статус , и ответное представление должно описать, что было сделано и где в конечном итоге возникают ошибки. ElasticSearch имеет конечную точку в своем REST API для массового обновления. Это может дать вам некоторые идеи на этом уровне: http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/bulk.html .

  • Асинхронный

Вы также можете реализовать асинхронную обработку для обработки предоставленных данных. В этом случае возвращается статус HTTP 202. Клиенту необходимо получить дополнительный ресурс, чтобы увидеть, что происходит.

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

Следующая ссылка также может вам помочь: https://templth.wordpress.com/2014/12/15/designing-a-web-api/ .

Надеюсь, это поможет тебе, Тьерри

Тьерри Темплиер
источник
У меня есть ответ на вопрос. Я выбрал плоские маршруты без вложенных дополнительных ресурсов. Для того, чтобы получить все документы я называю GET /docsи получить все документы в пределах определенного связующего GET /docs?binder_id=x. Чтобы удалить подмножество ресурсов, я DELETE /docs?binder_id=xдолжен позвонить или должен позвонить DELETE /docsс a {"binder_id": x}в теле запроса? Вы когда-нибудь использовали PATCH /docs?binder_id=xбы пакетное обновление или просто PATCH /docsпередавали пары?
Энди Фусняк
35

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

Делать PATCH /docs- определенно допустимый вариант. Возможно, вам будет сложно использовать стандартные форматы исправлений для вашего конкретного сценария. Не уверен в этом.

Вы можете использовать 200. Вы также можете использовать 207 - Multi Status

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

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

Даррел Миллер
источник
Огромное спасибо. К This can be done in a RESTful wayвы имеете в виду обновление и НАПИСАТЬ должно быть сделано отдельно?
Сэм Р.
1
@norbertpy Выполнение какой-либо операции записи на ресурсе может привести к обновлению и созданию других ресурсов из одного запроса. У REST нет проблем с этим. Я выбрал эту фразу потому, что некоторые платформы реализуют массовые операции путем сериализации HTTP-запросов в документы, состоящие из нескольких частей, а затем отправки сериализованных HTTP-запросов в виде пакета. Я думаю, что этот подход нарушает ограничение REST идентификации ресурса.
Даррел Миллер,
19

PUT ing

PUT /binders/{id}/docs Создать или обновить и связать отдельный документ с подшивкой

например:

PUT /binders/1/docs HTTP/1.1
{
  "docNumber" : 1
}

PATCH ИНГ

PATCH /docs Создайте документы, если они не существуют, и свяжите их с подшивками

например:

PATCH /docs HTTP/1.1
[
    { "op" : "add", "path" : "/binder/1/docs", "value" : { "doc_number" : 1 } },
    { "op" : "add", "path" : "/binder/8/docs", "value" : { "doc_number" : 8 } },
    { "op" : "add", "path" : "/binder/3/docs", "value" : { "doc_number" : 6 } }
] 

Позже я включу дополнительные идеи, а пока, если хотите, ознакомьтесь с RFC 5789 , RFC 6902 и « Пожалуйста» Уильяма Дюрана . Запись в блоге Don't Patch Like a Idiot .

Маурисио Моралес
источник
2
Иногда клиенту требуется массовая операция, и он не хочет заботиться о том, есть ли ресурс или нет. Как я сказал в вопросе, клиент хочет отправить кучу docsи связать их с binders. Клиент хочет создать подшивки, если они не существуют, и установить связь, если они есть. В ОДНОМ ОДНОМ ЗАПРОСЕ.
Сэм Р.
12

В проекте, над которым я работал, мы решили эту проблему, реализовав так называемые «пакетные» запросы. Мы определили путь, по /batchкоторому принимаем json, в следующем формате:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 5,
         binder: 8
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      }
   },
]

Ответ имеет код состояния 207 (Multi-Status) и выглядит так:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
      status: 200
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         error: {
            msg: 'A document with doc_number 5 already exists'
            ...
         }
      },
      status: 409
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      },
      status: 200
   },
]

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

Facebook и Google имеют похожие реализации:
https://developers.google.com/gmail/api/guides/batch
https://developers.facebook.com/docs/graph-api/making-multiple-requests.

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

  1. Заменены отправляемым вами документом (т.е. отсутствующие свойства в запросе будут удалены, а уже существующие заменены)?
  2. Объединены с отправляемым вами документом (т.е. отсутствующие свойства в запросе не будут удалены, а уже существующие свойства будут перезаписаны)?

Если вам нужно поведение из альтернативы 1, вы должны использовать POST, а если вы хотите поведение из альтернативы 2, вы должны использовать PUT.

http://restcookbook.com/HTTP%20Methods/put-vs-post/

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

Дэвид Берг
источник
5
Нравится этот ответ для Proof-of-Concept, а также для ссылок Google и Facebook. Но не согласен с заключительной частью POST или PUT. В двух случаях, упомянутых в этом ответе, первый должен быть PUT, а второй - PATCH.
RayLuo
@RayLuo, не могли бы вы объяснить, почему нам нужен PATCH в дополнение к POST и PUT?
Дэвид Берг
2
Потому что это то, для чего был изобретен PATCH. Вы можете прочитать это определение и увидеть, как PUT и PATCH соответствуют вашим двум точкам.
RayLuo
@DavidBerg, похоже, что Google предпочел другой подход к обработке пакетных запросов, т.е. разделить заголовок и тело каждого подзапроса на соответствующую часть основного запроса с границей вроде --batch_xxxx. Есть ли кардинальные отличия между решениями Google и Facebook? Кроме того, насчет того, чтобы «использовать ответ от одного запроса как ввод для другого», это звучит очень интересно, не могли бы вы поделиться более подробной информацией? или какой сценарий следует использовать?
Ян