Go依存性注入実戦:Uber Fxでモジュラーアプリケーションアーキテクチャを構築する5つのコアパターン

编程语言

Go依存性注入:2026年にまだ手動で依存関係を組み立てていますか?

まだmain()で数十の依存関係を手動作成していますか?1つのコンストラクタを変更するのに10箇所の呼び出しを修正していますか?テスト時のモック依存関係の差し替えで大量のコード変更が必要ですか?依存性注入(DI)はこれらの問題を解決するコアパターンです。2026年、Uber FxはGoエコシステムで最も成熟したDIフレームワークとなり、Uber、Lyftなどで大規模に使用されています。FxはProvider + Invoker + Lifecycleの3つのコア概念により、宣言的依存アセンブリ→自動依存グラフ構築→グレースフル起動/停止管理を実現しています。

本記事では5つのコアパターンから、モジュール定義→依存注入→ライフサイクル管理→オプショナル依存→テスト統合のフルパイプライン実戦を解説します。


コア概念

概念 説明
Provider 依存関係を提供するコンストラクタ関数
Invoker 依存関係を消費する関数
Module ProviderとInvokerの論理的グループ
Lifecycle アプリケーション起動/停止ライフサイクルフック
Value Group 同一型の複数依存のコレクション
Named 名前付き依存、同型の異なるインスタンスを区別
Optional オプショナル依存、存在しない場合もエラーにならない
Decorate デコレータ、既存の依存を置換またはラップ

問題分析:Go依存管理の5つの課題

  1. 手動組み立て:main()に数十行のNewXxx()呼び出し、順序に依存
  2. 循環依存:AがBに依存、BがAに依存、コンパイルエラー
  3. ライフサイクル管理:データベース接続、HTTPサーバーのグレースフルシャットダウン
  4. テスト困難:依存関係の差し替えに大量のコード修正が必要
  5. モジュール再利用:異なるサービスが同じインフラモジュールを共有

ステップバイステップ:5つのUber Fxコアパターン

パターン1:Fxアプリケーション基礎とモジュール定義

// cmd/server/main.go
package main

import (
	"go.uber.org/fx"
	"github.com/example/myapp/internal/config"
	"github.com/example/myapp/internal/database"
	"github.com/example/myapp/internal/httpserver"
	"github.com/example/myapp/internal/repository"
	"github.com/example/myapp/internal/service"
)

func main() {
	app := fx.New(
		fx.Provide(
			config.NewConfig,
			database.NewPostgresDB,
			repository.NewUserRepository,
			repository.NewOrderRepository,
			service.NewUserService,
			service.NewOrderService,
		),
		fx.Invoke(httpserver.RegisterRoutes),
	)
	app.Run()
}

パターン2:Provider依存注入とコンストラクタパターン

// internal/database/postgres.go
package database

import (
	"context"
	"fmt"
	"time"

	"github.com/example/myapp/internal/config"
	"github.com/jackc/pgx/v5/pgxpool"
	"go.uber.org/fx"
)

func NewPostgresDB(lc fx.Lifecycle, cfg *config.Config) (*pgxpool.Pool, error) {
	dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
		cfg.Database.User, cfg.Database.Password,
		cfg.Database.Host, cfg.Database.Port,
		cfg.Database.DBName, cfg.Database.SSLMode)

	poolCfg, err := pgxpool.ParseConfig(dsn)
	if err != nil {
		return nil, fmt.Errorf("failed to parse database config: %w", err)
	}

	poolCfg.MaxConns = 25
	poolCfg.MinConns = 5
	poolCfg.MaxConnIdleTime = 5 * time.Minute

	pool, err := pgxpool.NewWithConfig(context.Background(), poolCfg)
	if err != nil {
		return nil, fmt.Errorf("failed to create connection pool: %w", err)
	}

	lc.Append(fx.Hook{
		OnStart: func(ctx context.Context) error { return pool.Ping(ctx) },
		OnStop:  func(ctx context.Context) error { pool.Close(); return nil },
	})

	return pool, nil
}

パターン3:ライフサイクル管理とグレースフルシャットダウン

// internal/httpserver/server.go
package httpserver

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"go.uber.org/fx"
)

func NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux, cfg *config.Config) *http.Server {
	srv := &http.Server{
		Addr:         fmt.Sprintf(":%d", cfg.Server.Port),
		Handler:      mux,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	lc.Append(fx.Hook{
		OnStart: func(ctx context.Context) error {
			go srv.ListenAndServe()
			return nil
		},
		OnStop: func(ctx context.Context) error {
			shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
			defer cancel()
			return srv.Shutdown(shutdownCtx)
		},
	})

	return srv
}

パターン4:Value Groupと名前付き依存

// internal/middleware/module.go
package middleware

import (
	"net/http"
	"go.uber.org/fx"
)

type Middleware func(http.Handler) http.Handler

var Module = fx.Module("middleware",
	fx.Provide(
		fx.Annotate(NewLoggingMiddleware, fx.ResultTags(`group:"middlewares"`)),
		fx.Annotate(NewAuthMiddleware, fx.ResultTags(`group:"middlewares"`)),
		fx.Annotate(NewCORSMiddleware, fx.ResultTags(`group:"middlewares"`)),
		fx.Annotate(NewRateLimitMiddleware, fx.ResultTags(`group:"middlewares"`)),
	),
	fx.Invoke(ChainMiddlewares),
)

func ChainMiddlewares(mux *http.ServeMux, middlewares []Middleware) {
	var handler http.Handler = mux
	for i := len(middlewares) - 1; i >= 0; i-- {
		handler = middlewares[i](handler)
	}
	mux.Handle("/", handler)
}

パターン5:Fxテスト統合

// internal/service/user_test.go
package service_test

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"go.uber.org/fx/fxtest"

	"github.com/example/myapp/internal/repository"
	"github.com/example/myapp/internal/service"
)

