Kratos 依赖注入必须使用 Wire,它是官方唯一推荐且模板默认集成的编译期代码生成方案;错误(如循环依赖、类型不匹配)在 go build 阶段即暴露,而非运行时,所有组件初始化须通过 wire.go 声明,禁止手写或 runtime DI。

kratos 依赖注入必须用 Wire,不能手写或 runtime DI
Wire 是 Kratos 官方唯一推荐、项目模板默认集成的依赖注入方案。它在编译期生成 wire_gen.go,把对象创建和组装逻辑固化为普通 Go 代码——这意味着错误(比如循环依赖、类型不匹配、未提供必要参数)会在 go build 阶段立刻暴露,而不是运行时 panic 或静默失败。
常见错误现象:undefined: InitializeApp(wire.go 中声明了但没执行 wire gen)、cannot use ... as ... value in return statement(构造函数返回类型与 wire 提供的 interface 不一致)、no required provider found for *xxx.Service(漏写了某个 Provide 函数)。
使用场景:所有业务组件(Service、Data、Biz、DAO)的初始化都应通过 Wire 声明,而非在 main() 里手动 new。
-
wire.go必须放在internal/di/目录下,且包含InitializeApp()函数作为入口 - 每个 provider 函数(如
NewUserService)需显式标注参数依赖,Wire 才能推导调用顺序 - 避免在 provider 函数体内做副作用操作(如连接 DB、启动 goroutine),应移至
Start()/Stop()方法中
依赖声明要分层,不能跨 internal 子目录直接引用
Kratos 的 internal/ 目录结构不是装饰,而是强约束的依赖边界。Wire 会按目录层级校验依赖合法性:例如 service 层可依赖 biz 和 data,但 biz 层不能反向依赖 service;data 层只能依赖 model 和外部 SDK(如 redis.Client),不能引入 biz 的任何类型。
参数差异体现在 Wire 构造函数签名上:如果 NewUserRepo 需要 *sql.DB,就不能传入 database/sql.DB(Go 类型不兼容),也不能传 gorm.DB(即使语义相似)——类型必须完全匹配。
容易踩的坑:internal/data/ 里直接 new 一个 service.UserService,导致编译报错 import cycle not allowed;或在 biz 层 import api 包里的 pb 类型,破坏分层契约。
服务发现和客户端连接必须通过 registry.Discovery 注入
gRPC 客户端连接不是“new 一下就行”的资源,它依赖服务发现、负载均衡、健康检查等动态能力。Kratos 要求所有远程服务客户端(如 UserClient)的构造函数必须接收 registry.Discovery 实例,并在 grpc.DialContext 中使用 grpc.WithDiscovery 选项。
典型错误是硬编码 endpoint:"127.0.0.1:9001" —— 这会导致无法对接 Consul/Etcd,也无法做实例剔除和权重调整。正确写法是 "discovery:///user-service",由 discovery.Builder 解析并监听变更。
性能影响:如果在多个 provider 中重复调用 grpc.DialContext 且未复用 *grpc.ClientConn,会造成连接爆炸、DNS 频繁解析、Watcher 泄漏。应确保每个服务名只建立一次连接,并通过 Wire 单例注入。
- 客户端连接建议封装成 provider 函数,如
NewUserClient(discovery registry.Discovery) UserClient - 务必在 app shutdown 阶段调用
conn.Close(),否则registry.Watcher可能持续占用 etcd/consul 连接 - 不要把
registry.Discovery实例存到全局变量,它本身支持并发,但生命周期应由 Wire 统一管理
配置加载必须绑定 conf.Config 结构体,不能读 raw map
Kratos 的配置模块(config.Config)不是 key-value 字典,而是强类型的结构体绑定机制。所有配置项(如数据库地址、超时时间、开关)必须先定义在 internal/conf/conf.proto,再通过 conf.Load() 加载为 Go struct 实例,最后由 Wire 注入到需要它的组件中。
绕过该机制直接读 configs/config.yaml 的 string 或 map[string]interface{},会导致三个严重问题:无法做字段校验(比如 timeout_ms: "abc" 不报错)、热更新失效(config.Watch() 只作用于 struct 字段)、与 wire 依赖图脱节(Wire 不知道这个 map 从哪来)。
容易被忽略的地方:proto 文件修改后,必须运行 make api(或 protoc 手动生成),否则 conf.pb.go 不更新,conf.Load() 会 panic 报 unknown field 'xxx' in conf.Config。


















