Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] 语法设计上的一些建议和思考 #584

Closed
luo-zhan opened this issue Jun 30, 2023 · 6 comments
Closed

[Feature] 语法设计上的一些建议和思考 #584

luo-zhan opened this issue Jun 30, 2023 · 6 comments
Labels
Document 文档 文档 Enhancement 增强 增强功能、提高性能等 Question 使用问题 使用问题

Comments

@luo-zhan
Copy link

luo-zhan commented Jun 30, 2023

Description

作者你好,这是我第二次接触Apijson了,半年前被复杂的操作符和学习成本劝退,但是设计思想是一直比较吸引人,直到最近有机会再次深入阅读了一次文档,发现了项目很多的闪光点,但是对于包括我在内的一些觉得Apijson的学习门槛有点高的情况有一些个人建议。

1、操作符使用简化

Apijson中常用的筛选条件类型包括:

  1. 等值(=、in)
  2. 比较(>、<、>=、<=、between)
  3. 模糊查询(like、正则)
  4. 逻辑(与、或、非)

Apijson的语法在筛选条件的使用方式上有点不一致,比方说in>=都是列名后加{}表示指定筛选条件,但是在一些情况下又可以忽略{}

"id{}": [12, 15, 32]
"id{}": ">=2"
"id>=": 2  // 单一的比较可以简写
"id": 12 // 等于也可以简写

当遇到逻辑运算时,操作符看起来更复杂(比列名还长...)

"id&{}":">=300,<=400"

关联查询本质也是等于,但是需要列名后加@符号,此时也不用加{}

"id@": "/Moment/userId"  // 关联查询

以上,在类似的场景下,使用方式却各不相同,不太简单直观

建议

可以去掉{}并统一等值筛选的语法,因等值筛选使用频率很高,采用最简单的设计:

// 等值
"id": 12 // 等于
"id": "@/Moment/userId" // 关联查询,本质也是等于,将@符号移到value中
"id": [12, 15, 32]  // in,去掉“{}”

// 比较
"id>=": 2 
"id<": 2 

// 逻辑运算
"id&": ">300,<400"
"id!": [12, 15, 32]
"id|": ">400,<300"

ps.关联查询的@符号和Apijson的关键词前缀@是冲突的,对于新手来说就会经常遇到@符号一会在前一会在后,容易产生疑惑,这也是另一个不建议关联查询的@符号放在key中的原因

{
  "[]": {
    "Moment": {
      "@column": "id,date,content,praiseUserIdList" // @符号在前
    },
    "query": 2
  },
  "total@": "/[]/total" // @符号在后
}

2、符号的语义化

