Android

안드로이드 [Kotlin] - Clean Architecture / 모듈화(2)

🤖 Play with Android 🤖 2023. 7. 9. 20:11
728x90



01. 모듈

01-1. 모듈이란

image

공식문서에 따르면 모듈의 정의는 다음과 같다.

모듈은 소스 파일 및 빌드 설정으로 구성된 모음이며, 이를 통해 프로젝트를 별개의 기능 단위로 분할할 수 있습니다. 프로젝트에는 하나 이상의 모듈이 포함될 수 있으며, 하나의 모듈이 다른 모듈을 종속성으로 사용할 수 있습니다. 각 모듈은 개별적으로 빌드, 테스트 및 디버그할 수 있습니다.

위의 설명처럼 모듈은 각각 소스 파일과 빌드 설정으로 구성된 모음이다.

image

위와 같이 안드로이드 스튜디오에서 프로젝트를 생성하고 항상 당연하게 보았던 app 역시 하나의 모듈이다.

최근에는 앱 모듈(app 모듈)에 모든 코드를 작성(모놀리틱 프로그래밍)하지 않고, data나 domain 등의 모듈로 세분화 시켜 개발한 뒤 최종적으로 프로덕트를 만들 때 이러한 모듈들을 합쳐서 하나의 애플리케이션을 만들게 된다. 이런 방식을 모듈화 프로그래밍이라고도 한다.


모듈은 다음과 같은 특징을 갖는다.

  • 모듈은 논리적 또는 기능적으로 분리되어 격리되고 독립적인 일은 수행한다.
  • 작개 쪼개어 다루므로 재사용 및 유지보수가 수월하고 테스트에 용이하다.

01-2. Application, Android Library, Java & Kotlin Library

image

Application

  • Application은 안드로이드 프로젝트를 만들 때 기본으로 생성되는 app 모듈이다.
  • Android App Module 이라고도 부른다.
  • 하나의 프로젝트에 여러 개의 app 모듈이 들어갈 수 있다.
    • 기존에 만들어놓은 View나 코드를 재사용하기 용이하다.
    • 예를 들어 동일한 기능을 가진 앱이지만 하나는 현대버전, 하나는 기아버전으로 만들 수 있다.

Android Library

  • 어플리케이션의 기능 확장을 위해 이전에 컴파일 된 소스 코드들의 집합이다. 안드로이드 프로젝트에서 지원되는 모든 파일 형식을 포함할 수 있다.

Java & Kotlin Library

  • 순수하게 Java 혹은 Kotlin 파일로만 이루어진 라이브러리 이다.

Applicatoin과 Android Library는 구조적으로 동일한 구성을 가지고 있다. 이 말은 Android Library 역시 Kotlin 파일, layout이나 drawable 같은 리소스 파일, Mainfest 까지 모두 동일하게 포함을 하고 있다. 이 둘의 차이는 목적과 빌드 결과이다.

Application(Android App Module)은 빌드 결과 APK 파일을 생성하게 되고, Android Library는 AAR 이라는 파일, 그리고 Java, Kotlin Library는 JAR 파일을 생성하게 된다.

또한 Android Library는 다른 앱 모듈 혹은 다른 라이브러리에 종속, 즉 포함되려는 목적이 있다.

image
  • 초록색 점으로 표시되는게 Application(Android App Module)
  • 책 모양으로 표시되는게 Android Library
  • 파란색 네모로 표시되는게 Java & Kotlin Library 이다.

01-2. 단일 모듈 vs 멀티 모듈

단일 모듈

image
  • 하나의 앱 모듈로만 구성된 앱을 의미한다.
  • 소규모 앱을 만들 때 취하는 가장 이해하기 쉽고, 간편한 형태이다.
  • 하지만 단일 모듈로 구성된 앱은 자연스레 소스코드 간의 결합도가 높아지기 쉽다.
  • 시거대한 하나의 덩어리 형태로 되어 있어 수정이 어렵게 된다.

