【问题标题】:How to construct intersection in REST Hypermedia API?如何在 REST 超媒体 API 中构建交集?
【发布时间】:2015-12-31 05:35:59
【问题描述】:

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

问题

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

约束

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

恕我直言,这是一个非常合理的约束,所有超媒体 API 示例看起来都是这样的,即使在 draft-kelly-json-hal-07 中也是如此。

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

示例

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

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

到目前为止,我的建议是使用查询字符串:/categories/9/products?intersect=/companies/9,但我们能做得更好吗?

我为什么要这个?

因为我正在构建一个框架,它将基于 SQL 数据库关系自动生成 REST 超媒体 API。您可以将其视为对SELECT ... JOIN ... WHERE 查询的 URL 的反编译器,但 API 的客户端只能看到超媒体,并且客户端希望有一种很好的方式来进行交叉,就像在示例中一样。

【问题讨论】:

    标签: api rest hateoas hypermedia


    【解决方案1】:

    我认为您不应该总是将 REST 视为数据库表示,这种情况在我看来更像是一种特定功能。我想我会选择这样的:

    /intersection/comments?company=9&product=5

    写完之后我一直在挖掘,这就是我发现的 (http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api):

    有时您确实无法将操作映射到合理的 RESTful 结构。例如,将多资源搜索应用于特定资源的端点并没有真正意义。在这种情况下, /search 将最有意义,即使它不是资源。没关系 - 只需从 API 使用者的角度做正确的事,并确保清楚地记录下来以避免混淆。

    【讨论】:

    • 您是否建议客户不要使用魔术 ID 的 9 和 5?这不是很 RESTful
    【解决方案2】:

    您想要做的是过滤其中一个类别的产品......所以如果我们有,请按照您的示例:

    GET /categories/9/products
    

    上面将返回类别 9 中的所有产品,因此要过滤掉公司 7 的产品,我会使用类似这样的东西

    GET /categories/9/products?company=7
    

    您应该将 URI 视为获取所有数据的链接(就像 SQL 中的简单选择查询),并将查询参数视为 where、limit、desc 等。 使用这种方法,您可以构建复杂且可读的查询 fe。

    GET /categories/9/products?company=7&order=name,asc&offset=10&limit=20
    

    【讨论】:

      【解决方案3】:

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

      这不是 REST 约束。如果您想了解 REST 约束,请阅读Fielding dissertation

      因为我正在构建一个可以自动生成 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
      

      等等……

      【讨论】:

        【解决方案4】:

        RESTful 架构是关于返回包含提供状态转换的超媒体控件的资源。我在这里看到的是一个多步骤的状态转换过程。假设您有一个根资源并使用可用的超媒体控件以某种方式导航到/categories/9/products。我敢打赌,结果在 hal 中会是这样的:

        {
          _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}
             ]
          }
        }
        

        现在客户端只需根据链接的标题字段选择正确的超媒体控件(也称为链接)。

        这是最简单的解决方案。但是您可能会说有 1000 家公司我不想要 1000 条链接……好吧,如果确实如此……您只需在我们拥有的两者中间提供状态转换:

        {
          _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。

        【讨论】:

          猜你喜欢
          • 2012-07-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-31
          • 2014-04-09
          • 2016-09-15
          • 1970-01-01
          • 2013-06-30
          相关资源
          最近更新 更多