从本地客户端消费 HATEOAS API,REST 真的是 REST 吗?

Consuming HATEOAS API from native clients, Is REST really REST?

我在 ASP NET Core 中编写 Web API,我想从单页应用程序(例如使用 Angular、Vue、React)、本机桌面应用程序中使用它和移动应用程序。

我偶然发现了一个叫做 "HATEOAS" 的概念,我了解到我正在构建的 API 并不是真正的 RESTful 并且我错误地将其命名为 RESTful (https://devblast.com/b/calling-your-web-api-restful-youre-doing-it-wrong)。

似乎大多数人都不好用这个术语(Roy T. Fielding - REST 背后的人关于他的烦恼:http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

据我了解,HATEOAS 背后的理念是像看待网站一样看待您的应用程序,这意味着它公开资源链接(如 HTML 中的 <a href.../>),因此您不必必须在您的客户端代码中对 links/endpoints 进行硬编码,并且如果您更改某些端点 (?),您的客户端代码也不会中断。

另一件事是,它使您的 API 无需任何文档即可被发现,即 API 自我描述(如 meta API)

例如,当我查看现有的 "REST" 个 C# 客户端时:

他们的名字中有 "REST",但其中 none 深入研究了 HATEOAS 的概念。那他们到底为什么叫 "Rest clients"?

HATEOAS 应该如何在客户端使用?令我惊讶的是,互联网上并没有太多关于它的内容,有很多关于如何在服务器上实现 HATEOAS 的内容,但没有太多关于它应该如何在客户端上工作的内容。通常应该提供导航逻辑。

除此之外,还有许多 API 客户端生成器(解析 OpenAPI 规范),例如 AutoRest (nothing about hyperlinks and HATEOAS, to my surprise), or NSwag

我在谷歌上搜索了几个小时以了解 HATEOAS,但大多数人谈论它却没有描述如何使用它(几乎 none 客户端库支持它)。

HAL, Ion etc. But there are almost none rest client libraries implementing those standards. There's also json:api一样有很多标准。所有这些标准都非常相似。

所以我的问题是:

几乎有 none 个与 HATEOAS 或超媒体 API 交互的实际例子,或者我在这里遗漏了什么,我的客户端代码不应该使用它们?

对我来说,在客户端和服务器上实现它似乎是很多样板代码,那么为什么没有很多库支持它开箱即用(使用标准之一)。好像 json:api 有很多实现 http://jsonapi.org/implementations/#client-libraries-net

REST 是一种比 HTTP 协议和所谓的 "web APIs" 应用更广泛的范例。 RESTful Web API 是一种简单地将 REST 范例的原则应用于通过 HTTP 的客户端-服务器通信的网络。因此,它不需要遵循 REST 中的所有内容,也不一定需要

虽然 HATEOAS 是一个很好的概念,但没有实际开箱即用的 HTTP 客户端(至少我知道)。你可以随意让你的 API 实现它,但这并不意味着它会被实际使用,虽然你的 API 可能是 "RESTful",但这并不意味着每个客户将。 HTTP 协议的部分基础是自适应通信。换句话说,客户端不需要支持服务器的所有功能,反之亦然。相反,客户端和服务器使用它们在功能方面的共同点。

抱歉,很晚了,但我们已尝试为具有 .NET Core 后端的 angular SPA 实施 HATEOAS 解决方案。 我们寻找开箱即用的解决方案,但最终推出了我们自己的解决方案。简单地说,我们只是以我们的 UI 能够理解的标准格式从每个请求中 returned http links。他们看起来有点像这样:

"links": [
    {
        "title": "A thing I want to do",
        "href": "http://localhost:12345/SomeGuff/t703g176-4546-4345-643c-6615b4f166ec/tokenrenewals",
        "rel": "mybff:afunction",
        "display": "A thing I want to do",
        "method": "POST",
        "mediaTypes": [
            "application/json"
        ]
    }
]

因此,每个回复都会 return 一个 array/enumerable/list 标准格式。 rel 成为关键。这是 UI 和 BFF 之间的合同。 UI 会将 rel 映射到 angular 路由并处理要执行的操作。 简单来说,这非常有效。后端 (BFF) 只是 return 一个 link 的数组,UI 荣誉。

这里有更多的故事:

后端

我们采用了一个基于属性的系统,允许控制器用表示他们想玩游戏的细节进行装饰。

[HttpPost]
[Route("{id}/someguff", Name = "MyRouteName")]
[Consumes("application/json")]
[Produces("application/foo-token-1.0.0+json")]
[Links("Some guff", "mybff:myfunction", "I am doing some guff")]

允许服务询问控制器并找出要 return 的内容。所以控制器上的属性有一个名称和一个标题、描述等,调用者会对服务说“给我 link for link {name} with params { new { id ="x" } 或其他需要的参数。服务 returned 一堆 pre-populated link U 只是使用它们(基于 angular 合同)

var links = this.controllerMethodInformationService.BuildLinks(
    LinksBuilder.Create()
        .IfLinkNeeded(() => true)
            .AddLinkRequest(LinkRequestBuilder.BuildLinkRequestForId(Constants.RouteName.MyRouteName, request.Id))
        .Build());

那么 PUT/POST 模型呢?好吧,我们要么让你 UI 处理它们(它有一个 returned 模型的 GET,使用那种东西)。或者我们添加了一个 form 元素,允许 BFF 描述 PUT/POST 期望的模型。

那么查询字符串参数呢?好吧,当它们是 BFF 派生的时,它工作得很好。我们只是将它们作为 link 的一部分发送。但是查询字符串是什么 UI 正在做的事情的一部分?好吧,这通常是 UI/BFF 开发人员之间的对话,以商定解决方案!大多数时候,原始 link 被 return 编辑并且 UI 附加了额外的查询字符串参数

理想的做法是让 UI 只响应 BFF(s)。傻点。我认为它在 95% 的时间里都有效。我们已经可以添加一个 BFF 逻辑(假设是星期二,不显示 link)和 UI,因为它没有获取 link 不显示按钮.此外,在 BFF 中,我们可以汇总我们的权限,即使您请求 link,如果您没有权限,也不会 returned。

一些 BFF 工作是 reflection/swagger 类型的东西,但通常业务功能不关心。他们只是说 "return this link" 一切正常。

此外,我们使用 Mediatr 并且我们的 link 作为处理程序添加到我们的业务处理程序之后 运行s。不实现,酷,空 links returned。只需实现一个接口,Mediatr 处理程序就会 运行 您的代码。这是我们 CQRS (Mediatr) pipleline 的全部内容。

应用导航

以上都是在谈论 页面 交互。应用导航呢?好吧,我们使用了相同的进程(属性)和一个特殊的 BFF,它会调用所有其他 BFF 并说 "what are you endpoints"?

[NavigationLanding("SomeContext", 1, "This is a description of the context")]

这就是说 "I want to be shown on the menu"(如果您有正确的权限并且其他逻辑有效)。

每个 BFF 中的导航端点将 运行 并且所有 permission/business 逻辑将应用但导航 BFF 将聚合任何响应并将它们 return 到 UI.

这就是我们处理菜单和更多全局应用程序导航的方式!它工作得很好,使用相同的 models/services,到目前为止我们对它很满意!

我想说,对于我们的项目来说,这是真正的加分点之一。它并非没有挑战,并且确实需要做一些工作(让您的后端和 UI 人员交谈并同意它应该如何工作 - 这对我们有用)。当您可以在后台更改权限时突然一个按钮消失而没有 UI 更改通常很棒!

对于我们如何在 BFF 中识别控制器端点等的大部分内容,请查看 swagger/swashbuckle 是如何做到的。他们会为您指出正确的方向:-)

希望对大家有所帮助!