멀티 모듈

image
  • 여러 모듈로 구성된 앱을 의미 한다.
  • 앱의 코드베이스가 큰 경우에 다중 모듈 구성을 적용하는 것이 적합하다.
  • 시작하는 사람들에게 다중 모듈 프로젝트는 이해하기 어렵고, 가독성이 떨어질 수 있다.
  • 하지만 코드의 결합도가 낮기 때문에 앱을 확장하거나 수정할 때, 훨씬 더 유연하게 대응할 수 있다.

01-3. 멀티 모듈의 장점

의존성 줄이기(관심사의 분리)

  • 기존의 단일 모듈에서는 개발자의 실수로 의존성 규칙을 위반할 수 있다.
  • 멀티 모듈을 사용하면 각각의 모듈의 build.gradle 파일에서 의존성을 추가하지 않으면 다른 모듈의 코드를 사용 자체를 할 수 없기 때문에 의존성 규칙을 쉽게 관리 가능하다.

빌드 속도 단축

  • 전체 모듈을 빌드할 필요가 없으므로 빌드시간을 줄이는데 유리해진다.
image

코드 재사용성 증가

  • 계층, 기능 별로 모듈을 나누어서 코드를 작성하게 되면 해당 기능이 필요할 때 그 기능을 가지고 있는 모듈에 대한 의존성을 추가하여 사용하면 되기 때문에 재사용성이 늘어난다.

01-4. 다중 모듈을 구성하는 기준

모든 프로젝트에 딱 맞는 단 하나의 다중 모듈화 전략은 존재하지 않는다. 비즈니스의 특성, 고객의 요구사항 및 기타 환경에 알맞게 전략을 세워 멀티 모듈 프로젝트를 만들어야 한다.

안드로이드 공식문서에서는 일반적으로 모듈을 구성하는 기준을 3가지로 제시하고 있다.


01-4-1. 응집도

응집도는 소프트웨어에서 구성요소들이 하나의 목적을 위해 얼마나 밀접하게 관련되어 있느냐를 나타내는 개념이다.

  • 높은 응집도를 갖는 모듈은 해당 모듈의 내부 구성요소들이 동일한 목적을 가지고 서로 밀접하게 관련되어 있어서 모듈의 기능을 구현하고 유지보수하기 쉽다.
  • 반면에 낮은 응집도를 갖는 모듈은 다양한 목적을 가지고 있는 구성요소들이 서로 무관하게 연결되어 있어서 모듈의 기능을 파악하기 어렵고 유지보수하기 어렵다.

응집도는 소프트웨어 설계 단계에서 고려되어야 하는 중요한 요소 중 하나이며 모듈 내부의 구성요소들을 효과적으로 조직화하여 모듈의 응집도를 높이는 것이 좋은 소프트웨어 개발 방법론의 핵심 원칙 중 하나라고 할 수 있다.


01-4-2. 결합도

결합도는 소프트웨어 공학에서 다른 모듈들과의 상호작용의 정도를 나타내는 개념이다.

  • 높은 결합도를 갖는 모듈은 다른 모듈들과 밀접하게 상호작용하므로, 한 모듈의 변경사항이 다른 모듈들에게 영향을 미칠 가능성이 크다.
  • 이는 모듈간 의존성이 강하고, 유지보수가 어려워질 수 있다.
  • 반면에 낮은 결합도를 갖는 모듈은 다른 모듈들과의 상호작용이 적어서, 한 모듈의 변경사항이 다른 모듈들에게 영향을 덜 주게 된다.

결합도 또한 소프트웨어 설계 단계에서 고려되어야 하는 중요한 요소 중 하나이며, 모듈 간의 관계를 최대한 느슨하게 유지하면서 기능을 구현하는 것이 좋은 소프트웨어 개발 방법론의 핵심 원칙 중 하나다. 모듈 간의 결합도를 낮추기 위해서는 인터페이스 사용 등을 통해 코드를 추상화 할 수 있다.


