问题修复
Go 项目依赖注入:从紧耦合代码重构为可测试架构时如何选择手…
在 Go 项目中设计服务架构、引入依赖注入、重构紧耦合代码或管理单例/服务工厂时,按项目规模选择手动构造函数注入或合适的 DI 库。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
Go 项目依赖注入:从紧耦合代码重构为可通用
解决思路
- 依赖必须通过构造函数注入,绝不用全局变量或 init() 做服务初始化
- 小型项目(少于 10 个服务)应使用手动构造函数注入,无需引入库
- 接口必须定义在消费方而非实现方——接受接口,返回结构体
- 绝不使用全局注册表或包级服务定位器
- DI 容器只能存在于组合根(main() 或应用启动处),绝不把容器作为依赖传递
- 优先使用懒初始化——只在首次请求时创建服务
- 有状态服务(数据库连接、缓存)用单例,无状态服务用瞬态
- 在接口边界处 mock——DI 让这件事变得简单
- 保持依赖图浅层——过深的依赖链说明设计有问题
- 按项目规模和团队情况选择合适的 DI 库(见决策表)
- 设计模式:评估现有依赖图和生命周期需求,从决策表推荐手动注入或库,然后生成装配代码
- 重构模式:用最多 3 个并行子代理——代理 1 识别全局变量和 init() 服务初始化,代理 2 映射应改为接口的具体类型依赖,代理 3 定位服务定位器反模式(把容器作为参数传递)——然后汇总发现并提出迁移方案
- 重构大型耦合代码库时,扇出三个子代理(全局/init 发现、具体依赖映射、服务定位器检测)并整合为一份迁移计划
- 手动注入写法:在构造函数中接收依赖并存入结构体字段,在 main() 中按顺序手动装配所有服务
- 使用 samber/do 时:用 do.New() 创建注入器,用 do.Provide 注册构造函数,用 do.MustInvoke 解析,用 defer i.Shutdown() 处理关闭
- 测试时注入 mock 实现替代真实实现,在接口边界处定义 mock 结构体
- 使用 samber/do 测试时:用 do.New() 创建测试注入器,用 do.OverrideValue 覆盖需要 mock 的服务,其余服务提供真实实现
- 按信号决定是否引入 DI 库:少于 10 个服务且依赖简单用手动注入;10-20 个服务且有横切关注点考虑库;20+ 服务且需要生命周期管理强烈推荐库;需要健康检查和优雅关闭用内置生命周期支持的库;团队不熟悉 DI 概念先手动再逐步迁移
- 避免常见错误:不用全局变量作依赖;不用 init() 做服务初始化;不依赖具体类型;不把容器到处传递(服务定位器);避免过深依赖链(A->B->C->D->E);不为每个请求创建新容器(每应用一个容器,用 scope 做请求级隔离)
适用边界 · 注意事项
- 小型脚本只有 2-3 个函数时,手动装配即可,不要过度设计
- 少于 10 个服务且依赖简单时,不需要引入 DI 库
- 团队不熟悉 DI 概念时,不要直接上复杂框架,应先从手动注入开始