看到 Go 与 MongoDB 的交互方式,我想放弃 Go 了

2021-10-24 11:48:43 +08:00
 balabalaguguji

之前习惯了 python/js 这种语法,感觉很自然很方便。

今天看了下 MongoDB 官方的 Go 接口,哎呀,那交互方式,真的是痛苦。

例如查询用户为 1 的用户:{userid: 1},在 Go 里面你还得包裹为 bson.D{{"userid", 1}}

返回的结果是一个索引,要 Decode 下,Docode 还需要传递一个结构体过去。

还得传递一个 context (还没看为啥要这么做,其他语言不用)

感觉一点也不方便,代码很多不美观不优雅,习惯了 js/python 这种比较简单直观的语法,难以接受呀。

感觉 Google 最近出的东西,语法都那么特立独行的,还有一个是死亡嵌套 Flutter ,嵌套到怀疑人生。

官方教程: https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial

filter := bson.D{{"name", "Ash"}}

update := bson.D{
    {"$inc", bson.D{
        {"age", 1},
    }},
}

updateResult, err := collection.UpdateOne(context.TODO(), filter, update)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Matched %v documents and updated %v documents.\n", updateResult.MatchedCount, updateResult.ModifiedCount)
14117 次点击
所在节点    Go 编程语言
99 条回复
EPr2hh6LADQWqRVH
2021-10-24 11:54:16 +08:00
主要是因为 Go 里你可以自己安排内存,而这个库不想替你管理内存。

其实别的语言也一样,当一个组件不想建堆内存的时候,代码都一样拧巴。
EPr2hh6LADQWqRVH
2021-10-24 11:57:44 +08:00
哦我收回我的话,这不是主要原因。

