HATEOAS 和由 API 驱动的表单



我正在尝试将 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"
    }
    
  • 需要电子邮件和航班号(承运商仅处理AIPRORT连接)
    {
    "connectionId": "bbb",
    "email": "passenger@example.org",
    "flightNumber": "EA1234"
    }
    
  • 电话号码必填
    {
    "connectionId": "ccc",
    "phoneNumber": "+44 111 222 333"
    }
    

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

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

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

像由 API 驱动的表单这样的常见问题是否仍然没有用规范和工具解决?

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

我认为脱节是因为按照"现在我们有了可用连接的列表,客户端可以选择一个并创建一个Order"来思考。这是完全有效的,但它是远程过程调用(RPC)思维,而不是REST。客观上,两者都不比另一个更好,除非在一组特定的项目需求的上下文中,并且通常它们不应该混合在一起。

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

使用 REST/HATEOAS 思维方式,正确的方法是公开来自ConnectionOrders集合。集合中的每个Connection都有一个self链接和一个Order集合(链接或对象,由应用要求定义)。每个Order项都有一个self链接,这就是指定提示的位置。Order是客户端可能知道如何定义的已知类型(即使使用 REST/HATEOAS,客户端和服务也必须至少就共享词汇表达成一致)。该词汇表可以使用任何有效的机制来定义 - json-ld,XSD等。

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

例如,在 HAL 表单中,我会做这样的事情:

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

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

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

最新更新