157

我想知道您将如何在 REST 中实现以下用例。是否有可能在不损害概念模型的情况下做到这一点?

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

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

4

13 回答 13

93

考虑一个 RESTful 购物篮场景。购物篮在概念上是您的交易包装器。与您可以将多个商品添加到购物篮然后提交该购物篮以处理订单的方式相同,您可以将 Bob 的帐户条目添加到交易包装器中,然后将 Bill 的帐户条目添加到包装器中。当所有部分都到位后,您可以使用所有组件部分发布/放置事务包装器。

于 2008-09-29T02:13:30.790 回答
63

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

具体来说,一个不错的属性是:如果您 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", ...}

请注意,此时多个 put 无关紧要。甚至 txn 上的 GET 也会返回当前状态。具体来说,第二个 PUT 会检测到第一个 PUT 已经处于适当的状态,然后将其返回——或者,如果您在它已经处于“提交”状态后尝试将其置于“回滚”状态,您会得到一个错误,并返回实际提交的事务。

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

于 2011-01-19T22:46:28.447 回答
35

在 REST 术语中,资源是可以使用 CRUD(创建/读取/更新/删除)动词作用的名词。由于没有“转账”动词,我们需要定义一个可以使用 CRUD 执行的“交易”资源。这是 HTTP+POX 中的一个示例。第一步是创建(HTTP POST 方法)一个新的事务:

POST /transaction

这将返回一个事务 ID,例如“1234”和相应的 URL“/transaction/1234”。请注意,多次触发此 POST 不会创建具有多个 ID 的同一事务,并且还会避免引入“待处理”状态。此外,POST 不能始终是幂等的(REST 要求),因此最好尽量减少 POST 中的数据。

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

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

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

如果之前已经 PUT 了 ID 为“1234”的事务,则服务器给出错误响应,否则给出 OK 响应和 URL 以查看已完成的事务。

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

于 2011-04-03T23:56:16.000 回答
20

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

但他确实将“代表性状态转移”作为状态机谈论,链接移动到下一个状态。通过这种方式,文档(表示)跟踪客户端状态,而不是服务器必须这样做。通过这种方式,没有客户端状态,只有您在哪个链接上的状态。

我一直在想这个,在我看来,让服务器为你处理一些东西,当你上传时,服务器会自动创建相关资源,并给你它们的链接(实际上它不会不需要自动创建它们:它可以只告诉您链接,并且仅在您关注它们时创建它们 - 延迟创建)。并且还为您提供创建相关资源的链接 - 相关资源具有相同的 URI 但更长(添加后缀)。例如:

  1. 您上传(POST )包含所有信息的交易概念的表示。 这看起来就像一个 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 的一大胜利。

于 2011-12-14T04:33:37.473 回答
15

我已经离开这个话题 10 年了。回来,我无法相信当你谷歌休息+可靠时你涉足的伪装成科学的宗教。混乱是神话般的。

我会将这个广泛的问题分为三个:

  • 下游服务。您开发的任何 Web 服务都将具有您使用的下游服务,并且您别无选择,只能遵循其事务语法。您应该尝试对您的服务用户隐藏所有这些,并确保您的操作的所有部分作为一个组成功或失败,然后将此结果返回给您的用户。
  • 您的服务。客户希望 Web 服务调用得到明确的结果,而直接在实质性资源上发出 POST、PUT 或 DELETE 请求的通常 REST 模式让我觉得这是一种提供这种确定性的糟糕且易于改进的方式。如果您关心可靠性,则需要识别操作请求。这个 id 可以是在客户端创建的 guid,也可以是来自服务器上的关系数据库的种子值,没关系。对于服务器生成的 ID,使用“预检”请求-响应来交换操作的 ID。如果这个请求失败或成功一半,没问题,客户端只是重复请求。未使用的 id 没有害处。

    这很重要,因为它允许所有后续请求完全幂等,因为如果它们重复 n 次,它们将返回相同的结果并且不会导致任何进一步的发生。服务器存储所有针对 action id 的响应,如果它看到相同的请求,它会重放相同的响应。在这个 google doc中对模式进行了更全面的处理。该文档提出了一种实现,我相信(!),它广泛遵循 REST 原则。专家肯定会告诉我它是如何侵犯他人的。无论是否涉及下游事务,此模式都可用于对 Web 服务的任何不安全调用。
  • 将您的服务集成到由上游服务控制的“事务”中。在 Web 服务的上下文中,完整的 ACID 交易通常被认为不值得付出努力,但是您可以通过在确认响应中提供取消和/或确认链接来极大地帮助您的服务的消费者,从而通过补偿实现交易

您的要求是基本要求。不要让人们告诉你你的解决方案是不洁的。根据他们解决问题的好坏程度和简单程度来判断他们的架构。

于 2016-02-15T09:35:39.710 回答
13

