계속 증가하고 있는 코드베이스에서 확장성, 가독성 및 전반적 코드 품질은 시간이 지남에 따라 감소하는 경우가 많다.
그 원인은 코드베이스 규모가 커지고 있음에도 불구하고 유지관리 담당자가 쉽게 유지관리할 수 있는 구조를 적용하기 위한 적극적인 조치를 취하지 않기 때문이다.
모듈화는 유지관리 가능성을 개선하고 이러한 문제를 방지할 수 있는 방식으로 코드베이스를 구조화하는 방법이다.
모듈화는 코드베이스를 느슨하게 결합된 독립적인 부분으로 구성하는 방법이다.
각 부분이 모듈에 해당하며, 각 모듈은 독립적이며 명확한 역할을 한다.
하위 문제를 해결하기 위해 문제를 더 작고 쉬운 문제로 나누면 대형 시스템 설계와 유지보수의 복잡성이 줄어든다.

그림 1: 샘플 다중 모듈 코드베이스의 종속 항목 그래프
모듈화의 이점은 많지만 모두 코드베이스의 유지관리 가능성과 전반적인 품질을 개선하는 데 중점을 둔다.

위의 이점은 모듈식 코드베이스에서만 얻을 수 있다.
다음 이점은 다른 기술을 사용하여 얻을 수 있지만 모듈화를 통해 훨씬 더 쉽게 얻을 수 있다.

코드베이스의 세분화는 코드베이스가 모듈로 구성된 정도를 나타낸다. 코드베이스가 세분화되면 될수록 모듈이 더 작고 숫자가 늘어나며, 모듈식 코드베이스를 설계할 때 세부사항 수준을 결정해야 한다. 그렇게 하려면 코드베이스의 크기와 코드베이스의 상대적 복잡성을 고려해야 하는데, 너무 세분화되면 오버헤드가 가중되고 너무 대략적이면 모듈화의 이점이 줄어든다.
일반적인 몇 가지 함정은 다음과 같습니다.
- 너무 세분화됨: 빌드 복잡성과 상용구 코드가 늘어남으로써 모든 모듈에서 일정량의 오버헤드가 발생합니다. 복잡한 빌드 구성으로 인해 모듈 간에 일관된 구성을 유지하기가 어렵습니다. 상용구 코드가 너무 많아 관리하기 어렵고 번거로운 코드베이스가 됩니다. 오버헤드가 확장성 개선에 해가 되는 경우 일부 모듈을 통합하는 것이 좋습니다.
- 너무 대략적임: 반대로 모듈이 너무 커지면 또 하나의 모놀리식으로 끝날 수 있으며 모듈성이 제공하는 이점을 놓칠 수 있습니다. 예를 들어 작은 프로젝트에서는 데이터 영역을 단일 모듈 내에 넣어도 괜찮습니다. 그러나 크기가 커지면 저장소와 데이터 소스를 독립형 모듈로 분리해야 할 수 있습니다.
- 너무 복잡함: 프로젝트를 모듈화하는 것이 항상 적합한 것은 아닙니다. 결정적 요소는 코드베이스의 크기입니다. 프로젝트가 특정 기준점 이상으로 확장될 것 같지 않으면 확장성과 빌드 시간 이점은 해당되지 않습니다.
모듈식 코드베이스를 특징짓는 한 가지 방법은 결합력 및 응집력 속성을 사용하는 것이다.
결합력은 모듈이 서로 종속된 정도를 측정하는데, 이 맥락에서 응집력은 단일 모듈의 요소가 기능적으로 관련된 방식을 측정한다.
일반적으로 결합력은 낮추고 응집력은 높여야 한다.
- 결합력이 낮다는 것은 모듈이 최대한 서로 독립적이어야 한다는 의미이며,
한 모듈의 변경사항이 다른 모듈에 미치는 영향이 없거나 최소화된다.
모듈은 다른 모듈의 내부 작동을 알 수 없어야 한다.- 응집력이 높다는 것은 모듈이 시스템 역할을 하는 코드 모음으로 구성되어야 함을 의미한다.
모듈은 맡은 일이 명확히 규정되어 있고 특정 도메인 지식의 범위를 벗어나지 않아야 한다.
데이터 모듈에는 일반적으로 저장소, 데이터 소스, 모델 클래스가 포함되어 있다.
데이터 모듈의 세 가지 주된 역할은 다음과 같다.
- 특정 도메인의 모든 데이터 및 비즈니스 로직 캡슐화 : 각 데이터 모듈은 특정 도메인을 나타내는 데이터를 처리해야 한다. 관련이 있는 데이터라면 다양한 유형의 데이터를 처리할 수 있다.
- 저장소를 외부 API로 노출 : 데이터 모듈의 공개 API는 데이터를 앱의 나머지 부분에 노출하는 일을 담당하기 때문에 저장소여야 한다.
- 외부로부터 모든 구현 세부정보 및 데이터 소스 숨기기 : 데이터 소스는 같은 모듈의 저장소에서만 액세스 가능해야 하며, 외부에는 공개되지 않아야 한다.
Kotlin의 private 또는 internal 공개 상태 키워드를 사용하여 데이터 소스를 숨길 수 있다.

