Go Mongox 开源库设计分享:简化 MongoDB 开发的最佳实践

前言

在使用 Go 语言操作 MongoDB 时,Go 开发者的首选库通常是由 MongoDB 官方团队推出的 mongo-go-driver。这个库是专为 Go 语言开发者打造的,支持 MongoDB 的主要功能,并与最新版本的 MongoDB 兼容。通过 mongo-go-driver,Go 开发者可以便捷地连接数据库,并且能对集合进行查询、插入、更新、删除的操作。

尽管 mongo-go-driver 功能强大,但通过进一步封装,可以在实际开发中显著提升开发效率,特别是在复杂场景下减少代码冗余和提升可读性方面。封装后,可以有效解决以下常见的问题:

繁琐的 BSON 数据编写:构建查询条件、更新文档或聚合管道时,往往需要编写大量 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 简介

go mongox 是一个基于泛型的库,扩展了 MongoDB 的官方框架。通过泛型技术,它实现了结构体与 MongoDB 集合的绑定,旨在提供类型安全和简化的数据操作。go mongox 还引入链式调用,让文档操作更流畅,并且提供了丰富的 BSON 构建器和内置函数,简化了 BSON 数据的构建。此外,它还支持插件化编程和内置多种钩子函数,为数据库操作前后的自定义逻辑提供灵活性,增强了应用的可扩展性和可维护性。

功能特性

泛型的 MongoDB 集合文档的 CRUD 操作聚合操作内置基本的 Model 结构体,自动化更新默认的 field 字段支持 BSON 数据的构建支持结构体 tag 校验内置 Hooks支持插件化编程

泛型的 Collection

为了将结构体与 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} }1.2.3.4.5.6.7.8.9.10.11.

设计特点与优势

类型安全

通过泛型,Collection[T] 可以直接操作不同的数据模型类型 T,避免了传统方法中的类型断言和转换,提高了代码安全性和可读性。

代码复用性

泛型支持高度通用的逻辑封装,使得 CRUD 方法只需实现一次,即可适配所有数据模型类型。这种复用性显著减少了开发和维护成本。

兼容性

Collection() 方法允许用户直接访问底层的 *mongo.Collection,保留了原始功能,兼容复杂的 MongoDB 操作需求。

CRUD 操作器

图片

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) }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.
复制
// 省略细节代码 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 {}1.2.3.4.5.6.7.8.9.10.11.

设计特点与优势:

单一职责

每个操作器聚焦于特定的集合操作,符合单一职责原则(SRP)。通过划分不同的操作器模块,降低了功能间的耦合度。

扩展性强

每个操作器独立实现其功能逻辑,便于扩展。例如:如果需要新增批量更新功能,只需扩展 Updater 类型的功能。新增的功能模块不会影响其他模块的稳定性。

链式调用支持

操作器支持链式调用,便于组合复杂的集合操作,让集合操作的代码写起来更加丝滑。

使用示例

Finder
复制
user, err := userColl.Finder(). Filter(query.Id("60e96214a21b1b0001c3d69e")). FindOne(context.Background())1.2.3.
Creator
复制
insertOneResult, err := userColl.Creator(). InsertOne(context.Background(), &User{Name: "Mingyong Chen", Age: 18})1.2.
Updater
复制
updateResult, err := userColl.Updater(). Filter(query.Id("60e96214a21b1b0001c3d69e")). Updates(update.Set("name", "Mingyong Chen")). UpdateOne(context.Background())1.2.3.4.
Deleter
复制
deleteResult, err := userColl.Deleter(). Filter(query.Id("60e96214a21b1b0001c3d69e")). DeleteOne(context.Background())1.2.3.
Aggregator
复制
// 忽略年龄字段,只查询名字 users, err := userColl.Aggregator(). Pipeline(aggregation.NewStageBuilder().Project(bsonx.M("age", 0)).Build()). Aggregate(context.Background())1.2.3.4.

链式调用

在设计支持链式调用的操作器结构体时,需要明确结构体的职责和需要传递的参数。操作器支持链式调用的本质是 逐步构建操作所需的参数,最终在调用执行方法时将参数完整地传递并执行操作。

以 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) { // 忽略细节代码 }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.

设计特点与优势:

简洁流畅的参数构建

每个链式方法负责构建单一的操作参数(如 Filter 构建查询条件,Updates 构建更新内容),通过链式调用逐步完成复杂操作的参数准备,简化了方法的使用。

符合直觉的调用方式

链式调用的代码逻辑接近自然语言表达。例如:

复制
updateResult, err := userColl.Updater(). Filter(query.Id("60e96214a21b1b0001c3d69e")). Updates(update.Set("name", "Mingyong Chen")). UpdateOne(context.Background())1.2.3.4.
高扩展性与一致性

链式方法具有一致的设计风格,新增功能时只需扩展现有链式方法,无需改动底层实现。例如,可以轻松为 Updater 增加 Hook 或日志功能。

对于其他操作器,例如 Creator 和 Finder 等,其设计理念也是类似的。

BSON 构建

图片

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 包提供两种构建模式:直接函数构建和构建器构建。

为了支持简单查询语句和复杂查询语句的构建,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}}}} } // 忽略其他函数实现1.2.3.4.5.6.7.8.9.

