mcpField
G
golang-samber-do

golang-samber-do

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

golang-samber-do について

Go 向けのジェネリクスベース依存性注入ツール samber/do の活用スキルです。コンポジションルートでのコンテナ管理、プロバイダーのエラー処理、スコープ、登録の整理を扱います。

主な機能

  1. 01ジェネリクスベースの依存性注入
  2. 02遅延・即時・都度生成・値サービス
  3. 03名前やインターフェースによる解決
  4. 04Package による登録の整理
  5. 05スコープとライフサイクルの指針

活用シーン

  1. 01Go アプリの依存性注入の設定
  2. 02アプリのモジュールごとのサービス登録
  3. 03プロバイダーからのインターフェース実装の解決

インストール

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

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

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

同梱ファイル

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

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

詳細

ペルソナ: 依存性注入を設定する Go アーキテクトです。コンテナはコンポジションルートに置き、具象型ではなくインターフェースに依存し、プロバイダーのエラーを重大な失敗として扱います。

Go での依存性注入に samber/do を使う

Go 1.18 以降のジェネリクスに基づく、Go 用の型安全な依存性注入ツールキットです。

公式リソース:

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

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

v2 をインストールしてください。v1 は後継のバージョンに置き換えられており、以下で説明するジェネリクスベースのコンテナ、スコープ、ライフサイクルフックがありません。そのため、v1 時代のガイダンスでは、このスキルに記載されているすべての API について誤解を招きます。

go get -u github.com/samber/do/v2

コアコンセプト

インジェクター(コンテナ)
import "github.com/samber/do/v2"

injector := do.New()
サービスの型
  • 遅延(デフォルト):最初に要求されたときに作成されます
  • 即時:コンテナの起動時にただちに作成されます
  • 一時:要求のたびに新しいインスタンスが作成されます
  • 値:事前に作成された値で、インスタンス化は行われません
プロバイダー関数

サービスはプロバイダー関数経由で登録する必要があります。

type Provider[T any] func(i Injector) (T, error)

基本的な使い方

1. サービスを定義して登録する

「インターフェースを受け取り、構造体を返す」に従います。

// Register a service (lazy by default)
do.Provide(injector, func(i do.Injector) (Database, error) {
    return &PostgreSQLDatabase{connString: "postgres://..."}, nil
})

// Register a pre-created value
do.ProvideValue(injector, &Config{Port: 8080})

// Register a transient service (new instance each time)
do.ProvideTransient(injector, func(i do.Injector) (*Logger, error) {
    return &Logger{}, nil
})

// Register an eager service (created immediately at startup)
do.ProvideValue(injector, &Config{Port: 8080})
2. サービスを呼び出す

コンテナへのアクセスはコンポジションルートに限定する必要があります。

// Invoke with error handling — reserve for call sites outside the DI graph
// (e.g. an HTTP handler that must degrade gracefully instead of crashing)
db, err := do.Invoke[Database](injector)

// MustInvoke panics on error — preferred in providers, recovered by do.Invoke on the parent call
db := do.MustInvoke[Database](injector)

プロバイダー関数内では、エラーを返すバリアントではなく、常に do.MustInvoke(または MustInvokeAs/MustInvokeNamed/MustInvokeStruct)を使用してください。

  • プロバイダーはすでに (T, error) を返すため、依存関係のエラーを do.Invoke で伝播すると、呼び出しのたびに余分な if err != nil { return nil, err } が必要になります。
  • do.MustInvoke は代わりにパニックを発生させますが、samber/do はそのパニックを外側の Invoke 呼び出しで正しく捕捉して復旧し、通常のエラーに変換します。この復旧処理は呼び出し元のコードではなくライブラリ自体の内部で行われるため、プロバイダー内で MustInvoke を安全に使用できます。
  • 失敗は引き続きコンポジションルートでエラーとして表面化しますが、各プロバイダーで手動の定型コードを記述する必要はありません。
3. サービスの依存関係
func NewUserService(i do.Injector) (UserService, error) {
    db := do.MustInvoke[Database](i)
    cache := do.MustInvoke[Cache](i)
    return &userService{db: db, cache: cache}, nil
}

do.Provide(injector, NewUserService)
4. 暗黙のエイリアス(推奨)

具象型を登録し、明示的なエイリアスを設定せずにインターフェースとして呼び出します。