기능은 일반적으로 화면 또는 밀접하게 관련된 일련의 화면에 해당하는 독립적인 앱 기능을 의미 한다.
ex) 앱에 하단 탐색 메뉴가 있는 경우 각 대상이 기능일 가능성이 높다.

기능은 앱의 화면 또는 대상과 연결된다.
따라서 로직과 상태를 처리하기 위한 UI와 ViewModel이 연결될 가능성이 높다.
단일 기능이 단일 보기나 단일 탐색 대상으로 제한될 필요는 없으며, 기능 모듈은 데이터 모듈에 종속된다.

앱 모듈은 애플리케이션의 진입점이다.
앱 모듈은 기능 모듈에 종속되며 일반적으로 루트 탐색을 제공한다.
빌드 변형을 사용하면 단일 앱 모듈을 다양한 바이너리로 컴파일할 수 있다.

일반 모듈(핵심 모듈이라고도 함)에는 다른 모듈에서 자주 사용하는 코드가 포함된다.
일반 모듈은 중복성을 줄이는 역할을 하며, 앱 아키텍처의 특정 레이어를 나타내지는 않는다.
다음은 일반적인 모듈의 예이다.
- UI 모듈 : 앱에서 맞춤 UI 요소를 사용하거나 정교한 브랜딩을 사용하는 경우 모든 기능을 재사용할 수 있도록 위젯 컬렉션을 하나의 모듈로 캡슐화하는 것이 좋습니다. 이렇게 하면 서로 다른 기능에서 UI를 일관되게 만들 수 있습니다. 예를 들어 테마 설정이 일원화되어 있다면 리브랜딩이 발생할 때 골치 아픈 리팩터링 작업을 피할 수 있습니다.
- 애널리틱스 모듈 : 일반적으로 추적은 소프트웨어 아키텍처에 대한 고려 없이 비즈니스 요구사항에 따라 정해집니다. 애널리틱스 추적기를 서로 관련 없는 여러 구성요소에 사용하는 경우가 많으며, 이 경우 전용 애널리틱스 모듈을 사용하는 것이 좋습니다.
- 네트워크 모듈 : 많은 모듈에 네트워크 연결이 필요한 경우 http 클라이언트 제공 전용 모듈을 사용하는 것이 좋습니다. 이는 클라이언트에 맞춤 구성이 필요할 때 특히 유용합니다.
- 유틸리티 모듈 : 도우미라고도 하는 유틸리티는 일반적으로 애플리케이션 전체에서 재사용되는 작은 코드입니다. 유틸리티의 예로는 테스트 도우미, 통화 형식 지정 함수, 이메일 검사기 또는 맞춤 연산자가 있습니다.
모듈은 완전히 분리된 경우는 거의 없으며 다른 모듈에 의존해 서로 통신하는 경우가 많다.
모듈이 함께 작동하고 정보를 자주 교환하는 경우에도 결합력을 낮게 유지하는 것이 중요하다.
경우에 따라 두 가지 모듈 간의 직접 통신은 아키텍처 제약 조건의 경우에서처럼 바람직하지 않을 수 있는데, 두 가지 모듈 간의 직접 통신은 순환 종속 항목 등으로 인해 불가능할 수도 있다.

이 문제를 극복하기 위해 두 개의 다른 모듈 간을 중재하는 세 번째 모듈을 둘 수 있다.
중재 모듈은 두 모듈의 메시지를 수신 대기하고 필요에 따라 메시지를 전달할 수 있다.

