본문 바로가기

안드로이드

리플렉션으로 DI를 만들어보자 2

부제 자동 DI를 위한 여정이였던것 -> 테스트 편의성만 남음

자 이번 여정도 고난이다.

 

사실 2편에서는

  1. 안드로이드 컴포넌트 생명주기관련 특화기능
  2. 테스트 편의성을 위한 처리
  3. 힐트의 좋은 부분 뽑아오기(어노테이션을 좀 활용해야할듯 -> 미션임)
  4. 창의적인 추가기능
  5. 서비스로케이터와 DI에 대한 고찰

이런 주제에 대해서 전부 다루려 했다. 하지만 이중 2번과만 다루게 될것 같다.

왜냐하면 2번만 너무 빡셌기 때문이다. -> 미션 제출날 까지 씨름중

(사실 4번 창의적인 추가기능에 대한 내용이있지만 시간에 쫓겨서 학습을 마무리하지 못하여 당장 블로그 글은 작성하지 못할것같다. 언젠간 작성해야지 키워드는 진정한 자동 DI를 위한여정 Dalvik도 나오고 classLoader 등 첨보는것 투성이의 내용이다.)

 

일단 추후 나올 내용들에 대한 궁금한 점이 있을수도 있으니 변경전 코드와 변경후 코드를 링크로 먼저 남기려한다.

 

변경전 코드 : https://github.com/2chang5/android-di/tree/step1

변경후 코드: https://github.com/2chang5/android-di/tree/step2

 

테스트 편의성을 위한 처리

2번인 테스트 편의성을 위한 처리의 경우 사실 어렵다기 보다는 이미 짜놓은 코드를 공을 많이 들여서 클린하게 짜놓았는데 기능 및 로직들이 추가되면서 구조를 변경해야하는 상황이 일어났고 이때 추가되는 코드들이 못생기거나 거대화 되지않도록 기존코드와 융화시키는 과정이었다.

 

생각보다 이미 동작하는 코드 그것도 꽤나 맘에드는 코드 에다가 뭘 예쁘게 얹어낸다는게 참어려웠다.(뭔가 얹거나 하면 점점 간결하지않고 묵직하고 냄새나는 코드가 되었다.)

 

그래서 레벨 1하는 느낌으로 계속 함수분리, 클래스분리, 추상화의 연속이었던것 같다. (함수명 못생기면 참지를 못하겠다 분조장인가) 그래도 이를 거치면서 좀 리펙터링 과정과 클린한 코드를 유지하는 방법을 체화시키는것 같아서 기분이 좋긴하다.

 

각설하고 이번 미션 2단계를 진행하면서 변경한 구조를 설명하자면

기존의 동작하는 코드의경우 하나의 container를 object로 두고 모든 의존성들을 등록하는 형태였다.

package woowacourse.shopping.util.autoDI.dependencyContainer
​
import woowacourse.shopping.util.autoDI.ViewModelBundle
import woowacourse.shopping.util.autoDI.dependencyContainer.LifeCycleTypes.*
import woowacourse.shopping.util.autoDI.lifecycleTypeRegister.DisposableRegister
import woowacourse.shopping.util.autoDI.lifecycleTypeRegister.SingletonRegister
import woowacourse.shopping.util.autoDI.lifecycleTypeRegister.ViewModelRegister
import kotlin.reflect.KType
​
object DependencyContainer {
    const val NOT_EXIST_DEPENDENCY_ERROR =
        "search 함수로 검색한 dependency 가 존재하지 않습니다. qulifier 혹은 선언 모듈을 확인하세요"
​
    internal val singletons: Singletons = Singletons(mutableListOf())
    internal val disposables: Disposables = Disposables(mutableListOf())
//  액티비티 프래그먼트 추후 지원
//  private val activities: MutableList<LifeCycleType.Activity<*>> = mutableListOf()
//  private val fragments: MutableList<LifeCycleType.Fragment<*>> = mutableListOf()
​
    val totalLifeCycleTypes: List<LifeCycleTypes> = listOf(singletons, disposables)
​
    internal val viewModelBundles: ViewModelBundles = ViewModelBundles(mutableListOf())
​
    // 각각의 의존성 등록 및 순회 로직이 있다.
}

하나의 Object에서 등록되는 전체적인 의존성을 모두 관리하고 순회하도록 로직을 작성하니 발생하는 큰 문제점이 두가지 있었다.

 

첫째. 의존성을 등록하는 블록(클래스)를 분리할 수 없다.

-> 지금이야 샘플앱이고 그냥 테스트니까 각 등록하는 모듈의 크기가 커지는 고통을 체감하지 못하지만, 앱규모가 좀만 커져도 엄청나게 많은 의존성 등록이 발생할텐데 이를 분리하지 못한다? 나같으면 그딴 라이브러리 바로 안쓴다.

 

둘째. 테스트 할떄 의존성 갈아 끼울수 있어야하는데 Container 혹은 개별적인 의존성을 갈아 끼울 수 있는 방법이 없다.