// Register concrete type
do.Provide(injector, func(i do.Injector) (*PostgreSQLDatabase, error) {
    return &PostgreSQLDatabase{}, nil
})

// Invoke directly as interface (implicit aliasing)
db := do.MustInvokeAs[Database](injector)
5. 名前付きサービス

同じ型のサービスを複数登録します。

do.ProvideNamed(injector, "primary-db", func(i do.Injector) (*Database, error) {
    return &Database{URL: "postgres://primary..."}, nil
})

mainDB := do.MustInvokeNamed[*Database](injector, "primary-db")

パッケージ構成

do.Package() を使用して、モジュールごとにサービス登録を整理します。

// infrastructure/package.go
var Package = do.Package(
    do.Lazy(func(i do.Injector) (*postgres.DB, error) {
        cfg := do.MustInvoke[*Config](i)
        return postgres.Connect(cfg.DatabaseURL)
    }),
    do.Lazy(func(i do.Injector) (*redis.Client, error) {
        cfg := do.MustInvoke[*Config](i)
        return redis.NewClient(cfg.RedisURL), nil
    }),
)

// main.go
injector := do.New(infrastructure.Package, service.Package)

アプリケーション全体のセットアップ

func main() {
    injector := do.New(
        infrastructure.Package,
        repository.Package,
        service.Package,
        transport.Package,
    )

    server := do.MustInvoke[*http.Server](injector)
    go server.ListenAndServe()

    _ = injector.ShutdownOnSignalsWithContext(context.Background(), os.Interrupt)
}

ベストプラクティス

  1. 具体的な型ではなくインターフェースに依存します。そうすることで、本番コードに手を加えずに、テストで実装を差し替えられます。
  2. 各サービスの役割は1つにします。複数の責務を持つサービスは、テストも差し替えも難しくなります。
  3. 依存関係のツリーは浅く保ちます。3〜4階層を超えるチェーンは、初期化順序が不安定になり、エラーの追跡も難しくなります。
  4. プロバイダー関数でエラーを処理します。エラーを黙って無視するプロバイダーは、壊れたサービスを作り、後になって予期しない箇所でクラッシュを引き起こします。
  5. スコープを使って、サービスをライフサイクルごとに整理します。リクエストスコープのサービスはリークを防ぎ、グローバルサービスは冗長な初期化を防ぎます。
  6. プロバイダー関数の内部では do.Invoke* ではなく do.MustInvoke* を使います。samber/do は外側の Invoke 呼び出しでパニックを適切に捕捉して復旧し、返されたエラーに変換するため、プロバイダーの内部でも安全に使用でき、定型コードなしで同じエラー伝播を実現できます。

スコープ、ライフサイクル管理、構造体へのインジェクション、デバッグについては、高度な使い方を参照してください。

テストのパターン(クローン、オーバーライド、モック)については、テストを参照してください。

クイックリファレンス

登録
関数用途
do.Provide[T]()遅延サービスを登録(デフォルト)
do.ProvideNamed[T]()名前付き遅延サービスを登録
do.ProvideValue[T]()作成済みの値を登録
do.ProvideNamedValue[T]()名前付きの値を登録
do.ProvideTransient[T]()呼び出しごとに新しいインスタンスを登録
do.ProvideNamedTransient[T]()名前付きの一時サービスを登録
do.Package()サービスの登録をグループ化
呼び出し
関数用途
do.Invoke[T]()サービスを取得(エラーを返す)
do.InvokeNamed[T]()名前付きサービスを取得
do.InvokeAs[T]()インターフェースに一致する最初のサービスを取得
do.InvokeStruct[T]()タグを使って構造体のフィールドにインジェクション
do.MustInvoke[T]()サービスを取得(エラー時にパニック)
do.MustInvokeNamed[T]()名前付きサービスを取得(エラー時にパニック)
do.MustInvokeAs[T]()インターフェースでサービスを取得(エラー時にパニック)
do.MustInvokeStruct[T]()構造体にインジェクション(エラー時にパニック)

関連項目

  • → DI の概念、比較、DI ライブラリを導入するタイミングについては、samber/cc-skills-golang@golang-dependency-injection スキルを参照してください
  • → インターフェース設計パターンについては、samber/cc-skills-golang@golang-structs-interfaces スキルを参照してください
  • → 一般的なテストパターンについては、samber/cc-skills-golang@golang-testing スキルを参照してください

コメント