REST 中的事务? ?

我想知道您将如何在 REST 中实现以下用例。有没有可能在不影响概念模型的前提下完成呢?

读取或更新单个事务范围内的多个资源。例如,将100美元从鲍勃的银行账户转入约翰的账户。

据我所知,实现这个目标的唯一方法就是作弊。您可以 POST 到与 John 或 Bob 关联的资源,并使用单个事务执行整个操作。就我而言,这破坏了 REST 体系结构,因为您实际上是通过 POST 隧道化 RPC 调用,而不是真正操作单个资源。

88882 次浏览

你必须运行你自己的“事务 ID”类型的 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)

您必须处理将操作存储在 DB (如果负载平衡的话)或内存之类的地方,然后处理提交、回滚、超时。

在公园里可不是个休息的好日子。

我想你可以在 URL/资源中包含 TAN:

  1. PUT/事务获取 ID (例如“1”)
  2. [ PUT,GET,POST,whatever ]/1/account/bob
  3. [ PUT,GET,POST,whatever ]/1/account/bill
  4. ID 为1的 DELETE/事务

只是个想法。

考虑一个 RESTful 购物篮场景。购物篮在概念上是您的事务包装器。您可以将多个项目添加到一个购物篮中,然后提交该购物篮来处理订单,您可以将 Bob 的帐户条目添加到事务包装器,然后将 Bill 的帐户条目添加到事务包装器。当所有组件都就位后,您可以 POST/PUT 包含所有组件的事务包装器。

我认为在这种情况下,打破 REST 的纯理论是完全可以接受的。无论如何,我不认为 REST 中有什么东西实际上说你不能在需要它的业务案例中触及依赖对象。

我真的认为,如果您可以利用数据库来创建自定义事务管理器,那么就不值得为创建自定义事务管理器而进行额外的工作。

在简单的情况下(没有分布式资源) ,您可以将事务视为一种资源,在这种情况下,创建事务的行为达到了最终目标。

因此,要在 <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’在另一家银行的另一个服务后面)——除了说“试着用不需要分布式事务的方式表述所有操作”。

有几个重要的案例没有得到这个问题的回答,我认为这太糟糕了,因为它在谷歌的搜索关键词中排名很高: -)

具体来说,一个恰当的方法是: 如果您 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", ...}

注意,此时多次放置并不重要; 即使 txn 上的 GET 也会返回当前状态。具体来说,第二个 PUT 将检测到第一个 PUT 已经处于适当的状态,并返回它——或者,如果您尝试在它已经处于“提交”状态之后将其放入“回滚”状态,您将得到一个错误,并返回实际提交的事务。

只要与单个数据库或具有集成事务监视器的数据库交谈,这种机制实际上就可以很好地工作。您还可以为事务引入超时,如果愿意,甚至可以使用 Exires 头表示超时。

在 REST 术语中,资源是可以用 CRUD (create/read/update/delete)动词操作的名词。因为没有“转账”动词,所以我们需要定义一个可以使用 CRUD 操作的“事务”资源。下面是 HTTP + POX 中的一个示例。第一步是为 创造(HTTP POST 方法)创建一个新的 空荡荡的事务:

POST /transaction

这将返回一个事务 ID,例如“1234”和相应的 URL“/action/1234”。请注意,多次触发此 POST 不会创建具有多个 ID 的相同事务,而且还可以避免引入“挂起”状态。另外,POST 不能总是幂等的(REST 需求) ,因此最小化 POST 中的数据通常是一个好的实践。

您可以将事务 ID 的生成留给客户端。在这种情况下,您可以使用 POST/action/1234来创建事务“1234”,如果事务“1234”已经存在,服务器将返回一个错误。在错误响应中,服务器可以使用适当的 URL 返回当前未使用的 ID。使用 GET 方法向服务器查询新 ID 并不是一个好主意,因为 GET 永远不会改变服务器状态,创建/保留新 ID 会改变服务器状态。

接下来,我们使用 更新(PUT HTTP 方法)隐式提交包含所有数据的事务:

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

如果 ID 为“1234”的事务之前已经被 PUT,服务器将提供一个错误响应,否则将提供一个 OK 响应和一个 URL 来查看完成的事务。

注意: 在/account/John 中,“ John”应该是 John 的唯一帐号。

问得好,REST 主要通过类似数据库的例子来解释,其中存储、更新、检索、删除了一些内容。很少有像这样的例子,其中服务器应该以某种方式处理数据。我不认为罗伊 · 菲尔丁在他的论文中包含任何内容,毕竟他的论文是基于 http 的。

但是他确实把“ REST”说成是一个状态机,链接可以移动到下一个状态。通过这种方式,文档(表示)跟踪客户机状态,而不是服务器必须这样做。这样,就不存在客户端状态,只存在您所在的链接的状态。

