相关文章:RESTful API URI 设计的一些总结

问题场景:删除一个资源(Resources),URI 该如何设计?

应用示例:删除名称为 iPhone 6 的产品。

是不是感觉很简单呢?根据应用示例,我们用代码实现下:

public class ProductsController : ApiController
{
[HttpDelete]
[Route("api/products")]
public async Task<HttpResponseMessage> DeleteByProductName(string productName)
{
...
}
}

客户端调用:

delete /api/products?productName=iPhone 6

网上有关 RESTful API URI 的一些文章,都是一些示例性质的,也就是说并不能真正运用到实际应用中,比如删除一个资源的 URI,它会告诉你应该这样设计比较好:delete /api/products/{productId},productId 是产品的唯一标识,删除资源必须通过唯一标识?示例应用中可以这样,但在实际应用中的场景,往往比示例复杂几十上几百倍,比如我们可以把上面的示例复杂点:

  • 删除某一用户下,名称为 iPhone 6 和系统为 iOS 8 的产品

针对上面的应用示例,我们该如何设计 URI 呢?难道还要坚守“删除资源必须通过唯一标识”?如果真是这样,我们的解决方案就“简单”多了,对,没错,先查询再删除,就这么简单,但总感觉哪里不对,心里有个声音(不应该这样啊,肯定有其他解决方案)。另外,Delete 操作包含 ?productName=iPhone 6,这种方式是不是也感觉很怪,为什么呢?我们一般在设计 MVC URL 或 WebAPI URI 的时候,? 后面一般跟的是查询条件,既然是查询条件,那配合 Delete 操作,就非常“怪”了,我们看一下 GitHub API 中的一个示例:

"user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"

?q={query} 一般用于 URI 的 GET 操作,那能不能用于其他操作呢?我们先来看下 Query 的权威定义

The query component contains non-hierarchical data that, along with data in the path component (Section 3.3), serves to identify a resource within the scope of the URI's scheme and naming authority (if any). The query component is indicated by the first question mark ("?") character and terminated by a number sign ("#") character or by the end of the URI.

文字虽短,但信息量还是非常大的,主要是讲两个内容:Query 是什么?Query 的作用是什么?

non-hierarchical 是非分层结构的意思,也就是 URI 中的 /(前后结构),Query 并不包含非分层结构数据,简单一句话,? 之后并且 # 之前,这部分内容就是 Query,比如 user_search_url 中的 q={query}{&page,per_page,sort,order},那 Query 的作用是什么呢?标识资源(identify a resource),需要注意的是,上面那段话,丝毫没有提到 Get 的字眼,其实,URI 和 HTTP Method 没有半毛钱关系,更不是一一对应关系,这是我自己之前一个很深的误解,要纠正过来。

好,Query 的疑问解开了,我们接下来设计上面应用实例的 URI,大致两种方案:

  • delete /api/users/1/products?productName=iPhone 6&&system=iOS 8
  • delete /api/products?userId=1&&productName=iPhone 6&&system=iOS 8

这两种 URI 设计的不同点就是:第一个 URI 把 User 放在 Path 中,第二个 URI 把 User 放在 Query 中。到底哪种设计方案好呢?关于这个疑问,其实,我也没有确定的想法,然后在网上搜索了大量资料,但都是一些笼统的概念讲解,并没有针对问题的具体分析,偶然看到阮一峰的这篇博文《RESTful API 设计指南》,内容也是一大堆概念和简单示例,但有一个哥们的评论吸引了我的注意(遇到知音了),我直接复制下:


现在正在做一个基于RESTful API 架构的接口。但是遇到了一些令自己很困扰的问题:

假如每一个设备(devices)是拥有多张设备图片(images)的,那么按照RESTful,我们的URI 设计如下:

现在的设计:

  • GET /devices/ID/images:获取某个设备的所有图片
  • GET /devices/ID/images/ID:获取某个设备的某张图片(我们不提供改方法,有人认为没有意义的)
  • POST /devices/ID/images:给指定设备添加一张图片
  • PUT /Images/ID:修改某张图片(有疑问)
  • DELETE /images/ID : 删除某张图片(有疑问)

我觉得合理的设计是:

  • PUT /devices/ID/Images/ID:修改某个设备的某张图片
  • DELETE /devices/ID/images/ID : 删除某个设备的某张图片

