Go Mongox 開源庫設計分享:簡化 MongoDB 開發(fā)的最佳實踐
前言
在使用 Go 語言操作 MongoDB 時,Go 開發(fā)者的首選庫通常是由 MongoDB 官方團隊推出的 mongo-go-driver。這個庫是專為 Go 語言開發(fā)者打造的,支持 MongoDB 的主要功能,并與最新版本的 MongoDB 兼容。通過 mongo-go-driver,Go 開發(fā)者可以便捷地連接數(shù)據(jù)庫,并且能對集合進行查詢、插入、更新、刪除的操作。
盡管 mongo-go-driver 功能強大,但通過進一步封裝,可以在實際開發(fā)中顯著提升開發(fā)效率,特別是在復雜場景下減少代碼冗余和提升可讀性方面。封裝后,可以有效解決以下常見的問題:
- 繁瑣的 BSON 數(shù)據(jù)編寫:構(gòu)建查詢條件、更新文檔或聚合管道時,往往需要編寫大量 BSON 數(shù)據(jù)結(jié)構(gòu)。簡單格式的 BSON 數(shù)據(jù)較易編寫,但面對復雜多層嵌套的文檔時,不僅耗時,還容易出錯。即便是一個小的疏漏,也可能導致結(jié)果偏離預期,增加了調(diào)試難度。
- 重復的反序列化代碼:在查詢不同集合的數(shù)據(jù)時,常常需要編寫重復的反序列化代碼,不僅增加了代碼冗余,也提升了維護成本。
- 聚合管道操作不夠友好:在進行聚合操作時,缺少對聚合管道的直觀支持。開發(fā)者需要手動編寫復雜的 BSON 文檔來定義管道各個階段,這增加了復雜性。
因此,我開發(fā)了 go mongox 庫并 針對這些場景進行了優(yōu)化,利用 Go 語言的泛型特性綁定結(jié)構(gòu)體,同時引入模塊化的 Creator、Updater、Deleter、Finder 和 Aggregator 等功能,分別簡化插入、更新、刪除、查詢和聚合操作。此外,go mongox 還提供了查詢、更新和聚合語句的構(gòu)建器,以減少代碼冗余,提高開發(fā)效率,幫助開發(fā)者更專注于業(yè)務邏輯的實現(xiàn)。
本文將深入解析 go mongox 開源庫的設計思路與實踐經(jīng)驗。
準備好了嗎?準備一杯你最喜歡的咖啡或茶,隨著本文一探究竟吧。
倉庫地址:https://github.com/chenmingyong0423/go-mongox
官方文檔:https://go-mongox.dev
go mongox 簡介
go mongox 是一個基于泛型的庫,擴展了 MongoDB 的官方框架。通過泛型技術(shù),它實現(xiàn)了結(jié)構(gòu)體與 MongoDB 集合的綁定,旨在提供類型安全和簡化的數(shù)據(jù)操作。go mongox 還引入鏈式調(diào)用,讓文檔操作更流暢,并且提供了豐富的 BSON 構(gòu)建器和內(nèi)置函數(shù),簡化了 BSON 數(shù)據(jù)的構(gòu)建。此外,它還支持插件化編程和內(nèi)置多種鉤子函數(shù),為數(shù)據(jù)庫操作前后的自定義邏輯提供靈活性,增強了應用的可擴展性和可維護性。
功能特性
- 泛型的 MongoDB 集合
- 文檔的 CRUD 操作
- 聚合操作
- 內(nèi)置基本的 Model 結(jié)構(gòu)體,自動化更新默認的 field 字段
- 支持 BSON 數(shù)據(jù)的構(gòu)建
- 支持結(jié)構(gòu)體 tag 校驗
- 內(nèi)置 Hooks
- 支持插件化編程
泛型的 Collection
為了將結(jié)構(gòu)體與 MongoDB 的集合進行綁定,mongox 定義了一個泛型 Collection 結(jié)構(gòu)體。通過泛型參數(shù) 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}
}
設計特點與優(yōu)勢
- 類型安全
通過泛型,Collection[T] 可以直接操作不同的數(shù)據(jù)模型類型 T,避免了傳統(tǒng)方法中的類型斷言和轉(zhuǎn)換,提高了代碼安全性和可讀性。
- 代碼復用性
泛型支持高度通用的邏輯封裝,使得 CRUD 方法只需實現(xiàn)一次,即可適配所有數(shù)據(jù)模型類型。這種復用性顯著減少了開發(fā)和維護成本。
兼容性
Collection() 方法允許用戶直接訪問底層的 *mongo.Collection,保留了原始功能,兼容復雜的 MongoDB 操作需求。
CRUD 操作器
圖片
mongox 內(nèi)置了五個獨立的操作器類型:Finder、Creator、Updater、Deleter 和 Aggregator,分別負責集合的 查找、創(chuàng)建、更新、刪除 和 聚合 操作。這些操作器實例通過 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)
}
// 省略細節(jié)代碼
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 {}
設計特點與優(yōu)勢:
- 單一職責
每個操作器聚焦于特定的集合操作,符合單一職責原則(SRP)。通過劃分不同的操作器模塊,降低了功能間的耦合度。
- 擴展性強
每個操作器獨立實現(xiàn)其功能邏輯,便于擴展。例如:如果需要新增批量更新功能,只需擴展 Updater 類型的功能。新增的功能模塊不會影響其他模塊的穩(wěn)定性。
鏈式調(diào)用支持
操作器支持鏈式調(diào)用,便于組合復雜的集合操作,讓集合操作的代碼寫起來更加絲滑。
使用示例
- Finder
user, err := userColl.Finder().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
FindOne(context.Background())
- Creator
insertOneResult, err := userColl.Creator().
InsertOne(context.Background(), &User{Name: "Mingyong Chen", Age: 18})
- Updater
updateResult, err := userColl.Updater().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
Updates(update.Set("name", "Mingyong Chen")).
UpdateOne(context.Background())
- Deleter
deleteResult, err := userColl.Deleter().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
DeleteOne(context.Background())
- Aggregator
// 忽略年齡字段,只查詢名字
users, err := userColl.Aggregator().
Pipeline(aggregation.NewStageBuilder().Project(bsonx.M("age", 0)).Build()).
Aggregate(context.Background())
鏈式調(diào)用
在設計支持鏈式調(diào)用的操作器結(jié)構(gòu)體時,需要明確結(jié)構(gòu)體的職責和需要傳遞的參數(shù)。操作器支持鏈式調(diào)用的本質(zhì)是 逐步構(gòu)建操作所需的參數(shù),最終在調(diào)用執(zhí)行方法時將參數(shù)完整地傳遞并執(zhí)行操作。
以 Updater 為例,它專注于 更新 操作,在這一場景中,鏈式調(diào)用的目標是通過連續(xù)調(diào)用方法來逐步完成以下任務:
- 設置查詢條件(filter):指定需要更新的文檔范圍。
- 定義更新內(nèi)容(updates):明確如何修改文檔的字段。
- 執(zhí)行更新操作:將構(gòu)建好的參數(shù)應用到數(shù)據(jù)庫的更新方法中。
以下是 Updater 的實現(xiàn):
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) {
// 忽略細節(jié)代碼
}
func (u *Updater[T]) UpdateMany(ctx context.Context, opts ...options.Lister[options.UpdateOptions]) (*mongo.UpdateResult, error) {
// 忽略細節(jié)代碼
}
func (u *Updater[T]) Upsert(ctx context.Context, opts ...options.Lister[options.UpdateOptions]) (*mongo.UpdateResult, error) {
// 忽略細節(jié)代碼
}
設計特點與優(yōu)勢:
- 簡潔流暢的參數(shù)構(gòu)建
每個鏈式方法負責構(gòu)建單一的操作參數(shù)(如 Filter 構(gòu)建查詢條件,Updates 構(gòu)建更新內(nèi)容),通過鏈式調(diào)用逐步完成復雜操作的參數(shù)準備,簡化了方法的使用。
- 符合直覺的調(diào)用方式
鏈式調(diào)用的代碼邏輯接近自然語言表達。例如:
updateResult, err := userColl.Updater().
Filter(query.Id("60e96214a21b1b0001c3d69e")).
Updates(update.Set("name", "Mingyong Chen")).
UpdateOne(context.Background())
- 高擴展性與一致性
鏈式方法具有一致的設計風格,新增功能時只需擴展現(xiàn)有鏈式方法,無需改動底層實現(xiàn)。例如,可以輕松為 Updater 增加 Hook 或日志功能。
對于其他操作器,例如 Creator 和 Finder 等,其設計理念也是類似的。
BSON 構(gòu)建
圖片
mongox 庫提供了強大的 BSON 數(shù)據(jù)構(gòu)建功能,幫助開發(fā)者簡化與 MongoDB 交互時復雜 BSON 數(shù)據(jù)的構(gòu)建。為了解決開發(fā)中常見的構(gòu)建復雜查詢、更新內(nèi)容以及聚合管道時的繁瑣問題,mongox 將功能劃分為以下幾個包:
- query 包
專用于構(gòu)建查詢條件的 BSON 數(shù)據(jù)。
提供了一系列鏈式構(gòu)建器和函數(shù),支持條件拼接($and、$or)、范圍查詢($gt、$lt)等復雜查詢。
- update 模塊
專注于構(gòu)建更新操作的 BSON 數(shù)據(jù),例如 $set、$inc 等。
通過清晰的鏈式操作,幫助開發(fā)者快速構(gòu)建更新內(nèi)容。
aggregation 模塊
專注于構(gòu)建 MongoDB 的聚合管道(pipeline)。
提供了分步構(gòu)建復雜聚合管道的工具,支持 $match、$group、$project 等。
bsonx 模塊
提供了一系列便捷函數(shù)和通用構(gòu)建器,用于快速構(gòu)建各種 BSON 數(shù)據(jù),覆蓋查詢、更新和聚合之外的常見需求。
query 包
為了支持簡單構(gòu)建和復雜構(gòu)建,query 包提供兩種構(gòu)建模式:直接函數(shù)構(gòu)建和構(gòu)建器構(gòu)建。
為了支持簡單查詢語句和復雜查詢語句的構(gòu)建,query 包提供了兩種靈活的構(gòu)建模式:直接函數(shù)構(gòu)建 和 構(gòu)建器構(gòu)建。這兩種方式的結(jié)合滿足了從快速構(gòu)建到復雜邏輯表達的多種需求。
直接函數(shù)構(gòu)建
通過提供簡單的函數(shù),開發(fā)者可以快速構(gòu)建包含單個操作符的 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}}}}
}
// 忽略其他函數(shù)實現(xiàn)
使用示例:
/*
{
"name": "陳明勇"
}
*/
eq := query.Eq("name", "陳明勇")
構(gòu)建器構(gòu)建
對于復雜查詢邏輯的構(gòu)建,mongox 提供了功能強大的 Builder 構(gòu)建器,通過鏈式調(diào)用的方式逐步構(gòu)建復雜的 BSON 數(shù)據(jù)。
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
}
構(gòu)建器的核心是通過組合子構(gòu)建器(如 comparisonQueryBuilder)實現(xiàn)不同操作符的邏輯。每個子構(gòu)建器提供其專屬的鏈式方法,Builder 通過組合這些方法形成完整的功能集。
子構(gòu)建器的實現(xiàn)(示例)
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
}
構(gòu)建器主功能:
- 鏈式調(diào)用:開發(fā)者可以通過連續(xù)調(diào)用 Builder 提供的方法來逐步構(gòu)建查詢條件。
- 復雜邏輯管理:不同的查詢邏輯(如比較、邏輯、數(shù)組操作)由子構(gòu)建器獨立實現(xiàn),避免了功能混亂。
使用示例:
/*
{
"age": {
"$gt": {
"$numberInt": "18"
},
"$lt": {
"$numberInt": "30"
}
}
}
*/
query.NewBuilder().Gt("age", 18).Lt("age", 30).Build()
類似于 query 包,mongox 中的其他模塊(如 update、aggregation、bsonx)也采用了類似的設計模式,提供了直接函數(shù)構(gòu)建和構(gòu)建器構(gòu)建兩種方式,支持鏈式調(diào)用以簡化復雜邏輯的構(gòu)建。接下來就不對它們多做介紹了。
設計特點與優(yōu)勢
- 靈活性:
提供兩種構(gòu)建模式,分別滿足簡單場景和復雜邏輯場景。
直接函數(shù)構(gòu)建模式適合快速開發(fā),構(gòu)建器模式支持復雜需求。
- 職責分離:
不同類型的查詢操作(如比較、邏輯、數(shù)組)由獨立的子構(gòu)建器負責實現(xiàn),代碼結(jié)構(gòu)清晰,易于擴展。
鏈式調(diào)用:
構(gòu)建器支持鏈式調(diào)用,用戶可以直觀地通過方法鏈逐步構(gòu)建查詢條件,語義清晰,代碼自然流暢。
復用性與擴展性:
新增操作符只需擴展對應子構(gòu)建器,而無需改動核心邏輯。
不同子構(gòu)建器之間可獨立維護,降低了代碼的耦合度。
插件化編程
mongox 支持插件化編程,它提供了一種靈活的方式在數(shù)據(jù)庫操作的前后插入自定義的邏輯,從而增強應用的可擴展性和可維護性。非常適合用于以下場景:
- 默認字段填充:填充 _id 和創(chuàng)建時間以及更新時間的字段值。
- 日志記錄:記錄操作前后的信息。
- 數(shù)據(jù)驗證:在插入或更新前檢查數(shù)據(jù)的有效性。
- 權(quán)限校驗:根據(jù)業(yè)務需求在操作前校驗用戶權(quán)限。
核心設計:Callback 結(jié)構(gòu)體
Callback 是 mongox 插件化編程的核心。它通過一系列鉤子屬性(如 beforeInsert、afterInsert 等)將自定義邏輯綁定到集合操作的特定階段。
// 全局回調(diào)管理器
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 {
// 忽略實現(xiàn)細節(jié),根據(jù)操作類型 opType 執(zhí)行對應的回調(diào)函數(shù)。
}
return nil
}
- 鉤子類型:
每個集合操作(如插入、更新、查詢等)都有 before 和 after 兩種鉤子。
鉤子以切片形式存儲,支持注冊多個回調(diào)函數(shù),這些函數(shù)將按順序執(zhí)行。
- callbackHandler:
name:鉤子函數(shù)的名稱,便于管理和調(diào)試。
fn:具體的回調(diào)函數(shù),實現(xiàn)自定義邏輯。
包含兩個屬性:
CbFn 回調(diào)函數(shù):
ctx:上下文,用于控制回調(diào)的生命周期。
opCtx:操作上下文,包含數(shù)據(jù)庫操作相關(guān)的參數(shù)。
opts:可選參數(shù),用于傳遞額外信息。
定義了統(tǒng)一的函數(shù)簽名,參數(shù)包括:
回調(diào)執(zhí)行邏輯
通過 Execute 方法,根據(jù)操作類型查找對應的鉤子列表,并按順序執(zhí)行回調(diào)。
如果任何一個回調(diào)函數(shù)返回錯誤,則中斷執(zhí)行并返回錯誤信息。
操作上下文:OpContext
OpContext 是回調(diào)函數(shù)的核心參數(shù),提供了集合操作相關(guān)的詳細信息,供開發(fā)者在回調(diào)函數(shù)中靈活使用。
type OpContext struct {
Col *mongo.Collection `opt:"-"` // MongoDB 集合實例
Doc any // 文檔
Filter any // 查詢條件
Updates any // 更新內(nèi)容
Replacement any // 替換內(nèi)容
MongoOptions any // MongoDB 原生選項
ModelHook any // 用于判斷綁定的結(jié)構(gòu)體是否實現(xiàn) Model Hook
}
核心字段說明:
- Col:當前操作的集合實例。
- Doc:文檔。
- Filter:操作的查詢條件,如查找、更新或刪除時使用。
- Updates:更新內(nèi)容。
- Replacement:替換操作的文檔內(nèi)容。
- MongoOptions:傳遞 MongoDB 原生的操作選項。
- ModelHook:與模型相關(guān)的自定義上下文,可擴展使用。
使用示例
- 注冊與刪除回調(diào)
// 注冊插件
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)
- 執(zhí)行回調(diào) 在實際的集合操作中,調(diào)用 Execute 方法以運行注冊的回調(diào):
err = callback.GetCallback().Execute(ctx, globalOpContext, opType)
if err != nil {
return
}
設計特點與優(yōu)勢
- 靈活性:
每個操作類型支持多個 before 和 after 鉤子,開發(fā)者可以自由組合和擴展??蓴U展性:
回調(diào)以切片形式存儲,允許動態(tài)增加、移除或替換鉤子函數(shù)。
- 統(tǒng)一性:
回調(diào)函數(shù)使用統(tǒng)一簽名,結(jié)合 OpContext 提供全面的操作上下文,便于調(diào)試和擴展。
解耦性:
集合操作與業(yè)務邏輯分離,回調(diào)機制將非核心功能獨立實現(xiàn),保持代碼簡潔和高可維護性。
小結(jié)
本文詳細介紹了 go mongox 開源庫的設計思路與實踐經(jīng)驗,涵蓋了多個核心模塊的設計與實現(xiàn),包括以下內(nèi)容:
- Collection[T] 的設計與實現(xiàn):類型安全的集合封裝;
- CRUD 操作器(如 Finder、Creator、Updater、Deleter、Aggregator):模塊化的增刪改查設計;
- 鏈式調(diào)用的實現(xiàn):簡化復雜操作的流暢調(diào)用設計;
- BSON 數(shù)據(jù)構(gòu)建包(query、update、aggregate):高效構(gòu)建查詢、更新與聚合相關(guān)的 BSON 數(shù)據(jù);
- 插件化編程的設計:通過鉤子機制靈活擴展功能。
雖然開發(fā)一個功能類似 go mongox 的庫并不復雜,但如何通過精心設計實現(xiàn)出色的擴展性、易用性和復用性,才是開發(fā)者需要深思的問題。希望這篇文章能為你提供實用的思路與經(jīng)驗。