如果您回过头来总结这里的讨论,很明显 REST 不适用于许多 API,尤其是当客户端-服务器交互本质上是有状态的时,因为它具有非平凡事务。为什么要跳过所有建议的箍,对于客户端和服务器来说,为了迂腐地遵循一些不适合问题的原则?一个更好的原则是为客户提供最简单、最自然、最有效的方式来组合应用程序。

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

于 2014-03-09T03:57:25.197 回答
10

您必须推出自己的“交易 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)

您必须处理将操作存储在数据库(如果负载平衡)或内存等中,然后处理提交、回滚、超时。

在公园里不是真正的休息日。

于 2008-09-29T01:52:04.630 回答
3

首先,转账是您在单个资源调用中无法完成的事情。您要做的操作是汇款。因此,您将汇款资源添加到发件人的帐户中。

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

完毕。您不需要知道这是一个必须是原子的交易等。您只需转移资金。从 A 向 B 汇款。


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

如果你想做一些非常复杂的事情,在定义的上下文中涉及许多资源,并且有很多限制,实际上跨越了什么与为什么的障碍(业务与实施知识),你需要转移状态。由于 REST 应该是无状态的,因此您作为客户端需要转移状态。

如果您传输状态,则需要向客户端隐藏内部信息。客户不应知道仅执行所需的内部信息,但不携带与业务相关的信息。如果这些信息没有商业价值,则应该对状态进行加密,并且需要使用令牌、通行证等隐喻。

通过这种方式,人们可以传递内部状态,并且使用加密和签名系统仍然可以安全可靠。为客户找到正确的抽象为什么要传递状态信息取决于设计和架构。


真正的解决方案:

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

记住维基百科中关于 HTTP cookie 的内容:

Cookie 旨在成为网站记住状态信息(例如购物车中的商品)或记录用户浏览活动(包括单击特定按钮、登录或记录用户访问过哪些页面)的可靠机制回到几个月或几年前)。

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


更好的解决方案:

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

这是很自然的,但向客户端公开协议会使一切变得更加复杂。为了避免它,只要想想我们在现实世界中必须进行复杂的交互和事情时会做什么...... 我们使用代理。

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

一个复杂的例子:

买房:

您需要证明您的可信度(例如提供您的警察记录条目),您需要确保财务细节,您需要使用律师和受信任的第三方存储资金购买实际房屋,验证房屋现在属于您并且将购买的东西添加到您的税务记录等中(例如,某些步骤可能是错误的或其他)。

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

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

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

完毕。该机构会向您发回一份参考资料,您可以使用该参考资料查看和跟踪该工作的状态,其余工作由该机构的代理人自动完成。

例如,考虑一个错误跟踪器。基本上,您报告错误并可以使用错误 ID 来检查发生了什么。你甚至可以使用服务来监听这个资源的变化。任务完成。

于 2015-10-19T09:22:09.030 回答
2

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

REST 约束之一:

无状态

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

唯一的 RESTful 方式是创建事务重做日志并将其置于客户端状态。通过请求,客户端发送重做日志,服务器重做事务并

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

但也许使用支持服务器端事务的基于服务器会话的技术更简单。

于 2013-07-05T08:58:17.080 回答
1

我认为在这种情况下,在这种情况下打破 REST 的纯理论是完全可以接受的。无论如何,我认为 REST 中实际上没有任何内容表明您不能在需要它的业务案例中触及依赖对象。

我真的认为,当您可以利用数据库来执行此操作时,您将跳过创建自定义事务管理器的额外步骤是不值得的。

于 2008-09-29T02:35:37.257 回答
1

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

因此,要在<url-base>/account/a和之间转移<url-base>/account/b,您可以将以下内容发布到<url-base>/transfer

<转移>
    <from><url-base>/account/a</from>
    <to><url-base>/account/b</to>
    <数量>50</数量>
</转移>

这将创建一个新的传输资源并返回传输的新 url - 例如<url-base>/transfer/256

然后,在成功发布的那一刻,“真实”交易在服务器上进行,金额从一个帐户中删除并添加到另一个帐户。

然而,这不包括分布式事务(如果说“a”在一个服务后面的一家银行持有,而“b”在另一个服务后面的另一家银行持有)——除了说“试着把所有以不需要分布式事务的方式进行操作”。

于 2010-10-08T03:37:33.737 回答
1

我相信这将是使用在客户端生成的唯一标识符来确保连接中断不会暗示 API 保存的重复性的情况。

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

不知道更复杂的场景,例如多机票预订或微架构。

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

于 2015-03-26T19:22:24.927 回答
-4

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

  1. PUT /transaction 获取ID(例如“1”)
  2. [PUT,GET,POST,随便] /1/account/bob
  3. [PUT、GET、POST 等] /1/account/bill
  4. 删除/事务 ID 1

只是一个想法。

于 2008-09-29T02:01:48.117 回答