Answers:
考虑一个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已经处于适当的状态,然后将其返回-或者,如果在它已经处于“提交”状态之后尝试将其置于“回滚”状态,则会得到一个错误,并将实际的已提交事务退回。
只要您与单个数据库或具有集成事务监控器的数据库进行对话,该机制实际上就可以正常工作。您可能还会引入事务超时,如果愿意,您甚至可以使用Expires标头来表示。
用REST术语来说,资源是可以与CRUD(创建/读取/更新/删除)动词配合使用的名词。由于没有“转帐”动词,因此我们需要定义一个可以在CRUD上执行的“交易”资源。这是HTTP + POX中的示例。第一步是创建(HTTP POST方法)一个新的空事务:
POST /transaction
这将返回交易ID,例如“ 1234”,并返回URL“ / transaction / 1234”。请注意,多次触发此POST不会创建具有多个ID的同一事务,并且还避免引入“待处理”状态。另外,POST不一定总是幂等的(REST要求),因此通常最好的做法是最小化POST中的数据。
您可以将事务ID的生成留给客户。在这种情况下,您将发布/ transaction / 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主要通过类似于数据库的示例进行解释,其中存储,更新,检索,删除了某些内容。很少有这样的示例,其中服务器应该以某种方式处理数据。我认为罗伊·菲尔丁(Roy Fielding)毕竟没有以http为基础的论文。
但是他确实谈到了“代表性状态转移”作为状态机,并将链接移至下一个状态。这样,文档(表示形式)就可以跟踪客户端状态,而不必由服务器来执行。这样,就没有客户端状态,只有关于您所处链接的状态。
我一直在考虑这个问题,在我看来,让服务器为您处理某些事情是合理的,当您上传时,服务器会自动创建相关资源,并为您提供链接到它们的资源(实际上,无需自动创建它们:它可以告诉您链接,并且仅当您关注它们时才创建它们-惰性创建)。并为您提供创建新的相关资源的链接-相关资源具有相同的URI,但更长(添加后缀)。例如:
/transaction
毛刺将导致创建多个此类资源,每个资源都有不同的URI。/transaction/1234/proposed
, /transaction/1234/committed
这类似于网页的操作方式,最终网页上显示“您确定要这样做吗?”。最终的网页本身就是交易状态的表示,其中包括转到下一个状态的链接。不只是金融交易;也(例如)预览然后提交到维基百科。我猜想REST中的区别是状态序列中的每个阶段都有一个明确的名称(它的URI)。
在现实生活中的交易/销售中,针对交易的不同阶段(提案,采购订单,收据等)通常会有不同的物理文件。甚至更多用于购房,定居等。
OTOH感觉就像在玩语义学。我对将动词转换为名词以使其具有RESTful的标称感到不舒服,“因为它使用名词(URI)而不是动词(RPC调用)”。即名词“ committed transaction resource”而不是动词“ commit this transaction”。我想标称化的一个优点是您可以按名称引用资源,而不需要以其他方式指定资源(例如维护会话状态,因此您知道“此”事务是什么...)
但是重要的问题是:这种方法的好处是什么?即,这种REST风格比RPC风格更好?除存储/检索/更新/删除之外,一种适用于网页的技术是否还有助于处理信息?我认为REST的主要好处是可伸缩性。一方面不需要显式维护客户端状态(而是将其隐式包含在资源的URI中,然后将下一个状态作为其表示形式的链接)。从这个意义上讲,它有所帮助。也许这也有助于分层/流水线工作?OTOH只有一个用户可以查看他们的特定交易,因此缓存它没有优势,因此其他人可以读取它,这对http来说是一个巨大的胜利。
我离开这个话题已经有十年了。回来时,我不敢相信当您Google休息+可靠时,您会迷恋成科学的宗教。混乱是神话。
我将这个广泛的问题分为三个部分:
您的要求是基本要求。不要让别人告诉您您的解决方案不是犹太教。根据解决您的问题的能力和简便程度来判断他们的体系结构。
您必须推出自己的“交易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)
您必须处理动作存储在数据库(如果负载平衡)或内存等中,然后处理提交,回滚,超时。
在公园里,这并不是真正令人愉快的一天。
我认为在这种情况下打破REST的纯理论是完全可以接受的。无论如何,我认为REST中实际上没有任何内容表明您无法在需要它的业务案例中接触依赖对象。
我真的认为,当您只需要利用数据库来执行此操作时,创建一个自定义事务管理器就不会花额外的钱。
首先,转移资金是您在单个资源调用中无法做到的。您要执行的操作是汇款。因此,您可以将汇款资源添加到发送者的帐户中。
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兼容:)。
更好的解决方案:
如果您谈论的是客户端执行涉及多个请求的工作流,那么您通常会谈论协议。每种形式的协议都为每个可能的步骤附带了一组前提条件,例如在可以执行B之前执行步骤A。
这很自然,但是向客户端公开协议会使一切变得更加复杂。为了避免这种情况,只要想一想当我们必须在现实世界中进行复杂的交互和事物时该做什么。我们使用代理。
使用代理程序隐喻,您可以提供一种资源,该资源可以为您执行所有必需的步骤,并将正在执行的实际任务/指令存储在其列表中(这样我们就可以在代理程序或“代理机构”上使用POST)。
一个复杂的例子:
买房子:
您需要证明您的信誉(例如提供您的警察记录条目),需要确保财务细节,您需要使用律师和存储资金的受信任第三方购买实际房屋,并验证房屋现在属于您,并且将购买的商品添加到您的税收记录等中(例如,某些步骤可能是错误的或其他原因)。
这些步骤可能需要几天的时间才能完成,某些步骤可以并行执行等。
为此,您只需给代理商任务购买房屋即可,例如:
POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}.
做完了 代理商会将您的参考发回给您,您可以使用该参考来查看和跟踪此工作的状态,其余的则由代理商的代理商自动完成。
例如,考虑一个错误跟踪器。基本上,您报告错误,并可以使用错误ID来检查发生了什么。您甚至可以使用服务来监听此资源的更改。任务完成。
您不得在REST中使用服务器端事务。
REST约束之一:
无状态
客户端与服务器之间的通信受到请求之间没有存储在服务器上的客户端上下文的进一步限制。来自任何客户端的每个请求都包含服务该请求所需的所有信息,并且任何会话状态都保留在客户端中。
唯一的RESTful方法是创建事务重做日志并将其置于客户端状态。客户端根据请求发送重做日志,服务器重做事务,
但是,使用支持服务器端事务的基于服务器会话的技术也许更简单。
我相信使用客户端上生成的唯一标识符来确保连接中断不会暗示API保存了重复性的情况。
我认为将客户端生成的GUID字段与转账对象一起使用,并确保不再再次插入相同的GUID,这将是解决银行转账事宜的更简单解决方案。
不知道更复杂的情况,例如多张机票预订或微型架构。
我找到了一篇有关该主题的论文,涉及在RESTful服务中处理事务原子性的经验。
在简单的情况下(没有分布式资源),您可以将事务视为资源,创建事务的行为可以达到最终目标。
因此,要在<url-base>/account/a
和之间转移<url-base>/account/b
,您可以将以下内容发布到<url-base>/transfer
。
<转移> <from> <url-base> / account / a </ from> <to> <url-base> / account / b </ to> <amount> 50 </ amount> </ transfer>
这将创建一个新的传输资源并返回该传输的新url-例如<url-base>/transfer/256
。
然后,在成功过账的那一刻,在服务器上执行“真实”交易,并将金额从一个帐户中删除并添加到另一个帐户中。
但是,这不包括分布式交易(如果说“ a”存放在一项服务后面的一家银行,而“ b”存放在另一项服务后面的另一家银行)–除了说“尝试用全部措词以不需要分布式交易的方式进行操作”。
我想您可以在网址/资源中包含TAN:
只是个主意。