文章:求求测试们了,发现BUG后要这么提 评语:这篇文章以实用性和清晰性为核心,从开发视角出发,为测试人员提供了规范提交BUG的详细指南,旨在减少沟通成本、提升效率,是一篇针对实际问题的实战手册。
在使用 Go
语言操作 MongoDB
时,Go
开发者的首选库通常是由 MongoDB
官方团队推出的 mongo-go-driver
。这个库是专为 Go
语言开发者打造的,支持 MongoDB
的主要功能,并与最新版本的 MongoDB
兼容。通过 mongo-go-driver
,Go
开发者可以便捷地连接数据库,并且能对集合进行查询、插入、更新、删除的操作。
尽管 mongo-go-driver
功能强大,但通过进一步封装,可以在实际开发中显著提升开发效率,特别是在复杂场景下减少代码冗余和提升可读性方面。封装后,可以有效解决以下常见的问题:
BSON
数据结构。简单格式的 BSON
数据较易编写,但面对复杂多层嵌套的文档时,不仅耗时,还容易出错。即便是一个小的疏漏,也可能导致结果偏离预期,增加了调试难度。BSON
文档来定义管道各个阶段,这增加了复杂性。因此,我开发了 go mongox
库并针对这些场景进行了优化,利用 Go
语言的泛型特性绑定结构体,同时引入模块化的 Creator
、Updater
、Deleter
、Finder
和 Aggregator
等功能,分别简化插入、更新、删除、查询和聚合操作。此外,go mongox
还提供了查询、更新和聚合语句的构建器,以减少代码冗余,提高开发效率,帮助开发者更专注于业务逻辑的实现。
本文将深入解析 go mongox
开源库的设计思路与实践经验。
准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
仓库地址:https://github.com/chenmingyong0423/go-mongox 官方文档:https://go-mongox.dev
go mongox
是一个基于泛型的库,扩展了 MongoDB
的官方框架。通过泛型技术,它实现了结构体与 MongoDB
集合的绑定,旨在提供类型安全和简化的数据操作。go mongox
还引入链式调用,让文档操作更流畅,并且提供了丰富的 BSON
构建器和内置函数,简化了 BSON
数据的构建。此外,它还支持插件化编程和内置多种钩子函数,为数据库操作前后的自定义逻辑提供灵活性,增强了应用的可扩展性和可维护性。
MongoDB
集合CRUD
操作Model
结构体,自动化更新默认的 field
字段BSON
数据的构建tag
校验Hooks
为了将结构体与 MongoDB
的集合进行绑定,mongox
定义了一个泛型 Collection
结构体。通过泛型参数 T any
,它提供了类型安全的 MongoDB
集合操作,同时保留了对原始 *mongo.Collection
的访问。
type Collection[T any] struct {
collection *mongo.Collection
}
func (c *Collection[T]) Collection() *mongo.Collection {
return c.collection
}
func NewCollection[T any](collection *mongo.Collection) *Collection[T] {
return &Collection[T]{collection: collection}
}
设计特点与优势
Collection[T]
可以直接操作不同的数据模型类型 T
,避免了传统方法中的类型断言和转换,提高了代码安全性和可读性。CRUD
方法只需实现一次,即可适配所有数据模型类型。这种复用性显著减少了开发和维护成本。Collection()
方法允许用户直接访问底层的 *mongo.Collection
,保留了原始功能,兼容复杂的 MongoDB
操作需求。mongox
内置了五个独立的操作器类型:Finder
、Creator
、Updater
、Deleter
和 Aggregator
,分别负责集合的 查找、创建、更新、删除 和 聚合 操作。这些操作器实例通过 Collection[T]
对象提供,且每个操作器聚焦于一个具体的集合操作。
func (c *Collection[T]) Finder() *finder.Finder[T] {
return finder.NewFinder[T](c.collection)
}
func (c *Collection[T]) Creator() *creator.Creator[T] {
return creator.NewCreator[T](c.collection)
}
func (c *Collection[T]) Updater() *updater.Updater[T] {
return updater.NewUpdater[T](c.collection)
}
func (c *Collection[T]) Deleter() *deleter.Deleter[T] {
return deleter.NewDeleter[T](c.collection)
}
func (c *Collection[T]) Aggregator() *aggregator.Aggregator[T] {
return aggregator.NewAggregator[T](c.collection)
}
// 省略细节代码
type Finder[T any] struct {}
type Creator[T any] struct {}
type Updater[T any] struct {}
type Deleter[T any] struct {}
type Aggregator[T any] struct {}
设计特点与优势:
SRP
)。通过划分不同的操作器模块,降低了功能间的耦合度。Updater
类型的功能。新增的功能模块不会影响其他模块的稳定性。user, err := userColl.Finder().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
FindOne(context.Background())
insertOneResult, err := userColl.Creator().
InsertOne(context.Background(), &User{Name: "Mingyong Chen", Age: 18})
updateResult, err := userColl.Updater().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
Updates(update.Set("name", "Mingyong Chen")).
UpdateOne(context.Background())
deleteResult, err := userColl.Deleter().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
DeleteOne(context.Background())
// 忽略年龄字段,只查询名字
users, err := userColl.Aggregator().
Pipeline(aggregation.NewStageBuilder().Project(bsonx.M("age", 0)).Build()).
Aggregate(context.Background())
在设计支持链式调用的操作器结构体时,需要明确结构体的职责和需要传递的参数。操作器支持链式调用的本质是 逐步构建操作所需的参数,最终在调用执行方法时将参数完整地传递并执行操作。
以 Updater
为例,它专注于 更新 操作,在这一场景中,链式调用的目标是通过连续调用方法来逐步完成以下任务:
filter
):指定需要更新的文档范围。updates
):明确如何修改文档的字段。以下是 Updater
的实现:
type Updater[T any] struct {
collection *mongo.Collection
filter any
updates any
}
func (u *Updater[T]) Filter(filter any) *Updater[T] {
u.filter = filter
return u
}
func (u *Updater[T]) Updates(updates any) *Updater[T] {
u.updates = updates
return u
}
func (u *Updater[T]) UpdateOne(ctx context.Context, opts ...options.Lister[options.UpdateOptions]) (*mongo.UpdateResult, error) {
// 忽略细节代码
}
func (u *Updater[T]) UpdateMany(ctx context.Context, opts ...options.Lister[options.UpdateOptions]) (*mongo.UpdateResult, error) {
// 忽略细节代码
}
func (u *Updater[T]) Upsert(ctx context.Context, opts ...options.Lister[options.UpdateOptions]) (*mongo.UpdateResult, error) {
// 忽略细节代码
}
设计特点与优势:
Filter
构建查询条件,Updates
构建更新内容),通过链式调用逐步完成复杂操作的参数准备,简化了方法的使用。updateResult, err := userColl.Updater().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
Updates(update.Set("name", "Mingyong Chen")).
UpdateOne(context.Background())
Updater
增加 Hook
或日志功能。对于其他操作器,例如 Creator
和 Finder
等,其设计理念也是类似的。
mongox
库提供了强大的 BSON
数据构建功能,帮助开发者简化与 MongoDB
交互时复杂 BSON
数据的构建。为了解决开发中常见的构建复杂查询、更新内容以及聚合管道时的繁琐问题,mongox
将功能划分为以下几个包:
query
包BSON
数据。$and
、$or
)、范围查询($gt
、$lt
)等复杂查询。update
模块BSON
数据,例如 $set
、$inc
等。aggregation
模块MongoDB
的聚合管道(pipeline
)。$match
、$group
、$project
等。bsonx
模块BSON
数据,覆盖查询、更新和聚合之外的常见需求。为了支持简单构建和复杂构建,query
包提供两种构建模式:直接函数构建和构建器构建。
为了支持简单查询语句和复杂查询语句的构建,query
包提供了两种灵活的构建模式:直接函数构建 和 构建器构建。这两种方式的结合满足了从快速构建到复杂逻辑表达的多种需求。
通过提供简单的函数,开发者可以快速构建包含单个操作符的 BSON
查询条件。这种方式适用于无需组合逻辑的简单查询。
func Eq(key string, value any) bson.D {
return bson.D{bson.E{Key: key, Value: bson.D{{Key: "$eq", Value: value}}}}
}
func Lt(key string, value any) bson.D {
return bson.D{bson.E{Key: key, Value: bson.D{{Key: "$lt", Value: value}}}}
}
// 忽略其他函数实现
使用示例:
// {
// "name": "陈明勇"
// }
eq := query.Eq("name", "陈明勇")
对于复杂查询逻辑的构建,mongox
提供了功能强大的 Builder
构建器,通过链式调用的方式逐步构建复杂的 BSON
数据。
func NewBuilder() *Builder {
query := &Builder{
data: bson.D{},
err: make([]error, 0),
}
query.comparisonQueryBuilder = comparisonQueryBuilder{parent: query}
query.logicalQueryBuilder = logicalQueryBuilder{parent: query}
query.elementQueryBuilder = elementQueryBuilder{parent: query}
query.arrayQueryBuilder = arrayQueryBuilder{parent: query}
query.evaluationQueryBuilder = evaluationQueryBuilder{parent: query}
query.projectionQueryBuilder = projectionQueryBuilder{parent: query}
return query
}
type Builder struct {
data bson.D
comparisonQueryBuilder
logicalQueryBuilder
elementQueryBuilder
arrayQueryBuilder
evaluationQueryBuilder
projectionQueryBuilder
}
func (b *Builder) Build() bson.D {
return b.data
}
构建器的核心是通过组合子构建器(如 comparisonQueryBuilder
)实现不同操作符的逻辑。每个子构建器提供其专属的链式方法,Builder
通过组合这些方法形成完整的功能集。
子构建器的实现(示例)
type comparisonQueryBuilder struct {
parent *Builder
}
func (b *comparisonQueryBuilder) Eq(key string, value any) *Builder {
e := bson.E{Key: EqOp, Value: value}
if !b.parent.tryMergeValue(key, e) {
b.parent.data = append(b.parent.data, bson.E{Key: key, Value: bson.D{e}})
}
return b.parent
}
func (b *comparisonQueryBuilder) Gt(key string, value any) *Builder {
e := bson.E{Key: GtOp, Value: value}
if !b.parent.tryMergeValue(key, e) {
b.parent.data = append(b.parent.data, bson.E{Key: key, Value: bson.D{e}})
}
return b.parent
}
func (b *comparisonQueryBuilder) Lt(key string, value any) *Builder {
e := bson.E{Key: LtOp, Value: value}
if !b.parent.tryMergeValue(key, e) {
b.parent.data = append(b.parent.data, bson.E{Key: key, Value: bson.D{e}})
}
return b.parent
}
构建器主功能:
Builder
提供的方法来逐步构建查询条件。使用示例:
// {
// "age": {
// "$gt": {
// "$numberInt": "18"
// },
// "$lt": {
// "$numberInt": "30"
// }
// }
// }
query.NewBuilder().Gt("age", 18).Lt("age", 30).Build()
类似于 query
包,mongox
中的其他模块(如 update
、aggregation
、bsonx
)也采用了类似的设计模式,提供了直接函数构建和构建器构建两种方式,支持链式调用以简化复杂逻辑的构建。接下来就不对它们多做介绍了。
设计特点与优势
mongox
支持插件化编程,它提供了一种灵活的方式在数据库操作的前后插入自定义的逻辑,从而增强应用的可扩展性和可维护性。非常适合用于以下场景:
_id
和创建时间以及更新时间的字段值。Callback
是 mongox
插件化编程的核心。它通过一系列钩子属性(如 beforeInsert
、afterInsert
等)将自定义逻辑绑定到集合操作的特定阶段。
// 全局回调管理器
var Callbacks = initializeCallbacks()
// 初始化 Callback
func initializeCallbacks() *Callback {
return &Callback{
beforeInsert: make([]callbackHandler, 0),
afterInsert: make([]callbackHandler, 0),
beforeUpdate: make([]callbackHandler, 0),
afterUpdate: make([]callbackHandler, 0),
beforeDelete: make([]callbackHandler, 0),
afterDelete: make([]callbackHandler, 0),
beforeUpsert: make([]callbackHandler, 0),
afterUpsert: make([]callbackHandler, 0),
beforeFind: make([]callbackHandler, 0),
afterFind: make([]callbackHandler, 0),
}
}
type Callback struct {
beforeInsert []callbackHandler
afterInsert []callbackHandler
beforeUpdate []callbackHandler
afterUpdate []callbackHandler
beforeDelete []callbackHandler
afterDelete []callbackHandler
beforeUpsert []callbackHandler
afterUpsert []callbackHandler
beforeFind []callbackHandler
afterFind []callbackHandler
}
type callbackHandler struct {
name string
fn CbFn
}
type CbFn func(ctx context.Context, opCtx *operation.OpContext, opts ...any) error
// operation_type.go
type OpContext struct {
Col *mongo.Collection `opt:"-"`
Doc any
// filter also can be used as query
Filter any
Updates any
Replacement any
MongoOptions any
ModelHook any
}
func (c *Callback) Execute(ctx context.Context, opCtx *operation.OpContext, opType operation.OpType, opts ...any) error {
switch opType {
// 忽略实现细节,根据操作类型 opType 执行对应的回调函数。
}
return nil
}
before
和 after
两种钩子。callbackHandler
:name
:钩子函数的名称,便于管理和调试。fn
:具体的回调函数,实现自定义逻辑。CbFn
回调函数:ctx
:上下文,用于控制回调的生命周期。opCtx
:操作上下文,包含数据库操作相关的参数。opts
:可选参数,用于传递额外信息。Execute
方法,根据操作类型查找对应的钩子列表,并按顺序执行回调。OpContext
是回调函数的核心参数,提供了集合操作相关的详细信息,供开发者在回调函数中灵活使用。
type OpContext struct {
Col *mongo.Collection `opt:"-"` // MongoDB 集合实例
Doc any // 文档
Filter any // 查询条件
Updates any // 更新内容
Replacement any // 替换内容
MongoOptions any // MongoDB 原生选项
ModelHook any // 用于判断绑定的结构体是否实现 Model Hook
}
核心字段说明:
Col
:当前操作的集合实例。Doc
:文档。Filter
:操作的查询条件,如查找、更新或删除时使用。Updates
:更新内容。Replacement
:替换操作的文档内容。MongoOptions
:传递 MongoDB
原生的操作选项。ModelHook
:与模型相关的自定义上下文,可扩展使用。// 注册插件
mongox.RegisterPlugin("after find", func(ctx context.Context, opCtx *operation.OpContext, opts ...any) error {
if user, ok := opCtx.Doc.(*User); ok {
fmt.Println(user)
}
if users, ok := opCtx.Doc.([]*User); ok {
fmt.Println(users)
}
return nil
}, operation.OpTypeAfterFind)
// 删除插件
mongox.RemovePlugin("after find", operation.OpTypeAfterFind)
Execute
方法以运行注册的回调:err = callback.GetCallback().Execute(ctx, globalOpContext, opType)
if err != nil {
return
}
before
和 after
钩子,开发者可以自由组合和扩展。
可扩展性:
- 回调以切片形式存储,允许动态增加、移除或替换钩子函数。OpContext
提供全面的操作上下文,便于调试和扩展。本文详细介绍了 go mongox
开源库的设计思路与实践经验,涵盖了多个核心模块的设计与实现,包括以下内容:
Collection[T]
的设计与实现:类型安全的集合封装;CRUD
操作器(如 Finder
、Creator
、Updater
、Deleter
、Aggregator
):模块化的增删改查设计;BSON
数据构建包(query
、update
、aggregate
):高效构建查询、更新与聚合相关的 BSON
数据;虽然开发一个功能类似 go mongox
的库并不复杂,但如何通过精心设计实现出色的扩展性、易用性和复用性,才是开发者需要深思的问题。希望这篇文章能为你提供实用的思路与经验。
一起参与贡献吧,让
go mongox
更加实用! 如果有您的加入,go mongox 将会变得更加实用!
你好,我是陈明勇,一名热爱技术、乐于分享的开发者,同时也是开源爱好者。
成功的路上并不拥挤,有没有兴趣结个伴?
关注我,加我好友,一起学习一起进步!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。