mcpField
G
golang-uber-fx

golang-uber-fx

github.com/samber開発3.7k37.0k 回インストール5 日前に更新
リポジトリ

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() ではなくフックにライフサイクル処理を移し、モジュールを再利用の単位として扱います。

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 が追加する機能:

項目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 はすぐに戻る必要があります。ブロックする処理はゴルーチンを起動して実行してください。そうしないと起動が停止し、依存するフックが起動されません。

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 を参照してください。

ベストプラクティス

  1. main() は薄く保ちます。プロバイダー、モジュール、そして1つの Run() にとどめます。実際の処理はモジュールに移し、それぞれを単独でテストできるようにします。
  2. init() やコンストラクターから起動するゴルーチンではなく、ライフサイクルフックを使います。Start/Stop の順序はグラフのトポロジーに依存しますが、init() のゴルーチンはそれに依存しないため、競合状態やリソースリークにつながります。
  3. OnStart は速やかに返す必要があります。時間のかかる処理は、フック内でゴルーチンを起動して実行します。OnStart がブロックすると、残りの起動処理が停止します。
  4. フック内で ctx.Done() を適切に処理します。キャンセルを無視するフックはタイムアウト失敗として報告されますが、ゴルーチンは実行を続け、リソースリークにつながります。
  5. レイヤーではなくモジュールごとにグループ化します。モジュールは、1つの関心事(HTTP、DB、メトリクス)に関するプロバイダー、ライフサイクル、デコレーターを所有します。
  6. コンストラクターを fx.Out 構造体でラップするのではなく、タグには fx.Annotate を使います。これにより、fx の外でもコンストラクターを再利用できます。
  7. あらかじめ構築済みの値(設定、コマンドラインフラグ)には、fx.Provide ではなく fx.Supply を使います。より簡潔で、意図が明確になります。
  8. 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 を作成してください。

コメント