Реализация модульной архитектуры мобильного приложения
Monolith-приложение с 50+ экранами, где всё в одном модуле — это история про то, как команда из 8 разработчиков ждёт 4 минуты полной сборки на каждое изменение, и как фича одной команды ломает тесты другой потому что они оба импортируют один и тот же singleton. Модульная архитектура решает конкретно эти проблемы. Не «масштабируемость в будущем» — а медленная сборка и merge-конфликты прямо сейчас.
Как делим на модули
Стандартный подход — разделение по фичам с общим слоем:
:app — точка входа, DI-граф, навигация
:core:network — HTTP клиент, interceptors
:core:storage — БД, SharedPreferences/DataStore
:core:ui — общие компоненты, тема
:core:auth — токены, AuthInterceptor
:feature:profile — профиль пользователя
:feature:catalog — каталог товаров
:feature:checkout — оформление заказа
:feature:orders — история заказов
Каждый :feature:* модуль зависит только от :core:* и не знает о других фичах. Навигация между фичами — через абстракцию: NavigationManager в :core, которую каждая фича использует для перехода без прямого импорта другого feature-модуля. На Android — Navigation Component с NavGraph на уровне :app, deep links регистрируются в манифестах модулей. На iOS аналогично — Coordinator pattern или Router с protocol-based навигацией.
Android. Каждый :feature:* — отдельный Gradle-модуль (:feature:catalog → catalog/build.gradle.kts). Gradle Configuration Cache + Build Cache (~/.gradle/caches) радикально сокращают время пересборки: меняем только :feature:checkout — пересобирается только он. --configuration-cache в Gradle 8.x стабилен. Kapt → KSP: миграция annotation processor'ов с Kapt на KSP (Kotlin Symbol Processing) даёт +30–50% к скорости генерации кода.
iOS. Модули реализуются через Swift Packages (локальные) или как Framework targets в Xcode workspace. Swift Package Manager поддерживает локальные пакеты через path: в Package.swift. Workspace с несколькими проектами (Feature1.xcodeproj, Feature2.xcodeproj) — устаревший подход, новый — монорепо с локальными SPM пакетами + один основной .xcodeproj. Tuist или XcodeGen автогенерируют .xcodeproj из конфигурационных файлов, что исключает merge-конфликты в .pbxproj.
DI в модульной архитектуре
DI-граф не должен быть монолитным. На Android — Hilt с @InstallIn(SingletonComponent::class) для core-зависимостей и @InstallIn(ViewModelComponent::class) для feature-специфичных. Каждый feature-модуль объявляет свои @Module-классы, Hilt собирает граф в compile time. Это означает: если :feature:catalog не подключён в :app — его DI-модуль не компилируется и не влияет на сборку.
На iOS — ручной DI через Resolver/Swinject, или чистый factory-паттерн без DI-фреймворка. В модульном приложении DI-фреймворк на Swift менее критичен: зависимости передаются через инициализаторы, а Composition Root в :app собирает всё вместе.
Dynamic Delivery и опциональные модули
Android позволяет доставлять feature-модули по требованию через Play Feature Delivery (бывший Dynamic Delivery). Пользователь скачивает базовое приложение, тяжёлые фичи (AR-примерка, офлайн-карты) — при первом обращении. SplitInstallManager управляет загрузкой, SplitCompatActivity — активация. На iOS аналога нет — App Clips закрывают другую нишу.
Кейс. Ритейл-приложение: 6 команд, 80+ экранов, Android + iOS. До модуляризации: полная сборка Android — 7 минут, iOS — 11 минут. После разбивки на 14 модулей с настроенным Build Cache: инкрементальная сборка при изменении одного feature-модуля — 45–90 секунд. Merge-конфликты в общем коде снизились примерно втрое. Параллельная разработка фич без блокировок между командами.
Сроки
Модуляризация существующего приложения — трудоёмкий рефакторинг:
| Размер проекта | Ориентировочные сроки |
|---|---|
| 20–40 экранов, 1–2 команды | 4–8 недель |
| 50–100 экранов, 3–5 команд | 2–4 месяца |
| 100+ экранов, сложные зависимости | 4–8 месяцев |
Стоимость рассчитывается индивидуально после аудита архитектуры и зависимостей существующего кода.
Типичные ошибки при самостоятельной модуляризации
Circular dependencies. :feature:profile импортирует :feature:orders, :feature:orders импортирует :feature:profile. Gradle не соберёт — circular dependency. Решение: вынести общие модели в :core:domain или :shared:models, обе фичи зависят только от него.
Слишком мелкие модули. :core:extensions с 5 extension-функциями — overhead без выгоды. Рациональный минимум: модуль оправдан, если его можно менять независимо и он не зависит от модулей, которые меняются чаще.
Resource ID конфликты. На Android при объединении ресурсов нескольких модулей возникают коллизии имён. Конвенция: префикс модуля для всех ресурсов (catalog_item_card_background, не просто item_card_background). R class isolation через android.nonTransitiveRClass=true в gradle.properties — обязателен для больших проектов.







