Вопрос

Мне интересно, как бы вы реализовали следующий вариант использования в REST.Можно ли вообще обойтись без ущерба для концептуальной модели?

Чтение или обновление нескольких ресурсов в рамках одной транзакции.Например, переведите 100 долларов США с банковского счета Боба на счет Джона.

Насколько я могу судить, единственный способ реализовать это — обман.Вы можете отправить POST ресурс, связанный с Джоном или Бобом, и выполнить всю операцию, используя одну транзакцию.Насколько я понимаю, это нарушает архитектуру REST, поскольку вы, по сути, туннелируете вызов RPC через POST вместо того, чтобы работать с отдельными ресурсами.

Это было полезно?

Решение

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

Другие советы

Есть несколько важных случаев, на которые этот вопрос не дает ответа, что, на мой взгляд, очень плохо, поскольку он имеет высокий рейтинг в Google по поисковым запросам :-)

В частности, хорошим вариантом было бы:Если вы выполняете POST дважды (из-за сбоя в промежуточном кеше), вам не следует передавать сумму дважды.

Чтобы добиться этого, вы создаете транзакцию как объект.Он может содержать все уже известные вам данные и переводить транзакцию в состояние ожидания.

POST /transfer/txn
{"source":"john's account", "destination":"bob's account", "amount":10}

{"id":"/transfer/txn/12345", "state":"pending", "source":...}

Получив эту транзакцию, вы можете зафиксировать ее, например:

PUT /transfer/txn/12345
{"id":"/transfer/txn/12345", "state":"committed", ...}

{"id":"/transfer/txn/12345", "state":"committed", ...}

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

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

В терминах REST ресурсы — это существительные, с которыми можно работать с помощью глаголов CRUD (создать/читать/обновить/удалить).Поскольку глагола «перевести деньги» не существует, нам нужно определить ресурс «транзакции», с которым можно работать с помощью CRUD.Вот пример в HTTP+POX.Первый шаг – СОЗДАВАТЬ (метод HTTP POST) новый пустой сделка:

POST /transaction

Это возвращает идентификатор транзакции, например.«1234» и по URL «/transaction/1234».Обратите внимание, что запуск этого POST несколько раз не приведет к созданию одной и той же транзакции с несколькими идентификаторами, а также позволит избежать перехода в состояние «ожидание».Кроме того, POST не всегда может быть идемпотентным (требование REST), поэтому обычно рекомендуется минимизировать данные в POST.

Вы можете оставить создание идентификатора транзакции на усмотрение клиента.В этом случае вы должны выполнить POST /transaction/1234, чтобы создать транзакцию «1234», и сервер вернет ошибку, если она уже существовала.В ответе об ошибке сервер может вернуть неиспользуемый в настоящее время идентификатор с соответствующим URL-адресом.Не рекомендуется запрашивать у сервера новый идентификатор с помощью метода GET, поскольку GET никогда не должен изменять состояние сервера, а создание/резервирование нового идентификатора приведет к изменению состояния сервера.

Дальше мы ОБНОВЛЯТЬ (метод PUT HTTP) транзакция со всеми данными, неявно фиксирующая ее:

PUT /transaction/1234
<transaction>
  <from>/account/john</from>
  <to>/account/bob</to>
  <amount>100</amount>
</transaction>

Если транзакция с идентификатором «1234» уже была помещена ранее, сервер выдает ответ об ошибке, в противном случае — ответ «ОК» и URL-адрес для просмотра завершенной транзакции.

Примечание:в /account/john «john» действительно должен быть уникальным номером счета Джона.

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

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

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

  1. Вы загружаете (ПОЧТА) представление понятия транзакция со всей информацией. Это похоже на вызов RPC, но на самом деле он создает «предлагаемый ресурс транзакции».например URI: /transactionСбои приводят к созданию нескольких таких ресурсов, каждый со своим URI.
  2. В ответе сервера указывается URI созданного ресурса, его представление, включая ссылку (URI) для создания связанного ресурса новый «ресурс зафиксированной транзакции». На других связанных ресурсах есть ссылка на удаление предложенной транзакции.Это состояния конечного автомата, которым может следовать клиент.Логично, что это часть ресурса, созданного на сервере, помимо информации, предоставленной клиентом.например URI: /transaction/1234/proposed, /transaction/1234/committed
  3. Ты ПОЧТА по ссылке на создать «ресурс зафиксированной транзакции», который создает этот ресурс, изменяя состояние сервера (балансы двух учетных записей)**.По своей природе этот ресурс может быть создан только один раз и не подлежит обновлению.Поэтому сбоев при совершении большого количества транзакций возникнуть не может.
  4. Вы можете ПОЛУЧИТЬ эти два ресурса, чтобы увидеть их состояние.Если предположить, что POST может изменить другие ресурсы, предложение теперь будет помечено как «зафиксированное» (или, возможно, вообще недоступное).

