在RESTful API中,何时使用路径参数和查询参数?

482

我希望让我的RESTful API变得非常可预测。在决定使用URI而不是查询参数进行数据分段时,有哪些最佳实践呢?

对于支持分页、排序和分组的系统参数,将它们放在'?'之后是有意义的。但是对于像'status'和'region'这样分割集合的其他属性,如果这些也是查询参数,那么什么时候使用路径参数呢?有何经验之谈?


1
一个类似的问题在这里得到了回答...https://dev59.com/oXA75IYBdhLWcg3wm6ex - Lalit Mehra
8个回答

765

TL;DR: 设计RESTful API的最佳实践是使用路径参数标识特定资源或多个资源,而使用查询参数排序/过滤这些资源


举个例子。假设您正在为一个名为Car的实体实现RESTful API端点。您应该像这样构建端点:

GET /cars
GET /cars/:id
POST /cars
PUT /cars/:id
DELETE /cars/:id

这样,当您指定要获取哪个资源时,只使用路径参数,但这不会以任何方式对资源进行排序/过滤。

现在假设您想要添加按颜色过滤汽车的能力到您的GET请求中。因为颜色不是资源(它是资源的属性),所以可以添加一个查询参数来实现此功能。您可以将该查询参数添加到如下请求GET /cars中:

GET /cars?color=blue

这个端点会被实现为仅返回蓝色汽车。

我们可以使用&符号添加更多过滤参数:

GET /cars?color=blue&brand=ferrari

就语法而言,您的URL名称应全部小写。如果实体名称通常是英语中的两个单词,则应使用连字符分隔这些单词,而不是驼峰式大小写。

例如:/two-words


6
谢谢你的答复,Mike。这是一种清晰简单的方法,值得我点赞。然而,开发人员经常选择“cars/blue”方法,我想知道他们做这样的选择的原因...也许他们决定将路径参数用于必填字段,或者是为了表明数据库是按该分片进行分区。 - cosbor11
6
我不确定他们的推理是什么。老实说,我不同意他们的想法。我认为遵循传统并保持简单是最有意义的。通过这样做,您可以让API的使用者更好地理解他们需要做什么才能访问其功能。 - Mike
5
与其使用cars/1/?color=blue,不妨尝试使用/cars?id=1&color=blue。在每个场景中,您基本上都是在过滤汽车资源。 - mko
14
因为ID为1的车只有一辆,但蓝色的车可能有很多,所以这句话是错误的。这里存在着“身份”和“筛选条件”的区别。 - paul
41
有趣的小知识,“this-is-called-kebab-case”意为“烤肉串式命名法”。 - le3th4x0rbot
显示剩余5条评论

161

思考这个主题的基本方式如下:

URI是资源标识符,可唯一标识资源类型的特定实例。就像生活中的每个物体(它是某种类型的实例)一样,都有一组不变或暂时性的属性。

在上面的例子中,汽车是一个非常具体的对象,具有像制造商、型号和VIN之类的永恒属性,以及可能随时间而变化的颜色、悬挂等属性。因此,如果我们使用可能随时间而变化的属性(暂时性)对URI进行编码,则可能会得到同一对象的多个URI:

GET /cars/honda/civic/coupe/{vin}/{color=red}

多年以后,如果这辆车的颜色改为黑色:

GET /cars/honda/civic/coupe/{vin}/{color=black}

请注意,汽车实例本身(即对象)并未改变,只是颜色发生了变化。拥有指向同一对象实例的多个URI将迫使您创建多个URI处理程序-这不是有效的设计,当然也不直观。

因此,URI应仅由永远不会更改且将在其整个生命周期中继续唯一标识该资源的部分组成。所有可能发生更改的内容都应保留为查询参数,例如:

GET /cars/honda/civic/coupe/{vin}?color={black}

要点在于——思考多态性。


4
有趣的范例.. 这是常见的设计模式吗?你能提供一些在文档中使用此模式的 API 或一些概述此策略的参考资料吗? - cosbor11
2
我喜欢你在写“URI是唯一标识特定资源类型实例的资源标识符”时强调了“类型”。我认为这是一个重要的区别。 - jmrah
我认为路径变量可以帮助参数更加清晰易懂,从而使代码更具可读性。 - Jonathan Hagen
3
这是REST API设计中非常重要的一点和规则:URI应该只由那些永远不会改变且在其整个生命周期内可以唯一标识该资源的部分组成 - kravemir

30
在REST API中,你不应过于关注可预测的URI。URI可预测性的建议暗示了对RESTful架构的误解。它假设客户端应该自己构建URI,而实际上他们不应该这样做。
然而,我假设您并非在创建真正的REST API,而是一个“受REST启发”的API(例如Google Drive)。在这些情况下,经验法则是“路径参数=资源标识”,“查询参数=资源排序”。因此,问题变成了:能否在没有状态/区域的情况下唯一地标识您的资源?如果是,则可能是查询参数。如果否,则是路径参数。

