[MVVM 정복] 4. 어렵고 이해도 잘 안되는 DI, 그리고 Koin
728x90
반응형

이번편에서도 코드를 보면서 학습하길 권한다.

 

conquerex/mvvm-template

MVVM 학습과 앞으로 활용을 위한 템플릿. Contribute to conquerex/mvvm-template development by creating an account on GitHub.

github.com

 

DI(Dependency injection)가 "의존성 주입"이라는 의미인 것은 많은 분들이 알고 있다.

그런데 의존성 주입이 무엇이냐고 물어보면 제대로 답할 수 있는 사람이 얼마나 될까?

그래서 알아보았다. DI, 넌 누구냐.

 

일단 의존성이 무엇인지 알아보자.

  • 두 모듈의 연결, 두 클래스의 관계
  • 의존성이 크다 == 결합도가 높다

'의존성이 큰게 왜??'라는 생각이 들 수 있다. 의존성이 크게 되면 독립성은 낮아진다. 독립성이 낮아지면 개별 모듈을 테스트하거나 이용하기 어려워진다. 하나의 모듈을 수정하면 이 모듈과 의존성이 큰 모듈에도 영향을 줄 것이고 이런 관계로 인해 개별 모듈을 수정해야할 수 있다. 만약 그런 모듈이 다수라면 수정할 양이 상당할 것이다.(때려치워 모드 On) 테스트도 마찬가지. 개별 모듈을 테스트하고 싶지만 의존도가 높은 모듈이 많다면 하나의 모듈을 독립적으로 테스트했다고 보기 어려울 것이다. DI를 프레임워크라고 하기도하고 디자인패턴이라고 하는 사람도 있지만 암튼 그런 추상화된 개념이다.

 

즉 의존성 주입이란, 특정 객체의 인스턴스가 필요한 경우 이를 외부에서 생성하여 전달하는 기법이다.

 

여기서 의존성 주입(DI)의 목적을 알아보자.

  • 가장 큰 목적은 모듈을 Testable하게 만들 수 있다는 점이다. 즉, 독립된 모듈에 대한 테스트 코드를 작성할 수 있다.
  • 하나의 모듈이 변경되어도 다른 모듈들이 영향을 받지 않는다. 따라서 유지보수가 용이하다.
  • new를 이용한 생성자를 없애자. 모듈 내에서 다른 모듈을 초기화하면 결합도가 높아지므로 객체 생성은 다른 곳에서 하고 생성된 객체를 참조하자.
  • 객체 생성을 외부에서 하면 클래스의 독립성이 높아지고 이에 따라서 클래스를 테스트 가능하게 할 수 있으며 
  • 재사용을 할 가능성도 높아진다.

 

이제 DI 중 하나인 Koin을 알아보자. Dagger라는 것도 있지만 어렵다길래 Pass~

 

순수 코틀린만으로 작성이 되었으며 어노테이션 프로세싱을 및 리플렉션을 사용하지 않기 때문에 상대적으로 더 가볍다. 본격적으로 Koin을 사용하기 전에 알아두면 좋은 개념을 몇가지 소개해본다. 바로 Koin DSL(Domain Specific Language).  도메인 특화 언어로 위키피디아에는 "특정한 도메인을 적용하는데 특화된 언어" 라고 정의되어있다.

  • single
    • Dagger에서 Singleton 애노테이션으로 사용했던 것과 비슷
    • Retrofit의 객체처럼 App전체 주기동안 계속 살아서 이용할 수 있는 객체를 생성할 때 사용
    • 즉, 앱이 살아있는 동안 전역적으로 사용가능한 객체를 생성
  • factory
    • Single과는 반대로 매번 객체를 생성해서 사용할 경우에 사용
    • inject하는 시점에 해당 객체를 생성
  • module
    • 객체를 만들어서 주입해 줄 대상들에 대한 목록
    • Koin모듈을 정의할때 사용
  • bind
    • 생성할 객체를 다른 타입으로 바인딩하고 싶을 때 사용한다.
  • get
    • 주입할 각 컴포넌트끼리의 의존성을 해결하기 위해 사용한다. 
    • 타입 추론을 통해 컴포넌트 내에서 이미 생성된 객체를 참조하게 된다.

그럼 의존성 주입을 하는 방법은 어떻게 될까???

// 방법 1. 직접 주입

class MyActivity() : AppCompatActivity() {

    val service : BusinessService by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       
        val service : BusinessService = get()
    }
}
// 방법 2. 생성자에 주입

class Controller(val service : BusinessService){ 
  fun hello() {
     service.sayHello()
  }
} 

 

이제 작성한 코드를 살펴보자.

import org.koin.dsl.module.module

var retrofitPart = module {
    single<KakaoSearchService> {
        Retrofit.Builder()
            .baseUrl("https://dapi.kakao.com")
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(KakaoSearchService::class.java)
    }
}

 

여기서 single은 module.module에서 받아오는건데 실제 라이브러리를 열어보면 single이 없다. module은 아래의 타입을 리턴으로 받는다.

 

typealias Module = (KoinContext) -> ModuleDefinition

여기서 ModuleDefinition에 single도 있고 factory도 있고 get도 있다. single키워드를 이용하여, Singleton타입의 객체를 주입할 수 있는 모듈을 생성하였다. 이제 초기화를 해보자.

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin(applicationContext, myDiModule)
    }
}
<application android:name=".MyApplication" ...>

startKoin으로 Koin을 시작하고 여러개의 모듈을 사용하게 될테니 리스트로 구성된 myDiModule을 인자로 넣어준다.
주의!!! MyApplication은 Manifest에서 name으로 등록해야한다는 사실!!! 이제 의존성 주입을 할 준비가 되었다. 의존성 주입을 해보자.

 

 

참고자료
https://woovictory.github.io/2019/05/04/What-is-DI/ (👍추천)
https://jungwoon.github.io/android/2019/08/21/Koin/
https://beomseok95.tistory.com/188 (👍추천)

 

728x90
반응형