Kotlin MultiplatformSeniorSystem design
Каковы основные сложности при внедрении KMP в существующий проект?
Начинайте с изолированного shared-модуля под один сценарий — например, слой данных. Главный риск: общий модуль без явного владельца замедляет обе команды и накапливает платформенные утечки.
Основные сложности при внедрении KMP
Внедрение KMP в живой проект отличается от старта с нуля: нужно сохранить работоспособность Android и iOS, не сломать CI и договориться с двумя командами об ownership общего кода. Типичные сложности делятся на технические, организационные и операционные.
Технические сложности
- Gradle-конфигурация — добавление KMP-плагина меняет структуру source set-ов; существующие Android-модули нужно мигрировать на
androidTarget()вместоcom.android.library. - Platform-specific зависимости — библиотеки без KMP-артефактов (OkHttp, некоторые Room API) требуют expect/actual-обёрток или замены на multiplatform-аналоги (Ktor, SQLDelight).
- Swift/ObjC interop — публичный API из commonMain транслируется через Kotlin/Native; suspend-функции, generics и sealed-классы требуют дополнительного слоя адаптеров для Swift.
- Параллельные сборки — XCFramework нужно генерировать в CI, что увеличивает время сборки и требует macOS-агентов.
Стратегия внедрения
Начинайте с одного изолированного сценария: например, слой сетевых запросов или локального кэша. Убедитесь, что shared-модуль не тянет за собой Android SDK.
// build.gradle.kts (shared-модуль)
kotlin {
androidTarget()
iosArm64()
iosSimulatorArm64()
sourceSets {
commonMain.dependencies {
implementation(libs.ktor.client.core)
implementation(libs.kotlinx.coroutines.core)
}
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
}
}
// commonMain: контракт без платформенных деталей
class TokenRepository(private val api: AuthApi, private val store: TokenStore) {
suspend fun refreshIfNeeded(): String {
val cached = store.read() ?: return fetchAndStore()
return if (cached.isExpired()) fetchAndStore() else cached.value
}
private suspend fun fetchAndStore(): String {
val token = api.refresh()
store.write(token)
return token.value
}
}
Организационные сложности
- Ownership — без явного владельца shared-модуль деградирует: Android-команда добавляет Android-зависимости, iOS-команда не может их заменить.
- Контракт с iOS — нужно договориться об API-стабильности до публикации XCFramework; любой breaking change требует синхронного релиза на обеих платформах.
- Обучение — iOS-разработчики должны понимать Kotlin-идиомы (Flow, sealed class), Android-разработчики — ограничения Kotlin/Native.
Операционные сложности
- CI должен собирать и тестировать все targets; без этого проблемы обнаруживаются только на устройстве.
- Rollback-план: если shared-код ломает iOS, нужна возможность быстро вернуться к нативной реализации без релиза Android.
- Observability: логи и краш-репорты должны коррелировать с версией XCFramework, а не только с версией приложения.
Подводные камни
- Добавление Android-зависимости в commonMain вместо androidMain — компилируется, но ломает iOS-сборку в runtime.
- Использование
Dispatchers.Mainв common-коде без expect/actual — на iOS Main dispatcher не инициализирован автоматически до вызоваMainScope(). - Публикация suspend-функций напрямую в Swift без обёртки — iOS-код получает нечитаемые callback-и через
KotlinUnit. - Отсутствие hermetic-тестов для common-модуля — ошибки в бизнес-логике обнаруживаются только при запуске на устройстве.
- Один XCFramework на весь проект — любое изменение требует пересборки и обновления SPM/CocoaPods-зависимости в iOS-проекте.
- Игнорирование
iosSimulatorArm64при добавлении нового target — разработчики на Apple Silicon не могут запустить симулятор. - Shared ViewModel с Android lifecycle —
ViewModelиз Jetpack недоступен в commonMain; нужен KMP-совместимый аналог (decompose, moko-mvvm). - Миграция всего проекта сразу — резко увеличивает зону риска; iterative migration по модулям безопаснее.
Common mistakes
- Объяснять «внедрение KMP в существующий проект» только как синтаксис и не описывать поведение runtime/compiler.
- Игнорировать важный риск: Главный риск - создать общий модуль, который замедляет обе команды и не имеет ответственного владельца.
- Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.
What the interviewer is testing
- Формулирует суть темы «внедрение KMP в существующий проект» своими словами и связывает ее с кодом.
- Называет механизм: Нужны границы ownership, CI для всех targets, контракт с iOS-командой, observability, rollback и migration plan.
- Видит production-последствие: Главный риск - создать общий модуль, который замедляет обе команды и не имеет ответственного владельца.