Backend
Wire를 활용한 Go 의존성 주입(DI) 구현
Wire를 활용한 Go 의존성 주입(DI) 구현
개요
Wire는 Google에서 개발한 Go용 컴파일 타임 의존성 주입 도구입니다. 리플렉션 없이 코드 생성을 통해 DI를 구현하므로 런타임 오버헤드가 없습니다.
왜 Wire인가?
| 도구 | 방식 | 장점 | 단점 |
|---|---|---|---|
| 수동 DI | 직접 구성 | 단순, 명시적 | 보일러플레이트 |
| dig/fx | 런타임 리플렉션 | 유연 | 런타임 오버헤드, 디버깅 어려움 |
| Wire | 컴파일 타임 코드 생성 | 타입 안전, 오버헤드 없음 | 초기 설정 필요 |
설치
go install github.com/google/wire/cmd/wire@latest
기본 개념
Provider
Provider는 의존성을 생성하는 함수입니다:
// providers.go
package main
type Config struct {
DBHost string
DBPort int
}
func NewConfig() *Config {
return &Config{
DBHost: "localhost",
DBPort: 5432,
}
}
type Database struct {
config *Config
}
func NewDatabase(cfg *Config) (*Database, error) {
return &Database{config: cfg}, nil
}
type UserRepository struct {
db *Database
}
func NewUserRepository(db *Database) *UserRepository {
return &UserRepository{db: db}
}
type UserService struct {
repo *UserRepository
}
func NewUserService(repo *UserRepository) *UserService {
return &UserService{repo: repo}
}
Injector
Injector는 의존성 그래프를 정의하는 함수입니다:
// wire.go
//go:build wireinject
package main
import "github.com/google/wire"
func InitializeUserService() (*UserService, error) {
wire.Build(
NewConfig,
NewDatabase,
NewUserRepository,
NewUserService,
)
return nil, nil // Wire가 이 부분을 생성된 코드로 대체
}
코드 생성
wire ./...
# 생성된 파일: wire_gen.go
생성된 wire_gen.go:
// Code generated by Wire. DO NOT EDIT.
//go:build !wireinject
package main
func InitializeUserService() (*UserService, error) {
config := NewConfig()
database, err := NewDatabase(config)
if err != nil {
return nil, err
}
userRepository := NewUserRepository(database)
userService := NewUserService(userRepository)
return userService, nil
}
Provider Set
관련 Provider들을 그룹화할 수 있습니다:
// infrastructure/wire.go
package infrastructure
import "github.com/google/wire"
var InfraSet = wire.NewSet(
NewConfig,
NewDatabase,
NewRedisClient,
NewLogger,
)
// repository/wire.go
package repository
import "github.com/google/wire"
var RepositorySet = wire.NewSet(
NewUserRepository,
NewOrderRepository,
NewProductRepository,
)
// service/wire.go
package service
import "github.com/google/wire"
var ServiceSet = wire.NewSet(
NewUserService,
NewOrderService,
NewProductService,
)
// wire.go
package main
import "github.com/google/wire"
func InitializeApp() (*App, error) {
wire.Build(
infrastructure.InfraSet,
repository.RepositorySet,
service.ServiceSet,
NewApp,
)
return nil, nil
}
인터페이스 바인딩
인터페이스에 구현체를 바인딩할 수 있습니다:
// 인터페이스 정의
type UserRepository interface {
FindByID(id string) (*User, error)
Save(user *User) error
}
// 구현체
type userRepositoryImpl struct {
db *Database
}
func NewUserRepository(db *Database) *userRepositoryImpl {
return &userRepositoryImpl{db: db}
}
// Wire 바인딩
var RepositorySet = wire.NewSet(
NewUserRepository,
wire.Bind(new(UserRepository), new(*userRepositoryImpl)),
)
Struct Provider
구조체 필드를 직접 주입할 수 있습니다:
type App struct {
UserService *UserService
OrderService *OrderService
Logger *Logger
}
var AppSet = wire.NewSet(
wire.Struct(new(App), "*"), // 모든 필드 주입
// 또는 특정 필드만
// wire.Struct(new(App), "UserService", "Logger"),
)
Value Provider
정적 값을 제공할 수 있습니다:
func InitializeApp(cfg *Config) (*App, error) {
wire.Build(
wire.Value(&Config{DBHost: "localhost"}), // 정적 값
NewDatabase,
NewApp,
)
return nil, nil
}
실전 프로젝트 구조
myapp/
├── cmd/
│ └── api/
│ ├── main.go
│ ├── wire.go # Injector 정의
│ └── wire_gen.go # 생성된 코드
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── infrastructure/
│ │ ├── database.go
│ │ ├── redis.go
│ │ └── wire.go # Provider Set
│ ├── repository/
│ │ ├── user_repository.go
│ │ └── wire.go
│ ├── service/
│ │ ├── user_service.go
│ │ └── wire.go
│ └── handler/
│ ├── user_handler.go
│ └── wire.go
└── go.mod
cmd/api/wire.go
//go:build wireinject
package main
import (
"github.com/google/wire"
"myapp/internal/config"
"myapp/internal/infrastructure"
"myapp/internal/repository"
"myapp/internal/service"
"myapp/internal/handler"
)
func InitializeServer(cfg *config.Config) (*Server, error) {
wire.Build(
infrastructure.InfraSet,
repository.RepositorySet,
service.ServiceSet,
handler.HandlerSet,
NewServer,
)
return nil, nil
}
cmd/api/main.go
package main
func main() {
cfg := config.Load()
server, err := InitializeServer(cfg)
if err != nil {
log.Fatal(err)
}
server.Run()
}
테스트에서 Wire 활용
// wire_test.go
//go:build wireinject
package main
import "github.com/google/wire"
func InitializeTestUserService(mockDB *MockDatabase) *UserService {
wire.Build(
NewUserRepository,
NewUserService,
)
return nil
}
Makefile 통합
.PHONY: generate wire
wire:
wire ./...
generate: wire
go generate ./...
build: generate
go build -o bin/app ./cmd/api
주의사항
- 빌드 태그 필수:
//go:build wireinject잊지 말 것 - wire_gen.go 커밋: 생성된 코드는 버전 관리에 포함
- 순환 의존성 불가: Wire가 에러로 감지
- 에러 처리: Provider가 error를 반환하면 자동 전파