[Android, Multi Module] 멀티모듈 구성

 

일반적인 모듈화 패턴

멀티 모듈로 프로젝트를 구성할 때, 모든 프로젝트에 맞는 하나의 모듈화 전략은 없다.

Gradle의 유연한 특성으로 인해 프로젝트를 구성하는 방법에는 제약이 없으며,

다중 모듈로 Android 앱을 개발할 때 일반적인 규칙과 공통 패터만 지킨다면 자신만의 모듈화 전략을 세울 수 있다.

 

규칙1. 높은 응집력 및 낮은 결합력 원칙

가장 중요한 방법은 결합력 및 응집력 속성을 사용하는 것이다.

결합력은 모듈이 서로 종속된 정도를 측정하고, 응집력은 단일 모듈의 요소가 기능적으로 관련된 방식을 측정한다.

일반적으로 결합력은 낮추고 응집력은 높여야 한다.

 

[낮은 결합력]

- 모듈이 최대한 서로 독립적이어야 한다.

- 한 모듈의 변경사항이 다른 모듈에 미치는 영향이 없거나 최소화 되어야 한다.

- 모듈은 다른 모듈의 내부 작동을 알 수 없어야 한다.

 

[높은 응집력]

- 모듈이 시스템 역할을 하는 코드 모음으로 구성되어야 한다.

- 모듈이 맡은 일이 명확히 규정되어 있고, 특정 도메인 지식의 범위를 벗어나지 않아야 한다.

- 동일한 모듈에 서로 다른 기능 도메인들을 사용하는 경우를 방지해야 한다.

 

 

-> 두 모듈이 서로에 관한 지식에 크게 의존하는 경우는, 실제로 두 모듈이 하나의 시스템처럼 작동해야 한다.

-> 반면에 모듈의 두 부분이 서로 자주 상호작용하지 않는다면 별도의 모듈로 분류해야 한다.

 

규칙2. 앱 아키텍처에 따라 다른 모듈의 유형

모듈을 구성하는 방법은 주로 앱 아키텍처에 따라 다르며,

아래 권장 아키텍처를 통해서 앱에 도입할 수 있는 일반적 유형의 모듈을 구성할 수 있다.

https://developer.android.com/topic/architecture?hl=ko 

 

앱 아키텍처 가이드  |  Android 개발자  |  Android Developers

앱 아키텍처 가이드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에는 고품질의 강력한 앱을 빌드하기 위한 권장사항 및 권장 아키텍처가 포함

developer.android.com

 

 규칙3. 데이터 모듈 (Core)

데이터 모듈에는 일반적으로 저장소, 데이터 소스, 모델 클래스가 포함되어 있다.

데이터 모듈의 세 가지 주된 역할은 아래와 같다.

 

1. 특정 도메인의 모든 데이터 및 비즈니스 로직 캡슐화

- 각 데이터 모듈은 특정 도메인을 나타내는 데이터를 처리해야 하며, 관련 있는 데이터라면 다양한 유형으로 처리되어야 함

 

2. 저장소를 외부 API로 노출

- 데이터 모듈의 공개 API는 데이터를 앱의 나머지 부분에 노출하는 일을 담당하기 때문에 저장소여야 함

 

3. 외부로부터 모든 구현 세부정보 및 데이터 소스 숨기기

- 데이터 소스는 같은 모듈의 저장소에서만 엑세스 가능해야 한다. 

- 외부에 공개되지 않으며, private 또는 internal 공개 상태 키워드를 사용해서 데이터 소스를 숨겨야 한다.

 

샘플 데이터 모듈과 콘텐츠

 

규칙4. 기능 모듈 (Feature)

기능은 일반적으로 화면 또는 밀접하게 관련된 일련의 화면에 해당하는 독립적인 앱 기능을 의미한다.

앱의 하단 탐색 메뉴가 있는 경우, 각 대상이 기능일 가능성이 높다. (바텀 네비게이션)

 

예를 들어서 아래와 같이 하단 네비게이션이 구성된 경우,

 

아래와 같이 Feature 멀티 모듈을 구성할 수 있다.

 

위의 경우는 기능은 앱의 화면 또는 대상과 연결되며, 로직과 상태를 처리하기 위해서 ViewModel이 연결될 가능성이 높다.

단일 기능이 단일 보기나 탐색 대상으로 제한될 필요는 없으며, 기능 모듈은 데이터 모듈에 종속된다.

( 기능 모듈에서 ViewModel을 통해서 데이터 모듈에 접근한다는 뜻)

 

규칙5. 앱 모듈 (App)

앱 모듈은 애플리케이션의 진입점이다. 앱 모듈은 기능 모듈에 종속되며 일반적으로 루트 탐색을 제공한다.

demo 와 full 제품 버전 모듈의 종속 항목 그래프

app 모듈을 통해서  feature 모듈에 접근, feature 모듈을 통해서 data 모듈에 접근하는 종속 관계를 나타내고 있다.

 

만약 앱이 자동차, 웨어러블 기기 또는 TV와 같은 여러 기기 유형을 타겟팅하는 경우 기기별로 앱 모듈을 정의하는 것이 좋다.

아래 화면처럼 Wear 앱 종속 그래프를 가진다면, 모듈화의 이점을 최대한 활용할 수 있다.

 

 

규칙6. 그 외 일반 모듈..

 