36
我不同意,一个好的API应该是可预测的;无论是RESTful还是其他类型的。 - cosbor11
4
我同意。URI的形式应该有一定的规律,而不是任意命名端点。如果可以直观地编写API客户端而不需要不断参考文档,那么在我看来你已经编写了一个好的API。 - cosbor11
4
当一个人可以在不经常参考文档的情况下直观地编写API客户端时,这就是我认为我们对REST理解的不同之处... API客户端永远不应该需要“构建”URL。他们应该从先前API调用的响应中选择它。如果以网站为类比... 您进入facebook.com,然后选择到事件页面的链接。您不关心facebook事件URL是否“可预测”,因为您不会输入它。您通过超媒体链接到达那里。REST api也是如此。因此,将URI对您(服务器)有意义,但不对客户端有意义。 - Oliver McPhee
2
添加注释。这并不意味着URI不应该遵循易于理解的模式,它只是RESTful API的约束条件之一。这个领域最大的问题是人们认为客户端应该自己构建URLs。他们不应该这样做,因为这会在客户端和服务器之间创建一种不应存在的耦合。(例如-服务器不能更改URL而不破坏所有客户端应用程序)。在REST API中,服务器可以随意更改它们。 - Oliver McPhee
10
对于使用以下词汇表示认证和排序资源我给予肯定:“'path params = resource identification'”和“'query params = resource sorting'”。这真正帮助我澄清了这一点。 - Doug
显示剩余3条评论

14

分段更具有层次感和美观,但可能会有局限性。

例如,如果您有一个带有三个段的URL,每个段传递不同的参数来搜索汽车制造商、型号和颜色:

www.example.com/search/honda/civic/blue

这是一个非常漂亮的URL,更容易被最终用户记住,但现在您被限定于这种结构。假设您想让用户能够搜索所有蓝色汽车或所有本田思域,查询参数可以解决此问题,因为它提供了一个键值对。所以您可以传递:

www.example.com/search?color=blue
www.example.com/search?make=civic

现在,您可以通过它的键 - "color"或"make"在您的查询代码中引用该值。

您可以使用更多的段来创建一种键值结构,例如:

www.example.com/search/make/honda/model/civic/color/blue
希望那样说起来有意义..

我认为这就是资源的概念所在。制造商和颜色不是资源,而/cars则是一个资源。如果您有不同的车库销售汽车,那么/cars/[garage]/就是一个资源。然后,您可以在所有汽车中搜索制造商和颜色,或在车库中搜索制造商和颜色。 - icc97
在URL路径中提供键值过滤参数是一种不好的设计。这就是查询字符串的作用。URL路径更适合于分层查询。 - xyres

10
考虑单词“路径”-一种到达位置的方式。 路径参数应描述如何访问您感兴趣的位置/资源,包括目录、ID、文件等。
/vehicles/cars/vehicle-id-1

在这里,“vehicle-id-1”是一种路径参数。
考虑词“查询” - 我认为它是关于路径问问题的,例如我的路径是否是蓝色的,我的路径是否有100个结果。
/vehicles/cars/vehicle-id-1?color=blue&limit=100

这里的 color=bluelimit=100 是查询参数,帮助描述我们在到达资源后应该执行什么操作:过滤掉蓝色的,并将结果限制在100个。


10

我曾设计过一个API,其主要资源为people。通常用户会请求经过筛选的people,所以为了防止用户每次都调用/people?settlement=urban之类的内容,我实现了/people/urban,后来也容易地加入了/people/rural。此外,如果将来需要访问完整的/people列表,这也是可以实现的。简而言之,我的想法是为常见子集添加路径。

这里得到启发:

常见查询的别名

为了让API体验对于普通消费者更加愉快,考虑将一组条件打包成易于访问的RESTful路径。例如,上面提到的最近关闭的工单查询可以打包为GET /tickets/recently_closed


添加一个通用子集的路径是有意义的! - undefined

3

一般来说,当资源中存在明显的“层次结构”时,我倾向于使用路径参数,例如:

/region/state/42

如果这个单一的资源有状态,那么可以:
/region/state/42/status

然而,如果“region”不是资源的实际部分,那么它可能属于查询参数之一——类似于分页(如您所提到的)。

-11

示例URL:/rest/{关键字}

这个URL是路径参数的一个示例。我们可以使用@PathParam来获取这个URL的数据。

示例URL:/rest?keyword=java&limit=10

这个URL是查询参数的一个示例。我们可以使用@QueryParam来获取这个URL的数据。


10
问题是“何时”使用它们而不是“如何”使用它们。另外,@PathParam和@QueryParam是语言特定的,因此只对某些人有帮助。 - Simon Taylor

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