[英]RESTful api design, HATEOAS and resource discovery
HATEOAS背后的核心思想之一是客户端应该能够从单一入口点URL开始,并发现所有可用的公开资源和状态转换。 虽然我可以很好地看到它如何与HTML和人类在浏览器后面点击链接和“提交”按钮,我被问及这个原则如何应用于我(非)幸运地处理的问题。
我喜欢RESTful设计原则如何在论文和教育文章中呈现,这一切都有意义, 如何获得一杯咖啡就是一个很好的例子。 我将尝试遵循惯例,并提出一个简单且没有繁琐细节的例子。 我们来看看邮政编码和城市。
假设我想设计RESTful api,用于通过邮政编码查找城市。 我想出了嵌套在邮政编码中的称为“城市”的资源,因此在http://api.addressbook.com/zip_codes/02125/cities
上的GET会返回包含代表多切斯特和波士顿的两条记录的文档。
我的问题是:如何通过HATEOAS发现这样的网址? 在http://api.addressbook.com/zip_codes
下公开所有~40K邮政编码的索引可能是不切实际的。 即使拥有40K项目索引并不是一个问题,请记住我已经制作了这个例子,并且有更大规模的集合。
所以基本上,我想要暴露不是链接,而是链接模板,而不是像这样: http://api.addressbook.com/zip_codes/{:zip_code}/cities
: http://api.addressbook.com/zip_codes/{:zip_code}/cities
: http://api.addressbook.com/zip_codes/{:zip_code}/cities
,这违背了原则,依赖于外面的客户拥有的带知识。
假设我想通过某些过滤功能公开城市索引:
在http://api.addressbook.com/cities?name=X
只会返回名称与X
匹配的城市。
在http://api.addressbook.com/cities?min_population=Y
获取只返回人口等于或大于Y
。
当然这两个过滤器可以一起使用: http://api.addressbook.com/cities?name=X&min_population=Y
: http://api.addressbook.com/cities?name=X&min_population=Y
在这里,我不仅要公开url,还要公开这两个可能的查询选项以及它们可以组合在一起的事实。 如果没有客户端对这些过滤器的语义的带外知识以及将它们组合到动态URL中的原则,这似乎是根本不可能的。
那么HATEOAS背后的原则如何帮助制作这样简单的API真的是RESTful?
我建议使用XHTML表单:
GET /
HTTP/1.1 OK
<form method="get" action="/zip_code_search" rel="http://api.addressbook.com/rels/zip_code_search">
<p>Zip code search</p>
<input name="zip_code"/>
</form>
GET /zip_code_search?zip_code=02125
HTTP/1.1 303 See Other
Location: /zip_code/02125
HTML中缺少的是form
的rel
属性。
看看这篇文章 :
总而言之,将XHTML视为RESTful服务的默认表示有几个原因。 首先,您可以利用
<a>
,<form>
和<input>
等重要元素的语法和语义,而不是发明自己的语法和语义。 其次,您最终会得到与网站非常相似的服务,因为它们可以被用户和应用程序浏览。 XHTML仍然由人类解释 - 它只是开发期间的程序员而不是运行时的用户。 这简化了整个开发过程中的工作,使消费者更容易了解您的服务如何运作。 最后,您可以利用标准Web开发框架来构建RESTful服务。
还可以查看OpenSearch 。
HTTP/1.1 200 OK Content-Location: /zip_code/02125 <html> <head> <link href="/zip_code/02125/cities" rel="related http://api.addressbook.com/rels/zip_code/cities"/> </head> ... </html>
在回答问题1时,我假设您的单个入口点是http://api.addressbook.com/zip_codes
,目的是使客户端遍历整个邮政编码集合并最终检索相关的城市给他们。
在这种情况下,我会使http://api.addressbook.com/zip_codes
资源返回重定向到邮政编码的第一页,例如:
http://api.addressbook.com/zip_codes?start=0&end=xxxx
这将包含一个“页面”值的邮政编码链接(适用于系统处理的任何数字,以及指向下一页的链接(如果有的话,还有前一页)。
这将使客户端能够抓取整个邮政编码列表(如果需要)。
每页返回的网址看起来与此类似:
http://api.addressbook.com/zip_codes/02125
然后,决定是否将城市信息包含在邮政编码URL返回的表示中,或者根据需要将链接包含在链接中。
现在,客户可以选择是否遍历整个邮政编码列表,然后为每个邮政编码请求邮政编码(然后是城市),或者请求一页邮政编码,然后请求深入查询
我想到了这个解决方案,但我不确定我是否真的推荐它:不是返回资源URL,而是返回描述端点的WADL URL。 例:
<application xmlns="http://wadl.dev.java.net/2009/02" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<grammars/>
<resources base="http://localhost:8080/cities">
<resource path="/">
<method name="GET">
<request>
<param name="name" style="query" type="xs:string"/>
<param name="min-population" style="query" type="xs:int"/>
</request>
<response>
<representation mediaType="application/octet-stream"/>
</response>
</method>
</resource>
</resources>
</application>
该示例由CXF从此Java代码自动生成:
import javax.ws.rs.GET;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class Cities {
@GET
public Response get(@QueryParam("name") String name, @QueryParam("min-population") int min_poulation) {
// TODO: build the real response
return Response.ok().build();
}
}
我遇到了同样的问题 - 所以我通过一个实际的例子解决了这两个问题(还有一些你还没有想到)。 http://thereisnorightway.blogspot.com/2012/05/api-example-using-rest.html?m=1
基本上,问题1的解决方案是你改变你的表示(正如罗伊所说,花时间在资源上)。 您不必返回所有zip,只需使您的资源包含分页。 例如,当您从新闻网站请求新闻页面时 - 它会为您提供今天的新闻和更多链接,即使所有文章都可能存在于相同的网址结构下,即...文章/ 123等
问题2有点尴尬 - 在http中我使用了一个名为OPTIONS的小命令,我在示例中使用它基本上反映了url的功能 - 虽然你也可以在表示中解决这个问题,但它会更复杂。 基本上,它返回一个自定义结构,显示资源的功能(包括可选参数)。
让我知道你的想法!
我觉得你跳过了书签网址。 这是第一个网址,而不是获取城市或邮政编码的网址。
所以你从ab:= http://api.addressbook.com开始
第一个链接返回可用链接列表。 这就是网络的运作方式。 你去www.yahoo.com,然后你开始点击不知道他们去哪里的链接。
因此,从原始链接ab:您将返回其他链接,他们可以有REL链接,解释应如何访问这些资源或可以提交哪些参数。
我们在设计系统时首先想到的是从书签页面开始,确定可以访问的所有不同链接。
我同意你对'客户对那些过滤器语义的带外知识'的看法,我很难买到一台机器只能适应那里的东西,除非它有像HTML这样的先入为主的规范。 客户端更有可能由了解所有可能性的开发人员构建,然后对应用程序进行编码以“潜在地”期望这些链接可用。 如果链接可用,则程序可以使用开发人员在执行资源之前实现的逻辑。 如果它不存在那么它就不会执行链接。 最后,在开始遍历应用程序之前布置可能的路径。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.