← Назад к вопросам
Как организовать многомодульность приложения
2.2 Middle🔥 111 комментариев
#Архитектура и паттерны#Многомодульность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Организация многомодульности в Android приложении
Это архитектурный вопрос, который показывает опыт работы с крупными проектами. Правильная модульность улучшает сборку, тестирование и командную работу.
Преимущества многомодульности
- Быстрая сборка — изменение в одном модуле не пересобирает весь проект
- Изоляция — чистые границы между частями приложения
- Параллельная разработка — разные команды на разных модулях
- Переиспользование — выделение common библиотек
- Тестирование — меньше зависимостей = проще тестировать
Структура многомодульного проекта
app-project/
├── app/ # Главный модуль (точка входа)
├── feature/ # Feature модули
│ ├── feature-auth/ # Авторизация
│ ├── feature-profile/ # Профиль
│ └── feature-feed/ # Лента
├── domain/ # Бизнес-логика (не зависит от Android)
│ ├── user-domain/
│ └── post-domain/
├── data/ # Данные и API
│ ├── user-data/
│ ├── post-data/
│ └── shared-data/
├── core/ # Общие компоненты
│ ├── core-common/ # Утилиты, расширения
│ ├── core-network/ # HTTP клиент
│ ├── core-database/ # БД
│ ├── core-ui/ # Переиспользуемый UI код
│ └── core-testing/ # Тестовые утилиты
└── build-logic/ # Общие build скрипты
└── convention/
Стратегия разделения: Clean Architecture по модулям
feature-auth/
├── src/main/
│ └── java/com/example/auth/
│ ├── presentation/ # UI, ViewModel
│ ├── domain/ # UseCases, Entities
│ └── data/ # Repository, API
└── build.gradle.kts
Build.gradle.kts для модуля
plugins {
id("com.android.library")
kotlin("android")
}
android {
namespace = "com.example.feature.auth"
compileSdk = 35
defaultConfig {
minSdk = 24
}
}
dependencies {
// Зависимости на core модули (вверх по иерархии)
implementation(project(":core:core-common"))
implementation(project(":core:core-network"))
implementation(project(":core:core-ui"))
// НЕ зависим от других feature модулей (горизонтальные зависимости запрещены)
// implementation(project(":feature:feature-profile")) // ❌ Неправильно
implementation(libs.androidx.lifecycle)
implementation(libs.kotlin.coroutines)
}
Правило зависимостей
app → feature-* → domain → data → core
Правила:
✅ app может зависить от feature
✅ feature может зависить от core, domain, data
❌ feature НЕ может зависить от feature
❌ core НЕ может зависить от feature
Navigation между модулями (важно!)
Если feature-auth хочет открыть feature-profile, они не должны знать друг о друге:
// В feature-auth (не знает про feature-profile)
class LoginViewModel : ViewModel() {
fun onLoginSuccess() {
// Используем общий intent для навигации
val intent = Intent().apply {
// Динамическая инициализация через navigation library
action = "com.example.action.OPEN_PROFILE"
}
startActivity(intent)
}
}
// В feature-profile (AndroidManifest.xml)
<activity android:name=".ProfileActivity">
<intent-filter>
<action android:name="com.example.action.OPEN_PROFILE" />
</intent-filter>
</activity>
Модернее — через Navigation Compose или routing library:
// Navigation модуль для связи
interface ProfileNavigator {
fun openProfile(userId: String)
}
// feature-auth получает реализацию через DI
class LoginViewModel(
private val profileNavigator: ProfileNavigator
) : ViewModel() {
fun onLoginSuccess(userId: String) {
profileNavigator.openProfile(userId)
}
}
Shared модули (core)
// core-common — чистый Kotlin, без Android
object DateUtils {
fun formatDate(date: LocalDateTime): String { ... }
}
// core-ui — Android компоненты, переиспользуемые везде
Composable Button(text: String) { ... }
// core-network — HTTP клиент для всех
val httpClient = OkHttpClient { ... }
// core-database — Room БД для всех
@Entity
data class UserEntity { ... }
Domain слой (бизнес-логика)
Критично: Domain модули НЕ должны знать про Android. Это чистый Kotlin.
// user-domain (no Android dependencies)
data class User(
val id: String,
val name: String,
val email: String
)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User): Unit
}
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(userId: String): User {
return repository.getUser(userId)
}
}
Data слой (Repository реализация)
// user-data
class UserRepositoryImpl(
private val api: UserApi,
private val database: UserDao
) : UserRepository {
override suspend fun getUser(id: String): User {
return try {
api.getUser(id).toDomain() // Сеть
} catch (e: Exception) {
database.getUser(id).toDomain() // Локальный кеш
}
}
}
Dependency Injection (Hilt)
@Module
@InstallIn(ActivityComponent::class)
object AuthModule {
@Provides
fun provideLoginViewModel(
useCase: GetUserUseCase,
profileNavigator: ProfileNavigator
): LoginViewModel = LoginViewModel(useCase, profileNavigator)
}
Тестирование модулей
// user-domain/test
class GetUserUseCaseTest {
private val mockRepository: UserRepository = mockk()
private val useCase = GetUserUseCase(mockRepository)
@Test
fun `getUser returns user data` = runTest {
// Мокируем только интерфейс, не зависим от реализации
coEvery { mockRepository.getUser("123") } returns
User("123", "John", "john@example.com")
val user = useCase("123")
assertEquals("John", user.name)
}
}
Порядок разработки новой фичи
- domain модуль — UseCase, Repository интерфейс, Entities
- data модуль — Repository реализация, API, DB
- feature модуль — ViewModel, UI, связь через DI
- Тесты — на domain (независимо), на feature (с моками data)
На собеседовании
Покажите понимание:
- Иерархия зависимостей — что может зависить от чего
- Разделение ответственности — domain, data, feature
- Domain = чистый Kotlin — без Android
- DI для навигации — feature не зависят друг от друга
- Быстрая сборка — почему многомодульность помогает
- Тестирование — проще тестировать domain отдельно
Правильная модульность — признак архитектурной зрелости.