通常,当我们在 在线讨论 关于 REST 和 HATEOAS 时,我们可能会说类似这样的话
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 吗?
由于天生固执,我们仍然不愿在没有充分的 事实 的情况下承认这一点
等等:这些关于 REST 的技术性口水战让互联网上的争论变得如此特别令人愉快。
然而,这里有一个更深层次的 事实,它并不涉及JSON API 本身,而是电线的另一端:接收 JSON 的客户端。
针对非 RESTful JSON API 的这个提议解决方案的更深层次的问题是,为了使这个 JSON 响应能够在 超媒体系统 中正确参与,使用 JSON 的客户端也需要满足 RESTful 架构风格对整个系统施加的约束。
特别是,客户端需要满足 统一接口,其中客户端代码除了能够将给定的超媒体显示给用户之外,对响应的“形状”或细节一无所知。在一个正常工作的 RESTful 系统中,客户端不允许拥有关于特定超媒体表示(嗯,表示)域的任何“带外”知识。
让我们再次看看提议的 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"
}
}
}
现在,这个 API 的客户端可以使用通用算法将这个 JSON 转换为例如一些 HTML。它可以通过客户端模板语言来实现,例如,该语言遍历 JSON 对象的所有属性。
但是有一个问题:请注意 JSON 响应中并没有太多演示信息。它是一个关于所讨论账户的相当原始的数据表示,带有一些额外的 URL。
想要满足 REST 的统一接口约束的客户端,对于如何向用户展示这些数据信息并没有太多信息。因此,客户端将需要采用一种非常基本的方法来向最终用户展示此帐户。
它可能最终会变成一组名称/值对和一组通用的操作按钮或链接,对吧?
在保持对 JSON 响应形式的不可知的情况下,它能做的不多。
我们可以通过使我们的 JSON API 更详细来解决这个问题,并开始包含有关如何布局信息的信息:也许是关于某些字段应该强调还是隐藏的指示等等。
但这只是故事的一部分。
我们还需要更新客户端,以便它能够正确地解释我们 JSON API 的这些新元素。因此,我们不再仅仅是 API 设计师:我们也开始涉足超媒体客户端创建业务。或者,更可能的是,我们要求我们的API 客户端也加入超媒体客户端业务。
现在,迈克·阿蒙德森已经写了一本关于如何构建一个合适的通用超媒体客户端的 优秀书籍。但是你会在书中看到,创建一个好的超媒体客户端并非易事,而且,当然,它肯定不是大多数工程师用来使用 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 浏览器的存在,但请花点时间思考一下,仅仅为了在正常、日常的 Web 请求中将 HTML 解析并呈现给最终用户,需要投入多少技术。这非常复杂。
因此,如果我们想构建基于 Web 的 超媒体驱动应用程序,那么使用标准的基于 Web 的超媒体客户端(即浏览器)可能是一个好主意。
它已经是一个非常强大且经过充分测试的超媒体客户端。而且,在一些帮助下,它可以成为一个更好的超媒体客户端。
总的来说,构建一个满足 REST 所有约束的良好超媒体客户端是困难的,我们应该倾向于使用(并扩展)现有客户端,而不是构建自己的新客户端。
话虽如此,有时构建新的超媒体客户端也是合适的。例如,这就是为什么像 Hyperview 这样的技术如此令人印象深刻和特殊的原因。Hyperview 不仅仅为新的移动友好超媒体提供规范,HXML。
它还为开发人员提供了一个超媒体客户端,该客户端知道如何渲染 HXML。
如果没有这个超媒体客户端,Hyperview 就会像上面的 JSON 一样,仅仅是理论上的超媒体,而不是一个引人入胜、实用且完整的 RESTful 超媒体解决方案。
没有超媒体客户端的超媒体就像没有自行车的鱼一样,只不过这条鱼真的只擅长骑自行车。
我花了很长时间才意识到客户端对于一个合适的 RESTful 超媒体系统有多么重要。这是可以理解的,因为关于 REST 的早期讨论主要集中在 API 设计 上,而客户端很少被提及。
我现在看到的是,许多这些讨论都是本末倒置:RESTful 超媒体 API 只有在被合适的超媒体客户端使用时才有用。否则,你的超媒体控制就会浪费在一个最终只想要完成任务的特定于域的厚客户端上。
此外,你的超媒体 API 几乎肯定会需要在其自身中承载相当多的演示层信息,以使整个系统可用。事实证明,理查森成熟度模型 的“第 3 级”,即超媒体控制,不足以达到“REST 的荣耀”。
实际上,你需要添加一些实用的演示级技术来使你的超媒体 API 真正发挥作用,并且你需要一个构建良好的超媒体客户端来使用它。
当我写 HATEOAS 是为人类而设计的 时,我对此有一个初步的感觉,但当时我没有意识到客户端/Web 浏览器是多么特别。
REST 不仅仅是关于 API:正如 Roy Fielding 在其论文中明确指出,它是一个系统架构。
除了像 迈克 这样的少数人之外,我们一直在很大程度上忽视了 REST 故事中更大(实际上,更大得多)的一部分