
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` 技能。
使用 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 架構師。你在組合根(composition root)進行圖形接線,將生命週期邏輯推入鉤子而非 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 基本元素(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 |
選擇 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 限制的 context — 請尊重取消訊號。OnStart 必須快速返回 — 為阻塞工作啟動 goroutine;否則啟動會掛起,且依賴的鉤子永遠不會觸發。
fx.StartHook / fx.StopHook / fx.StartStopHook 可適應更簡單的簽名(無 context、無錯誤,或兩者皆有):
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 結構體。對於簡潔的 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"`
}
附加 ,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()
}
將每個模組視為一個可被提升到另一個應用程式的小型函式庫 — 其公開表面是它 Provide 的型別。
有關 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.Provide 提供應為 fx.Supply 的內容 |
預先建構的值(設定、機密)應屬於 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 圖形驗證
交叉參考
- → 請參閱
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 鉤子中的 context 傳播 - → 請參閱
samber/cc-skills-golang@golang-testing技能以了解一般測試模式
如果您在 uber-go/fx 中遇到錯誤或非預期行為,請在 https://github.com/uber-go/fx/issues 開啟問題。





