为什么要使用两步方法来通过REST删除多个项目

30
我们都知道,通过REST删除单个项的“标准”方法是向URI example.com/Items/666 发送一个DELETE请求。接下来让我们来看看如何批量删除。由于我们不需要原子删除(或真正的事务,即全部或无),我们可以告诉客户端“很遗憾,请发送多个请求”,但那样并不好。因此,我们需要一种允许客户端请求同时删除多个'Items'的方法。
据我了解,这个问题的“典型”解决方案是采用“两步”方法。首先,客户端POST列表项ID,并返回一个URI(例如example.com/Items/Collection/1)。创建该集合后,他们调用DELETE操作。
现在,我认为这个方法也可以正常工作,但对我来说,它是一个糟糕的解决方案。首先,你强迫客户端进行两次请求以适应服务器。其次,“我认为DELETE应该删除一个Item?”,调用此URI上的DELETE应该有效地取消事务(尽管不是真正的事务),那么我们该如何取消它呢?如果有某种形式的“执行”操作,那最好了,但我不能太过分。它还强迫服务器考虑“JSON看起来更像是修改这些项的请求,但请求是DELETE...所以我想我会将它们删除”。这种方法也开始在客户端/服务器上强加某种状态,虽然不是真正的状态,但确实如此。
我认为,更好的解决方案是简单地调用example.com/Items(或者可能是example.com/Items/Collection)上的DELETE,并传递包含您希望删除的ID列表的JSON数据。据我所见,这基本上解决了第一种方法所遇到的所有问题。它更容易作为客户端使用,减少了服务器需要做的工作量,是真正无状态的,更语义化。

我希望您能反馈一下这个问题,我是否忽略了REST的某些东西,从而使我解决这个问题的方法变得不现实?如果有比较这两种方法的文章,请提供链接,我会非常感激。尽管这在SO上通常是不被批准的,但我需要能够驳斥只有第一种方法才是真正的RESTful,并证明第二种方法是可行的解决方案。当然,如果我走错了路,请告诉我。

3个回答

25

我最近花了一周左右的时间阅读了大量关于REST的内容,据我所知,把这两个解决方案描述为“RESTful”是错误的,相反,你应该说“这两个解决方案都符合REST的意思”。

简而言之,就是REST并没有涵盖如何以REST方式删除资源(单个或多个)的主题,正如Roy Fielding的论文(见第5章)所述。没错,没有“正确的RESTful删除资源的方法”……不过也不完全是这样。

REST本身并不定义如何删除资源,但它确实定义了使用的协议(记住REST不是协议)将决定如何执行这些操作。协议通常是HTTP;“通常”是关键词,因为Fielding 会指出, REST并不等同于HTTP。

所以我们需要查看HTTP来确定哪种方法是“正确的”。不幸的是,就HTTP而言,这两种方法都是可行的。是的,“可行”。HTTP允许客户端发送带有效载荷的POST请求(以创建集合资源),然后调用DELETE方法来删除这个新集合中的资源;它还允许您在单个DELETE方法的有效载荷中发送数据以删除资源列表。HTTP只是将请求发送到服务器的媒介,由服务器适当地做出响应。对我来说,HTTP协议在某些方面似乎相当开放于解释,但它确实为操作意味着什么,如何处理它们以及应该给出什么响应提供了相当清晰的指导线;只是它是一个“你应该这样做”而不是“你必须这样做”,但也许我在措辞上有点过于追求精细。
有些人会认为,“两阶段”方法不可能是“REST”,因为服务器必须为客户端存储一个“状态”才能执行第二个操作。这只是某些部分的误解。必须理解的是,在列表被POST和随后被DELETE之间,客户端和服务器都没有存储关于对方的任何“状态”信息。是的,必须先创建列表才能删除它,但服务器不记得是客户端alpha创建了这个列表(这种方法允许客户端简单地调用“DELETE”作为下一个请求,并且服务器记得使用该列表,这根本不是无状态的),因此客户端必须告诉服务器删除特定的列表,即它获得特定URI的列表。如果客户端尝试删除不存在的集合列表,它将被告知“找不到资源”(最可能是经典的404错误)。如果您希望声称这种两步方法确实保持状态,那么您也必须声称仅仅获取URI需要状态,因为URI必须首先存在。声称存在这种持久状态是误解“状态”的含义。而且,进一步证明这种两阶段方法确实是无状态的,“证据”是您可以让客户端alpha POST列表,稍后客户端beta(没有与其他客户端通信)调用列表资源上的DELETE。
我认为很明显第二种选择,即在DELETE请求的负载中发送列表,是无状态的。完成请求所需的所有信息完全存储在一个请求中。
然而,可以争论DELETE操作只应调用“有形”资源,但这样做会公然忽视REST的REpresentational部分; 这就是名字!它是表现方面“允许”URI,例如http://example.com/myService/timeNow,当“获取”时,它将动态返回当前时间,而无需加载某个文件或从某个数据库读取。这是一个关键概念,即URI不直接映射到某些“有形”的数据。
然而,那种无状态性质的一个方面必须受到质疑。正如Fielding在5.1.3节中描述的“客户端无状态服务器”,他说:
We next add a constraint to the client-server interaction: communication must
  be stateless in nature, as in the client-stateless-server (CSS) style of
  Section 3.4.3 (Figure 5-3), such that each request from client to server must
  contain all of the information necessary to understand the request, and
  cannot take advantage of any stored context on the server. Session state is
  therefore kept entirely on the client.

