RESTful API HATEOAS

RESTful API 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 }
    ]
  }
}

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

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

多年来我们从 HTML 获得的相同价值。

想想看,这正是超文本的意义所在,也是 HTML 的设计目的。

当浏览器点击“www.pizza.com”时,浏览器不知道用户访问的其他路径 可以访问,它不会连接字符串以产生 link 到订单页面 -> “www.pizza.com/order”,它只是呈现锚点
并在用户点击它们时进行导航。这就是允许 Web 开发人员在不更改任何客户端(浏览器)的情况下将路径从“/order”更改为“/shut-up-and-take-my-money”的原因。

上述想法也适用于表单,浏览器不会猜测订购比萨饼所需的参数,它们只是呈现表单及其输入,并处理其提交。

我在前端和后端看到太多构建字符串的代码行
喜欢 -> "https://api.com" + "/order" - 你没看到浏览器这样做,对吧?

HATEOAS 的问题

给出上面的例子(“/books”响应),为了创建一本新书,客户需要解析响应以利用这个 RESTful API,否则,他们可能会假设字段的名称是什么、哪些字段是必需的、它们的预期类型是什么等等...

现在考虑让您公司内的两个客户使用此 API, 一个用 JS 编写的网络(浏览器),另一个用 Java 编写的移动设备(比如 android 应用程序)。它们可以作为 SDK 发布,希望让 3 方消费者更容易集成。

一旦 API 被您无法控制的客户使用,比如与 python 有亲和力的第 3 方开发人员,目的是创建一本新书。
该开发人员需要解析这样的响应,弄清楚参数是什么,它们的名称,URL 将输入发送到,等等。

在我多年的开发过程中,我还没有遇到 API 我心目中的那种。 我觉得这种 API 只不过是白日梦,我希望在开始实施阶段之前了解我的假设是否正确,以及它带来的失败。

P.S 如果不清楚,这正是符合 HATEOAS API 的全部内容 - 当创建书籍的字段更改时,客户会在不中断的情况下进行调整。

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

changing a resource URI is risky given I don't believe clients actually "navigate" the API

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

如果您公开 HATEOAS API,任何客户端的开发人员仍然可以查看您的链接 return 并可能决定自行构建它们,因为他们知道 API 正在做,所以然后认为他们可以绕过可能需要的任何其他导航并直接前往 URL,因为它始终是 /products/categories/123,直到 - 当然 - 它不再存在.

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

  • 您是否需要 HATEOAS 提供的灵活性来证明实施的额外复杂性?
  • 您想让客户更容易还是更难消费您的 API?
  • 双方(服务器开发人员和客户端开发人员)是否具备使这一切正常工作的知识和纪律?

大多数时候,答案是否定的。此外,很多时候这些问题甚至都没有被问到,人们最终会选择更熟悉的方法,因为他们已经看到 APIs 人们构建、使用过或之前构建过的那种方法.此外,很多时候,REST API 只是停留在数据库前面,除了将数据库中的数据公开为 JSON 或 XML 外,并没有做太多事情,所以没那么需要在那里导航。

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

最后,当您更改 API(HATEOAS 或没有 HATEOAS)时,总是有可能破坏您的客户,因为您无法控制客户,也无法控制客户端开发人员的知识渊博程度、纪律程度如何,或者他们在实施其他人的 API.

方面所做的工作有多出色

Hypermedia Maturity Model (HMM) 上,您给出的示例是级别0。在这个级别,您对这种方法的问题是绝对正确的。这是很多工作,开发人员可能会忽略它并硬编码一些东西。然而,使用通用的支持超媒体的媒体类型,不仅所有额外的工作都消失了,它实际上减少了开发人员的工作。

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

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

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

媒体类型在 Hypermedia Maturity Model 上的级别越高,通用浏览器对媒体类型、URI 和 HTTP 详细信息的抽象方式就越多。这是一个简短的解释。查看上面 post link 的博客以获取更多详细信息和示例。

级别 0:在此级别,超媒体以特殊方式编码。浏览器对此无能为力,因为每个 API 编码的内容可能略有不同。浏览器充其量可以使用启发式或 AI 来猜测某些东西是 link 或一种形式并将其视为这样,但通常 HMM 级别 0 媒体类型旨在供开发人员阅读解释。这导致了您确定问题的许多挑战。示例:JSON 和 XML.

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

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

级别3:在这个级别,除了超媒体控件之外,数据也是自描述的。还记得我提到的那三个主要组成部分吗?最后一个,“驱动程序”,是在网络上使用超媒体和在 API 中使用超媒体之间的主要区别。在 Web 上,驱动程序是一个人(为简单起见不包括爬虫),但是对于 API,驱动程序是一个应用程序。人类可以解释所呈现的事物的含义并应对变化。应用程序可能会根据启发式甚至 AI 采取行动,但通常它们会遵循固定的例程。如果数据发生了应用程序没有预料到的变化,应用程序就会中断。在这个级别,我们使用 JSON-LD. This allows us to construct drivers that are better at dealing with change and can even make decisions without human intervention. Examples: Hydra.

之类的东西将语义应用于数据

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

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