查询(Query)和标识(Identify)

相关文章: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 相关的一些文章:

查询(Query)和标识(Identify)的更多相关文章

  1. SQVI和SAP查询QUERY的区别和使用注意事项

    SQVI.SAP查询QUERY都适用于简单的表连接数据查询,但都不能打包传输到PRD,不同环境需要重复创建.可以生成报表程序供T-CODE调用,用se93指定事务码和程序名称. 区别1-权限: SQV ...

  2. Lucene 查询(Query)子类

    QueryParser(单域查询) QueryParser子类对单个域查询时创建查询query,构造方法中需要传入Lucene版本号,检索域名和分词器. QueryParser parser = ne ...

  3. hibernate在使用sql查询query自动转化成model类型数据,query.addEntity

    hibernate使用自动的hql查询或者其封装的查询方法都能字段转化成对象 而如果在hibernate中使用sql时大多返回为Object[]对象 那么如何将object[]转换成model呢,答案 ...

  4. Mongodb条件查询Query的用法

    Query.All("name", "a", "b");//通过多个元素来匹配数组Query.And(Query.EQ("name ...

  5. elasticsearch 查询 query

    对于 类型是 text的字段,并且分析器指明是ik_max_word的会建立倒排索引 查询的分类: match查询:  会自动转换大小写,会分词, term查询: 不会转换和分词,只能值匹配 term ...

  6. Hibernate查询 Query Language

    1,Native SQL ->HQL->EJBQL->QBC(Query By Cretira)->QBE(Query By Example) 此排列是根据可实现功能大小排序.

  7. 高级查询query

    详细看 https://www.kancloud.cn/ldkt/tp5_db/229042

  8. Solr查询query效果对比

    q条件 默认分词(org.apache.solr.analysis.TokenizerChain) "parsedquery" IK分词(org.wltea.analyzer.lu ...

  9. Elasticsearch(入门篇)——Query DSL与查询行为

    ES提供了丰富多彩的查询接口,可以满足各种各样的查询要求.更多内容请参考:ELK修炼之道 Query DSL结构化查询 Query DSL是一个Java开源框架用于构建类型安全的SQL查询语句.采用A ...

  10. [转]NHibernate之旅(4):探索查询之条件查询(Criteria Query)

    本节内容 NHibernate中的查询方法 条件查询(Criteria Query) 1.创建ICriteria实例 2.结果集限制 3.结果集排序 4.一些说明 根据示例查询(Query By Ex ...

随机推荐

  1. 为Linux用ISO制作U盘启动及基本原理

    制作成功后的基本最简文件夹文件图 一.系统的基本引导流程: 首先系统要引导isolinux.bin可执行程序,此程序是移动介质上引导用的,isolinux.bin执行成功后会载入其配置文件syslin ...

  2. 图像特效——摩尔纹 moir

    %%% Moir %%% 摩尔纹 clc; clear all; close all; addpath('E:\PhotoShop Algortihm\Image Processing\PS Algo ...

  3. 如何解决Android SDK无法下载Package的问题(.net)

    有些用户在安装好Android SDK后,打开Android SDK Manager下载API时一直显示“Done loading packages”却迟迟不能前进,界面显示的Package空空如也. ...

  4. Blend4精选案例图解教程(一):丰富的形状(Shape)资源

    原文:Blend4精选案例图解教程(一):丰富的形状(Shape)资源 Blend4资源面板中内置了丰富的形状素材,为我们在构建程序时提供极大的方便.系统默认内置18种常用形状,通过其属性设置可以自定 ...

  5. 在CentOS下安装配置MySQL(转)

    今天刚把项目做完,需要马上部署到新到的测试服务器上,于是乎,要在服务器上安装环境,由于好久在linux上部署了,手有些生,比较费劲装完,现在把重要步骤贴出,供自己以后参照.1.首先要看看有没有已经安装 ...

  6. 《炉石传说》建筑设计欣赏(6):卡&amp;在执行数据时,组织能力

    上一篇文章我们看到了<炉石传说>核心存储卡的数据,今天,我们不断探索卡&身手. 基本的类 通过之前的分析,卡牌&技能涉及到几个类体系:Entity.Actor.Card.S ...

  7. [Unity3D]Unity3D游戏开发Lua随着游戏的债券(于)

    ---------------------------------------------------------------------------------------------------- ...

  8. UI —— 计算器

    #import <UIKit/UIKit.h> @interface MyViewController :UIViewController { NSInteger _firstName; ...

  9. 用SourceTree轻巧Git项目图解

    用SourceTree轻松Git项目图解 这篇文档的目的是:让使用Git更轻松. 看完这篇文档你能做到的是: 1.简单的用Git管理项目. 2.怎样既要开发又要处理发布出去的版本bug情况. Sour ...

  10. 找呀志_java网络编程(4)TCP/IP、Http和Socket差额

    经java网络编程(1)网络体系结构及通信协议我知道IP协议相应于网络层.TCP协议相应于传输层.而HTTP协议相应于应用层, 三者从本质上来说没有可比性 TPC/IP协议是传输层协议,主要解决数据怎 ...