其他
在 Go 长驻服务中用 uber-go/fx 做依赖注入…
当项目使用或引入 `go.uber.org/fx`、需要长驻服务(HTTP 服务、worker、daemon)的启动顺序、生命周期钩子和信号感知优雅关闭时,用 fx 组织依赖图和模块。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 39/100 ×0.15
适用场景
在 Go 长驻服务中用 uber-go/特定
解决思路
- 安装依赖:执行 `go get go.uber.org/fx`。
- 用 `fx.New` 构建应用,将 providers、invokes、modules 传入,然后调用 `app.Run()`;`app.Run()` 会阻塞直到 SIGINT/SIGTERM,并执行 OnStop hooks。
- 理解启动阶段:`fx.New` 只验证类型不运行构造函数;`app.Start(ctx)` 运行每个 `fx.Invoke` 并按拓扑顺序触发 OnStart hooks;main 阻塞在 `app.Done()`;`app.Stop(ctx)` 按逆序触发 OnStop hooks。默认超时 15 秒,可用 `fx.StartTimeout` / `fx.StopTimeout` 覆盖。
- 用 `fx.Provide` 注册构造函数(惰性);用 `fx.Invoke` 触发;没有 Invoke(直接或传递引用)的类型,其构造函数永远不会运行。
- 注入 `fx.Lifecycle` 并 append hooks;构造函数应快速返回,长任务放 `OnStart`。例如在 `NewHTTPServer` 中监听成功后用 goroutine 执行 `srv.Serve(ln)`,在 `OnStop` 中执行 `srv.Shutdown(ctx)`。
- 生命周期回调收到的 context 受 `StartTimeout`/`StopTimeout` 限制,必须尊重取消。`OnStart` 必须快速返回,否则启动挂起,依赖它的 hooks 不会触发。
- 使用 `fx.StartHook` / `fx.StopHook` / `fx.StartStopHook` 适配更简单的签名(无 context、无 error 或两者皆有),例如 `lc.Append(fx.StartStopHook(srv.Start, srv.Stop))`。
- 当构造函数有 4 个以上依赖,或需要使用 `name`/`group`/`optional` tags 时,使用 `fx.In` / `fx.Out` 参数和结果对象。
- 使用 `fx.Annotate` 包装构造函数,以添加 tags 或接口绑定,而不写 `fx.Out` 结构;优先用它处理 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"`)))`。
- 使用 Value Groups 处理多个构造函数、一个 consumer slice 的场景,典型如 routes、health checks、metrics collectors;生产者用 `fx.Out` 加 `group:"routes"`,消费者用 `fx.In` 加 `Routes []http.Handler` 和相同 group tag。
- 在 group tag 后追加 `,flatten`(例如 `group:"routes,flatten"`)来展开 slice 而不是嵌套。
- 不要依赖 group 顺序;顺序重要时,由一个构造函数提供显式有序的 slice。
- 使用 `fx.Module` 按名称分组 providers、invokes 和 decorators;模块会把 decorators 作用域限制到自身和子模块,例如在 `fx.Module("db", ...)` 中重命名的 logger 只对该模块内代码显示为重命名。
- 把每个 module 当作可抽出复用到其他 app 的小库;module 的公共面是它 Provides 的类型。
- 保持 `main()` 薄:只放 providers、modules 和单个 `Run()`;把实际工作推到 modules,以便隔离测试。
- 用 lifecycle hooks 代替 `init()` 或从构造函数启动 goroutine;Start/Stop 顺序依赖图拓扑,而 `init()` goroutine 不依赖图拓扑,会导致竞争和泄漏。
- OnStart 必须迅速返回,长工作放在 hook 内的 goroutine。
- 在 hooks 中尊重 `ctx.Done()`;忽略取消的 hook 会被报告为 timeout failure,但其 goroutine 继续运行并泄漏资源。
- 按 module 而不是按 layer 分组;一个 module 负责一个关注点(HTTP、DB、metrics)的 providers、lifecycle 和 decorators。
- 用 `fx.Annotate` 加 tags,而不是把构造函数包进 `fx.Out` 结构,以保持构造函数可在 fx 外部复用。
- 对预构建值(config、command-line flags)用 `fx.Supply` 替代 `fx.Provide`;更短且表达意图。
- 在 CI 中通过启动 `fx.New(...).Err()` 验证图;在部署前捕获缺失 providers 和循环。
- 避免在 OnStart 中直接做长任务;在 OnStart 内启动 goroutine,hook 自身快速返回,以便依赖 hooks 可以运行。
- 预构建值(config、secrets)用 `fx.Supply`,不要用 `fx.Provide` 包一个空操作构造函数。
- 模块 decorator 不要泄漏到兄弟模块;在 `fx.Module(...)` 内 Decorate,decorators 只流向后代,顶层 `fx.Decorate` 是全局的。
- 不要假设 group 顺序;顺序重要时,从单个构造函数提供有序 slice。
- 构造函数不要有副作用;副作用放 OnStart。构造函数应廉价且尽量纯,因为它们可能并发、惰性运行。
- 每个 app 至少添加一个 `fx.Invoke`;否则构造函数不会运行。
- 用 `go.uber.org/fx/fxtest` 与 `*testing.T` 集成:失败会调用 `t.Fatal`,`RequireStop` 会注册为 `t.Cleanup`。
- 测试中用 `fx.Populate(&target)` 从图中拉取值,用 `fx.Replace` 把真实依赖替换为 fakes。
- 需要自定义结构化事件日志时,使用 `fx.WithLogger` / `fxevent`。
适用边界 · 注意事项
- 不需要生命周期、优雅关闭、启动顺序或模块化组合的 CLI 工具、向调用者暴露容器的库、测试 harness,或已自行管理生命周期的应用嵌入 DI:选择 raw dig 或不用 fx。
- 需要 compile-time DI 且不希望 runtime container 时:考虑 `google/wire`,不要用 fx。
- 只需要 raw DI、不需要 lifecycle 时:使用 `samber/cc-skills-golang@golang-uber-dig` 技能中的 dig。