mcpField
G
golang-uber-dig

golang-uber-dig

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

golang-uber-dig について

uber-go/digでアプリケーションの依存関係を組み立てるGo開発者向けのスキルガイドです。コンストラクターや入出力オブジェクト、グラフのテストを扱い、digとfxの役割の違いも説明します。

主な機能

  1. 01依存グラフの登録と解決
  2. 02dig.Inとdig.Outの利用
  3. 03名前付き値と値グループの設定
  4. 04具象型をインターフェースとして公開
  5. 05グラフ検証とコンストラクターのテスト

活用シーン

  1. 01CLIツールの依存関係の組み立て
  2. 02テストでの依存グラフ検証
  3. 03複数実装の名前付き依存関係の区別

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-uber-dig

インストール後、Claude Code で/golang-uber-digを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
advanced.md
recipes.md
testing.md

詳細

ペルソナ: dig を使ってアプリケーションのグラフを組み立てる Go アーキテクトです。コンテナはコンポジションルートに置き、具象型ではなくインターフェースに依存し、コンストラクターのエラーを最優先で扱います。

Go での依存性注入に uber-go/dig を使う

リフレクションベースの DI ツールキットです。アプリケーションフレームワークを支えるために設計されており(uber-go/fx のエンジンです)、起動時にオブジェクトグラフを解決します。

公式リソース:

このスキルは網羅的ではありません。詳細については、ライブラリのドキュメントとコード例を参照してください。

  • Go パッケージのドキュメント、シンボル、バージョン、インポーター、既知の脆弱性については、samber/cc-skills-golang@golang-pkg-go-dev スキル(godig)を参照してください。Go パッケージの情報については Context7 より優先されます。
  • 独自のコード内でこのライブラリの使い方(定義、呼び出し箇所、診断情報)を調べるには、samber/cc-skills-golang@golang-gopls スキル(gopls)を参照してください。
  • Context7 は、pkg.go.dev にインデックスされていないドキュメントの代替手段として引き続き利用できます。
go get go.uber.org/dig

dig と fx の比較

fx は dig をベースに構築されており、同じコンテナエンジンを共有しています。DI の基本機能(Provide、Invoke、In/Out 構造体、名前付き値、値グループ)は同一です。fx.In/fx.Out は dig.In/dig.Out を再エクスポートしたものです。

fx が dig に追加する機能:

懸念事項digfx
DI コンテナ✅ dig.New()✅(埋め込み)
ライフサイクルフック❌✅ fx.Lifecycle の OnStart/OnStop
モジュールシステム❌✅ スコープ付きデコレーターを備えた fx.Module
シグナル対応の実行ループ❌✅ app.Run() が SIGINT/SIGTERM を待機
構造化イベントログ❌✅ fx.WithLogger/fxevent
起動/終了タイムアウト❌✅ fx.StartTimeout/fx.StopTimeout

配線グラフだけが必要な場合は、dig を選択してください。CLI ツール、呼び出し元にコンテナを公開するライブラリ、テストハーネス、または独自のライフサイクルを管理する既存アプリへの DI 組み込みに適しています。

長時間稼働するサービス(HTTP サーバー、ワーカー、デーモン)には、fx を選択してください。こうした環境ではライフサイクル管理とシグナル処理が不可欠です。samber/cc-skills-golang@golang-uber-fx スキルを参照してください。

コンテナ

import "go.uber.org/dig"

c := dig.New()

便利なオプション:dig.DeferAcyclicVerification()(起動を高速化)、dig.RecoverFromPanics()(パニックを dig.PanicError に変換)、dig.DryRun(true)(呼び出しを行わずに検証)。

Provide と Invoke

// Register a constructor — lazy, only runs when its output is needed
err := c.Provide(func(cfg *Config) (*sql.DB, error) {
    return sql.Open("postgres", cfg.DSN)
})

// Pull a service out of the container by asking for it as a function parameter
err = c.Invoke(func(db *sql.DB) error {
    return db.Ping()
})

コンストラクターは遅延実行され、メモ化されます。出力型はそれぞれ一度だけ構築され、共有されます(コンテナごとのシングルトン)。コンストラクターの形式が正しくない場合、登録時に Provide がエラーを返します。Invoke は、依存関係の経路を付加したコンストラクターのエラーを返します。

dig のコンストラクターとは、入力が依存関係、出力が提供される型である任意の関数です。最後の戻り値である error は、構築の失敗を示します。「インターフェースを受け取り、構造体を返す」方針に従ってください。

dig.In を使ったパラメーターオブジェクト

コンストラクターの依存関係が 4 つ以上になったら、dig.In を埋め込み、フィールドを構造体にまとめてタグを付けます。

type HandlerParams struct {
    dig.In

    Logger *zap.Logger
    DB     *sql.DB
    Cache  *redis.Client `optional:"true"`           // zero value if not provided
    DBRO   *sql.DB       `name:"readonly"`           // named dependency
    Routes []http.Handler `group:"routes"`           // value group
}

func NewHandler(p HandlerParams) *Handler { /* ... */ }

タグ:name:"..."、optional:"true"、group:"..."。

dig.Out を使った結果オブジェクト

1 つのコンストラクターから複数の値を返し、結果に name/group タグを付けます。

type ConnResult struct {
    dig.Out

    ReadWrite *sql.DB `name:"primary"`
    ReadOnly  *sql.DB `name:"readonly"`
}

func NewConnections(cfg *Config) (ConnResult, error) { /* ... */ }

名前付き値

同じ型を提供するものが 2 つあると、競合します。dig.Name で区別します。

c.Provide(NewPrimaryDB,  dig.Name("primary"))
c.Provide(NewReadOnlyDB, dig.Name("readonly"))

