golang-dependency-injection について
Go の依存性注入を設計・リファクタリングするスキルです。手動配線と DI ライブラリの選択を支援し、テストしやすく疎結合なサービス構成を計画します。
主な機能
- 01設計・リファクタリングモード
- 02手動注入と DI ライブラリの比較
- 03コンストラクター注入の指針
- 04サブエージェントによるリファクタリング分析
- 05注入したモックによるテスト
活用シーン
- 01新しい Go サービスの依存関係の配線
- 02グローバル状態や密結合コードの改修
- 03規模に応じた DI 方式の選択
- 04モック依存を使ったサービスのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-dependency-injectionインストール後、Claude Code で/golang-dependency-injectionを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/google-wire.mdmanual-di.mdsamber-do.mduber-dig-fx.md詳細
ペルソナ: あなたは Go ソフトウェアアーキテクトです。テストしやすく、疎結合な設計へチームを導きます。問題を解決する最もシンプルな DI 手法を選び、決して過剰に設計しません。
オーケストレーションモード: 大規模で密結合なコードベースを依存性注入に向けてリファクタリングする場合は、Refactor mode で説明されている 3 つのサブエージェント(グローバル変数/init の検出、具象依存関係のマッピング、サービスロケーターの検出)を並行して実行し、結果を 1 つの移行計画にまとめます。Claude Code では、ultracode を使ってマルチエージェントオーケストレーションを明示的に有効にします。
モード:
- Design mode(新しいプロジェクト、新しいサービス、または既存の DI セットアップへのサービス追加):既存の依存関係グラフとライフサイクル要件を評価し、判断表に基づいて手動注入またはライブラリを推奨してから、配線コードを生成します。
- Refactor mode(既存の密結合コード):最大 3 つのサブエージェントを並行して使用します。Agent 1 はグローバル変数と
init()によるサービス設定を特定し、Agent 2 はインターフェースにすべき具象型の依存関係をマッピングし、Agent 3 はサービスロケーターのアンチパターン(引数として渡されるコンテナー)を特定します。その後、調査結果をまとめて移行計画を提案します。
コミュニティのデフォルト。
samber/cc-skills-golang@golang-dependency-injectionスキルを明示的に置き換える企業スキルがある場合は、そちらを優先します。
Go における依存性注入
依存性注入(DI)とは、コンポーネント自身に依存関係を作成または検索させるのではなく、依存関係をコンポーネントに渡すことです。Go では、これによってテストしやすく疎結合なアプリケーションを構築できます。サービスが必要なものを宣言し、呼び出し元(またはコンテナー)がそれを提供します。
このスキルは網羅的なものではありません。DI ライブラリ(google/wire、uber-go/dig、uber-go/fx、samber/do)を使用する場合は、現在の API シグネチャとコード例について、各ライブラリの公式ドキュメントを参照してください。
インターフェースベース設計の基礎(インターフェースを受け取り、構造体を返す)については、samber/cc-skills-golang@golang-structs-interfaces スキルを参照してください。
ベストプラクティスの概要
- 依存関係はコンストラクター経由で注入しなければなりません(MUST)。サービスの設定にグローバル変数や
init()を使ってはいけません(NEVER)。 - 小規模なプロジェクト(サービスが 10 個未満)では、手動のコンストラクター注入を使うべきです(SHOULD)。ライブラリは必要ありません。
- インターフェースは実装側ではなく、利用側で定義しなければなりません(MUST)。インターフェースを受け取り、構造体を返します。
- グローバルレジストリやパッケージレベルのサービスロケーターを使ってはいけません(NEVER)。
- DI コンテナーはコンポジションルート(
main()またはアプリケーションの起動処理)にのみ存在しなければなりません(MUST)。コンテナーを依存関係として渡してはいけません(NEVER)。 - 遅延初期化を優先します。サービスは最初に要求されたときにのみ作成します。
- 状態を持つサービス(DB 接続、キャッシュ)にはシングルトンを、状態を持たないサービスにはトランジェントを使います。
- インターフェース境界でモックします。DI を使えば簡単に実現できます。
- 依存関係グラフは浅く保ちます。深いチェーンは設計上の問題を示しています。
- プロジェクトの規模とチームに適した DI ライブラリを選びます。以下の判断表を参照してください。
依存性注入が必要な理由
| DI を使わない場合の問題 | DI による解決方法 |
|---|---|
| 関数が独自に依存関係を作成する | 依存関係を注入するため、実装を自由に差し替えられます |
| テストに実際のデータベースや API が必要になる | テストではモック実装を渡します |
| あるコンポーネントを変更すると、他のコンポーネントが壊れる | インターフェースによる疎結合で、コンポーネント同士が互いの内部実装を知る必要がありません |
| サービスがあちこちで初期化される | 集約されたコンテナーがライフサイクル(シングルトン、ファクトリー、遅延)を管理します |
| すべてのサービスが起動時に読み込まれる | 遅延読み込みにより、サービスは最初に要求されたときにのみ作成されます |
グローバル状態と init() 関数が使われる | 起動時に明示的に配線するため、予測しやすくデバッグも容易です |
DI は、HTTP サーバー、マイクロサービス、プラグインを備えた CLI ツールなど、多数のサービスが相互に接続されたアプリケーションで特に効果を発揮します。関数が 2~3 個の小さなスクリプトなら、手動で配線すれば十分です。過剰に設計しないでください。
手動によるコンストラクターインジェクション(ライブラリなし)
小規模なプロジェクトでは、依存関係をコンストラクター経由で渡します。アプリケーション全体の例については、手動 DI の例を参照してください。
// ✓ Good — explicit dependencies, testable
type UserService struct {
db UserStore
mailer Mailer
logger *slog.Logger
}
func NewUserService(db UserStore, mailer Mailer, logger *slog.Logger) *UserService {
return &UserService{db: db, mailer: mailer, logger: logger}
}
// main.go — manual wiring
func main() {
logger := slog.Default()
db := postgres.NewUserStore(connStr)
mailer := smtp.NewMailer(smtpAddr)
userSvc := NewUserService(db, mailer, logger)
orderSvc := NewOrderService(db, logger)
api := NewAPI(userSvc, orderSvc, logger)
api.ListenAndServe(":8080")
}
// ✗ Bad — hardcoded dependencies, untestable
type UserService struct {
db *sql.DB
}
func NewUserService() *UserService {
db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL")) // hidden dependency
return &UserService{db: db}
}
手動 DI では、次のような場合に対応が難しくなります。
- 相互依存するサービスが 15 個以上ある
- ライフサイクル管理(ヘルスチェック、グレースフルシャットダウン)が必要
- 遅延初期化やスコープ付きコンテナーが必要
- 配線の順序が壊れやすく、保守が難しくなる
DI ライブラリの比較
Go には、DI ライブラリの主なアプローチが 3 つあります。
- google/wire の例 — コンパイル時のコード生成
- uber-go/dig + fx の例 — リフレクションベースのフレームワーク
- samber/do の例 — ジェネリクスベース、コード生成なし
判断基準の表
| 基準 | 手動 | google/wire | uber-go/dig + fx | samber/do |
|---|---|---|---|---|
| プロジェクトの規模 | 小規模(サービス 10 個未満) | 中〜大規模 | 大規模 | 規模を問わない |
| 型安全性 | コンパイル時 | コンパイル時(コード生成) | 実行時(リフレクション) | コンパイル時(ジェネリクス) |
| コード生成 | なし | 必須(wire_gen.go) | なし | なし |
| リフレクション | なし | なし | あり | なし |
| API スタイル | 該当なし | プロバイダーセット + ビルドタグ | 構造体タグ + デコレーター | シンプルなジェネリック関数 |
| 遅延読み込み | 手動 | 該当なし(すべて即時) | 組み込み(fx) | 組み込み |
| シングルトン | 手動 | 組み込み | 組み込み | 組み込み |
| 一時インスタンス/ファクトリー | 手動 | 手動 | 組み込み | 組み込み |
| スコープ/モジュール | 手動 | プロバイダーセット | モジュールシステム(fx) | 組み込み(階層型) |
| ヘルスチェック | 手動 | 手動 | 手動 | 組み込みインターフェース |
| グレースフルシャットダウン | 手動 | 手動 | 組み込み(fx) | 組み込みインターフェース |
| コンテナーの複製 | 該当なし | 該当なし | 該当なし | 組み込み |
| デバッグ | print 文 | コンパイルエラー | fx.Visualize() | ExplainInjector()、ウェブインターフェース |
| Go のバージョン | 任意 | 任意 | 任意 | 1.18 以降(ジェネリクス) |
| 学習曲線 | なし | 中程度 | 高い | 低い |
簡単な比較:配線スタイル
同じ依存関係グラフ — Config -> Database -> UserStore -> UserService -> API — を、手動とコンテナーで配線します。違いは配線コードが表す内容です。手動では自分で管理する順序付きの呼び出しシーケンスですが、コンテナーではプロバイダーの集合を渡し、順序をコンテナーに任せます。
// Manual — you own the order; adding a dependency means editing every call site downstream
cfg := NewConfig()
db := NewDatabase(cfg)
store := NewUserStore(db)
svc := NewUserService(store)
api := NewAPI(svc)
api.Run()
// No shutdown hooks, health checks, or lazy loading — add them yourself
// Container (samber/do) — order is derived from the constructor signatures
i := do.New()
do.Provide(i, NewConfig)
do.Provide(i, NewDatabase)
do.Provide(i, NewUserStore)
do.Provide(i, NewUserService)
api := do.MustInvoke[*API](i)
api.Run()
defer i.Shutdown() // shutdown and health checks come from the container
google/wire と uber-go/fx は同じグラフを異なる方法で表現します。wire は wire.Build のプロバイダーリストから、上記の手動シーケンスをビルド時に生成します(プロバイダーが返す func() でクリーンアップし、ライフサイクルフックはありません)。一方、fx は fx.Provide でプロバイダーを登録し、実行時にリフレクションで解決して、OnStart/OnStop フックを利用します。それぞれの配線例については、google/wire、uber-go/dig + fx、samber/doを参照してください。
DI を使用したテスト
DI を使うと、実際の実装の代わりにモックを注入できるため、テストが簡単になります。
// Define a mock
type MockUserStore struct {
users map[string]*User
}
func (m *MockUserStore) FindByID(ctx context.Context, id string) (*User, error) {
u, ok := m.users[id]
if !ok {
return nil, ErrNotFound
}
return u, nil
}
// Test with manual injection
func TestUserService_GetUser(t *testing.T) {
mock := &MockUserStore{
users: map[string]*User{"1": {ID: "1", Name: "Alice"}},
}
svc := NewUserService(mock, nil, slog.Default())
user, err := svc.GetUser(context.Background(), "1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got %q, want %q", user.Name, "Alice")
}
}
samber/do を使用したテスト — 複製とオーバーライド
コンテナーを複製すると、必要なサービスだけをモックに置き換えられる、分離されたコピーを作成できます。
func TestUserService_WithDo(t *testing.T) {
// Create a test injector with mock implementation
testInjector := do.New()
// Provide the mock UserStore interface
do.OverrideValue[UserStore](testInjector, &MockUserStore{
users: map[string]*User{"1": {ID: "1", Name: "Alice"}},
})
// Provide other real services as needed
do.Provide[*slog.Logger](testInjector, func(i *do.Injector) (*slog.Logger, error) {
return slog.Default(), nil
})
svc := do.MustInvoke[*UserService](testInjector)
user, err := svc.GetUser(context.Background(), "1")
// ... assertions
}
これは、ほとんどのサービスは実際のものを使いながら、特定の境界(データベース、外部 API、メール送信サービス)をモックにする必要がある統合テストで特に便利です。
DI ライブラリを導入するタイミング
| 兆候 | 対応 |
|---|---|
| サービスが 10 個未満で、依存関係がシンプル | 手動のコンストラクターインジェクションを続ける |
| サービスが 10〜20 個で、横断的関心事がいくつかある | DI ライブラリを検討する |
| サービスが 20 個以上で、ライフサイクル管理が必要 | 強く推奨 |
| ヘルスチェックやグレースフルシャットダウンが必要 | ライフサイクル管理が組み込まれたライブラリを使用する |
| チームが DI の概念に不慣れ | 手動で始め、段階的に移行する |
よくある間違い
| 間違い | 修正方法 |
|---|---|
| 依存関係としてグローバル変数を使う | コンストラクターまたは DI コンテナを通じて渡します |
サービスのセットアップに init() を使う | main() またはコンテナで明示的に初期化します |
| 具象型に依存する | 利用箇所の境界ではインターフェースを受け取ります |
| コンテナをあらゆる場所に渡す(サービスロケーター) | コンテナではなく、個別の依存関係を注入します |
| 依存関係の連鎖が深い(A->B->C->D->E) | フラット化します。ほとんどのサービスは、リポジトリと設定に直接依存するようにします |
| リクエストごとに新しいコンテナを作成する | アプリケーションごとにコンテナを1つ使い、リクエスト単位の分離にはスコープを使用します |
相互参照
- → samber/do の詳しい使用パターンについては、
samber/cc-skills-golang@golang-samber-doスキルを参照してください - → インターフェースの設計と合成については、
samber/cc-skills-golang@golang-structs-interfacesスキルを参照してください - → 依存性注入を使ったテストについては、
samber/cc-skills-golang@golang-testingスキルを参照してください - → DI の初期化箇所については、
samber/cc-skills-golang@golang-project-layoutスキルを参照してください