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

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

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

有帮助吗?

解决方案

考虑一个RESTful购物篮场景。购物篮在概念上是您的交易包装。以同样的方式,您可以将多个项目添加到购物篮,然后提交该篮子来处理订单,您可以将Bob的帐户条目添加到事务包装器,然后将Bill的帐户条目添加到包装器。当所有部分都到位后,您可以使用所有组件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", ...}

请注意,此时多次放置并不重要;即使是txn上的GET也会返回当前状态。具体来说,第二个PUT将检测到第一个PUT已经处于适当的状态,并且只是返回它 - 或者,如果你试图将它放入<!>“rollback <!>”;在它已经在<!> quot; commit <!>之后的状态状态,您将收到错误,并返回实际提交的事务。

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

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

POST /transaction

这会返回一个交易ID,例如QUOT <!>; <!> 1234 QUOT;并根据URL <!> quot; / transaction / 1234 <!> quot;。请注意,多次触发此POST不会创建具有多个ID的同一事务,并且还会避免引入<!> quot; pending <!> quot;州。此外,POST不能始终是幂等的(REST要求),因此通常最好将POST中的数据最小化。

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

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

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

如果ID <!>的交易“1234 <!>”;之前一直是PUT,服务器给出错误响应,否则有OK响应和URL来查看已完成的事务。

NB:in / account / john,<!> quot; john <!> quot;应该是John唯一的帐号。

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

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

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

  1. 您上传( POST )包含所有信息的事务概念的表示。这看起来就像一个RPC调用,但它确实创建了<! >“建议的交易资源<!>”;例如URI:/transaction 毛刺会导致创建多个这样的资源,每个资源都有不同的URI。
  2. 服务器的响应说明创建的资源的URI,其表示 - 这包括用于创建新<!>的相关资源的链接( URI );已提交的事务资源<! > quot; 其他相关资源是删除建议交易的链接。这些是状态机中的状态,客户端可以遵循这些状态。从逻辑上讲,这些是在服务器上创建的资源的一部分,超出了客户端提供的信息。例如URI:/transaction/1234/proposed/transaction/1234/committed
  3. 发布指向创建<!>“已提交事务资源<!> 的链接,该资源会创建该资源,从而更改服务器的状态(两个账户的余额)**。就其本质而言,此资源只能创建一次,并且无法更新。因此,不会发生提交许多交易的故障。
  4. 您可以获取这两种资源,了解其状态。假设POST可以更改其他资源,则提案现在将标记为<!> quot; committed <!> quot; (或者根本不可用)。
  5. 这类似于网页的运作方式,最后的网页上写着<!>“你确定要这样做吗?<!> quot;最终的网页本身就是交易状态的表示,其中包括转到下一个状态的链接。不只是金融交易;也(例如)预览然后在维基百科上提交。我想REST中的区别在于状态序列中的每个阶段都有一个显式名称(它的URI)。

    在现实交易/销售中,交易的不同阶段(提案,采购订单,收据等)通常有不同的实物文件。甚至更多的是买房子,还有结算等。

    OTOH这对我来说就像玩语义一样;我对将动词转换为名词以使其成为RESTful的名词化感到不舒服,因为它使用名词(URI)代替动词(RPC调用)<!>“;即名词<!>“;已提交的交易资源<!>”;而不是动词<!>“提交此事务<!>”;我想名词化的一个优点是你可以按名称引用资源,而不需要以其他方式指定它(例如维护会话状态,所以你知道什么是<!> quot; this <!> quot; transaction i小号...)

    但重要的问题是:这种方法有哪些好处?即这种REST风格在哪种方式比RPC风格更好?对于网页而言,这项技术是否也有助于处理信息,而不是存储/检索/更新/删除?我认为REST的主要优点是可扩展性;其中一个方面是不需要显式维护客户端状态(但是将其隐含在资源的URI中,并将下一个状态作为其表示中的链接)。从这个意义上说它有所帮助也许这有助于分层/流水线? OTOH只有一个用户会查看他们的特定交易,因此缓存它没有任何优势,所以其他人可以阅读它,这是http的重大胜利。

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

总之,如果你真的在应用程序中做了很多事务(类型,而不是实例),那么你真的不应该创建一个RESTful API。

你必须推出自己的<!>“;交易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中存储操作(如果负载平衡)或在内存中等,然后处理提交,回滚,超时。

在公园里并不是一个充实的日子。

我已经远离这个话题十年了。回来后,我不敢相信当你在谷歌上搜索“rest+reliable”时,你会涉足伪装成科学的宗教。这种混乱是神话般的。

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

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

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

您的要求是一个基本要求。不要让人们告诉您您的解决方案不符合犹太洁食。根据他们解决问题的程度和简单程度来判断他们的架构。

首先,转移资金并不是单一资源调用中无法做到的事情。你想要做的就是汇款。因此,您将汇款资源添加到发件人的帐户。

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兼容:)。


更好的解决方案:

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

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

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

一个复杂的例子:

买房:

您需要证明自己的可信度(比如提供您的警察记录条目),您需要确保财务细节,您需要使用律师和存储资金的可信第三方购买实际房屋,验证房屋现在属于给你并将购买的东西添加到你的税务记录等(仅作为一个例子,一些步骤可能是错误的或等等。)

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

为了做到这一点,你只需给代理商买房子就像:

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

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

考虑一下bug跟踪器。基本上你报告错误,并可以使用错误ID来检查发生了什么。你甚至可以使用服务来倾听o此资源的更改。任务完成。

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

我真的认为,只要利用数据库来创建自定义事务管理器,就不值得花费额外的箍。

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

REST的一个限制因素:

  

无状态

     

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

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

  1. 回滚事务但提供了一个新的事务重做日志(更进了一步)
  2. 或最后完成交易。
  3. 但也许使用支持服务器端事务的基于服务器会话的技术更简单。

我相信使用在客户端上生成的唯一标识符可以确保连接打嗝不会暗示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-base>/transfer/256

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

然而,这并不包括分布式交易(如果,'a'在一个银行后面的一个银行持有,'b'在另一个银行后面的另一个银行持有) - 除了说<! >“尝试以不需要分布式事务的方式对所有操作进行短语<。>”。

我猜你可以在网址/资源中加入TAN:

  1. PUT / transaction获取ID(例如<!> quot; 1 <!> quot;)
  2. [PUT,GET,POST,无论如何] / 1 / account / bob
  3. [PUT,GET,POST,无论如何] / 1 / account / bill
  4. 使用ID 1
  5. 删除/交易

    只是一个想法。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top