5

我正在尝试将 HATEOAS 应用于现有应用程序,但在对由 API 响应驱动的表单输入进行建模时遇到了麻烦。

该应用程序允许搜索和预订两个地方之间的联系。第一个端点允许搜索连接GET /connections?from={lat,lon}&to={lat,lon}&departure={dateTime}并返回以下有效负载(响应正文)。

[
  {
    "id": "aaa",
    "carrier": "Fast Bus",
    "price": 3.20,
    "departure": "2019-04-05T12:30"
  },
  {
    "id": "bbb",
    "carrier": "Airport Bus",
    "price": 4.60,
    "departure": "2019-04-05T13:30"
  },
  {
    "id": "ccc",
    "carrier": "Slow bus",
    "price": 1.60,
    "departure": "2019-04-05T11:30"
  }
]

为了订购其中一个连接,客户端需要POST /orders使用以下有效负载之一(请求正文)发出请求:

  • 需要电子邮件
    {
      "connectionId": "aaa",
      "email": "passenger@example.org"
    }
    
  • 需要电子邮件和航班号(承运人仅处理机场连接)
    {
      "connectionId": "bbb",
      "email": "passenger@example.org",
      "flightNumber": "EA1234"
    }
    
  • 需要电话号码
    {
      "connectionId": "ccc",
      "phoneNumber": "+44 111 222 333"
    }
    

有效载荷是不同的,因为不同的连接可能由不同的运营商处理,并且它们中的每一个都可能需要提供一些不同的信息集。我想通知 API 客户端,创建订单时需要哪些字段。我的问题是如何使用 HATEOAS 做到这一点?

我检查了不同的规格,这是我从阅读规格中可以看出的:

  1. HAL & HAL-FORMS有,"_templates"但是,模板本身没有 URI。假定它在 self 链接上运行,在我的情况下是/connections...而不是/orders
  2. JSON-LD我找不到有关表单或模板支持的任何信息。
  3. JSON-API我找不到任何关于表单或模板支持的信息。
  4. Collection+JSON每个文档最多有一个"template",因此假定集合的所有元素都具有相同的字段,而在我的应用程序中并非如此。
  5. Siren看起来"actions"适合我的用例,但该项目似乎已死,并且没有许多主要语言的支持库。
  6. CPHL该项目似乎已死,文档很少,也没有库。
  7. Ion对表单有很好的支持,但我找不到任何支持库。看起来它现在只是一个规范。

像 API 驱动的表单这样的常见问题是否仍然无法通过规范和工具解决?

4

1 回答 1

1

在您的示例中,这似乎Connections是资源。目前尚不完全清楚是否Orders是真正的资源。我猜可能是的,但是要拥有一个,Order你需要一个Clientand Connection。因此,要创建Order一个集合,您需要公开一个集合,可能来自ClientConnection,也可能两者兼而有之。

我认为断开连接是因为“现在我们已经有了一个可用连接列表,客户端可以选择一个并创建一个Order.”。这是完全有效的,但它是远程过程调用 (RPC) 思维,而不是 REST。客观上两者都不比另一个好,除非在一组特定的项目要求的背景下,通常它们不应该混合在一起。

在 RPC 思维模式下,定义了创建订单方法(例如使用 OpenAPI),并且任何客户端都应使用一些带外信息来确定所需的正确形式(即通过阅读 OpenAPI 规范)。

使用 REST/HATEOAS 思维方式,正确的方法是OrdersConnection. 集合中的每个Connection都有一个self链接和一个Orders 集合(链接或对象,由应用程序要求定义)。的每个项目Order都有一个self链接,这是指定可供性的地方。AnOrder是客户端大概知道如何定义的已知类型(即使使用 REST/HATEOAS,客户端和服务必须至少就共享词汇表达成一致)。可以使用任何有效的机制来定义该词汇表——json-ld、XSD 等。

HATEOAS 要求结果包含客户端更新状态所需的所有内容。不能有带外信息(共享词汇表除外)。因此,要解决您的问题,您要么需要公开Orders的集合,要么需要Connection允许Order通过发布到Connection. 如果后者看起来有点像黑客,它可能是。

例如,在 HAL-Forms 中,我会执行以下操作:

{
  "connections": [{
    "id": "aaa",
    "carrier": "Fast Bus",
    "price": 3.20,
    "departure": "2019-04-05T12:30"
    "_links": { 
      "self": { ... }, // link to this connection
      "orders": {} // link to collection of orders for this connection
    }
  },
  , ...],
  "_links": {
    "self": { ... } // link to the collection
  },
  "_templates": { ... } // post/put/patch/delete connection
}

客户将通过链接orders来往于那里,将获得_templates包含管理Order资源说明的集合。OrderPOST 可能需要连接标识符和客户端信息。HAL-Forms 规范定义了一个正则表达式属性,可用于指定为任何特定表单元素提供的数据类型。由于您已通过特定连接导航到达订单,因此您可以在您_templates的订单中准确指定需要哪些字段。例如/orders?connectionType=aaa,将返回一组不同的必需属性,/orders?connectionType=bbb但两者都使用相同的self链接,/orders?connectionType={type}并且您将在 POST/PUT/PATCH 上对其进行验证。

我应该注意到 Spring-HATEOAS 超出了 HAL-Forms 规范,并允许多个_links_templates. 请参阅此 GitHub 问题

看起来 HATEOAS/REST 比简单的 OpenAPI/RPC API 需要更多的工作,而且确实如此。但是,假设设计良好的客户,您在简单性中放弃的东西,您正在获得灵活性和弹性。哪种方法是正确的取决于很多因素,其中大多数不是技术因素(团队技能、预期的消费者、您对客户的控制程度、维护等)。

于 2019-07-29T15:59:55.070 回答