일반 모듈 (핵심 모듈)에는 다른 모듈에서 자주 사용하는 코드가 포함된다.

일반 모듈은 중복성을 줄이는 역항르 하며, 앱 아키텍처의 특정 레이어를 나타내지는 않는다.

 

<UI 모듈>

- 앱에서 맞춤 UI 요소를 사용하거나, 정교한 브랜딩을 사용하는 경우 모든 기능을 재사용할 수 있도록 하나의 모듈로 캡슐화

- 이를 통해서 서로 다른 기능에서 UI를 일관되게 만들 수 있으며, 리팩터링 작업을 피할 수 있다.

 

<애널리틱스 모듈>

- 일반적으로 추적은 소프트웨어 아키텍처에 대한 고려 없이 비즈니스 요구사항에 따라 정해진다.

- 애널리틱스 추적기를 서로 관련 없는 여러 구성요소에 사용하는 경우가 많다.

 

<네트워크 모듈>

- 많은 모듈에 네트워크 연결이 필요한 경우 http 클라이언트 제공 전용 모듈을 사용하는 것이 좋다.

 

<유틸리티 모듈>

- 도우미라고도 하는 유티릴티를 사용하는데, 이는 애플리케이션 전체에서 재사용되는 작은 코드이다.

- 테스트 도우미, 통화 형식 지정 함수, 이메일 검사기 또는 맞춤 연산자가 있다.

 

규칙7. 가장 중요한! 모듈 간 통신

 

모듈은 완전히 분리된 경우는 거의 없으며, 다른 모듈에 의존해 서로 통신하는 경우가 많다.

직접적으로 같은 모듈에 접근하는 것은 좋지 않으며, 종속받는 기능에서 통신하는 것이 좋다.

 

정보를 자주 교환하는 경우에도 결합력을 낮게 유지해야 하며,

두 가지 모듈 간의 직접 통신은 순환 종속 항목 등으로 인해 불가능할 수도 있다. (권장하지 않음)

 

따라서 위 이미지처럼 모듈간의 직접적인 양방향 통신을 하지 않는 것이 좋으며,

샘플 데이터에서는 app 모듈을 통해서 도 모듈 간의 데이터 흐름을 조정하고 있다. (중재 모듈)

 

두 모듈간의 중재를 위해서 개별적으로 새로운 모듈을 두는 경우도 있으며, 이를 중재 모듈이라고 한다.

일반적으로는 app 모듈이 중재 역할을 하지만, 필요하다면 중재 모듈을 두는 것이 좋다.

중재 모듈은 탐색 그래프를 소유한 모듈로 아래 예시에서는 Navigation 구성요소를 사용하여

탐색을 통해 홈 기능의 데이터를 결제 기능을 전달한다.

 

navController.navigate("checkout/$bookId")

 

결제 대상은 도서 ID를 인수로 수신하고, 이를 통해서 도서 정보를 가져온다.

개발자는 저장된 상태 핸들을 사용하여 대상 기능의 viewModel 내부에서 탐색 인수를 검색할 수 있습니다.

 

class CheckoutViewModel(savedStateHandle: SavedStateHandle, …) : ViewModel() {

   val uiState: StateFlow<CheckoutUiState> =
      savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
          // produce UI state calling bookRepository.getBook(bookId)
      }
      …
}

 

이 때 객체를 탐색 인수로 전달해서는 안되며, 데이터 영역에서 원하는 리소스에

엑세스하고 로드하기 위해 기능에서 사용할 수 있는 간단한 ID를 사용한다.

이렇게 하면 결합력은 낮게 유지하고, 단일 소스 저장소 원칙을 위반하지 않는다.

 

아래 예시처럼 두 기능 모듈이 동일한 데이터 모듈에 종속되는 경우가 있다.

이 경우 모듈은 객체를 전달하는 대신 기본 ID를 교환하고 공유 데이터 모듈에서 리소스를 로드해야 한다.

 

 

규칙8. 일반 권장사항

이는 필수 사항이 아니며, 멀티 모듈 앱을 개발할 수 있는 하나의 방법은 없다.

하지만 좋은 코드를 작성하는 가능성을 높이기 위해서는 아래의 권장사항을 지키는 것이 좋다.

 

1. 구성을 일관되게 유지

- 모든 모듈에서는 구성 오버헤드가 발생하며, 모듈 수가 특정 기준점에 도달하면 일관된 구성을 관리하기 어렵다.

- 동일한 버전의 종속 항목을 사용하는 것이 좋다 ( 실수 가능성 감소)

 

2. 가능한 노출 최소화

- 모듈의 공개 인터페이스는 최소화하고, 필수 부분만 노출해야 한다.

- private 또는 internal 공개 상태 범위를 사용하여 선언을 모듈 비공개로 설정한다.

- 모듈에서 종속 항목을 선언할 때는 api보다 implementation을 사용하는 것이 좋다.

 

3. Kotlin 및 자바 모듈 선호

- 앱 모듈은 애플리케이션의 진입점으로 활용되며, 소스코드, 리소스, 에셋 및 AndroidManifest.xml을 포함한다.

- 라이브러리 모듈에는 앱 모듈과 동일한 콘텐츠가 포함되어 있다.

- 코틀린 및 자바 라이브러리에는 Android 리소스, 에셋 또는 매니페스트 파일 등이 포함되지 않는다.