Android アプリ開発におけるマルチモジュール化
マルチモジュール化とは?
マルチモジュール化とは、大規模な Android アプリケーションを、機能や役割ごとに独立した「モジュール」に分割する開発手法。各モジュールは、独立してビルド・テスト・デプロイが可能になり、関心の分離、再利用性の向上、ビルド時間の短縮といったメリットが期待できる。
マルチモジュール化と聞くとビビるが分割して開発するだけで普通のAndroid開発と変わらない。
大規模なアプリケーション開発において、コードベースが肥大化すると、以下のような課題が生じやすくなる。
- ビルド時間の長期化:
- フルビルドに時間がかかり、開発効率が低下する。
- コードの可読性・保守性の低下:
- コードが複雑に入り組み、変更の影響範囲が把握しにくい。
- 機能追加・修正の困難化:
- コードの修正や機能追加が、他の箇所に予期せぬ影響を与える可能性がある。
マルチモジュール化は、これらの課題を解決し、より効率的で持続可能な開発体制を構築するための有効な手段となる。
マルチモジュール化のメリット (詳細)
マルチモジュール化には、以下のようなメリットがある。
-
ビルドスピードの向上:
- 並列ビルド: モジュールごとに独立してビルドできるため、複数のモジュールを同時にビルド (並列ビルド) することが可能。
-
関心の分離:
- 役割・機能ごとの分割: 機能や役割 (例: UI, データ取得, ビジネスロジック) ごとにモジュールを分割することで、コードの可読性・保守性が向上する。
-
モジュール間の依存関係の明確化: モジュール間の依存関係を
build.gradle.kts
で明示的に定義することで、設計の健全性を維持しやすくなる。 - 影響範囲の局所化: 機能追加や修正時の影響範囲をモジュール内に局所化し、他のモジュールへの影響を抑制することで、開発効率を高める。
- 開発チームの分割: モジュールごとに担当チームを分けることで、並行開発を効率的に進めることができる。
-
コードの再利用性:
- 共通モジュールの作成: 共通機能やライブラリをモジュールとして独立させることで、他のプロジェクトやモジュールでの再利用 が容易になる。
- コードの重複削減: 共通モジュールを活用することで、コードの重複を削減し、保守性を向上させることができる。
- ライブラリ化: 作成したモジュールを Android Library として公開し、他のプロジェクトで利用することも可能。
やってみてのメリット (だと感じたもの)
実際にマルチモジュール化を導入してみて、以下のようなメリットを感じた。
- 関心の分離による開発のしやすさ: 役割・機能でモジュール分割すると、関心の分離が明確になり、開発に集中しやすくなりなった。例えば、API 通信の箇所 (network モジュール) を作っているときに、UI など表示に関わるファイルがコード内に登場しないため、思考が整理され、実装に集中できた。
- 階層がゴチャつかない: 各モジュールの責務が明確になるため、コードが整理され、階層がゴチャつきにくくなりなった。結果として、コードの見通しが良くなり、目的のファイルに素早くアクセスできるようになった。
- コード修正や追加が容易になる (気がする): モジュール単位でコードがまとまっているため、コード修正や機能追加時の影響範囲を把握しやすくなった。まだ大規模な修正や追加は経験していませんが、コード量が増えても、比較的容易に対応できるのではないかと感じている。
-
gradle ファイルもゴチャつかないと思う: 各モジュールの
build.gradle.kts
は、そのモジュールに必要な依存関係のみを記述すればよいため、シンプルに保つことができる。ルートのbuild.gradle.kts
も、プロジェクト全体の依存関係を管理するだけで済み、肥大化を防ぐことができる。
やってみたデメリット
-
Gradle の依存関係:
-
依存関係の設定ミス: モジュール間の依存関係を
build.gradle.kts
で定義する際に、設定ミスが発生しやすい。特に、implementation
とapi
の違いを理解せずに使用すると、意図しないコンパイルエラーや実行時エラーが発生する可能性がある。 -
implementation
、api
などの依存関係の種類を適切に使い分けることが重要。api
は依存先モジュールの変更がコンパイル時に影響範囲を広げるため、モジュール間の依存関係を意識的に設計 する必要がある。 - バージョン管理: 依存関係のあるライブラリのバージョン管理を適切に行わないと、依存関係の衝突が発生する可能性がある。プロジェクト全体で使用するライブラリのバージョンを統一するなど、バージョン管理を徹底する必要がある。
- Dependency graph の活用: Android Studio の "Project Dependencies" ツール (Dependency graph) を活用すると、モジュール間の依存関係を視覚的に確認できる。依存関係が複雑になった場合は、このツールで依存関係を把握し、問題を早期に発見することが重要。
-
依存関係の設定ミス: モジュール間の依存関係を
-
モジュールの肥大化・複雑化:
- モジュールの作りすぎ: 安易にモジュールを分割しすぎると、モジュールの数が肥大化し、管理が煩雑になる可能性がある。また、モジュール間の依存関係が複雑になり、かえって開発効率を低下させることもある。
- モジュールの粒度: モジュールの粒度を適切に設計することが重要。単一責任の原則 (SRP) に基づき、小さくまとまったモジュール を作成することを心がける。
- 凝集度と結合度: 機能の境界 を明確にし、凝集度が高く (モジュール内の関連性が高い)、結合度が低い (モジュール間の依存関係が少ない) モジュール分割を目指す。
- 定期的な見直し: モジュール構成は、プロジェクトの成長や変化に合わせて、定期的に見直し、必要に応じて再構成することも重要。
導入方法
新規プロジェクトの場合:
- Android Studio で新規プロジェクトを作成する際に、最初からモジュール構成を意識して設計する。
- プロジェクトの機能や役割に基づいて、必要なモジュールを洗い出しする。
- 各モジュールを Android Studio で作成 (後述)。
既存プロジェクトの場合:
- 既存プロジェクトに、段階的にモジュールを導入する。
- まずは、影響範囲の小さい機能 や 独立性の高い機能 からモジュール化を始めると良い。例えば、共通 UI コンポーネントやユーティリティ関数などをモジュール化することから始めるのがおすすめ。
- リファクタリングを伴うため、テストコード を充実させてから行うと安全。
- 徐々にモジュール化の範囲を広げていき、最終的にはアプリケーション全体をモジュール化することを目指す。
Module の作成方法:
- プロジェクトを右クリック
-
New
→Module
を選択 -
Create New Module
ダイアログで、モジュールの種類を選択する。- Android Library: 機能モジュールや共通コンポーネントなど、再利用可能なモジュールを作成する場合に選択する。
- Phone & Tablet Module: 新しいアプリケーションモジュールを作成する場合に選択 (既存の
app
モジュールに追加でアプリケーションモジュールを作成することは稀)。
- モジュール名 (Module name) を入力し、
Finish
をクリック。
app モジュールでの Module の利用:
-
app
モジュールで、作成したモジュール (例:hogeModule
) を利用するには、app
モジュールのbuild.gradle.kts
ファイルに依存関係を追加する。
dependencies {
// : を忘れそうなので注意
implementation(project(":hogeModule"))
}
-
Sync Now
をクリックして、Gradle を同期する。 -
hogeModule
の関数をapp
モジュールから呼び出せば、モジュール間の連携が完了。
一般的なモジュール種類の例:
Android アプリケーションにおける一般的なモジュール種類の例を以下に示す。
-
app
: アプリケーションのエントリーポイントとなるモジュール。UI, 画面遷移、DI の設定など、アプリケーション全体の構成を管理する。 -
feature-{機能名}
: 個別の機能モジュール。機能ごとに分割することで、関心の分離を促進し、開発・保守性を高める (例:feature-timeline
,feature-profile
,feature-login
)。 -
ui-component
: 共通 UI コンポーネントモジュール。ボタン、テキストフィールド、カードなど、再利用可能な UI コンポーネントをまとめて管理する (例:ui-button
,ui-card
,ui-dialog
)。 -
data
: データ取得・管理モジュール。API 通信、データベース、ローカルファイルなど、データソースへのアクセスとデータ処理を担う (例:data-network
,data-local
,data-database
)。 -
domain
: ビジネスロジックモジュール。アプリケーション固有のビジネスルールやユースケースを実装する。データ層と UI 層を分離し、テスト容易性を高める。 -
di
: 依存性注入 (Dependency Injection) モジュール。DI コンテナの設定やモジュールの提供を行う (例: Dagger, Hilt)。 -
util
: ユーティリティ関数モジュール。アプリケーション全体で共通的に使用するユーティリティ関数や拡張関数などをまとめる。 -
test
: テスト関連モジュール。単体テスト、結合テスト、UI テストなど、テストコードをまとめて管理する (例:test-unit
,test-integration
,test-ui
,test-shared
)。
参考文献
Android アプリのモジュール化のガイド | App architecture | Android Developers