진행, 배포 후 운영 중인 프로젝트 위니를 싱글 모듈에서 멀티 모듈 + 컴포즈 기반으로 마이그레이션하기로 마음먹었다. 멀티모듈, 컴포즈 따로따로 진행할까도 생각했지만 데이터바인딩을 걷어내고, 컴포즈 의존성을 추가하면서 한번에 변경하는게 더 좋을 것 같다는 생각을 하고 실행에 옮기는 중이다. 다른 프로젝트에서도 멀티모듈 이전을 진행중이라, 이전에 한번 멀티모듈에 대해 공부했었는데 이번 기회로 아예 정리해 블로그에 업데이트해보자.
모듈화
먼저 모듈화에 대해 알아보자. 모듈이란 프로그램을 구성하는 시스템을 기능 단위로 독립적인 부분으로 분리한 것을 의미한다. 따라서 모듈화란 모듈로 쪼갬으로서 기능적인 분리를 통해서 유지보수와 코드 재사용성을 높여 소프트웨어를 설계하는 기법을 말한다.
모듈화의 장점으로는 다음과 같은 것들이 있다.
- 재사용성, 확장성
- 결합도 감소, 응집도 증가
- 캡슐화
- 테스트 용이성 증가
- 각 모듈 별로 명확한 책임 부여
- 빌드 시간 감소
- 엄격한 공개 상태 제어
안드로이드 공식 문서에서 제안하는 멀티 모듈
안드로이드 프로젝트에서는 여러 Gradle 모듈이 있는 프로젝트를 멀티 모듈 프로젝트라고 말하며, 안드로이드 앱에서도 위와 같은 이점을 얻기 위해 모듈화를 진행한다.
그렇다면 모듈화에도 방법과 기준이 있을텐데 어떤걸 택해야할까 ? 기준을 생각해보면 우리는 당연히 레이어와 기능이라는 두 가지 기준을 떠올릴 수 있다.
레이어를 기준으로 한 모듈화
먼저 레이어에 대해 간단히 짚고 넘어가자.
UI 레이어
- UI (View, Composable)와 State holder (ViewModel) 등을 포함한다
도메인 레이어
- use case를 포함한 비즈니스 유형
데이터 레이어
- repository, data source를 포함한 데이터 유형
위를 기준으로 한 모듈화는 기본적으로 응집도가 높아 좋다. 레이어 분리를 통해 각 레이어가 각자의 책임을 다하기 때문이다. 하지만 결합도는 어떨까? 레이어만 기준으로 진행한 모듈화의 경우, 도메인 모듈의 내용이 바뀌면 UI 모듈 또한 변경되어야 하고, 데이터 모듈의 내용이 바뀌면 전체가 다시 빌드되어야할 경우가 빈번할 것이다. 따라서 레이어를 기준으로한 모듈화는 프로젝트의 규모가 커지면 그 모듈의 크기가 또한 커져 결합도가 증가해 아쉽다.
기능을 기준으로 한 모듈화
기능을 기준으로 모듈화를 진행하면, UI, 도메인, 레이어가 분명 재사용 되는 부분이 있을텐데 이를 위해서는 모듈을 계속해서 참조해야하므로 결합도가 높아져 역시나 아쉽다.
따라서 안드로이드 멀티모듈 앱은 각각의 장점만을 살려 적절히 섞어 계층, 기능 별로 모듈화 한다.
위와 같이 UI, 도메인, 데이터로 먼저 레이어 별로 분리한 후, 안에서 기능 별로 분리해 모듈화를 진행하면 적당한 세분화를 하면서 결합도는 떨어지고, 응집도는 좋은 구조를 가질 수 있다.
그렇다면 안드로이드 공식 문서에서 제공하는 일반적인 모듈 종류에 대해서 더 자세히 알아보자.
모듈 타입
data 모듈
일반적으로 repository, data source, model 클래스가 포함되어 있다. 역할은 다음과 같다.
- 특정 도메인의 모든 데이터 및 비즈니스 로직 캡슐화: 각 데이터 모듈은 특정 도메인을 나타내는 데이터를 처리해야 한다
- 저장소를 외부 API로 노출: 데이터 모듈의 공개 API repository로서 외부에 노출
- 외부로부터 모든 구현 세부정보 및 데이터 소스 숨기기: data source는 같은 모듈의 저장소에서만 액세스 가능해야 한다. 외부에는 공개되지 않아야 하므로 Kotlin의 private 또는 internal 공개 상태 키워드를 사용하여 data source를 숨긴다
feature 모듈
일반적으로 화면 또는 밀접하게 관련된 일련의 화면에 해당하는 독립적인 기능을 의미하고 data 모듈에 종속된다.
app 모듈
앱 모듈은 애플리케이션의 진입점으로, 앱 모듈은 feature 모듈에 종속되며 일반적으로 루트 탐색을 제공한다. 앱이 자동차, 웨어러블 기기, TV 등 여러 기기 유형을 타겟팅 하는 경우 기기별로 앱 모듈을 정의해 플랫폼 별 종속 항목을 구분할 수 있다. 또 build variant를 사용해 단일 앱 모듈을 다양하게 컴파일 가능하다.
core 모듈
core 모듈(핵심 모듈이라고도 함)에는 다른 모듈에서 자주 사용하는 코드가 포함된다. core 모듈은 중복성을 줄이는 역할을 하며, 앱 아키텍처의 특정 레이어를 나타내지는 않는다. 다음과 같은 모듈들이 보통 core 모듈에 해당한다.
- 디자인 시스템
- 애널리틱스
- 네트워크
- 유틸
test 모듈
테스트용으로만 사용되는 Android 모듈이다.. 테스트 모듈에는 테스트 실행에만 필요하고 애플리케이션 런타임에는 필요하지 않은 테스트 코드, 테스트 리소스, 테스트 종속 항목이 포함된다. 테스트 모듈은 기본 애플리케이션과 테스트용 코드가 분리되도록 생성되므로 모듈 코드를 더 쉽게 관리하고 유지할 수 있다.
모듈 간 통신
모듈은 완전히 분리되는 경우는 거의 없으며, 다른 모듈에 의존하여 서로 통신하는 경우가 많다. 모듈이 함께 작동하고 정보를 자주 교환할 때도 결합도를 낮게 유지하는 것이 중요하다. 두 모듈 간의 직접적인 통신은 아키텍처 제약 조건과 마찬가지로 바람직하지 않다. 두 모듈이 직접 통신하면 순환 참조 같은 문제가 발생할 수 있다.
이런 문제를 극복하기 위해 두 개의 모듈을 중재하는 모듈을 둘 수 있다. 중재 모듈은 두 모듈의 메시지를 수신 대기하고 필요에 따라 메시지를 전달할 수 있다. 아래의 사진에서 두개의 기능 모듈은 동일한 데이터 모듈에 종속된다. 이렇게 하면 중재자 모듈이 전달해야 하는 데이터 양을 최소화하고 모듈 간의 결합력을 낮게 유지할 수 있다. 모듈은 객체를 전달하는 대신 기본 ID를 교환하고 공유 데이터 모듈에서 리소스를 로드해야 한다.
종속 항목 역전
종속 항목 역전이란 추상화가 구체적인 구현으로부터 분리되도록 코드를 구성하는 것을 말한다. 이때 각각의 의미는 다음과 같다.
- 추상화: 애플리케이션의 구성요소 또는 모듈이 서로 상호작용하는 방식을 정의하는 것으로, 시스템의 API를 정의하고 인터페이스와 모델을 포함한다
- 구체적인 구현: 추상화 모듈에 종속되며 추상화의 동작을 구현체 모듈이다
작동하려면 데이터베이스가 필요한 기능 모듈이 있다고 가정해보자. 기능 모듈을 추상화 모듈에 종속시키고, 구현체 모듈은 추상화 모듈에 정의된 API 의 실제 구현을 하면, 종속 항목 역전이 된다. 이를 통해 기능 모듈은 데이터베이스의 기본 구현 세부정보를 알 필요 없이 어떠한 데이터베이스도 사용할 수 있다. 정리해보면 다음과 같다.
1. 추상화 모듈 만들기: 이 모듈은 기능의 동작을 정의하는 API(인터페이스 및 모델)를 포함해야 한다.
2. 구현 모듈 만들기: 구현 모듈은 API 모듈에 종속되고 추상화의 동작을 구현해야 한다. (구현체)
3. 상위 모듈을 추상화 모듈에 종속: 모듈이 특정 구현에 직접 종속되는 대신 추상화 모듈에 종속되도록 한다. 상위 모듈은 구현 세부정보를 알 필요가 없으며 계약(API)만 있으면 된다.
4. 구현 모듈 제공: 종속 항목의 실제 구현을 제공해야 한다. 보통 앱 모듈에 구현한다.
이렇게 안드로이드 공식문서에 제안하는 멀티모듈 프로젝트의 구조와 그 과정에서 결합도는 낮추고, 응집력은 높이며 추상화가 구체적인 구현을 알지 못하도록 분리하는 종속 항목 역전 방법까지 알아보았다.
이를 바탕으로 다음 포스트에서는 종속 항목 부여를 위한 버전 카탈로그와 build logic에 대해 알아보자
참고자료
일반적인 모듈화 패턴 | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 일반적인 모듈화 패턴 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 모든 프로젝트에 맞는 하나의
developer.android.com