RESTful api设计,HATEOAS和资源发现

时间:2012-02-01 18:52:52

标签: api rest hateoas

HATEOAS背后的核心思想之一是客户端应该能够从单一入口点URL开始,并发现所有可用的公开资源和状态转换。虽然我可以很好地看到HTML和人类如何使用浏览器点击链接和“提交”按钮,但我被问到这个原则如何应用于我(非)幸运地处理的问题。 / p>

我喜欢RESTful设计原则如何在论文和教育文章中呈现,这一切都有意义,How to GET a Cup of Coffee就是一个很好的例子。我将尝试遵循惯例,并提出一个简单且没有繁琐细节的例子。我们来看看邮政编码和城市。

问题1

假设我想设计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,这违背了原则并依赖于客户拥有的带外知识。

问题2

假设我想要使用某些过滤功能公开城市索引:

  • http://api.addressbook.com/cities?name=X上的GET只返回名称与X匹配的城市。

  • http://api.addressbook.com/cities?min_population=Y上的GET只返回人口等于或大于Y的城市。

当然,这两个过滤器可以一起使用:http://api.addressbook.com/cities?name=X&min_population=Y

在这里,我不仅要公开网址,还要公开这两个可能的查询选项,以及它们可以合并的事实。如果没有客户对这些过滤器的语义的带外知识以及将它们组合到动态URL中的原则,这似乎是不可能的。

那么HATEOAS背后的原则如何帮助使这些简单的API真正具有RESTful性呢?

5 个答案:

答案 0 :(得分:4)

我建议使用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中缺少的是rel的{​​{1}}属性。

结帐this article

  

总而言之,有几个理由认为XHTML是   RESTful服务的默认表示形式。首先,你可以   利用form等重要元素的语法和语义,   <a><form>,而不是发明自己的。{1}}。其次,你会结束   因为他们会成为像网站一样的服务   用户和应用程序都可以浏览。 XHTML仍然存在   由人类解释 - 它只是一个程序员在开发过程中   而不是运行时的用户。这简化了整个过程   开发过程使消费者更容易学习如何   你的服务有效。最后,您可以利用标准Web   用于构建RESTful服务的开发框架。

另请查看OpenSearch

<小时/> 要减少请求数量,请考虑此响应:

<input>

答案 1 :(得分:2)

我想到了这个解决方案,但我不确定我是否推荐它:不返回资源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();
    }
}

答案 2 :(得分:2)

在回答问题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返回的表示中,或者根据需要链接到它。

现在,客户可以选择是否遍历整个邮政编码列表,然后为每个邮政编码请求邮政编码(然后是城市),或者请求一页邮政编码,然后请求下钻到参与者

答案 3 :(得分:1)

我遇到了同样的问题 - 所以我通过一个实际的例子解决了这两个问题(还有一些你还没有想到)。 http://thereisnorightway.blogspot.com/2012/05/api-example-using-rest.html?m=1

基本上,问题1的解决方案是你改变你的表现形式(正如罗伊所说,花时间在资源上)。您不必返回所有zip,只需使您的资源包含分页。例如,当您从新闻网站请求新闻页面时 - 它会为您提供今天的新闻以及更多的链接,即使所有文章都可能存在于相同的网址结构中,即I.e. ......文章/ 123等

问题2有点尴尬 - 在http中我使用了一个名为OPTIONS的小命令,我在示例中使用它基本上反映了url的功能 - 虽然你也可以在表示中解决这个问题,但它会更复杂。基本上,它返回一个自定义结构,显示资源的功能(包括可选参数)。

让我知道你的想法!

答案 4 :(得分:0)

我觉得你跳过了书签网址。这是第一个网址,而不是获取城市或邮政编码的网址。

所以你从ab:= http://api.addressbook.com

开始

此第一个链接返回可用链接列表。这就是网络的运作方式。你去www.yahoo.com,然后你开始点击不知道他们去哪里的链接。

因此,从原始链接ab:您将获得其他链接,他们可以有REL链接,解释应如何访问这些资源或可以提交哪些参数。

我们在设计系统时首先想到的是从书签页面开始,确定可以访问的所有不同链接。

我确实同意你关于'客户对这些过滤器的语义的带外知识'的说法,我很难买到一台机器可以适应那里的东西,除非它有一些像HTML这样的先入为主的规范。客户端更有可能由了解所有可能性的开发人员构建,然后对应用程序进行编码以“潜在地”期望这些链接可用。如果链接可用,则程序可以使用开发人员在执行资源之前实现的逻辑。如果它不存在那么它就不会执行链接。最后,在开始遍历应用程序之前布置可能的路径。