关于 golang-uber-fx
面向 Go 架构师的 uber-go/fx 使用指南,通过依赖注入、生命周期钩子、模块和信号感知退出来组合长期运行的服务。重点讲解启动顺序、优雅关闭与模块化服务依赖图。
主要功能
- 01基于反射的依赖注入
- 02生命周期启动与停止钩子
- 03可复用的应用模块
- 04信号感知的运行循环
- 05结构化事件日志
- 06启动与停止超时控制
适用场景
- 01组合长期运行的 Go 服务
- 02安排服务启动和关闭顺序
- 03按应用模块组织 Provider
- 04测试依赖图和生命周期行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-uber-fx安装后在 Claude Code 中输入/golang-uber-fx确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/advanced.mdrecipes.mdtesting.md详细介绍
角色: 你是一位使用 fx 构建长期运行服务的 Go 架构师。你在组合根中组装依赖图,通过钩子而非 init() 承载生命周期,并将模块视为复用单元。
使用 uber-go/fx 进行 Go 应用组装
应用框架结合了基于反射的 DI 容器(基于 uber-go/dig)、生命周期、模块系统、支持信号的运行循环以及结构化事件日志。适用于重视启动顺序、优雅关闭和模块化组合的长期运行服务。
官方资源:
本技能并未涵盖所有内容——更多信息请参阅库文档和代码示例:
- 对于 Go 包文档、符号、版本、导入方和已知漏洞,→ 请参阅
samber/cc-skills-golang@golang-pkg-go-dev技能(godig);查询 Go 包信息时优先于 Context7。 - 要在自己的代码中查找此库的用法(定义、调用位置、诊断信息),→ 请参阅
samber/cc-skills-golang@golang-gopls技能(gopls)。 - 对于未收录在 pkg.go.dev 上的文档,可将 Context7 作为备用方案。
go get go.uber.org/fx
fx 与 dig 的区别
fx 构建于 dig 之上,并使用相同的基于反射的容器引擎。DI 原语(Provide、Invoke、In/Out 结构体、命名值、值组)完全相同——fx.In/fx.Out 是 dig.In/dig.Out 的重新导出。
fx 在此基础上增加了:
| 关注点 | dig | fx |
|---|---|---|
| DI 容器 | ✅ dig.New() | ✅(内嵌) |
| 生命周期钩子 | ❌ | ✅ fx.Lifecycle OnStart/OnStop |
| 模块系统 | ❌ | ✅ 带作用域装饰器的 fx.Module |
| 支持信号的运行循环 | ❌ | ✅ app.Run() 会阻塞并等待 SIGINT/SIGTERM |
| 结构化事件日志 | ❌ | ✅ fx.WithLogger / fxevent |
| 启动/关闭超时 | ❌ | ✅ fx.StartTimeout / fx.StopTimeout |
对于长期运行的服务(HTTP 服务端、工作进程、守护进程),选择 fx——这些场景离不开始生命周期管理和信号处理,而模块能让大型服务依赖图更易管理。
当你需要在不使用框架的情况下组装依赖时,选择原生 dig:例如 CLI 工具、向调用方暴露容器的库、测试框架,或将 DI 嵌入自行管理生命周期的现有应用中。请参阅 samber/cc-skills-golang@golang-uber-dig 技能。
应用
import "go.uber.org/fx"
app := fx.New(
fx.Provide(NewLogger, NewDatabase, NewServer),
fx.Invoke(RegisterRoutes),
)
app.Run() // blocks until SIGINT/SIGTERM, then runs OnStop hooks
启动阶段:fx.New 会验证类型(不会运行构造函数);app.Start(ctx) 会运行每个 fx.Invoke,并按拓扑顺序触发 OnStart 钩子;主程序会阻塞在 app.Done();app.Stop(ctx) 会按相反顺序触发 OnStop 钩子。默认超时为 15 秒——可通过 fx.StartTimeout / fx.StopTimeout 覆盖。
Provide 和 Invoke
fx.New(
fx.Provide(NewLogger, NewDatabase, NewServer), // lazy
fx.Invoke(RegisterRoutes, StartMetricsExporter), // always run during Start
)
fx.Provide 用于注册构造函数;fx.Invoke 用于触发——如果没有任何 Invoke(直接或间接)引用某个类型,该类型的构造函数就永远不会运行。
生命周期钩子
注入 fx.Lifecycle 并添加钩子。构造函数应快速返回;耗时较长的工作应放在 OnStart 中。
func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger, cfg *Config) *http.Server {
srv := &http.Server{Addr: cfg.Addr}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
ln, err := net.Listen("tcp", srv.Addr)
if err != nil { return err }
go srv.Serve(ln) // blocking work in a goroutine
return nil
},
OnStop: func(ctx context.Context) error {
return srv.Shutdown(ctx)
},
})
return srv
}
两个回调都会收到受 StartTimeout/StopTimeout 限制的上下文——应遵循取消信号。OnStart 必须快速返回——对于阻塞工作,应启动一个 goroutine;否则启动过程会卡住,依赖它的钩子也永远不会触发。
fx.StartHook / fx.StopHook / fx.StartStopHook 可适配更简单的函数签名(无上下文、无错误,或两者皆无):
lc.Append(fx.StartStopHook(srv.Start, srv.Stop)) // matched pair
参数和结果对象
fx 将 dig 的 dig.In / dig.Out 重新导出为 fx.In / fx.Out。当构造函数有 4 个或更多依赖项,或需要 name/group/optional 标签时,请使用它们。
type ServerParams struct {
fx.In
Logger *zap.Logger
DB *sql.DB
Cache *redis.Client `optional:"true"`
Routes []http.Handler `group:"routes"`
}
func NewServer(p ServerParams) *Server { /* ... */ }
fx.Annotate
fx.Annotate 可包装构造函数,无需 fx.Out 结构体即可添加标签或接口绑定。对于简洁的 name/group/As 绑定,优先使用它:
fx.Provide(
fx.Annotate(NewPrimaryDB, fx.ResultTags(`name:"primary"`)),
fx.Annotate(NewPostgresDB, fx.As(new(Database))), // expose interface
fx.Annotate(NewUserHandler,
fx.As(new(http.Handler)),
fx.ResultTags(`group:"routes"`),
),
)
值组
多个构造函数,一个使用者切片——常用于路由、健康检查和指标采集器:
type RouteResult struct {
fx.Out
Handler http.Handler `group:"routes"`
}
type ServerParams struct {
fx.In
Routes []http.Handler `group:"routes"`
}
添加 ,flatten(group:"routes,flatten"),即可展开切片,而不是将其嵌套。顺序不保证——如果顺序很重要,请提供显式排序的切片。
fx.Module
fx.Module 按名称对提供器、调用和装饰器进行分组。模块会将装饰器的作用范围限定在自身及其子模块内——在 fx.Module("db", ...) 中重命名的日志记录器,只会在该模块内的代码中显示为新名称。
var DatabaseModule = fx.Module("database",
fx.Provide(NewConnection, NewUserRepository),
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("db")
}),
)
func main() {
fx.New(
fx.Provide(NewConfig, NewLogger),
DatabaseModule,
HTTPModule,
).Run()
}
将每个模块视为一个小型库,可以移植到其他应用中——它的公共接口就是它所提供的类型。
关于 fx.Supply/fx.Replace/fx.Decorate、可选依赖、自定义日志记录、手动生命周期和快速参考,请参阅 advanced.md。
最佳实践
- 保持
main()精简——只包含提供器、模块和一次Run()。将实际工作移入模块,以便分别测试。 - 使用生命周期钩子,而不是
init()或在构造函数中启动 goroutine——Start/Stop 顺序取决于依赖图拓扑,而init()中的 goroutine 不受其影响,因此会导致竞态和泄漏。 - OnStart 必须尽快返回——耗时较长的工作应放在钩子内的 goroutine 中。阻塞的 OnStart 会挂起其余启动流程。
- 在钩子中遵循
ctx.Done()——忽略取消的钩子会被报告为超时失败,但其 goroutine 仍会继续运行,造成资源泄漏。 - 按模块而非按层分组——一个模块负责某项关注点(HTTP、DB、指标)的提供器、生命周期和装饰器。
- 使用
fx.Annotate添加标签,而不是用fx.Out结构体包装构造函数——这样构造函数在 fx 之外也能复用。 - 对预构建值(配置、命令行标志)使用
fx.Supply代替fx.Provide。写法更简洁,也能清楚表达意图。 - 在 CI 中通过
fx.New(...).Err()启动应用来验证依赖图——在部署前发现缺少的提供器和循环依赖。
常见错误
| 错误 | 修正方法 |
|---|---|
| 在 OnStart 中直接执行长时间运行的工作 | 在 OnStart 中启动 goroutine;钩子本身必须尽快返回,以便依赖它的钩子能够运行。 |
对本应使用 fx.Supply 的值使用 fx.Provide | 预构建值(配置、密钥)应使用 fx.Supply——这样更清晰,也能避免空操作构造函数。 |
| 模块装饰器泄漏到同级模块 | 在 fx.Module(...) 内进行装饰——装饰器只会传递到后代模块。顶层 fx.Decorate 则是全局的。 |
| 假定值组有顺序 | 值组没有顺序。如果顺序很重要,请由一个构造函数提供排序后的切片。 |
| 构造函数带有副作用 | 副作用应放在 OnStart 中——构造函数应当开销小且尽量纯粹,因为它们可能并发、延迟运行。 |
忘记 fx.Invoke | 没有 Invoke(或下游使用者)时,构造函数永远不会运行。每个应用至少添加一个 Invoke。 |
测试
使用 go.uber.org/fx/fxtest 将 fx 集成到 *testing.T 中(失败时调用 t.Fatal,RequireStop 会注册为 t.Cleanup)。fx.Populate(&target) 会从依赖图中取出值;fx.Replace 可用假依赖替换真实依赖。完整模式请参阅 testing.md。
延伸阅读
- advanced.md — Supply/Replace/Decorate、可选依赖、自定义事件日志、手动生命周期、完整快速参考
- recipes.md — 完整的 HTTP 服务(含数据库/指标)、支持优雅排空的后台工作进程、同一接口的多个实现、用于 CLI 嵌入的手动生命周期
- testing.md — fxtest 模式、
fx.Replace、fx.Populate、隔离的生命周期测试、CI 依赖图验证
交叉引用
- → 有关底层容器、
dig.In/dig.Out以及不含生命周期管理的 DI,请参阅samber/cc-skills-golang@golang-uber-dig技能 - → 有关 DI 概念和库对比,请参阅
samber/cc-skills-golang@golang-dependency-injection技能 - → 有关无需反射的泛型替代方案,请参阅
samber/cc-skills-golang@golang-samber-do技能 - → 有关编译时 DI(无运行时容器),请参阅
samber/cc-skills-golang@golang-google-wire技能 - → 有关接口设计模式,请参阅
samber/cc-skills-golang@golang-structs-interfaces技能 - → 有关 OnStart/OnStop 钩子中的上下文传递,请参阅
samber/cc-skills-golang@golang-context技能 - → 有关通用测试模式,请参阅
samber/cc-skills-golang@golang-testing技能
如果您在 uber-go/fx 中遇到错误或意外行为,请在 https://github.com/uber-go/fx/issues 提交问题。