【问题标题】:Consuming HATEOAS API from native clients, Is REST really REST?从本地客户端使用 HATEOAS API,REST 真的是 REST 吗?
【发布时间】:2018-09-29 16:53:01
【问题描述】:

我正在用 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 clients”呢?

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

除此之外,还有许多 API 客户端生成器(解析 OpenAPI 规范),例如 AutoRest(与超链接和 HATEOAS 无关,令我惊讶的是)或 NSwag

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

它有很多标准,如HALIon 等。但几乎没有其他客户端库实现这些标准。还有json:api。所有这些标准都非常相似。

所以我的问题是:

  • HATEOAS 是否适用于 SPA、移动和桌面客户端等应用程序?

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

  • 是否值得为此烦恼?

几乎没有与 HATEOAS 或超媒体 API 交互的实际示例,或者我在这里遗漏了一些东西并且它们不应该被我的客户端代码使用?

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

【问题讨论】:

  • 虽然我认为这是一个很好的问题,但我认为它不太适合这里,而是Software Engineering
  • @PaulKertscher 说得好
  • 我同意 Paul 的观点,但简短的回答是您是对的,REST API 不是“纯”REST。很难为 .NET 世界找到一个好的、完整的 REST 框架。有一次,我使用并喜欢 OpenRasta,但 3.x 重写有点横空出世。我也使用过 Nancy,但它不像 Seb 使用 OpenRasta 管理的那样纯粹。遗憾的是它还没有流行起来,如果您从一开始就拥有走这条路的奢侈,那么纯 REST 是一种很好的工作方式。 (不过,以后几乎不可能融入项目。)
  • 这取决于你的项目。如果您只是在做一个 API,那么所有开销都没有多大意义,出于同样的原因,ASP.NET API 项目默认情况下不会为您提供像 Razor 这样的东西。如果你正在做一个完整的站点,坦率地说,我现在不知道在 .NET / .NET Core 中以正确的方式进行全面 REST 的好方法——但我最近也没有真正寻找过.
  • @McGuireV10 我认为做完整的 REST 不值得那么痛苦,因为我发现直接序列化我的模型要简单得多,而无需所有元数据和链接。我使用 swashbuckle 生成 OpenAPI 规范并为我生成文档,我还将使用 NSwag 或 Autorest 等工具生成客户端代码

标签: c# rest asp.net-core-webapi hateoas json-api


【解决方案1】:

REST 是一种范式,其应用范围不仅限于 HTTP 协议和所谓的“Web API”。 RESTful Web API 是一种简单地将 REST 范式的原则应用于通过 HTTP 进行的客户端-服务器通信的 API。因此,它不需要遵循 REST 中的所有内容,也不一定需要

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

【讨论】:

    【解决方案2】:

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

    "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 映射到有角度的路线并处理要做什么。 简单来说,这非常有效。后端 (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 只使用它们(基于角度合同)

    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(s) 中,我们可以汇总我们的权限,即使您要求提供链接,如果您没有权限,也不会被退回。

    一些 BFF 工作是反射/招摇类型的东西,但通常业务功能并不关心。他们只是说“返回此链接”,这一切都正常。

    此外,我们使用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 是如何做到的。他们会为你指明正确的方向:-)

    我希望这对人们有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-06
      • 1970-01-01
      • 1970-01-01
      • 2012-08-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多