go 无需内置或第三方di框架,应通过显式构造函数传递依赖;手动di即在main中自底向上调用newxxx初始化,依赖作为参数传入,确保关系显性化、可测试、易调试。

Go 本身没有内置的依赖注入(DI)机制,也不需要——你得自己组织好构造函数和接口,再靠显式传递来“注入”,否则容易掉进反射、隐藏依赖、测试难的坑里。
为什么不用第三方 DI 框架(比如 wire / dig)?
多数小到中型项目根本用不上。wire 是代码生成,dig 是运行时反射,两者都增加了构建复杂度和调试成本。
-
wire要写wire.go文件、加//+build wireinject标签、跑wire gen,改个依赖就得重生成 -
dig的错误信息是运行时 panic,比如missing type *http.Client,但你根本不知道哪层调用链漏传了 - 接口没定义清楚时,DI 容器反而掩盖设计缺陷:本该拆成两个接口的,硬塞进一个 struct 里靠 DI “凑活用”
怎么手动做 DI —— 从 main 函数开始串起依赖
核心就一条:把依赖当参数传进去,用构造函数(不是 new)封装初始化逻辑。
- 每个 service 或 handler 都该有明确的
NewXXX函数,参数全是它真正需要的依赖(*sql.DB、Logger、Cache等) - main 函数是唯一的“组装点”:先创建底层依赖(DB、Logger),再一层层往上 New,最后传给 HTTP server 或 CLI 入口
- 避免在结构体里留未初始化的字段,比如
db *sql.DB不赋值就直接用 —— 这会导致 nil panic,且无法被静态检查捕获
示例:NewUserService 显式要 db *sql.DB 和 cache Cache,不接受 config map[string]interface{} 这种模糊输入:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
func NewUserService(db *sql.DB, cache Cache) *UserService {
return &UserService{db: db, cache: cache}
}
什么时候该考虑用 wire?
只有当你的依赖树深、交叉多、且手动 New 开始重复粘贴时才值得引入。
- 典型场景:多个 handler 共享同一组 service,而这些 service 又各自依赖 DB、Redis、第三方 client,且初始化逻辑分散在不同包里
- wire 不解决设计问题,只解决“写太多 New 调用”的体力活;如果连
NewUserRepo和NewOrderRepo该不该共享同一个*sql.DB都没想清,加 wire 只会让混乱更隐蔽 - 注意 wire 生成的代码不可读、不可 debug —— panic 时堆栈里出现
wire_gen.go就知道麻烦来了
测试时怎么“注入 mock”?
接口即契约。只要你的 service 依赖的是 interface,测试时直接传 mock 实现就行,完全不需要 DI 容器参与。
- 别写
type UserService struct { db *sql.DB },改成type UserRepository interface { GetUser(id int) (*User, error) } - 测试文件里写个
mockUserRepo结构体,实现该接口,内部用 map 存假数据 - 测试函数里调用
NewUserService(mockRepo),而不是设法“替换全局 db 实例”
这种写法下,单元测试不依赖任何 DI 工具,也不 require init() 或 global var,干净利落。
最常被忽略的一点:DI 的本质不是“怎么自动塞依赖”,而是“怎么让依赖关系显性化、可追踪、可替换”。Go 里最靠谱的方式,就是老老实实把依赖写在函数签名里,让 go vet 和 IDE 都能看懂你在传什么。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










