DI_관리_기술_선정 - boostcampwm-2024/and04-Nature-Album GitHub Wiki
💡 의존성 관리 기술 선정 개요
우리 팀원들이 각각 의존성 관리를 수동으로 구현해본 결과, 각 클래스나 모듈에서 어떤 의존성이 주입되는지 파악하기 어려운 문제가 있었다. 특히, Service Locator 패턴을 사용했던 팀원도 Hilt를 통해 의존성 주입을 자동화하면 신경 써야 할 부분이 줄어들어 코드 관리가 더 수월해진다고 언급했다. 이러한 경험을 바탕으로, 수동 의존성 주입보다는 라이브러리를 사용하여 효율적으로 관리하는 것이 유리하다고 판단했다.
따라서 팀 전체의 의존성 관리 방식을 통일하고자 기술 선정에 대한 논의를 진행하였으며, Hilt와 Koin을 비교해 최적의 의존성 관리 기술을 선정하게 되었다.
아래의 DI 관련 정리는 앞서 논의 과정 중 도윤님께서 세미나를 진행하며 정리된 내용이다.
💡 DI 관련 정리
의존성 주입을 하지 않을 경우
class A(){
val b = B()
// 위와 같이 진행할 경우
A >> 교체 하려 할 떄 B 까지 신경
B에 의해 A가 간섭 받음 테스트도 불편함 (
}
의존성 주입할 경우
class A(b: B){
// 위와 같이 진행할 경우
A >> 교체 하려 할 떄 B 까지 신경 쓰지 않아도 됨
테스트도 용이해짐
}
수동 의존성 주입
Service Locator
- 각 클래스가 있을 때 Service Locator에 몰아두고 Application에 연결해서 각 Class가 사용하려고 할 때 Service Locator를 통해 전달
class A
class B
class C
class ServiceLocator{
private val a = A()
private val b = B()
private val c = C()
fun getA() = a
fun getB() = b
fun getC() = c
}
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
serviceLocator = ServiceLocator()
}
companion object {
// 싱글톤 생략
lateinit var serviceLocator: ServiceLocator
}
}
val testClass = TestClass(MyApplication.serviceLocator.getA()) // 직접 주입
단점
- 의존성 주입이 필요한 클래스가 많아지면 위와 같은 코드가 많아지고, 관리가 어려워진다. (수동)
- 어디서 어떻게 주입되는지 알기 어려워지는 경우가 생김
- 테스트가 어렵다.
- Service Locator가 갖고 있는 다른 클래스들에 대한 의존이 함께 발생
- 런타임에서 오류 가능성
자동 의존성 주입
Hilt
- 컴파일 과정에서 의존성 주입 검증이 일어나기 때문에 컴파일 과정에서 에러 발생
- 어플리케이션 시작 시 단 한 번만 반영
장점
- LifeCycle 관리
- 의존 관계 파악 용이
- 보일러플레이트 감소
- 테스트 용이
단점
- 컴파일 속도 저하(어노테이션 사용)
- Stub 파일 생성으로 인한 빌드 시간 증가
@HiltAndroidApp
class MyApplication: Application(){}
@AndroidEntryPoint
class Activity: AppCompatActivity(){
}
@HiltViewModel
class MyViewModel: ViewModel @Inject constructor(
private val repo: AnalyticsService,
private val repo2: AnalyticsService
){
}
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
@SameReturnValue
@Provides
fun provideAnalyticsService2(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com2")
.build()
.create(AnalyticsService::class.java)
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
interface AnalyticsService {
fun analyticsMethods()
}
class AnalyticsServiceImpl @Inject constructor(
private val repo: AnalyticsService
) : AnalyticsService { ... }
@Qualifier
annotation class SameReturnValue
provideAnalyticsService
처럼 반환값이 같은 경우에는 1개만 주입된다. 이때@Qualifier
를 사용하여 구분 지어 의존성 주입될 수 있다.
Hilt를 사용한 종속 항목 삽입 | Android Developers
Koin
- Service Locator 기반
- 문제가 있을 경우 런타임에서 에러 발생
- 유연성 증가 >> 동적으로 코드 변경 또는 확장 가능
- 컴파일 시간 단축 (어노테이션 사용 x)
- *리플렉션을 사용하기 때문에 성능이 저하 될 수 있음
[Why Koin? | Koin](https://insert-koin.io/docs/setup/why/)
💡 Hilt를 선택한 이유
- 빠른 시간 내에 프로젝트를 개발하고 도입해야 하는 우리 팀의 요구 사항에는 Koin이 적합해 보였으나, 장기적인 유지보수와 안정성을 고려했을 때 Hilt가 더 유리하다고 판단하였다.
- Koin은 런타임에서 오류가 발생할 가능성이 높아, 프로젝트 관리와 유지보수가 어려워질 수 있다.
따라서, 우리 팀은 Hilt를 선택하여 의존성 관리를 통일하기로 결정하였다. Hilt를 사용함으로써 코드의 복잡성을 줄이고, 유지보수성을 높이며, 프로젝트의 안정성을 보장할 수 있을 것이다.
*리플렉