Модульность — это принцип, при котором приложение собирается из независимых модулей, каждый из которых отвечает за одну функциональность. По данным Android Developers, разделение на модули ускоряет сборку за счёт параллельной компиляции и позволяет командам работать над разными частями приложения независимо. Модульная архитектура стала стандартом для крупных мобильных проектов с десятками разработчиков.
Главное
Модульность — это способ организации кода, при котором приложение состоит из слабосвязанных модулей, каждый из которых предоставляет строго определённую функциональность через публичный интерфейс. В отличие от монолитной архитектуры, где все классы находятся в одном проекте, модульный подход разделяет код на физически независимые единицы сборки.
Основная цель модульности — управление сложностью. Разработчик может сфокусироваться на одном модуле, не держа в голове всю кодовую базу. Каждый модуль имеет свою зону ответственности и может разрабатываться, тестироваться и деплоиться независимо от остальных. Это особенно ценно в проектах с 10+ разработчиками, где параллельная работа над монолитом приводит к частым конфликтам слияния.
Важно отличать модульность от слоистой архитектуры. Слои (Presentation, Domain, Data) делят код по техническому признаку, а модули — по функциональному. Модуль «Профиль пользователя» может содержать свои собственные слои внутри себя. На практике модульный подход и слоистая архитектура комбинируются: каждый модуль имеет свою трёхслойную структуру.
Feature-модули — самый популярный вид модулей. Каждый экран или группа связанных экранов выделяется в отдельный модуль: Onboarding, Profile, Settings, Feed. Feature-модуль содержит всё необходимое для работы функции: UI, бизнес-логику, data-слой. Границы модуля защищены — другие функции не могут получить доступ к его внутренним классам.
Core-модули содержат общую инфраструктуру: работу с сетью, базой данных, аналитикой, дизайн-системой. Они не зависят от feature-модулей, но feature-модули зависят от них. Такое разделение гарантирует, что изменение аналитического SDK не затронет сетевой слой, и наоборот. Core-модули переиспользуются между фичами без дублирования кода.
Shared-модули содержат код, который используется несколькими фичами: модели данных, утилиты, константы, кастомные View. Основная проблема shared-модулей — риск превращения в свалку («misc module»), где со временем скапливается разнородный код. Правило: shared-модуль должен иметь чёткую тематику, например «shared-ui» или «shared-models».
В Android shared-модули часто выделяют в библиотеки с префиксом lib: lib-network, lib-database, lib-ui-components. В iOS те же функции выполняют внутренние Swift Packages внутри Workspace. На практике команды ограничивают количество shared-модулей 3–5, чтобы не создавать избыточную сеть зависимостей, усложняющую сборку.
Отдельные test-модули позволяют запускать тесты только для изменённого модуля, не прогоняя всю тестовую базу. Это сокращает время CI/CD пайплайна с часов до минут. Module-модули обеспечивают SoC на уровне сборки: модуль сетевого слоя не может случайно импортировать UI-библиотеки в тестах.
Каждый модуль должен иметь чётко определённый публичный API. В Android это достигается через access modifiers и api vs implementation в Gradle. В iOS — через public/internal модификаторы доступа и managed dependencies через Package.swift. Сокращение видимости до минимально необходимой — ключевая практика модульного дизайна.
Gradle поддерживает модульную архитектуру нативно: каждый модуль — это отдельная единица сборки со своим build.gradle файлом. Android проекты используют комбинацию application модуля (app) и нескольких library модулей. Библиотечные модули не могут быть запущены как приложение, но могут публиковаться как AAR в репозиторий.
Ключевая фича Gradle — параллельная сборка независимых модулей. Если modules A, B и C не зависят друг от друга, Gradle компилирует их одновременно, используя все ядра процессора. В проектах с 20+ модулями это сокращает полную сборку с 15 до 3–5 минут. Incremental сборка изменённого модуля занимает секунды.
Gradle предоставляет два типа зависимостей между модулями: api (транзитивные) и implementation (нетранзитивные). Разница критически важна для модульности: implementation скрывает транзитивные зависимости от потребителей модуля. Если модуль :profile использует :networking через implementation, потребители :profile не знают о :networking и не могут к нему обратиться.
// settings.gradle — декларация модулей
include ':app'
include ':feature:profile'
include ':feature:settings'
include ':core:network'
include ':core:database'
// build.gradle feature/profile — зависимости модуля
dependencies {
implementation project(':core:network')
implementation project(':core:database')
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
}
Код показывает структуру модульного Android-проекта. Settings.gradle перечисляет все модули, а build.gradle каждого feature-модуля указывает только те core-модули, которые ему нужны. Build system автоматически разрешает транзитивные зависимости и собирает модули в правильном порядке.
Swift Package Manager (SPM) — стандартный инструмент модульности в iOS с 2019 года. SPM позволяет разбивать приложение на Swift Packages, каждый из которых может быть library или executable. Package определяет модули (targets) и их зависимости через Package.swift. SPM интегрирован в Xcode и не требует дополнительных инструментов.
CocoaPods остаётся основным менеджером зависимостей для сторонних библиотек. Podfile и Podspec задают модульную структуру, а CocoaPods генерирует workspace с отдельными pod-проектами. Для собственной модульности проекта команды всё чаще выбирают SPM, так как он встроен в Xcode и не требует установки.
В iOS модульности важную роль играет access control: public, package, internal, fileprivate и private. Модуль публикует только те типы, которые должны быть доступны другим модулям. Внутренние детали реализации скрыты за internal и private модификаторами. Это предотвращает появление скрытых зависимостей между модулями.
// Package.swift — модульная структура iOS проекта
let package = Package(
name: "MyApp",
platforms: [.iOS(SupportedPlatform.iOSVersion.v17)],
products: [
.library(name: "ProfileFeature", targets: ["ProfileFeature"]),
.library(name: "NetworkCore", targets: ["NetworkCore"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
],
targets: [
.target(name: "ProfileFeature", dependencies: ["NetworkCore"]),
.target(name: "NetworkCore", dependencies: ["Alamofire"]),
]
)
Package.swift объявляет два library-продукта: ProfileFeature и NetworkCore. ProfileFeature зависит от NetworkCore, но не знает о существовании Alamofire — тот скрыт внутри NetworkCore. Такая изоляция — прямое применение SoC на уровне модулей: изменения в HTTP-клиенте не требуют перекомпиляции ProfileFeature.
Основное преимущество модульности — скорость разработки. Команды работают параллельно над разными модулями без конфликтов в коде. CI/CD пайплайн собирает только изменённые модули и запускает только их тесты. Время фидбека сокращается, а частота релизов растёт. Spotify, Uber и Airbnb публиковали кейсы миграции на модульную архитектуру с улучшением метрик в 2–3 раза.
Второе преимущество — изоляция ошибок. Баг в модуле Profile не влияет на модуль Payments, если между ними нет прямых зависимостей. Это особенно важно в приложениях с high-risk функцией (платежи, медицинские данные), где ошибка в несвязанном экране не должна блокировать релиз критического функционала.
Главная сложность — управление зависимостями. При неправильном проектировании возникает граф модулей, где изменение одного модуля каскадно пересобирает десятки других. Решение — следовать правилу ацикличности: граф зависимостей модулей должен быть направленным ациклическим графом (DAG). Инструменты вроде Gradle Module Graph Assert помогают обнаружить циклы на этапе сборки.
Вторая сложность — рост времени первоначальной настройки. Создание модульной архитектуры требует больше времени на этапе инициализации проекта. Мелкие проекты с 1–3 разработчиками могут не получить выгоды от модульности, затрачивая время на поддержку границы модулей без реальной потребности в распараллеливании. Решение — начинать с монолита и выделять модули по мере роста команды.
Feature-first подход группирует модули по функциональности: каждый экран или группа экранов становится отдельным модулем. Layer-first подход делит код по техническому признаку: отдельные модули для UI, бизнес-логики и данных. На практике большинство команд выбирают feature-first с core-модулями — это даёт лучшую изоляцию и понятную навигацию по проекту.
Выбор между подходами зависит от размера команды и предсказуемости функциональности. Если вы точно знаете, какие экраны будут в проекте, feature-first позволяет каждому разработчику отвечать за свой модуль. Если функциональность часто меняется и пересекается между экранами, layer-first даёт больше гибкости при переиспользовании кода между разными фичами.
Часто задаваемые вопросы
Оптимальное количество зависит от размера проекта и команды. Для команды из 5 человек достаточно 6–10 модулей. Для 20+ разработчиков — 20–40 модулей. Правило: модуль должен быть достаточно мал, чтобы один разработчик понимал его целиком, и достаточно велик, чтобы не создавать избыточную сеть зависимостей.
Правильная модульность ускоряет сборку за счёт параллельной компиляции и кэширования. Но избыточное количество модулей с плотными зависимостями замедляет сборку — Gradle и Xcode тратят время на разрешение графа. Ключ к быстрой сборке — минимизация транзитивных зависимостей и соблюдение ацикличности.
Да, но итеративно. Начните с выделения core-модулей (сеть, база данных), затем выносите фичи по одной. Используйте feature flags, чтобы включать новый модульный код параллельно со старым монолитным. Полная миграция крупного приложения занимает от 3 до 12 месяцев.
Модули — это единицы компиляции внутри одного приложения. Микросервисы — отдельные процессы, работающие на разных серверах. Модули разделяют код, микросервисы разделяют runtime. В мобильной разработке часто используется термин «microapps» как гибрид: feature-модули, которые могут запускаться как самостоятельные приложения.
Каждый модуль имеет свои Unit-тесты, запускаемые независимо. Integration-тесты проверяют взаимодействие между модулями. UI-тесты покрывают feature-модули с mock-данными. Модульная архитектура упрощает тестирование: мокнуть зависимость другого модуля проще, чем мокнуть часть монолита.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также