RESTful API 仇恨

ela*_*hen 10 api rest hateoas

我得出的结论是,构建一个真正的 RESTful API(使用 HATEOAS)几乎是不可能的。

我遇到的所有内容要么未能说明 HATEOAS 的真正威力,要么根本没有明确提及 HATEOAS 动态特性的固有痛点。

我相信 HATEOAS 的意义在于:

根据我的理解,真正的 HATEOAS API 应该拥有与 API 交互所需的所有信息,虽然这是可能的,但使用起来却是一场噩梦,尤其是在使用不同的堆栈时。

例如,考虑位于“/books”的资源集合:

{
  "items": [
    {
        "self": "/book/sdgr345",
        "id": "sdgr345",
        "name": "Building a RESTful API - The unspoken truth",
        "author": "Elad Chen ;)",
        "published": 1607606637049000
    }
  ],

  // This describes every field needed to create a new book
  // just like HyperText Markup Language (i.e. HTML) rendered on the server does with forms
  "create-form": {
    "href": "/books",
    "method": "POST",
    "rel": ["create-form"],
    "accept": ["application/x-www-form-urlencoded"],
    "fields": [
        { "name": "name", "label": "Name", "type": "text", "max-length": "255", "required": true }
        { "name": "author", "label": "Author", "type": "text", "max-length": "255", "required": true }
        { "name": "author", "label": "Publish Date", "type": "date", "format": "dd/mm/YY", "required": true }
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

给出上述响应后,客户端(例如 Web 应用程序)可以使用“create-form”属性来呈现实际的 HTML 表单。

我们从所有这些工作中获得什么价值?

多年来我们一直从 HTML 获得同样的价值。

想一想,这正是超文本的全部内容,也是 HTML 的设计目的。

当浏览器点击“www.pizza.com”时,浏览器不知道用户可以访问的其他路径,它不会连接字符串来生成指向订单页面的链接 ->“www.pizza.com/order” ,它只是
在用户单击锚点时呈现锚点并进行导航。这使得 Web 开发人员可以将路径从“/order”更改为“/shut-up-and-take-my-money”,而无需更改任何客户端(浏览器)。

上述想法对于表单来说也是如此,浏览器不会猜测订购披萨所需的参数,它们只是渲染表单及其输入,并处理其提交。

我在前端和后端看到了太多行代码,这些代码构建了类似
->“https://api.com”+“/order”的字符串 -你没有看到浏览器这样做,对吗?

HATEOAS 的问题

给出上面的示例(“/books”响应),为了创建一本新书,客户端需要解析响应,以便利用此 RESTful API 的真正功能,否则,他们将面临假设字段名称的风险是,其中哪一个是必需的,他们期望的类型是什么,等等......

现在考虑一下您的公司中有两个客户端正在使用此 API,一个用于用 JS 编写的 Web(浏览器),另一个用于用 Java 编写的移动设备(例如 Android 应用程序)。它们可以作为 SDK 发布,希望使 3 方消费者能够更轻松地集成。

一位对 Python 有兴趣的第三方开发人员说,一旦 API 被您无法控制的客户端使用,其目的就是创建一本新书。
开发人员需要解析这样的响应,找出参数是什么、它们的名称、要发送输入的 URL 等等。

在我多年的开发生涯中,我还没有遇到过像我想到的那样的 API。我有一种感觉,这种类型的 API 只不过是一个白日梦,我希望在开始实施阶段之前了解我的假设是否正确,以及它会带来哪些缺陷。

PS,以防不清楚,这正是 HATEOAS 兼容 API 的全部内容 - 当创建书籍的字段发生变化时,客户端会在不中断的情况下进行调整。

Jas*_*ers 9

超媒体成熟度模型(HMM) 上,您给出的示例处于级别 0。在这个级别上,您对这种方法的问题是绝对正确的。这是一项繁重的工作,开发人员可能会忽略它并进行硬编码。然而,使用通用的超媒体支持的媒体类型,不仅所有额外的工作都消失了,而且实际上减少了开发人员的工作。

让我们退一步考虑一下网络是如何工作的。它由三个主要组件组成:Web 服务器、Web 浏览器和驱动程序(通常是人类用户)。Web 服务器提供 HTML,Web 浏览器执行该 HTML 来呈现图形用户界面,驾驶员可以使用该界面跟踪链接并填写表格。浏览器使用 HTML 从驱动程序中完全抽象出有关如何呈现表单以及如何通过 HTTP 发送表单的所有细节。

在 API 世界中,抽象出媒体类型和 HTTP 详细信息的通用浏览器这一概念尚未普及。据我所知,唯一既活跃又高质量的就是Ketting。使用像 Ketting 这样的浏览器,可以消除开发人员在使用所有超媒体时必须投入的所有额外工作。超媒体浏览器就像 API 供应商经常提供的 SDK,只不过它适用于任何 API。理论上,您可以从一个 API 链接到另一个完全不相关的 API。API 将不再是孤岛,它们将成为一个网络。

使超媒体浏览器成为可能的是通用超媒体支持的媒体类型。HTML 当然是最成功和最著名的示例,但也有基于 JSON 的媒体类型。一些更广泛使用的示例是HALSiren

媒体类型在超媒体成熟度模型上的级别越高,通用浏览器对媒体类型、URI 和 HTTP 详细信息进行抽象的能力就越强。这是一个简短的解释。查看上面链接的博客文章以获取更多详细信息和示例。

级别 0:在此级别,超媒体以临时方式编码。浏览器对此无能为力,因为每个 API 的编码可能略有不同。浏览器充其量可以使用启发式或人工智能来猜测某物是链接或表单并将其视为链接或表单,但通常 HMM 0 级媒体类型旨在供开发人员阅读解释。这导致了您所提出的问题中的许多挑战。示例:JSON 和 XML。

第 1 级:在此级别,链接是一流的功能。媒体类型有明确定义的方式来表示链接。浏览器明确知道什么被解释为链接,并且可以提供一个接口来跟踪该链接,而无需用户关心 URI 或 HTTP。这对于只读 API 来说已经足够了,但是如果我们需要用户提供输入,我们就没有办法表示类似表单的超媒体控件。人们需要阅读文档或临时表单表示来了解如何提交数据。示例:HALRESTful JSON

级别 2:在此级别,表单(或类似表单的控件)是一流的功能。媒体类型具有明确定义的方式来表示类似表单的超媒体控件。浏览器可以使用此媒体类型执行诸如构建 HTML 表单、验证用户输入、将用户输入编码为可接受的媒体类型以及使用适当的 HTTP 方法发出 HTTP 请求等操作。假设您想要更改 API 以支持 PATCH,并且您希望应用程序开始使用它而不是 PUT。如果您使用的是 HMM 2 级媒体类型,则可以更改表示中的方法,并且任何使用知道如何构造 PATCH 请求的超媒体浏览器的应用程序将开始发送 PATCH 而不是 PUT,而无需任何开发人员干预。如果没有 HMM 2 级媒体类型,您将不得不使用这些 PUT,直到您可以获得使用您的 API 更新其代码的所有应用程序。示例:HTML、HAL-FormsSirenJSON Hyper-SchemaUberMasonCollection+JSON

第 3 级:在此级别,除了超媒体控件之外,数据也是自描述的。还记得我提到的这三个主要组成部分吗?最后一个“驱动程序”是在 Web 上使用超媒体与在 API 中使用超媒体之间的主要区别。在网络上,驱动程序是人(为了简单起见,不包括爬虫),但对于 API,驱动程序是一个应用程序。人类可以解释所呈现的事物的含义并应对变化。应用程序可能会根据启发式甚至人工智能进行操作,但通常它们遵循固定的例程。如果数据发生了应用程序没有预料到的变化,应用程序就会崩溃。在这个级别,我们使用JSON-LD之类的东西将语义应用于数据。这使我们能够构建更善于应对变化的驱动程序,甚至可以在无需人工干预的情况下做出决策。例子:九头蛇

我认为选择在 API 中使用超媒体的唯一缺点是大多数语言都没有可用于生产的 HMM Level 2 超媒体浏览器。但是,好消息是,一种实现将涵盖使用其支持的媒体类型的任何 API。凯特廷适用于任何用 JavaScript 编写的 API 和应用程序。只需要几个类似的实现就可以覆盖所有主要语言,并且选择使用超媒体将是一个简单的选择。

选择超媒体的另一个原因是它有助于 API 设计过程。我个人使用JSON Hyper-Schema来快速构建 API 原型,并使用通用 Web 客户端单击链接和表单来了解 API 工作流程。即使没有其他人使用这些模式,对我来说,即使只是在设计阶段也是值得拥有的。


Bog*_*dan 3

实现 HATEOAS API 需要在服务器和客户端上完成,因此您在评论中提出的这一点确实非常有效:

更改资源 URI 是有风险的,因为我不相信客户端实际上“导航”了 API

除了万维网(HATEOAS 的最佳实现)之外,我只知道现已退役的 Project Kenai 上的 SunCloud API。大多数 API 并没有完全利用 HATEOAS,而只是一堆记录的 URL,您可以在其中获取或提交特定资源(基本上,它们不是“超媒体驱动”,而是“文档驱动”)。使用这种类型的 API,客户端实际上并不导航 API,而是连接字符串以到达他们知道可以找到特定资源的特定端点。

如果您公开 HATEOAS API,任何客户端的开发人员仍然可以查看您返回的链接,并可能决定自行构建它们,因为他们知道该 API 正在做什么,因此认为他们可以绕过任何其他可能的导航需要时直接访问 URL,因为它总是如此/products/categories/123,直到 - 当然 - 它不再是了。

HATEOAS API 更难构建,并且增加了服务器和客户端的复杂性,因此在决定构建一个 API 时,问题是:

  • 您是否需要 HATEOAS 提供的灵活性来证明实施的额外复杂性是合理的?
  • 您想让客户更容易还是更难地使用您的 API?
  • 双方(服务器开发人员和客户端开发人员)是否都具备使这一切顺利进行的知识和纪律?

大多数时候,答案是否定的。此外,很多时候这些问题甚至没有被问到,相反,人们最终会采用更熟悉的方法,因为他们已经见过人们构建的 API 类型、使用过一些 API 或之前构建过一些 API。此外,很多时候,REST API 只是停留在数据库前面,除了将数据库中的数据以 JSON 或 XML 形式公开之外,并没有做太多事情,因此不需要太多导航。

没有人强迫您在 API 中实现 HATEOAS,也没有人阻止您这样做。归根结底,问题是要决定是否要以这种方式公开 API(另一个示例是,是否将资源公开为 JSON、XML,还是让客户端选择内容类型?)。

最后,当您更改 API(HATEOAS 或无 HATEOAS)时,总是有可能破坏您的客户,因为您无法控制您的客户,也无法控制客户开发人员的知识程度、纪律程度,或者他们在实现别人的 API 方面做得有多好。