【问题标题】:RESTful atomic update of multiple resources?多个资源的 RESTful 原子更新?
【发布时间】:2012-02-21 18:47:34
【问题描述】:

想象一个 Web 应用程序存储一些数据资源,该数据资源具有一些​​ id,每个数据存储三个附件(例如 pdf)。

网址方案是

data/{id}/attachment1
data/{id}/attachment2
data/{id}/attachment3

附件存在一个 RESTful API,提供 GET/PUT/DELETE 操作,在服务器端实现 CRUD 操作。

设 id 为 123,我想在哪里执行一个操作

  • attachment1 被新附件替换(这样GET file/123/attachment1 返回新附件)
  • attachment2 被删除(这样GET file/123/attachment2 返回 404)
  • attachment3 保持不变。

更新应该是原子的 - 完整的更新由服务器执行或根本不执行。

应用简单的PUT file/123/attachment1DELETE file/123/attachment2 不是原子的,因为客户端可能在 PUT 之后崩溃,而服务器没有提示他应该在这种情况下进行回滚。

那么如何以 RESTful 方式实现操作呢?

我想到了两种解决方案,但它们似乎都不是 100% RESTful:

  • 使用 PATCH(可以是 PUT,但 PATCH 更好地反映了 部分更新)在 data/123 上使用 multipart/form-data: multipart/form-data 是由一个新的实体组成的序列 与字段“附件 1”关联的“应用程序/pdf”和 代表空值的东西,表示删除 附件2。

虽然这确保了原子性,但我怀疑这是 RESTful,因为我使用不同的参数列表重载 PATCH 方法,这违反了统一接口约束。

  • 使用代表事务的资源。我可以发布数据 id 123 到将创建事务资源的事务 URL 表示存储的数据资源当前状态的副本 在服务器上,例如交易/数据/123。现在我可以调用 PUT 和 删除这个临时资源的附件(例如DELETE transaction/data/123/attachment2)并沟通 通过 PUT on 将此版本的资源提交到服务器 交易/数据/123。这确保了原子性,而必须 实现额外的服务器端逻辑来处理多个客户端 更改从未提交的相同资源和崩溃的客户端。

虽然这似乎与 REST 一致,但它似乎违反了无状态的约束。事务资源的状态不是服务状态而是应用程序状态,因为每个事务资源都与单个客户端相关联。

我有点卡在这里,所以任何想法都会有所帮助,谢谢!

【问题讨论】:

  • 第二种方法的好处是可以提供良好的数据更改历史记录,并且可以让您跳过一些日志记录。
  • @mtsz 我现在正在努力解决这个问题。我喜欢您在下面选择的答案,但是创建一个具有短暂、临时生命周期的事务资源似乎需要做很多工作。您认为将要执行的原子事务命名为“switcheroo”之类的名称并创建一个执行该事务的特定 Web 服务会很糟糕吗?例如,POST /doSwitcheroo 的主体为 {fileId: 123} ...。此服务将具有对 id 为 123 的文件自动执行上述操作的逻辑

标签: rest transactions crud atomic multipartform-data


【解决方案1】:

我没有经验,但我有一个解决方案的想法,因为我在开发中正面临这个问题。

首先,我使用我(客户端)在房子(有资源的服务器)中向 Fred1 发送消息的类比,我希望他关闭电灯开关(更改部分资源的状态)并打开水壶(更改资源另一部分的状态)。不幸的是,在关掉电灯开关后,弗雷德心脏病发作了。

现在我没有从 Fred 那里得到任何回复来说明他是否按照我的要求做了。弗雷德被另一个弗雷德取代。我发送的消息没有收到回复。我可以继续的唯一方法是询问 Fred2 电灯开关是否关闭并且水壶是否打开(资源处于我要求他为我做事后我期望的状态)。这是一种不幸的事态(错误)并增加了我的工作量,但我现在可以在我知道 Fred1 心脏病发作前做了什么的基础上继续进行。我可以返回到绘图板(通知用户出了问题,我们需要重新做)或进行更改以完成我的请求(如果仍然相关)(打开水壶)。

这是我如何做的开始,显然有关于范围的问题,但如果我已经定义了我的范围(我只对电灯开关和水壶感兴趣)那么我应该有足够的信息(知道电灯开关和水壶的状态)向 Fred2 发出新命令,而无需返回用户指令。

听起来怎么样?

