Jsonql——给RESTful API插上一对翅膀

十度 笔记 2017年03月22日 收藏
RESTful API是目前比较成熟的一套互联网应用程序的API设计理论,规范了服务端资源的定义及访问。我们团队服务端就采用了RESTful。

可是在现实开发过程中,还是有些问题。

客户端在获取资源的时候,可能不同地方需要资源的不同的属性,而服务端常常会把几乎所有属性全部返回,这在App上会造成一些流量的浪费,譬如我要加载一个普通的产品列表,需要产品图片、产品名称、价格、库存等属性,而另一个浏览历史可能只需要名称和价格,这总不能写两个API吧?或者说附带一个请求参数告诉服务端要返回哪些属性,这倒行得通,可很不优雅。

重要的是客户端还会有一个界面可能需要调用多个不同类型的资源的情况,最经典的就是电商App,个人中心界面里,既需要用户的一些信息,又要查询收藏数量、关注数量、足迹数量,又要查询不同状态下的订单数量,或者下方还有推荐商品列表。客户端一个界面请求多个资源URI会降低客户端的体验自然不好,或者需要服务端需要额外提供适配客户端的API能解决,可一旦界面有变化这些接口也要重新做适配。

这些问题不是很重要,可问题多了,客户端开发和服务端开发要掐架。

有没有相对好的解决办法呢?

本人不才,搞了个 Jsonql(GitHub:https://github.com/liyanjie8712/Jsonql),意图给RESTful按上一对翅膀。

Jsonql是什么,咱给它起了个高大上的名字,Jsonql = Responsive Json Query Language,响应式Json查询语言,客户端要什么样的数据,由客户端来决定。服务端只提供资源及支持的查询函数,客户端编写查询请求,服务端解析并组装数据返回给客户端。这下服务端一劳永逸,客户端界面及数据绑定随便折腾去吧,挖哈哈~

先来看看Jsonql的语法:

变量的定义:$变量名

变量必须先定义后使用,所有在定义前的引用都是错误的。
变量具有作用域,只在当前定义它的 “{}” 及嵌套的子 “{}” 内有效,同时子作用域会覆盖父作用域定义的同名变量。

例:$abc。

资源的引用:资源名[]

资源名必须与服务端公开的资源列表中的名称一致并区分大小写。

例:users[]。

方法的调用:.方法名(参数)

例:.where(age>=18).count()。

字段的访问:.字段名

例:.user.name。

资源的枚举:=>$

使用资源访问符 “=>” 对资源进行枚举,“=>” 左侧定义资源的引用,右侧定义资源的输出(类似javascript对象)。
如果右侧定义以 “[]” 结尾,则表示遍历资源输出一个数组,否则表示只输出索引为0的元素。
“$” 表示对资源中元素的引用。
定义输出时,输出字段与元素字段名称一致时,可以省略 “$.”。

例:users[] => { uuid:$.id, name, age }

使用表达式:{{表达式}}

使用表达式可以执行一些运算操作,包括四则运算、逻辑运算、位运算等。 表达式必须使用“{{}}”包括,其运算结果为一个值。 表达式中可以使用变量,可以访问属性,但不可使用资源,不可调用方法。

例:{{ 1 + 2 - 3 * 4 / 5 % 6 }}、{{ $abc > 0 }}、{{ true ? 1 : 0 }}

写个查询的Demo,就拿用户与订单来:

  {
     //获取用户信息
     user: users[].where(id==1) =>
     {
         uuid: $.id,                             //ID
         username,                               //用户名
         avatar,                                 //头像
         account:
         {
             coins: $.account.coins,
             points: $.account.points
         }
     },
     //定义一个订单资源的变量
     $orders: orders[].where(user.id==1),
     //订单不同状态下的数量
     orderCount:
     { 
         created: $orders.count(status==1),
         payed: $orders.count(status==2),
         delivered: $orders.count(status==3),
         completed: $orders.count(status==4)
     },
     //后去该用户的订单列表,前10条数据
     orders: $orders.orderBy(createTime).skip(0).take(10) =>
     {
         uuid: $.id,                             //ID
         serial,                                 //订单号
         status                                  //订单状态
     }[]
 }

将要返回什么数据呢?来看看:

 {
     "user": {
         "uuid": 1,
         "username": "abcdefg",
         "avatar": "http://img.xxxx.com/avatars/1.jpg",
         "account": {
             "coins": 8888,
             "points": 9999
         }
     },
     "ordercount": { 
         "created": 0,
         "payed": 2,
         "delivered": 1,
         "completed": 18
     },
     "orders": [
         {
             "uuid": 1,
             "serial": "00001",
             "status": 2,
         },
         {
             "uuid": 2,
             "serial": "00002",
             "status": 2,
         },
         //…………
     ]
 }

查什么返回什么,是不是方便多了?并且支持Linq Method,搞C#的童鞋是不是很眼熟?当然还支持动态表达式计算,譬如:

 {
     $abc: 1,
     $def: 2,
     result: {{$abc + $def + 3}}  
 }

返回结果:

 {
     "result": 6
 }

Jsonql只是为了让查询更好用,资源的增删改操作还交给RESTful吧。

就到这里。目前Jsonql支持AspNet与AspNetCore,GitHub里带有简单的Demo,有兴趣的童鞋可以下载下来玩一玩,相应的依赖包已发布到:http://myget.org/gallery/liyanjie。