REST中的事务?
我想知道如何在REST中实现下面的用例。 甚至有可能做到不损害概念模型?
在单个事务的范围内读取或更新多个资源。 例如,从Bob的银行账户转账$ 100到John的账户。
据我所知,实施这个的唯一方法就是作弊。 您可以对与John或Bob关联的资源进行POST,并使用单个事务执行整个操作。 就我而言,这打破了REST体系结构,因为你基本上是通过POST隧道进行RPC调用,而不是真正在单个资源上运行。
考虑一个RESTful购物篮场景。 购物篮在概念上是您的交易包装。 同样,您可以将多个项目添加到购物篮,然后提交该购物篮以处理订单,您可以将Bob的帐户条目添加到交易包装,然后将Bill的帐户条目添加到包装。 当所有的部分都在那里,那么你可以POST / PUT事务包装与所有组件。
有几个重要的案例没有回答这个问题,我认为这太糟糕了,因为它在Google上的search条件排名很高:-)
具体而言,一个不错的财产将是:如果你两次POST(因为一些caching在中间打嗝)你不应该两次转移的金额。
为了达到这个目的,你创build一个事务作为一个对象。 这可能包含您已知的所有数据,并将事务处于未决状态。
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将检测到第一个已经处于适当的状态,并且只是返回它 – 或者,如果在已经处于“提交”状态之后尝试将其置于“回退”状态,则会得到错误,以及实际提交的事务回来。
只要你和一个单一的数据库或一个集成了事务监视器的数据库交谈,这个机制实际上就可以正常工作。 您可能还会引入交易超时,如果您愿意,您甚至可以使用Expires标题来表示交易。
在REST术语中,资源是可以通过CRUD(创build/读取/更新/删除)动词来执行的名词。 由于没有“转账”动词,我们需要定义一个可以用CRUD来处理的“交易”资源。 这是HTTP + POX中的一个例子。 第一步是为CREATE (HTTP POST方法)一个新的空的事务:
POST /transaction
这将返回一个事务ID,例如“1234”,并根据URL“/ transaction / 1234”。 请注意,多次触发此POST不会创build具有多个ID的同一事务,并且也避免引入“挂起”状态。 另外,POST不能总是幂等的(一个REST要求),所以在POST中最小化数据通常是很好的做法。
您可以将交易ID的生成保留给客户端。 在这种情况下,您将POST / transaction / 1234创build事务“1234”,如果服务器已经存在,服务器将返回一个错误。 在错误响应中,服务器可以使用适当的URL返回当前未使用的ID。 使用GET方法向服务器查询新ID不是一个好主意,因为GET决不会改变服务器状态,创build/保留新ID将会改变服务器状态。
接下来,我们用所有数据UPDATE (PUT HTTP方法)事务,隐式提交它:
PUT /transaction/1234 <transaction> <from>/account/john</from> <to>/account/bob</to> <amount>100</amount> </transaction>
如果之前已经有一个ID为“1234”的事务,服务器会给出一个错误响应,否则是一个OK响应和一个URL来查看已完成的事务。
注意:在/ account / john中,“john”应该是John的唯一帐号。
很好的问题,REST主要是用类似数据库的例子来解释的,其中存储,更新,检索,删除。 像这样的例子很less,服务器应该以某种方式处理数据。 我不认为罗伊·菲尔丁(Roy Fielding)在他的论文中包含了任何基于http的论文。
但是他确实把“代表性的国家转移”作为一个国家机器谈论,并将链接转移到下一个状态。 通过这种方式,文档(表示)跟踪客户端状态,而不是服务器必须这样做。 这样,就没有客户的状态,只有状态,你在哪个链接。
我一直在想这个,在我看来合理的是,让服务器为你处理一些东西,当你上传时,服务器会自动创build相关的资源,并给你链接(事实上,它wouldn不需要自动创build它们:它可以告诉你链接,只有当你遵循它们时才创build它们 – 懒惰创build)。 并且还为您提供创build新相关资源的链接 – 相关资源具有相同的URI,但时间更长(添加后缀)。 例如:
- 您可以上传( POST ) 所有信息的交易概念的表示forms。 这看起来就像一个RPC调用,但它确实创build了“build议的事务资源”。 例如URI:
/transaction
毛刺会导致创build多个这样的资源,每个都有不同的URI。 - 服务器的响应说明创build的资源的URI,它的表示 – 这包括链接( URI )来创build一个新的“提交事务资源”的相关资源。 其他相关资源是删除拟议交易的链接。 这些是状态机中的状态,客户可以遵循。 从逻辑上讲,这些是服务器上创build的资源的一部分,超出了客户端提供的信息。 例如:URI:
/transaction/1234/proposed
,/transaction/1234/committed
- 您通过POST链接创build“提交事务资源” ,创build该资源,更改服务器的状态(两个帐户的余额)**。 就其性质而言,该资源只能创build一次,不能更新。 因此,承诺多次交易的故障不会发生。
- 你可以获得这两个资源,看看他们的状态是什么。 假设一个POST可以改变其他资源,那么这个提议现在被标记为“提交”(或者根本不可用)。
这与网页的操作方式类似,最终的网页上会显示“你确定要这么做吗?” 最终的网页本身就是交易状态的表示,其中包括转到下一个状态的链接。 不只是金融交易; 也可以(例如)预览,然后提交维基百科。 我猜REST的区别在于,状态序列中的每个阶段都有一个明确的名字(它的URI)。
在实际交易/销售中,交易的不同阶段(build议,采购订单,收据等)往往有不同的实物文件。 更多的是买房子,结算等
OTOH这感觉就像在玩我的语义。 因为它使用名词(URI)而不是动词(RPC调用)“,所以我对将动词转换成名词以使其成为RESTful的名词化感到不自在。 即名词“提交事务资源”而不是动词“提交此事务”。 我认为名义化的一个优点是你可以通过名称来引用资源,而不需要用其他方式来指定它(比如维护会话状态,所以你知道这个事务是什么…)
但重要的问题是:这种方法有什么好处? 即,这种REST风格比RPC风格更好的方式是什么? 是一种对网页有用的技术,除了存储/检索/更新/删除外,还有助于处理信息? 我认为REST的主要好处是可扩展性。 其中一个方面就是不需要显式地维护客户端状态(但是将其隐含在资源的URI中,而下一个状态作为其表示中的链接)。 在这个意义上,它有帮助。 也许这有助于分层/stream水线呢? OTOH只有一个用户会看他们的具体交易,所以没有优势来caching它,所以其他人可以阅读它,http的大胜。
你将不得不推出你自己的“事务ID”types的TXpipe理。 所以这将是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)
你必须处理数据库中的操作(如果是负载平衡的)或者内存等,然后处理commit,rollback,timeout。
在公园里不是真正的RESTful的一天。
如果您退一步来总结讨论的话,很明显REST并不适用于许多API,特别是当客户端 – 服务器交互本身是有状态的时候,就像是非平凡的事务一样。 为什么要跳过所有的build议,为了客户端和服务器端,迂回地遵循一些不适合这个问题的原则? 一个更好的原则是给客户提供最简单,最自然,最高效的方式来组成应用程序。
总之,如果你真的在应用程序中做了很多事务(types,而不是实例),那么你真的不应该创build一个RESTful API。
我已经从这个话题转移了10年。 回想起来,我不能相信这个宗教冒充科学,当你谷歌rest+可靠时,你会涉足。 混乱是神话。
我将这个广泛的问题分为三个:
- 下游服务。 您开发的任何Web服务都将使用您使用的下游服务,而您的交易语法只能遵循。 你应该尝试隐藏你的服务的用户,并确保你的操作的所有部分成功或失败,然后将这个结果返回给你的用户。
- 你的服务。 客户希望获得明确的Web服务调用结果,而通常在实质性资源上直接发布POST,PUT或DELETE请求的REST模式让我觉得这是一种糟糕而容易改进的提供这种确定性的方式。 如果你关心可靠性,你需要确定行动请求。 这个id可以是在客户端上创build的guid,也可以是服务器上关系数据库的种子值,这没关系。 对于服务器生成的ID,请求 – 响应专用于交换ID。 如果这个请求失败或一半成功,没有问题,客户端只是重复请求。 未使用的ID不会造成伤害。
这很重要,因为它让所有后续的请求都是完全幂等的,从这个意义上说,如果它们重复n次,它们会返回相同的结果,并且不会导致进一步的发生。 服务器存储所有对操作ID的响应,如果它看到相同的请求,则重播相同的响应。 在这个谷歌文档更充分的模式处理。 文档提出了一个实现,我相信(!)广泛地遵循REST原则。 专家一定会告诉我这是怎么违反他人的。 无论是否存在下游事务,这种模式都可以用于任何不安全的Web服务调用。
- 将您的服务整合到由上游服务控制的“交易”中。 在networking服务的情况下,完整的ACID交易通常被认为是不值得的,但是通过在您的确认响应中提供取消和/或确认链接,您可以极大地帮助消费者的服务,从而通过补偿来实现交易 。
您的要求是一个基本的要求。 不要让别人告诉你你的解决scheme不是犹太教。 根据他们解决问题的方式,以及简单的方式来判断他们的架构。
我认为在这种情况下打破REST的纯理论是完全可以接受的。 无论如何,我不认为在REST中实际上有什么东西说你不能在需要它的商业案例中触及依赖对象。
我真的认为,如果您只是利用数据库来创build自定义事务pipe理器,那么您就不必花费额外的代价。
您不能在REST中使用服务器端事务。
其中一个REST限制:
无状态
客户端 – 服务器之间的通信进一步受限于在请求之间没有客户端上下文存储在服务器上。 来自任何客户端的每个请求都包含为请求提供服务所需的所有信息,并且任何会话状态都保存在客户端中。
唯一的RESTful方法是创build一个事务重做日志并将其放入客户端状态。 随着请求客户端发送重做日志和服务器重做事务和
- 将事务回滚,但提供新的事务重做日志(更进一步)
- 或者最后完成交易。
但是也许使用支持服务器端事务的基于服务器会话的技术更简单。
我相信这将是使用客户端上生成的唯一标识符,以确保连接打嗝不意味着由API保存的双重性。
我认为使用客户端生成的GUID字段以及传输对象,并确保不重新插入相同的GUID将是一个更简单的银行转账问题的解决scheme。
不知道更复杂的情况,如多个机票预订或微架构。
我find了关于这个主题的论文,将RESTful服务中处理事务primefaces性的经验联系起来 。
在简单的情况下(没有分布式资源),您可以将交易视为资源,创build资源的行为达到最终目标。
因此,要在<url-base>/account/a
和<url-base>/account/b
,您可以将以下内容发布到<url-base>/transfer
。
<转移> <从> <URL基> /帐户/ A </从> <至> <URL基> /帐户/ B </至> <量> 50 </量> </转印>
这将创build一个新的传输资源并返回传输的新url,例如<url-base>/transfer/256
。
在成功发布的那一刻,“真实”交易在服务器上进行,金额从一个账户中移除并且被添加到另一个账户中。
然而,这不包括分布式交易(如果在一个服务之后在一个银行中持有“a”,并且在另一个服务之后在另一个银行中持有“b”) – 除了说“试图将所有运营方式不需要分布式交易“。
首先转移资金并不是在单一资源调用中无法做到的。 你想要做的行动是送钱。 所以你添加一个汇款资源到发件人的帐户。
POST: accounts/alice, new Transfer {target:"BOB", abmount:100, currency:"CHF"}.
完成。 你不需要知道,这是一个交易,必须是primefaces等你只是转账又名。 把钱从A寄到B
但是对于这里罕见的情况来说,一个通用的解
如果你想在一个定义好的上下文中执行一些非常复杂的事情,包括很多限制,实际上跨越了什么和为什么屏障(业务或实现的知识),你需要传输状态。 由于REST应该是无状态的,所以作为客户端需要转移状态。
如果你转移状态,你需要从客户端隐藏信息。 客户不应该只知道实施所需要的内部信息,但是不包含与业务相关的信息。 如果这些信息没有商业价值,那么国家就应该被encryption,象征,通行证或者其他什么东西需要被使用。
通过这种方式,可以传递内部状态,使用encryption和签名系统仍然是安全可靠的。 为客户find正确的抽象,为什么他传递状态信息是由devise和架构决定的。
真正的解决scheme:
记得REST正在谈论HTTP,而HTTP则使用了Cookie的概念。 当人们谈论REST API和跨多个资源或请求的工作stream和交互时,往往会忘记这些Cookie。
请记住维基百科关于HTTP cookies的内容:
Cookie被devise成为网站记住有状态信息(例如购物车中的物品)或者logging用户的浏览活动(包括点击特定button,login或者logging用户到目前为止访问过哪些页面的可靠机制回到几个月或几年前)。
所以基本上如果你需要传递状态,使用cookie。 它的devise原理完全是一样的,它是HTTP,因此它与RESTdevise兼容:)。
更好的解决scheme:
如果您谈论客户端执行涉及多个请求的工作stream程,您通常会谈论协议。 每个协议的forms都有一系列的前提条件,比如执行步骤A,然后才能做B。
这是很自然的,但是向客户公开协议使得一切都变得更加复杂。 为了避免它只是想当我们在现实世界中做复杂的相互作用和事情时所做的事。 我们使用一个代理。
使用代理隐喻,您可以提供一个资源,可以为您执行所有必要的步骤,并将其正在执行的实际作业/指令存储在其列表中(这样我们可以在代理或“代理”上使用POST)。
一个复杂的例子:
买房子:
你需要certificate你的信誉(如提供你的警察logging),你需要确保财务细节,你需要购买实际的房子,使用律师和一个值得信赖的第三方存储资金,确认房子现在属于你和添加购买东西到你的税务logging等(只是作为一个例子,一些步骤可能是错误的或不pipe)。
这些步骤可能需要几天才能完成,有些可以并行完成。
为了做到这一点,你只需给代理人任务买房子:
POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}.
完成。 该机构向您发送一个引用给您,您可以使用它来查看和跟踪这个工作的状态,其余的工作由代理机构的代理自动完成。
想想一个错误跟踪器。 基本上你会报告错误,并可以使用错误ID来检查发生了什么事情。 您甚至可以使用服务来侦听此资源的更改。 任务完成。
我想你可以在URL /资源中包含TAN:
- PUT /交易得到的ID(例如“1”)
- [PUT,GET,POST,无论] / 1 /帐户/鲍勃
- [PUT,GET,POST,无论] / 1 /帐号/帐单
- 用ID 1删除/交易
只是一个想法。