「Build finished in 12m 4s」。コーヒーを淹れ終わってもまだ終わらないGradleビルドに、開発者のモチベーションは静かに削られていきます。機能追加のたびに肥大化するモノリス構造(単一モジュール)は、ビルド時間の増大だけでなく、予期せぬ依存関係の結合やマージコンフリクトの温床となります。本記事では、大規模化するAndroidプロジェクトを「正しく」解体し、開発体験(DX)を取り戻すためのマルチモジュール戦略について、実際の移行経験をもとに解説します。
なぜモノリスは限界を迎えるのか
私が担当していたEコマースアプリのプロジェクトでは、当初5名だったチームが20名に増えた時点で開発効率が急激に低下しました。主な症状は「どこかを直すと、全く関係ない画面が壊れる」という現象です。これは、全てのコードがappモジュールに詰め込まれ、クラス間のアクセス制限が機能していなかったためです。
Googleの公式ドキュメントでも推奨されている通り、コードベースを機能単位やレイヤー単位で分割することは、単なる整理整頓ではなく、並列ビルドを有効にするための必須条件です。
機能ベース分割(Feature Modularization)の設計戦略
モジュール化には主に「レイヤーによる分割(Layer-based)」と「機能による分割(Feature-based)」がありますが、スケーラビリティの観点からは機能による分割を強く推奨します。以下は、依存関係を整理し、循環参照を防ぐための推奨構成です。
推奨されるモジュール階層
- :app - DIのグラフ構築やナビゲーションの起点。実装コードはほぼ置かない。
- :feature:home, :feature:checkout - 各機能画面。UIとViewModelを含む。
- :core:network, :core:database - アプリ全体で共有されるインフラロジック。
- :core:ui - 共通のデザインシステムコンポーネント。
Gradleの設定ファイル(build.gradle.kts)では、implementationとapiを使い分けることが重要です。推移的依存関係(Transitive Dependencies)を不用意に公開しないことで、ビルド時間を短縮できます。
// feature/checkout/build.gradle.kts
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
dependencies {
// 他の機能モジュールには直接依存しないのが理想
// 必要な場合はインターフェース(APIモジュール)を介する
// Coreモジュールの利用
implementation(project(":core:network"))
implementation(project(":core:ui"))
// 外部ライブラリ
implementation("androidx.core:core-ktx:1.12.0")
}
ビルド速度の改善効果
モノリスからマルチモジュールへ移行し、Configuration CacheなどのGradle最適化を適用した結果、ビルド時間は以下のように変化しました。
| 計測項目 | モノリス構成 | マルチモジュール構成 | 改善率 |
|---|---|---|---|
| Clean Build | 14分 30秒 | 5分 45秒 | 60%短縮 |
| Incremental Build (UI変更) | 1分 20秒 | 12秒 | 85%短縮 |
移行時の注意点:循環参照の罠
移行プロセスで最も躓きやすいのが「循環参照(Circular Dependency)」です。例えば、:feature:loginが:feature:homeを呼び出し、:feature:homeがログアウト処理のために:feature:loginを参照するようなケースです。
これを防ぐためには、インターフェースによる抽象化が必要です。共通のインターフェースを定義した:core:navigationのような軽量モジュールを作成し、各Featureモジュールはそれに依存するように設計します。
buildSrcを使用すると、変更時にフルビルドが走る可能性があります。依存関係の管理には、最新のベストプラクティスであるVersion Catalog (libs.versions.toml)の使用を強く推奨します。
結論
Androidのマルチモジュール化は、初期のセットアップコストこそ高いものの、中長期的なプロジェクトの健全性を保つためには不可欠な投資です。単にコードを分割するだけでなく、各モジュールの責務を明確にし、Gradleの並列処理の恩恵を最大限に受けることで、開発チームは「待ち時間」ではなく「機能開発」に集中できるようになります。まずは、独立性の高い機能や、共通のユーティリティクラス(Coreモジュール)の切り出しから小さく始めてみてください。
Post a Comment