01-4-3 세분성

세분성은 소프트웨어 설계에서 모듈의 크기와 복잡도에 대한 개념으로, 모듈이 나누어지는 기준, 크기 및 세부화 정도를 나타낸다.

모듈의 세분화 정도가 적절하지 않으면 코드의 가독성이 떨어지고 유지보수가 어려워지는 문제가 발생할 수 있다. 따라서, 적절히 모듈을 나누고 구성하는 것이 중요하다.

처음부터 완벽하게 기능과 목적에 맞게 모듈을 나눈 다는 것은 쉽지 않기 때문에 끊임없이 모듈로 분리하고 통합하는 과정을 겪으면, 진행하고 있는 프로젝트에 최적의 기준을 찾을 수 있다.


02. 모듈 세분화

02-1. 계층별로 모듈 세분화

image

안드로이드의 일반적인 아키텍처 다이어그램으로 다음과 같이 3가지 모듈로 구성된다.

  • UI Layer : UI계층으로 ViewModel, View, Composables 등이 포함되며, 일반적으로 app 모듈 또는 presentation 모듈이 된다.
  • Domain Layer : Use case가 포함되어 복잡한 비즈니스의 캡슐화를 담당한다.
  • Data Layer : Repository, Data Source 등이 포함되며, 앱에서 처리하는 다양한 유형의 데이터를 다룬다.

서비스가 성장함에 따라 코드베이스가 증가하게 되고, 결국 각각의 모듈도 거대해진다. 예를 들어 Domain 계층에서 작은 변화가 생길 때 이를 의존하고 있는 UI 계층에도 영향을 미치기 때문에 새로 컴파일이 필요하고, 이는 빌드시간 지연으로 이어진다. 즉, 코드의 결합도가 높아지는 문제가 발생한다고 할 수 있다.

이때는 모듈을 더 세분화하는 것을 고려해볼 수 있다.


02-2. 기능별로 모듈 세분화

공식문서에서 설명한 앱을 기준으로 보자. 책과 관련 된 앱을 만든다고 가정하자.

image
  • 이 앱은 책(:books), 저자(:authors),리뷰(:reviews) 기능들로 구성되어 있다.
  • 기능별로 모듈을 구성해서 앱을 만든다면 위 그림과 같이 설계 할 수 있다.

image
  • 하지만 기능별로 모듈을 구성하면 코드 재사용을 위해 기능 모듈끼리 의존하게 된다.
  • 예를 들면 :books 모듈이 :authors 또는 :reviews에 있는 코드를 참조하기 위해 해당 모듈을 다음 그림과 같이 의존해야 한다.

이렇게 되면 각 모듈간에 강하게 결합되어 결합도를 낮추어야 한다는 기준에 위배된다. 그렇다고 각 모듈별로 동일한 코드를 갖고 있게 되면 불필요한 코드 중복이 일어나게 된다.


02-3. 기능 + 계층 별로 세분화

앞에서 언급한것 처럼 계층별로 모듈을 나눌 때 그리고 기능별로 모듈을 나눌 때 모두 각각의 장단점이 있다.

계층별로 나누기

  • 계층별로 나눌 때는 코드의 응집도는 높다는 장점이 있다.
  • 하지만 결합도가 높고 모듈의 세분화 정도가 떨어지는 단점이 있다.

기능별로 나누기

  • 기능별로 나눌 때는 세분화가 잘되는 장점이 있다.
  • 하지만 결합도가 높아지기 쉽다는 단점이 있다.

두 가지를 결합하여 서로의 단점을 보완해보도록 하자.


image

위 다이어그램은 상위 계층에서 하위 계층으로만 의존한다는 전제조건을 갖는다. (수평으로도 의존하면 안된다.)

