用产品思维设计API(一)——RESTful就是个骗局
来源:互联网 发布:如何登陆香港的淘宝网 编辑:程序博客网 时间:2024/06/03 22:44
用产品思维设计API(一)——RESTful就是个骗局
前言
最近公司内部在重构项目代码,包括API方向的重构,期间遇到了很多的问题,不由得让我重新思考了下。
- 一个优雅的API该如何设计?
- 前后端分离之后,API真的解耦分离了吗?
- 不断的版本迭代,API的兼容性该如何做?
年前,我司内部的接口已经进入了一个完全的重构阶段,参考了市面上各大平台的API和文档,自己也总结出了很多的心得。这里向大家分享一下,接下来一个月,我们向从下面几个方面向大家介绍一个优雅的API(至少我认为挺优雅)该如何设计。
- RESTful就是个骗局 (http://blog.csdn.net/yzzst/article/details/53775319)
- 数据解耦,才是前后分离的本质(http://blog.csdn.net/yzzst/article/details/53844590)
- 版本控制,没有你想的这么简单(http://blog.csdn.net/yzzst/article/details/54755077)
- 随意定义错误码,你还在这样干?(http://blog.csdn.net/yzzst/article/details/54799971)
- 安全,就只能用HTTPS?(http://blog.csdn.net/yzzst/article/details/54882346)
ps. 打一个广告,公司内部现在在招聘各种技术岗位,Java、Android、前端等,待遇保证能让你涨30%,有兴趣的朋友可以加我微信,二维码在文章最后。
Ok,今天是第一篇文章——再看RESTful。
回顾一下HTTP协议
基于HTTP协议的API使我们在开发APP、网站中最常见的形式,为了更好的了解如何设计一个良好的API,我们这里先简单的回顾一个HTTP协议。
先抓包看一个请求demo
我们用Fiddler抓了一下360浏览器的任务中心的API接口信息,如下是它的请求信息:
POST http://task.browser.360.cn/online/setpoint HTTP/1.1Accept: */*User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)Pragma: no-cacheContent-Type: application/x-www-form-urlencodedHost: task.browser.360.cnContent-Length: 449Cookie: T=s%3Ddaf1a2e6347e01ebccc72d639441f9ef%26t%3D1456561881%26lm%3D%26lf%3D1%26sk%3D03714c868adc12684c89a65c05bc7709%26mt%3D1466821360%26rc%3D4%26v%3D2.0%26a%3D1; zsmodel=MI%20NOTE%20LTE; zsosv=4.4.2; __guid=243694361.1257306006931263000.1482113838601.9001stamp=1482151473&qt=Q%3Du%3Dfgpubh%26n%3D%25PO%25QR%25P9%25R1%25P0%25RS%25O5%25P4%25PO%25N7%25O9%25S8%26le%3Dp3EwnT91WGDjZGLmYzAioD%3D%3D%26m%3DZGZlWGWOWGWOWGWOWGWOWGWOZwDl%26qid%3D29340825%26im%3D1%5Ft013ba372ccf308e7b6%26src%3D360se%26t%3D1%0D%0AT%3Ds%3D76f8c61854f171f63f66a8f552962e8e%26t%3D1456561881%26lm%3D%26lf%3D1%26sk%3De79c0ecb263d447d96e85f23825c3924%26mt%3D1466821360%26rc%3D9%26v%3D2%2E0%26a%3D1&verify=6fce1222e64cefa2b5b3d24d65fa9eb1
得到的服务器响应结果,如下所示:
HTTP/1.1 200 OKServer: nginx/1.6.3Date: Mon, 19 Dec 2016 12:42:58 GMTContent-Type: text/html; charset=utf-8Transfer-Encoding: chunkedConnection: close{"errno":"0","errmsg":"success","lastpoint":"2016-12-19-20-42"}
这里,没有任何文档我们也能够直接的看出来。大概步骤是:
1. 浏览器向 http://task.browser.360.cn/online/setpoint 发起了一个 POST请求
2. 该请求中附带了一些cookie信息,也带了一些自定义的消息体
3. 响应结果是正确的,返回给浏览器一个JSON格式的数据。
Http协议的构成
对于一个完整的请求(Request)、响应(Response)来说,还是有一定的套路的,这里我们看一下HTTP请求和响应的规范格式。
对于上面360浏览器的demo,结合两个协议图文,我们能够看到更多的信息:
- 请求Request
- 请求的方式 POST
- 请求的地址 URL
- 版本号
- 请求的头部信息,headers(cookie\UA等在于此处存着)
- 附属体信息 (通常为自定义上传的信息)
- 响应Response
- 版本号
- 状态码 (http协议的状态码)
- 相应头部信息headers (时间、数据格式、编码等信息)
- 附属体信息(通常为相应的自定义数据体)
OK,既然我们已经了解了HTTP协议的请求与响应的构成,理论上我们已经可以利用上述的逻辑加入完成自己的API设计了。
设计之前,我们需要思考一下,我们需要设计吗?目前市面上最流行的RESTful API协议为什么我们不用?
再看RESTful
在API设计上,如果有一样东西获得广泛认可的话,那就是 RESTful 原则。
REST的关键原则与将你的API分割成逻辑资源紧密相关,并采用所用并理解的原理。使用HTTP请求控制这些资源,其中,这些方法(GET, POST, PUT, PATCH, DELETE)具有特殊含义。
举例来说,有一个API提供公司内部(company)的信息,还包括各种部门(departments
)和雇员(employees)的信息,则它的路径应该设计成下面这样。
https://api.example.com/companyhttps://api.example.com/departmentshttps://api.example.com/employees
基于请求的方式和路径来作为常见的CURD(增删改查)
GET /company:列出所有公司POST /company:新建一个公司GET /company/ID:获取某个指定公司的信息PUT /company/ID:更新某个指定公司的信息(提供该公司的全部信息)PATCH /company/ID:更新某个指定公司的信息(提供该公司的部分信息)DELETE /company/ID:删除某个公司GET /company/ID/employees:列出某个指定公司的所有雇员DELETE /company/ID/employees/ID:删除某个指定公司的指定雇员
对于请求后的响应结果,RESTful也做了一个很好得定义:
GET /collection:返回资源对象的列表(数组)GET /collection/resource:返回单个资源对象POST /collection:返回新生成的资源对象PUT /collection/resource:返回完整的资源对象PATCH /collection/resource:返回完整的资源对象DELETE /collection/resource:返回一个空文档
RESTful API设计确实比较不多,对于一些简单的APP来说,能够快速的开发,并满足他们的绝大部分需求。
但是,请来一个神,就必须将神供着!
我们在使用之中也出现了很多头痛的问题,如:
问题1:多表多条件联查接口,如何设计?
很多情况下,我们的API并不是简简单单的查询(select)数据库中的数据,直接返回给调用者。我们可能会涉及到多表联查(left join , inner join)、排序(order by)、条件判断(where)、合并(union)等等。
如:找一个在我公司中找32~40岁,月收入在8000元,且在IT部门的人,属于北京事业群,将他们的工作KPI按逆序排列输出。
N多条件在一起的时候,API请求路径就会变得很难设计。
当然,网上有采用如下方式
/api/company?field1=abc&field2__like=%abc%&field3__gt=100&field4__not=999
但是,调用者调用API的时候,就感觉像是写SQL,API本身亦变得不可维护。
问题2:多逻辑请求接口,很难命名
不要小看命名的问题,我们要从名字上便于理解,又不要太长,又不能随意的缩写,其实很难。
如针对上述我们要求查询的逻辑来说,整个结构请求的路径就会变为如下所示:
GET /company/:ID/departments/:ID/employees/:ID/AREA/:ID/KPI/:ID?agemin=32&agemax=40&income=8000&order=api&sort=desc
虽然能够请求,但是已经很难从请求路径上看出我们的请求目的是什么,背离了所见即所需求的目的。
问题3:完整资源对象的返回,并不是调用者所需要的
如果一个数据库的字段很多,如我们的产品表,将近40~50个字段,每一个字段的类型、输出格式化都不一样,这个时候,如果直接将bean打印出来,如下所示:
出现几个问题
- 太多冗余字段:APP需要的PC不需要,PC需要的,H5不需要
- 每个字段都很难理解:调用API的人,估计看文档要看疯了,需求稍微一遍,前端样式也要跟着改,再看一次文档(韬哥认为这个是最重要的)
- 整个库设计完全暴露:安全安全!
{ "totalCount": 15, "data": [ { "id": 7956, "sTitle": "信证-通汇安盈一号", "productTypeId": 3, "zdPrice2": 2, "nianHuaShouYiStart": 8.5, "nianHuaShouYiEnd": 9.5, "jdt": 100, "touZiLingYu": "基础设施", "level": 99, "saleStatus": 121, "category": 30, "jdTime": "2016-12-20 09:48:13", "raiseProgress": "【2016年12月20日9时更新】本期为第六期,本期规模不限,本期已封帐,目前年前总剩余额度2520万,需要资产证明,有下期,下期无缝对接中;", "touZiMenkan": 100, "collectCount": 1, "hasCollect": false, "redPack": "", "fundType": "基础设施", "visitCount": 24631, "docPreviewCount": 11, "producttagids_intarray": "\t13\t11", "productTags": "11,二年期;13,固定收益类;", "title": "信证-通汇安盈一号(第6期)", "bid": 3, "statusId": 40, "qiXian": 24, "peibi": "", "pmName": "王佳慧", "pmUserName": "王佳慧", "daXiao": "小额畅打", "lingYu": null, "shouYiType": "固定收益类", "payStatus": "半年付息", "downLoad": "Upload/ProductPDF/20161202/信证-通汇安盈_473.zip", "groupName": null, "zdPrice": 1, "addr": null, "companyId": 272, "adminId": 473, "groupMaxPrice": 0, "nianHuaShouYiExt": "", "companyName": "汇蕴", "fxList": [ { "title": "100万≤X<300万", "price": "2.0%", "isFloat": false, "earningRate": "8.5%", "packingRate": 8.5 }, { "title": "300万≤X<1000万", "price": "1.5%", "isFloat": false, "earningRate": "9%", "packingRate": 9 }, { "title": "1000万≤X", "price": "1.0%", "isFloat": false, "earningRate": "9.5%", "packingRate": 9.5 } ], "sourceRepayment": "1、项目公司运营收入\n2、项目公司股东自有资金\n3、银行贷款资金置换", "fundInvest": "用于佛(山)清(远)从(化)高速公路北段工程建设项目的建设,以期获得投资收益", "windControl": "1、央企中电建路桥出具完工承诺,保证项目按期运营\n2、投资人成为融资主体股东", "highlights": "❶【省级重点】标的是广东省基建-高速公路建设,是从“十一五”就提出的建设规划,此项目为广东省重点工程,由政府牵头并给予大力支持,项目公司也与清远市交通运输局签订《特许经营协议》,项目违约成本极高; \n❷【项目把控实力】中证基金是项目公司第一大股东,对整体项目有掌控权,有助于资金安全及还款的及时有效;同时一同参与的有中电建、龙浩集团;\n❸【管理人背景】管理人由中证基金99%控股,中证基金股东为中信证券、华夏资本、中诚信托;\n❹【央企增信】央企中电建路桥出具完工承诺,保证项目能够按期运营;\n❺【银行资金】项目已有意向银行资金83.36亿贷款划拨计划,安全边际较高;", "productOrganizationId": 318, "productOrganizationPic": "Upload/productOrganization/20161012/logo_473.png", "bqqsr": "2016-12-12 00:00:00", "province": 440000, "proviceName": "广东省", "cityName": "佛山市", "dyl": -1, "addrId": null, "updateTime": "2016-12-20 09:48:13", "listingTime": "2016-12-12 11:41:00", "parentId": 7300, "phase": 6, "issuerCompanyId": 0, "issuerCompanyName": "", "issuerId": 0, "issuerPhone": "", "issuerName": "", "project": 3, "attr": 7, "jianBan": "", "appointmentCount": 53, "downloadCount": 385, "sendEmailCount": 21, "cashback": "", "tagsArray": [ { "id": "13", "name": "固定收益类" }, { "id": "11", "name": "二年期" } ], "bestEarningRate": "9.5%", "bestEarningRate_fore": 9, "bestEarningRate_back": "5", "bestPrice": "2.0%", "bestPrice_fore": 2, "bestPrice_back": "0", "bestGroupPrice": "待定", "bestGroupPrice_fore": "待定", "bestGroupPrice_back": 0, "bestEmployeePrice": "待定", "bestEmployeePrice_fore": "待定", "bestEmployeePrice_back": 0, "saleStatusName": "募集中", "isHot": false, "isHotSale": true, "isRecommend": false, "packingRate": 0, "returnCash": 0 } ], "isSuccess": true, "code": 0, "runSpanTime": 97}
像上面这个demo数据一样。泥他妈这么多字段,让前端、APP都去理解?是否前端也要参与数据库设计?前后端分离的意义在哪?
sorry,忍不住吐槽。
问题4:安全,还是安全
安全这个方向的问题太多,很难一一排查,如见到此类问题就想把协议换为HTTPS的人来说,你基本是还不了解安全。对ROOT后的Android系统来说,HTTPS并没有这么神秘。API上必须考虑安全性。
- 查询ID是不是该用主键?
- 所有下发的字段显示是否应该直接下发?
- 登录限制,频率限制
- 加密措施,如何做?
- 请求路径将表内关系完全暴露,响应结果将表结构暴露,SQL注入\数据爬虫\Replay攻击 防范要求太高。
当然,RESTful API规则上还有很多关于过滤、错误码(我最不能接受的就是状态码,很多运营商直接都劫持了,把你坑死)的说明,这里我们就不一一列举了。
上述一系列的问题,让我们在整个系统开发的过程中,使用倍感困惑。也许是自己对RESTful的了解不够到位,或者使用上无法掌握其精髓。所以,我们最后在设计自己API的时候,采取的是类RESTful协议,用其思想,并在RESTful的基础是做了很多的自定义操作功能。
request设计
整个设计上借鉴了RESTful的思想,将操作同样分为CURD(增删改查),如对用户(User)进行操作
- sheme命名
user/create # 创建user/delete # 删除user/update # 更新user/login # 登录user/info # 用户信息user/list # 列表# 多表联查getUserKpi?agemin=30&agemax=45 #kpi表和user表,根据需求来取名
- 参数过滤
user/list?query=周%&pageSize=10&start=0 #分页参数过滤
- 通用请求头部,自定义header
当然,最重要的是,我们的API都需要监控,根据版本号做兼容等,我们需要在request的header之中自定义一些信息,这里我们定义为json格式的信息。如下:
{ "userId": "1000", "platform": "android", "imei": "xxxxx", "appVersion": "1.0", "cityId": "0", "platformVersion": "4.2", "deviceId": "xxxx", "channel": "xxx", "protoVersion": 1}
response设计
相比request,response的设计规范相对简单很多(返回内容设计还是比较重要的,下一篇向大家介绍)。
还是根据RESTful的方式,我们将返回结果分为两部分,错误码和实际结果,如下所示:
{ "head": { "ret": 0, "msg": "ok", "cmd": "user/info" }, "body": { "list": [ { } ] }}
如何设计body里面的结果才是response的关键,整个API数据解耦的难题。这里不做详细介绍了,下一篇给大家一一分享下。
有问题,或者想吐槽的,请加韬哥微信:
/*
* @author zhoushengtao(周圣韬)
* @since 2016年12月21日 凌晨0:53:13
* @weixin stchou_zst
* @blog http://blog.csdn.net/yzzst
/
- 用产品思维设计API(一)——RESTful就是个骗局
- 【API设计风格—RESTful】:什么是RESTful(一)
- 【API设计风格—RESTful】:什么是RESTful(一)
- 用产品思维设计API(三)——版本控制,没有你想的这么简单
- 用产品思维设计API(五)—— 安全,就只能用HTTPS?
- 用产品思维设计API(二)——数据解耦,才是前后分离的本质
- 用产品思维设计API(四)——随意定义错误码,你还在这样干?
- 产品思维(一)
- 【API设计风格—RESTful】:服务端如何编写RESTful风格的API(二)
- 【API设计风格—RESTful】:前端如何调用RESTful风格的API(三)
- Restful API ——设计最佳实践
- NodeJs——RESTful API【一】
- RESTful API设计系列一:简介
- Laravel & Lumen RESTFul API 扩展包:Dingo API(一) —— 安装配置篇
- Laravel & Lumen RESTFul API 扩展包:Dingo API(一) —— 安装配置篇
- Laravel & Lumen RESTFul API 扩展包:Dingo API(一) —— 安装配置篇
- RESTful API 设计指南——最佳实践
- 10个有关RESTful API良好设计的最佳实践
- Material Design 的学习
- Mac OS 10.12.2 重装升级 openssl
- 基本Kmeans算法介绍及其实现
- iconv(文件编码格式转换)
- LeetCode解题报告 349. Intersection of Two Arrays [easy]
- 用产品思维设计API(一)——RESTful就是个骗局
- Lambda语法(中)
- 给IP段获取起始IP和末尾IP
- 单例模式
- 数据库连接池的使用方法
- Swagger UI教程 API 文档神器 搭配Node使用
- 支付宝架构图
- 网易云音乐API分析
- OpenGL应用开发---基础变换