Урок по треку Архитектура мобильных приложений 6 мин

Модульность мобильного приложения

Зачем делить на модули, feature и core, api и impl, графы зависимостей и скорость сборки.

Когда в приложении больше десятка разработчиков и сотни тысяч строк, без модулей сборка занимает минуты, а изменения в одной фиче ломают другую. На middle и senior собесах спрашивают, как делить приложение и как не превратить модули в новый клубок.

Зачем

  • Скорость сборки: изменился модуль, пересобираются он и его потребители, а не всё.
  • Границы: модуль открывает публичный API и прячет реализацию.
  • Параллельная работа: команды владеют своими модулями.
  • Переиспользование: модуль можно подключить в другое приложение или расширение.

Размер приложения и скорость запуска модульность сама по себе не улучшает. На iOS десятки динамических фреймворков даже замедляют холодный старт.

Типы модулей

:app                    сборка и склейка зависимостей
:feature:cart:api       интерфейсы и точки входа фичи
:feature:cart:impl      реализация: экраны, view model, data
:core:network           сетевой клиент
:core:database          хранилище
:core:designsystem      UI-компоненты
:core:analytics
:testing                общие фейки и утилиты тестов

Правила зависимостей

  • Фича зависит от core и от api других фич, но никогда от их impl.
  • Циклов нет (Gradle и SPM их и не допустят).
  • Склеивает реализации app-модуль через DI.

Переход из корзины на карточку товара:

// :feature:product:api
interface ProductNavigator { fun openProduct(id: String) }

// :feature:cart:impl зависит только от product:api
class CartViewModel(private val productNavigator: ProductNavigator)

Если два модуля зависят друг от друга, граница проведена неверно: общую часть выносят в третий модуль или вводят интерфейс в нижнем модуле.

Почему сборка может не ускориться

Если все модули зависят от огромного core, который правят каждый день, любое изменение пересобирает всех. Помогают:

  • маленькие стабильные core-модули;
  • разделение api/impl;
  • в Gradle implementation вместо api там, где зависимость не торчит в публичных сигнатурах: тогда её изменения не пересобирают потребителей;
  • контроль графа на CI.

Платформенные детали

iOS: локальные Swift-пакеты (SPM) или фреймворки в Xcode-проекте, генераторы Tuist или XcodeGen, в больших компаниях Bazel. Модули линкуют статически, динамическими оставляют только код, общий для приложения и расширений.

Android: Gradle-модули, convention plugins для общей конфигурации, version catalog для версий. Домен удобно держать в чистых Kotlin-модулях, они собираются быстрее Android-библиотек. Dynamic Feature Modules позволяют загружать части приложения по требованию.

Flutter: пакеты в монорепозитории (melos). React Native: пакеты в монорепозитории (workspaces).

Демо-приложения

Для крупной фичи делают маленькое приложение, которое собирает только её с фейковыми зависимостями. Итерации быстрее, а заодно проверяется независимость: если фичу нельзя запустить отдельно, связи слишком сильные.

Модуляризация монолита

  1. Измерить время сборки и граф зависимостей.
  2. Вынести инфраструктуру в core.
  3. Новые фичи писать сразу модулями.
  4. Старые выносить по одной, начиная с самых часто меняющихся: найти границы, разорвать зависимости, описать API, перенести код, подключить через DI.
  5. Проверять правила графа на CI, владельцев модулей закрепить в CODEOWNERS.

Что спросят на собесе

  • Зачем делить приложение на модули и какие бывают модули?
  • Как организовать переход между фичами в разных модулях?
  • Почему после модуляризации сборка может не ускориться?
  • Статические или динамические модули на iOS?
  • Как модуляризировать монолит без остановки разработки?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.