두 방법을 합치는 기능 + 계층별로 세분화하면 다음과 같은 장점을 갖는다.

  • 각 모듈은 각자의 역할에 전문적이며 독립적이기 때문에 코드 응집도를 높일 수 있다.
  • 각 모듈은 느슨하게 결합되므로 추후에 시스템을 손쉽게 수정할 수 있다.
  • 모듈이 세분화되어 있으므로 쉽게 재사용할 수 있다.

03. 코드 실습 with Hilt(DI 라이브러리)

03-1. 설계

image

클린 아키텍처를 기반으로 3개의 모듈 + app 모듈로 구성한다.

  • Application Module : app
  • Android Library : presentation, data
  • Java & Kotlin Library : domain

domain 모듈은 안드로이드 의존성을 가지지 않고 Java or Kotlin 코드로만 작성되어 있다. 또한 app 모듈은 Manifest 파일 설정과 Hilt 의존성 주입, Hilt 앱 설정을 위해 사용한다.


03-2. Manifest 설정

Manifest
image


MyApplication.kt
image


클린 아키텍처 의존 방향을 따라보자

Presentation -> Domain <- Data

Presentation 모듈의 build.gradle
image

  • Presentation 모듈이 Domain 모듈에 의존하도록 설정한다.

Data 모듈의 build.gradle
image

  • Data 모듈 역시 Domain 모듈에 의존하도록 설정한다.

Domain 모듈의 build.gradle
image

  • Domain 모듈은 어떤 모듈에도 의존하고 있지 않다.
  • 오직 외부 라이브러리만 사용하고 있는 모습이다.

app 모듈의 build.gradle
image

  • Activity 정보를 알기 위해 Presentation 모듈을 알고 있다. (app 모듈의 manifest에 Activity를 정의하기 위해)
  • 뿐만 아니라 Repository 구현체를 만들어야 하기 때문에 모든 모듈을 알고 있다.

모듈 설정에 대한 설명

app 모듈에서 Application 클래스에 HiltAndroidApp 어노테이션을 사용할경우 Application 클래스가 속한 모듈(app 모듈)에 힐트 계층도가 만들어지게 된다. 그리고 app 모듈이 계층도의 가장 윗단계인 SingletonComponent 가 된다. 적재되는 객체에 대한 정보를 모두 알고있어야 하기때문에 app 모듈은 다른 모든 모듈들을 알고있어야 한다.

현재 domain 모듈에서 Repository 인터페이스를 정의하고 data 모듈에서 Repository의 구현체를 만들어 사용하고 있다. 즉 domain 모듈은 data 모듈을 모르는 상태고 data 모듈은 domain 모듈을 알고있는 구조이다.

Hilt의 경우 의존성 주입을 받을경우 받는 객체가 현재의 계층도에 없다면 점점 위 계층도로 올라가면서 해당 객체를 찾게 된다. 만약 ActivityComponent 에 주입받아야 하는 객체가 존재하지 않는다면 SingletonComponent 에서 찾게 되는 것이다. 만약 ActivityComponent 에서 주입받는 객체를 찾지 못할경우 SingletonComponent 에서는 찾을 수 있지만 FragmentComponent 와 같이 계층도 아래로는 객체를 찾을 수 없다. 예를들어 현재 domain 모듈의 UseCase에서 Repository 타입의 객체를 주입받고 있다. UseCase의 경우 SingleComponent 에 적재되어있으므로 SingleComponent 에서 Repository 타입의 객체를 찾게된다.


03-2. Hilt(의존성 주입 라이브러리)를 통한 Repository 연결

  • Doamin 모듈에서 Repository 인터페이스를 만들었다.image

- Data 모듈에서 위에서 만든 Repository의 구현체, 즉 RepositoryImpl을 만들었다. image
- Data 모듈에서 RepositoryModule을 만들어 Repository 인터페이스 인스턴스를 주입할 수 있도록 하였다. image

이렇게 Hilt를 사용하면 멀티 모듈을 프로젝트에 보다 쉽게 적용할 수 있다.