使用本地客户端消费HATEOAS API,REST是否真正符合REST原则?

4
我正在使用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.../>),因此您不必在客户端代码中硬编码链接/终点,并且如果更改了某个终点,您的客户端代码不会中断。另一件事是它使您的API可发现,无需任何文档,即API自描述(如元API)。
当我查看现有的C#“REST”客户端时,例如: 他们的名称中都带有“REST”,但它们没有深入探讨HATEOAS的概念。那么它们为什么被命名为“Rest客户端”呢?
如何在客户端使用HATEOAS?令人惊讶的是,互联网上关于此方面的资料并不多,大部分内容都是关于如何在服务器上实现HATEOAS,但是很少有关于它在客户端应该如何工作的资料。通常,它应该提供导航逻辑。
除此之外,还有许多API客户端生成器(解析OpenAPI规范),例如AutoRest(出乎意料地没有关于超链接和HATEOAS的内容),或者NSwag
我花了几个小时在谷歌上搜索学习HATEOAS,但大多数人只是谈论它而没有描述如何使用它(几乎没有支持它的客户端库)。
有许多相关标准,如HALIon等,但几乎没有一个REST客户端库实现这些标准。还有json:api。所有这些标准都非常相似。
因此,我的问题是:
  • HATEOAS是否适用于SPA、移动和桌面客户端等应用程序?

  • HATEOAS的真正用例是什么,如果我可以硬编码我的端点,或者根据OpenAPI(Swagger)规范生成新的API客户端,因为它发生了变化?

  • 甚至值得去管它吗?

几乎没有与HATEOAS或超媒体API交互的实际示例,或者我错过了什么,它们不应该被我的客户端代码使用吗?
对我来说,似乎在客户端和服务器上都实现它需要大量的样板代码,那么为什么没有很多库直接支持它(使用其中一个标准)呢?看起来json:api有许多实现。http://jsonapi.org/implementations/#client-libraries-net

1
虽然我认为这是一个非常好的问题,但我认为它并不完全适合在这里讨论,而应该在[softwareengineering.se]上进行。 - Paul Kertscher
@PaulKertscher 这是一个很好的观点。 - Konrad
1
我同意Paul的观点,但简单回答是你是对的,REST API并不是“纯粹”的REST。在.NET领域中,很难找到一个好的、完整的REST框架。曾经我使用和喜欢过OpenRasta,但3.x重写有些偏离方向。我也用过Nancy,但它没有像Seb管理的OpenRasta那样纯净。很遗憾,它没有得到广泛应用,如果你有从一开始就采用这种方式的奢侈条件,那么纯REST是一个很好的工作方式。(虽然几乎不可能后来将其融入项目中。) - McGuireV10
1
这取决于你的项目。如果你只是在做一个API,那么没有必要增加太多开销,同样的道理,ASP.NET API项目默认不提供Razor等功能。如果你正在做一个完整的网站,我坦率地说,我现在不知道在.NET / .NET Core中正确地实现全面的REST的好方法 - 但我最近也没有真正寻找过。 - McGuireV10
1
@McGuireV10 我认为做全面的REST不值得这么麻烦,因为我发现只需将我的模型序列化即可,而无需所有元数据和链接,这样会简单得多。我使用Swashbuckle生成OpenAPI规范并为我生成文档,我还将使用NSwag或Autorest等工具生成客户端代码。 - Konrad
显示剩余2条评论
2个回答

3
REST是一种比HTTP协议和所谓的“Web API”更广泛应用的范例。一个“RESTful”的Web API,就是将REST范例的原则简单地应用于HTTP上的客户端-服务器通信。因此,它不必遵循REST中的所有内容,也不一定需要这样做。
虽然HATEOAS是一个很好的概念,但实际上没有HTTP客户端直接实现它(至少我不知道有哪个)。您可以随意让您的API实现它,但这并不意味着它会被真正使用,而且尽管您的API可能是“RESTful”的,但并不意味着每个客户端都是如此。HTTP协议的基础之一是自适应通信。换句话说,客户端不需要支持服务器的所有功能,反之亦然。客户端和服务器相互配合,只使用它们在功能上共享的部分。