Это похоже на то, как работают веб -страницы, с окончательной веб -страницей «Вы уверены, что хотите это сделать?» Эта последняя веб -страница сама является представлением состояния транзакции, которая включает ссылку для перейти в следующее состояние.Не только финансовые операции;также (например) просмотрите, а затем зафиксируйте в Википедии.Я предполагаю, что отличие REST заключается в том, что каждый этап последовательности состояний имеет явное имя (его URI).

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

OTOH Для меня это похоже на игру с семантикой;Мне не нравится номинализация преобразования глаголов в существительные, чтобы сделать его RESTful, «потому что он использует существительные (URI) вместо глаголов (вызовы RPC)».то естьсуществительное «зафиксированный ресурс транзакции» вместо глагола «зафиксировать эту транзакцию».Я предполагаю, что одним из преимуществ номинализации является то, что вы можете ссылаться на ресурс по имени вместо необходимости указывать его каким-либо другим способом (например, поддерживать состояние сеанса, чтобы вы знали, что такое «эта» транзакция...)

Но важный вопрос:Каковы преимущества этого подхода?то естьЧем этот стиль REST лучше стиля RPC?Является ли метод, который отлично подходит для веб-страниц, полезным и для обработки информации, помимо сохранения/извлечения/обновления/удаления?Я думаю, что ключевым преимуществом REST является масштабируемость;один из аспектов этого заключается в том, что нет необходимости явно поддерживать состояние клиента (но сделать его неявным в URI ресурса, а следующие состояния - в виде ссылок в его представлении).В этом смысле это помогает.Возможно, это тоже помогает в многоуровневой/конвейерной обработке?OTOH только один пользователь будет просматривать свою конкретную транзакцию, поэтому нет смысла кэшировать ее, чтобы другие могли ее прочитать, что является большой победой для http.

Если вы отступите и подведете итог обсуждения, то станет совершенно ясно, что REST не подходит для многих API, особенно когда взаимодействие клиент-сервер по своей сути имеет состояние, как это происходит с нетривиальными транзакциями.Зачем прыгать через все предложенные препятствия, как для клиента, так и для сервера, чтобы педантично следовать какому-то принципу, который не соответствует задаче?Лучший принцип — предоставить клиенту самый простой, естественный и продуктивный способ создания приложения.

Подводя итог, если вы действительно выполняете в своем приложении много транзакций (типов, а не экземпляров), вам действительно не следует создавать RESTful API.

Вам придется внедрить свой собственный тип управления передачей «идентификатор транзакции».Итак, это будет 4 вызова:

http://service/transaction (some sort of tx request)
http://service/bankaccount/bob (give tx id)
http://service/bankaccount/john (give tx id)
http://service/transaction (request to commit)

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

Не совсем ОТДЫХАЮЩИЙ день в парке.

Я отошёл от этой темы на 10 лет.Возвращаясь назад, я не могу поверить в религию, маскирующуюся под науку, в которую вы погружаетесь, когда гуглите rest+reliable.Путаница мифическая.