我的理解中,这里的关键部分是“无法利用服务器上任何存储的上下文”。现在我会同意你,'context'有点开放性的解释。但是我觉得很难想象如何考虑存储一个列表(无论是在内存中还是在磁盘上),以便用于给出实际有用的含义不违反这个“规则”。没有这个“列表上下文”,删除操作就没有意义。因此,我只能得出结论,使用两步方法执行诸如删除多个资源之类的操作不能且不应该被认为是“RESTful”的。
我也有点不满意必须花费这么多精力来找出支持或反对这种方式的论据。整个互联网似乎都迷上了这个想法,即采用两步方法是执行此类操作的“RESTful”方式,并且认为“这是RESTful的做法”。如果你暂时离开其他人所做的事情,你会发现任何一种方法都需要发送相同的列表,因此可以忽略该参数。两种方法都是“表现层状态转移”的方式。唯一的真正区别在于某种原因,一种方法决定要求两个请求。这两个请求随后会带来一些跟进问题,例如“您要保留该数据多长时间”和“客户端如何告诉服务器它不再需要该集合,但希望保留它所引用的实际资源”。因此,在某种程度上,我正在用同样的问题回答自己的问题,“为什么要考虑采用两步方法?”

2
我认为REST的重点不在于服务器不能有任何状态(例如,需要状态将列表中的ID映射到服务器上的某个内容),而是它没有任何会话级别的状态。因此,这两种方法都可能合格,尽管我同意一步法仍然可能更可取。 - Jerry Coffin
你是如何得出单步方法包含每个会话状态的结论的?我认为我已经很清楚地表明,这两种方法都不需要像购物车之类的每个会话状态。 - thecoshman
我并没有得出任何这样的结论——我只是得出结论,两步方法中涉及的“状态”不会(必然)违反REST的理念。 - Jerry Coffin
哦,我现在明白你的意思了。嗯,我仍然不认为采用两步方法利用“状态”。但正如我所说,“存储上下文”有些含糊不清。你可以扭曲那个预期的意思,说“获取数据利用存储的上下文,因此不是REST”。我认为我的关键点是,这两种方法都是“有效”的“RESTful”方法,你必须考虑到它们才能得出使用哪种方法的结论。 - thecoshman

3

我的看法:

在删除现有集合中的所有成员时,使用HTTP DELETE方法是可行的。但只为了删除所有成员而创建集合似乎很奇怪。正如你自己提到的,只需使用JSON(或任何其他有效载荷格式)传递要删除的项目的ID即可。我认为服务器应该将多个删除操作作为一个内部事务来处理。


据您所知,是否存在任何技术原因,使您无法像这样在DELETE请求的正文中发送有效载荷?根据我所阅读的HTTP规范,除非另有说明,否则所有请求方法都可以具有消息正文,而DELETE请求并未另有说明。尽管如此,为了预防挑剔者,是的,它是一个“实体”。 - thecoshman
我不知道在DELETE中使用有效载荷存在任何问题。我们已经在DELETE和GET(别问)方法中都使用它了。 - wilx
我将要处理一个接口,你希望一次性发送大量的操作;创建、删除、更新数百个。这是由于内部机制导致的,意味着我们可以比逐个接收更快地处理。好奇地问,你会如何实现这个?你知道任何讨论这两种方法的资源吗?对我来说,这似乎相当简单,信号请求更容易使用,并且可以节省服务器担心保留这些列表的时间等事情。 - thecoshman
1
一些HTTP服务器不支持在DELETE请求中发送消息体,而一些客户端(尤其是Apache)也不支持在DELETE请求中发送消息体。 - goto10

1
我认为HTTP已经提供了一种删除多个项目的方法,即通过持续连接和流水线。在HTTP协议级别上,以流水线方式请求幂等方法(如DELETE)是完全可以的-也就是说,在单个连接上一次性发送所有DELETE请求并等待所有响应是可行的。
对于在浏览器中运行的AJAX客户端来说,这可能会有问题,因为很少有浏览器默认启用流水线支持。不过,这不是HTTP的错,而是特定客户端的错。

1
这实际上是通过“持久”管道完成的多个单个删除请求。因此,这只是一个解决方法(而且不错),用于处理HTTP基本上每个请求一个资源的事实。 - thecoshman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接