-> 테스트 할때 의존성 주입을 테스트용 객체로 해주어서 테스트 하고 싶을수도 있고 의존성 뭉치 혹은 개별 의존성을 변경해주거나 동적으로 갈아끼워 기능을 변경시키고 싶을수도 있을텐데 현재의 구조로는 그것이 불가했다.

 

위에 설명한 두 문제점에 대한 개선으로 인하여 구조 자체를 대대적으로 변경하였는데

변경사항을 이미지로 살펴보며 설명하도록 하겠다.

(이미지 mermaid 로 그리면 좋긴한데 그거 문법 배울시간없다. 나같은 개발 호소인은 그냥 피그마로 그린다 ㅎㅎㅎ)

 

기존의 컨테이너

그냥 구조도 라고 할것도 없이 하나의 Object 형태의 container위에 각 의존성을 포장한 객체들의 일급 컬렉션이 있고 여기에 의존성을 등록하는 형태로 사용하였다.

 

이렇다보니 의존성을 묶음으로 갈아끼울 방법이 없었다.(사실 꾸역꾸역 변경로직을 이 형태에서 짠다면 짜겠지만 깔끔하게 의존성 묶음을 변경하는것이 아닌 container 전체를 다 조사하여 변경하려는 것들을 다 갈아끼워줘야하는 형태여서 맘에 안들었다.)

 

또한 각 의존성들을 하나의 class 단위로 묶고싶어도 불가능하고 각각 개별 의존성 단위로만 관리해야 했기 때문에 이 또한 불편한 점이였다.

 

만약 의존성을 하나의 단위로 묶어서 관리하고 이를 container에서 관리한다면 의존성 묶음 단위를 더 쉽게 제거,추가가 가능하여 테스트시 원하는 의존성 묶음만 변경하여 사용하는 등 좀더 편의성이 올라갈것이라고 생각하고 각각의 의존성이 개별적으로 움직여 로직들이 둥둥 떠다니는 것이 아닌 객체로 생성하여 관리하여 관리 측면에서도 이점을 가질 수 있다고 생각하였다.

 

그래서 중간 관리 단위인 module을 도입하였다(사실 다른 라이브러리에 다있는거긴 하지만 말이다.)

 

새로운 컨테이너

해결책으로 각각의 의존성을 묶는 단위인 module 를 도입하였는데 사실 기존 container를 Object에서 class 로 변경하고 그 클래스들을 관리하는 container를 하나 상위에 생성해주면 되는 일이었다.

 

하지만 말처럼 쉽지않은게 뭘 자꾸 추가하니까 (사실 추가한건 없지만 각각이 나눠지니까 유지하기위해 코드가 추가됨) 클래스가 겁나 비대해지고 그게 못마땅한 나는 레벨 1을 하듯 클래스 분리 메서드 분리만 겁나해대기 시작했다.

 

그래서 일단 기존의 container를 class 형태로 변경한 부분을 살펴보자

그냥 object 키워드를 class 로 변경하면 되는거아님?이라고 최초에 했지만 현재 라이브러리에서 DSL을 사용하고 있었기 때문에 module 또한 DSL 형태로 생성할 수 있어야했다.

 

그러다보니 기존에는 DSL 을 사용할수 있게 해주는 등록 로직들이 외부에 기능을 노출하는 AutoDI(object)에서 AutoDIModule(class)로 옮겨와야했고 단순 옮겼더니 클래스가 너무 거대화되어서 화가났다.

 

그래서 클래스 분리를 고려하였고 어짜피 다른 성격을 가지는 LifeCycleType과 AndroidComponent(지금은 ViewModel 밖에 없지만)의 저장 및 관리 로직을 각각의 클래스로 분리하고 이를 composition 형태로 구성하였다.

 

그렇게 일단 모듈을 구성할 수있는 코드를 만들었으니 다음 관문인 모듈들을 관리하는 컨테이너를 구성해야했다.

 

그래서 이렇게 AutoDIModule의 일급 컬렉션을 만들어 각 모듈을 오버라이딩 할수있는 로직과(qualifier를 통해 구분)

각 의존성을 개별적으로 오버라이딩할 수 있는 로직들도 추가하여 테스트 혹은 의존성주입 로직에 대한 변경에 대비하였다.

 

사실상 개념자체는 그냥 추상화와 하나의 계층을 더 넣는것 뿐이었는데 이미 구현된 로직속에서 DSL등등을 우겨넣으려니 클래스 분리가 제일 빡셌던것 같다.

근데 지금와서 코드를 갈무리하는걸 보니 나어쩌면 우테코 레벨1,2 잘 소화했을지도 ㅋㅋㄹㅃㅃ

 

결국 지금은 테스트할때도 원하는 부분만 갈아끼워주는것이 가능해서 테스트에 대해서도 별걱정이 없다. 물론 내가 Hilt,Koin의 테스트관련기능을 안써봐서 바보같은 구현을 했을수도 있는데 일단그렇다. -> 창의력을 위해 기존라이브러리는 최대한 참고를 자제하고있다.

 

사실 이 이후에 메인스토리가 더 있는데 너무 길어져서 글을 쪼갤겸 너무 방대해서 학습을 마무리 하지 못한 겸사겸사 여기서 이번글은 마치려 한다.