먼저, 의존성 주입(Dependency Injection, DI)에 대해 설명하겠다.
의존성 주입은 여러 컴포넌트 간 의존성이 강한 개발 환경에서 클래스 간 결합도를 낮춰주는 멋진 도구다.
객체를 생성할 때는 자연스럽게 클래스 간 의존성이 생긴다. 예를 들어, A 객체를 만들기 위해 B 객체가 필요하다면, A 객체를 생성하는 시점에 불필요하게 B 객체까지 알아야 하는 상황이 발생한다. 이런 식으로 의존 관계가 꼬리를 물고 이어진다.
// main.kt
fun main() {
val b = B()
val a = A(b)
}
class A(val b: B) {
// 어쩌구 저쩌구
}
class B {
// 어쩌구 저쩌구
}
이때 A 클래스에 의존성 주입을 적용하면, main
에서 B를 몰라도 되는 아름다운 일이 생긴다.
정리하자면, 불필요한 의존 관계를 줄이기 위해 의존성 주입을 쓰는 것이다.
// main.kt
fun main() {
val a = A()
}
class A @Inject constructor(val b: B) {
// 어쩌구 저쩌구
}
class B {
// 어쩌구 저쩌구
}
의존성 주입을 위해 안드로이드에서는 주로 Hilt라는 라이브러리를 사용한다.
오늘 이야기할 주제는 바로 "UseCaseModule을 꼭 만들어야 할까?" 이다.
미리 정답을 말하자면, 아니다.
의존성 주입을 하려면 보통 아래처럼 @Binds
를 이용한 모듈 클래스를 만들고, 빌드 시점에 필요한 의존성들을 한 번에 생성한다.
@Module
@InstallIn(SingletonComponent::class)
abstract class DataModule {
@Binds
internal abstract fun bindsUserRepository(
oauthRepositoryImpl: UserRepositoryImpl,
): UserRepository
}
이렇게 세팅해두면, 아래처럼 @Inject constructor
로 바로 꺼내 쓸 수 있다.
class GetUserInfoUseCase @Inject constructor(
private val repo: UserRepository
) {
suspend operator fun invoke(): Result<User> {
return repo.getUser()
}
}
@Inject
는 Hilt에 포함된 어노테이션이지만,
domain 계층에서는 안드로이드 라이브러리에 대한 의존성이 없어야 한다.
그렇다면 어떻게 해야 할까?
implementation("javax.inject:javax.inject:1")
이렇게 하면 된다.
필요한 건 @Inject
뿐이므로, 불필요하게 Hilt 전체를 끌어오지 않아도 된다.
다른 방법으로는 UseCase 전용 모듈을 만드는 방식이 있다.
@Module
@InstallIn(SingletonComponent::class)
object UseCaseModule {
@Provides
@Singleton
fun provideGetUserInfoUseCase(
repo: UserRepository
): GetUserInfoUseCase = GetUserInfoUseCase(repo)
}
이렇게 하면 아래과 도메인 모듈에서@Inject
없이도 사용할 수 있지만…
UseCase를 하나 추가할 때마다 provide
함수를 또 작성해야 한다는 점이 개인적으로는 마음에 들지 않는다.
class GetUserInfoUseCase(
private val repo: UserRepository
) {
suspend operator fun invoke(): Result<User> {
return repo.getUser()
}
}
그래서 필자는 방법 1. @Inject
의존성만 추가하는 방식으로 결정했다!