HATEOAS

#前言:HATEOAS — 另一种解释

本页面是对 维基百科上关于 HATEOAS 的条目 的重新演绎,该条目使用 JSON。在这里我们使用 HTML 来解释这个概念,并将其与 JSON API 进行对比。这是一个比维基百科更主观的解释,但我们认为它是更准确的。

超媒体作为应用状态的引擎 (HATEOAS) 是 REST 应用架构 的一个约束条件,它将 REST 与其他网络应用程序架构区分开来。

使用 HATEOAS,客户端与网络应用程序进行交互,该应用程序的服务器通过 超媒体 动态地提供信息。REST 客户端只需对超媒体有一个基本的了解,就几乎不需要任何关于如何与应用程序或服务器交互的先验知识。

相比之下,如今基于 JSON 的 Web 客户端通常通过一个固定的接口进行交互,该接口通过像 Swagger 这样的工具通过文档共享。

HATEOAS 强加的限制解耦了客户端和服务器。这使得服务器功能能够独立地演化。

#示例

实现 HTTP 的用户代理通过一个简单的 URL 对 REST 端点发出 HTTP 请求。用户代理可能发出的所有后续请求都可以在对每个请求的超媒体响应中发现。用于这些表示的媒体类型以及它们可能包含的链接关系是标准化的。客户端通过从超媒体表示中的链接中选择或通过以其媒体类型允许的其他方式操作表示来转换应用程序状态。

这样,RESTful 交互是由超媒体驱动的,而不是带外信息。

一个具体的例子将阐明这一点。考虑这个由 Web 浏览器发出的 GET 请求,它获取一个银行账户资源

GET /accounts/12345 HTTP/1.1
Host: bank.example.com

服务器使用 HTML 响应一个超媒体表示

HTTP/1.1 200 OK

<html>
  <body>
    <div>Account number: 12345</div>
    <div>Balance: $100.00 USD</div>
    <div>Links:
        <a href="/accounts/12345/deposits">deposits</a>
        <a href="/accounts/12345/withdrawals">withdrawals</a>
        <a href="/accounts/12345/transfers">transfers</a>
        <a href="/accounts/12345/close-requests">close-requests</a>
    </div>
  <body>
</html>

响应包含以下可能的后续操作:导航到 UI 以输入存款、取款、转账或关闭请求(以关闭帐户)。

考虑在帐户透支后的某个时间点的情况。现在,由于帐户状态发生了变化,不同的链接集可用。

HTTP/1.1 200 OK

<html>
  <body>
    <div>Account number: 12345</div>
    <div>Balance: -$50.00 USD</div>
    <div>Links:
        <a href="/accounts/12345/deposits">deposits</a>
    </div>
  <body>
</html>

只有一个链接可用:存入更多资金。在帐户当前透支状态下,其他操作不可用,这一点在超媒体中反映出来。Web 浏览器不知道透支帐户的概念,甚至不知道帐户是什么。它只知道如何向用户呈现超媒体表示。

因此,我们有了超媒体作为应用程序状态的引擎的概念。可能的动作会随着资源状态的变化而变化,而这些信息都编码在超媒体中。

将上面的 HTML 响应与典型的 JSON API 进行对比,后者可能返回一个具有状态字段的帐户表示

HTTP/1.1 200 OK

{
    "account": {
        "account_number": 12345,
        "balance": {
            "currency": "usd",
            "value": -50.00
        },
        "status": "overdrawn"
    }
}

在这里我们可以看到,客户端必须明确知道status字段的含义以及它可能如何影响用户界面的渲染,以及可以对其进行哪些操作。客户端还必须知道用于操作此资源的 URL,因为它们没有编码在响应中。这通常是通过查询 JSON API 文档来实现的。

正是这种对带外信息的需要将这个 JSON API 与实现 HATEOAS 的 RESTful API 区分开来。

这展示了两种方法之间的核心区别:在 RESTful、HATEOAS HTML 表示中,所有操作都直接编码在响应中。在 JSON API 示例中,处理和使用远程资源需要带外信息。

#起源

HATEOAS 约束是 REST 的 “统一接口” 特性的一个重要组成部分,如 Roy Fielding 在其 博士论文 中所定义的。Fielding 的论文讨论了早期 Web 架构,当时主要由 HTML 和 HTTP 组成。

Fielding 在他的 博客 上进一步描述了这个概念以及超媒体的关键要求。

#HATEOAS 和 JSON

注意:本节的客观语气存在争议

在 21 世纪初,REST 的概念从它最初作为对早期 Web 的描述的概念环境中被借用到 Web 开发的其他领域:首先是 XML API 开发(通常使用 SOAP),然后是 JSON API 开发。尽管如此,XML 和 JSON 并不是像 HTML 那样天然的超媒体。

为了描述在这些新领域中对 REST 遵守程度的不同,提出了 Richardson 成熟度模型,该模型包含 API 的不同“成熟度”级别,最高级别,第 3 级,包含“超媒体控制”。

JSON 并不是天然的超媒体,因此超媒体概念只能强加在 JSON 之上。试图满足 Richardson 成熟度模型第 3 级的 JSON 工程师可能会返回以下 JSON,对应于上面的银行帐户示例

HTTP/1.1 200 OK

{
    "account": {
        "account_number": 12345,
        "balance": {
            "currency": "usd",
            "value": 100.00
        },
        "links": {
            "deposits": "/accounts/12345/deposits",
            "withdrawals": "/accounts/12345/withdrawals",
            "transfers": "/accounts/12345/transfers",
            "close-requests": "/accounts/12345/close-requests"
        }
    }
}

在这里,“超媒体控制”编码在帐户对象的links属性中。

不幸的是,此 API 的客户端仍然需要了解相当多的额外信息

将上面的 JSON 与以下 HTTP 响应进行比较,该响应是在用户点击第一个 HTML 示例中找到的指向/accounts/12345/deposits的链接后由浏览器检索到的

HTTP/1.1 200 OK

<html>
  <body>
    <form method="post" action="/accounts/12345/deposits">
        <input name="amount" type="number" />
        <button>Submit</button>
    </form>
  <body>
</html>

注意,此 HTML 响应编码了更新帐户余额所需的所有信息,提供了一个带有methodaction属性的form,以及正确更新资源所需的输入。

JSON 表示不具有与 HTML 表示相同的自包含“统一接口”。

Roy Fielding 针对对 JSON API 的标记,无论它们如何偏离 RESTful 概念,都将其称为“REST”,他说

我越来越受不了那些将任何基于 HTTP 的接口称为 REST API 的人。今天的例子就是 SocialSite REST API。那是 RPC。它就是 RPC。它展示了如此多的耦合,应该被评为 X 级。

虽然已经尝试在 JSON API 上强加更精细的超媒体控制,但总的来说,业界已经放弃了这种方法,转而采用更简单的 RPC 风格 API,放弃了 HATEOAS 和 RESTful 架构的其他元素。

这一事实有力地证明了,像 HTML 这样的天然超媒体是构建 RESTful 系统的实际必要条件。

</>