使用示例:

复制
/* { "name": "陈明勇" } */ eq := query.Eq("name", "陈明勇")1.2.3.4.5.6.
构建器构建

对于复杂查询逻辑的构建,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 }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.

构建器的核心是通过组合子构建器(如 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 }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.

构建器主功能:

链式调用:开发者可以通过连续调用 Builder 提供的方法来逐步构建查询条件。复杂逻辑管理:不同的查询逻辑(如比较、逻辑、数组操作)由子构建器独立实现,避免了功能混乱。

使用示例:

复制
/* { "age": { "$gt": { "$numberInt": "18" }, "$lt": { "$numberInt": "30" } } } */ query.NewBuilder().Gt("age", 18).Lt("age", 30).Build()1.2.3.4.5.6.7.8.9.10.11.12.13.

类似于 query 包,mongox 中的其他模块(如 update、aggregation、bsonx)也采用了类似的设计模式,提供了直接函数构建和构建器构建两种方式,支持链式调用以简化复杂逻辑的构建。接下来就不对它们多做介绍了。

设计特点与优势

灵活性:

提供两种构建模式,分别满足简单场景和复杂逻辑场景。

直接函数构建模式适合快速开发,构建器模式支持复杂需求。

职责分离:

不同类型的查询操作(如比较、逻辑、数组)由独立的子构建器负责实现,代码结构清晰,易于扩展。

链式调用:

构建器支持链式调用,用户可以直观地通过方法链逐步构建查询条件,语义清晰,代码自然流畅。

复用性与扩展性:

新增操作符只需扩展对应子构建器,而无需改动核心逻辑。

不同子构建器之间可独立维护,降低了代码的耦合度。

插件化编程

mongox 支持插件化编程,它提供了一种灵活的方式在数据库操作的前后插入自定义的逻辑,从而增强应用的可扩展性和可维护性。非常适合用于以下场景:

默认字段填充:填充 _id 和创建时间以及更新时间的字段值。日志记录:记录操作前后的信息。数据验证:在插入或更新前检查数据的有效性。权限校验:根据业务需求在操作前校验用户权限。

核心设计:Callback 结构体

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 }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.
钩子类型:

每个集合操作(如插入、更新、查询等)都有 before 和 after 两种钩子。

钩子以切片形式存储,支持注册多个回调函数,这些函数将按顺序执行。

callbackHandler:

name:钩子函数的名称,便于管理和调试。

fn:具体的回调函数,实现自定义逻辑。

包含两个属性:

CbFn 回调函数:

ctx:上下文,用于控制回调的生命周期。

opCtx:操作上下文,包含数据库操作相关的参数。

opts:可选参数,用于传递额外信息。

定义了统一的函数签名,参数包括:

回调执行逻辑

通过 Execute 方法,根据操作类型查找对应的钩子列表,并按顺序执行回调。

如果任何一个回调函数返回错误,则中断执行并返回错误信息。

操作上下文:OpContext

OpContext 是回调函数的核心参数,提供了集合操作相关的详细信息,供开发者在回调函数中灵活使用。

复制
type OpContext struct { Col *mongo.Collection `opt:"-"` // MongoDB 集合实例 Doc any // 文档 Filter any // 查询条件 Updates any // 更新内容 Replacement any // 替换内容 MongoOptions any // MongoDB 原生选项 ModelHook any // 用于判断绑定的结构体是否实现 Model Hook }1.2.3.4.5.6.7.8.9.

核心字段说明:

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)1.2.3.4.5.6.7.8.9.10.11.12.13.
执行回调 在实际的集合操作中,调用 Execute 方法以运行注册的回调:
复制
err = callback.GetCallback().Execute(ctx, globalOpContext, opType) if err != nil { return }1.2.3.4.

设计特点与优势

灵活性:

每个操作类型支持多个 before 和 after 钩子,开发者可以自由组合和扩展。可扩展性:

回调以切片形式存储,允许动态增加、移除或替换钩子函数。

统一性:

回调函数使用统一签名,结合 OpContext 提供全面的操作上下文,便于调试和扩展。

解耦性:

集合操作与业务逻辑分离,回调机制将非核心功能独立实现,保持代码简洁和高可维护性。

小结

本文详细介绍了 go mongox 开源库的设计思路与实践经验,涵盖了多个核心模块的设计与实现,包括以下内容:

Collection[T] 的设计与实现:类型安全的集合封装;CRUD 操作器(如 Finder、Creator、Updater、Deleter、Aggregator):模块化的增删改查设计;链式调用的实现:简化复杂操作的流畅调用设计;BSON 数据构建包(query、update、aggregate):高效构建查询、更新与聚合相关的 BSON 数据;插件化编程的设计:通过钩子机制灵活扩展功能。

虽然开发一个功能类似 go mongox 的库并不复杂,但如何通过精心设计实现出色的扩展性、易用性和复用性,才是开发者需要深思的问题。希望这篇文章能为你提供实用的思路与经验。

THE END
本站服务器由亿华云赞助提供-企业级高防云服务器