Архитектура мобильного приложения: подходы и паттерны
Если коротко: архитектура приложения — это то, как внутри разделена ответственность между частями кода: где живёт интерфейс, где бизнес-логика, где работа с данными. Пользователь её не видит, но именно она определяет, сможете ли вы быстро добавлять функции, легко чинить ошибки и менять команду без переписывания всего заново, — или приложение через год превратится в клубок, который страшно трогать. Поэтому архитектура — это не вопрос вкуса разработчика, а вопрос денег: цены изменений, поддержки и скорости развития.
Code Pilots с 2014 года проектирует и разрабатывает мобильные приложения, в том числе нагруженные продукты вроде VK Fest и платформы GigAnt, и по умолчанию работает на Flutter. Архитектуру мы выбираем под продукт, а не «по моде»: маленькому приложению сложная архитектура только мешает, большому без неё не выжить. Этот материал — про то, что такое архитектура приложения и почему это про деньги, как устроены слои, чем отличаются паттерны MVC, MVP, MVVM и MVI, что такое Clean Architecture и управление состоянием, и как выбрать подход под свой проект. Выбор технологии (Flutter или нативка) и платформенные детали разобраны в статьях про iOS и Android; здесь — про устройство кода внутри.
Зачем бизнесу архитектура, если её не видно
Соблазн понятный: пользователю всё равно, как устроен код, — лишь бы приложение работало. На старте плохая архитектура и правда не видна: первые экраны выходят даже быстрее, потому что никто не тратит время на «правильное» разделение. Расплата приходит позже, и она денежная.
Когда логики становится больше, а ответственность размазана — интерфейс, бизнес-правила и работа с данными свалены в одном месте (то, что разработчики называют «god object», бог-объект на всё), — происходит вот что. Каждое изменение задевает всё подряд: поправили одно, сломалось три. Новый разработчик неделями разбирается, что где лежит, вместо того чтобы писать функции. Покрыть код тестами невозможно, потому что логику не отделить от интерфейса, — и каждый релиз становится лотереей. В какой-то момент команда говорит фразу, которая стоит дороже всего: «это проще переписать с нуля».
Хорошая архитектура покупает ровно обратное: предсказуемую цену изменений (добавить функцию стоит примерно одинаково и на первом месяце, и на втором году), тестируемость (логика отделена и проверяема), быстрый онбординг (новый человек видит, где что лежит) и возможность менять команду без катастрофы. Это и есть актив, за который платят, — не «красивый код ради красоты», а защита от того, чтобы продукт не сгнил под собственным весом.
Слои приложения: presentation, domain, data
В основе почти любой вменяемой архитектуры лежит простая идея — разделение ответственности по слоям. Каждый слой занимается своим и не лезет в чужое:
- Presentation (представление) — всё, что связано с интерфейсом: экраны, их состояние, реакция на действия пользователя. Этот слой знает, как показать, но не знает, откуда берутся данные и по каким правилам считаются.
- Domain (домен, бизнес-логика) — сердце приложения: правила и сценарии, ради которых оно существует. «Можно ли оформить заказ», «как считается скидка», «что происходит при оплате». Этот слой не зависит ни от интерфейса, ни от того, откуда пришли данные.
- Data (данные) — работа с источниками: сеть, локальная база, кэш. Слой знает, откуда брать и куда сохранять, но не знает, как это покажут пользователю.
Смысл разделения в том, что слои можно менять независимо: переделали дизайн экрана — бизнес-логика не тронута; сменили API на бэкенде — интерфейс не заметил. Большинство архитектурных подходов — это разные способы провести эти границы аккуратно и не дать слоям перепутаться.
Паттерны представления: MVC, MVP, MVVM, MVI
Внутри слоя представления есть несколько устоявшихся паттернов — способов развести интерфейс и логику его поведения. Они развивались, решая проблемы предыдущих.
| Паттерн | Идея | Когда уместен |
|---|---|---|
| MVC (Model-View-Controller) | Классика: модель, представление, контроллер | Быстрый старт, простые экраны; плохо масштабируется |
| MVP (Model-View-Presenter) | Логику выносят в Presenter, View — пассивна | Когда нужна тестируемость, но без реактивности |
| MVVM (Model-View-ViewModel) | ViewModel хранит состояние, интерфейс обновляется автоматически | Баланс скорости и поддержки — дефолт для большинства приложений |
| MVI (Model-View-Intent) | Однонаправленный поток данных, неизменяемое состояние | Сложные UI-состояния, где важна предсказуемость |
На практике MVVM — рабочий выбор по умолчанию для большинства продуктов: ViewModel держит состояние экрана, а интерфейс сам перерисовывается при его изменении, без ручной возни. MVI идёт дальше: данные текут в одном направлении, а состояние экрана неизменяемо и пересоздаётся целиком — это даёт предсказуемость там, где UI-состояний много и цена ошибки высока (платежи, сложные формы, многошаговые сценарии). MVC и MVP сегодня берут реже: MVC — для совсем простого, MVP — там, где нужна тестируемость без реактивного подхода. Выбор паттерна — не религия, а соответствие сложности экрана: простому хватит MVVM, сложному с кучей состояний выгоднее MVI.
Clean Architecture: это правила, а не ещё один паттерн
Clean Architecture часто ставят в один ряд с MVVM и MVI — и зря, это про другое. MVC, MVP, MVVM, MVI отвечают за слой представления; Clean Architecture (её сформулировал Роберт Мартин, известный как «Uncle Bob») — это набор правил о том, как устроено всё приложение целиком и как слои зависят друг от друга.
Главное правило — правило зависимостей: зависимости направлены внутрь, к бизнес-логике. Доменный слой (правила бизнеса) не знает ничего о внешнем мире — ни про интерфейс, ни про конкретную базу или API. Это presentation и data зависят от домена, а не наоборот. Практический выигрыш: бизнес-логику можно тестировать в полной изоляции, а внешние детали (заменить REST на GraphQL, поменять локальную базу, переписать UI) меняются, не задевая ядро. Clean Architecture отлично сочетается с паттернами представления: внутри presentation работает MVVM или MVI, а Clean задаёт границы между presentation, domain и data на уровне всего приложения.
Цена за это — больше слоёв, интерфейсов и кода на старте. Поэтому Clean Architecture оправдана на средних и крупных продуктах с долгой жизнью, а для крошечного MVP она избыточна — это тот случай, когда «правильно по учебнику» означает «дорого и медленно без пользы».
Управление состоянием: где живёт правда о данных
Современная мобильная разработка крутится вокруг одного вопроса — управления состоянием (state management): где хранится актуальное состояние экрана и как интерфейс узнаёт, что его пора обновить. Когда пользователь нажал «добавить в корзину», счётчик в шапке должен обновиться, экран корзины — тоже, и всё это должно остаться согласованным. Чем сложнее приложение, тем важнее, чтобы у данных был единый предсказуемый источник правды, а не десяток мест, где состояние живёт по-своему и расходится.
Инструменты зависят от технологии. Во Flutter, на котором мы работаем по умолчанию, состоянием управляют через Provider, Riverpod или BLoC — последний реализует MVI-подход с однонаправленным потоком и предсказуемым состоянием, и его берут на крупных приложениях именно за надёжность. В нативной разработке свои инструменты: на Android — ViewModel и Jetpack Compose с Kotlin Flow, на iOS — SwiftUI и Combine. Подход к состоянию выбирают под сложность: простому экрану хватит лёгкого решения, нагруженному приложению с множеством взаимосвязанных состояний нужен строгий однонаправленный поток. Ошибка в управлении состоянием — частый источник плавающих багов, которые сложно поймать, поэтому на нагруженных продуктах это проектируют осознанно, а не «как получится».
Как выбрать архитектуру под проект
Универсального ответа «всегда так» нет — архитектуру подбирают под размер и горизонт продукта. Перебор так же вреден, как недобор: Clean Architecture для приложения на три экрана — это сожжённый бюджет, а god object в продукте на пятьдесят экранов — гарантированная переделка.
| Размер проекта | Разумный выбор |
|---|---|
| MVP, небольшое приложение | MVVM с чистым разделением слоёв, без лишних абстракций — быстро выйти и проверить гипотезу |
| Средний продукт | MVVM + Clean Architecture (data/domain/presentation) — запас для роста |
| Крупный продукт | MVI + Clean Architecture + модульность (каждая фича — отдельный модуль) — независимость команд и предсказуемость |
Логика простая: чем дольше живёт продукт и чем больше команда, тем сильнее окупаются строгие границы и тем дороже их отсутствие. Маленькому и короткоживущему важнее скорость — лишние слои только замедлят. Поэтому правильная архитектура — не самая сложная, а соответствующая задаче; и хороший подрядчик предложит ту, что подходит вашему продукту, а не самую модную.
Частые ошибки в архитектуре приложения
- Отсутствие архитектуры и god object. Всё свалено в одном месте — интерфейс, логика, данные. Работает на старте, превращается в нередактируемый клубок через год.
- Бизнес-логика в интерфейсе. Когда правила «как считается скидка» живут прямо в экране, их нельзя ни переиспользовать, ни протестировать, а любой редизайн рискует их сломать.
- Карго-культ паттернов. Взять модный паттерн, потому что «так у всех», не понимая, какую проблему он решает, — и получить сложность без пользы.
- Оверинжиниринг. Clean Architecture, модульность и пять слоёв абстракции для крошечного приложения — это медленно и дорого без отдачи. Сложность должна быть оправдана размером.
- Игнорировать управление состоянием. Состояние, размазанное по экранам без единого источника правды, рождает плавающие баги, которые потом неделями ловят.
С чего начать
- Трезво оценить размер и горизонт продукта (короткий MVP или система надолго)
- Развести ответственность по слоям с самого начала — интерфейс отдельно, бизнес-логика отдельно, данные отдельно
- Выбрать паттерн представления под сложность экранов (MVVM по умолчанию, MVI там, где состояний много) и решить про управление состоянием
- Добавлять строгие границы Clean Architecture и модульность по мере роста, а не на старте
- Не тащить бизнес-логику в интерфейс и не строить пять слоёв ради трёх экранов
Архитектуру выбирают под продукт, а не наоборот. Самое дорогое в приложении — не написать его, а поддерживать и развивать год за годом; и именно архитектура определяет, будет это дёшево или превратится в «проще переписать». Code Pilots проектирует архитектуру под задачу — от простого MVVM для MVP до Clean Architecture с модульностью для нагруженных продуктов на Flutter, силами senior-команды. Расскажите о задаче — подскажем, какая архитектура подойдёт именно вашему приложению и где не стоит переусложнять.
Частые вопросы (FAQ)
Это то, как внутри разделена ответственность между частями кода: где интерфейс, где бизнес-логика, где работа с данными. Пользователь её не видит, но именно она определяет, легко ли добавлять функции, чинить ошибки и менять команду. Плохая архитектура незаметна на старте и дорого обходится потом, когда приложение превращается в клубок, который страшно трогать.
MVVM и MVI — паттерны слоя представления: они разводят интерфейс и логику его поведения. MVVM хранит состояние во ViewModel и автоматически обновляет интерфейс; MVI добавляет однонаправленный поток данных и неизменяемое состояние для предсказуемости в сложных сценариях. Clean Architecture — не паттерн представления, а правила для всего приложения: как слои (data/domain/presentation) зависят друг от друга. Они не альтернативы, а сочетаются: MVVM или MVI внутри, Clean — снаружи.
По размеру и горизонту. Для MVP и небольшого приложения — MVVM с чистым разделением слоёв, без лишних абстракций. Для среднего продукта — MVVM плюс Clean Architecture с разделением на data/domain/presentation. Для крупного — MVI плюс Clean плюс модульность, где каждая фича — отдельный модуль. Главное — не переусложнять: сложность архитектуры должна быть оправдана размером продукта.
Управление состоянием (state management) — это то, как приложение хранит актуальное состояние экранов и синхронно их обновляет: нажал «в корзину» — счётчик и экран корзины обновились согласованно. Чем сложнее приложение, тем важнее единый предсказуемый источник правды о данных, иначе состояние расходится и появляются плавающие баги. Во Flutter для этого используют Provider, Riverpod или BLoC, в нативной разработке — свои инструменты.
Да, и для небольших продуктов это разумно — начать с MVVM и чистого разделения слоёв, без избыточных абстракций. Главное — сразу развести интерфейс, логику и данные: тогда добавить строгие границы Clean Architecture и модульность по мере роста несложно. А вот если на старте всё свалить в одно место, переход к нормальной архитектуре потом превращается в переписывание — поэтому базовое разделение слоёв закладывают с первого дня.