1
抱歉,来晚了,但我们尝试为一个带有.NET Core后端的angular SPA实现HATEOAS解决方案。 我们寻找了现成的解决方案,但最终还是自己开发了。简单地说,我们只需以标准格式从每个请求中返回http链接,供我们的UI理解。它们看起来有点像这样:
"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"
        ]
    }
]

每个响应都会以标准格式返回这些内容的数组/枚举/列表。"rel" 成为了关键。它是UI和BFF之间的合同。UI将 "rel" 映射到Angular路由并处理操作。
简单来说,这非常有效。后端 (BFF) 只返回一个链接数组,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")]

允许服务询问控制器并确定返回值。因此,控制器上的属性具有名称、标题、描述等,调用者会对服务说“给我链接{name}的链接,带有{ new { id ="x" }或所需参数。服务返回一堆预填充的链接,U仅使用它们(基于Angular合同)。
var links = this.controllerMethodInformationService.BuildLinks(
    LinksBuilder.Create()
        .IfLinkNeeded(() => true)
            .AddLinkRequest(LinkRequestBuilder.BuildLinkRequestForId(Constants.RouteName.MyRouteName, request.Id))
        .Build());

那PUT/POST模型呢?我们要么让你的UI处理它们(如果有一个返回模型的GET,就使用那种类型)。或者我们添加了一个form元素,允许BFF描述PUT/POST期望的模型。
那查询字符串参数呢?当它们是BFF派生的时候,它们很好用。我们只需将它们作为链接的一部分发送即可。但是,如果查询字符串是UI在处理的一部分呢?这通常是UI/BFF开发人员之间协商解决的问题!大多数情况下,原始链接会被返回,而UI会附加额外的查询字符串参数。
理想情况是让UI只响应BFF。保持简单。我认为这在95%的时间里都有效。我们已经能够添加BFF逻辑(比如说今天是星期二,不显示链接),因为UI没有得到链接,所以不会显示按钮。此外,在BFF中,我们可以聚合我们的权限,即使您请求链接,如果您没有权限,它也不会被返回。
一些BFF工作是反射/ swagger类型的,但通常业务功能并不关心。他们只会说“返回这个链接”,然后一切都正常运行。
此外,我们使用Mediatr,将链接添加为在业务处理程序之后运行的处理程序。如果不实现它,则返回空链接。只需实现一个接口即可,Mediatr处理程序将运行您的代码。这是我们CQRS(Mediatr)管道的一部分。
应用程序导航
以上都是关于页面交互的讨论。那么应用程序导航呢?好吧,我们使用相同的过程(属性)和一个特殊的BFF来调用所有其他BFF,并说“你的端点是什么”?
[NavigationLanding("SomeContext", 1, "This is a description of the context")]

这句话的意思是“如果你有正确的权限和其他逻辑工作正常,我希望我的菜单上显示出来。”

每个BFF中的导航终点都会运行,并且所有权限/业务逻辑都将应用,但导航BFF将聚合任何响应并将它们返回给UI。

这就是我们处理菜单和更全局应用程序导航的方式!它运作得相当不错,使用相同的模型/服务,到目前为止我们对此感到满意!

我想说的是,在我们的项目中,这是真正的优点之一。它并非没有挑战,需要一些工作(让后端和UI人员交流并达成一致,这对我们很有效)。当您可以在后端更改权限并突然看到按钮消失而没有UI更改时,通常是很棒的!

对于我们如何在BFF(s)中识别控制器终点等大多数内容,请查看swagger/swashbuckle的做法。他们会指引您正确的方向:-)

我希望这能帮助大家!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接