REST HATEOAS (成熟度级别3)有多么有用/重要?

我正在参与一个项目,其中一些高级团队成员认为 REST API 必须兼容 HATEOAS 并实现所有 Richardson 的成熟度级别(http://martinfowler.com/articles/richardsonMaturityModel.html) !

AFAIK 大多数 REST 实现都不符合 HATEOAS,应该有一个很好的理由为什么更多的人不这样做。我可以想到诸如增加复杂性、缺乏框架(服务器端和客户端)以及性能担忧等原因。

你觉得怎么样? 你有在现实世界的项目中使用 HATEOAS 的经验吗?

26691 次浏览

REST 社区中没有人说 REST 是容易的。仇恨只是给 REST 体系结构增加难度的方面之一。

人们不会因为你提出的所有理由去做仇恨: 这很难。它增加了服务器端和客户端的复杂性(如果您确实希望从中受益的话)。

然而,今天有数十亿人体验到了 REST 的好处。你知道什么是“ 退房网址在亚马逊?我不知道。然而,我可以每天结账。网址改了吗?我不知道,也不在乎。

你知道谁在乎吗?任何编写过屏幕刮痕亚马逊自动客户端的人。有些人可能已经煞费苦心地嗅探网络流量,阅读 HTML 页面等,以找到什么链接调用什么时候和什么有效载荷。

一旦 Amazon 改变了他们的内部流程和 URL 结构,那些硬编码的客户端就失败了——因为链接中断了。

然而,休闲网民可以整天购物,几乎没有任何障碍。

这就是 REST 的实际作用,它只是被人类所增强,人类能够解释和直觉地理解基于文本的界面,识别一个带有购物车的小图形,并弄清楚这实际上意味着什么。

大多数编写软件的人不会这么做。大多数写自动化客户端的人都不在乎。大多数人发现,当客户机发生故障时,修复它们要比设计应用程序从一开始就不发生故障要容易得多。大多数人只是在重要的地方没有足够的客户。

如果您正在编写一个内部 API,用于在两个系统之间进行通信,这两个系统都有专家技术支持,并且在通信的两端都有 IT,他们能够快速、可靠地通信变更,并且有一个变更时间表,那么 REST 不会给您带来任何好处。你不需要它,你的应用程序不够大,也不够长寿,不够重要。

拥有大量用户的大型网站确实存在这个问题。他们不能要求人们在与他们的系统交互时一时兴起改变他们的客户端代码。服务器的开发计划与客户端的开发计划不同。对 API 的突然改变对于所有相关人员来说都是不可接受的,因为它会扰乱双方的通信和操作。

因此,这样的操作很可能从 HATEOAS 获益,因为它更容易版本化,更容易迁移老客户端,更容易向后兼容。

将大部分工作流委托给服务器并根据结果进行操作的客户机对服务器更改的鲁棒性要比不委托的客户机强得多。

但大多数人不需要这种灵活性。他们为2到3个部门编写服务器代码,都是内部使用的。如果它坏了,他们会修好它,他们已经把这个因素纳入了正常的操作中。

灵活性,无论是来自 REST 还是其他任何东西,都会滋生复杂性。如果你想要简单、快捷,那么你就不要让它变得灵活,你只需要“去做”,然后就可以完成了。当你向系统添加抽象和解引用时,事情就变得更加困难,更多的样板文件,更多的代码需要测试。

很多 REST 都没有达到“你不需要它”的要点,当然,直到你需要它为止。

如果你需要它,那就使用它,并按照它的布局使用它。REST 不是通过 HTTP 来回推送东西。从来都不是,比那高多了。

但是当您确实需要 REST,并且您确实使用 REST 时,HATEOAS 就是必需的。这是整个系统的一部分,也是让它运作的关键。

示例:- 为了更好地理解它,让我们看看以下从服务器(http://localhost:8080/user/123)检索 id 为123的用户的响应:

{
"name": "John Doe",
"links": [{
"rel": "self",
"href": "http://localhost:8080/user/123"
},
{
"rel": "posts",
"href": "http://localhost:8080/user/123/post"
},
{
"rel": "address",
"href": "http://localhost:8080/user/123/address"
}
]
}

我在一些真实的项目中使用了 HATEOAS,但是与 Richardson 的解释不同。如果这是你老板想要的,那我想你应该照做。我把 HATEOAS 理解为您的资源应该包括 HTML 文档类型、相关资源的超链接和 HTML 表单,以公开 GET 以外的动词的功能。(这是当 Accept 类型为 text/html 时——其他内容类型不需要这些额外内容。)我不知道整个应用程序中的所有 REST 资源必须粘合在一起的信念从何而来。网络应用程序应该包含多个可能直接相关也可能不直接相关的资源。或者为什么认为 XML、 JSON 和其他类型需要遵循这一点。(HATEOAS 是 HTML 特定的。)

是的,我对 API 中的超媒体有一些经验。下面是一些好处:

  1. 可探索的 API: 这听起来可能微不足道,但是不要低估了一个可探索的 API 的力量。浏览数据的能力使得客户机开发人员更容易构建 API 及其数据结构的心理模型。

  2. 内联文档: 使用 URL 作为链接关系可以指引客户机开发人员查看文档

  3. 简单的客户逻辑: 简单地遵循 URL 而不是自己构造它们的客户机应该更容易实现和维护。

  4. 服务器获得 URL 结构的所有权: 超媒体的使用消除了客户机对服务器使用的 URL 结构的硬编码知识

  5. 将内容卸载到其他服务: 将内容卸载到其他服务器(例如 CDN)时,必须使用超媒体。

  6. 链接版本: Hypermedia 有助于 API 的版本化

  7. 同一服务/API 的多个实现: 当存在同一服务/API 的多个实现时,超媒体是必需的。例如,服务可以是一个博客 API,其中包含用于添加文章和评论的资源。如果服务是通过链接关系而不是硬编码的 URL 来指定的,那么相同的服务可以在不同的 URL 上实例化多次,这些 URL 由不同的公司承载,但仍然可以通过一个客户端定义良好的相同链接集来访问。

你可以在这里找到对这些要点的深入解释: http://soabits.blogspot.no/2013/12/selling-benefits-of-hypermedia.html

(这里有一个类似的问题: https://softwareengineering.stackexchange.com/questions/149124/what-is-the-benefit-of-hypermedia-hateoas,我已经给出了相同的解释)

Richardson 的成熟度级别3很有价值,应该采用。Jørn Wildt 已经总结了一些优点,Wilt 的另一个答案很好地补充了这一点。

然而,理查森的成熟度等级3不同于菲尔丁的 HATEOAS。Richardson 的成熟度级别3仅与 API 设计有关。Fielding 的 HATEOAS 也是关于 API 设计的,但也规定 客户端软件不应该假定资源具有超出媒体类型定义的结构之外的特定结构。这需要一个非常普通的客户端,比如一个网页浏览器,它不了解特定的网站。由于 Roy Fielding 创造了 REST 这个术语,并将 HATEOAS 设置为遵从 REST 的一个要求,问题是: 我们是否想要采用 HATEOAS,如果不想,我们是否仍然可以调用我们的 API RESTful?我想我们可以。听我解释。

假设我们已经达到仇恨。应用程序的客户端现在非常通用,但是很可能用户体验很差,因为如果不了解资源的语义,就无法对资源的表示进行调整以反映这些语义。如果资源“ car”和资源“ house”具有相同的媒体类型(例如 application/json) ,那么它们将以相同的方式呈现给用户,例如作为一个属性表(名称/值对)。

但是好吧,我们的 API 是真正的 RESTful。

现在,假设我们在这个 API 之上构建第二个客户机应用程序。第二个客户机违反了 HATEOAS 思想,并且拥有关于资源的硬编码信息。它以不同的方式展示汽车和房子。

API 仍然可以被称为 RESTful 吗?我想是的。其中一个客户机违反了 HATEOAS 并不是 API 的错。

我建议构建 RESTful API,即通用客户端可以实现 理论上是的 API,但在大多数情况下,为了满足可用性需求,需要 一些硬编码的客户端资源信息。尽管如此,还是尽量少硬编码,以减少客户机和服务器之间的依赖性。

我在 REST 实现模式中包含了一个关于 HATEOAS 的部分,称为 开玩笑

我们正在构建一个 REST 级别3的 API,其中我们的响应在 HAL-Json 中。 HATEOAS 对于前端和后端都很好,但是它也带来了挑战。我们为在 HAL-Json 响应中也管理 ACL 做了一些定制/添加(这没有违反 HAL-Json 标准)。

HATEOAS 最大的优点是我们不需要在我们的前端应用程序中编写/猜测任何 URL。您所需要的只是一个入口点(https://hostname) ,从那里开始,您可以使用响应中提供的链接或模板链接浏览资源。 就像版本控制可以很容易地处理一样,重命名/替换 url,使用附加关系扩展资源而不破坏前端代码。

在前端缓存资源是使用自我链接的小菜一碟。 我们还通过套接字连接将资源推送到客户端,因为这些资源也是在 HAL 中呈现的,所以我们可以通过同样的方式轻松地添加它们来缓存。

使用 HAL-Json 的另一个优点是,响应模型应该是什么样子很清楚,因为应该遵循一个文档化的标准。

我们的定制之一是,我们在自链接对象中添加了一个操作对象,该对象向前端公开经过身份验证的用户允许在各自的资源(create:POSTread:GETupdate:PUTedit:PATCHdelete:DELETE)上执行的操作或 CRUD 操作。像这样,我们的前端 ACL 完全由我们的 REST API 响应决定,将这个责任完全转移到后端模型。

举个简单的例子,你可以在 HAL-Json 中有一个 post 对象,它看起来像这样:

{
"_links": {
"self": {
"href": "https://hostname/api/v1/posts/1",
"actions": {
"read": "GET",
"update": "PUT",
"delete": "DELETE"
}
}
},
"_embedded": {
"owner": {
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com",
"_links": {
"self": {
"href": "https://hostname/api/v1/users/1",
"actions": {
"read": "GET"
}
}
}
}
},
"subject": "Post subject",
"body": "Post message body"
}

现在我们在前端所要做的就是使用 isAllowed方法构建一个 AclService来检查我们想要执行的操作是否在 action 对象中。

目前在前端,它看起来简单如: post.isAllowed('delete');

我认为 REST 级别3很棒,但是它可能会导致一些令人头疼的问题。您需要对 REST 有很好的理解,如果您想使用3级 REST,我建议您严格遵循 REST 的概念,否则您在实现它时很容易迷失方向。

在我们的情况下,我们有优势,我们正在建设前端和后端,但原则上它不应该有什么不同。 但是我在我们的团队中看到的一个常见的陷阱是,一些开发人员试图通过改变他们的后端模型来解决前端问题(体系结构) ,使其“适合”前端需求。