高效的事务封装函数,使用MongoDB Go驱动程序。

huangapple go评论79阅读模式
英文:

Efficient transaction wrapper function with mongodb go driver

问题

我目前正在将事务逻辑集成到我的go+mongodb API中。

我已经创建了这个示例端点。它允许您检索用户文档并将其以JSON编码发送回客户端。

func GetUser(w http.ResponseWriter, r *http.Request) {
    id := mux.Vars(r)["user-id"]
    objectId, _ := primitive.ObjectIDFromHex(id)

    user, err := UserById(objectId)
    if err != nil {
        // ...
    }

    // 对用户进行一些操作,无论是什么

    // 最终将其发送回去
    json.NewEncoder(w).Encode(user)
}

func UserById(id primitive.ObjectID) (models.StoredUser, error) {
    session, err := mongoClient.StartSession()
    if err != nil {
        return models.StoredUser{}, fmt.Errorf("failed starting session for transaction | %s", err.Error())
    }
    defer session.EndSession(context.TODO())

    callback := func(ctx mongo.SessionContext) (any, error) {
        res := usersCollection.FindOne(
            ctx,
            bson.M{
                "_id": id,
            },
        )

        if res.Err() != nil {
            return models.StoredUser{}, fmt.Errorf("failed querying db | %s", res.Err())
        }

        return res, nil
    }

    result, err := session.WithTransaction(context.TODO(), callback)
    if err != nil {
        return models.StoredUser{}, fmt.Errorf("failed executing transaction | %s", err.Error())
    }

    asserted := result.(*mongo.SingleResult)
    var ret models.StoredUser
    if err := asserted.Decode(&ret); err != nil {
        return models.StoredUser{}, fmt.Errorf("failed parsing user data in struct | %s", err.Error())
    }

    return ret, nil
}

以下是大致的步骤:

  1. 解析请求内容以获取用户ID
  2. 创建会话以执行事务
  3. 使用ID参数声明回调函数
  4. 从事务中调用回调函数
  5. *mongo.SingleResult作为interface{}返回,并将其解析回原始类型
  6. 解码包含在*mongo.SingleResult中的bson文档,将其放入返回的结构体中

这个函数可以工作,但是非常冗长。代码非常重复。

我想知道是否有一种方法可以避免为每个要创建的函数重复相同的代码。我的之前的包装函数尝试没有取得任何成果,因为实际上我每次调用时都需要这些变量。

尽管如此,步骤2和5似乎特别冗余和低效。

有人有任何想法吗?

英文:

I'm currently integrating the transaction logic into my go+mongodb api.

I already created this example endpoint. It allows you to retrieve a user document and send it back to the client with json encoding.

func GetUser(w http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["user-id"]
	objectId, _ := primitive.ObjectIDFromHex(id)

	user, err := UserById(objectId)
	if err != nil {
		// ...
	}

	// do some stuff with the user, whatever

    // eventually send it back
	json.NewEncoder(w).Encode(user)
}

func UserById(id primitive.ObjectID) (models.StoredUser, error) {
	session, err := mongoClient.StartSession()
	if err != nil {
		return models.StoredUser{}, fmt.Errorf("failed starting session for transaction | %s", err.Error())
	}
	defer session.EndSession(context.TODO())

	callback := func(ctx mongo.SessionContext) (any, error) {
		res := usersCollection.FindOne(
			ctx,
			bson.M{
				"_id": id,
			},
		)

		if res.Err() != nil {
			return models.StoredUser{}, fmt.Errorf("failed querying db | %s", res.Err())
		}

		return res, nil
	}

	result, err := session.WithTransaction(context.TODO(), callback)
	if err != nil {
		return models.StoredUser{}, fmt.Errorf("failed executing transaction | %s", err.Error())
	}

	asserted := result.(*mongo.SingleResult)
	var ret models.StoredUser
	if err := asserted.Decode(&ret); err != nil {
		return models.StoredUser{}, fmt.Errorf("failed parsing user data in struct | %s", err.Error())
	}

	return ret, nil
}

Here are the big steps :

  1. Parse the request content to get the user id
  2. Create a session to perform the transaction
  3. Declare the callback function using the id argument
  4. Call the callback function from a transaction
  5. Get back the *mongo.SingleResult as an interface{} and parsing it back to its original type
  6. Decode the bson document contained in the *mongo.SingleResult to put it in the return struct

This function works, but is very verbose. The code is very duplicated.

I wonder if there is a way of not repeating the same code for each function I wanna make. My previous wrapper function attempts didn't lead to anything, as I actually need the variables where they are now at each call.

Still, the steps 2 and 5 especially seem very redundant and inefficient.

Anyone got any idea ?

答案1

得分: 0

好的,以下是翻译好的内容:

func Transaction(callback func(ctx mongo.SessionContext) (any, error)) (any, error) {
	session, err := mongoClient.StartSession()
	if err != nil {
		return nil, fmt.Errorf("创建会话失败 | %s", err.Error())
	}

	defer session.EndSession(context.TODO())

	res, err := session.WithTransaction(ctx, callback)
	if err != nil {
		return nil, fmt.Errorf("执行事务失败 | %s", err.Error())
	}

	return res, nil
}

假设我想获取用户对象:

func GetUsers() ([]models.User, error) {
	callback := func(ctx mongo.SessionContext) (any, error) {
		res, err := usersCollection.Find(ctx, bson.M{})
		if err != nil {
			return nil, fmt.Errorf("查询用户集合失败 | %s", err.Error())
		}

		var ret []models.User
		if err := res.All(context.TODO(), &ret); err != nil {
			return nil, fmt.Errorf("解析结果到结构体失败 | %s", err.Error())
		}

		return ret, nil
	}

	result, err := Transaction(callback)
	if err != nil {
		return []models.User{}, fmt.Errorf("执行事务失败 | %s", err.Error())
	}

	users, _ := result.([]models.User)
	return users, nil
}
英文:

Ok I found the following :

func Transaction(callback func(ctx mongo.SessionContext) (any, error)) (any, error) {
	session, err := mongoClient.StartSession()
	if err != nil {
		return nil, fmt.Errorf("failed creating session | %s", err.Error())
	}

	defer session.EndSession(context.TODO())

	res, err := session.WithTransaction(ctx, callback)
	if err != nil {
		return nil, fmt.Errorf("failed executing transaction | %s", err.Error())
	}

	return res, nil
}

Let's say i want then to fetch user objects :


func GetUsers() ([]models.User, error) {
	callback := func(ctx mongo.SessionContext) (any, error) {
		res, err := usersCollection.Find(ctx, bson.M{})
		if err != nil {
			return nil, fmt.Errorf("failed querying users collection | %s", err.Error())
		}

		var ret []models.User
		if err := res.All(context.TODO(), &ret); err != nil {
			return nil, fmt.Errorf("failed parsing results in struct | %s", err.Error())
		}

		return ret, nil
	}

	result, err := Transaction(callback)
	if err != nil {
		return []models.User{}, fmt.Errorf("failed executing transaction | %s", err.Error())
	}

	classes, _ := result.([]models.StoredClass)
	return classes, nil
}

huangapple
  • 本文由 发表于 2022年8月6日 18:16:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/73258897.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定