APIJSON-以坚持和偏执,回敬傲慢和偏见
APIJSON简介:
APIJSON是一种JSON传输结构协议。
客户端可以定义任何JSON结构去向服务端发起请求,服务端就会返回对应结构的JSON字符串,所求即所得。
一次请求任意结构任意数据,方便灵活,不需要专门接口或多次请求。
支持增删改查、模糊搜索、远程函数调用等。还能去除重复数据,节省流量提高速度!
从此HTTP传输JSON数据没有接口,更不需要文档!
客户端再也不用和服务端沟通接口或文档问题了!再也不会被文档各种错误坑了!
服务端再也不用为了兼容旧版客户端写新版接口和文档了!再也不会被客户端随时随地没完没了地烦了!
项目主页详细介绍:
https://github.com/TommyLemon/APIJSON
下载试用
前些天我发了几篇博客来推广APIJSON,几乎每次发布后都引起了很大的争议,有赞赏和认可,也有质疑和否定。
评论太乱太分散,就挑出部分偏见,在这里对关于APIJSON的开发、功能、使用、性能、安全做一个统一的解答吧。
首先声明:
开发APIJSON是为了解决小公司、团队及个人开发者中客户端和服务端的接口、文档和沟通问题,简化开发、提高效率、缩短周期。
希望大家不要用大公司的业务去要求APIJSON的功能、性能、安全等,而且个人也能力有限,所以只要方便、够用就好。
一般来说小公司只能开发小型系统,只有大公司才能开发大型系统。APIJSON解决的问题在小公司很容易出现并且容易造成致命的影响(开发周期长、产品上线迟导致资金链断裂而倒闭),但大公司因为资源(人才、资金、管理)充裕可以很好地避免这些问题。APIJSON降低了开发门槛和成本,使得小公司也能快速实现产品、节约资源,提高自己的竞争力。
另外,一般大公司基本都是自己造轮子用的,说不定有些公司也开发了类似APIJSON的数据交换协议,只是没有开源。
一、关于开发
翻了下聊天记录,大概是从2016年11月5日的问题开始构思的:
如果一个界面(如微信详细资料界面)需要获取1个User(个人资料)和它对应的1个pictureList(个人相册),目前有这几种方法:
1.接口返回的User里包括pictureList;
2.分别请求User和pictureList两个接口;
3.后台新增一个接口,返回数据包括User和pictureList。
这几种哪种最好呢?
我陆续问了公司群、技术群、圈子内的朋友们,获得了很多阿里、网易、恒生、微店、蘑菇街、挖财等客户端、前端、后端开发者的回答,但得到的方案都是上面的,我都不满意。
或许是真的没有人知道新的方案,也可能是公司的方案不能外传。之前也网上搜索了一些接口定制、组合、嵌套等相关的关键词,都没找到满意的答案。
求人不如求己,还是自己造一个轮子吧。
然后我花了大概一周的时间构思和评估新方案的
需求(实现微信详细资料的用户+最近相册、QQ空间的用户+动态+评论等数据的各种组合嵌套)、
方式(客户端可定制、组合、嵌套服务端的返回数据)、
实现(数据库、数据结构、传输与交互、封装与解析算法等)。
之后就开始动工了,先花了一周实现了最重要的
封装与解析协议可行性验证(确定了3个方案中基于JSON的方案二)、
数据库MySQL JDBC查询{User:{id:1}}、
简单对象组合{User:{},pictureList:[]}与列表+对象组合{[]:{User:{},Moment:{},Comment[]:{}}}的封装与解析,
然后实现了
客户端与服务端的HTTP通信、
客户端对Reqeust的封装和对Response的解析、
服务端对Reqeust的解析和对Response的封装。
APIJSON初步完成,放到Bitbucket远程仓库用SourceTree管理和备份,后来迁移到码云。
之后又经过了1.0-3.1的内部版本迭代,2016年11月21日放到GitHub上开源。
开发过程中遇到了很多问题:
1.一开始打算用请求格式最简洁的方案三,因为已经脱离JSON的合法格式不能封装和解析,尝试定制FastJSON失败,于是转为符合JSON格式的方案二。
2.对列表+对象组合Request的解析出现的各种问题,经过好几天的反复尝试、调试和修改终于完成。
3.URL中含有转义的字符'/'被UTF-8编码后发送到服务端仍然无法解析,后来通过多层encode解决。
...
偏见:
以我的经验来看,此方式不通。也许只是作者一个人的狂热。闭门造车出来的产物吧...
回敬:
APIJSON不是闭门造车的产物,而恰恰是为了解决自己和开发圈子里的各种接口问题而总结出来的实践经验。
APIJSON解决了传统方式导致的开发周期长(客户端要等服务端开发完接口、写好文档才能请求和解析接口)、客户端容易被文档的各种错误坑从而导致耗费大量的调试和与后端沟通的时间等问题。
二、关于功能
APIJSON目前已实现:
大体功能:增删改查、分页查询、统计与验证、注册登录、模糊搜索、结构校验、数据保护、远程函数调用等
操作方式:增、删、改、查、调用远程函数
操作对象:单个对象、可关联的多个对象、数组等
请求方法:GET,HEAD,POST_GET,POST_HEAD,POST,PUT,DELETE
请求结构:{Table:{...}}、{Table0:{...},Table1{...},Table2:{...}...}、{"[]":{Table:{...}}}、{"[]":{Table0:{...},Table1{...},"Array0[]":{...},...}}等各种组合和嵌套
返回结构:对应请求结构的各种JSON结构。
功能符号:
"key[]":{} // 查询数组 "key{}":[] // 匹配选项范围 "key{}":">=2,length(key)<10..." // 匹配条件范围 "key()":"function(Type0:value0,Type1:value1...)" // 远程调用函数 "key@":"key0/key1.../targetKey" // 引用赋值 "key$":"SQL搜索表达式" // 模糊搜索 "key?":"正则表达式" // 正则匹配 "key+":key指定类型的Object // 增加/扩展 "key-":key指定类型的Object // 减少/去除 "name:alias" // 新建别名 "@key":key指定类型的Object // 关键词。如返回字段@column、排序@order、自定义关键词@position
具体见文档:
https://github.com/TommyLemon/APIJSON
偏见1:
表连接查询
回敬:
table都是通过id关联的,连接查询在文章的 查询类微信朋友圈动态列表数据 里就给了demo,用户User、动态Moment、评论Comment就是通过id关联查询的。
偏见2:
那要是3个表4个表内连接呢?
回敬:
APIJSON给出的Demo里的Moment,User,Comment这3个表就是通过id内连接的。文中给出的查询类似微信朋友圈动态列表数据的demo:
{ "[]": { //请求一个array "page": 0, //array条件 "count": 2, "User": { //请求查询名为User的table,返回名为User的JSONObject "sex": 0 //object条件 }, "Moment": { "userId@": “/User/id” //缺省依赖路径,从同级object的路径开始 }, "Comment[]": { //请求一个名为Comment的array "page": 0, "count": 2, "Comment": { "momentId@": “[]/Moment/id” //完整依赖路径 } } } }
这就表示Moment.userId = User.id, Comment.momentId = Moment.id,不就实现了3个表内连接查询么?而且还是多个数组和单个Object复杂嵌套的应用场景。
偏见3:
处理不了复杂的请求,只能看着玩玩。
回敬:
APIJSON就是为复杂请求和数据结构变化而生的。
不管是简单对象组合{User:{},pictureList:[]},还是列表+对象组合{[]:{User:{},Moment:{},Comment[]:{}}}都可以。
微信朋友圈动态列表够复杂吧?APIJSON查询:(可下载App测试)
请求:
{ "[]": { //请求一个array "page": 0, //array条件 "count": 2, "User": { //请求查询名为User的table,返回名为User的JSONObject "sex": 0 //object条件 }, "Moment": { "userId@": “/User/id” //缺省依赖路径,从同级object的路径开始 }, "Comment[]": { //请求一个名为Comment的array "page": 0, "count": 2, "Comment": { "momentId@": “[]/Moment/id” //完整依赖路径 } } } }
返回:
{ "[]":[ { "User":{ "id":38710, "sex":0, "phone":"1300038710", "name":"Name-38710", "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000" }, "Moment":{ "id":470, "title":"Title-470", "content":"This is a Content...-470", "userId":38710, "pictureList":["http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"] }, "Comment[]":[ { "Comment":{ "id":4, "parentId":0, "momentId":470, "userId":310, "targetUserId":14604, "content":"This is a Content...-4", "targetUserName":"targetUserName-14604", "userName":"userName-93781" } }, { "Comment":{ "id":22, "parentId":221, "momentId":470, "userId":332, "targetUserId":5904, "content":"This is a Content...-22", "targetUserName":"targetUserName-5904", "userName":"userName-11679" } } ] }, { "User":{ "id":70793, "sex":0, "phone":"1300070793", "name":"Name-70793", "head":"http://static.oschina.net/uploads/user/1174/2348263_50.png?t=1439773471000" }, "Moment":{ "id":170, "title":"Title-73", "content":"This is a Content...-73", "userId":70793, "pictureList":["http://my.oschina.net/img/portrait.gif?t=1451961935000"] }, "Comment[]":[ { "Comment":{ "id":44, "parentId":0, "momentId":170, "userId":7073, "targetUserId":6378, "content":"This is a Content...-44", "targetUserName":"targetUserName-6378", "userName":"userName-88645" } }, { "Comment":{ "id":54, "parentId":0, "momentId":170, "userId":3, "targetUserId":62122, "content":"This is a Content...-54", "targetUserName":"targetUserName-62122", "userName":"userName-82381" } } ] } ] }
三、关于使用
见项目的文档和服务端及客户端的Demo工程:
https://github.com/TommyLemon/APIJSON
偏见1:
有违项目开发前后端分离的趋势。
回敬:
传统方式连客户端的UI都是和后端接口关联的,比如类似微信朋友圈、QQ空间的动态的列表都需要特定接口来传输对应结构的数据,耦合性很高。
而APIJSON让服务端和客户端UI完全分离了,不需要服务端为客户端UI定制接口,也不用为了兼容旧版客户端保留冗余字段以及开发新版接口(v2,v3...)和更新接口文档。
难道APIJSON不是更好地实现了前后端分离?
偏见2:
数据结构过于罗嗦,为什么我还需要声明返回值是一个数组?
回敬:
数组非必须,只有获取列表(数组)数据才需要,可能你只看了最上面复杂结构的APIJSON请求,误解了APIJSON请求方式。
比如获取一个User可以是{"User":{"id":1}},获取一个女性User列表可以是{"[]":{"User":{"sex":1}}},page和count也都是可选参数非必须。
仅从请求上看,APIJSON的数据确实比传统方式的多;但从整体上看,APIJSON让客户端看请求知结果,所求即所得,让服务端和客户端都大幅降低了开发和沟通成本,所以是值得的。
传统模式下是不同后端写出水平不一、参差不齐的还可能只有在公司内才能看的文档。
APIJSON的文档仅针对通用特性,少量、统一、清晰;
传统模式下的文档要根据不同业务逻辑、不同功能划分,一般的程序员还真写不好,内容多、容易混乱,难免出现不少错误和坑。当然如果和你配合的后端是个大牛就当我没说。
总之,传统方式不仅导致服务端开发成本高、客户端要等服务端开发完接口和文档才能请求,而且很容易被文档的错误坑,增加大量的调试和沟通成本;
而APIJSON大幅简化了连接方式,降低了双方的开发和沟通成本;将后端的业务逻辑和前端/客户端的UI分离类似于MVP开发模式的Model和View解耦,大幅降低了开发和调试难度。
偏见5:
如果要这样设计的话,那不如让js直接访问数据库,还可以没有接口!
回敬:
这会导致以下问题:
1.前端、客户端需要了解后端结构、会使用数据库;
2.容易引发sql注入,不安全。如果要预防sql注入,需要从文本提取字段去分析,代码又会很臃肿。
偏见6:
APIJSON可以让客户端传一个columns字段来指定需要的字段这种方式会造成客户端服务器的强耦合,比如有一天我删了这个columns,所有依赖这个columns的消费方都挂了
回敬:
传统方式删掉不照样会挂?你把currentUserId、pageNum或者User里的id、name、head删掉一个试试?
偏见7:
业务模型如果发生变化.就会出现不知道要改哪些地方的问题.
后端改字段会直接影响到前端.
回敬:
传统方式也是如此,并且还需要更新文档,然后再通知客户端修改代码。
而APIJSON做到了不会因为前端/客户端的UI变化而要后端修改返回JSON结构的代码,也不会因为后端改接口带来开发和沟通成本,这恰好是APIJSON的优势。
所以APIJSON在版本迭代上也是远超传统方式的,不需要服务端写兼容接口,然后更新文档,只要客户端改就行了。
APIJSON还不会因为服务端给的JSON结构不好用导致解析很麻烦,更不会因为复杂数据结构的变化导致修改非常困难(服务端和客户端都要重构接口相关代码)。
偏见8:
不赞同大范围使用这种设计
要有多种客户端,一种写一套,写得乱七八糟,真好管理!
回敬:
同版本的客户端的需求都是统一的,都得实现统一的需求,请求和解析JSON逻辑也是基本一致的,怎么会混乱不好管理呢?传统方式为了兼容旧版客户端要写v2,v3一堆新版接口和文档不是更乱管理更麻烦?
偏见9:
url不友好,容易混乱
回敬:
GET: base_url/get/
HEAD: base_url/head/
POST_GET: base_url/post_get/
POST_HEAD: base_url/post_head/
POST: base_url/post/
PUT: base_url/put/
DELETE: base_url/delete/
再加上login、register、get_authcode等少数几个url。
这难道还多?请求方法和url对应这么清晰,很还混乱?
传统方式要对几乎每个Table定对应几个方法的几个url,甚至还要对细分功能单独定制url,随便一个业务主要在服务端的APP都需要50个以上的url。
我之前两家公司的两个产品的url都超过100个。对第二个产品随便翻了一个非最新版的Word文档,总共66页,url有112个,状态码233个,字符数15148。
每次去文档里搜索查找url、字段、状态码一堆东西都要不少时间,还经常被文档里的字段名、字段类型等错误坑,有的表格还多了或者少了字段!
所以你这句话应该用在传统方式上,APIJSON恰恰大幅减少了url数量,解决了接口混乱问题。
偏见10:
没觉得方便。
偏见11:
没看出什么优势。开发往往不是技术问题,而是协作沟通问题。
回敬:
服务端不需要写接口,不需要写接口文档;客户端不用等服务端开发完接口写好文档才能请求和解析,也不会被文档各种错误坑,更不会有因为文档问题带来的调试和与服务端沟通的成本。
IDE什么用?用记事本照样能写。
Java有什么用?用C照样能写。
SDK有什么用?里面的功能都能实现。
上面都不是技术问题,而是协作沟通问题?
偏见12:
跟前端直接把sql传到后端相比,有什么优势?
回敬:
用APIJSON比直接传SQL语句要方便且安全得多。
1.APIJSON支持远程函数调用,这是SQL语句没有的特性。
2.SQL delete忘加where条件引发的数据库被清空事件一直都有,前段时间还有一不小心就删了整个公司的新闻。
而客户端请求APIJSON的方法不是GET或HEAD时,客户端发出的Request必须满足服务端的配置(具体看table目录下的sys_Request.sql,可用MySQLWorkbench查看)。
并且都只允许操作某个id对应的table,不会发生忘加条件导致非法DELETE,POST,PUT等请求污染甚至清空数据。
3.主流的编辑器对SQL语句没有检查,一旦出错,可能不仅仅是不能返回正确结果,还可能破坏数据。
而APIJSON的Request使用JSONRequest封装,里面一般都是由业务model给定键值对。
比如获取一个id为38710的User
/** * 用户模型 */ public class User { private Long id;
public User() { } public Long getId() { return id; } public User setId(Long id) { this.id = id; return this; } }
直接传SQL:
select * from User where id='38710'
这种操作不能保证id的值是某个类型,甚至可能出现id='3u710',id='38 710'等。关键词和词语顺序也很容易打错, 例如
select User * whre id='38710'
APIJSON:
JSONRequest request = new JSONRequest(new User().setId(38710));
这样就得到了 {"User":{"id":38710}} 这个请求一个User的Request的body。
如果传入id值的类型不是Long,编辑器就能检查出错误,另外JSON也有很多校验工具(代码、网站等)。
APIJSON使用阿里巴巴开源的fastjson,会对JSON格式校验。最后服务器上生成的SQL语句一定是合法合理的。
四、关于性能
JSON数据很轻量对客户端设备的性能影响可以忽略不计,至于web前端我还不够深入所以不好回答。
偏见:
查询性能问题
回敬:
你是愿意多花几个月开发、测试、沟通,还是更愿意多花几百元升级下服务器配置?
性能与方便是需要权衡的,只要性能在可接受范围内就值得使用,如果都往性能一边倒,现在就不会有Java,JavaScprit等语言了。
java刚出来时:垃圾!虚拟机边解释边运行,性能肯定很差!结果java现在基本上统一了后端、客户端;
JavaScript刚发布时,垃圾动态语言!边运行边判断类型,一定会卡到爆!结果现在它成了当之无愧的前端之王。
历史总是惊人地相似。
当一个新的技术出现时,如果它能促进技术和行业的进步,我们作为走在时代前沿的人是不是应该这么想:我该如何推动它的发展?
五、关于安全
1.APIJSON会对请求的格式进行校验。
2.APIJSON只有GET,HEAD请求才是明文,其它如POST都是非明文,这个和传统方式是一样的。
3.APIJSON会对非GET、HEAD请求的请求方法、结构、内容进行严格校验。
4.APIJSON对Table默认保护不可访问,需要服务端配置允许的请求与结构才能用指定的请求方法与结构访问。
偏见1:
不怕别人偷数据?
回敬:
怎么偷?没有权限不能访问需要非公开数据。如果是撞库,传统方式已经被成功黑进去拖库N次了,iCloud艳照门、网易邮箱、京东数据外泄等,安全性也不见得强。
偏见2:
容易出现安全问题。
偏见3:
直接查询数据库的数据库查询安全。
偏见4:
这跟直接连数据库没什么区别了,安全性是大问题。
偏见5:
把行为交给客户端控制,安全性不够
偏见6:
回敬:
1.APIJSON不是客户端直接查数据库。中间隔着协议,怎么会和直接连数据库没区别?照这么说,哪种方式都和直连数据库没区别了。
2.APIJSON拼接SQL是在服务端完成的,客户端是不能直接发送SQL给服务端的。整个数据库操作都是服务端完全可控的。
3.APIJSON安全性有多重机制保障,根本不会发生一次性误删数据库的问题,甚至连脏数据都很难产生。
APIJSON对Table默认保护不可访问,需要配置允许的请求才能用指定的请求方法访问。比如开放User的GET和PUT请求,需要Server工程内的AccessVerifier.java内加上一行代码
accessMap.put("User", new RequestMethod[]{GET,PUT});//只允许GET和PUT方法访问
如果允许全部请求,可以
accessMap.put("User", RequestMethod.values());//允许RequestMethod内所有方法。
点击或复制到浏览器测试:(请求成功)
http://apijson.cn:8080/get/{"User":{"id":38710}}
而Wallet需要保护,就没有配置GET请求,通过GET是访问不了的。配置了POST_GET,可以用POST_GET访问,非明文,请求body和返回结果都被保护。
点击或复制到浏览器测试:(请求失败,无GET权限)
http://apijson.cn:8080/get/{"Privacy":{"id":38710}}
对于Wallet,因为服务端Demo工程指定了POST_GET请求的JSON结构,即便用POST_GET方法,也不能随意访问。
{"Wallet":{"disallowColumns":"!", "necessaryColumns":"userId"}, "necessaryColumns":"currentUserId,loginPassword"}
请求结构必须是类似以下这种:
{"Wallet":{"userId":38710}, "currentUserId":38710, "loginPassword":"apijson"}
以下请求都不合法:
{"Comment":{"id":100,...}, ....} //缺少tag
{"tag":"Comment", ....} //缺少table,这里指Comment
{"Comment":{"id":100,...}, "tag":"Moment", ....} //tag和table名不匹配
{"Moment":{"id":100,...}, "tag":"Comment", ....} //tag和table名不匹配
如果多传了没有配置的table,例如
{"Comment":{"id":100,...},"Moment":{...}, "tag":"Comment", ....}
服务端只会接受Comment,而Moment无效。
服务端在QueryConfig中还会继续对id值进行校验,传空值不让通过。
偏见7:
我想说的是权限控制很多场景下是要根据内容授权的,你这个允许客户端指定查询哪个表甚至查询条件,无疑是给客户端开放的权限太大了,这跟留一个后门没什么区别
回敬:
目前密码是单独放在Password表里的,不给GET权限,推荐密码加密后存储,只允许比较,不允许获取,成熟的方案很多。
对于Column的单独授权目前可以设置dissallowColumns禁止访问,对细分场景的配置会继续完善。关于权限的配置希望大家能给些建议。
开放的权限没你说的那么大,关于安全之前的回复已一一说明。
权限的开放程度是要权衡和取舍的,黑莓手机安全吧,Android,iPhone都不能比,可用的人越来越少。
功能手机更安全,不能联网不能安装应用,远程黑入绝不可能,可为什么安全性相对差很多的智能机却越来越普及了呢?
另外网络安全这东西很复杂,涉及学科和范围很广,需要软硬件结合、各种加密验证机制等多重手段去保证,用哪种请求方式都不是能轻易解决的。
APIJSON主要面向的是互联网小公司、团队及个人开发者,一般不会对安全有太高的要求,所以Demo给的是简单的示例。
如果对安全要求很高,可以做Table或者Column映射。
统计了下,大部分偏见都是没有根据的凭空臆测!实践是检验真理的唯一标准!
项目主页提供了详细的文档,为什么不看一下?
文档里提供了测试链接,为什么不点一下?
博客和文档里都提供了App下载链接,为什么不用一下?
开源库提供了客户端和服务端源码,为什么不运行一下?
。。。
我现在只是杭州电子科技大学一名普通的大四学生,
最受欢迎的开源项目ZBLibrary只有800多个Star;
拿过最大的奖不过是华为创想杯的决赛入围奖;
待过最好的公司只是国内500强企业的一个子公司;
和几个兄弟一起创业的蝙蝠饿了手机游戏项目也失败了。
既没有国外FAG的荣誉,也没有国内BAT的光环。
我知道博客中讲黄段子容易吸引阅读,我也知道项目中放美女图容易获得Star,但这些都是我一直不愿触及的心里防线。
或许是没有光环、人微言轻,又不愿妥协,才会有如此多的凭空臆测的质疑和否定。
但我仍然满腔热血,渴望对开源社区贡献一份微薄的力量;
但我仍然对APIJSON充满希望,一如既往地不断更新迭代,将它发展壮大;
但我仍然认为群众的眼睛是雪亮的,开源的力量是强大的。
我会保持一颗开放包容、虚心学习、积极进取的心,同广大的开发者们互相交流探讨,一起学习成长。
最后,很感谢大家提出的各种建设性意见和宝贵的建议。
技术改变世界,以此共勉。
APIJSON,让接口和文档见鬼去吧!
下载试用(测试服务器地址:http://apijson.cn:8080)
源码及文档(觉得不错就Star支持下吧^_^)
https://github.com/TommyLemon/APIJSON