안드로이드 애플리케이션 모듈화 (Modularizing Android Applications)
지난 포스트에서는 MVP 또는 MVVM 같은 pattern을 코드에 적용함으로서 어떻게 관심사의 분리(Separation of concerns)를 진행했는지를 알아보았다. 우리는 관심사의 분리를 통해 유닛테스트(Unit test coverage) 범위를 향상시킬 수 있었고, 이를 통해 좀 더 안정적인 릴리즈를 진행할 수 있었다. 하지만, 우리는 여전히 하나의 모듈에서 너무나 많은 작업을 진행하고 있었다.
2011년부터 많은 개발자들이 쿠팡 모바일 애플리케이션(이하 쿠팡앱)에 참여를 하게 되었고, 이를 하나의 모듈에서 개발하였다. 하지만, 회사가 성장해가면서 비즈니스 영역 또한 지속적으로 확장되었고 쿠팡앱 이외의 다양한 모바일 애플리케이션 프로젝트들이 늘어나게 되었다.
새롭게 시작하는 프로젝트들은 이미 쿠팡앱에서 검증되어진 코드를 재활용하여 빠르고 안정적으로 모바일 애플리케이션을 개발하기를 원하였고 그에 따라 하나의 모듈에 점점 많은 코드가 추가되면서 지속적으로 빌드 시간이 증가하는 문제점에 직면하게 되었다. 그 뿐만 아니라 인스턴트 앱(Instant apps), 앱 번들(App bundles)과 같이 새로운 Distribution features을 적용해야 할 필요성이 늘어나고 있었기 때문에 모듈화 작업이 반드시 진행되어야 하는 상황이었다.
결국 우리는 모듈화를 진행하기로 결정하였고 아래와 같은 고민을 토대로 모듈화를 진행하게 되었다.
어떻게 개발자들이 기존에 이미 검증된 코드들을 재활용할 수 있을까?
어떻게 개발자들의 업무 효율을 높이고 앱의 신뢰성과 유지보수성을 개선할 것인가?
어떻게 개발자 간의 코드 영향도를 최소화할 수 있을까?
이번 포스트에서는 갓(God) 클래스/오브젝트와 마찬가지로 하나의 모듈(갓 모듈)을 모듈화 한 원칙과 과정에 대해 소개하기로 하겠다.
모듈의 종류
우리는 아래와 같이 역할별로 3가지의 모듈의 종류를 정의하고 이를 기반으로 모듈화를 진행하기로 하였다.
애플리케이션 모듈 (Application Module)
피처 모듈 (Feature Module)
코어 모듈 (Core Module)
각각의 모듈은 다음과 같은 특징을 가진다.
애플리케이션 모듈(Application Module)
쿠팡 애플리케이션의 메인 모듈로서 애플리케이션에서 반드시 존재하며 application plugin을 통해서 build.gradle 선언되어진다.
apply plugin: ‘com.android.application
하나의 모듈로 존재하는 프로젝트라면 이 애플리케이션 모듈만 존재할 것이다. 우리는 모듈화로 인해 분리되어진 모듈들을 다시 조립하여 하나의 애플리케이션을 생성할 수 있고, 애플리케이션 전체의 필요한 설정 값을 정의하는 역할을 담당하도록 하였다.
피처 모듈(Feature Module)
각각의 비즈니스 도메인 모델에 특화된 모듈로서 홈, 검색 등과 같이 비즈니스 도메인 간의 의존성을 최소화하여 분리한 모듈이며 library plugin을 통해서 build.gradle 선언되어진다.
apply plugin: ‘com.android.library’
검색이라는 비즈니스 도메인 모델은 독립적으로 존재할 수도 있지만, 때로는 장바구니와 같이 모든 영역에 영향을 주는 비즈니스 도메인 모델도 존재한다. 같은 라이브러리 모듈이지만, 사용되는 방식에 따라 공유형과 독립형 피처 모듈을 구분하여 역할을 담당하도록 하였다.
코어 모듈(Core Module)
특정 모바일 애플리케이션에 의존성을 가지지 않는 독립적인 모듈로서 어떠한 애플리케이션에서도 사용할 수 있는 모듈이며 피처 모듈과 동일하게 library plugin을 통해서 build.gradle 선언되어진다.
코어 모듈은 네트워크 라이브러리와 같이 명확히 독립적인 기능만 제공하고, 이를 비즈니스 도메인 모델과 무관하게 테스트, 배포할 수 있도록 라이브러리화하기로 하였다. 그리고 설정을 주입하여, 각 비즈니스 도메인에서 사용할 수 있도록 하였고, 언제나 대체 가능하도록 인터페이스화 하였다.
모듈화 과정
우리는 3개의 모듈을 아래와 같이 구성하고자 하였다. 모듈화에 대해 방향은 설정되었으나 30만 라인이 넘는 크고 복잡한 앱을 모듈화하는 작업은 결코 쉽지 않았다. 그래서 우리는 모듈 종류에 맞게 단계적으로 모듈화를 진행하기로 하였다.
1. 코어 모듈의 분리
우리는 오랜 기간 동안 안정적으로 사용되어진 기능들 중에서 직관적이고 독립적으로 분리가 가능한 코어 모듈부터 분리하기 시작했다. 그리고 이 과정에서 발생하는 비즈니스의 모델과 의존성을 제거하여, 순수 해당 기능에만 충실한 모듈로 분리하기 시작했다.
1) 코어 모듈의 설정
package
com.your.company.core1
com.your.company.core2
settings.gradle
Include ‘:core1’
Include ‘:core2’
build.gradle
compile project(‘core1’) // or implementation
compile project(‘core2’)
2) 코어 모듈을 생성시 고려해야할 사항
모듈간의 dependency 충돌 최소화(gradle extra)
모듈간의 override될 설정 값 등 Merged 되어질 AndroidManifest
Android Lint rule 기존 모듈과 동일하게 설정
Proguard rule 중복설정 확인
코어 모듈들은 때로는 third-party library를 사용하는 책임을 가지고 있고, 인터페이스를 통해 실제 구현체를 직접적으로 사용할 수 없도록 하였다. 이는 새로운 라이브러리로 교체시 반드시 필요한 인터페이스만 허용하여, 오용을 막고 기타 업그레이드 및 라이브러리 교체에서도 애플리케이션 모듈의 영향을 최소화 하기 위함이다.
많은 코드들이 추출되어 모듈로 분리되었고, 코드품질을 유지하기 위한 방법으로 유닛테스트 커버리지를 80%이상으로 유지하도록 정책을 수립하고 적용하였다. 그리고 수차례의 코드 리뷰와 QA를 통해 리스크를 최소화하였다. 이러한 노력을 통해 우리는 1개의 애플리케이션 모듈과 13개의 모듈을 분리하는데 성공했다.
2. 피처 모듈의 분리
코어 모듈의 성공적인 분리 이후 우리는 전략적으로 어떻게 안전하게 피처 모듈을 분리할 수 있을지 고민했고, 피처 모듈에도 크게 2가지의 유형이 필요하다는 결론에 내렸다. 전체 비즈니스 프로젝트의 공통적으로 사용되어 각 비즈니스에 영향을 미치는 공유형과 다른 비즈니스와 영향이 없는 독립형 모듈이 그것이다.
공유형 피처 모듈은 더 세분화하여 할 수 있겠지만, 우리는 하나의 큰 덩어리의 모듈을 만들어 이를 관리하기로 하였다. 그리고 독립형 피처 모듈을 하나씩 분리해 나가면서 리소스 등과 같이 의존성의 영향이 있는 요소들은 우선 공유형 피처 모듈로 옮기면서 모듈화를 진행했다.
1) 피처 모듈의 설정
package
com.your.company.projectname-common
com.your.company.projectname-feature1
com.your.company.projectname-feature2
settings.gradle
Include ‘:projectname-common’
Include ‘:projectname-feature1
Include ‘:projectname-feature2
build.gradle
compile project(‘:projectname-common’) // or implementation
compile project(‘:projectname-feature1’)
compile project(‘:projectname-feature2’)
그 결과 아직 완벽하지는 않지만 우리는 공유형 피처 모듈을 분리하게 되었고, 지속적으로 독립형 피처 모듈 분리를 진행하고 있다.
결론
수 개월 동안 여러 팀과의 협업을 통해 우리는 코어 모듈 13개를 분리하였고, 각 모듈들의 유닛테스트 커버리지를 80%로 관리하고 있다. 모듈화를 통해 코드의 안정성과 유지보수성이 향상된 것은 물론이고 쿠팡앱이 아닌 다른 모바일 프로젝트에서는 분리된 코어 모듈을 재활용함으로서 생산성을 비약적으로 높힐 수 있게 되었다.
3가지 모듈 분류 중 애플리케이션 모듈에는 의존성을 제거하고 모듈화를 해야 할 부분이 아직 많이 남아 있다. 이어지는 포스트에서는 모듈화를 위해 어떻게 리패키징(repackaging)을 진행했고, 의존성을 개선해 나가고 있는지 소개하도록 하겠다.
박성철, Sr. Manager, Software Engineering