처음에 언급했듯이 다중 모듈 앱을 개발할 수 있는 하나의 올바른 방법은 없다.
많은 소프트웨어 아키텍처가 있는 것처럼 앱을 모듈화하는 방법도 다양하다.
그렇지만 다음과 같은 일반 권장사항은 코드의 판독과 유지관리 및 테스트 가능성을 높여주는 데 도움이 된다.
모든 모듈에는 구성 오버헤드가 발생한다. 모듈 수가 특정 기준점에 도달하면 일관된 구성을 관리하기가 어렵다. 예를 들어 모듈에서 동일한 버전의 종속 항목을 사용하는 것이 중요하다. 단지 종속 항목 버전을 늘리기 위해 많은 수의 모듈을 업데이트해야 하는 경우 많은 노력이 들어갈 뿐 아니라 실수의 가능성도 발생하는데, 이 문제를 해결하려면 Gradle 도구 중 하나를 사용하여 구성을 중앙 집중화하면 된다
- 버전 카탈로그는 동기화 중에 Gradle에서 생성된 종속 항목의 유형 안전 목록입니다. 이는 모든 종속 항목을 선언할 수 있는 중앙 위치로, 프로젝트의 모든 모듈에서 사용 가능합니다.
- 규칙 플러그인을 사용하여 모듈 간에 빌드 로직을 공유합니다.
모듈의 공개 인터페이스는 최소화하고 필수 부분만 노출해야 한다. 구현 세부정보가 외부에 유출되면 안되며, 모든 범위를 가능한 한 최소 수준으로 지정한다.
Kotlin의 private 또는 internal 공개 상태 범위를 사용하여 선언을 모듈 비공개로 한다. 모듈에서 종속 항목을 선언할 때는 api보다 implementation을 사용하는 것이 좋습니다. 전자는 모듈 소비자에게 전이 종속 항목을 노출한다. 구현을 사용하면 다시 빌드해야 하는 모듈의 수가 줄어들기 때문에 빌드 시간을 개선할 수 있다.
Android 스튜디오에서 지원하는 세 가지 필수 모듈 유형은 다음과 같다.
- 앱 모듈은 애플리케이션의 진입점입니다.
앱 모듈은 소스 코드, 리소스, 애셋 및 AndroidManifest.xml을 포함할 수 있습니다.
앱 모듈의 출력은 Android App Bundle(AAB) 또는 Android 애플리케이션 패키지(APK)입니다.- 라이브러리 모듈에는 앱 모듈과 동일한 콘텐츠가 포함되어 있습니다.
라이브러리 모듈은 다른 Android 모듈에 종속 항목으로 사용됩니다.
라이브러리 모듈의 출력은 앱 모듈과 구조적으로 동일한 Android 보관 파일(AAR)입니다.
하지만 이는 나중에 다른 모듈에서 종속 항목으로 사용할 수 있는 Android 보관 파일(AAR)로 컴파일됩니다.
라이브러리 모듈을 사용하면 여러 앱 모듈 간에 동일한 로직과 리소스를 캡슐화하고 재사용할 수 있습니다.- Kotlin 및 자바 라이브러리에는 Android 리소스, 애셋 또는 매니페스트 파일이 포함되지 않습니다.
Android 모듈에는 오버헤드가 발생하므로 가능하면 Kotlin 또는 자바 종류를 사용하는 것이 좋습니다.
기능 혹은 도메인 단위를 기준으로 분리하는 것을 핵심관심모듈 (Core concern, Vertical) 이라 하고,
Layer 혹은 생명주기를 단위로 공통로직을 분리하는 것을 부가관심모듈 (Cross-Cutting concern, Horizontal) 이라고 한다.

분리는 신중하게 하도록하자.
잘못된 분리를 다시 합치는 비용은 비싸기 때문이다.
패키지, 모듈, 서비스 등 물리적 단위의 분리는 정말 필요할 때 사용해야 한다.
공통적으로 쓰이는 로직을 common 혹은 util 혹은 library 라는 이름의 모듈로 분리하지 않도록 한다.
이유는 우아한 형제들 기술 블로그의 공통(Common) 모듈의 저주 를 참고해서 읽도록한다.
해당 사례를 읽고 느낀 문제점을 간략히 표현하자면 아래와 같다.
- "해당 기능을 2군데 이상 사용하는 곳이 있다면 common에 넣자" 라고 생각하여 개발하다 보니, common이 너무 비대해졌다.
- 모호한 비즈니스 로직이 common 모듈에 포함되었다.
- 점점 꼬리를 물고 복잡해지는 모듈...
독립 모듈, 공통 모듈을 나누는 방법은 다음과 같이 생각해 볼 수 있다.
- 공통모듈은(common) 전역적으로 사용되고, 독립 모듈로 구성하기 애매한 기능만 넣는다 (비즈니스 로직 제외)
- 비즈니스 로직별로 독립 모듈을 구성한다.
- 공통 모듈이 아예 없는건 아니고, 비즈니스 로직을 제외한 지원, 유틸성 가벼운 코드만 포함한다.