我一直在思考这个问题,在我看来,让服务器为你处理一些东西是合理的,当你上传时,服务器会自动创建相关的资源,并给你链接到它们(事实上,它不需要自动创建它们: 它可以只告诉你链接,它只在你遵循它们的时候创建它们——懒惰创建)。并且还为您提供了创建 新的相关资源的链接-相关资源具有相同的 URI,但是更长(添加后缀)。例如:

  1. 您上传(职位) 所有信息的交易。概念的表示这看起来就像一个 RPC 调用,但它实际上创建了“建议的事务资源”。例如: /transaction 故障将导致创建多个这样的资源,每个资源都有不同的 URI。
  2. 服务器的响应声明创建的资源的 URI,它的表示-这包括链接(URI)来创建 一个新的“提交的事务资源”。的相关资源其他相关资源是删除建议的事务的链接。这些是状态机中的状态,客户机可以遵循这些状态。逻辑上,这些是在服务器上创建的资源的一部分,超出了客户机提供的信息。例如 URI: /transaction/1234/proposed/transaction/1234/committed
  3. 职位链接到 创建“提交的事务资源”,该链接创建该资源,并更改服务器的状态(两个帐户的余额) * * 。就其本质而言,此资源只能创建一次,不能更新。因此,提交许多事务时不会发生故障。
  4. 您可以获取这两个资源,查看它们的状态。假设 POST 可以更改其他资源,那么该提议现在将被标记为“已提交”(或者可能根本不可用)。

这类似于网页的操作方式,最后一个网页上写着“你确定要这样做吗?”最后一个网页本身就是交易状态的一个表示,其中包括一个链接到下一个状态。不仅仅是金融交易; 也(如)预览然后在维基百科上提交。我猜测 REST 中的区别在于状态序列中的每个阶段都有一个显式名称(它的 URI)。

在实际的交易/销售中,对于交易的不同阶段(建议书、订单、收据等) ,通常有不同的物理文档。甚至更多的买房子,与结算等。

OTOH 对我来说,这就像是在玩语义游戏; 我对将动词转换为名词以使其具有 RESTful 的名词化感到不舒服,“因为它使用名词(URI)而不是动词(RPC 调用)”。例如,名词“已提交的事务资源”代替了动词“提交此事务”。我想名词化的一个好处是您可以通过名称引用资源,而不需要以其他方式指定它(比如维护会话状态,这样您就知道“ this”事务是什么...)

但重要的问题是: 这种方法的好处是什么?也就是说。这种 REST 风格在哪些方面优于 RPC 风格?除了存储/检索/更新/删除之外,对网页非常有用的技术对处理信息也有帮助吗?我认为 REST 的主要好处是可伸缩性; 其中一个方面是不需要显式地维护客户机状态(但是要在资源的 URI 中隐含它,并且在其表示中将下一个状态作为链接)。从这个意义上说,这很有帮助。也许这也有助于分层/流水线?OTOH 只有一个用户会查看他们的特定事务,因此缓存它没有优势,让其他人可以阅读它,这是 http 的大胜。

不能在 REST 中使用服务器端事务。

REST 约束之一:

无国籍

在请求之间,服务器上没有存储客户机上下文,这进一步限制了客户机-服务器通信。来自任何客户端的每个请求都包含为请求提供服务所需的所有信息,并且任何会话状态都保存在客户端中。

RESTful 的惟一方法是创建事务重做日志并将其放入客户机状态。对于请求,客户机发送重做日志,服务器重做事务,然后

  1. 回滚事务,但提供新的事务重做日志(更进一步)
  2. 或者最终完成交易。

但是使用基于服务器会话的技术来支持服务器端事务可能更简单。

如果您退后一步来总结这里的讨论,那么很明显,REST 不适合于许多 API,特别是当客户机-服务器交互本质上是有状态的时候,就像处理非平凡事务一样。为什么要跳过客户端和服务器端建议的所有环节,以便迂腐地遵循一些不适合这个问题的原则?一个更好的原则是为客户端提供最简单、最自然、最有效的方法来组合应用程序。

总之,如果您真的在应用程序中执行大量事务(类型,而不是实例) ,那么您真的不应该创建 RESTful API。

我相信使用在客户端上生成的唯一标识符来确保连接打嗝不会意味着 API 节省了双重性。

我认为使用客户端生成的 GUID 字段和传输对象并确保不再重新插入相同的 GUID 将是银行传输问题的一个更简单的解决方案。

不了解更复杂的场景,如多机票预订或微架构。

我找到了一篇关于这个主题的论文,叙述了 处理 RESTful 服务中的事务原子性的经历。

首先,资金转移没有什么是你不能在一个单一的资源调用。你想做的事情是寄钱。因此,您向发送者的帐户添加一个资金转帐资源。

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

成交。您不需要知道这是一个必须是原子的事务等。你只要转账就行了。从 A 寄钱给 B。


但对于这种罕见的情况,一个通用的解决方案是:

