单个调用中多个集合的REST API设计最佳实践

4

我有两个资源/users/products,我可以通过/users/{id}/products获取用户的产品。这很简单,我会返回以下JSON响应:

{
   "items": {
      ...
   }
}

如果我想同时获取用户和他们的产品,该怎么办?我知道可以在/users/{id}调用中使用?expand=products,并返回以下数据:

{
   "item": {
      ...
      "products": {
          ...
      }
   }
}

但这是最佳实践吗?还是返回类似于以下内容更好:
{
   "user": {
      "item": {
         ...
      }
   }
   "products": {
      "items": {
         ...
      }
   }
}

所以我的问题是:

  1. 返回多个数据集的最佳实践是什么?
  2. 我知道减少调用API的数量是更好的做法,但如果我的应用程序在某个时间点需要大量信息,例如用户产品货币等,使用它们各自的API端点并进行多次调用是否更好,还是创建一个端点一次性返回所有数据?

1
使用它们各自的API端点并进行多次调用,还是创建一个端点,在一次调用中返回所有数据,哪种做法更好?如果您正在检索彼此不相关的多个集合,请使用多个调用。 - Tim
谢谢@TimCastelijns - 所以在我的例子中,userproducts应该在同一个调用中,而currencies应该在另一个调用中吗? - Wasim
1
是的,那听起来很合乎逻辑。 - Tim
3个回答

3
我认为/users/{id}/products/users/{id}?expand=products是相同的,因此建议使用第一个(无查询字符串)以保持URL的一致性。然后以以下格式返回数据:
{
    username: "Dave",
    userid: 123,
    products: [
        {
            productname: "amazing product",
            productid: "ab123"
        }
    ]
}

用户的产品是用户的子数据。
对于问题2,我应该使用多个端点还是只用一个,取决于您构建该资源的方式(如果它被良好索引/缓存,可能会更“便宜”),是否存在安全风险,您实际使用每个项目的数量等。

感谢您的回答,非常感激。实际上,/users/{id}/products 只返回用户的产品,但是 /users/{id}?expand=products 返回用户并展开他们的产品。这难道不是它应该工作的方式吗?或者 /users/{id}/products 应该同时返回用户和产品? - Wasim
1
我会这么做,因为添加用户数据的成本比进行第二次请求要便宜,而且大多数情况下,您显示用户产品的地方也应该显示一些用户细节。 - Adam Hopkinson

1
通常包含其他数据的方法类似于以下方式(您的第二个示例):
{
    "users: [
        {
            ...
            "products": [
                {
                    ...
                }
            ]
        }
    ]
}

如果你通常将结果包装在一个“items”容器中,我只会为用户这样做,但是我已经看到两者都被包装在容器中。我不认为有真正的共识,但我会倾向于第一种方法。另外一点,因为你在问题中提到它,我不会在“item”/“items”之间切换,因为这会破坏拥有数据的通用容器的目的。相反,始终使用items,如果只有一个项目,请将其作为数组返回。
是否应该包含items取决于情况。由于对包含的项进行分页不可行,当可能返回大量项时,我倾向于不允许包含。对于你的例子(用户拥有的产品),如果它意味着来自供应商的产品(例如在目录中),我可能不会使用它,但如果它是某种购物车,那对我来说就没问题了。

谢谢你的回答。那么你认为即使我只获取一个user,我也应该将其包装在items容器中,而不是item - Wasim
1
没错。就我所理解的“items”这个概念而言,它的作用是始终在相同节点上访问数据。如果您必须检查响应中是否存在关键项或多个项,则会在某种程度上失去这一点。使用“data”可能是一种解决方法。根据API消费者所使用的语言,这可能会有些麻烦,因为您可能会得到一个项目列表或单个项目。这就是为什么我喜欢在使用容器对象时总是返回列表的原因。另外,我发现看“最佳实践”并不应该是主要问题,而应该关注您的主要消费者。 - dbrumann

0

我认为你问题的答案可能在你提到的视频关联的slideshare的第75到77张幻灯片中(http://www.slideshare.net/stormpath/elegant-rest-design-webinar)。

我相信在这种情况下另一个好用的(类似)方法是定义一个更高层次的复合资源(例如,“账户”,“用户产品”等),其中包括你需要的两个子资源,就像它们的容器一样。例如:

{
    "account" : {
       "id" : "123",
       "user": {
          "items": {
             ...
          }
       }
       "products": {
          "items": {
             ...
          }
       }
    }
}

他们做的事情与slideshare中看到的类似,他们创建“容器”资源,但不是嵌入集合数据,而是提供指向各个集合的链接。

但我不确定多个集合是否适合您提供的示例。在您的情况下,我认为从/users/{id}/products返回整个产品列表,并简单地标记实际由该用户拥有的产品(将非所有权产品解释为对该用户可用),可能是合适的:

{
    "user" : {
       "id" : "123",
       "products": [{
          "id" : "A456",
          "owns" : true,
          "details": {
             ...
          }
       }]
    }
}

您甚至可能想要控制结果中是否出现“非所有者”项目,例如:

/users/{id}/products?ownership=all

换句话说,“给我列出给定用户可用的产品列表,并标记他们实际拥有的产品。”
/users/{id}/products?ownership=owned

同样地,“仅给我实际拥有的产品”。
在基于资源的REST架构风格中,查询参数适用于选择(也称为搜索;哪些行),分页(多少),排序(哪个顺序)和投影(哪些列)。
“所有权”查询参数适合于选择/搜索类别。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接