我希望让我的RESTful API变得非常可预测。在决定使用URI而不是查询参数进行数据分段时,有哪些最佳实践呢?
对于支持分页、排序和分组的系统参数,将它们放在'?'之后是有意义的。但是对于像'status'和'region'这样分割集合的其他属性,如果这些也是查询参数,那么什么时候使用路径参数呢?有何经验之谈?
我希望让我的RESTful API变得非常可预测。在决定使用URI而不是查询参数进行数据分段时,有哪些最佳实践呢?
对于支持分页、排序和分组的系统参数,将它们放在'?'之后是有意义的。但是对于像'status'和'region'这样分割集合的其他属性,如果这些也是查询参数,那么什么时候使用路径参数呢?有何经验之谈?
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
思考这个主题的基本方式如下:
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}
要点在于——思考多态性。
URI应该只由那些永远不会改变且在其整个生命周期内可以唯一标识该资源的部分组成
。 - kravemir分段更具有层次感和美观,但可能会有局限性。
例如,如果您有一个带有三个段的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/vehicles/cars/vehicle-id-1
/vehicles/cars/vehicle-id-1?color=blue&limit=100
这里的 color=blue
和 limit=100
是查询参数,帮助描述我们在到达资源后应该执行什么操作:过滤掉蓝色的,并将结果限制在100个。
我曾设计过一个API,其主要资源为people
。通常用户会请求经过筛选的people
,所以为了防止用户每次都调用/people?settlement=urban
之类的内容,我实现了/people/urban
,后来也容易地加入了/people/rural
。此外,如果将来需要访问完整的/people
列表,这也是可以实现的。简而言之,我的想法是为常见子集添加路径。
从这里得到启发:
常见查询的别名
为了让API体验对于普通消费者更加愉快,考虑将一组条件打包成易于访问的RESTful路径。例如,上面提到的最近关闭的工单查询可以打包为
GET /tickets/recently_closed
一般来说,当资源中存在明显的“层次结构”时,我倾向于使用路径参数,例如:
/region/state/42
/region/state/42/status
示例URL:/rest/{关键字}
这个URL是路径参数的一个示例。我们可以使用@PathParam
来获取这个URL的数据。
示例URL:/rest?keyword=java&limit=10
这个URL是查询参数的一个示例。我们可以使用@QueryParam
来获取这个URL的数据。