【问题标题】:RESTful api design, HATEOAS and resource discoveryRESTful api 设计、HATEOAS 和资源发现
【发布时间】:2012-02-01 18:52:51
【问题描述】:

HATEOAS 背后的核心理念之一是客户端应该能够从单个入口点 URL 开始,并发现所有可用的公开资源和状态转换。虽然我可以完美地看到它如何与 HTML 和浏览器后面的人点击链接和“提交”按钮一起工作,但我想知道如何将这一原则应用于我(不)幸运处理的问题。

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

问题 1

假设我想设计 RESTful api 来通过邮政编码查找城市。我想出了嵌套在邮政编码中的称为“城市”的资源,因此 http://api.addressbook.com/zip_codes/02125/cities 上的 GET 返回包含例如代表多切斯特和波士顿的两条记录的文档。

我的问题是:如何通过 HATEOAS 发现这样的 url?在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,还要公开这两个可能的查询选项以及它们可以组合的事实。如果没有客户对这些过滤器的语义和将它们组合成动态 URL 背后的原则的带外知识,这似乎是根本不可能的。

那么 HATEOAS 背后的原则如何帮助使这些微不足道的 API 真正成为 RESTful?

【问题讨论】:

  • 顺便说一句,我也在探索 api 版本控制的选项,这就是为什么每个版本都有一个入口点很诱人。 these lines.

标签: api rest hateoas


【解决方案1】:

我建议使用 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 中缺少的是formrel 属性。

查看this article:

总而言之,有几个理由将 XHTML 视为 RESTful 服务的默认表示。首先,你可以 利用 &lt;a&gt; 等重要元素的语法和语义, &lt;form&gt;&lt;input&gt; 而不是自己发明。第二,你会结束 提供感觉很像网站的服务,因为它们将 用户和应用程序都可以浏览。 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>

【讨论】:

  • 这很有趣,它有助于解决问题 2,但是如何用表格表示问题 1?对于 HTTP 标头的特定要求也是如此。我认为 XHTML 表单的表现力远不如 WADL。
  • @Chris Dolan:我的例子是关于问题 1。解决它的另一种方法是使用 URI 模板。我同意 WADL 更完整,但是使用 XHTML 您可以免费获得 UI。带有&lt;?xml-stylesheet?&gt; 的 WADL 应该很不错。
  • 是的,但是您忽略了他的 API,它是 /zip_codes/{:zip_code}/cities,您在其中定义了 /zip_code_search?zip_code={:zipcode} - 完全不同,而且使用 xhtml 更容易。尽管如此,我认为我们实际上离达成共识并不遥远。快速搜索发现了一些我认为可行的样式表:github.com/ipcsystems/wadl-stylesheetgithub.com/mnot/wadl_stylesheets(我更喜欢前者)
  • @Chris Dolan:请注意,响应包含一个Location 标头,其中包含指向/zip_codes/{zip_code} 的链接。为避免请求过多,响应可以是 OK 并包含 zip_code 表示,带有 Content-Location: /zip_codes/{zip_code} 标头,以及表示中的城市链接。
【解决方案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();
    }
}

【讨论】:

  • 哇,感谢您的详细回答! WADL 是一种合理的方式,但这不会立即使我们更接近 SOAP 域吗?我认为 HATEOAS 与此相反,正如我从“获取咖啡”示例和其他类似论文中得出的那样。
  • 就像我说的,我实际上并不推荐这个解决方案!这只是一种可能性。我认为它不像 WSDL/SOAP 那样复杂。是的,它比 "zip_codes/{:zip_code}/cities" 更正式,但它具有更大的灵活性并且是机器可读的。 HATEOAS 的一个隐含点是 REST API 的机器可读性,对吧?好吧,我认为这只是朝着这个方向迈出的又一步。
【解决方案3】:

在回答问题 1 时,我假设您的单一入口点是 http://api.addressbook.com/zip_codes,其目的是让客户端能够遍历整个邮政编码集合并最终检索与其相关的城市。

在这种情况下,我会让http://api.addressbook.com/zip_codes 资源返回一个重定向到邮政编码的第一页,例如:

http://api.addressbook.com/zip_codes?start=0&amp;end=xxxx

这将包含一个“页面”价值的邮政编码链接(任何适合系统处理的数字,加上一个指向下一页(如果有的话,还有上一页)的链接。

这将使客户端能够根据需要抓取整个邮政编码列表。

每个页面中返回的 url 看起来类似于:

http://api.addressbook.com/zip_codes/02125

然后根据需要决定是在邮政编码 URL 返回的表示中包含城市信息,还是包含指向它的链接。

现在客户可以选择是遍历整个邮政编码列表,然后请求每个邮政编码(然后是城市),还是请求一个邮政编码页面,然后请求向下钻取到一个部分

【讨论】:

    【解决方案4】:

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

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

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

    让我知道你的想法!

    【讨论】:

      【解决方案5】:

      我觉得您跳过了书签 URL。那是第一个 url,而不是获取城市或邮政编码的那些。

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

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

      所以从原始链接 ab:你会得到其他链接,它们可能有 REL 链接来解释应该如何访问这些资源或可以提交哪些参数。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-17
        • 1970-01-01
        • 1970-01-01
        • 2021-09-30
        • 2020-05-30
        • 1970-01-01
        相关资源
        最近更新 更多