我用 Golang 写了个增删查改框架 GoooQo,正在尝试做一些推广,欢迎给点意见 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
f0rb
V2EX    程序员

我用 Golang 写了个增删查改框架 GoooQo,正在尝试做一些推广,欢迎给点意见

  •  
  •   f0rb 2024-08-26 17:17:45 +08:00 2364 次点击
    这是一个创建于 465 天前的主题,其中的信息可能已经有所发展或是发生改变。

    GoooQo 是一个基于 OQM ( Object Query-Language Mapping )技术的框架。

    OQM 技术与传统的 ORM ( Object Relational Mapping )技术的最大区别是,OQM 技术提出通过对象直接构造各种增删查改语句。项目目前还是一个 MVP ,已经完成了基于字段后缀推导查询条件的功能,例如通过字段名称ageGt推导出对应的查询条件为age > ?。并且围绕数据库的增删查改接口构建了一个简单的 RESTFul 模块提供 Web 的 API 。

    目前打算通过撰写文章和 Demo ,尝试在掘金、HelloGitHub 、Hacker News 、Reddit 、Product Hunt 等平台进行一些推广。

    掘金新发的文章今天在反馈页吐槽了一下展现数的问题,编辑提了点修改意见后就给推荐上了首页,到现在有 6000 多的展现量了。其他的,HN 的帖子太容易沉了,Reddit 感觉很容易被 downvotes 然后被屏蔽,所以来请教下大家有没有什么好的推广方法。

    掘金文章: https://juejin.cn/post/7406900130764881972

    GitHub 地址: https://github.com/doytowin/goooqo

    Demo 地址: https://github.com/doytowin/goooqo-demo

    30 条回复    2024-09-20 22:21:45 +08:00
    wkong
        1
    wkong  
       2024-08-26 17:29:10 +08:00
    f0rb
        2
    f0rb  
    OP
       2024-08-26 17:36:28 +08:00
    我感觉国外一些开发一看到 ORM 几个字母就 PTSD 了,也不看内容,一上来就攻击你
    bunny189
        3
    bunny189  
       2024-08-26 17:45:34 +08:00
    非常好的
    VVVYGD
        4
    VVVYGD  
       2024-08-26 17:50:05 +08:00
    非常好, 之前我也写过一个类似的,不过用于 document 数据库 https://github.com/ddx2x/crossgate
    matrix1010
        5
    matrix1010  
       2024-08-26 17:53:28 +08:00
    Go 的数据库查询一般分 2 个流派: 代码生成和 SQL builder. 代码生成很相当于你这个的强化版,缺点在于修改 schema 要重跑而且代码多。另一方面 SQL builder 的好处则是简洁,适合简单的 CRUD 。

    代码生成的 type 一般是完全安全的,而 SQL builder 的安全性会低一些,比如`func (c *Cond) In(field string, value ...interface{}) `. 但相比你这个完全手写还是更安全些。
    f0rb
        6
    f0rb  
    OP
    2024-08-26 20:01:17 +08:00
    @matrix1010 我这里一张表只需要编写实体对象和查询对象两个结构体,根据查询对象字段的名称生成查询条件,生成方式有两种,一种是通过反射读取字段信息,生成查询条件,一种是通过 gen 包为查询对象生成一堆 if 来拼接查询条件,比如这个 https://github.com/doytowin/goooqo/blob/main/main/user_query_builder.go 。 除了这两个结构体和两行构建代码,其他代码全部都封装了。
    没明白你这里说的完全手写是什么意思。

    我这里的代码生成是基于查询对象生成 if 构建语句,而不是从表生成实体对象。
    f0rb
        7
    f0rb  
    OP
       2024-08-26 20:06:25 +08:00
    @VVVYGD 我这里面也有个 MongoDB 的模块,这次文章里没提,用的是代码生成的方法,比如为 QtyGt 生成这样的代码 if q.QtyGt != nil {
    d = append(d, D{{"qty", D{{"$gt", q.QtyGt}}}})
    }
    完整的在这里:
    https://github.com/doytowin/goooqo/blob/main/main/inventory_query_builder.go
    f0rb
        8
    f0rb  
    OP
       2024-08-26 20:08:42 +08:00
    @VVVYGD 我看到你这个是 rust 写的,我是先用 Java 写了一个,然后写专利的时候写了个 Go 的,来验证 OQM 这个 idea 是不是能适用所有语言
    wwhontheway
        9
    wwhontheway  
       2024-08-27 10:25:08 +08:00
    请教一个问题,对于文档数据库,有没有类似 go-gorm/gen 的工具,可以生成 sql
    wwhontheway
        11
    wwhontheway  
       2024-08-27 11:09:43 +08:00
    wwhontheway
        12
    wwhontheway  
       2024-08-27 11:10:28 +08:00
    @wwhontheway gorm/gen 只有一个 model struct 就可以了
    f0rb
        13
    f0rb  
    OP
       2024-08-27 11:43:31 +08:00
    @wwhontheway intentory.go 里就包含了增删查改的全部代码

    Gorm 只能基于 model 构造比较符是’=‘的查询条件:

    db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;

    非=的查询条件需要手搓:

    db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
    // SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

    而使用 GoooQo ,你只需要定义好查询对象,
    type UserQuery struct {
    PageQuery
    NameNe *string
    AgeGt *int
    }

    然后基于查询参数构造实例就好了
    userDataAccess.Query(ctx, &UserQuery{NameNe: PStr("f0rb"), AgeGt: PInt(20)})

    这就是 OQM 技术中通过后缀来推导字段对应的查询条件。查询对象专注于构建查询子句,符合单一职责原则。

    好处就是,前端向后端传递查询参数时,可以通过反射自动构建查询对象的实例。而当需求变更需要增减查询条件时,只需要在查询对象里增减字段即可。
    f0rb
        14
    f0rb  
    OP
       2024-08-27 11:56:04 +08:00
    @wwhontheway 传统的 ORM 概念里,使用实体构建一些使用等号为比较符的查询条件,使得实体对象额外承担了构建查询子句的责任,显然不符合单一职责原则。

    实体对象的字段只能对应一个列名,如 age ,但是查询子句可能会包含同一列的多个查询条件,如 age > ? and age < ?,这就是使得实体对象无法同时兼顾列名的映射和查询条件的映射。

    而单独使用查询对象构建查询子句就不存在这样的限制,例如
    type UserQuery struct {
    PageQuery
    Name *string
    NameNe *string
    Age *int
    AgeLt *int
    AgeGt *int
    }

    注意这里字段的类型都是指针,在构建查询子句时,只为不为 nil 的字段构建查询条件和参数。
    VVVYGD
        15
    VVVYGD  
       2024-08-27 17:26:15 +08:00
    @f0rb 是的,我用 rust 写的并加入了查询谓词解析,例如查询 ab=123 & abc='123' 这样的,你可以参考里面的 yacc 在 go 里使用。
    VVVYGD
        16
    VVVYGD  
       2024-08-27 17:32:22 +08:00
    VVVYGD
        18
    VVVYGD  
       2024-08-27 17:36:31 +08:00
    @f0rb 汗,按个回车就回复了,我这里抽象 Condition<Filter> ,然后把 ab=123 & abc='123' 这种语法转成 mongo db 的 filter, 实现了大部份的逻辑,我小团队业务中用还是挺好用的,你可以看看。
    f0rb
        19
    f0rb  
    OP
       2024-08-28 08:19:47 +08:00
    @VVVYGD 我看了一下例子,是用 format!("a > 1");转成 ("a":"$gt",1) ? 这还是要拼字符串吧?
    我这儿的方案是把列名和谓词合二为一,把原来需要 3 个元素的查询条件变成了两个,变成键值对的形式,
    比如 aGt = 1 ,既能表示为 a > 1 ,也能表示为("a":"$gt",1) ,相当于直接把 OO 编程语言翻译成不同数据库的查询语言了,不需要额外去拼接字符串
    f0rb
        20
    f0rb  
    OP
       2024-08-28 08:24:10 +08:00
    @VVVYGD 我这个方案是重构来的,一开始也是拼字符串,不过是在 Java 的注解里,比如这样
    public class UserQuery {
    @QueryField(and = "username Like CONCAT('%', ?, '%')")
    private String usernameLike;
    }
    这种写法遇到 IN 查询的时候有个问题,就是不能提前知道数组里元素的个数来确定占位符的个数
    后面发现,字段名称里面已经包含列名和谓词两个要素了,剩下的占位符直接根据赋值去确定
    然后就确定了这种通过后缀来推导查询条件的方案。
    因为通过重构把重复的代码都进行了封装,所以代码量是最少的
    VVVYGD
        21
    VVVYGD  
       2024-09-09 17:08:56 +08:00
    差不多吧, a > 1 会转换成 yacc 语法解析成 field(a, gt,1). ->. mongo 的实现者转换成 真实的 {$gt:{a:1}}
    VVVYGD
        22
    VVVYGD  
       2024-09-09 17:12:01 +08:00
    @f0rb 例如 :a=1&&b=2||c=1&&b=2&&abc='abc21' 转换成的就是
    MongoFilter(Document({"$or": Array([Document({"$and": Array([Document({"a": Document({"$eq": Int64(1)})}), Document({"b": Document({"$eq": Int64(2)})})])}), Document({"$and": Array([Document({"c": Document({"$eq": Int64(1)})}), Document({"$and": Array([Document({"b": Document({"$eq": Int64(2)})}), Document({"abc": Document({"$eq": String("abc21")})})])})])})])})


    参考 https://github.com/ddx2x/crossgate/blob/master/src/store/condition.rs 测试样例
    f0rb
        23
    f0rb  
    OP
       2024-09-09 21:25:46 +08:00
    @VVVYGD 不一样哦,a > 1 对应 field(a, gt,1) 需要 3 个参数,而 aGt 只需要一个参数,值是跟着参数传进来
    前后端传参也不一样的方式也不一样,aGt=1 对应 json 是{"aGt":1}, 或者是?aGt=1&b=2, 不太清楚你这个前端参数是怎么传的,我这也有个构造或语句的例子: https://juejin.cn/post/7409608924054700047
    f0rb
        24
    f0rb  
    OP
       2024-09-09 21:30:31 +08:00
    @VVVYGD 而且使用字符串不利于 IDE 的代码检查还有代码重构和复用
    VVVYGD
        25
    VVVYGD  
       2024-09-10 10:04:09 +08:00
    @f0rb 其实就是类似 SQL 解析,这里 https://github.com/ddx2x/crossgate/blob/master/condition/src/cond.rs
    https://github.com/ddx2x/crossgate/blob/master/condition/src/cond.y 将语法树解析为 Expr ,然后将解析的 Expr 转换成 mongo 的谓词条件 https://github.com/ddx2x/crossgate/blob/master/src/store/mongo/filter.rs ,当然其实这个过程直接转换成 mongo SDK 的对象 doc. 例如原本 sql:select username ,amount from users where local="abc" and(xx=1 or
    b =2 and c=3) 我这边的使用写法是 new_condition().withTable("users").fields(["username","amount"]).wheres(#b" local="abc" && (xx=1 || b=2 && c=3)")
    VVVYGD
        26
    VVVYGD  
       2024-09-10 10:08:13 +08:00
    @f0rb 因为这个是转换成 Expr 的,在 rust 里是可以做前置检查的。
    f0rb
        27
    f0rb  
    OP
       2024-09-10 14:57:41 +08:00
    @VVVYGD 所以你这里每条查询都需要构造一次类似这样的条件是吧:#b" local="abc" && (xx=1 || b=2 && c=3)",那你这种怎么重用了,到处都是字符串吗?你这里不过是把 AND 换成&&,OR 换成||了,别的和 SQL 有什么区别呢?你并没有造出个新轮子,只是给旧轮子涂了层新漆。

    你要考虑到做一个信息系统, 前端都会有多个输入框由用户填写查询条件进行搜索,而多个查询条件是可以任意组合的,只能靠 if 语句判断用户是否填写了某个条件来决定是否拼接对应的条件,不知道这种场景你是怎么处理的。

    pub enum MongoOp {
    Eq,
    Gt,
    Gte,
    Lt,
    Lte,
    Ne,
    Like,
    NotLike, // { item: { $not: { $regex: "^p.*" } } }
    In,
    NotIn,
    }

    我看你这里都定义了这些谓词了,你直接把列名和他们拼接成 ageGt 这样,再解析成条件岂不是方便多了。
    f0rb
        28
    f0rb  
    OP
       2024-09-10 20:15:24 +08:00
    @VVVYGD "其实就是类似 SQL 解析"
    看懂了,我做的东西和你不一样,
    你做的是 SQL 转 MongoDB
    我做的是对象转 SQL 或者 MongoDB 或者其他 NoSQL 语言
    VVVYGD
        29
    VVVYGD  
       2024-09-18 12:37:47 +08:00
    @f0rb 是的,这里用了一种类 SQL 的方式去转成 mongo 的查询,也可以实现其他的的 nosql 查询,虽然我也不太明确还有哪些语法,哈哈。
    f0rb
        30
    f0rb  
    OP
       2024-09-20 22:21:45 +08:00
    @VVVYGD 是啊,因为所有的查询本质上都是两个值进行比较嘛,只不过不同的语言用的语法不一样
    我这里是把比较语法集成到面向对象语言的字段名称上了
    一般人有点不理解这个思路,所以感觉好难推广
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3519 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 10:36 PVG 18:36 LAX 02:36 JFK 05:36
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86