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つの課題
- 手動組み立て:main()に数十行のNewXxx()呼び出し、順序に依存
- 循環依存:AがBに依存、BがAに依存、コンパイルエラー
- ライフサイクル管理:データベース接続、HTTPサーバーのグレースフルシャットダウン
- テスト困難:依存関係の差し替えに大量のコード修正が必要
- モジュール再利用:異なるサービスが同じインフラモジュールを共有
ステップバイステップ: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を確認 |
高度な最適化
- モジュラー編成:fx.Moduleでビジネスドメイン別にProviderとInvokerをグループ化
- デコレータパターン:fx.Decorateでテスト環境の依存を置換
- 条件付き注入:fx.Annotateのfx.OptionalTagsで条件付き依存を実現
- 依存置換:fx.Replaceでテスト中に特定の依存をスワップ
- カスタムロギング:fx.WithLoggerでFxログ出力形式をカスタマイズ
比較分析
| 次元 | Uber Fx | Wire | Dig | google/wire |
|---|---|---|---|---|
| 注入方式 | ランタイム | コンパイル時 | ランタイム | コンパイル時 |
| 型安全性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| ライフサイクル | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | ⭐⭐ |
| Value Group | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| テスト親和性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| デバッグ情報 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 学習曲線 | 中 | 高 | 低 | 高 |
まとめ:Uber FxはGoの依存性注入を「手動組み立て」から「宣言的自动注入」へ進化させます。Provider→Invoker→Lifecycle→Value Group→テスト統合の5つが一体となり、依存の自動解決、ライフサイクル管理、グレースフルシャットダウンを実現します。核心原則:コンストラクタはProvider、依存はパラメータ、ライフサイクルはHook。
オンラインツール推奨
- JSONフォーマッター:/ja/json/format
- Hash計算:/ja/encode/hash
- cURL→コード変換:/ja/dev/curl-to-code
ブラウザローカルツールを無料で試す →
#Go依赖注入#Uber Fx#Go DI框架#Go应用架构#2026#编程语言