RESTful的正确姿势
RESTfull定义
很多人都有这样的疑问:
- 什么是RESTful API/RESTful接口?
- RESTful接口规范是什么样子的?
下面是摘抄自百度百科词条RESTful。
REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,Roy Fielding是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。RESTFUL特点包括:
1、每一个URI代表1种资源;
2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3、通过操作资源的表现形式来操作资源;
4、资源的表现形式是XML或者HTML;
5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
RESTful标准用法实践
这里以一个物流系统为例定义一组接口,这里需要完成的功能如下
- 维护物流运单。用户需要对物流运单进行增删改查操作。每个运单上都有单号,承运商,运输方式等信息。
- 维护发票信息。用户需要管理发票,每个发票都有重量,金额和相关物流运单号。
- 统计不同运输方式、承运商在给定时间的费用信息。
通过需求可以清晰的看到两个资源,物流运单和发票,分别定义为delivery和invoice,根据REST风格的特点,定义如下接口
创建物流运单
REST建议使用POST创建资源
创建物流运单接口 URL:[POST]/delivery Request: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 } Response: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 }
错误码:
200: 正常创建
- 409:单号冲突了
- 422:参数不正确。仅靠错误码那难以给出更为准确的提示,类似:运输方式kongyun错误,有效运输方式为[Air,Sea,Truck]。
查询物流运单
REST建议使用GET来获取资源。
查询所有: URL:[GET]/delivery Response: [ {运单信息}, {运单信息} ]根据ID查询 URL:[GET]/delivery/{delivery-id} Response: {运单信息}多条件查询 URL:[GET]/delivery/?mot={运输方式}&carrier={承运商}... Response: [ {运单信息}, {运单信息} ]
错误码:
- 200: 正常查询
- 404: 查询的资源不存在
- 422: 查询参数不正确
在实际应用中,这个查询被挑战的最多
参数太多难以传递
GET传递参数的常规方式就是URL参数,但URL长度是受限的,难以完成类似一次查询1万单号这种需求。
复杂参数难以传递
GET传递参数的常规方式就是URL参数,但是URL上的参数只能传递结构简单的数据,难以处理对象数组参数,类似[{“carrier”=“c1”, “mot”=“Sea”}, {“carrier”=“c2”, “mot”=“Air”}]这种复杂参数需要特殊处理。对于把文件当做过滤条件的就更处理不了了。
工作量多
后台在处理复杂查询请求时,一般都会有一个表示请求条件的结构,通过URL参数构造这个结构需要额外工作量,前台在发送请求时,将所有参数拼接到URL中也需要工作量,还面临着各种编码问题。
修改物流运单
REST建议使用PUT来获取资源。
修改物流运单接口 URL:[PUT]/delivery/{delivery-id} Request: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 } Response: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 }
错误码:
- 200: 正常修改
- 422: 参数错误。和创建一样无法报告更为准确的错误。
在实际应用中,这个修改操作还面临着批量修改和部分修改的挑战
批量修改
用户希望修改满足条件的所有运单的某些属性为固定值。这个需求很常见,但是REST没说怎么办,当前基本上是开发人员在自由发挥。
部分修改
复杂的业务数据经常涉及到部分修改功能。比如每抵达一站,都会有一个称重员对货物称重,记录称重结果,这个人不关系目的地一类的信息,他只是在这个运单上增加一个称重记录
删除物流运单
REST建议使用DELETE来获取资源。
删除物流运单接口 URL:[DELETE]/delivery/{delivery-id} Response: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 }
实际操作还有批量删除的场景
批量上传物流运单
REST没建议
批量下载物流运单
REST没建议
发票管理接口。参照物流运输单来一套就好了。
统计接口
统计信息是发票和运单的综合数据,我们称他为分摊后发票,是每个发票在单个运单上的金额信息,记为delivery-invoice。在后台可以有一个组件在自动更新。虽然这个接口是一个查询接口,但是查询的内容不是原始数据,是统计数据,不知道应该叫什么URL了。
REST enhance
技术和规范都是服务于业务的,在业务需求的推动下,所有的技术和规范必须与时俱进。针对上面提到的问题,做做了一些调整。
基本规则
不使用HTTP Code返回错误信息
HTTP Code肩负着传输层的功能,让它兼职业务逻辑,其实很为难它。所有的业务返回信息,都放在Response中更为合理。所以定义了一个通用的返回结构
{"error": [], // 使用字符串返回所有严重的影响业务继续执行的错信息"warning": [], // 使用字符串返回所有警告类业务信息"message": ... // 根据业务需要返回需要的信息 }
扩展URI指定的资源
URI仍然用于定位资源,现在资源的范围扩展到原有资源的属性
/delivery // 表示运单资源 /delivery/123 // 表示某一个具体的运单资源 /delivery/123/weight-check // 运单123相关的重量检测记录 /delivery/123/weight-check/1 // 运单123上id为1的重量检测记录 /delivery/mot // 这个不是资源,不能这么写
通过Head扩展不同的Command
在原来的4个Method无法表达的情况下,通过Head中增加REST_Command来扩展功能
REST_Command=BATCH_CREATE // 表示批量创建请求 REST_Command=SEARCH // 表示当前操作是一个查询请求 REST_Command=SEARCH_SUMMARY // 表示查询摘要信息,根据业务需要返回比较少的内容,类似只返回运单基本信息,没有重量检测信息 REST_Command=PART_UPDATE // 部分修改。此时修改操作仅仅修改运单数据,不影响重量检测列表 REST_Command=BATCH_UPDATE // 批量修改。 REST_Command=BATCH_DELETE // 批量删除 REST_Command=GENERATE // 对于没有持久化,而是实时计算出来的结果,使用这个命令
Request为业务参数
业务参数必须和业务相关才是业务参数,类似用户登录信息,用于标记查询、修改一类的命名,都不是业务信息。
Demo
创建物流运单
创建运单
URL:[POST]/delivery Request: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 } Response: {"error": [],"warning": [],"message": {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 } }
创建运单子对象
URL:[POST]/delivery/{delivery-id}/weight-check Request: {"id": 1,"time": "2020-02-02 02:02:02","weight": 234 } Response: {"error": [],"warning": [],"message": {"id": 1,"time": "2020-02-02 02:02:02","weight": 234} }
子对象是必须已存与运单的信息,如果没有了运单,这个信息也没有了。运单的重量检测记录,属于子对象,但是分摊到运单的发票不是,没有运单,发票还在呀。
批量创建
创建物流运单接口 URL:[POST]/delivery Head: REST_Command=BATCH_CREATE Request: { // 这个request可以根据需求做一些定制"doNum": ["12345678", "1234569"],"carrier": "shunfeng","mot": "kongyun","weight": 234 } Response: {"error": [],"warning": [],"message": [{"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 }, ...] }
查询物流运单
查询所有
URL:[GET]/delivery Head: REST_Command=SEARCH;SEARCH_SUMMARY; // 不同的命令返回数据不同 Response: {"error": [],"warning": [],"message": [{运单信息}, {运单信息}] }
根据ID查询单个
URL:[GET]/delivery/{delivery-id} Head: REST_Command=SEARCH;SEARCH_SUMMARY; // 不同的命令返回数据不同 Response: {"error": [],"warning": [],"message": {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234,"weightCheck": [{"id": 1,"time": "2020-02-02 02:02:02","weight": 234}]} }
复杂条件查询
URL:[POST]/delivery Head: REST_Command=SEARCH;SEARCH_SUMMARY; // 不同的命令返回数据不同 Request: {"mot": ["haiyun", "kongyun"],"carrier": ["承运商1", "承运商2"] } Response: {"error": [],"warning": [],"message": [{运单信息}, {运单信息}] }
查询子对象列表
URL:[GET]/delivery/{delivery-id}/weight-check/{check-id} Response: {"error": [],"warning": [],"message": [{重量检测信息}, {重量检测信息}] }
修改物流运单
全量修改运单
URL:[PUT]/delivery/{delivery-id} Request: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 } Response: {"error": [],"warning": [],"message": {运单信息} }
部分修改运单
URL:[PUT]/delivery/{delivery-id} Head: REST_Command=PART_UPDATE Request: {"doNum": "12345678","carrier": "shunfeng","mot": "kongyun","weight": 234 } Response: {"error": [],"warning": [],"message": {运单信息} }
修改运单子对象
URL:[PUT]/delivery/{delivery-id}/weight-check/1 Request: {"id": 1,"time": "2020-02-02 02:02:02","weight": 234 } Response: {"error": [],"warning": [],"message": {"id": 1,"time": "2020-02-02 02:02:02","weight": 234} }
批量修改运单
URL:[PUT]/delivery/ Head: REST_Command=BATCH_UPDATE Request: { // 业务自定义请求内容"doNum": ["12345678", "12345679"]"carrier": "shunfeng","mot": "kongyun","weight": 234 } Response: {"error": [],"warning": [],"message": [{运单信息}] }
删除物流运单
删除操作在删除对象不存在时直接返回,因为期望结果和当前结果一致的。
删除运单
URL:[DELETE]/delivery/{delivery-id} Response: {"error": [],"warning": [],"message": [{运单信息}] }
删除运单子对象
URL:[DELETE]/delivery/{delivery-id}/weight-check/{check-id} Response: {"error": [],"warning": [],"message": [{重量检测信息}] }
批量删除
URL:[POST]/delivery Head: REST_Command=BATCH_DELETE Request: {"mot": ["haiyun", "kongyun"],"carrier": ["承运商1", "承运商2"] } Response: {"error": [],"warning": [],"message": [{运单信息}, {运单信息}] }
批量上传物流运单
批量上传可以按照批量创建的接口做。更新REST_Command=BATCH_UPLOAD。
批量下载物流运单
批量下载可以按照批量查询的接口做。更新REST_Command=DOWNLOAD。
统计接口
统计信息是发票和运单的综合数据,我们称他为分摊后发票,是每个发票在单个运单上的金额信息,记为delivery-invoice。在后台可以有一个组件在自动更新。虽然这个接口是一个查询接口,但是查询的内容不是原始数据,是统计数据,所以命名为delivery-invoice-report。
URL:[POST]/delivery-invoice-report Head: REST_Command=GENERATE Request: { // 自定义查询条件"mot": ["haiyun", "kongyun"],"carrier": ["承运商1", "承运商2"] } Response: {"error": [],"warning": [],"message": {统计结果} }
总结
- URI固定表示资源,子对象也算资源,实时计算的内容使用虚拟资源
- HTTP Code是传输层的内容,不再参与业务逻辑
- 使用公共的Response结构来返回各种错误信息
- 扩展Head,增加REST_Command完成各种多样功能
RESTful的正确姿势相关推荐
- SpringBoot 2.0 开发案例之参数传递的正确姿势
前言 开发这么多年,肯定还有不少小伙伴搞不清各种类型的参数是如何传递的,很多同学都是拿来即用,复制粘贴一把撸,遇到问题还是一脸懵逼. 姿势 学习参数传递的正确姿势,先说怎么做,再说为什么,本质上还是复 ...
- Ubuntu创建新用户的正确姿势
作者按:因为教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步<Ubuntu 创建新用户的正确姿势>原文地址.更欢迎来我的小站看更多原创内容:godbmw.com,进行&q ...
- io在Linux,在Linux进行IO的正确姿势
原标题:在Linux进行IO的正确姿势 很多C/C++程序虽然在做网络编程, 但大多用别人封装好的库, 对底层不甚了解, 感觉 IO 操作不是很简单吗? 我敢说, 大多数人进行 IO 的姿势都不对, ...
- 互联网大厂内推求职的正确姿势?
作者 | 码农唐磊 来源 | 程序猿石头(ID:tangleithu) 背景 每个人的职业生涯基本上都离不开"投简历找工作"这件事(什么,你家里有矿?当我没说),那拿着简历找工作正 ...
- Android获取设备状态栏status bar高度的正确姿势
Android获取设备状态栏高度的正确姿势 正确代码方式: int height = 0;int resourceId = getApplicationContext().getResources() ...
- 开发函数计算的正确姿势——支持 ES6 语法和 webpack 压缩
为什么80%的码农都做不了架构师?>>> 首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算 ...
- 开发函数计算的正确姿势 —— 爬虫
2019独角兽企业重金招聘Python工程师标准>>> 在 <函数计算本地运行与调试 - Fun Local 基本用法> 中,我们介绍了利用 Fun Local 本地运行 ...
- 怎么用linux的HDD存储,Linux学习的正确姿势12:Linux存储概览
原标题:Linux学习的正确姿势12:Linux存储概览 从工作原理区分 机械 HDD 固态 SSD SSD的优势 SSD是摒弃传统磁介质,采用电子存储介质进行数据存储和读取的一种技术,突破了传统机械 ...
- 自学python需要买书吗-学习Python的正确姿势—基础教学,教科书该怎么买?
学习Python的正确姿势-基础教学,教科书该怎么买? 2019-05-07 18:52:46 23点赞 377收藏 4评论 写在前面: 四月读书季,京东图书大促销,赶上优惠的尾巴,抢购了几本心水很久 ...
最新文章
- 客快物流大数据项目(六十二):主题及指标开发
- 安全 - 堡垒机 - Jumpserver
- HighCharts报表 API
- 36.rustc编译参数.txt
- 《大型网站服务器容量规划》一1.1 容量规划背景
- PHP的stdClass
- python编程高手之路——函数调用
- android setCompoundDrawables和setCompoundDrawablesWithIntrinsicBounds区别
- javascript图片隐写术,感觉可以用它来干点有想法的事情
- 2018牛客网暑假ACM多校训练赛(第六场)I Team Rocket 线段树
- 怎么用python下载网易云_如何使用python批量下载网易云音乐的免费音乐
- USB转RS232串口应用
- 2021年T电梯修理免费试题及T电梯修理试题及解析
- 计算机公式求时间差公式,日期差计算(Excel表格中如何计算日期、时间差)
- Annoying day
- java8:新特性及Lamber表达式语法及四大函数接口
- oppo文件管理android在哪里,OPPO手机中缓存视频文件路径在哪里查看?怎么查看?
- 《大明王朝1566》台词摘录
- 机器学习Numpy库入门25例
- RealView MDK开发工具