Repository(或 Infra) 要如何获取
*gorm.DB对象的?是通过全局变量,还是通过结构体注入?
在 Go 的 GORM 开发中,获取 *gorm.DB 对象主要有两种流派:"简单粗暴型"和"工程严谨型"。
作为一名 Go 开发者,理解这两者的区别对你调整架构至关重要:
1. 全局变量模式(简单粗暴型)
这是很多教程里的写法。在 infra 包里定义一个全局变量 DB。
-
代码长这样:
// internal/infra/db.go var DB *gorm.DB func InitDB() { db, _ := gorm.Open(...) DB = db } -
如何调用: 在任何地方(Handler/Logic/Repo)直接调用
infra.DB.Find(&user)。 -
评价:
- 优点:写起来极快,不用传参。
- 缺点:极其难测。如果你想写单元测试,或者想连接两个不同的数据库,全局变量会让你痛不欲生。而且这种隐藏的依赖关系会导致代码越写越乱。
2. 结构体注入模式(工程严谨型 - 推荐)
这是主流微服务(如 go-zero, kratos)和互联网大厂的写法。
-
代码逻辑:
infra负责生成*gorm.DB并返回。repository定义一个结构体,把*gorm.DB存进去。logic调用repository的实例。
-
代码示例:
// internal/repository/user_repo.go type UserRepo struct { db *gorm.DB // 私有变量,不暴露给外面 } // 构造函数,创建 Repo 时必须传入 DB func NewUserRepo(db *gorm.DB) *UserRepo { return &UserRepo{db: db} } func (r *UserRepo) FindByID(id uint) (*model.User, error) { var user model.User err := r.db.First(&user, id).Error return &user, err }
3. 为什么推荐"结构体注入"?
- 链路清晰:你在
main.go启动时就能一眼看出:DB 传给了 Repo,Repo 传给了 Logic。 - 方便做"假"测试(Mock):写单元测试时,你可以传一个模拟的 DB 进去,而不需要真的连数据库。
- 支持多库:如果你以后业务大了,读写分离或者有多个库,你只需要在初始化时传入不同的 DB 实例即可,逻辑层代码一行都不用改。
4. 结合你之前的架构:如何串联起来?
如果你打算采用推荐的写法,你的 main.go 启动流程大致如下:
- 加载配置:
cfg := config.Load() - 初始化基础设施:
db := infra.NewDB(cfg)(返回*gorm.DB) - 创建 Repository:
userRepo := repository.NewUserRepo(db) - 创建 Logic:
userLogic := logic.NewUserLogic(userRepo) - 注册 Handler:
r.POST("/user", handler.NewUserHandler(userLogic))