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за допомогою {"binder_id": x}телу запиту? Чи могли б ви коли-небудь використовувати PATCH /docs?binder_id=xдля пакетного оновлення або просто PATCH /docsпередавати пари?
Енді Фусняк,

35

Ймовірно, вам доведеться використовувати POST або PATCH, тому що навряд чи один запит, який оновлює та створює кілька ресурсів, буде ідемпотентним.

Виконання PATCH /docsдійсно є валідним варіантом. Можливо, вам буде важко використовувати стандартні формати виправлень для вашого конкретного сценарію. Не впевнений у цьому.

Ви можете використовувати 200. Ви також можете використовувати 207 - Multi Status

Це можна зробити в покійному вигляді. На мою думку, ключовим є наявність ресурсу, призначеного для прийняття набору документів для оновлення / створення.

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


Дуже дякую. До This can be done in a RESTful wayви маєте в виду оновлення і НАПИСАТИ має бути зроблено окремо?
Sam R.

1
@norbertpy Виконання якоїсь операції запису на ресурсі може призвести до оновлення та створення інших ресурсів з одного запиту. REST не має з цим проблем. Я вибрав фразу, оскільки деякі фреймворки реалізують масові операції шляхом серіалізації HTTP-запитів у багатокомпонентні документи, а потім надсилання серіалізованих HTTP-запитів як пакет. Я думаю, що такий підхід порушує обмеження REST щодо ідентифікації ресурсів.
Даррел Міллер

19

PUT ІНГ

PUT /binders/{id}/docs Створіть або оновіть і зв’яжіть один документ із підшивкою

наприклад:

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

PATCH ing

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 та « Будь ласка» Вільяма Дюрана . Не виправляйте, як ідіот, запис у блозі.


2
Іноді клієнту потрібна групова операція, і він не хоче бачити, є ресурс чи ні. Як я вже сказав у запитанні, клієнт хоче надіслати купу docsта пов’язати їх binders. Клієнт хоче створити прив'язки, якщо вони не існують, і створити асоціацію, якщо вони існують. В ОДНОМУ ОДНОМУ БАКС запиті.
Sam R.

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. У 2 випадках, згаданих у цій відповіді, перший повинен бути PUT, а другий - PATCH.
RayLuo

@RayLuo, ти можеш пояснити, чому нам потрібен PATCH на додаток до POST та PUT?
Девід Берг,

2
Тому що саме для цього був винайдений PATCH. Ви можете прочитати це визначення та побачити, як PUT та PATCH відповідають вашим 2 крапкам.
RayLuo

@DavidBerg, Здається, Google віддав перевагу іншому підходу до обробки пакетних запитів, тобто відокремив заголовок та тіло кожного підзапиту до відповідної частини основного запиту, маючи межу типу --batch_xxxx. Чи існують суттєві відмінності між рішеннями Google і Facebook? Крім того, щодо "використовуйте відповідь одного запиту як вхідний сигнал для іншого", це звучить дуже цікаво, чи не могли б ви поділитися більше деталей? або який сценарій слід використовувати?
Ян
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.