我正在尝试将 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 做到这一点?
我检查了不同的规格,这是我从阅读规格中可以看出的:
- HAL & HAL 形式 有
"_templates"
,但模板本身没有 URI。它假定在自链接上运行,在我的例子中是/connections...而不是/orders。 - JSON-LD 我找不到有关表单或模板支持的任何信息。
- JSON-API 我找不到有关表单或模板支持的任何信息。
- 集合+JSON 每个文档最多有一个
"template"
,因此假定集合的所有元素都具有相同的字段,而在我的应用程序中并非如此。 - Siren 看起来
"actions"
适合我的用例,但该项目似乎已经死了,并且没有许多主要语言的支持库。 - CPHL 这个项目似乎已经死了,文档很少,也没有库。
- 离子 对表单有很好的支持,但我找不到任何支持库。看起来它现在只是一个规格。
像由 API 驱动的表单这样的常见问题是否仍然没有用规范和工具解决?
在您的示例中,似乎Connections
是资源。目前还不完全清楚Orders
是否真的是资源。我猜可能是的,但是要有一个Order
你需要一个Client
和Connection
.因此,要创建Order
,您需要公开一个集合,可能来自Client
或Connection
,可能两者兼而有之。
我认为脱节是因为按照"现在我们有了可用连接的列表,客户端可以选择一个并创建一个Order
"来思考。这是完全有效的,但它是远程过程调用(RPC)思维,而不是REST。客观上,两者都不比另一个更好,除非在一组特定的项目需求的上下文中,并且通常它们不应该混合在一起。
使用RPC思维方式,定义了创建顺序方法(例如使用OpenAPI),并且任何客户端都应使用一些带外信息来确定所需的正确形式(即通过阅读OpenAPI规范)。
使用 REST/HATEOAS 思维方式,正确的方法是公开来自Connection
的Orders
集合。集合中的每个Connection
都有一个self
链接和一个Order
集合(链接或对象,由应用要求定义)。每个Order
项都有一个self
链接,这就是指定提示的位置。Order
是客户端可能知道如何定义的已知类型(即使使用 REST/HATEOAS,客户端和服务也必须至少就共享词汇表达成一致)。该词汇表可以使用任何有效的机制来定义 - json-ld,XSD等。
HATEOAS 要求结果包含客户端更新状态所需的所有内容。不能有带外信息(共享词汇表除外)。因此,要解决您的问题,您要么需要公开来自Connection
的Order
集合,要么需要允许通过发布到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
集合。Order
POST 可能需要连接标识符和客户端信息。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更多的工作,而且确实如此。但是你在简单中放弃的东西,你正在获得灵活性和弹性,假设精心设计的客户端。哪种方法是正确的取决于很多因素,其中大多数不是技术因素(团队技能、预期的消费者、您对客户的控制程度、维护等)。