REST API设计 - 如何通过REST使用不同的参数获取资源但保持相同的URL模式

144

我有一个与REST URL设计相关的问题。我在这里找到了一些相关帖子:同一资源的不同RESTful表示按不同字段获取资源的RESTful URL,但是回答并没有很清楚地阐明最佳实践和原因。下面是一个例子。

我有用于表示“用户”资源的REST URL。我可以通过id或电子邮件地址获取用户,但URL表示对于两者都保持不变。通过阅读很多博客和书籍,我看到人们一直以许多不同的方式进行这样做。例如:

我在一本书中和StackOverflow的某个地方阅读过这个做法(我似乎再也找不到那个链接了)。

GET /users/id={id}
GET /users/email={email}

阅读此练习在许多博客上都可以找到。

GET /users/{id}
GET /users/email/{email}

查询参数通常用于过滤由URL表示的资源的结果,但我也看到过这种做法被使用。

GET /users?id={id}
GET /users?email={email}

在所有这些实践中,哪一种对于使用API的开发人员来说最有意义?我相信在REST URL设计和命名约定方面没有规定的硬性规则,但我想知道我应该采取哪种方式来帮助开发人员更好地理解API。


2
我知道这有点老了,但是在寻找类似资源时,我偶然发现了这个问题和你所寻找的那一个。我相信这就是它:https://dev59.com/w3RA5IYBdhLWcg3wvgob#9743414。 - Joel Berger
3个回答

162
在我的经验中,GET /users/{id} GET /users/email/{email} 是最常见的方法。如果提供的idemail不存在用户,则我也会期望这些方法返回一个404 Not Found。我不会惊讶看到 GET /users/id/{id} (尽管在我看来,它是多余的)。
对于其他方法的评论:
  1. GET /users/id={id} GET /users/email={email}
    • 我认为我从未见过这种方式,即使我看到了,这可能会非常令人困惑。这几乎就像是试图使用路径参数模仿查询参数。
  2. GET /users?id={id} GET /users?email={email}
    • 当你提到使用查询参数进行过滤时,我认为你说得很准确。
    • 是否有意义调用此资源并同时传递idemail(例如GET /users?id={id}&email={email})?如果没有,我将不会使用这样的单一资源方法。
    • 我期望使用此方法检索用户列表,并可使用可选的查询参数进行过滤,但我不期望idemail或任何唯一标识符是其中之一。例如:GET /users?status=BANNED 可能会返回被禁止的用户列表。

查看来自相关问题的此回答


谢谢您的输入。实际上,您是正确的。看起来我在《RESTful java with Jax-rs》一书中读到了#1,但有些超出了上下文。但我非常清楚地记得在stackoverflow本身的一个问题的答案中读到过这个(相信我,我正在努力寻找那个链接,以向我的同事证明 :)),而#2正是我所需要的。对设计的一些反馈。谢谢! - shahshi15
7
我的回答是,引用特定用户的适当方式是使用/users/{id}。如果您想通过特定电子邮件地址查找用户,但该地址不是唯一标识符,则应使用/users?email={email},因为这是一种过滤用户集合的方法,而不是像/users/{id}那样用于识别特定资源的资源标识符。 - Kevin M
1
如果我有用户ID列表,假设我有5个用户ID,那么端点会是什么样子。 - Ankit Kumar
6
使用REST的做法不好,users/email 应该是一个返回邮件列表的端点,而 users/email/{email} 只应该返回单个电子邮件或 NotFound。相较于路径变量,查询参数更加适合。 - Nik Kashi
2
我很惊讶这是被验证的答案。我同意@mohsen-kashi,GET users/emails 应该返回电子邮件资源。我也会使用查询参数。 - user108828
显示剩余5条评论

53

从实用的角度来看,你有一组用户:

/users   # this returns many

每个用户都有一个专用的资源位置:

/users/{id}    # this returns one

您还有许多搜索用户的方法:

/users?email={email}
/users?name=*bob*

由于这些都是对/users的查询参数,它们都应该返回列表,即使只有一个元素也是如此。

我在这里写了一篇关于实用RESTful API设计的博客文章,其中讨论了这个问题和其他相关内容:http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api


