相关文章: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. C# Listview 数据绑定

    今天搞Winform,有串数据需要绑定到TabControl里面,原来用datatable,组长说这玩意会有问题不让用,菜鸟实在不会,百度查的Listview用法,写了个数组进去绑定 using Sy ...

  2. CSS Reset

    html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, ac ...

  3. Python之路【第六篇】python基础 之面向对象(一)

    一.三大编程范式 1.面向过程编程 2.函数式编程 3.面向对象编程 二.编程进化论 1.编程最开始就是无组织无结构,从简单控制流中按步写指令 2.从上述的指令中提取重复的代码块或逻辑,组织到一起(比 ...

  4. replace实现正则过滤替换非法字符

    html+js结构如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http: ...

  5. 【hihoCoder】1288 : Font Size

    题目:http://hihocoder.com/problemset/problem/1288 手机屏幕大小为 W(宽) * H(长),一篇文章有N段,每段有ai个字,要求使得该文章占用的页数不超过P ...

  6. xml中DTD解析

    DTD的作用是"文档类型的定义" DTD申明始终以<!DOCTYPE开头(开头后空一格). 本标签一共有三种写法 一.内部DTD: <!DOCTYPE 根元素 [ 文档 ...

  7. JDBC的连接和增删改和查找

    package Test2;import java.sql.*;import java.sql.DriverManager;import java.sql.SQLException;public cl ...

  8. 谢欣伦 - OpenDev原创教程 - 蓝牙设备查找类CxBthRemoteDeviceFind

    这是一个精练的蓝牙设备查找类,类名.函数名和变量名均采用匈牙利命名法.小写的x代表我的姓氏首字母(谢欣伦),个人习惯而已,如有雷同,纯属巧合. CxBthRemoteDeviceFind的使用如下: ...

  9. 几种简单的负载均衡算法及其Java代码实现

    什么是负载均衡 负载均衡,英文名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助.通过某种负载分担技 ...

  10. iOS开发系列--C语言之构造类型

    概述 在第一节中我们就提到C语言的构造类型,分为:数组.结构体.枚举.共用体,当然前面数组的内容已经说了很多了,这一节将会重点说一下其他三种类型. 结构体 枚举 共用体 结构体 数组中存储的是一系列相 ...