← Назад к вопросам

Как организовать многомодульность приложения

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)
    }
}

Порядок разработки новой фичи

  1. domain модуль — UseCase, Repository интерфейс, Entities
  2. data модуль — Repository реализация, API, DB
  3. feature модуль — ViewModel, UI, связь через DI
  4. Тесты — на domain (независимо), на feature (с моками data)

На собеседовании

Покажите понимание:

  1. Иерархия зависимостей — что может зависить от чего
  2. Разделение ответственности — domain, data, feature
  3. Domain = чистый Kotlin — без Android
  4. DI для навигации — feature не зависят друг от друга
  5. Быстрая сборка — почему многомодульность помогает
  6. Тестирование — проще тестировать domain отдельно

Правильная модульность — признак архитектурной зрелости.