主要原因就是 Go 描述动态对象太拧巴了
Rache1
2021-10-24 12:00:59 +08:00
封装一下,应该还行
codehz
2021-10-24 12:02:39 +08:00
context 这个可以洗一下,它可以用于在请求撤销的时候撤回发出去的其他请求,避免了资源的浪费,在 go 的主要应用场景--中间件方面意义重大
JS 里也有类似的东西,叫 AbortSignal
(虽然 context 功能上更多一点,这里只考虑它的这个功能(
cheng6563
2021-10-24 12:43:29 +08:00
静态语音就是这样的,对接第三方的时候都是先定义模型,再用这个模型的实例去做交互。
通常用泛型会简化一点这种操作,但是 go 。。。
balabalaguguji
2021-10-24 13:07:09 +08:00
@Rache1 #3 好像不好封装,Go 里面没有支持任意类型的 Map
balabalaguguji
2021-10-24 13:08:10 +08:00
@codehz #4 这个特性感觉我都用不到,但是每次都得写,感觉就有点强制多余了
balabalaguguji
2021-10-24 13:09:13 +08:00
@cheng6563 #5 嗯,静态语言应该都是要先定义模型,太不灵活了。
XTTX
2021-10-24 13:33:26 +08:00
官方 doc 就劝退了, 那你还是千万别用 go+mongoDB 了
XTTX
2021-10-24 13:34:26 +08:00
//FindReadingListArticleAction finds if articleAction exists by checking articleId and ownerID
func (aam *articleActionDB) FindUserArticleActionArticle(ownerID primitive.ObjectID) (*[]Article, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

client, err := mongo.Connect(ctx, options.Client().ApplyURI(aam.connectionString))
if err != nil {
return nil, err
}
defer client.Disconnect(ctx)
collection := client.Database(aam.dataBaseName).Collection(aam.collectionName)
matchStage := bson.D{{"$match", bson.M{"ownerID": ownerID}}}
projectStage := bson.D{{"$project", bson.M{"articleID": 1}}}

type articleID struct {
ArticleID primitive.ObjectID `bson:"articleID"`
}

var articleIDs []articleID

cursor, err := collection.Aggregate(ctx, mongo.Pipeline{matchStage, projectStage})
if err != nil {
fmt.Println(err)
return nil, err
}
if err = cursor.All(ctx, &articleIDs); err != nil {
return nil, err
}
articleClient, err := mongo.Connect(ctx, options.Client().ApplyURI(aam.connectionString))
if err != nil {
return nil, err
}
defer articleClient.Disconnect(ctx)
articleCollection := client.Database("crawler").Collection("article")
var articles []Article
var article Article
for i := 0; i < len(articleIDs); i++ {
err := articleCollection.FindOne(ctx, bson.M{"_id": articleIDs[i].ArticleID}).Decode(&article)
if err != nil {
fmt.Println(err)
}
articles = append(articles, article)
}
return &articles, nil
}

我 6 个月前写的,我现在完全看不懂当初写了什么寂寞
XTTX
2021-10-24 13:39:57 +08:00
````
//FindFinishedListArticleAction finds if articleAction exists by checking articleId and ownerID
func (aam *articleActionDB) FindFinishedListArticleAction(ownerID primitive.ObjectID) (*[]Article, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

client, err := mongo.Connect(ctx, options.Client().ApplyURI(aam.connectionString))
if err != nil {
return nil, err
}
defer client.Disconnect(ctx)
collection := client.Database(aam.dataBaseName).Collection(aam.collectionName)
matchStage := bson.D{{"$match", bson.M{"ownerID": ownerID}}}
matchStage1 := bson.D{{"$match",
bson.M{"finisheddate": bson.M{"$exists": true}}}}
projectStage := bson.D{{"$project", bson.M{"articleID": 1}}}

type articleID struct {
ArticleID primitive.ObjectID `bson:"articleID"`
}

var articleIDs []articleID

cursor, err := collection.Aggregate(ctx, mongo.Pipeline{matchStage, matchStage1, projectStage})
if err != nil {
fmt.Println(err)
return nil, err
}
if err = cursor.All(ctx, &articleIDs); err != nil {
return nil, err
}
articleClient, err := mongo.Connect(ctx, options.Client().ApplyURI(aam.connectionString))
if err != nil {
return nil, err
}
defer articleClient.Disconnect(ctx)
articleCollection := client.Database("crawler").Collection("article")
var articles []Article
var article Article
for i := 0; i < len(articleIDs); i++ {
err := articleCollection.FindOne(ctx, bson.M{"_id": articleIDs[i].ArticleID}).Decode(&article)
if err != nil {
fmt.Println(err)
}
articles = append(articles, article)
}
return &articles, nil
}
````

这种东西写的难受, 看得更难受。
pengtdyd
2021-10-24 13:41:20 +08:00
说这话的人显然没有写过 rust
wxlwsy
2021-10-24 14:07:33 +08:00
你应该没写过原生 JDBC 代码,否则也不会这么说了。
darksword21
2021-10-24 14:15:13 +08:00
那是相当难受了,特别层级很多的话每层都要检查 err
Kisesy
2021-10-24 14:20:09 +08:00
mongo 官方库就是不好用,有点底层,当然我没用过不好评价
他这设计有些不对,各种操作必须要传 ctx 这也太那啥了,本来 Go 的操作就繁琐
完全可以写成 collection.WithCtx(context.TODO()).UpdateOne(filter, update),想用 ctx 的时候才调用
要是 Go 能支持默认参数就好了,能省很多事
XTTX
2021-10-24 14:47:46 +08:00
@Kisesy 我猜当初设计的时候,mongoDB 就是要做 data warehouse,所以必须读 ctx withTimeout 。mongoDB 的市场营销是所有 tech 里做得最好的, 让所有人都觉得其他人都在用 mongoDB 。 官网的文档那叫一个省事,复杂一点的查询方式根本不介绍。 当初我找查询的语法,真的找到吐血。
balabalaguguji
2021-10-24 14:54:47 +08:00
@XTTX #10 写个复杂点的 aggregate 感觉会代码量巨大,巨难理解
balabalaguguji
2021-10-24 14:55:27 +08:00
@pengtdyd #12 没写过,还有比这更蛋疼的
balabalaguguji
2021-10-24 14:56:00 +08:00
@wxlwsy #13 写过,真没这个难受
Weny
2021-10-24 15:09:54 +08:00
简单的 filter 可以自己封装一下,几百行代码的事情。
```GO
import (
"context"
"fmt"
"strings"
)

func initQuery() *Query {
q := Query{
operation: "",
options: nil,
conditions: make(M),
field: "",
}
return &q
}

func NewQuery(fn ...func(query *Query)) *Query {
q := Query{
operation: "",
options: &Options{
skip: 0,
projection: make(M),
},
conditions: make(M),
field: "",
}
if len(fn) > 0 {
for _, f := range fn {
f(&q)
}
}
return &q
}

func SetConn(c Connection) func(query *Query) {
return func(query *Query) {
query.connection = c
}
}

func NewQueryWitConn(c Connection) *Query {
return NewQuery(SetConn(c))
}

type Query struct {
connection Connection
operation string
options *Options
conditions M
field string
model interface{}
}

func (q *Query) GetConditions() M {
return q.conditions
}

func (q *Query) GetSelect() M {
return q.options.projection
}

func mappingStringToFieldSets(value Input, projection bool) Input {
out := -1
if projection {
out = 0
}
obj := make(M)
switch value.(type) {
case string:
strArray := strings.Fields(strings.TrimSpace(value.(string)))
for _, v := range strArray {
if v[0] == '-' {
v = v[1:]
obj[v] = out
} else {
obj[v] = 1
}
}
case M:
obj = value.(M)
}
return obj
}

func (q *Query) Sort(value interface{}) *Query {
q.options.sort = mappingStringToFieldSets(value, false).(M)
return q
}

func (q *Query) Select(value interface{}) *Query {
q.options.projection = mappingStringToFieldSets(value, true).(M)
return q
}

func (q *Query) Skip(value int32) *Query {
q.options.skip = value
return q
}

func (q *Query) Where(args ...interface{}) *Query {
//q.field = field
switch len(args) {
// first args is string
case 1:
if field, ok := args[0].(string); ok {
q.Set(field)
}
// Where("version",1) where version is equals q
case 2:
if field, ok := args[0].(string); ok {
q.Set(field).Eq(args[1])
}
// Where("version",">=",1) gte 1
case 3:
if field, ok := args[0].(string); ok {
q.Set(field)
}
if operators, ok := args[1].(string); ok {
q.bindCondition(
chain(
getFlagWrapperFromString(operators),
inputBuilder,
)(args[2]),
)
}
}
return q
}

func (q *Query) Set(field string) *Query {
q.field = field
return q
}

func (q *Query) Eq(input interface{}) *Query {
q.
ensureField("Eq").
bindCondition(
chain(
inputLogger,
inputBuilder,
)(input),
).
resetField()
return q
}

func (q *Query) Equals(input interface{}) *Query {
q.
ensureField("Equals").
bindCondition(
chain(inputBuilder)(input),
).
resetField()
return q
}

func (q *Query) AutoBindConditions(flag string, condition interface{}) *Query {
if q.hasField() {
q.bindCondition(
chain(
inputBuilder,
)(condition),
).resetField()
} else {
q.bindCondition(
chain(
inputWrapper(flag),
inputBuilder,
)(condition),
).resetField()
}
return q
}

/*
e.g. query.Or([]M{{"name": "weny"}, {"age": "20"}})
*/
func (q *Query) Or(value interface{}) *Query {
flag := "$or"
return q.AutoBindConditions(flag, value)
}

/*
e.g. query.Nor([]M{{"name": "weny"}, {"age": "20"}})
*/
func (q *Query) Nor(value interface{}) *Query {
flag := "$nor"
return q.AutoBindConditions(flag, value)
}

func (q *Query) And(value interface{}) *Query {
flag := "$and"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Not(value interface{}) *Query {
flag := "$not"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Gt(value interface{}) *Query {
flag := "$gt"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Gte(value interface{}) *Query {
flag := "$gte"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Lt(value interface{}) *Query {
flag := "$lt"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Lte(value interface{}) *Query {
flag := "$lte"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Ne(value interface{}) *Query {
flag := "$ne"
return q.AutoBindConditions(flag, value)
}

func (q *Query) In(value interface{}) *Query {
flag := "$in"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Nin(value interface{}) *Query {
flag := "$nin"
return q.AutoBindConditions(flag, value)
}

func (q *Query) All(value interface{}) *Query {
flag := "$all"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Regex(value interface{}) *Query {
flag := "$regex"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Size(value interface{}) *Query {
flag := "$size"
return q.AutoBindConditions(flag, value)
}

func (q *Query) MaxDistance(value interface{}) *Query {
flag := "$maxDistance"
return q.AutoBindConditions(flag, value)
}

func (q *Query) MinDistance(value interface{}) *Query {
flag := "$minDistance"
return q.AutoBindConditions(flag, value)
}

func (q *Query) Mod(value interface{}) *Query {
flag := "$mod"
return q.AutoBindConditions(flag, value)
}

//TODO: geometry

func (q *Query) Exists(value bool) *Query {
flag := "$exists"
return q.AutoBindConditions(flag, value)
}

func (q *Query) ElemMatch(value interface{}) *Query {
flag := "$elemMatch"
return q.AutoBindConditions(flag, value)
}

func (q *Query) bindCondition(value Input) *Query {
q.conditions[q.field] = value
return q
}

func (q *Query) resetField() *Query {
q.field = ""
return q
}

func (q *Query) setField(field string) *Query {
q.field = field
return q
}
func (q *Query) hasField() bool {
if q.field == "" {
return false
}
return true
}

func (q *Query) ensureField(method string) *Query {
if q.field == "" {
panic(method + " must be used after Where() ")
}
return q
}

func inputLogger(input Input) Input {
go func() {
fmt.Print(input)
}()
return input
}

func inputWrapper(flag string) InputEndpoint {
return func(input Input) Input {
return M{flag: input}
}
}
func getFlagWrapperFromString(arg string) InputEndpoint {
switch arg {
case "<":
return inputWrapper("$lt")
case ">":
return inputWrapper("$gt")
case "=":
return inputWrapper("$eq")
case ">=":
return inputWrapper("$gte")
case "<=":
return inputWrapper("$lte")
default:
return inputWrapper("$eq")
}
}

func inputBuilder(input Input) Input {
var res interface{}
switch input.(type) {
case func(q *Query):
query := NewQuery()
input.(func(q *Query))(query)
res = query.conditions
break
case func(q *Query) *Query:
res = input.(func(q *Query) *Query)(initQuery()).conditions
break
case interface{}:
res = input
break
}
return res
}
```

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/810126

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX