HATEOAS

前言:HATEOAS — 另一种解释

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

超媒体作为应用状态引擎(HATEOAS)是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>账号:12345</div>
    <div>余额:100.00美元</div>
    <div>链接:
        <a href="/accounts/12345/deposits">存款</a>
        <a href="/accounts/12345/withdrawals">取款</a>
        <a href="/accounts/12345/transfers">转账</a>
        <a href="/accounts/12345/close-requests">关闭请求</a>
    </div>
  </body>
</html>

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

考虑账户透支后的情况。现在,由于账户状态的变化,可用的链接集合不同了。

HTTP/1.1 200 OK

<html>
  <body>
    <div>账号:12345</div>
    <div>余额:-50.00美元</div>
    <div>链接:
        <a href="/accounts/12345/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的多个“成熟度”级别组成,最高级别Level 3包含“超媒体控件”。

JSON不是自然超媒体,因此超媒体概念只能在其之上强加。尝试达到Richardson成熟度模型Level 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>提交</button>
    </form>
  </body>
</html>

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

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

将无论偏离RESTful概念多远的JSON API标记为'REST',使得Roy Fielding说:

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

尽管有人尝试在JSON API上强加更复杂的超媒体控件,但行业普遍拒绝了这种方法,转而支持更简单的RPC风格API,放弃了HATEOAS和RESTful架构的其他元素。

这一事实强烈支持了以下断言:像HTML这样的自然超媒体是构建RESTful系统的实际必要条件。

</>