dig.In フィールドに name:"primary"/name:"readonly" を追加して値を受け取ります。

値グループ

多数のプロバイダーから 1 つのコンシューマーのスライスへ渡します。HTTP ハンドラー、ヘルスチェック、マイグレーションでよく使われます。

type RouteResult struct {
    dig.Out
    Handler http.Handler `group:"routes"`
}

func NewUserHandler(db *sql.DB) RouteResult { /* ... */ }
func NewPostHandler(db *sql.DB) RouteResult { /* ... */ }

type ServerParams struct {
    dig.In
    Routes []http.Handler `group:"routes"`
}

フラット化 — ,flatten を追加します(例:group:"routes,flatten")。これにより、スライスを入れ子にせず展開できます。グループの順序は保証されません。順序が重要な場合は、単一のコンストラクターから順序を明示したスライスを提供してください。

インターフェースとして提供する(dig.As)

具象コンストラクターを登録し、別のアダプターを用意せずに 1 つ以上のインターフェースとして公開します。

c.Provide(NewPostgresDB, dig.As(new(Database), new(io.Closer)))
// Consumers ask for Database or io.Closer; *PostgresDB stays hidden.

アプリケーション全体の例

func main() {
    c := dig.New()

    must(c.Provide(NewConfig))
    must(c.Provide(NewLogger))
    must(c.Provide(NewDatabase))
    must(c.Provide(NewServer))

    err := c.Invoke(func(srv *http.Server) error {
        return srv.ListenAndServe()
    })
    if err != nil {
        log.Fatal(err)
    }
}

func must(err error) { if err != nil { panic(err) } }

dig には組み込みのライフサイクル管理機能がありません。OnStart/OnStop フック、シグナル処理、グレースフルシャットダウンが必要な場合は、fx を使用してください。詳細は samber/cc-skills-golang@golang-uber-fx スキルを参照してください。

Decorate、Scopes、オプションの依存関係、エラーヘルパー、Visualize については、advanced.md を参照してください。

ベストプラクティス

  1. コンテナはコンポジションルートに置いてください。*dig.Container をパラメーターとして渡さず、main() の配線の詳細として扱ってください。サービスロケーターパターンは、DI によるテスト容易性の向上を損ないます。
  2. 具象型ではなくインターフェースに依存してください。これにより、実運用コードに手を加えずにテストで実装を差し替えられます。また、dig.As を使って、幅広い構造体から必要なインターフェースだけを公開できます。
  3. コンストラクターの依存関係が 4 つ以上になったら、パラメーターオブジェクト(dig.In 構造体)を優先してください。呼び出し箇所の可読性を保て、新しい依存関係の追加も、シグネチャを壊す変更ではなく 1 行の変更で済みます。
  4. 登録はモジュールごとにまとめてください(各モジュールの型に対して c.Provide を呼び出すファイルを 1 つ用意します)。レビューやリファクタリングをモジュール単位で行えるようになり、配線を書き換えずに、後からモジュールを fx.Module として切り出せます。
  5. テストでグラフを早期に検証してください。CI でコンポジションルートに対して c.Invoke を呼び出し、最初のリクエスト時ではなく、起動時にプロバイダーの不足を検出します。DryRun(true) を使うと、コンストラクターの実行をスキップできます。
  6. パニックを起こす代わりに、コンストラクターからエラーを返してください。dig は依存関係のパスを付けてエラーをラップするため、失敗箇所が明確になります。

よくある間違い

間違い修正方法
コンテナをサービスに渡すコンテナは main() に置きます。サービスに必要な型付きの依存関係を注入してください。そうしないと、テストでも実際のコンテナを構築する必要があります。
同じ型に対して Name を指定せずに 2 つのプロバイダーを登録するdig は Provide の時点でエラーを返します。名前を付けるか、dig.Out の結果構造体を返す単一のプロバイダーにまとめてください。
Provide のエラーを無視する各 Provide を must ヘルパーでラップしてください。登録エラーを見逃すと、かなり後になって型不足のエラーとして発生します。
順序が重要な場面でグループを使うグループの順序は保証されません。順序が重要な場合(ミドルウェアチェーン、マイグレーションの順序など)は、コンストラクターを 1 つ使って、順序を明示したスライスを提供してください。
インポート時に副作用のあるコンストラクターを実行するinit() は空のままにしてください。グラフの構築後、コンストラクター内でのみ処理を開始してください。

テスト

dig のコンテナは軽量です。テストごとに新しいコンテナを構築し、Decorate でプロバイダーを差し替えて、Invoke を呼び出してシステムを実行してください。テストごとの配線、共有ヘルパー、CI でのグラフ検証、配線時エラーのアサーション、コンストラクターのパニックからの復旧など、テストの詳細なパターンについては testing.md を参照してください。

関連資料

  • advanced.md — Decorate、Scopes、オプションの依存関係、エラーヘルパー、Visualize、クイックリファレンス全体
  • recipes.md — エンドツーエンドの例:ルートグループを使った HTTP サーバー、2 つのデータベース、リクエストスコープ、デコレーター、ドライラン検証
  • testing.md — テストパターンとグラフ検証

相互参照

  • → dig を基盤とするアプリケーションのライフサイクル、モジュール、シグナルに対応した Run() については、samber/cc-skills-golang@golang-uber-fx スキルを参照してください
  • → 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 スキルを参照してください
  • → 一般的なテストパターンについては、samber/cc-skills-golang@golang-testing スキルを参照してください

uber-go/dig でバグや予期しない動作に遭遇した場合は、https://github.com/uber-go/dig/issues で issue を作成してください。

コメント