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() ではなくフックにライフサイクル処理を移し、モジュールを再利用の単位として扱います。
Go アプリケーションの配線に uber-go/fx を使う
リフレクションベースの 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 はすぐに戻る必要があります。ブロックする処理はゴルーチンを起動して実行してください。そうしないと起動が停止し、依存するフックが起動されません。
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 構造体を使わずにタグやインターフェースのバインディングを追加します。名前/グループ/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"`),
),
)
値グループ
複数のコンストラクターから、1つのコンシューマー用スライスを作成します。ルート、ヘルスチェック、メトリクスコレクターなどで一般的です。
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()は薄く保ちます。プロバイダー、モジュール、そして1つのRun()にとどめます。実際の処理はモジュールに移し、それぞれを単独でテストできるようにします。init()やコンストラクターから起動するゴルーチンではなく、ライフサイクルフックを使います。Start/Stop の順序はグラフのトポロジーに依存しますが、init()のゴルーチンはそれに依存しないため、競合状態やリソースリークにつながります。- OnStart は速やかに返す必要があります。時間のかかる処理は、フック内でゴルーチンを起動して実行します。OnStart がブロックすると、残りの起動処理が停止します。
- フック内で
ctx.Done()を適切に処理します。キャンセルを無視するフックはタイムアウト失敗として報告されますが、ゴルーチンは実行を続け、リソースリークにつながります。 - レイヤーではなくモジュールごとにグループ化します。モジュールは、1つの関心事(HTTP、DB、メトリクス)に関するプロバイダー、ライフサイクル、デコレーターを所有します。
- コンストラクターを
fx.Out構造体でラップするのではなく、タグにはfx.Annotateを使います。これにより、fx の外でもコンストラクターを再利用できます。 - あらかじめ構築済みの値(設定、コマンドラインフラグ)には、
fx.Provideではなくfx.Supplyを使います。より簡潔で、意図が明確になります。 - CI で
fx.New(...).Err()を使って起動し、グラフを検証します。これにより、デプロイ前にプロバイダーの不足や循環依存を検出できます。
よくある間違い
| 間違い | 修正方法 |
|---|---|
| OnStart 内で時間のかかる処理を直接実行する | OnStart 内でゴルーチンを起動します。依存するフックを実行できるよう、フック自体は速やかに返す必要があります。 |
fx.Supply を使うべき値に fx.Provide を使う | あらかじめ構築済みの値(設定、シークレット)には fx.Supply を使います。意図が明確になり、何もしないコンストラクターも不要になります。 |
| モジュールのデコレーターが兄弟モジュールに漏れる | fx.Module(...) 内でデコレートします。デコレーターは子孫にのみ適用されます。トップレベルの fx.Decorate はグローバルに適用されます。 |
| グループの順序を前提にする | グループの順序は保証されません。順序が重要な場合は、1つのコンストラクターから順序指定済みのスライスを渡します。 |
| コンストラクターで副作用を起こす | 副作用は OnStart に置きます。コンストラクターは並行かつ遅延して実行される場合があるため、軽量でほぼ純粋にします。 |
fx.Invoke を忘れる | Invoke(または後続のコンシューマー)がなければ、コンストラクターは実行されません。アプリごとに少なくとも1つの 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 で issue を作成してください。