[App] Koin: 경량화된 의존성 주입

[App] Koin: 경량화된 의존성 주입
https://insert-koin.io

최근 다양한 프로젝트에서 MVVM, MVI 패턴을 기본으로 하는 경우가 많아졌다. 그러면서 Android 개발자들이 새로 배워야 하는 개념이 나왔는데 이것이 바로 DI(Data Injection, 의존성 주입)이다. 객체 및 모듈 간의 결합도를 감소시키고 재사용성을 높이기 위해 사용한다. 직접 구현해도 되겠지만 유지보수와 편리함을 위해 라이브러리를 주로 사용하는데 Dagger Hilt, Koin이 대표적이다. 해당 글은 이 중 Koin에 대해 다뤄보려 한다.

Koin

Koin이 관심을 받게 된 주요 이유는 간단함빠른 시작에 있다. Dagger Hilt는 강력하고 안정적인 컴파일 타임 검증을 제공하지만, 설정과 어노테이션이 많아 처음 사용하기에 진입 장벽이 높을 수 있다. 하지만, Koin은 설정도 간편하고 어노테이션 없이 Kotlin의 함수형 스타일과 DSL을 활용해 간결하고 읽기 쉬운 코드를 작성할 수 있어 인기를 얻고 있다.

또한, 최근 Kotlin Multiplatform(이하 KMP)에 대한 관심도가 높아지고 있는데 KMP에서는 DI 라이브러리에서 Dagger Hilt를 지원하지 않는다. 둘 중에서는 Koin만 지원하는데 해당 스택 개발을 위해서도 공부를 할 필요가 있어졌다.

Koin이란?

Kotlin으로 작성된 경량 DI 라이브러리이다. 선언적이고 사용하기 쉬운 API를 제공하여 XML이나 복잡한 설정 없이 코드만으로 의존성을 주입할 수 있어 진입 장벽이 낮다.

Koin 장점

  • 간단한 설정 및 사용: 별도의 복잡한 코드 생성 과정 없이, Kotlin DSL로 간결하게 의존성을 정의할 수 있어 빠르게 시작할 수 있음.
  • Kotlin 친화적: Android 자체에 의존적이기 보다 Kotlin 언어와 문법을 최대한 활용해 코드를 작성할 수 있게 하여 읽기 쉽고 유지보수하기가 편리함.
  • 유연한 런타임 바인딩: 컴파일 타임이 아닌 런타임에 의존성을 주입하여 빠른 프로토타이핑과 테스트가 용이하다.
  • 가벼운 라이브러리: 무거운 코드 생성이나 복잡한 설정이 필요하지 않아 프로젝트에 부담없이 통합할 수 있다.

Koin vs Dagger Hilt

가장 대표적인 라이브러리인 이 둘의 차이점에 대해 말해보려 한다.

Koin Dagger Hilt
설정 방식 코드 기반 DSL로 런타임에 모듈 구성 어노테이션 기반, 컴파일 타임 코드 생성
학습 곡선 상대적으로 쉬움 초기 설정 및 개념 이해 필요 (예: 컴포넌트, 스코프 등)
성능 런타임에 바인딩 → 성능 오버헤드 가능 컴파일 타임 생성 → 높은 성능 및 안정성
확장성 소규모/중규모 프로젝트에 적합 대규모 프로젝트 및 복잡한 의존성 그래프 관리에 유리
디버깅 런타임 오류를 통해 디버깅 가능 컴파일 시점에 문제를 발견할 수 있어 디버깅이 용이함

적용 예시

먼저 build.gradle.kts 파일에 라이브러리를 추가한다.

// build.gradle.kts (Module: app)
plugins {
    id("com.android.application")
    kotlin("android")
}

...

dependencies {
    // Android
    implementation("io.insert-koin:koin-android:$koin_android_version")
    
    // Kotlin Multiplatform
    implementation("io.insert-koin:koin-core:$koin_version")
}

아래처럼 사용할 모듈들을 appModule에 정의한다.

val appModule = module {
    // Repository를 싱글톤으로 등록
    single<Repository> { RepositoryImpl() }
    
    // MainViewModel을 factory로 등록
    factory { MainViewModel(get()) }
}

이후 MainApplication에 아래와 같이 Koin을 초기화해주면 끝이다. startKoin 안에 기타 context, log, modules 등 필요한 내용들을 넣어주면 된다.

class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        startKoin {
            // Kolin Log를 Android Logger에 추가
            androidLogger()
            // Android Context
            androidContext(this@MainApplication)
            // appModule 로드
            modules(appModule)
        }
    }
}

아래는 Activity에서 어떻게 사용하는지에 대한 예시이다. getViewModel() 또는 koinViewModel을 통해 의존성을 주입한다.

@Composable
fun MainScreen(mainViewModel: MainViewModel = getViewModel()) {
    // 또는 파라미터 기본값으로 koinViewModel<MainViewModel>() 사용 가능
    val data = mainViewModel.getData()

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(text = data)
    }
}

마무리하며

나는 처음 MVVM 패턴을 접하고 실제 프로젝트에 적용하기 위해 구글이 Github에 올려놓은 sunflower 예제를 많이 참고했다. 구글이 작성한 예제다 보니 Dagger Hilt 라이브러리를 사용했고 나도 이를 그대로 따라하여 적용시켰다. 그러다 최근 KMP 애플리케이션 개발을 위해 Koin을 적극적으로 활용해보고 있는데 처음 든 생각은 이렇게만 하면 설정 끝인가? 였다. 생각보다 너무 간단해서 제대로 구현이 되고 있는건지 궁금할 정도였다. 걱정과는 달리 잘 적용이 되었고 아직 나의 경험으로는 성능 상으로도 크게 다른 점이 없는 것 같다.

여러 장점 중 Koin은 Kotlin 기반이라는 점이 굉장히 마음에 들기 때문에 간단한 프로젝트에는 Koin 라이브러리를 자주 사용해보지 않을까 싶다.

참고 사이트

Koin - The Kotlin Dependency Injection Framework
The Kotlin Dependency Injection Framework
Park Sang-uk

Park Sang-uk

South Korea