Apijson的设计规则是列名+操作符+值,另一个上手难度就是在操作符的功能定义上,其中+-><&|@的定义是完全ok的,基本没有学习成本,而$~%{}<>}{的定义就还有设计余地,导致学习成本比较高(比方我看了两遍文档了还是不能准确快速的背下来...)

以下摘取自官方文档:

// $ 模糊查询
"content$": "%APIJSON%"

// ? 正则查询
"content?": "^[0-9]+$"
"content?": "APIJSON" // 和模糊查询%APIJSON%效果相同

// % 范围查询
"date%":"2017-10-01,2018-10-01"

// {} 筛选
"id{}": ">=2"

// <> json包含
"contactIdList<>":38710

// }{ 存在
"id}{@":{
  "from":"Comment",
  "Comment":{
    "momentId":15
  }
}

建议

不知道作者对上述操作符的选取有没有特别的用意,如果有的话建议写在文档里加强使用者的记忆,降低学习成本,下面是我个人的一些想法,可供参考。

like查询

可以使用代表不确定的结果、模糊的含义,表示模糊查询。

"content?": "%APIJSON%" // like '%APIJSON%'

正则查询

使用正则表达式开头的符号^作为操作符,比较好记

"content^": "^[0-9]+$" // 正则

ps.这里我再多提一点想法:

正则查询和like查询都是模糊查询,如果两者能使用?的话对新手上手是最友好的,所以如果更看重用户体验,可以考虑就用表示模糊查询,然后通过value值是否包含%来判定是like查询还是正则查询

"content?": "^[0-9]+$" // 正则
"content?": "%三%" // like

但是这样会存在一个bug——如果正则表达式中有%则会被误判,此时就要求开发者必须使用^符号明确指定是正则查询了,考虑到大多数开发者应该会偏向于使用其中某一种查询方式,而另外一种查询很少会用(像我们团队就几乎不用正则查询),那这种特殊情况在文档中标注清楚解决办法就好,不算大问题。

这样就既对新手友好,又能符合功能需求,可以考虑一下。

范围查询

一般范围是两个值,比如日期范围“1992~2023”,金额范围“100~999”,使用~就非常直观

"date~": "2017-10-01,2018-10-01"
"id~": "1,99"

另外既然范围是两个值,value使用方括号可读性可能会更好(不过这个不是必须,个人感觉都行)

"date~": ["2017-10-01", "2018-10-01"]
"id~": [1, 99]

筛选 {}

这个操作符和其他单字符操作符不同,是两个字符拼成的,其实会对使用者的体验造成割裂的。建议都是用单字符操作符。

在筛选操作符这里,个人感觉{}可以省略掉,像上述几种查询方式都是筛选,但只有在in条件和多个比较条件时才需要使用{},有点奇怪的,不知道是不是有实现上的难点无法去除,还是有别的设计原因我没发现的,希望能不吝赐教,再探讨一下。

json包含

<>这个操作符也是两个字符的,建议使用单字符(就好,括号有包括之意,也像字母Contain的首字母C,方便记忆

"contactIdList(":38710

这样和json操作符增加、减少元素的使用体验上,也更加和谐一些:

"contactIdList(": 38710
"contactIdList+": 38710
"contactIdList-": 38710

存在 }{

必须要吐槽一下这个符号的设计实在是太奇怪了,在官方示例中配合@一起使用时,甚至给人一种乱码的既视感😂

"id}{@"

而且在某些输入法下,输入{就会自动打出一对括号{},就导致输入}{时,总是要手动删除输入法自动打出来的右大括号:}{}}{

这个符号因为使用场景应该也不会太多,建议换个冷门符号,比如/,记忆方法就类似湖南/中国,只有中国存在才会有湖南,也不难记忆。

"id/":{
  "from":"Comment",
  "Comment":{
    "momentId":15
  }
}

ps.id/后的@符号也去掉了,因为存在场景应该只有子查询一种情况吧,是不是必然有@符号,那么就不如省略掉(如果还有其他使用场景希望指正~)

pss.我对这个id字段的意义还是比较疑惑,文档中说这个id用来标识是哪个判断,并不是很清晰,希望能解答一下,谢谢。

3、Api使用简化

在下例中,Comment已经作为key了,那么能否省略使用from再指定的一次:

"id}{@":{
  "from":"Comment",
  "Comment":{
    "momentId":15
  }
}

希望的使用方式:

"id/":{
  "Comment":{
    "momentId":15
  }
}

还是说有什么别的设计原因?

类似的,“子查询相关比较”希望的使用方式:

"id>":{
   "Comment":{
      "@column":"min(userId)"
   }
}

上面都是基于我作为新手来阅读上手指南的一些感受和思考,可能有很多因为我的不够了解所以想当然了,但是确实是初次使用时的真实感受,相信也有很多初学者跟我有一样的想法,如果我的一些疑惑能解决,后续再放到上手指南中,对后来者也有所帮助,所以希望能和作者进一步的探讨。

Apijson确实是个有创意且强大的框架,如果在使用体验和上手指南上更进一步就更好了。如果有需要的话我也愿意帮助完善上手指南文档,让初学者更轻松的学习这个优秀的框架:D

@TommyLemon
Copy link
Collaborator

TommyLemon commented Jul 2, 2023

你好,感谢你的关注。
从你的描述看来,你不是后端开发,可能是前端/客户端开发。

所有能自动化的前提一定是标准化,而且不能有 歧义/矛盾/冲突 的地方。

1、操作符使用简化

简化后会有歧义,和 = 冲突。id 不一定是主键名,类型不一定是 int,可能是 varchar 等,简化后会导致冲突等各种问题。
另外生态项目 APIAuto 基于 APIJSON 语法在 JSON 键值对右侧生成注释,如果简化后就不能保证是正确的。

没有任何地方可以省略作为 IN 或者 条件 的操作符 {},只不过是 "key>=" 这种换了功能符来替代而已。

"id{}": [82001, 82002] // id IN(82001, 82002),表示在一个选项范围
"id{}": "<30,>=100" // id <30 OR id >=100,表示在一个条件范围

@ 符号不能省,还是歧义与冲突问题,省一个字符的工作量几乎没有意义,反而导致各种问题。
类似 Android XML 布局属性 "color": "@color/red" 那样放到 值里面,仍然容易和 color = '@color/red' 冲突。
APIJSON 统一把功能符放到 key 中,避免了各种歧义/矛盾/冲突。

@column, @order@key , @ 在前面表示关键词,和字段名等区分开来,类比微信聊天、GitHub 评论等各种场景, "@luo-zhan" 并不是单纯 "@" 和 "luo-zhan" 的组合,而是带有 用户标识,甚至可点击跳转的超链接 等的特殊 字符串片段。
"id@": "Comment/userId" ,表示后面的 value 不是普通字符串,而是引用赋值路径,不能随便传。

"address": "China/Shenzhen" 表示的 address = 'China/Shenzhen' 等场景不是一回事。

2、符号的语义化

APIJSON 用 $ 表示 Search,$ 和 S 很像,对应 SQL 的 LIKE。各种关系型数据库基本都有 LIKE 语法,例如 name LIKE '%a%' 表示 name 包含 a,tag LIKE 'b%' 表示从 b 开始等,%, _ 都是 LIKE 语法的通配符。

APIJSON 早期用 "key?": "^[0-9]+$" 这种 ? 结尾表示 value 为正则表达式,但后面看到 PostgreSQL 用 ~ 表示正则语法,就改为了 ~。 ~ 用于 100~200 这种范围,确实接近平常在数学上的语法,之前也考虑过,不过已经用于正则了。

"key%": "100,200" 表示 key BETWEEN 100 AND 200,没有哪个符号比 % 更能直观形象地表示夹在两个值中间的 BETWEEN AND 语法了。"key%":["100,200", "500,1000"] 表示 key BETWEEN 100 AND 200 OR key BETWEEN 500 AND 1000,一开始实现是 "key%": [100, 200] 表示 key BETWEEN 100 AND 200,但为了多个条件,以及和 "key$":["a%", "b%", "%c%" ] 统一,更方便记忆,所以改成了目前的方式,当然对于 PostgreSQL 等,需要传参 "@cast": "date%:DATE", "date%": "2020-01-01,2022-01-01" 来 CAST('2020-01-01' AS DATE),不过也可以 URI 传参 stringtype=unspecified 支持传字符串,数据库自动转为对应的 DATE 等类型。
#112
#349

"key{}": [1, 2, 3],用 "{}" 表示 IN 来源于 Java 等语言中定义数组的语法:

int[] arr = new int[]{1, 2, 3}

就是用 {} 包含数组元素。EXIST 和 IN 功能接近,经常被放在一块讨论,所以就把符号反过来 }{ 作为 EXISTS,}{ 本身也看起来像 X,也像 E 的正反两面左右拼起来,删除一个字符 } 并没有显著增加工作量。/ 已经用与 key 路径分隔符,不能在 key 中包含,否则就会解析引用赋值路径 User/id, []/Moment/id 等出错,除非转义,但转义成 %2F 甚至有的地方转移成 %252F 显然不是好的方式。key}{ 中的 key 只是在所属对象中的一个唯一标识,只要不和其它 key 同名即可。EXISTS(SELECT id FROM sys.Comemnt WHERE toId=0) ,一般建议

"toId}{@":{  "from": "Comment", "Comment":{"@column": "id", "toId": 0 } }  

更直观表示是 toId 相关的子查询,当然把 "toId}{" 换成 "anyKey}{" 在功能使用上也没有任何问题。

"key<>": 1 ,用 "{}" 表示 IN 来源于 Java 等语言中定义集合泛型的语法:

List<Integer> list = new ArrayList<>();

List 可包含类型为 Integer 的元素。

3、API 使用简化

"from":"Comment" 是为了方便解析,性能也更好。去掉的话是能实现,就像 "[]":{ "Comment":{} } 遍历即可,但不划算,毕竟子查询用得少,多打一个键值对也没增加大的工作量。另外子查询内部也可以使用 JOIN,传多个表对象。

去掉 @ 等一个字符,带来歧义、冲突等各种问题,很不划算,就像是用 位运算、冷门语法 减少几行代码,难以理解导致可维护性差,况且这里 APIJSON 仅仅是节省一个字符的而已,还没有代码中 冷门语法 带来的提升明显。

后续如果新增一些新的功能,例如

"contactIdList<>": 82001 表示 json_contains(contactIdList, '$', 82001),位置固定为最外层,

如果需要自定义位置,可以

"structure<>": {
   "path": "$.User.contactIdList" 
   "value": 82001
}

表示 json_contains(structure, '$.User.contactIdList', 82001)
如果把 @ 去掉了

"structure<>": {
}

就只能表示 json_contains(structure, 子查询),不能这样扩展功能了。


感谢你的关注和贡献意愿,开源仅靠少数个人的力量是远远不够的,要大家一起参与才能可持续发展,欢迎为 APIJSON 贡献~
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
image
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md

@TommyLemon
Copy link
Collaborator

APIJSON 规划及路线图
https://github.com/Tencent/APIJSON/blob/master/Roadmap.md
image

建议收集箱 关于新增功能、优化性能等的一些想法
#37 (comment)
IMG_20221027_185342

@luo-zhan
Copy link
Author

luo-zhan commented Jul 4, 2023

很认同你说的语法设计要做到没有歧义和冲突,我想表达的是避免冲突的前提下尽量简化使用。

1.看了回复之后我发现我之前有个误解,我原以为{}符号是代表所有条件筛选,其实只是代表范围查询(官方文档这块介绍可以优化一下),不过既然代表范围查询,它和%><等符号的功能又有重叠,如果一种功能多种方式都能实现,肯定是加大学习成本且产生选择困难:

'id%': '1,10'
'id{}': '>=1,<=10'

ps.{}的符号选择既然来源于java的数组定义,为什么又可以代表><等范围查询,一种符号代表两种含义,也是有点奇怪的。

2.关于范围查询符号{},我的想法是:

  • 在in的功能上,去掉{},可以通过value值格式来判断是in还是=,value是数组就是in嘛
  • 在比较功能上,不要让用户去使用类似"id{}":">1"这种只有一个比较条件的语法,应直接用>,>=等符号代替,类似的如果是范围查询应直接使用%代替
  • {}的设计可以改成“用于实现多个复杂查询条件的集合”,如"id{}": ">1,<=10"
    这样定义就比较清晰了:
// 简单查询
'id': 1
'id': [1,2] // in 
'id!': [1,2]  // not in
'id>': 5
'id%': "1,5"

// 复杂查询使用{}
'id{}': '>10,<=5,[7,8,9,10]' 

这样设计应该不会有冲突问题,使用更简单,而且上手文档中很容易表达清楚。

3.@符号放在单词前面表示关键词这个我是很赞成的,我只是对“@放在后面代表引用”的设计有不同意见,其实像很多语言中的引用变量都是放在value中的,这样设计会更符合使用习惯:

// js
var a = 'Hello';
var b = `${a}`;
#!/bin/bash
a=Hello
b=$a

如果觉得放在value中会引起冲突,可以设计成非引用时通过\转义:

'id': '@[]/Moment/id'    //引用
'user_id': '\@luozhan'  // 等于

即使退一万步讲因为种种原因没法放在value中,也建议换一个符号,@符号已经用来代表内置字段,就尽量不要再含有第二层意思,这个和{}符号既代表in又代表比较的情况类似,让人第一时间难以琢磨确切含义。

4.存在操作符使用/符号放在结尾,为什么和引用赋值路径会冲突呢,不是很理解。另外看了你的解释,"toId}{@"的用法不如改成@exist用内置字段来的直观。

5.“"from":"Comment"是为了方便解析,性能也更好”。

这里其实可以同时兼容写from和不写from的情况啊,不写from就自动判断拿第一个表名字段就好了,在大多数情况下写起来是简单的、符合直觉的,这样前期上手根本不需要了解from字段,遇到复杂情况再找找from用法就好。(这点性能损失可以忽略,再说咱也不是主打性能,既然是提高开发效率为目的的框架,使用体验会更重要~)

ps.这里的from不也是内置字段吗,为啥不用加‘@’符号呢?

最后,其实所有想法想表达的就是一点:降低上手难度,好的设计应该是大部分情况直接就能用,当遇到复杂情况再查查文档,顺便学习一下加深映像,整个学习曲线是平滑的。

语法设计要做到没有歧义不难,但同时还能简化用法、符合用户直觉就比较难了,语法复杂就会带来更高的学习成本,也同时带来更高的解释成本(写文档和回答问题),都阻碍这个优秀框架的流行。

@TommyLemon
Copy link
Collaborator

TommyLemon commented Jul 8, 2023

1."key{}": String 可以实现各种复杂条件,有 SQL 函数则不拼接 key,没有就拼接:例如

"key!{}":"%2=0;length(key)>5;date_diff(key,now())<=7" 

对应 SQL:

NOT(key %2=0 OR length(key)>5 OR date_diff(key,now())<=7)

没有完整的替代方案,所以仍会保留

2.以上已经说明歧义和冲突,符号必须保留,不要想当然觉得 id 这个字段名就一定是主键,类型一定是 int/varchar。
而且如果有 contactIdList json 这个字段,本身存储格式就是 [1, 2, 3...],那么
"contactIdList": [1, 2, 3]
对应一定是 contactIdList = '[1, 2, 3]' 而不能是 contactIdList IN (1, 2, 3),只有
"contactIdList{}": ['[1, 2, 3]', '[4, 5]'] 这样才有正确的含义表达 contactIdList IN ('[1, 2, 3]', '[4, 5]')

3.仍然是歧义和冲突问题,通用编程语言功能强大复杂,转义可以理解,但 Java 都不支持直接这样写,而是必须调函数 String.format 等来解析占位符。
APIJSON 不会这么做,我非常反感转义这种大幅降低易用性和可读性、容易导致人和机器出错的方式,目前也没有任何必要这么做。

4.上面已经恢复了,APIJSON 用 / 作为路径分隔符,如果是

"id/": {
   "Comment": {},
   "User": {
       "id@": "id//Comment/userId" // 注意即写成相对路径 /Comment/userId 也会自动补全为前面的绝对路径
   }
}

会误解析为 ["id", "", "Comment", "userId"]

"id/@": {
   "Comment": {},
   "User": {
       "id@": "id//Comment/userId" // 注意即写成相对路径 /Comment/userId 也会自动补全为前面的绝对路径
   }
}

会误解析为 ["id", "@", "Comment", "userId"]

改成 @exist 虽然确实能让理解成本低些,但多个 EXISTS 就得传数组套对象

"@exist": [{
   "Moment": {}
}, {
   "Comment": {},
   "User": {
       "id@": "/Comment/userId" 
   }
}
...
]

或者两层对象嵌套了

"@exist": {
 "momentId{}": {
   "Moment": {}
 }, 
 "commentId>": {
   "Comment": {},
   "User": {
       "id@": "/Comment/userId" 
   }
}
...
}

还不如现在的方式简单

 "momentId{}@": {
   "Moment": {}
 }, 
 "commentId>@": {
   "Comment": {},
   "User": {
       "id@": "/Comment/userId" 
   }
}

5.from 可以通过扫描 符合表名的 key 且 value 为 JSONObject 类型来改为不传。
value instanceof JSONObject && JSONResponse.isTableKey(key)

https://github.com/Tencent/APIJSON/blob/master/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java#L386-L394
image

@ 符号是为了和对象内其它容易混淆的字段名等做区分,避免重名,很难混淆的地方就尽量不加,例如:
tag, version 等只放在最外层的全局关键词、
cout, page, query, join 等只放在 []:{} 层表示分页属性或 JOIN 的关键词、
from, range 等只放在 key@:{} 子查询层的关键词

都不加 @,因为几乎不可能有冲突,这些地方一般不会有自定义字段,即便有也能轻松避免同名冲突,
一般除了 APIJSON 关键次就只传表对象,表对象 key 在 APIJSON 传参必须是大写英文字母开头;
表对象内的字段名就不一样了,太容易和 order 等字段重名,让用户去改所有与 APIJSON 对象关键词同名的表字段名不合理,
或者把 APIJSON 对象关键词改名也会导致要自己维护文档和培训使用、生态周边工具受影响的问题。

@TommyLemon
Copy link
Collaborator

TommyLemon commented Jul 8, 2023

新增支持子查询对象内省略关键词 from,自动取最上方的表对象 key 作为 from
0f1b105

image

@TommyLemon TommyLemon added Enhancement 增强 增强功能、提高性能等 Question 使用问题 使用问题 Document 文档 文档 labels Jul 9, 2023
@luo-zhan
Copy link
Author

过了几天再来看,发现已经看不懂你回复里面的EXISTS 语法和截图里面的@from@了,各种重复出现的{}@符号看的眼花缭乱,只能说语法设计属实是难,学习成本也低不下来😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Document 文档 文档 Enhancement 增强 增强功能、提高性能等 Question 使用问题 使用问题
Projects
None yet
Development

No branches or pull requests

2 participants