Какой принцип разделения на модули на последнем многомодульном проекте?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Принципы модульной архитектуры в современном Android-проекте
В нашем последнем крупном проекте мы реализовали многоуровневую модульную архитектуру, основанную на принципах чистой архитектуры (Clean Architecture) и модульности по функциональности (Feature Modularization). Основная цель — достижение слабого связывания (loose coupling), тестируемости и масштабируемости.
Ключевые принципы организации модулей
1. Слоистая архитектура по типам ответственности
Мы разделили модули на три основных категории:
- Корневые модули (app, build-logic) — отвечают за сборку и точку входа
- Функциональные модули (features/) — инкапсулируют конкретные пользовательские сценарии
- Общие модули (core/, libs/) — предоставляют инфраструктурные компоненты
2. Строгое соблюдение направлений зависимостей
Мы использовали конфигурации Gradle для контроля зависимостей:
// build.gradle.kts feature-пользователя
dependencies {
implementation(projects.core.network)
implementation(projects.core.database)
implementation(projects.core.ui)
// Только внутренние зависимости модуля
implementation(projects.feature.user.domain)
// Внешние зависимости
implementation(libs.koin.android)
implementation(libs.coroutines.android)
// Запрещены зависимости на другие feature-модули напрямую
// compileOnly(projects.feature.settings) // Так нельзя!
}
Детальная структура модулей
Feature-модули организованы по принципу "один экран — одна фича":
features/
├── auth/
│ ├── presentation/ # UI-слой (Compose/View)
│ ├── domain/ # бизнес-логика
│ └── data/ # источники данных
├── profile/
├── settings/
└── feed/
Каждый feature-модуль имеет внутреннюю слоистую структуру:
// Пример структуры feature-модуля
class UserProfileViewModel(
private val getUserUseCase: GetUserUseCase, // domain-зависимость
private val userRepository: UserRepository // data-зависимость
) : ViewModel() {
// UI-логика
}
Core-модули разделены по типам функциональности:
core-network— REST API, WebSocket, сетевые утилитыcore-database— Room, миграции, DAOcore-ui— общие компоненты Compose, темы, ресурсыcore-utils— расширения, хелперы, утилиты
Критические принципы реализации
1. Изоляция сборки (Build Isolation)
Каждый модуль имеет независимые:
- Версионирование зависимостей через
libs.versions.toml - Конфигурацию сборки в
build.gradle.kts - Правила линтинга и статического анализа
2. Контроль видимости (Visibility Control)
// Разрешаем доступ только к интерфейсам из data-слоя
internal class UserRepositoryImpl : UserRepository {
// Реализация скрыта от других модулей
}
3. Навигация через зависимости времени выполнения
Вместо жестких зависимостей между фичами используем:
- Deep Links с параметризованными маршрутами
- Navigation Graph в корневом модуле
- Событийную шину для межмодульной коммуникации
Преимущества такого подхода
Ускорение сборки за счет:
- Кэширования независимых модулей
- Параллельной компиляции
- Инкрементальных сборок
Улучшение тестируемости благодаря:
- Изоляции бизнес-логики в domain-слоях
- Возможности мокать зависимости модулей
- Независимому тестированию фич
Эффективная работа команды за счет:
- Возможности параллельной разработки фич
- Четких контрактов между модулями
- Минимальных конфликтов в Git
Практические ограничения и решения
Мы столкнулись с ростом сложности конфигурации и решили это через:
- Convention plugins для стандартизации сборки
- Shared build logic в
build-logicмодуле - Автоматическую генерацию navigation-графа
Такой подход требует строгой дисциплины от команды, но окупается при масштабировании проекта и поддержке кодовой базы 5+ лет. Критически важно не создавать циклических зависимостей и соблюдать принцип инверсии зависимостей на всех уровнях архитектуры.