type MockUserRepository struct {
	mock.Mock
}

func (m *MockUserRepository) GetByID(ctx context.Context, id int64) (*repository.User, error) {
	args := m.Called(ctx, id)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*repository.User), args.Error(1)
}

func TestUserService_GetUser(t *testing.T) {
	mockRepo := new(MockUserRepository)
	mockRepo.On("GetByID", mock.Anything, int64(1)).Return(&repository.User{
		ID: 1, Username: "testuser", Email: "test@example.com",
	}, nil)

	svc := service.NewUserService(mockRepo)
	user, err := svc.GetUser(context.Background(), 1)

	assert.NoError(t, err)
	assert.Equal(t, "testuser", user.Username)
}

よくある落とし穴

落とし穴1:循環依存

// ❌ 間違い:AがBに依存、BがAに依存
fx.Provide(NewA, NewB)

// ✅ 正しい:共有依存をCに抽出
fx.Provide(NewC, NewA, NewB)

落とし穴2:Providerの登録忘れ

// ❌ 間違い:未登録の依存を使用
fx.Invoke(func(svc *service.OrderService) { ... })

// ✅ 正しい:すべての依存を登録
fx.Provide(service.NewOrderService),
fx.Invoke(func(svc *service.OrderService) { ... }),

落とし穴3:ライフサイクルHookのブロック

// ❌ 間違い:OnStartでブロック
lc.Append(fx.Hook{
    OnStart: func(ctx context.Context) error {
        srv.ListenAndServe()  // ブロック!
        return nil
    },
})

// ✅ 正しい:goroutineで起動
lc.Append(fx.Hook{
    OnStart: func(ctx context.Context) error {
        go srv.ListenAndServe()
        return nil
    },
})

落とし穴4:Value Groupの型不一致

// ❌ 間違い:Group要素の型が不一致
fx.Annotate(NewLogMiddleware, fx.ResultTags(`group:"middlewares"`))   // Middleware
fx.Annotate(NewAuthHandler, fx.ResultTags(`group:"middlewares"`))     // http.Handler

// ✅ 正しい:Group要素の型を統一
fx.Annotate(NewLogMiddleware, fx.ResultTags(`group:"middlewares"`))   // Middleware
fx.Annotate(NewAuthMiddleware, fx.ResultTags(`group:"middlewares"`)) // Middleware

落とし穴5:テストでfxtest未使用

// ❌ 間違い:手動でFxアプリを作成してテスト
app := fx.New(...)

// ✅ 正しい:fxtestで自動検証
app := fxtest.New(t, ...)
app.RequireStart()
defer app.RequireStop()

エラートラブルシューティング

# エラーメッセージ 原因 解決方法
1 missing type: *service.UserService Provider未登録 fx.Provideを追加
2 cycle detected 循環依存 共有依存を抽出
3 cannot provide function returns コンストラクタ署名エラー 依存型を返すことを確認
4 OnStart hook timed out 起動タイムアウト Hookがブロックしていないか確認
5 already providing type Providerの重複登録 fx.Namedで区別
6 no value found for group Value Groupが空 GroupタグとProviderを確認
7 optional dependency not found オプショナル依存の欠落 fx.AnnotateでOptionalをマーク
8 invalid Annotate usage アノテーション使用エラー fx.Annotateパラメータを確認
9 context cancelled シャットダウン中の操作 OnStop Hookを確認
10 failed to build dependency graph 依存グラフ構築失敗 すべてのProviderとInvokeを確認

高度な最適化

  1. モジュラー編成:fx.Moduleでビジネスドメイン別にProviderとInvokerをグループ化
  2. デコレータパターン:fx.Decorateでテスト環境の依存を置換
  3. 条件付き注入:fx.Annotateのfx.OptionalTagsで条件付き依存を実現
  4. 依存置換:fx.Replaceでテスト中に特定の依存をスワップ
  5. カスタムロギング:fx.WithLoggerでFxログ出力形式をカスタマイズ

比較分析

次元 Uber Fx Wire Dig google/wire
注入方式 ランタイム コンパイル時 ランタイム コンパイル時
型安全性 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
ライフサイクル ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐
Value Group ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐
テスト親和性 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
デバッグ情報 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
学習曲線

まとめ:Uber FxはGoの依存性注入を「手動組み立て」から「宣言的自动注入」へ進化させます。Provider→Invoker→Lifecycle→Value Group→テスト統合の5つが一体となり、依存の自動解決、ライフサイクル管理、グレースフルシャットダウンを実現します。核心原則:コンストラクタはProvider、依存はパラメータ、ライフサイクルはHook


オンラインツール推奨

ブラウザローカルツールを無料で試す →

#Go依赖注入#Uber Fx#Go DI框架#Go应用架构#2026#编程语言