golang-uber-fx

golang-uber-fx

热门

使用 uber-go/fx 的 Golang 应用框架——包括 fx.New、fx.Provide、fx.Invoke、fx.Module、fx.Lifecycle 钩子、fx.Annotate(name/group/As)、fx.Decorate、fx.Supply、fx.Replace、fx.WithLogger 以及信号感知的 Run()。适用于使用或采用 uber-go/fx 的场景,当代码库导入 `go.uber.org/fx` 或使用 fx.New 编排服务时。对于无生命周期的原始 DI,请参见 `samber/cc-skills-golang@golang-uber-dig` 技能。

2261Star
150Fork
更新于 2026/6/6
SKILL.md
只读
名称
golang-uber-fx
描述

使用 uber-go/fx 的 Golang 应用框架——包括 fx.New、fx.Provide、fx.Invoke、fx.Module、fx.Lifecycle 钩子、fx.Annotate(name/group/As)、fx.Decorate、fx.Supply、fx.Replace、fx.WithLogger 以及信号感知的 Run()。适用于使用或采用 uber-go/fx 的场景,当代码库导入 `go.uber.org/fx` 或使用 fx.New 编排服务时。对于无生命周期的原始 DI,请参见 `samber/cc-skills-golang@golang-uber-dig` 技能。

角色: 你是一位使用 fx 构建长期运行服务的 Go 架构师。你在组合根处编排依赖图,将生命周期逻辑推入钩子而非 init(),并将模块视为可复用的单元。

在 Go 中使用 uber-go/fx 进行应用编排

应用框架,结合了基于反射的 DI 容器(构建于 uber-go/dig 之上)、生命周期、模块系统、信号感知的运行循环和结构化事件日志。适用于启动顺序、优雅关闭和模块化组合至关重要的长期运行服务。

官方资源:

本技能并非详尽无遗。请参考库文档和代码示例以获取更多信息。Context7 可作为可发现性平台提供帮助。对于 Go 包文档、版本、符号和已知漏洞,→ 参见 samber/cc-skills-golang@golang-pkg-go-dev 技能。

go get go.uber.org/fx

fx 与 dig 对比

fx 构建于 dig 之上,共享相同的基于反射的容器引擎。DI 原语(ProvideInvokeIn/Out 结构体、命名值、值组)是相同的——fx.In/fx.Outdig.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

选择 fx 用于长期运行的服务(HTTP 服务器、工作进程、守护进程)——生命周期和信号处理是必需的,模块使大型服务图易于管理。

选择原始 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() // 阻塞直到 SIGINT/SIGTERM,然后运行 OnStop 钩子

启动阶段:fx.New 验证类型(构造函数不运行);app.Start(ctx) 运行每个 fx.Invoke 并按拓扑顺序触发 OnStart 钩子;main 阻塞在 app.Done() 上;app.Stop(ctx) 按逆序触发 OnStop 钩子。默认超时为 15 秒——使用 fx.StartTimeout / fx.StopTimeout 覆盖。

Provide 和 Invoke

fx.New(
    fx.Provide(NewLogger, NewDatabase, NewServer),  // 惰性
    fx.Invoke(RegisterRoutes, StartMetricsExporter), // 在 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)         // 在 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))   // 匹配对

参数和结果对象

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 结构体。优先使用它来获得 ergonomic 的 name/group/As 绑定:

fx.Provide(
    fx.Annotate(NewPrimaryDB, fx.ResultTags(`name:"primary"`)),
    fx.Annotate(NewPostgresDB, fx.As(new(Database))),    // 暴露接口
    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"`
}

追加 ,flattengroup:"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.Provide 本应是 fx.Supply 的内容 预构建的值(配置、密钥)属于 fx.Supply——更清晰,避免无操作的构造函数。
模块装饰器泄漏到同级 fx.Module(...) 内部装饰——装饰器仅流向后代。顶层的 fx.Decorate 是全局的。
假定组顺序 组是无序的。如果顺序重要,从一个构造函数提供一个有序切片。
构造函数带有副作用 副作用属于 OnStart——构造函数应轻量且相对纯净,因为它们可能并发且惰性运行。
忘记 fx.Invoke 没有 Invoke(或下游消费者),构造函数永远不会运行。每个应用至少添加一个 Invoke。

测试

使用 go.uber.org/fx/fxtest 将 fx 与 *testing.T 集成(失败时调用 t.FatalRequireStop 注册为 t.Cleanup)。fx.Populate(&target) 从图中提取值;fx.Replace 将真实依赖替换为模拟。完整模式见 testing.md

延伸阅读

  • advanced.md — Supply/Replace/Decorate、可选依赖、自定义事件日志、手动生命周期、完整快速参考
  • recipes.md — 完整的 HTTP 服务(带数据库/指标)、带优雅排空的后台工作进程、同一接口的多个实现、用于 CLI 嵌入的手动生命周期
  • testing.md — fxtest 模式、fx.Replacefx.Populate、隔离生命周期测试、CI 图验证

交叉引用

  • → 参见 samber/cc-skills-golang@golang-uber-dig 技能,了解底层容器、dig.In/dig.Out 和无生命周期的 DI
  • → 参见 samber/cc-skills-golang@golang-dependency-injection 技能,了解 DI 概念和库比较
  • → 参见 samber/cc-skills-golang@golang-samber-do 技能,了解基于泛型且无反射的替代方案
  • → 参见 samber/cc-skills-golang@golang-google-wire 技能,了解编译时 DI(无运行时容器)
  • → 参见 samber/cc-skills-golang@golang-structs-interfaces 技能,了解接口设计模式
  • → 参见 samber/cc-skills-golang@golang-context 技能,了解 OnStart/OnStop 钩子中的上下文传播
  • → 参见 samber/cc-skills-golang@golang-testing 技能,了解通用测试模式

如果你遇到 uber-go/fx 中的错误或意外行为,请在 https://github.com/uber-go/fx/issues 提交 issue。