我想让我的RESTful API非常可预测。决定何时使用URI而不是使用查询参数分割数据的最佳实践是什么?

对我来说,支持分页、排序和分组的系统参数在'?'之后是有意义的。但是,像“status”和“region”这样的字段或其他属性如何划分您的集合呢?如果这些是查询参数,什么是关于知道什么时候使用路径参数的经验法则?


当前回答

思考这个问题的基本思路是:

URI是唯一标识资源类型的特定实例的资源标识符。像生活中的其他事物一样,每个对象(某种类型的实例)都有一组属性,这些属性要么是时不变的,要么是暂时的。

在上面的例子中,一辆车是一个非常有形的物体,它具有像制造、型号和VIN这样的属性,这些属性永远不会改变,而颜色、悬挂等属性可能会随着时间的推移而改变。因此,如果我们用可能随时间变化的属性来编码URI,我们可能会得到同一个对象的多个URI:

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

若干年后,如果这辆车的颜色变成黑色:

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

注意,car实例本身(对象)并没有改变——只是颜色改变了。让多个URI指向同一个对象实例将迫使您创建多个URI处理程序——这不是一个有效的设计,当然也不直观。

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

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

底线——多态。

其他回答

在REST API中,您不应该过度关注可预测的URI。URI可预测性暗示了对RESTful体系结构的误解。它假设客户端应该自己构造uri,实际上他们不应该这样做。

然而,我假设你不是在创建一个真正的REST API,而是一个“REST启发”的API(比如谷歌Drive)。在这些情况下,经验法则是“路径参数=资源标识”和“查询参数=资源排序”。所以,问题就变成了,你能在没有状态/区域的情况下唯一地识别你的资源吗?如果是,那么它可能是一个查询参数。如果不是,那么它是一个路径参数。

TL;DR: RESTful API设计的最佳实践是,路径参数用于标识一个或多个特定资源,而查询参数用于对这些资源进行排序/筛选。


举个例子。假设您正在为一个名为Car的实体实现RESTful API端点。你可以这样构造端点:

GET /汽车 得到/汽车/:id POST /汽车 /汽车/:id 删除/汽车/:id

这样,在指定要获取的资源时只使用路径参数,但不会以任何方式对资源进行排序/筛选。

现在假设您希望在GET请求中添加按颜色过滤汽车的功能。因为颜色不是资源(它是资源的属性),所以可以添加一个查询参数来执行此操作。你可以像这样将查询参数添加到GET /cars请求中:

获取/汽车?颜色=蓝色

这个端点将被实现,以便只返回蓝色的汽车。

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

GET /cars?color=blue&brand=ferrari

就语法而言,URL名应该全部小写。如果实体名称通常由两个英文单词组成,则应该使用连字符分隔单词,而不是驼峰大小写。

例/两个词

考虑一下“路径”这个词——到达某地的一种方式。路径参数应该描述如何到达您感兴趣的位置/资源。这包括目录、id、文件等。

/vehicles/cars/vehicle-id-1

其中vehicle-id-1为路径参数。

考虑一下“查询”这个词——我认为它是在问一个关于路径的问题,例如,我的路径是蓝色的吗,我的路径是否有100个结果。

/vehicles/cars/vehicle-id-1?color=blue&limit=100

这里color=blue和limit=100是查询参数,它们帮助描述我们在获得资源后应该做什么:过滤掉蓝色的,并将它们限制为100个结果。

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

/region/state/42

如果该单一资源具有状态,则可以:

/region/state/42/status

但是,如果‘region’实际上不是所公开的资源的一部分,它可能属于查询参数之一——类似于分页(如您所提到的)。

有一次我设计了一个API,主要资源是人。通常用户会请求过滤过的人,以防止用户调用/people?每次我都执行了/people/urban,后来我可以轻松添加/people/rural。此外,这允许访问完整的/people列表,如果它以后有任何用处的话。简而言之,我的推理是为公共子集添加一个路径

从这里开始:

常见查询的别名 为了使API体验对普通消费者来说更愉快,可以考虑将一组条件打包到易于访问的RESTful路径中。例如,上面的最近关闭的门票查询可以打包为GET /tickets/recently_closed