HATEOAS

前言:HATEOAS — 另一种解释

此页面是对 HATEOAS 的 Wikipedia 条目 的改写,该条目使用了 JSON。 在这里,我们使用 HTML 来解释这个概念,并将其与 JSON API 进行对比。这是一个比 Wikipedia 更具主观性的概念解释,但我们认为它更准确。

超媒体作为应用状态的引擎(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

注:本节的中性语气存在争议

在 2000 年代早期,REST 的概念从其最初的概念环境(作为早期 Web 的描述)被挪用到 Web 开发的其他领域:首先是 XML API 开发(通常使用 SOAP),然后是 JSON API 开发。尽管 XML 和 JSON 都不是像 HTML 那样自然的超媒体。

为了表征这些新领域对 REST 的不同遵守程度,提出了 Richardson 成熟度模型,它包括 API 的各种“成熟度”级别,最高级别 3 包括“超媒体控件”。

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 表示那样的自包含“统一接口”。

无论 JSON API 偏离 RESTful 概念多远,将其标记为“REST”已导致 Roy Fielding 说:

我对将任何基于 HTTP 的接口称为 REST API 的人数感到沮丧。今天的一个例子是 SocialSite REST API。那是 RPC。它尖叫着 RPC。显示的耦合如此之多,以至于它应该被评为 X 级。

虽然已经尝试在 JSON API 上施加更复杂的超媒体控件,但行业广泛拒绝了这种方法,转而青睐放弃 HATEOAS 和 RESTful 架构其他元素的更简单的 RPC 风格 API。

这一事实有力地证明了像 HTML 这样的自然超媒体对于构建 RESTful 系统是实际必需的。

</>