Backend

Wire를 활용한 Go 의존성 주입(DI) 구현

2025-12-304 min read

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

주의사항

  1. 빌드 태그 필수: //go:build wireinject 잊지 말 것
  2. wire_gen.go 커밋: 생성된 코드는 버전 관리에 포함
  3. 순환 의존성 불가: Wire가 에러로 감지
  4. 에러 처리: Provider가 error를 반환하면 자동 전파

참고 자료

Share

Related Articles

Comments

이 블로그는 제가 알고 있는 것들을 잊지 않기 위해 기록하는 공간입니다.
직접 작성한 글도 있고, AI의 도움을 받아 정리한 글도 있습니다.
정확하지 않은 내용이 있을 수 있으니 참고용으로 봐주세요.

© 2026 Seogyu Kim