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

Какие знаешь способы проектирования архитектуры KMP-приложения?

2.7 Senior🔥 61 комментариев
#Архитектура и паттерны#Многомодульность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектурные подходы для KMP-приложения (Kotlin Multiplatform)

Проектирование архитектуры Kotlin Multiplatform (KMP) приложения требует баланса между общими бизнес-логикой для всех целевых платформ (Android, iOS, Desktop, Web) и специфичными реализациями для каждой из них. Вот основные подходы и практики, которые я использую:

1. Стратегия разделения модулей: общие (shared) и платформные (platform)

Ключевой принцип — четкое разделение кода на common (общий) и platform-specific (платформно-специфичный) модули.

// Пример структуры проекта в Gradle:
// build.gradle.kts (общий модуль)
kotlin {
    android()
    ios()
    
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(kotlin("stdlib"))
                // Общие KMP-библиотеки, например, korlibs, ktor-client
            }
        }
        val androidMain by getting {
            dependencies {
                // Android-специфичные зависимости
            }
        }
        val iosMain by getting {
            dependencies {
                // iOS-специфичные зависимости
            }
        }
    }
}
  • Общий модуль (commonMain) содержит:
    - Бизнес-логику (Use Cases, Interactors).
    - Модели данных (Data Classes, Entities).
    - Репозитории (интерфейсы или абстрактные классы).
    - Источники данных (интерфейсы для работы с сетью или базой данных).
    - Утилиты и расширения (pure Kotlin код).

  • Платформные модули (androidMain, iosMain и др.) реализуют:
    - Конкретные реализации репозиториев (например, с использованием `OkHttp` на Android и `NSURLSession` на iOS).
    - UI-компоненты и навигацию (полностью платформно-специфичные).
    - Интеграцию с платформными API (уведомления, файловая система, Bluetooth).

2. Архитектурные паттерны в общем модуле

Для организации кода в common модуле я применяю проверенные паттерны, адаптированные для KMP.

MVVM (Model-View-ViewModel) или MVI (Model-View-Intent)

// commonMain: Пример ViewModel (или Presenter/Controller)
class ProductsViewModel(
    private val productsRepository: ProductsRepository
) {
    private val _state = MutableStateFlow(ProductsState.Loading)
    val state: StateFlow<ProductsState> = _state.asStateFlow()
    
    fun loadProducts() {
        viewModelScope.launch {
            _state.value = ProductsState.Loading
            try {
                val products = productsRepository.getAll()
                _state.value = ProductsState.Success(products)
            } catch (e: Exception) {
                _state.value = ProductsState.Error(e.message)
            }
        }
    }
}

// Общие State и Model
sealed class ProductsState {
    data class Success(val products: List<Product>) : ProductsState()
    data class Error(val message: String?) : ProductsState()
    object Loading : ProductsState()
}
  • ViewModel остается в общем модуле, но использует корутины (CoroutineScope) через kotlinx.coroutines. Для iOS потребуется дополнительная адаптация (MainDispatcher).
  • State определяется как sealed классы или интерфейсы в common модуле.

Clean Architecture + многослойность

Я разделяю общий модуль на слои:

  • Domain Layer: Entities, Use Cases (Interactors), интерфейсы Repository.
  • Data Layer: Реализации Repository, Sources (Network, Database), DTOs.
  • Presentation Layer: ViewModels, States, Events.
// Пример UseCase в Domain Layer
class GetProductsUseCase(
    private val repository: ProductsRepository
) {
    suspend operator fun invoke(): Result<List<Product>> {
        return try {
            Result.success(repository.getAll())
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

3. Dependency Injection (DI) в KMP

Управление зависимостями критично для тестирования и замены платформных реализаций.

  • Manual DI (ручная инъекция): простой подход для небольших проектов, где зависимости создаются вручную в платформных модулях.
  • KMP-библиотеки для DI: такие как Koin (есть KMP версия) или Dagger (через общие интерфейсы, но реализации на платформах). Однако, нужно учитывать, что не все библиотеки полноценно поддерживают KMP.
// Пример с Koin для common модуля
val commonModule = module {
    single<ProductsRepository> { ProductsRepositoryImpl(get()) }
    factory { ProductsViewModel(get()) }
}

// На Android: androidApp.module инициализирует Koin с commonModule
// На iOS: нужно использовать Koin в Swift через Kotlin/Native export

4. Работа с платформно-специфичным кодом: ожидания (expect) и реализации (actual)

Kotlin Multiplatform предоставляет механизм expect/actual для объявления общих интерфейсов (ожиданий) и их платформных реализаций.

// commonMain: объявляем expect класс или функцию
expect class PlatformFileSystem {
    fun readFile(path: String): String
}

// androidMain: реализация для Android
actual class PlatformFileSystem {
    actual fun readFile(path: String): String {
        // Используем Android API (java.io.File)
        return File(path).readText()
    }
}

// iosMain: реализация для iOS
actual class PlatformFileSystem {
    actual fun readFile(path: String): String {
        // Используем iOS API (Swift/Objective-C через Kotlin/Native interop)
        // Здесь может быть C-код или вызов в Swift
        return ""
    }
}

Этот подход идеально подходит для:

  • Управления файловой системой.
  • Работы с сетью (создание HTTP-клиента).
  • Локальных хранилищ (Keychain на iOS, SharedPreferences на Android).
  • Получения информации о устройстве (версия OS, разрешение дисплея).

5. Тестирование и инструменты

Архитектура должна позволять тестирование:

  • Unit-тесты в common модуле: тестируем бизнес-логику, Use Cases, репозитории (с моками).
  • Интеграционные тесты на платформах: проверяем, что платформные реализации корректно работают с common кодом.

Инструменты:

  • Gradle Multiplatform Configuration: настройка sourceSets, зависимостей.
  • Kotlin/Native и Swift interoperability: важно для iOS стороны (использование cinterop, генерация Objective-C заголовков).
  • CI/CD: сборка и тестирование всех целевых платформ автоматически.

Выводы и рекомендации

Выбор архитектуры зависит от масштаба проекта:

  • Для простых приложений с фокусом на бизнес-логике: MVVM + expect/actual для ключевых платформных операций.
  • Для комплексных продуктов: Clean Architecture с четким разделением слоев, DI библиотека (Koin) и возможно MVI для более строгого управления состоянием.

Главное — максимально изолировать общую логику от платформных деталей, используя интерфейсы и абстракции, чтобы обеспечить легкую замену и тестирование. Также важно продумать навигацию и UI, которые остаются полностью платформными, но могут быть связаны с общими ViewModel через общие State и Events.

Какие знаешь способы проектирования архитектуры KMP-приложения? | PrepBro