Я бы разделил этот общий вопрос на три:

  • Нижестоящие услуги.Любая веб-служба, которую вы разрабатываете, будет иметь последующие службы, которые вы используете, и синтаксису транзакций которых у вас нет другого выбора, кроме как следовать.Вам следует попытаться скрыть все это от пользователей вашего сервиса и убедиться, что все части вашей операции успешны или неудачны как группа, а затем вернуть этот результат своим пользователям.
  • Ваши услуги.Клиенты хотят однозначных результатов для вызовов веб-сервисов, и обычный шаблон REST, заключающийся в выполнении запросов POST, PUT или DELETE непосредственно к основным ресурсам, кажется мне плохим и легко улучшаемым способом обеспечения этой уверенности.Если вы заботитесь о надежности, вам необходимо определить запросы на действия.Этот идентификатор может быть идентификатором, созданным на клиенте, или начальным значением из реляционной базы данных на сервере, это не имеет значения.Для идентификаторов, сгенерированных сервером, запрос-ответ предназначен для обмена идентификатором.Если этот запрос не удался или наполовину удался, не проблема, клиент просто повторяет запрос.Неиспользованные идентификаторы не причиняют вреда.

    Это важно, поскольку позволяет всем последующим запросам быть полностью идемпотентными в том смысле, что если они повторяются n раз, они возвращают один и тот же результат и больше ничего не вызывают.Сервер сохраняет все ответы по идентификатору действия, и если он видит тот же запрос, он воспроизводит тот же ответ.Более полное рассмотрение этого узора находится в этот гугл документ.В документе предлагается реализация, которая, как мне кажется (!), в целом соответствует принципам REST.Эксперты наверняка скажут мне, как это нарушает других.Этот шаблон можно с пользой использовать для любого небезопасного вызова вашего веб-сервиса, независимо от того, задействованы ли нисходящие транзакции.
  • Интеграция вашего сервиса в «транзакции», контролируемые вышестоящими сервисами.В контексте веб-сервисов полные ACID-транзакции обычно не стоят затраченных усилий, но вы можете значительно помочь потребителям вашего сервиса, предоставив ссылки на отмену и/или подтверждение в ответе на подтверждение и, таким образом, достичь сделки по вознаграждению.

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

Прежде всего, перевод денег – это то, что вы не можете сделать за один вызов ресурса.Действие, которое вы хотите сделать, — это отправить деньги.Таким образом вы добавляете ресурс денежных переводов на счет отправителя.

POST: accounts/alice, new Transfer {target:"BOB", abmount:100, currency:"CHF"}.

Сделанный.Вам не нужно знать, что это транзакция, которая должна быть атомарной и т. д.Вы просто переводите деньги, т.е.отправить деньги из А в Б.


Но для редких случаев есть общее решение:

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

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

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


Реальное решение:

Помните, что REST говорит о HTTP, а HTTP предполагает использование файлов cookie.Эти файлы cookie часто забывают, когда люди говорят о REST API, рабочих процессах и взаимодействиях, охватывающих несколько ресурсов или запросов.

Помните, что написано в Википедии о файлах cookie HTTP:

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

В общем, если вам нужно передать состояние, используйте файл cookie.Он разработан по той же причине: это HTTP и, следовательно, он совместим с REST по дизайну :).


Лучшее решение:

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

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

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

Сложный пример:

Покупка дома:

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

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

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

POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}.

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

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

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

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

Вы не должны использовать транзакции на стороне сервера в REST.

Одно из ограничений REST:

Лицо без гражданства

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

Единственный способ RESTful — создать журнал повтора транзакций и перевести его в состояние клиента.Вместе с запросами клиент отправляет журнал повторов, а сервер повторяет транзакцию и

  1. откатывает транзакцию, но предоставляет новый журнал повтора транзакции (на один шаг дальше)
  2. или наконец завершить транзакцию.

Но, возможно, проще использовать технологию, основанную на сеансах сервера, которая поддерживает транзакции на стороне сервера.

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

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

Не знаю о более сложных сценариях, таких как многократное бронирование авиабилетов или микроархитектуры.

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

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

Итак, для перемещения между <url-base>/account/a и <url-base>/account/b, вы можете опубликовать следующее в <url-base>/transfer.

<transfer>
    <from><url-base>/account/a</from>
    <to><url-base>/account/b</to>
    <amount>50</amount>
</transfer>

Это создаст новый ресурс передачи и вернет новый URL-адрес передачи - например <url-base>/transfer/256.

Таким образом, в момент успешной публикации на сервере выполняется «настоящая» транзакция, и сумма снимается с одного счета и добавляется на другой.

Однако это не распространяется на распределенную транзакцию (если, скажем, «а» хранится в одном банке за одной услугой, а «б» — в другом банке за другой услугой) — кроме как сказать «попробуйте сформулировать все операции способами, которые не требуют распределенных транзакций».

Я думаю, вы могли бы включить TAN в URL/ресурс:

  1. PUT/транзакция для получения идентификатора (например."1")
  2. [PUT, GET, POST, что угодно] /1/account/bob
  3. [PUT, GET, POST, что угодно] /1/аккаунт/счет
  4. DELETE/транзакция с идентификатором 1

Просто идея.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top