超媒体客户端

Carson Gross

通常,当我们在在线讨论 中令人厌烦地吹毛求疵地谈论RESTHATEOAS 时,我们会说类似这样的话:

JSON 不是超媒体,因为它没有超媒体控件。

看看这个 JSON:

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

看到了吗?没有超媒体控件。

所以这个 JSON 不是超媒体,因此,返回这个 JSON 的 API 不是 RESTful 的。

对此,偶尔会有聪明且经验丰富的 Web 开发者回复类似这样的话:

好吧,REST 先生裤子,你觉得这个 JSON 怎么样?

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

好了,现在这个响应中有超媒体控件(正常人称之为链接,顺便说一句),所以这个 JSON 是超媒体。

所以这个 JSON API 现在是 RESTful 的。感觉好点了吗?

😑

必须承认,至少从高层来看,我们的在线对手在这里有一些论点:这些看起来像是超媒体控件,而且它们确实出现在 JSON 响应中。所以,你不能称这个 JSON 响应为 RESTful 吗?

由于天生固执,我们仍然不愿意轻易承认这一点,而没有一个好的 ackchyually 或两个:

诸如此类:这种吹毛求疵的挑剔使得互联网上关于 REST 的技术争论如此 特别 的乐趣。

然而,这里有一个更深层的 ackchyually,它不涉及 JSON API 本身,而是涉及线路的另一端:接收 JSON 的 客户端

超媒体客户端与呈现信息

这种针对非 RESTful JSON API 的拟议修复的更深层问题是,对于这个 JSON 响应要正确参与 超媒体系统,消耗 JSON 的 客户端 也需要 满足 RESTful 架构风格对整个系统施加的 约束

特别是,客户端需要满足 统一接口, 其中客户端代码除了能够向用户显示给定的超媒体之外,对响应的“形状”或细节一无所知。在正确运行的 RESTful 系统中,客户端不允许对特定超媒体表示所代表的领域有任何“带外”知识。

让我们再看看拟议的 JSON-as-hypermedia:

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

现在,这个 API 的客户端 可以 使用通用算法将这个 JSON 转换为,例如,一些 HTML。它可以通过客户端模板语言来实现,例如,迭代 JSON 对象的所有属性。

但有一个问题:注意 JSON 响应中没有太多 呈现信息。它是一个相当原始的账户数据表示,加上一些额外的 URL。

想要满足 REST 统一接口约束的客户端没有太多关于如何向用户呈现此数据的信息。因此,客户端将需要采用一种非常基本的方法来向最终用户显示此账户。

它很可能最终会成为一组名称/值对和一组通用的按钮或链接用于操作,对吗?

在对 JSON 响应形式保持不可知的情况下,它根本无法做更多。

进一步推进我们的 JSON API

我们可以通过使我们的 JSON API 更复杂并开始包含更多关于如何布局信息的信息来修复这个问题:也许指示某些字段应该被强调或隐藏等。

但这只是故事的一部分。

我们还需要更新客户端来正确解释我们 JSON API 的这些新元素。所以我们不再只是 API 设计师:我们也进入了超媒体 客户端 创建业务。或者,更可能的是,我们要求我们的 API 客户端 也进入超媒体客户端业务。

现在,Mike Amundsen 写了一本优秀的书籍,讲述了如何构建一个正确的、通用超媒体客户端。但你在书中会看到,创建一个好的超媒体客户端并不简单,而且,进一步说,它当然不是大多数工程师会构建来消耗 JSON API 的东西,即使 JSON API 在响应中具有越来越复杂的超媒体控件和呈现信息。

“低效”的表示

当我们开始考虑向 JSON 响应添加更多信息时,Roy Fielding 论文中的一句话跳入脑海:

不过,这种权衡是,统一接口会降低效率,因为信息以标准化形式传输,而不是特定于应用程序需求的形式。

-Roy Fielding, https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5

对 HTML 的一个批评是,它将“呈现”信息与“语义”信息混合。这通常与典型 JSON API 响应的简洁性形成不利对比。

然而,正是这种呈现信息,以及 Web 浏览器(即超媒体客户端)将其转换为人类可以交互的 UI 的能力,使得 HTML 作为更大超媒体系统(即 Web)组件如此出色。

而这正是我们为了支持正确的超媒体客户端而添加到自己的 JSON API 中的内容。

构建超媒体客户端

所以,你可以看到,仅在 JSON API 响应中提供超媒体控件是不够的。它是 REST 故事的 一部分,但不是整个故事。而且,我已经认识到,它并不是故事的 难点。事实上,创建超媒体 客户端 是难点,而创建 好的 超媒体客户端是 真正的难点

现在,我们都习惯了 Web 浏览器就存在,但想想看,所有用于简单解析和渲染 HTML 到最终用户的日常 Web 请求的技术。它的 极其 复杂。

这就是为什么,如果我们想构建基于 Web 的 超媒体驱动应用程序,使用标准的、基于 Web 的超媒体客户端:浏览器,可能是个好主意。

它已经是一个极其强大、经过充分测试的超媒体客户端。而且,通过一些帮助,它可以成为一个更好的超媒体客户端。

一般来说,构建一个满足 REST 所有约束的好的超媒体客户端很困难,我们应该倾向于使用(并扩展)现有的客户端,而不是构建我们自己的新客户端。

Hyperview

话虽如此,有些时候构建新的超媒体客户端是合适的。例如,这正是使得像 Hyperview 这样的技术如此令人印象深刻和特殊的原因。Hyperview 不仅仅提供了一个新的、移动友好的超媒体规范,HXML

它还为开发者提供了一个了解如何渲染 HXML 的超媒体 客户端

没有那个超媒体客户端,Hyperview 就会只是另一个理论上的超媒体,就像上面的 JSON 一样,而不是一个引人注目、实用且 完整 的 RESTful 超媒体解决方案。

没有超媒体客户端的超媒体就像没有自行车的鱼一样,除了鱼真的只擅长骑自行车。

结论

我花了很长时间才欣赏到 客户端 对正确 RESTful 超媒体系统的重要性。这是可以理解的,因为 REST 周围的大多数早期讨论都围绕着 API 设计,而客户端根本没有被提及。

我现在看到的是,这些讨论中的许多都把马车放在马前面:RESTful 超媒体 API 唯一有用的方式是如果它被正确的超媒体客户端消耗。否则,你的超媒体控件就会浪费在本质上是一个特定领域的厚客户端上,它只是想完成任务。

此外,你的超媒体 API 几乎肯定需要携带相当多的呈现层信息来使整个系统可用。事实证明,Richardson 成熟度模型 的“Level 3”,超媒体控件,不足以 达到“REST 的荣耀”。

在实践中,你将需要添加一堆实用的呈现级技术来使你的超媒体 API 真正工作,并且 你将需要一个正确构建的超媒体客户端来消耗它。

当我写 HATEOAS Is For Humans 时,我有这种初步的感觉,但那时我没有充分认识到客户端/Web 浏览器有多么特殊。

REST 不仅仅是关于 API 的:正如 Roy Fielding 在他的论文中清楚说明的,它是一个 系统 架构。

除了像 Mike 这样少数人之外,我们在很大程度上忽略了 REST 故事中更大(实际上,大得多)的部分:

构建超媒体客户端很困难的笑话
</>