0Pricing
Android Academy · Урок

Управление зависимостями модулей

Поддерживайте чистый граф без циклов

«Управление зависимостями модулей» — бесплатный урок Android Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 4 уроков всего.

Граф зависимостей

Каждый модуль объявляет, от каких других модулей он зависит. Вместе они образуют граф зависимостей. Здоровый граф — это DAG (ориентированный ациклический граф): зависимости направлены в одну сторону и никогда не образуют цикл.

В этом уроке вы научитесь аккуратно объявлять зависимости, выбирать правильную конфигурацию Gradle, совместно использовать версии и предотвращать циклы.

Объявление зависимости модуля

Добавьте зависимость от другого модуля с помощью project(":path:to:module") внутри блока dependencies. Путь повторяет структуру папок и соответствует тому, что объявлено в 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: зависимость повторно раскрывается (транзитивно). Используйте его только тогда, когда ваши открытые типы происходят из этой зависимости.

Почти всегда отдавайте предпочтение 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 изменение распространяется на каждого транзитивного потребителя.

Практическое правило: добавляйте зависимость в api, только если она встречается в открытых типах модуля (типах возвращаемых значений и открытых параметрах). В противном случае используйте implementation.

// Public -> needs api
fun observeUser(): Flow<User>   // Flow and User leak out

// Internal -> implementation is enough
private val client: OkHttpClient // never exposed

Централизация версий: каталог версий

В модулях не хочется повсюду повторять версии библиотек. Каталог версий Gradle (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" }

Использование каталога в модуле

Теперь модули обращаются к библиотекам через созданный аксессор libs. Если в файле сборки нет номеров версий, обновление выполняется в одном месте.

// core/network/build.gradle.kts
dependencies {
    implementation(platform(libs.compose.bom))
    implementation(libs.retrofit)
}

Главный грех: циклы

Цикл возникает, когда модуль A зависит от B, а B прямо или через цепочку снова зависит от A. Gradle отказывается собирать проект с циклической зависимостью. Это также указывает на проблему в проектировании: граница между двумя модулями выбрана неправильно.

// :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

Разрыв цикла

Чтобы разорвать цикл, выделите общую часть в модуль более низкого уровня, от которого смогут зависеть оба модуля. Если двум функциональностям нужны данные друг друга, общий контракт должен находиться в ядре, а не в одной из функциональностей.

Так восстанавливается направленный вниз поток: обе функциональности указывают на ядро, а ядро никогда не указывает обратно вверх.

// 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"))

Инверсия: зависимость от абстракций

Иногда модулю низкого уровня требуется поведение, расположенное выше. Вместо зависимости от верхнего уровня объявите интерфейс в нижнем модуле, а модуль верхнего уровня предоставит реализацию через внедрение зависимостей. Это и есть инверсия зависимостей.

// 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 автоматически создают диаграмму.

# Print the project structure
./gradlew projects

# Inspect why :feature:home pulls in a library
./gradlew :feature:home:dependencies --configuration debugRuntimeClasspath

Чистый ациклический пример

Вот здоровый граф. Читайте его сверху вниз: ни одна стрелка не указывает обратно вверх, и никакие два модуля не указывают друг на друга. Именно к этому следует стремиться.

// :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 только внутри себя — этот тип никогда не появляется в сигнатурах открытых функций. Какую конфигурацию Gradle следует использовать для объявления зависимости от OkHttp?

Итоги: управление зависимостями модулей

Вы научились поддерживать граф модулей в здоровом состоянии:

  • Объявляйте зависимости между модулями с помощью project(":path").
  • По умолчанию используйте implementation; применяйте api только для типов из открытого интерфейса модуля.
  • Централизуйте версии в каталоге версий (libs.versions.toml).
  • Никогда не создавайте циклы — Gradle отклоняет их; разрывайте их, вынося общий код вниз или выполняя инверсию с помощью интерфейсов.

Далее вы свяжете функциональности друг с другом через навигацию, не создавая жёсткой связанности.

Часто задаваемые вопросы

Урок «Управление зависимостями модулей» бесплатный?

Да — полный текст урока «Управление зависимостями модулей» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 4 уроков всего.

Чему я научусь в уроке «Управление зависимостями модулей»?

Поддерживайте чистый граф без циклов Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Android Academy?

Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Управление зависимостями модулей»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Android Academy?

Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Зачем нужна модульность
  2. Модули функций и ядра
  3. Управление зависимостями модулей
  4. Навигация между модулями
← Назад к Android Academy