如何在REST超媒体API中构造交集



这个问题与语言无关。让我们不要担心框架或实现,我们只是说一切都可以实现,让我们以抽象的方式来看待REST API。换句话说:我现在正在构建一个框架,但我没有看到任何解决这个问题的方法。

如何为两个独立的REST路径返回集合的交集构建REST URL端点?简短的例子:如何相交/users/1/comments/companies/6/comments ?

约束

所有端点应返回单个数据模型实体或实体集合。

我认为这是一个非常合理的约束,所有超媒体api的例子都是这样的,即使在draft-kelly-json-hal-07中也是如此。

如果你认为这是一个无效的约束,或者你知道一个更好的方法,请告诉我。

假设我们有一个应用程序,它有三种数据类型:products, categoriescompanies。每个公司都可以在他们的个人资料页面上添加一些产品。在添加产品时,他们必须为产品附加一个类别。例如,我们可以像这样访问这类数据:

  • 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->YZ->Y,并且我们想要所有来自XZY,那么我们就有问题了。

到目前为止,我的建议是使用查询字符串:/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/commentsGET /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

最新更新