最稳妥做法是将*gorm.DB注入Data层并用Wire绑定,需在internal/data中定义Data结构体持有该字段、NewData接收参数赋值、ProviderSet包含NewDB初始化函数,确保驱动注册、连接池配置、AutoMigrate等完整,严禁在Service/Biz层直连数据库。

直接在 Data 层注入 *gorm.DB 实例,并通过 Wire 绑定,是最稳妥、最符合 Kratos 分层规范的做法。硬塞全局变量、在 service 层直连数据库、或绕过 data 层调用 GORM,都会破坏依赖倒置和测试可替换性。
怎么把 GORM 实例注入到 Data 层
Kratos 要求数据访问逻辑收口在 internal/data,所以第一步是让 Data 结构体持有 *gorm.DB:
- 在
internal/data/data.go中给Data类型添加字段:db *gorm.DB - 修改
NewData函数签名,接收*gorm.DB参数,并赋值给结构体字段 - 确保
internal/data/data.go的ProviderSet里包含NewDB(或你自定义的 DB 初始化函数) -
NewDB应返回*gorm.DB,并完成驱动注册、连接池配置、日志钩子等初始化(如使用 MySQL:mysql.Open(dsn))
示例关键片段:
func NewDB(c *conf.Data) (*gorm.DB, error) {
dsn := c.Database.Source // 推荐从配置读取
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return nil, err
}
db.Exec("SET time_zone = '+08:00'") // 可选:统一时区
return db, nil
}
为什么不能在 Service 或 Biz 层直接用 GORM
Service 层只负责编排、校验、调用 UseCase;Biz 层定义业务契约(接口),不关心实现细节;只有 data 层才被允许持有具体数据源实例。否则会出现:
- 单元测试无法 mock 数据库行为(因为 DB 实例泄露到上层)
- 切换数据库(比如从 MySQL 换成 PostgreSQL)需修改多层代码
- 事务控制失效——
UseCase无法统一传入事务上下文,data方法无法感知当前是否在事务中 - Wire 依赖图断裂,启动时 panic 报 “missing provider”
CRUD 逻辑该写在哪一层
所有 GORM 操作必须封装在 data 层的具体 Repo 实现里,例如 UserRepo.Create、UserRepo.FindByID:
- Repo 方法只接收业务模型(如
user.User),内部转为 GORM 模型(如model.User)再操作 - 不要在 Repo 方法里做复杂业务判断(比如“积分不足则拒绝创建”),那是
UseCase的职责 - 若用
go-crud/gorm,它只是帮你生成标准方法体,仍要放在data层,且需适配你的模型和错误处理风格 - GORM 的
Preload、Joins等能力可在 Repo 内部安全使用,但别暴露原始*gorm.DB给上层
容易被忽略的初始化细节
很多失败不是因为代码逻辑错,而是初始化漏了关键项:
- 没调用
db.AutoMigrate(&model.User{})—— 表不会自动建,报ERROR: relation "users" does not exist - 没设置连接池:
db.DB().SetMaxOpenConns(100),高并发下直接卡死 - MySQL DSN 缺少
parseTime=True&loc=Local,导致time.Time字段解析出错或时区偏移 - Wire 注入顺序错乱:比如
NewData依赖*gorm.DB,但ProviderSet里没先声明NewDB,启动时报cannot find value for type *gorm.DB
最终效果是:data 层拿到的是一个可用、可测、可换、可事务化的 *gorm.DB 实例,其余层完全无感。











