构造函数注入是唯一能保证编译期校验的方式,通过显式传递依赖、接口类型声明、nil校验、最小化接口、首字母大写命名、Wire提供者链管理、错误传播、避免IO、手动关闭、接口契约对齐、调用方包定义接口、fake完整实现、main唯一组装入口、禁止全局变量和反向依赖,确保依赖清晰可控。

构造函数注入是唯一能保证编译期校验的方式
Go 没有运行时 DI 容器,所谓“平滑”不是靠框架自动扫包,而是靠构造函数参数强制显式传递。如果 NewUserService 不接收 UserRepository,编译器就无法阻止你漏传依赖——等到运行时 panic 才发现 nil pointer dereference,已经晚了。
- 结构体字段必须声明为接口类型,比如
repo UserRepository,不能是*sql.DB或具体实现 -
NewXXX函数必须校验关键依赖是否为nil,例如if repo == nil { panic("repo is required") } - 接口方法要最小化:只包含当前模块实际调用的方法,避免把
*sql.DB全部方法塞进一个接口 - 接口名首字母大写(如
UserRepository),否则测试包无法实现它
Wire 生成代码前必须理清提供者链和错误传播路径
Wire 不是魔法,它只是帮你把手动写的初始化链变成可维护的代码。但如果你的 NewDB() 返回 *sql.DB,而 NewCache() 需要 redis.Client,且两者都可能失败,那 Wire 生成的代码里就得处理这些 error —— 它不会自动帮你加 if err != nil { return err }。
- 每个提供者函数(
func() *sql.DB)应返回(T, error),而不是直接 panic - 注入器函数(
func() (*App, error))要声明 error,让启动失败能被main捕获并退出 - 避免在提供者里做 IO(如读配置文件、连数据库),否则 Wire 生成的代码会在
main之前就执行失败 - Wire 不处理关闭逻辑:
db.Close()和redis.Close()必须由你手动注册 shutdown hook
接口实现不匹配是编译报错最常见原因
看到 cannot use *connpool.ConnPool as type ConnPoolInterface?这不是 Wire 的问题,是接口契约没对齐。Go 要求方法签名完全一致,包括返回类型——哪怕 ConnPool.GetConn() 返回的是 *connpool.Conn,而接口要求 ConnInterface,也不算实现。
- 不要在调用方强转:
ConnPoolInterface(connpool.New())是错的 - 要在实现包里让具体类型主动满足接口:修改
GetConn()返回ConnInterface,并确保*connpool.Conn实现该接口 - 接口定义最好放在调用方包(如
service/),而非实现方包(如connpool/),避免循环导入 - 测试 fake 实现时,必须完整实现接口所有方法,少一个就会编译失败
main 函数是依赖组装的唯一合法入口
所有依赖必须在 main() 里自底向上创建:先 cfg := loadConfig(),再 db := NewDB(cfg),然后 repo := NewUserRepo(db),最后 svc := NewUserService(repo)。任何把初始化逻辑藏进 init()、var 声明或某个 NewXXX 内部的做法,都会让依赖图不可见、不可测、不可控。
立即学习“go语言免费学习笔记(深入)”;
- 不要在
service包里 importdb包并 new 出来——这等于硬编码依赖 - 不要用全局变量存
db或logger,测试时无法替换 - fake 实现直接传指针:
NewUserService(&fakeRepo{}),不需要容器注册 - 模块间依赖方向必须单向:handler → service → repository,禁止 service 反向 import handler


