看到,对于修改和删除某张图片,我是有疑问的,现在的删除是直接通过图片ID进行删除(不管这张图片属于哪一个设备的)。其实真的符合RESTful标准吗? 他们这样设计的理由是:由于图片本身就有自己的图片ID,为什么直接通过ID来删除呢,还非得指定某个设备的图片,然后再删除。

RESTful 强调资源的唯一性,images/ID其实就是唯一的图片资源,而/devices/ID/images/ID也是设备的唯一图片,那么,我都不知道应该采用哪一个URI的设计。


看到上面是不是有点熟悉的赶脚呢,和我们的应用示例其实存在一样的疑问,但很遗憾,没有人回复这个哥们,我想回复他,但自己都没搞明白,拿什么回复呢?好,既然发现了同病相怜的人,那就更有勇气去搜索了,后来在茫茫的 Google 搜索中,又偶然发现这个帖子:Designing a REST api by URI vs query string.

这位哥们的疑问是,祖父母(外祖父母)下面是父母,父母下面是孩子,那如果去查询孩子,该如何设计 URI 呢?来看这位哥们的三种 URI 设计:

  • GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}
  • GET /myservice/api/v1/parents/{parentID}/children?search={text}
  • GET /myservice/api/v1/children?search={text}&grandparentID={id}&parentID=${id}

关于这个疑问,大神们做了详细的回答,每个回答都可以写成一篇文章了,我大概看了几篇,也是云里雾里的,大家如果有英文好的,可以翻译下这个帖子,我觉得是很有价值的。

下面说一下我自己的体会,首先,URI 中的层级结构,并不一定适用,比如上面那个示例,现在的情况是三级,但实际上是多极的,祖父母上面还有祖祖父母等等,所以,如果非要用层级结构来体现 URI,也并不是可取的,其次,要明确你要请求的资源到底是什么?祖父母、父母、还是孩子?如果是孩子,那么不管是哪种 URI 设计,层级结构中最后的那个词一定是 Childrens,这是肯定的,那如果存在层级结构关系,是设计成 Query?还是 Path 呢?我们看一下某一段回复:

I believe I have already thoroughly beaten this to death, but query strings are not for "filtering" resources. They are for identifying your resource from non-hierarchical data. If you have drilled down your hierarchy with your path by going /person/{id}/children/ and you are wishing to identify a specific child or a specific set of children, you would use some attribute that applies to children you are identifying and include it inside the query.

再强调下,Query 并不是过滤资源,而是 Identify 资源(非层级结构数据),他这样设计的 URI:/person/{id}/children/,我觉得这是一个好的设计,族谱的结构可以再抽象出来,人都有孩子,不管是祖父母,还是父母,这个层级关系是确定的,由确定某一个人,再确定他的孩子,这样更加明确具体,至于之外的标识,都可以放在 Query 中,用来具体标识这个层级结构下的资源,所以,有时候可能不是你的 URI 问题,而是你的 URI 设计问题。

回过头看我们一开始设计的两种 URI,其实我自己觉得都是可以的,用户作为层级结构可以,作为标识资源(Query)也可以,毕竟它们其实没有特别强的层级结构关系,如果有一些层级关系,对于最末端的资源,标识资源中可以进行明确它,比如删图片那哥们的 ImageID,这个就可以直接标识某一个具体的图片,也可以不用 Device 来标识了,这个问题,我自己现在觉得,并没有唯一性,关键看具体的应用场景,和对 RESTful API URI 的理解,但不管怎样,设计出来的 URI,一定要简洁,并让别人看得懂。

我、删图片的那位哥们、还有族谱的那位哥们,所抛出的问题,虽然具体的应用场景不一样,但其实本质上都是一样的,我觉得像这类问题,可以深入探究下,这篇博文就到这。

再补充 REST 相关的一些文章:

