这个问题与语言无关。让我们不要担心框架或实现,我们只是说一切都可以实现,让我们以抽象的方式来看待REST API。换句话说:我现在正在构建一个框架,但我没有看到任何解决这个问题的方法。
如何为两个独立的REST路径返回集合的交集构建REST URL端点?简短的例子:如何相交/users/1/comments
和/companies/6/comments
?
约束
所有端点应返回单个数据模型实体或实体集合。
我认为这是一个非常合理的约束,所有超媒体api的例子都是这样的,即使在draft-kelly-json-hal-07中也是如此。
如果你认为这是一个无效的约束,或者你知道一个更好的方法,请告诉我。
假设我们有一个应用程序,它有三种数据类型:products
, categories
和companies
。每个公司都可以在他们的个人资料页面上添加一些产品。在添加产品时,他们必须为产品附加一个类别。例如,我们可以像这样访问这类数据:
-
GET /categories
将返回所有类别集合 -
GET /categories/9
将返回id为9的类别 -
GET /categories/9/products
将返回id为9的类别内的所有产品 -
GET /companies/7/products
将返回id为7的公司的个人资料页面中添加的所有产品
我故意省略了_links
超媒体部分,因为它很简单,例如/
将_links
提供给/categories
和/companies
等。我们只需要记住,通过使用超媒体,我们正在遍历关系图。
如何编写URL返回:所有来自公司(7)和类别(9)的产品?换句话说,如何使/categories/9/products
和/companies/7/products
相交?
假设所有端点都应该表示数据模型资源或它们的集合,我相信这是REST超媒体API的一个基本问题,因为在遍历超媒体API时,我们是沿着一条路径遍历关系图,所以不可能描述这种交集,因为它是两个独立图路径的横截面。
换句话说,我认为我们不能只用一条路径来表示两条独立的路径。通常我们遍历A->B->C
这样的一条路径,但是如果我们有X->Y
和Z->Y
,并且我们想要所有来自X
和Z
的Y
,那么我们就有问题了。
/categories/9/products?intersect=/companies/9
,但我们能做得更好吗?为什么我想要这个?
因为我正在构建一个框架,它将基于SQL数据库关系自动生成REST超媒体API。您可以将其视为url到SELECT ... JOIN ... WHERE
查询的转换编译器,但是API的客户端只看到超媒体,并且客户端希望有一种很好的方式来进行交叉,就像示例中一样。
我认为您不应该总是将REST视为数据库表示,这种情况在我看来更像是一种特定的功能。我想我会这样写:
/intersection/comments?company=9&product=5
我写完后一直在挖掘,这是我发现的(http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api):
有时候,您确实没有办法将操作映射到一个合理的RESTful结构。例如,将多资源搜索应用于特定资源的端点实际上没有意义。在这种情况下,/search是最有意义的,即使它不是资源。这是可以的——只要从API使用者的角度做正确的事情,并确保它被清楚地记录下来,以避免混淆。
您要做的是在一个类别中过滤产品…所以按照你的例子,如果我们有:
GET /categories/9/products
上面的将返回类别9中的所有产品,因此要过滤出公司7的产品,我将使用如下命令
GET /categories/9/products?company=7
你应该把URI作为获取所有数据的链接(就像SQL中简单的select查询)和查询参数作为where, limit, desc等。使用这种方法,您可以构建复杂且可读的查询。
GET /categories/9/products?company=7&order=name,asc&offset=10&limit=20
所有端点应返回单个数据模型实体或集合实体。
这不是REST约束。如果你想了解REST约束,那么请阅读Fielding的论文。
因为我正在构建一个将自动生成REST的框架基于SQL数据库关系的超媒体API。
这是一个错误的方法,与REST无关。
REST通过在响应中发送超链接来描述可能的资源状态转换(或操作调用模板)。如果您使用HTTP和URI标准构建统一接口(我们通常这样做),这些超链接由HTTP方法和URI(以及其他现在不相关的数据)组成。uri(不一定)是数据库实体和集合标识符,如果您应用这样的约束,您将最终使用CRUD API,而不是REST API。
如果你不能用HTTP方法和现有资源的组合来描述一个操作,那么你需要一个新的资源。
在您的示例中,您希望聚合GET /users/1/comments
和GET /companies/6/comments
响应,因此您需要使用GET和第三个资源定义一个链接:
GET /comments/?users=1&companies=6
GET /intersection/users:1/companies:6/comments
GET /intersection/users/1/companies/6/comments
等等……
RESTful架构是关于返回包含提供状态转换的超媒体控件的资源。我在这里看到的是一个多步骤的状态转换过程。让我们假设您有一个根资源,并使用可用的超媒体控件以某种方式导航到/categories/9/products
。我敢打赌结果会像这样:
{
_links : {
self : { href : "/categories/9/products"}
},
_embedded : {
item : [
{json of prod 1},
{json of prod 2}
]
}
}
如果您希望您的客户端能够将此集合与另一个集合相交,则需要向他们提供执行此操作的机制。你必须给他们一个超媒体控制。HAL只有链接、模板化链接和嵌入控件类型。让我们使用链接,将响应更改为:
{
_links : {
self : { href : "/categories/9/products"},
x:intersect-with : [
{
href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 1",
title : "Company 6 products"
},
{
href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 2",
title : "Company 5 products"
},
{
href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 3",
title : "Company 7 products"
}
]
},
_embedded : {
item : [
{json of prod 1},
{json of prod 2}
]
}
}
现在客户端只是根据链接的标题字段选择正确的超媒体控件(又名链接)。
这是最简单的解决方案。但你可能会说,有成千上万的公司,我不想要成千上万的链接……好吧,如果真是这样的话……你只需要在两者之间提供一个状态转换:
{
_links : {
self : { href : "/categories/9/products"},
x:intersect-options : { href : "URL to a Paged collection of all intersect options"},
x:intersect-with : [
{
href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 1",
title : "Company 6 products"
},
{
href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 2",
title : "Company 5 products"
},
{
href : "URL IS ABSOLUTELY IRRELEVANT!!! but unique 3",
title : "Company 7 products"
}
]
},
_embedded : {
item : [
{json of prod 1},
{json of prod 2}
]
}
}
看到我做了什么了吗?用于额外状态转换的额外控件。就像你有一个网页一样。你可能会把它放在一个弹出窗口,这也是你的应用程序的客户端对那个控件的结果所能做的。
就这么简单…只要想想如何在HTML中做同样的事情。
这里最大的好处是,客户端永远不需要知道一个公司或类别id或曾经插入到一些模板。id是实现的细节,客户端永远不会知道它们的存在,它们只是执行超媒体控件,这就是RESTful