УСПОКОИТЕЛЬНАЯ идемпотентность

Я разрабатываю УСПОКОИТЕЛЬНЫЙ веб-сервис, использующий ROA (Ресурс ориентировал архитектуру).

Я пытаюсь разработать эффективный способ гарантировать идемпотентность для ПОМЕЩЕННЫХ запросов, которые создают новые ресурсы в случаях, что сервер определяет ключ ресурса.

От моего понимания традиционный подход должен создать тип ресурса транзакции, такого как/CREATE_PERSON. Взаимодействие клиент-сервер для создания нового ресурса человека было бы в двух частях:

Шаг 1: Получите уникальный идентификатор транзакции для создания нового ресурса ЧЕЛОВЕКА:::

**Client request:**
POST /CREATE_PERSON

**Server response:**
200 OK
transaction-id:"as8yfasiob"

Шаг 2: Создайте новый ресурс человека в запросе, который, как гарантируют, будет уникален при помощи идентификатора транзакции:::

**Client request**
PUT /CREATE_PERSON/{transaction_id}
first_name="Big bubba"

**Server response**
201 Created             // (If the request is a duplicate, it would send this
PersonKey="398u4nsdf"   // same response without creating a new resource.  It
                        // would perhaps send an error response if the was used
                        // on a transaction id non-duplicate request, but I have
                        // control over the client, so I can guarantee that this
                        // won't happen)

Проблема, которую я вижу с этим подходом, состоит в том, что он требует отправляющего двух запросов к серверу, чтобы сделать к единственной операции создания нового ресурса ЧЕЛОВЕКА. Это создает увеличение проблем производительности шанса, что пользователь будет ждать клиент для завершения их запроса.

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

Существует ли способ сделать это?

Править::::::

Решение, с которым мы закончили тем, что шли, состояло в том, чтобы клиент получил UUID, и отправьте его наряду с запросом. UUID является очень большим количеством, занимающим место 16 байтов (2^128). Вопреки тому, что кто-то с умом программирования мог бы интуитивно думать, это - принятая практика, чтобы случайным образом генерировать UUID и предположить, что это - уникальное значение. Это вызвано тем, что количество возможных значений является столь большим, что разногласия генерации двух из того же числа случайным образом являются достаточно низкими, чтобы быть фактически невозможными.

Один протест состоит в том, что мы сделали, чтобы наши клиенты запросили UUID с сервера (GET uuid/). Это вызвано тем, что мы не можем гарантировать среду, в которой работает наш клиент. Если бы была проблема такой как с отбором генератор случайных чисел на клиенте, то очень хорошо могла быть коллизия UUID.

5
задан Chris Dutrow 8 June 2010 в 22:51
поделиться

4 ответа

Вы используете неправильную команду HTTP для операции создания. RFC 2616 определяет семантику операций для POST и PUT .

Параграф 9.5:

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

Параграф 9.6

Метод PUT запрашивает, чтобы закрытый объект храниться под предоставленный Request-URI.

Есть тонкие детали этого поведения, например PUT может использоваться для создания нового ресурса по указанному URL, если он еще не существует. Однако POST никогда не должен помещать новый объект в URL запроса, а PUT всегда должен помещать любой новый объект в URL запроса. Эта связь с URL-адресом запроса определяет POST как CREATE и PUT как UPDATE .

Согласно этой семантике, если вы хотите использовать PUT для создания нового человека, его следует создать в / CREATE_PERSON / {transaction_id} .Другими словами, идентификатор транзакции, возвращаемый вашим первым запросом, должен быть личным ключом, используемым для получения этой записи позже. Не следует делать запрос PUT к URL-адресу, который не будет конечным местоположением этой записи.

Однако еще лучше, вы можете сделать это как атомарную операцию, используя POST до / CREATE_PERSON . Это позволяет вам с помощью одного запроса создать новую запись о человеке и в ответе получить новый идентификатор (который также должен быть указан в заголовке HTTP Location ).

Между тем, в правилах REST указано, что глаголы не должны быть частью URL-адреса ресурса. Таким образом, URL-адрес для создания нового человека должен быть таким же, как и местоположение для получения списка всех лиц - / PERSONS (я предпочитаю форму множественного числа :-)).

Таким образом, ваш REST API становится следующим:

  • для получения всех лиц - GET / PERSONS
  • для получения одного человека - GET / PERSONS / {id}
  • для создания нового человека - POST / PERSONS с телом, содержащим данные для новой записи
  • для обновления существующего человека или создания нового человека с известным идентификатором - PUT / PERSONS / {id} с тело, содержащее данные для обновленной записи.
  • для удаления существующего человека - DELETE / PERSONS / {id}

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

Обновление: Вы правы, что POST не идемпотентен и соответствует спецификации HTTP. POST всегда возвращает новый ресурс. В приведенном выше примере этот новый ресурс будет контекстом транзакции.

Однако я хочу сказать, что вы хотите, чтобы PUT использовался для создания нового ресурса (записи о человеке) и, согласно спецификации HTTP, сам новый ресурс должен располагаться по URL-адресу. В частности, когда ваш подход ломается, URL-адрес, который вы используете с PUT , является представлением транзакционного контекста, созданного POST, а не представлением самого нового ресурса. Другими словами, запись о человеке - это побочный эффект обновления записи транзакции, а не ее непосредственный результат (обновленная запись транзакции).

Конечно, при таком подходе запрос PUT будет идемпотентным, поскольку после создания записи о человеке и «завершения» транзакции последующие запросы PUT ничего не сделают. Но теперь у вас другая проблема - чтобы действительно обновить эту запись о человеке, вам нужно будет сделать запрос PUT на другой URL-адрес - тот, который представляет запись человека, а не транзакцию, в которой она была создана. Итак, теперь у вас есть два отдельных URL-адреса, которые ваши клиенты API должны знать и делать запросы для управления одним и тем же ресурсом.

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

4
ответ дан 14 December 2019 в 08:43
поделиться

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


POST /persons

first_name=foo

ответ будет:


HTTP 201 CREATED
...
payload_containing_data_and_auto_generated_id

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

0
ответ дан 14 December 2019 в 08:43
поделиться

Я только что наткнулся на этот пост: Простое доказательство того, что GUID не является уникальным

Хотя вопрос повсеместно высмеивается, некоторые из ответов углубляются в объяснение GUID. Похоже, что GUID - это число размером 2^128 и что вероятность случайной генерации двух одинаковых чисел такого размера настолько мала, что невозможна для всех практических целей.

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

2
ответ дан 14 December 2019 в 08:43
поделиться

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

Ваша первая операция - это GET, но это небезопасная операция, поскольку она «создает» новый идентификатор транзакции. Я бы посоветовал использовать POST более подходящий глагол.

Вы упомянули, что вас беспокоят проблемы с производительностью, которые могут возникнуть у пользователя, вызванные двумя циклами приема-передачи. Это потому, что ваш пользователь собирается создать 500 объектов одновременно, или потому что вы находитесь в сети с огромными проблемами задержки?

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

1
ответ дан 14 December 2019 в 08:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: