mcpField
G
golang-uber-fx

golang-uber-fx

github.com/samber开发3.7k37.0k 次安装6 天前更新
代码仓库

关于 golang-uber-fx

面向 Go 架构师的 uber-go/fx 使用指南,通过依赖注入、生命周期钩子、模块和信号感知退出来组合长期运行的服务。重点讲解启动顺序、优雅关闭与模块化服务依赖图。

主要功能

  1. 01基于反射的依赖注入
  2. 02生命周期启动与停止钩子
  3. 03可复用的应用模块
  4. 04信号感知的运行循环
  5. 05结构化事件日志
  6. 06启动与停止超时控制

适用场景

  1. 01组合长期运行的 Go 服务
  2. 02安排服务启动和关闭顺序
  3. 03按应用模块组织 Provider
  4. 04测试依赖图和生命周期行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-uber-fx

安装后在 Claude Code 中输入/golang-uber-fx确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
advanced.md
recipes.md
testing.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 在此基础上增加了:

关注点digfx
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。

最佳实践

  1. 保持 main() 精简——只包含提供器、模块和一次 Run()。将实际工作移入模块,以便分别测试。
  2. 使用生命周期钩子,而不是 init() 或在构造函数中启动 goroutine——Start/Stop 顺序取决于依赖图拓扑,而 init() 中的 goroutine 不受其影响,因此会导致竞态和泄漏。
  3. OnStart 必须尽快返回——耗时较长的工作应放在钩子内的 goroutine 中。阻塞的 OnStart 会挂起其余启动流程。
  4. 在钩子中遵循 ctx.Done()——忽略取消的钩子会被报告为超时失败,但其 goroutine 仍会继续运行,造成资源泄漏。
  5. 按模块而非按层分组——一个模块负责某项关注点(HTTP、DB、指标)的提供器、生命周期和装饰器。
  6. 使用 fx.Annotate 添加标签,而不是用 fx.Out 结构体包装构造函数——这样构造函数在 fx 之外也能复用。
  7. 对预构建值(配置、命令行标志)使用 fx.Supply 代替 fx.Provide。写法更简洁,也能清楚表达意图。
  8. 在 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 提交问题。

评论

支持 Markdown