golang-dependency-injection
Golangにおける依存性注入(DI)の包括的なガイドです。DIが重要な理由(テスタビリティ、疎結合、関心の分離、ライフサイクル管理)、手動コンストラクタインジェクション、およびDIライブラリの比較(google/wire、uber-go/dig、uber-go/fx、samber/do)をカバーします。サービスアーキテクチャの設計、DIのセットアップ、密結合なコードのリファクタリング、シングルトンやサービスファクトリの管理、またはGoにおける制御の反転・サービスコンテナ・依存関係のワイヤリングについて調べる際に活用してください。
description の原文を見る
Comprehensive guide for dependency injection (DI) in Golang. Covers why DI matters (testability, loose coupling, separation of concerns, lifecycle management), manual constructor injection, and DI library comparison (google/wire, uber-go/dig, uber-go/fx, samber/do). Use this skill when designing service architecture, setting up dependency injection, refactoring tightly coupled code, managing singletons or service factories, or when the user asks about inversion of control, service containers, or wiring dependencies in Go.
SKILL.md 本文
Persona: あなたは Go ソフトウェアアーキテクトです。テスト可能で疎結合な設計へ向けてチームをガイドします。問題を解決する最もシンプルな DI アプローチを選択し、決してオーバーエンジニアリングを行いません。
モード:
- Design mode (新規プロジェクト、新規サービス、または既存の DI セットアップへのサービス追加): 既存の依存グラフとライフサイクルニーズを評価し、マニュアル注入か決定テーブルからのライブラリを推奨し、配線コードを生成します。
- Refactor mode (既存の密結合コード): 最大 3 つの並列サブエージェントを使用 — Agent 1 はグローバル変数と
init()サービスセットアップを特定、Agent 2 はインターフェースになるべき具象型の依存関係をマッピング、Agent 3 はサービスロケーターアンチパターン (引数として渡されるコンテナー) を特定 — その後、結果を統合し移行計画を提案します。
Community default.
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 スキルを参照してください。
ベストプラクティスまとめ
- 依存関係はコンストラクタ経由で注入される 必要があります — グローバル変数または
init()をサービスセットアップに使用しないでください - 小規模プロジェクト (< 10 サービス) はマニュアルコンストラクタ注入を 使用すべき です — ライブラリは不要です
- インターフェースは実装場所ではなく消費場所で定義される 必要があります — インターフェースを受け入れ、構造体を返します
- グローバルレジストリーまたはパッケージレベルのサービスロケーターを使用 しないでください
- DI コンテナーはコンポジション ルート (
main()またはアプリスタートアップ) にのみ存在する 必要があります — コンテナーを依存関係として渡さないでください - 遅延初期化を優先 — サービスを最初にリクエストされたときだけ作成します
- ステートフルサービス (DB 接続、キャッシュ) にはシングルトンを使用し、ステートレスサービスには一時的なものを使用します
- インターフェース境界でモックする — DI でこれは簡単です
- 依存グラフを浅く保つ — 深いチェーンは設計上の問題を示します
- プロジェクトサイズとチームに合った DI ライブラリを選択 — 以下の決定テーブルを参照してください
依存性注入がなぜ必要?
| DI がない場合の問題 | DI がこれをどう解決するか |
|---|---|
| 関数が独自の依存関係を作成する | 依存関係が注入される — 実装を自由に切り替える |
| テストは実際のデータベース、API が必要 | テストではモック実装を渡します |
| 1 つのコンポーネントを変更すると他が壊れる | インターフェース経由の疎結合 — コンポーネント同士が内部を知りません |
| サービスが至る所で初期化される | 集中管理されたコンテナーはライフサイクルを管理します (シングルトン、ファクトリー、遅延) |
| すべてのサービスがスタートアップで読み込まれる | 遅延読み込み — サービスは最初にリクエストされたときだけ作成されます |
グローバル状態と init() 関数 | スタートアップでの明示的な配線 — 予測可能でデバッグ可能 |
DI は多くの相互接続されたサービスを持つアプリケーション — HTTP サーバー、マイクロサービス、プラグイン付き CLI ツール で輝きます。2-3 個の関数を持つ小さいスクリプトの場合、マニュアル配線で問題ありません。オーバーエンジニアリングしないでください。
マニュアルコンストラクタ注入 (ライブラリなし)
小規模プロジェクトでは、コンストラクタを通じて依存関係を渡します。完全なアプリケーション例については、Manual DI examples を参照してください。
// ✓ Good — 明示的な依存関係、テスト可能
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 — マニュアル配線
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 — ハードコードされた依存関係、テスト不可能
type UserService struct {
db *sql.DB
}
func NewUserService() *UserService {
db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL")) // 隠された依存関係
return &UserService{db: db}
}
マニュアル DI は以下の場合に破綻します:
- 15 以上のサービスがあり相互依存がある
- ライフサイクル管理が必要 (ヘルスチェック、グレースフルシャットダウン)
- 遅延初期化またはスコープ付きコンテナーが必要
- 配線順序が脆弱になり保守が困難
DI ライブラリの比較
Go には DI ライブラリへの 3 つの主要なアプローチがあります:
google/wire examples— コンパイル時コード生成uber-go/dig + fx examples— リフレクションベースフレームワークsamber/do examples— ジェネリクスベース、コード生成なし
決定テーブル
| 基準 | マニュアル | google/wire | uber-go/dig + fx | samber/do |
|---|---|---|---|---|
| プロジェクトサイズ | 小 (< 10 サービス) | 中-大 | 大 | 任意のサイズ |
| 型安全性 | コンパイル時 | コンパイル時 (コード生成) | ランタイム (リフレクション) | コンパイル時 (ジェネリクス) |
| コード生成 | なし | 必須 (wire_gen.go) | なし | なし |
| リフレクション | なし | なし | はい | なし |
| API スタイル | N/A | Provider セット + ビルドタグ | Struct タグ + デコレータ | シンプルなジェネリック関数 |
| 遅延読み込み | マニュアル | N/A (すべてイーガー) | 組み込み (fx) | 組み込み |
| シングルトン | マニュアル | 組み込み | 組み込み | 組み込み |
| 一時的/ファクトリー | マニュアル | マニュアル | 組み込み | 組み込み |
| スコープ/モジュール | マニュアル | Provider セット | モジュールシステム (fx) | 組み込み (階層的) |
| ヘルスチェック | マニュアル | マニュアル | マニュアル | 組み込みインターフェース |
| グレースフルシャットダウン | マニュアル | マニュアル | 組み込み (fx) | 組み込みインターフェース |
| コンテナークローニング | N/A | N/A | N/A | 組み込み |
| デバッグ | Print ステートメント | コンパイルエラー | fx.Visualize() | ExplainInjector()、ウェブインターフェース |
| Go バージョン | 任意 | 任意 | 任意 | 1.18+ (ジェネリクス) |
| 学習曲線 | なし | 中 | 高 | 低 |
クイック比較: 同じアプリ、4 つの方法
依存グラフ: Config -> Database -> UserStore -> UserService -> API
マニュアル:
cfg := NewConfig()
db := NewDatabase(cfg)
store := NewUserStore(db)
svc := NewUserService(store)
api := NewAPI(svc)
api.Run()
// 自動シャットダウン、ヘルスチェック、遅延読み込みなし
google/wire:
// wire.go — その後 wire ./... を実行
func InitializeAPI() (*API, error) {
wire.Build(NewConfig, NewDatabase, NewUserStore, NewUserService, NewAPI)
return nil, nil
}
// シャットダウンまたはヘルスチェックサポートなし
uber-go/fx:
app := fx.New(
fx.Provide(NewConfig, NewDatabase, NewUserStore, NewUserService),
fx.Invoke(func(api *API) { api.Run() }),
)
app.Run() // ライフサイクルを管理しますが、リフレクションベース
samber/do:
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() — すべてのクリーンアップを自動的に処理
DI でのテスト
DI はテストを簡単にします — 実装の代わりにモックを注入するだけです:
// モックを定義
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
}
// マニュアル注入でテスト
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) {
// モック実装でテストインジェクターを作成
testInjector := do.New()
// モック UserStore インターフェースを提供
do.Override[UserStore](testInjector, &MockUserStore{
users: map[string]*User{"1": {ID: "1", Name: "Alice"}},
})
// 必要に応じて他の実際のサービスを提供
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")
// ... アサーション
}
これは特にほとんどのサービスが実際である統合テストで、特定の境界 (データベース、外部 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スキルを参照
参考文献
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- samber
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/samber/cc-skills-golang / ライセンス: MIT
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。