モジュール依存関係の管理
依存関係グラフを整理し、非循環に保ちます。
「モジュール依存関係の管理」はCoddyKit上の無料Android Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAndroid Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Android Academyコースには全4レッスンが含まれています。
依存関係グラフ
すべてのモジュールは、依存する他のモジュールを宣言します。これらを合わせたものが依存関係グラフです。健全なグラフはDAG(有向非巡回グラフ)になります。依存関係が一方向に向かい、ループを作らない構造です。
このレッスンでは、依存関係をきれいに宣言する方法、適切なGradle設定の選び方、バージョンの共有方法、循環依存の防ぎ方を学びます。
モジュール依存関係を宣言する
dependencies ブロック内で project(":path:to:module") を指定すると、別のモジュールへの依存関係を追加できます。パスはフォルダー構成を反映し、settings.gradle.kts で宣言した内容と一致します。
// feature/profile/build.gradle.kts
dependencies {
implementation(project(":core:data"))
implementation(project(":core:designsystem"))
implementation(project(":core:model"))
}implementation と api
選択した設定によって、依存先の何が利用側に公開されるかが決まります。
implementation: 依存関係は非公開になります。あなたのモジュールに依存するモジュールからは見えません。通常はこちらを選びます。api: 依存関係が再公開されます(推移的)。依存先の型が公開APIに現れる場合に限って使用します。
ほとんどの場合はimplementationを優先してください。非公開の依存関係を変更しても利用側の再コンパイルが不要になるため、ビルドが高速になります。
// core/data/build.gradle.kts
dependencies {
// Repository signatures return :core:model types,
// so consumers need to SEE it -> api
api(project(":core:model"))
// Network is an internal detail -> implementation
implementation(project(":core:network"))
}implementation でビルドが高速になる理由
implementation を使うと、Gradleは非公開の依存関係を変更してもモジュールの公開ABIに影響しないことを把握できます。そのため、利用側を再コンパイルする必要がありません。apiでは、変更の影響がすべての推移的な利用側へ波及します。
目安として、依存関係がモジュールの公開型(戻り値やpublicな引数)に現れる場合にのみapiへ追加します。それ以外ではimplementationを使います。
// Public -> needs api
fun observeUser(): Flow<User> // Flow and User leak out
// Internal -> implementation is enough
private val client: OkHttpClient // never exposedバージョンを一元管理する: Version Catalog
モジュールが多い場合、ライブラリのバージョンをあちこちに繰り返し書きたくはありません。Gradleのversion catalog(gradle/libs.versions.toml)では、バージョンとエイリアスを一度だけ定義できます。各モジュールは同じエイリアスを参照します。
# gradle/libs.versions.toml
[versions]
compose-bom = "2024.09.00"
retrofit = "2.11.0"
[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }モジュールでCatalogを使う
各モジュールでは、生成されたlibsアクセサーを通じてライブラリを参照します。ビルドファイルにバージョン番号を書かずに済むため、アップグレードを1か所で行えます。
// core/network/build.gradle.kts
dependencies {
implementation(platform(libs.compose.bom))
implementation(libs.retrofit)
}最大の禁忌: 循環依存
循環とは、モジュールAがBに依存し、Bが直接または別のモジュールを介してAに戻って依存する状態です。Gradleは循環依存があるとビルドを拒否します。また、これは設計上の問題も示しています。2つのモジュール間の境界が適切ではありません。
// :feature:cart -> implementation(project(":feature:checkout"))
// :feature:checkout -> implementation(project(":feature:cart"))
//
// Gradle error:
// Circular dependency between the following tasks:
// :feature:cart:compile -> :feature:checkout:compile -> :feature:cart:compile循環依存を解消する
循環依存を解消するには、共有部分を両方が依存できる下位のモジュールへ切り出します。2つの機能が互いのデータを必要とする場合、その共有契約はどちらかの機能ではなく、コアに配置します。
これにより下向きの流れが復元されます。両方の機能がコアを下向きに参照し、コアが上位へ戻って参照することはありません。
// Before: cart <-> checkout (cycle)
// After: cart -> :core:order <- checkout
// core/order/OrderContract.kt
data class Order(val items: List<CartItem>, val total: Double)
// feature/cart -> implementation(project(":core:order"))
// feature/checkout-> implementation(project(":core:order"))逆転: 抽象に依存する
低レベルのモジュールが、より上位に存在する振る舞いを必要とすることがあります。上位へ依存する代わりに、低レベルのモジュールでinterfaceを定義し、高レベルのモジュールから依存性注入を通じて実装を提供します。これが依存性逆転です。
// core/analytics defines the contract
interface AnalyticsLogger {
fun log(event: String)
}
// :app provides the real implementation and injects it down
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {
@Provides
fun logger(impl: FirebaseAnalyticsLogger): AnalyticsLogger = impl
}グラフを可視化して守る
Gradleにモジュールグラフを出力または描画させたり、禁止されている依存関係(たとえばコアモジュールから機能モジュールへの依存)が現れた場合にビルドを失敗させるテストを追加したりできます。module-graph pluginのようなツールを使えば、図を自動生成できます。
# Print the project structure
./gradlew projects
# Inspect why :feature:home pulls in a library
./gradlew :feature:home:dependencies --configuration debugRuntimeClasspathクリーンで非循環な例
こちらが健全なグラフです。上から下へ読んでください。矢印が上向きに戻ることはなく、2つのモジュールが互いを参照することもありません。これが目指す構成です。
// :app
// -> :feature:home -> :core:data -> :core:network -> :core:model
// -> :feature:profile -> :core:data -> :core:database -> :core:model
// -> :core:designsystem
//
// Every path ends at :core:model. No cycles. Builds in parallel.理解度チェック
:core:network モジュールでは、OkHttpClient を内部でのみ使用しており、公開関数のシグネチャには一切現れません。OkHttpの依存関係を宣言するには、どのGradle設定を使うべきでしょうか。
まとめ: モジュール依存関係を管理する
モジュールグラフを健全に保つ方法を学びました。
project(":path")を使ってモジュール依存関係を宣言します。- 基本は
implementationとし、公開範囲に現れる型に対してのみapiを使います。 - バージョンはversion catalog(
libs.versions.toml)に一元化します。 - 循環依存は決して作りません。Gradleに拒否されるため、共有コードを下位へ切り出すか、interfaceで依存関係を逆転させて解消します。
次は、機能同士を結合させずにナビゲーションで接続する方法を学びます。
よくある質問
「モジュール依存関係の管理」レッスンは無料ですか?
はい。「モジュール依存関係の管理」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Android Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Android Academyコースには全4レッスンが含まれています。
「モジュール依存関係の管理」で何を学びますか?
依存関係グラフを整理し、非循環に保ちます。 ブラウザで直接実行するハンズオンコードでAndroid Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Android Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAndroid Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「モジュール依存関係の管理」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAndroid Academyレッスンでコードを書いて実行できますか?
はい。すべてのAndroid Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- モジュール化する理由
- FeatureモジュールとCoreモジュール
- モジュール依存関係の管理
- モジュール間のナビゲーション