RESTful API URI 设计: 查询(Query)和标识(Identify)的更多相关文章

  1. RESTful API URI 设计: 判断资源是否存在?

    相关的一篇文章:RESTful API URI 设计的一些总结. 问题场景:判断一个资源(Resources)是否存在,URI 该如何设计? 应用示例:判断 id 为 1 用户下,名称为 window ...

  2. RESTful API URI 设计的一些总结

    非常赞的四篇文章: Resource Naming Best Practices for Designing a Pragmatic RESTful API 撰写合格的 REST API JSON 风 ...

  3. 好RESTful API的设计原则

    说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间,如有人愿意转载请注明出处,谢谢^_^ P ...

  4. RESTful API的设计原则

    好RESTful API的设计原则   说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间, ...

  5. 好的RESTful API的设计原则

    转载自一位大佬 英文原版 Principles of good RESTful API Design Good API design is hard! An API represents a cont ...

  6. [Medium翻译]RESTful API权威设计指南-设计更好的API

    本文为授权译文.希望查看原文的同学请戳链接:https://hackernoon.com/restful-api-design-step-by-step-guide-2f2c9f9fcdbf 对于我们 ...

  7. [转]10个有关RESTful API良好设计的最佳实践

    Web API已经在最近几年变成重要的话题,一个干净的API设计对于后端系统是非常重要的. 通常我们为Web API使用RESTful设计,REST概念分离了API结构和逻辑资源,通过Http方法GE ...

  8. 10个有关RESTful API良好设计的最佳实践

    Web API已经在最近几年变成重要的话题,一个干净的API设计对于后端系统是非常重要的. 通常我们为Web API使用RESTful设计,REST概念分离了API结构和逻辑资源,通过Http方法GE ...

  9. Restful API的设计与实践

    Restful这个名称应该很多人都不陌生,但是我发现不少人对Restful存在或多或少的理解偏差,其中不泛比较厉害的程序员,所以有必要为Restful来“正名”. Restful是一种软件架构风格,设 ...

随机推荐

  1. JavaScript数组去重的几种方法

    这个老问题,网上普遍都有了,为什么要再写一遍呢,主要是为了记个笔记... 遍历时搜索结果数组 思路很明确,如下 新建一个数组存放结果 循环遍历原数组,对于每一个元素,在结果数组中搜索是否存在 若不存在 ...

  2. Jquery.load() 使用

    调用load方法的完整格式是:load( url, [data], [callback] ) url:是指要导入文件的地址. data:可选参数:因为Load不仅仅可以导入静态的html文件,还可以导 ...

  3. [WPF] 我的WPF自学日记2,自定义入口

    在winform中入口文件就是Program.cs,而在WPF中看不到,因为它是自动生成的,可以说隐藏了,我们可以自定义一个入口文件,然后修改项目属性中的启动对象为我们自定义的入口文件. 首先新建入口 ...

  4. 选择列表中除了第一个li的其他元素

    //选择div中除了第一个li的其他所以li元素 div li:not(:first-child){ }

  5. Html5+NodeJS——拖拽多个文件上传到服务器

    实现多文件拖拽上传的简易Node项目,可以在github上下载,你可以先下载下来:https://github.com/Johnharvy/upLoadFiles/. 解开下载下的zip格式包,建议用 ...

  6. oracle 全文检索创建脚本示例

    --创建全文索引 grant execute on ctx_ddl to username;--使用其他帐号对username授权exec ctx_ddl.create_preference('my_ ...

  7. bgp多线

    BGP(边界网关协议)主要用于互联网AS(自治系统)之间的互联,BGP的最主要功能在于控制路由的传播和选择最好的路由. 中国网通 .中国电信.中国铁通.教育网和一些大的民营IDC运营商都具有AS号,全 ...

  8. Sequence Project Showplan Operator 序列映射运算符

    Sequence Project Showplan Operator 序列映射运算符 序列映射运算符会从一个已经排序的集合里通过不停添加集合里的列执行计算. 运算符根据一个或多个列的值把输入集合分为多 ...

  9. Excel导入导出的业务进化场景及组件化的设计方案(上)

    1:前言 看过我文章的网友们都知道,通常前言都是我用来打酱油扯点闲情的. 自从写了上面一篇文章之后,领导就找我谈话了,怕我有什么想不开. 所以上一篇的(下)篇,目前先不出来了,哪天我异地二次回忆的时候 ...

  10. PMO是什么?如何与其他部门协作配合提高项目成功率?

    许多公司里,有许多IT项目,特别是在软件公司里,许多开发团队并没有运用灵敏开发来进行项目办理.在某些状况下,尤其在一些公司里IT不是很受注重的,只能作为一个事务支撑部分,灵敏团队面对的首要疑问,是缺少 ...