【讨论】:

  • 有趣,但有问题:当 Fred 在关灯后心脏病发作时,您的状态不一致。只要您使用客户端检查状态并将另一条消息发送给下一个 Fred(希望他不会死),它就会不一致。如果你的客户在弗雷德死后就死了,会发生什么?然后,预期的状态将永远丢失。其他客户端将面临当前不一致的状态,这可能会导致严重的麻烦......
  • 确实很有趣 :) 数据库呢?它们是如何做到的?数据库必须在内部是原子的,所以也许这是另一个值得关注的地方。我正在运行 PostgreSQL,所以会检查文档。这是 PostgreSQL atomic 的链接:postgresql.org/files/developer/transactions.pdf
  • 您可以采用数据库会话的概念(例如 sql 会话):在您提交会话之前,不会执行您在会话中应用的所有修改。当其中一项修改发生不良情况时,会话将回滚并保留先前的一致状态。因此,当 Fred 在打开电灯开关后死亡时,他会抛出一个被捕获的 HeartFailureException,触发会话回滚并且灯再次亮起;)
【解决方案2】:

您想使用第二个选项,即交易选项。

您缺少的是交易的创建:

POST /transaction

HTTP/1.1 301 Moved Permanently
Location: /transaction/1234

现在您有了一个作为一等公民的事务资源。您可以添加、删除、查询以查看其当前内容,然后最终提交或删除(即回滚)事务。

当交易正在进行时,它只是另一种资源。这里没有客户端状态。任何人都可以添加到此交易中。

全部完成后,服务器会使用此处超出范围的某种内部事务机制一次性应用所有更改。

您可以在事务子操作中捕获诸如 Etag 和 if-modified 标头之类的内容,这样当它们全部应用时,您就知道某些东西并没有在您背后发生变化。

【讨论】:

  • 听起来很合理。在这种情况下,“应用程序状态”的概念可能有点过分夸大了,这个“一等公民”是正常的服务状态。 Richardson & Ruby 提倡这种方案,虽然是应用于多服务器的场景。我想知道,罗伊菲尔丁会做什么? :)
  • 您可以像查看购物车一样查看交易。在购物车中,客户随着时间的推移建立他们的交易,然后他们简单地完成结账过程,最终以他们“确认”或“取消”订单结束。当您以这些术语和词汇来看待它时,它会更有意义。但是在 10,000 英尺处,它们基本上是相同的问题。 “这是我想做的所有事情的清单——现在,开始吧!”购物车绑定到特定用户这一事实是安全/身份问题,而不是“状态”与“无状态”问题。
  • 我已经实现了这个效果很好的解决方案。您可以在不丢失数据的情况下免费解决“编辑竞赛”的好处,这是“列表”解决方案无法做到的。
  • "当一切都完成后,服务器..."
  • @Niko Bellic 您可以简单地将交易链接发布到 /commit 资源。您可以将事务状态 (COMMITED, UNCOMMITED) 作为资源的属性,完成后,将其更改为 COMMITED 并 PUT 资源。这也可以正常工作。查看事务资源的另一种方式类似于购物车。购物车只是一种特殊的交易类型。
【解决方案3】:

假设您的 URI 是分层的:

PUT data/{id}
[attachment2,attachment3]

您的部分问题是 attachment1/2/3 是一个糟糕的标识符。索引不应该是您的 URI 的一部分。

【讨论】:

  • 标识符是任意的,以使场景更通用。想象一个具体的场景,你有带有预期模型、描述、图片等的数据块。我看不出您的解决方案如何解决多个附加资源的一致更新问题,因为 PUT 是顺序的?
  • 将您的附件列表视为一种资源。您正在输入内容和顺序。 1 次操作。
  • 所以,如果我理解正确的话,您选择使用媒体类型建模序列,如 multipart/form-data 或自定义类型。由于我最初的问题包含此解决方案,因此阅读此解决方案的合理性会很有趣。
【解决方案4】:

非常有趣的问题。卢加诺大学(瑞士)的一位 C.S. 教授写了一些关于这种情况的幻灯片:

http://www.slideshare.net/cesare.pautasso/atomic-transactions-for-the-rest-of-us

但是我不太确定他提供的解决方案是否完全是 RESTful,因为它在服务器端看起来并不是真正的无状态。

老实说,由于事务本身是由多个状态组成的,我认为这个问题不可能有一个完全 RESTful 的解决方案。

【讨论】:

  • 我浏览了他们的论文(Towards Distributed Atomic Transactions over RESTful Services)。他们提出了一个带有协调器资源的 Try-Cancel/Confirm 协议,该协调器资源最终确认所有参与资源的更改(或启动先前状态的恢复)。资源是分布式服务器,所以范围比我的稍大。尽管如此,这个 TCC 协议应该与上面提到的第二种解决方案非常相似。至少他们在概念上解决了应用程序状态问题,方法是通过附加手段来恢复以前的状态,将其转变为服务状态。也许就是这样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多