쿠팡 안드로이드 아키텍처 — Part 2
앱의 모듈화
By Seongchul Park (박성철)
시리즈
이번 포스트는 “쿠팡 안드로이드 아키텍처” 시리즈의 두번째 이야기입니다.
- Part 1 — 관심사의 분리
- Part 2 — 앱의 모듈화
- Part 3 — 리패키징을 통한 의존성 제거
본 포스트는 영문으로도 제공됩니다.
지난 포스트에서는 MVP 패턴을 코드에 적용해 관심사의 분리(Separation of Concerns)를 진행했던 방법을 알아보았습니다. 이를 통해 저희는 유닛테스트의 코드 커버리지를 향상시키고 더욱 안정적으로 릴리즈할 수 있게 되었지만, 여전히 하나의 모듈 위에서 많은 작업을 수행하고 있었습니다.
또한 쿠팡은 이커머스 앱으로서 상당한 진전을 이루었고 새로운 분야로 사업을 확장하고 있었습니다. 새롭게 시작하는 프로젝트들은 쿠팡 앱에서 이미 검증된 코드를 재활용하면서 더 빠른 속도로 안정적으로 개발되긴 했지만, 그에 따라 하나의 모듈에 점점 더 많은 코드가 추가되면서 빌드 시간은 계속 증가했습니다. 그 뿐만 아니라 인스턴트 앱(Instant Apps), 앱 번들(App Bundles)과 같이 구글의 새로운 배포 기능을 적용해야 할 필요성도 늘어나고 있었습니다. 이런 상황을 해결하기 위해 모듈화 작업은 반드시 진행되어야 했습니다. 이 포스트를 통해 쿠팡 앱의 모듈화가 어떻게 위의 문제들을 해결할 수 있었는지를 간략히 공유드리려고 합니다.
주요 목표
저희는 아래과 같은 3개의 주요 목표를 기반으로 모놀리식(Monolithic) 코드베이스를 역할 기반의 모듈로 분리했습니다.
- 코드 재활용 늘리기
- 앱 무결성 및 유지보수성 향상
- 코드 의존성 줄이기
앱 모듈(App module)
쿠팡 앱에 반드시 존재하는 메인 모듈로 앱 플러그인을 통해 build.gradle
에 선언됩니다.
apply plugin: 'com.android.application'
앱 모듈은 모든 모듈을 바인딩하며 앱의 필요한 설정 값을 정의하는 역할을 담당합니다.
피처 모듈(Feature modules)
피처 모듈은 각 비즈니스 도메인 모델에 특화된 모듈로 홈, 검색과 같은 비즈니스 도메인 간의 의존성을 최소화하여 분리한 모듈입니다. 라이브러리 플러그인을 통해 build.gradle
에 선언됩니다.
apply plugin: 'com.android.library'
검색이라는 비즈니스 도메인 모델은 독립적으로 기능할 수도 있지만, 장바구니와 같이 모든 영역에 영향을 주는 비즈니스 도메인 모델도 있습니다. 따라서 피처 모듈은 필요에 따라 독립적 또는 공용으로 사용될 수 있도록 설계되었습니다.
코어 모듈(Core modules)
코어 모듈은 특정 모바일 애플리케이션에 의존성을 가지지 않는 독립적인 모듈로서 어떠한 앱에서도 사용될 수 있습니다. 피처 모듈과 동일하게 라이브러리 플러그인을 통해 build.gradle
에 선언됩니다.
코어 모듈이 라이브러리처럼 기능할 수 있도록 설계했으며 설정 주입(configuration injection)을 통해 각 비즈니스 도메인 내 클래스에서도 사용 가능합니다.
모듈화 과정
위 3개의 모듈 유형을 바탕으로 모듈화 작업을 시작했습니다. 하지만 30만 라인이 넘는 크고 복잡한 코드베이스를 모듈화하는 작업은 결코 쉽지 않았습니다. 그래서 모듈 종류에 맞게 단계적으로 모듈화를 진행하기로 하였습니다
1. 코어 모듈의 분리
먼저 가장 쉬운 작업부터 시작하기로 했습니다. 즉, 오랜 기간 동안 안정적으로 사용된 기능 중 직관적이고 독립적으로 분리가 가능한 코어 모듈부터 분리하기 시작했습니다. 그리고 이 과정에서 발생하는 비즈니스 모델과 의존성을 제거하여 순수하게 기능에만 충실한 모듈로 분리하기 시작했습니다.
package:
com.yourcompany.core1
com.yourcompany.core2settings.gradle:
include ':core1'
include ':core2'build.gradle:
compile project('core1') // or implementation
compile project('core2')
또한 코어 모듈을 생성할 시 아래 사항을 고려했습니다.
- 모듈간의 의존성 충돌 (Gradle Extra) 최소화
- 잘못된 관계 설정으로 인한 오버라이드, 머징 이슈 해결
- 작동 및 성능을 체크하는 Android Lint Rule을 기존 모듈과 동일하게 설정
- ProGuard Rule 중복설정 확인
코어 모듈은 때로 외부 라이브러리 사용을 관리하는 책임을 가집니다. 라이브러리 업그레이드 또는 변경 중에 오용을 방지하고 애플리케이션 모듈에 미치는 영향을 최소화하기 위해 다른 클래스에 의한 직접적인 구현을 제한했습니다.
수 차례의 코드 리뷰와 QA 테스트 후 저희는 코드베이스를 1개의 애플리케이션 모듈과 13개의 모듈을 분리하게 되었습니다.
2. 피처 모듈의 분리
코어 모듈의 성공적인 분리 뒤, 피처 모듈의 독립적인 분할을 시작했으며 공통 피처 모듈에 의존성을 가진 리소스를 공유형 피처 모듈로 옮겼습니다.
package:
com.yourcompany.projectname-common
com.yourcompany.projectname-feature1
com.yourcompany.projectname-feature2settings.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% 이상 수준으로 관리할 수 있게 되었습니다.
이러한 모듈화 작업을 통해 코드의 안정성과 유지보수성이 향상된 것은 물론이고 쿠팡 앱이 아닌 다른 모바일 프로젝트에서 분리된 코어 모듈도 재활용할 수 있게 됨으로써 생산성을 비약적으로 높일 수 있게 되었습니다. 그럼 이어지는 포스트에서는 모듈화를 위해 어떻게 리패키징(Repackaging)을 진행했고, 의존성을 개선해 나가고 있는지 소개하도록 하겠습니다.
열린 사고로 질문하고 도전하며 맡은 역할에 책임을 다하는 인재는 언제나 환영입니다. 이 포스트가 재미있었다면 Coupang Careers에서 현재 채용 중인 포지션을 확인해 보세요!