Сделки в REST?

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

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

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

Ответ 1

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

Ответ 2

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

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

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

POST /transfer/txn
{"source":"john account", "destination":"bob 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", ...}

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

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

Ответ 3

В терминах REST ресурсы - это существительные, с которыми можно работать с CRUD (create/read/update/delete) глаголами. Поскольку нет глагола "переводных денег", нам нужно определить ресурс транзакции, с которым можно бороться с CRUD. Вот пример в HTTP + POX. Первый шаг - CREATE (метод HTTP POST) - новая транзакция empty:

POST /transaction

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

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

Далее, мы UPDATE (метод PUT HTTP) транзакцию со всеми данными, неявно совершая ее:

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

На самом деле не день RESTful в парке.

Ответ 8

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

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

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


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

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

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

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


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

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

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

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

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


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

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

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

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

Комплексный пример:

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

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

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

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

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

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

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

Ответ 9

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

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

Ответ 10

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

Один из элементов REST:

Безгражданства

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

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

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

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

Ответ 11

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

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

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

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

Ответ 12

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

Итак, для передачи между <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.

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

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

Ответ 13

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

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

Просто идея.