如果您想要做一些非常复杂的事情,在一个已定义的上下文中涉及许多资源,并且有许多限制,这些限制实际上跨越了“什么”和“为什么”的障碍(业务与实现知识) ,那么您需要转移状态。由于 REST 应该是无状态的,因此作为客户端,您需要将状态传递给周围的用户。

如果传输状态,则需要对客户端隐藏内部信息。客户端不应该知道只有实现才需要的内部信息,而不应该携带与业务相关的信息。如果这些信息没有业务价值,那么应该对状态进行加密,并使用令牌、传递或其他类似的隐喻。

这样就可以传递内部状态,使用加密和签名系统仍然可以是安全和健全的。为客户机寻找正确的抽象,为什么他要传递状态信息,这取决于设计和体系结构。


真正的解决办法是:

记住 REST 是在谈论 HTTP,HTTP 带来了使用 cookie 的概念。当人们谈论 REST API 和工作流以及跨多个资源或请求的交互时,这些 cookie 常常被遗忘。

还记得维基百科上关于 HTTP cookie 的内容吗:

Cookies 被设计成一种可靠的机制,让网站记住有状态的信息(比如购物车中的商品) ,或者记录用户的浏览活动(包括点击特定的按钮,登录,或者记录用户早在几个月或几年前访问过哪些页面)。

所以基本上,如果需要传递状态,使用 cookie。它的设计原因完全相同,它是 HTTP,因此它在设计上与 REST 兼容:)。


更好的解决办法是:

如果你谈论一个客户端执行一个涉及多个请求的工作流,你通常会谈论协议。每种形式的协议都为每个可能的步骤提供了一组先决条件,比如在执行步骤 A 之后才能执行步骤 B。

这很正常,但是向客户机公开协议会使一切变得更加复杂。为了避免这种情况,只要想想当我们必须在现实世界中做复杂的交互和事情时,我们会做什么... ..。我们需要一个特工。

使用 Agent 的比喻,您可以提供一个资源,该资源可以为您执行所有必要的步骤,并将实际的分配/指令存储在其列表中(因此我们可以对代理或“代理”使用 POST)。

一个复杂的例子:

买房子:

你需要证明你的可信度(比如提供你的警方记录条目) ,你需要确保财务细节,你需要通过律师和储存资金的可信第三方购买实际的房子,核实房子现在是属于你的,并将购买的东西添加到你的税务记录等等(只是一个例子,一些步骤可能是错误的或什么)。

这些步骤可能需要几天时间才能完成,有些可以并行完成等。

为了做到这一点,你只需要给代理人买房子的任务,如:

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

成交。代理机构会给你发回一份参考资料,你可以用它来查看和跟踪这份工作的状态,其余的工作由代理机构的代理机构自动完成。

例如,想想一个错误追踪器。基本上,您可以报告错误并使用错误 ID 来检查正在发生的事情。您甚至可以使用服务来监听此资源的更改。任务完成。

我已经离开这个话题10年了。回过头来看,我简直不敢相信,当你在谷歌上搜索 rest + 可靠性时,你会涉足这个伪装成科学的宗教。这种困惑是虚构的。

我将把这个宽泛的问题分为三类:

  • 下游服务。您开发的任何 Web 服务都将具有您所使用的下游服务,以及您别无选择只能遵循的事务语法。您应该尝试对您的服务的用户隐藏所有这些,并确保您的操作的所有部分作为一个组成功或失败,然后将这个结果返回给您的用户。
  • 你的服务。客户希望对 Web 服务调用产生明确的结果,而通常的 REST 模式是直接对实质性资源发出 POST、 PUT 或 DELETE 请求,这种方式给我的印象是提供这种确定性的一种糟糕且容易改进的方式。如果您关心可靠性,则需要确定操作请求。这个 id 可以是在客户机上创建的 guid,也可以是来自服务器上的关系数据库的种子值,这无关紧要。对于服务器生成的 ID,使用“预起飞”请求-响应来交换操作的 ID。如果此请求失败或一半成功,没有问题,客户端只是重复该请求。没用过的身份证没有坏处。< br > < br > 这很重要,因为它允许所有后续请求完全幂等,也就是说,如果它们重复 n 次,它们返回相同的结果,并且不会导致进一步的事情发生。服务器根据动作 ID 存储所有响应,如果它看到相同的请求,它将重播相同的响应。在 这个谷歌文档中对该模式进行了更全面的处理。文档提出了一个实现,我相信(!),大致遵循 REST 原则。专家们肯定会告诉我它是如何侵犯他人的。无论是否涉及下游事务,这种模式都可以有效地用于对 Web 服务的任何不安全调用。
  • 将您的服务集成到由上游服务控制的“事务”中。在 Web 服务的上下文中,完整的 ACID 事务通常被认为不值得付出努力,但是您可以通过在确认响应中提供取消和/或确认链接来极大地帮助服务的消费者,从而实现 补偿交易

你的要求是最基本的。不要让别人告诉你你的解决方案不合规矩。根据它们处理您的问题的好坏和简单程度来判断它们的架构。