代码之家  ›  专栏  ›  技术社区  ›  Gili

剩余交易?

  •  134
  • Gili  · 技术社区  · 16 年前

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

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

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

    13 回复  |  直到 8 年前
        1
  •  82
  •   Darrel Miller    16 年前

    考虑一个安静的购物篮场景。购物篮概念上是您的事务包装器。同样,您可以将多个项目添加到购物篮,然后提交购物篮以处理订单,您可以将Bob的帐户条目添加到事务包装器,然后将Bill的帐户条目添加到包装器。当所有的片段都就位后,您就可以将事务包装器与所有的组件片段一起发布/放置。

        2
  •  52
  •   Jon Watte    13 年前

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

    具体来说,一个不错的特性是:如果你发布两次(因为一些缓存在中间中断了),你不应该传输两次。

    要实现这一点,您需要创建一个事务作为对象。这可能包含您已经知道的所有数据,并将事务置于挂起状态。

    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头来表示超时。

        3
  •  29
  •   Tuckster    13 年前

    在rest术语中,resources是可以用crud(create/read/update/delete)动词操作的名词。由于没有“transfer money”动词,我们需要定义一个可以用crud操作的“transaction”资源。下面是http+pox中的一个例子。第一步是 创造 (HTTP Post方法)新的 空的 交易:

    POST /transaction
    

    这将返回事务ID,例如“1234”,并根据URL“/transaction/1234”返回。请注意,多次触发此日志不会创建具有多个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”的事务,服务器将给出错误响应,否则将给出OK响应和查看已完成事务的URL。

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

        4
  •  18
  •   13ren    12 年前

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

    但他确实把“代表性状态转移”说成是一个状态机,并将链接转移到下一个状态。这样,文档(表示)就可以跟踪客户机状态,而不是服务器必须这样做。这样,就没有客户机状态,只有根据您所处的链接来声明。

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

    1. 你上载( )A概念的表示 处理所有信息。 这看起来就像一个RPC调用,但它实际上是在创建“建议的事务资源”。URI: /transaction 故障将导致创建多个这样的资源,每个资源具有不同的URI。
    2. 服务器的响应声明创建的资源的URI及其表示形式-这包括链接( 尿嘧啶尿路感染 )创建的相关资源 新的“提交的事务资源”。 其他相关资源是删除建议事务的链接。这些是状态机中的状态,客户机可以遵循这些状态。从逻辑上讲,这些是在服务器上创建的资源的一部分,超出了客户机提供的信息。尤里斯: /transaction/1234/proposed , /transaction/1234/committed
    3. 链接到 创建“提交的事务资源” ,这将创建该资源,并更改服务器的状态(两个帐户的余额)**。根据其性质,此资源只能创建一次,不能更新。因此,提交许多事务时不会出现问题。
    4. 您可以获取这两个资源,以了解它们的状态。假设一个帖子可以改变其他资源,那么这个建议现在将被标记为“已提交”(或者可能根本不可用)。

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

    在实际交易/销售中,交易的不同阶段(提案、采购订单、收据等)通常有不同的实际凭证。更重要的是买房子,有结算等。

    奥托,这对我来说就像在玩语义游戏;我不喜欢把动词转换成名词以使其不受干扰,“因为它使用名词(uri)而不是动词(rpc调用)”。例如,名词“committed transaction resource”而不是动词“committ this transaction”。我想名义化的一个优点是,您可以按名称引用资源,而不需要以其他方式指定它(例如维护会话状态,这样您就知道什么是“this”事务…)

    但重要的问题是:这种方法的好处是什么?也就是说,这个REST样式在什么方面比RPC样式更好?除了存储/检索/更新/删除之外,对于网页来说非常有用的技术还有助于处理信息吗?我认为REST的主要优点是可伸缩性;其中一个方面不需要显式地维护客户机状态(但使其隐式地存在于资源的URI中,而下一个状态则作为其表示中的链接)。从这个意义上说,这是有帮助的。也许这也有助于分层/管道化?Otoh只有一个用户会查看他们的特定事务,因此在缓存它以便其他人可以读取它方面没有优势,这是HTTP的一大胜利。

        5
  •  11
  •   Peris    10 年前

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

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

        6
  •  9
  •   TheSoftwareJedi jac    16 年前

    您必须滚动自己的“事务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)
    

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

    在公园里可不是一个安静的日子。

        7
  •  8
  •   bbsimonbb    8 年前

    我已经偏离这个话题10年了。回来后,我不敢相信当你谷歌休息+可靠的时候,宗教伪装成你涉入的科学。混乱是神话。

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

    • 下游服务。您开发的任何Web服务都将具有您所使用的下游服务,并且您只能遵循这些服务的事务语法。您应该尝试对服务的用户隐藏所有这些信息,并确保操作的所有部分作为一个组成功或失败,然后将此结果返回给用户。
    • 您的服务。客户希望Web服务调用有明确的结果,而通常在实质性资源上直接发布、放置或删除请求的休息模式让我觉得这是一种提供这种确定性的糟糕且容易改进的方式。如果你关心可靠性,你需要确定行动请求。这个ID可以是在客户机上创建的GUID,也可以是服务器上关系数据库的种子值,这无关紧要。对于服务器生成的ID,请求响应专门用于交换ID。如果此请求失败或一半成功,没有问题,客户端只会重复请求。未使用的ID不会造成伤害。

      这一点很重要,因为它允许所有后续的请求都是完全等幂的,从某种意义上说,如果它们被重复n次,它们将返回相同的结果,并且不会导致进一步的事情发生。服务器根据操作ID存储所有响应,如果它看到相同的请求,则会重播相同的响应。对图案的更全面的处理 this google doc .文档建议实现,我相信!!)广泛遵循其他原则。专家们肯定会告诉我它是如何侵犯他人的。无论是否涉及下游事务,此模式都可用于对Web服务的任何不安全调用。
    • 将您的服务集成到由上游服务控制的“事务”中。在Web服务的上下文中,完整的ACID事务通常被认为不值得这样做,但是您可以通过在确认响应中提供取消和/或确认链接来极大地帮助您的服务的消费者,从而实现 transactions by compensation .

    你的要求是最基本的。不要让别人告诉你,你的解决办法是不洁。根据他们解决问题的能力和简单程度来判断他们的体系结构。

        8
  •  3
  •   Martin Kersten    9 年前

    首先,在一次资源调用中,转移资金是不可能的。你想做的就是寄钱。因此,您可以向发送者的帐户添加一个转账资源。

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

    完成。你不需要知道这是一个必须是原子的交易,你只需要转移钱。把钱从A寄到B。


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

    如果您想做一些非常复杂的事情,涉及到定义环境中的许多资源,并且有很多限制,这些限制实际上跨越了什么与为什么障碍(业务与实现知识),那么您需要转移状态。因为REST应该是无状态的,所以作为一个客户机,您需要转移状态。

    如果您传输状态,则需要从客户机隐藏内部信息。客户不应只知道实施所需的内部信息,而应不携带与业务相关的信息。如果这些信息没有业务价值,那么应该对状态进行加密,并且需要使用一个类似于标记、通行证之类的隐喻。

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


    真正的解决方案:

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

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

    cookie是一种可靠的机制,用于网站记忆状态信息(如购物车中的项目)或记录用户的浏览活动(包括单击特定按钮、登录或记录用户早在几个月或几年前访问过哪些页面)。

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


    更好的解决方案:

    如果您谈论的是执行涉及多个请求的工作流的客户机,那么您通常会谈论协议。每种形式的协议都为每一个潜在步骤提供了一组前提条件,比如在执行b之前先执行步骤a。

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

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

    一个复杂的例子:

    买房:

    你需要证明你的信誉(比如提供你的警察记录条目),你需要确保财务细节,你需要使用律师和可信的第三方来购买实际的房子,存储资金,核实房子现在属于你,并将购买的东西添加到你的税务记录中等(举个例子,有些步骤可能是错误的或无论如何)。

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

    为了做到这一点,您只需向代理提供购买房屋的任务,如下所示:

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

    完成。代理将向您发送一个引用,您可以使用该引用查看和跟踪此作业的状态,其余的由代理自动完成。

    例如,考虑一个bug追踪器。基本上,你可以报告这个bug并使用bug id来检查发生了什么。甚至可以使用服务来侦听此资源的更改。任务完成了。

        9
  •  2
  •   Toby Hede    16 年前

    我认为在这种情况下,打破纯休息理论是完全可以接受的。在任何情况下,我认为在需要它的业务案例中,没有任何东西实际上是在休息中说您不能触摸依赖对象的。

    我真的认为不值得你为了创建一个定制的事务管理器而去做额外的工作,因为你可以利用数据库来完成这项工作。

        10
  •  1
  •   bebbo    11 年前

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

    其他禁忌之一:

    无国籍的

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

    唯一宁静的方法是创建一个事务重做日志并将其置于客户机状态。通过这些请求,客户机发送重做日志,服务器重做事务,并且

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

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

        11
  •  1
  •   Eduardo Rolim    9 年前

    我相信这就是使用客户机上生成的唯一标识符来确保连接中断不意味着API保存的重复性的情况。

    我认为使用客户机生成的guid字段和传输对象,并确保不再重新插入相同的guid,对于银行传输来说是一个更简单的解决方案。

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

    我找到了一篇关于这个主题的论文,介绍了 dealing with the transaction atomicity in RESTful services .

        12
  •  0
  •   Phasmal    14 年前

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

    所以,在 <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”在另一个服务后面的另一个银行持有),而不是说“尝试以不需要分布式事务的方式表述所有操作”。

        13
  •  -3
  •   Till    16 年前

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

    1. 输入/交易以获取ID(例如“1”)。
    2. [输入,获取,发布,无论什么]/1/帐户/Bob
    3. [输入,获取,邮寄,无论什么]/1/账户/账单
    4. 删除/ID为1的事务

    只是一个想法。