将数据与RESTful API进行比较

时间:2009-11-07 22:33:11

标签: rest

对于我正在研究定义RESTful API的网站。我相信我(大多数)使用适当的资源URI并正确使用GET / POST / UPDATE / DELETE来获得它。

然而有一点我无法弄清楚在“REST”中执行它的正确方法是什么 - 比较列表。

假设我有书店,顾客可以有心愿单。愿望清单由书籍(其完整的书籍记录,即名称,概要等)组成,并且客户端上存在该列表的完整副本。设计RESTful API以允许客户端查询其本地心愿单的正确性(即了解在服务器端的心愿单上添加/删除了哪些书籍)的好方法是什么?

一种选择是从服务器下载完整的愿望清单并在本地进行比较。然而,这是相当大量的数据(由于嵌入式内容),这是一个带宽较低的移动客户端,因此会产生很多问题。

另一个选择是不下载整个心愿单(即不包括图书信息),而只下载书籍标识符列表。这将不是太多数据(与之前的选项相比),客户端可以在本地比较列表。但是,要获得新添加的书籍的完整书籍记录,必须为每本新书进行REST调用。同样,由于这是一个网络连接不良的移动客户端,这可能会有问题。

第三个选项和我最喜欢的选项是,客户端将其标识符列表发送到服务器,服务器将其与心愿单进行比较,并返回删除的书籍和添加的书籍数据。这意味着单次往返并且只有必要数量的数据。由于心愿单大小估计少于100个条目,因此仅发送ID将是最小量的数据(~0.5kb)。但是我不知道什么样的调用是合适的 - 它不能GET,因为我们发送数据(并将它全部放在URL中感觉不对),它不能像我们那样POST / UPDATE不要改变服务器上的任何东西。显然它也不是DELETE。

您如何实施第三个选项?

附带问题:你将如何解决这个问题(即为什么选项3愚蠢或者有什么更好的简单解决方案?)

谢谢。

P.S。:第四种选择是实现更复杂的协议,其中服务器跟踪列表的变化(添加/删除),并且客户端可以例如根据版本标识符或简单的时间戳查询更改。但是,我更喜欢第三种选择,因为在实现方面,它在客户端和服务器上更简单,更不容易出错。

5 个答案:

答案 0 :(得分:3)

HTTP中没有任何内容表明POST必须更新服务器。人们似乎忘记了RFC2616中关于一次使用POST的以下内容:

  
      
  • 提供一个数据块,例如提交的结果       形式,数据处理过程;
  •   

将您的客户端心愿单和POST发布到资源上没有任何问题,该资源的唯一目的是返回一组差异。

POST /Bookstore/WishlistComparisonEngine

答案 1 :(得分:2)

REST背后的整个概念是您利用底层HTTP协议的强大功能。

在这种情况下,有两个HTTP标头可以帮助您查看移动设备上的列表是否过时。另一个好处是移动设备上的客户端本身可能支持这些头文件,这意味着您不必添加任何客户端代码来实现它们!

If-Modified-Since:检查自客户端首次检索服务器副本后是否更新了服务器副本 Etag:检查客户端本地副本的唯一标识符是否与服务器上的标识符匹配。生成服务器上ETag所需的唯一字符串的简单方法是使用MD5散列服务的文本输出。

您可以尝试阅读Mark Nottingham的优秀HTTP caching tutorial,了解这些标头的工作原理。

如果您使用的是Rails 2.2或更高版本,则有built in support for these headers

Django 1.1支持conditional view processing

this MIX video显示了如何使用ASP.Net MVC实现。

答案 2 :(得分:2)

我认为这里的关键问题是书籍和愿望清单的定义,以及愿望清单的权威副本。

我会以这种方式攻击这个问题。首先,您拥有书籍,这些书籍由ISBN编号键入并具有描述书籍的所有元数据(标题,作者,描述,出版日期,页面等)。然后您有愿望列表,它们仅仅是ISBN号列表。您还将拥有客户和其他资源。

您可以将Book资源命名为:

/book/{isbn}

和愿望清单资源:

/customer/{customer}/wishlist

假设每个客户有一个愿望清单。

权威的愿望列表位于服务器上,客户端具有本地缓存​​副本。同样,权威书籍在服务器上,客户端也有缓存副本。

Book表示可以是带有元数据的XML文档。愿望清单表示将是书籍资源名称(以及可能的元数据片段)列表。 Atom和RSS格式似乎非常适合愿望清单表示。

因此,您的客户端 - 服务器同步将如下所示:

GET /customer/{customer}/wishlist
for ( each Book resource name /book/{isbn} in the wishlist )
    GET /book/{isbn}

这完全是RESTful,稍后让客户端执行PUT(更新Wishlist)和删除(删除它)。

这种同步在有线连接上效率非常高,但由于您使用的是移动设备,因此需要更加小心。正如@marshally所指出的,HTTP 1.1具有许多优化功能。请阅读HTTP缓存教程,并确保您的Web服务器正确设置Expires标头,ETag等。然后确保客户端具有HTTP缓存。如果您的应用是基于浏览器的,则可以利用浏览器缓存。如果您正在滚动自己的应用程序,并且找不到要使用的缓存库,则可以编写一个非常基本的HTTP 1.1缓存,将返回的表示存储在数据库或文件系统中。缓存条目将按资源名称编制索引,并保留过期日期,实体标记号等。此缓存可能需要几天或一周或两周才能写入,但它是同步问题的一般解决方案。

您还可以考虑在响应中使用GZIP压缩,因为这会将大小减少60%。所有主流浏览器和服务器都支持它,如果你的编程语言还没有,你可以使用客户端库(例如,Java有GzipInputStream)。

答案 3 :(得分:1)

如果我从您的问题中删除特定于域的详细信息,请参阅以下内容:

在RESTful客户端 - 服务器应用程序中,客户端存储大型资源的本地副本。客户端需要定期检查服务器以确定其资源副本是否是最新的。

marshally's suggestion是使用HTTP缓存,如果可以在应用程序的约束(例如,身份验证系统)中完成,IMO是一种很好的方法。

缺点是,如果资源以任何方式陈旧,您将下载整个列表,这听起来似乎在您的情况下不可行。

相反,如何重新评估是否需要保留愿望清单的本地副本:

  • 您的客户目前如何使用本地愿望清单?
  • 如果必须,您将如何使用从服务器获取的数据替换本地副本?
  • 在构建愿望清单视图和执行业务逻辑时,您做了什么来最小化客户的数据要求?

答案 4 :(得分:0)

你的第三种选择听起来不错,但我同意它对RESTfull感觉不舒服......

这是另一个可能有效或无效的建议:如果您保留列表的版本历史记录,则可以在特定版本之后请求更新。这感觉更像是可以进行GET操作的东西。版本标识符可以是简单的版本号(例如svn),或者如果你想支持分支或其他非线性历史记录,它们可能是某种校验和(例如单调)。

免责声明:无论如何,我都不是REST理念或实施方面的专家。

编辑:我在加载问题后是否刊登了PS广告?或者在写答案之前,我是不是一直都没读过你的问题?抱歉。不过,我仍然认为版本控制可能是一个好主意。