의존성 주입(Dependency Injection)은 객체가 필요한 의존성 (Dependency)을 스스로 생성하지 않고 외부에서 주입받는 것이다. 객체가 스스로 의존성을 만들지 않고 외부가 객체의 흐름을 제어하는 것을 제어의 역전 (Inversion of Control)이라고 한다.
type UserService struct {
userRepository UserRepository
}
type UserRepository struct {
db Database
}
type Database struct {
conn string
}
위 코드에서 UserService는 UserRepository에 의존하고, UserRepository는 Database에 의존한다. 이처럼 객체 간 의존 관계가 형성될 때, 각 객체가 자신의 의존성을 직접 생성하는 대신 외부에서 주입받도록 설계하는 것이 의존성 주입이다.
func NewUserService(userRepository UserRepository) *UserService {
return &UserService{userRepository: userRepository}
}
func NewUserRepository(db Database) *UserRepository {
return &UserRepository{db: db}
}
func NewDatabase(conn string) *Database {
return &Database{conn: conn}
}
이렇게 생성자 함수를 통해 의존성을 주입받도록 직접 구현하면, 각 객체는 자신이 필요로 하는 의존성을 파라미터로 받아 초기화한다. 의존성 주입을 통해 다음의 장점을 얻을 수 있다:
느슨한 결합 (Loosely Coupled)
확장성 (Flexibility)과 테스트 용이성 (Testability)
Go의 의존성 주입은 위의 예시처럼 함수 인자나 struct 생성자에 필요한 의존성을 직접 넘겨주는 수동 DI를 보편적으로 활용한다. 다만 이렇게 수동 구조체 생성 순서와 의존성 연결을 직접 해주는 것은 초기에는 편리하지만, 시스템 전반에 존재하는 의존성이 많아진다면 관리에 어려움이 생긴다:
uber-go/fx는 이런 문제를 해결하기 위해 의존성 주입을 자동화하는 프레임워크이다. Fx는 다음과 같은 기능을 제공한다:
init()에 의존하지 않고 구성 요소를 생성자 함수 형태로 선언할 수 있다.func New() *fx.App {
app := fx.New(
// module
fx.Provide(NewUserService),
fx.Provide(NewUserRepository),
fx.Provide(NewDatabase),
// hook
fx.Invoke(registerHooks),
)
}
func registerHooks(lc fx.Lifecycle) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
// start hook
// ...
return nil
},
OnStop: func(ctx context.Context) error {
// stop hook
// ...
return nil
},
})
}
Fx는 내부적으로 크게 4가지 핵심 컴포넌트로 구성되어 있으며, 이들은 fx.App 안에서 하나의 실행 환경을 이룬다.
uber-go/dig는 fx 내부에서 의존성을 해결하는 DI Container이다. 예를 들어 fx의 함수는 다음으로 치환된다:
fx.Provide(New) 호출 -> dig.Provide(New)로 컨테이너에 생성자를 등록한다.
fx.Invoke(hooks) 호출 -> dig.Invoke(hooks)로 컨테이너를 통해 실행될 함수를 등록한다.컴포넌트별로 훅을 등록하면 의존성 그래프 순서대로 애플리케이션의 라이프사이클을 관리한다. 서버 및 DB 연결과 같은 리소스 생성 및 정리가 해당 함수 내엣 이뤄진다.
OnStart(ctx context.Context) error: 앱 시작 시점에 의존성 생성 순서대로 실행OnStop(ctx context.Context) error: 애 종료 시점에 의존성 생성의 반대 순서로 실행hook은 dig를 통해 자동으로 주입되는 컴포넌트 내부에서 등록된다.
Provide, Invoke, Decorate를 그룹으로 묶어 모듈이라는 하나의 이름공간로 관리할 수 있다.
fx.Option이고, 다른 모듈과도 결합할 수 있다.Fx의 최상위 실행 단위이며, 내부에서 DI Container, Lifecycle Manager, Module을 관리하는 오케스트레이션 엔진이다. App이 관리하는 구성 요소는 다음과 같다: