[Android] Clean Architecture 모듈화 해보기 - 2

Android/Android · 2023. 3. 22. 19:39
반응형

 실제 예제로 연습해 보겠습니다. 저는 아래 프로젝트를 활용했고, 살짝 코드를 변경했습니다. 

 

https://github.com/philipplackner/DictionaryYT/tree/app

 

GitHub - philipplackner/DictionaryYT

Contribute to philipplackner/DictionaryYT development by creating an account on GitHub.

github.com

 

밑은 제가 변경한 코드입니다. (Mapper object 추가 및 coomon, core 패키지 분리)

 

https://github.com/HanBI24/DictCacheApp_Clean_Architecture/tree/non_module

 

GitHub - HanBI24/DictCacheApp_Clean_Architecture

Contribute to HanBI24/DictCacheApp_Clean_Architecture development by creating an account on GitHub.

github.com

 

 해당 프로젝트에 간단히 소개드리자면, 사용자가 검색어를 입력하면 Dictionary API를 HTTP 통신해서 가져오고, 한 번 검색된 검색어는 로컬 DB에 저장(Room)하여 오프라인 상태에서도 볼 수 있는 Cache 기능을 수행하는 프로젝트입니다.

 

모듈화를 진행하기 전, 유튜브 한 번 보시고 오는 것을 추천드립니다 ! (해당 프로젝트 구성 유튜브)

https://youtu.be/Mr8YKDh3li4

 

 

모듈화 하기 전 프로젝트 패키지 구조는 다음과 같습니다.

 

모듈화 진행 전

 

 보통은 제일 상위에 data, domain, presentation이 위치하겠지만, 이는 앱의 규모가 커질수록 프로젝트를 더욱 복잡하게 만듭니다. 이를 해결하기 위해 각 화면 별로 feature_(screen_name)로 나누어서 진행했습니다. 또한, commoncore 패키지가 보일 것입니다. 이것이 하는 역할은 다음과 같습니다.

 

common: 해당 프로젝트에서 자주 쓰이는 간단한 기능들을 정의해 놓은 공간입니다.

(BaseURL을 저장한 Constants.kt 파일과 SnackBar, Toast 등을 호출할 수 있는 UIEvent.kt 파일이 있습니다.)

 

core(library): core 또는 library라고 합니다. 여기엔 핵심 비즈니스 로직이 추가될 수 있기 때문에 domain layer와 같이 순수 Java, Kotlin 코드 또는 Coroutine만 작성할 수 있습니다.

(네트워크 통신 상태에 따른 처리를 하기 위해 Resource.kt 파일이 위치해 있습니다.)

 

이것을 최종적으로 모듈화 하면 다음과 같이 구성됩니다.

 

모듈화 진행 후

 

거의 똑같다고 볼 수 있죠? 바로 진행하겠습니다.

 

먼저 모듈을 생성해 보겠습니다. 모듈을 생성하기 위해 Android -> Project로 변경합니다.

 

 

그리고 최상위 모듈인 app 모듈을 feature_dictionary로 이름을 변경하겠습니다.

 

[ app 선택 후 Shift + F6   =>   Rename module 선택  =>  feature_dictionary로 변경 ]

 

 

잠시 기다리면 이름이 변경됩니다. 이후 나머지 모듈들도 추가해 보겠습니다.

 

[ 해당 프로젝트 우 클릭 => New => Module 선택 => Android Library 선택 후 Module name에 입력 ]

 

그리고 common, core 모듈을 추가해 줍니다. 추가한 결과는 다음과 같습니다.

 

 

이번엔 feature_dictionary 모듈에 data, domain, presentation 모듈을 추가하겠습니다.

 

[ feature_dictionary 우 클릭 => New => Module 선택 => Android Library 선택 후 Module name에 입력 ]

 

!! 주의 !!

mylibrary 이 부분만 변경해야 합니다. 상위 모듈 표시를 당연히 해줘야 합니다!!

 

mylibrary만 수정하기.

 

모두 추가하셨으면, 다음과 같이 모듈 구조가 완성됩니다. 이제 다시 Project -> Android로 변경해 봅시다.

 

모듈 생성 완료

 

 위 사진을 보시면 굉장히 많은 build.gradle 파일이 보이실 겁니다. 이것은 Project 수준의 파일과 각 모듈에 대한 파일입니다. 이 상태에서 실행해도 문제없이 실행됩니다. 아직 어떠한 의존성도 추가하지 않았고, app을 feature_dictionary로 이름만 바꿨기 때문입니다.

 

 이제 각 모듈에 대한 의존성과 라이브러리를 추가해 보도록 하겠습니다.

 

- common 모듈

해당 모듈은 어떠한 의존성과 라이브러리를 갖지 않습니다.

 

- core 모듈

해당 모듈은 어떠한 의존성과 라이브러리를 갖지 않습니다.

(비즈니스 로직을 담당하기 때문에 순수 Java와 Kotlin, Coroutine 코드만 작성할 수 있습니다.)

 

- feature_dictionary 모듈

 

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

dependencies {
    implementation project(':common')
    implementation project(':feature_dictionary:presentation')
    implementation project(':feature_dictionary:domain')
    implementation project(':feature_dictionary:data')
    
    ...
    ...
    ...
    
        // Compose dependencies
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"

    // Coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'

    // Coroutine Lifecycle Scopes
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"

    //Dagger - Hilt
    implementation "com.google.dagger:hilt-android:2.38.1"
    kapt "com.google.dagger:hilt-android-compiler:2.37"
    kapt "androidx.hilt:hilt-compiler:1.0.0"
    implementation 'androidx.hilt:hilt-navigation-compose:1.0.0-alpha03'

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
    implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"

    // Room
    implementation "androidx.room:room-runtime:2.5.0"
    kapt "androidx.room:room-compiler:2.5.0"

    // Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:2.3.0"
}

 

 feature_dictionary 모듈은 common, presentation, domain 모듈을 의존하기 때문에 모듈을 추가해야 합니다.

모듈을 추가하는 코드는 implementation project(':module_name')이며, 상위 프로젝트의 모듈을 추가하려면 콜론(:)을 기준으로 작성하시면 됩니다.

 

 또한, 해당 프로젝트는 Jetpack Compose, Room, Retrofit, Coroutine, Hilt를 활용한 프로젝트이기 때문에 하단의 라이브러리를 추가합니다.

 

- presentation 모듈

 

plugins {
    id 'com.android.library'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

dependencies {
    implementation project(':core')
    implementation project(':common')
    implementation project(':feature_dictionary:domain')
    implementation project(':feature_dictionary:data')
    
    ...
    ...
    ...
    
        // Compose dependencies
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"

    // Coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'

    // Coroutine Lifecycle Scopes
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"

    //Dagger - Hilt
    // hilt 최신 버전 사용하면 conflict 발생
    implementation "com.google.dagger:hilt-android:2.38.1"
    kapt "com.google.dagger:hilt-android-compiler:2.37"
    kapt "androidx.hilt:hilt-compiler:1.0.0"
    implementation 'androidx.hilt:hilt-navigation-compose:1.0.0-alpha03'

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
    implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"

    // Room
    implementation "androidx.room:room-runtime:2.5.0"
    kapt "androidx.room:room-compiler:2.5.0"

    // Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:2.5.0"
}

 

 presentation 모듈은 core, domain, domain, data 모듈에 의존합니다. 해당 모듈도 필요한 라이브러리를 추가해 줍니다.

 

 

- domain 모듈

 

dependencies {
    implementation project(':core')
    
    ...
}

 

 domain 모듈은 어느 모듈에 의존하지 않지만, repository interface를 구현하기 위해 core 모듈 의존성을 추가합니다.

Clean Architecture에서 위배되는 행위인 것 같아 다른 자료를 찾아보았더니 이렇게 구현한 사람도 있긴 했습니다. 이 부분에 대해선 좀 더 공부가 필요해 보입니다. 

 

물론 core 모듈도 다른 android 라이브러리나 이런 것이 포함된 코드가 아니기 때문에 큰 문제는 없다고 생각합니다...

 

 

- data 모듈

 

plugins {
    id 'com.android.library'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}

dependencies {
    implementation project(':core')
    implementation project(':common')
    implementation project(':feature_dictionary:domain')
    
    ...
    ...
    ...
    
    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
    implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"

    // Room
    implementation "androidx.room:room-runtime:2.5.0"
    kapt "androidx.room:room-compiler:2.5.0"

    // Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:2.5.0"
}

 

 마찬가지로 data 모듈도 필요한 의존성과 라이브러리를 추가해 줍니다. 여기까지 마치셨으면 "Sync Now"를 눌러 동기화를 진행합니다. 그리고 이상이 없는지 실행해 보시고, 앱이 정상적으로 작동하면 여기까지 잘 진행하신 겁니다!!

 

(만약 Room 관련 에러가 발생하면 Room 버전을 최신으로 업데이트해보세요.)

 

 

 이제 해당 패키지에 있는 파일들을 전부 각 모듈에 맞게 이동시켜 주면 됩니다. 패키지 내의 하위 패키지 및 파일들을 모두 옮겨주시면 됩니다. 위치는 당연히 java(또는 kotlin) / package_name / module_name에 위치시켜 주면 되겠죠?

 

 (Refactor가 뜨면 Refactor 누르고, 무슨 경고창이 뜨면 Continue 누르면 됩니다.)

 

 이동시킨 후 남아있는 패키지는 헷갈리지 않게 삭제하면 됩니다.

 

각 모듈에 맞게 이동

 

 

 여기서 주의해야 할 부분은 presentation의 view 패키지 내부에 있는 파일들은 feature_dictionary 모듈에 넣어주셔야 합니다. presentation 모듈에 넣으시면 안 됩니다 !!

 

 이유는 해당 프로젝트에서 따로 navigation 처리를 하지 않았기 때문입니다. 만약 presentation 모듈로 옮기시면 AndroidManifest.xml에서 오류가 발생할 것입니다. 잘 따라오셨다면 다음과 같이 presentation 모듈 구조가 완성되어 있을 것입니다.

 

 

 

이제 한 번 실행을 해보면 정상적으로 모든 기능이 실행될 것입니다.

 

 

감사합니다.

반응형