谢谢回复,Vinay。但如果我们从另一个角度来看待这个问题,{email} 就像 {id} 一样,始终只会返回一个结果。如果我们真的在搜索,为什么不像对待电子邮件那样使用 ?id={id} 呢?我想知道是否有一种方法能保持一致性。 附注:我是 stackoverflow 的新手,不知道我只能给一个答案点赞。但还是感谢你的回复!感激不尽。也许你可以帮我解决这个问题:http://stackoverflow.com/questions/20508008/scim-endpoints-and-other-extended-resources-in-scim - shahshi15
9
这就是 API 设计中 “设计” 方面的作用。您的资源应该有一个专门的位置。这个位置在哪里?位置是按 id 吗?如果是这样的话,那么你不是通过 id 进行“搜索”,而是通过 id 进行“获取”。但你可以通过其他方式进行搜索。当然,这里没有硬性规定。这只是一个观点 :) - Vinay Sahni
7
我认为采用这种方式设计API会更加稳定。你确实知道ID是唯一的,但你对电子邮件地址真的、真的很确定吗?即使它是唯一的,用户也可以更改电子邮件地址,因此/users?email={email}清楚地表明这是一个查询,而不是访问具有永久标识符的资源。 - lex82
我相信这实际上是更正确的答案。按电子邮件地址过滤集合可能是通配符基础的,因此它不总是只返回一个结果。 - Kevin M
@VinaySahni 你对这样的模式有什么看法: /user?by=email&email="abc@pqr.com" /user?by=id&id="xashkhx" /users?filterA="a"&filterB="b"(用于多个用户的复数形式) - Shasak
@shahshi15(抱歉打扰,但是)你不能说一个电子邮件地址总是只对应一个用户。/users/id中的ID是唯一标识符,而其他任何属性如名称或电子邮件地址都只是属性。唯一保证唯一的是ID(也许你允许多个用户共享一个电子邮件地址,比如小办公室、用户创建测试账户、家庭等)。 - DaveD

14

关于用户资源

- 单例资源

在路径/users/[user_id]中,你可以期望发生几件事情,例如:

  • 如果你没有进行身份验证,且只有经过身份验证的用户才能访问此用户资源,那么会返回未经授权的401响应。
  • 如果你没有被允许访问请求的用户资源,则会返回禁止的403响应。
  • 如果不存在带有[user_id]的用户资源,则会返回未找到的404响应。
  • 但是,在成功的200响应中,你应该得到一个表示具有标识符[user_id]字段的用户资源的单例资源。

每个单例都由其路径和标识符唯一标识,并且你可以使用它们来查找资源。不能使用多个路径来表示相同的单例。

- 集合资源

在路径/users上,你将始终返回一组用户资源。

你可以使用查询参数(GET参数)查询路径/users。这将返回一个满足请求条件的用户集合。返回的集合应包含用户资源,所有这些资源都具有其标识资源路径。搜索参数可以是集合资源中存在的任何字段;firstNamelastNameid

例如:

/users?id=1
/users?firstName=John
/users?lastName=Doe

或者甚至以上所有选项的组合:

/users?firstName=John&lastName=Doe

集合响应始终是一个数组。该数组可以为空(无匹配项),包含一项(确定性),或者包含多个项(不确定性)。

关于电子邮件

电子邮件可以是用户资源的资源或属性/字段。

- 作为用户属性的电子邮件:

如果该字段是用户的属性,用户响应将类似于以下内容:

{ 
  id: 1,
  firstName: 'John'
  lastName: 'Doe'
  email: 'john.doe@example.com'
  ...
}

这意味着没有专门的电子邮件终结点,但您现在可以通过发送以下请求按其电子邮件查找用户: /users?email=john.doe@example.com (假设电子邮件唯一对应于用户),将返回一个匹配电子邮件的用户项集合。

- 电子邮件作为资源:

但如果来自用户的电子邮件也是资源。那么您可以创建一个API,其中/users/[user_id]/emails返回具有id user_id的用户的电子邮件地址集合。 /users/[user_id]/emails/[email_id]返回用户ID和['email_id']的电子邮件。您用作标识符的是什么取决于您,但我会坚持使用整数。您可以通过向标识要删除的电子邮件的路径发送DELETE请求来从用户中删除电子邮件。例如,在/users/[user_id]/emails/[email_id]上进行DELETE将删除由用户ID拥有的带有email_id的电子邮件。最有可能只允许拥有资源的用户执行此删除操作。其他用户将获得403响应。

如果用户只能有一个电子邮件地址,则可以坚持使用/users/[user_id]/email。 这将返回一个单例资源。用户可以通过在该URL上PUT电子邮件地址来更新其电子邮件地址。


肯定符合REST原则。 - Yahya Alshaar
在��看来,这应该是经过验证的答案! - user108828

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