Транзакции в REST? - PullRequest
       59

Транзакции в REST?

139 голосов
/ 29 сентября 2008

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

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

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

Ответы [ 13 ]

84 голосов
/ 29 сентября 2008

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

52 голосов
/ 20 января 2011

Есть несколько важных случаев, на которые этот вопрос не получил ответа, и я думаю, что это слишком плохо, поскольку он имеет высокий рейтинг в 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, если хотите.

30 голосов
/ 04 апреля 2011

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

POST /transaction

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

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

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

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

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

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

18 голосов
/ 14 декабря 2011

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

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

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

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

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

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

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

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

11 голосов
/ 09 марта 2014

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

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

9 голосов
/ 29 сентября 2008

Вам нужно было бы свернуть свой собственный тип транзакции с идентификатором управления. Так было бы 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)

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

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

8 голосов
/ 15 февраля 2016

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

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

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

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

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

3 голосов
/ 19 октября 2015

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

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

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


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

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

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

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


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

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

Помните, что написано в Википедии о HTTP-куки:

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

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


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

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

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

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

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

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

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

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

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

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

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

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

2 голосов
/ 29 сентября 2008

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

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

1 голос
/ 26 марта 2015

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

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

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

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

...