Архитектура мобильного приложения: подходы и паттерны
Архитектура приложения — это то, как внутри разделена ответственность между частями кода: где интерфейс, где бизнес-логика, где работа с данными. Пользователь её не видит, но именно она определяет, легко ли будет добавлять функции через год. Мы в Code Pilots ведём мобильную разработку с 2014 года и архитектуру выбираем под продукт и горизонт его жизни.
Если совсем коротко. Слоёв три — интерфейс, бизнес-логика, данные, — и смысл архитектуры в том, чтобы они не перепутались. Паттерн представления для большинства продуктов MVVM, для сложных состояний MVI, Clean Architecture — не паттерн, а правила связей между слоями. Отдельная и самая недооценённая часть мобильной архитектуры — работа без сети, синхронизация и конфликты данных. Проверить архитектуру можно, не читая код.
В статье
- Зачем бизнесу архитектура
- Слои приложения
- Паттерны представления
- Clean Architecture
- Управление состоянием
- Офлайн без сети
- Синхронизация и конфликты
- Работа с сетью
- Что хранится на устройстве
- Как выбрать архитектуру
- Бюджет второго года
- Как проверить архитектуру
- Смена команды
- Частые ошибки
- С чего начать
- Часто задаваемые вопросы
Зачем бизнесу архитектура, если её не видно
Соблазн понятный: пользователю всё равно, как устроен код, лишь бы приложение работало. На старте плохая архитектура не видна, а первые экраны выходят даже быстрее, потому что никто не тратит время на разделение ответственности.
Расплата приходит позже и выглядит одинаково во всех проектах. Новая функция, которая должна занять неделю, занимает три, потому что задевает половину кода. Исправление одной ошибки порождает две в других местах. Новый разработчик входит в проект месяц. Тестирование становится ручным, потому что автоматически проверить логику, размазанную по экранам, невозможно.
В деньгах это выглядит как рост стоимости изменений со временем. Хорошая архитектура держит стоимость доработок примерно ровной на всём сроке жизни продукта; плохая делает каждую следующую доработку дороже предыдущей.
Есть и обратная крайность. Команда, увлечённая правильностью, строит для приложения на пять экранов конструкцию, рассчитанную на банковский продукт: десяток уровней абстракции, интерфейсы ради интерфейсов, генерация кода. Первые функции выходят вдвое медленнее, а выигрыш не наступает никогда, потому что продукт не дорастает до масштаба, ради которого всё затевалось.
Разумная архитектура — это та, которая соответствует горизонту продукта. Приложение на год живёт по одним правилам, продукт на пять лет с командой из десяти человек — по другим.
Слои приложения: представление, логика, данные
В основе почти любой вменяемой архитектуры лежит разделение ответственности по слоям.
Представление — всё, что связано с интерфейсом: экраны, их состояние, реакция на действия. Этот слой знает, как показать, но не знает, откуда берутся данные.
Бизнес-логика — правила и сценарии, ради которых продукт существует. Можно ли оформить заказ, как считается скидка, что происходит при оплате. Слой не зависит ни от интерфейса, ни от источника данных.
Данные — работа с источниками: сеть, локальная база, кеш. Слой знает, откуда брать и куда сохранять, но не знает, как это покажут.
Смысл разделения в независимости: переделали дизайн экрана — логика не тронута; сменили API на сервере — интерфейс не заметил. Большинство архитектурных подходов — это разные способы провести границы аккуратно.
Отдельно стоит держать в голове, что у мобильного приложения есть вторая половина — серверная часть. Граница между ними проходит по API, и от того, насколько она продумана, зависит и клиентская архитектура. Как устроен сервер, разобрано в материале про бэкенд.
Паттерны представления: MVC, MVP, MVVM, MVI
Внутри слоя представления есть несколько устоявшихся способов развести интерфейс и логику его поведения. Они развивались, решая проблемы предыдущих.
| Паттерн | Идея | Когда уместен |
|---|---|---|
| MVC | Классика: модель, представление, контроллер | Быстрый старт, простые экраны; плохо масштабируется |
| MVP | Логику выносят в отдельный компонент, интерфейс пассивен | Нужна тестируемость без реактивности |
| MVVM | Состояние хранится отдельно, интерфейс обновляется автоматически | Баланс скорости и поддержки, выбор по умолчанию |
| MVI | Однонаправленный поток данных, неизменяемое состояние | Сложные состояния экрана, где важна предсказуемость |
На практике MVVM — рабочий выбор для большинства продуктов: состояние экрана живёт отдельно, интерфейс перерисовывается сам. MVI идёт дальше: данные текут в одном направлении, состояние пересоздаётся целиком, и это даёт предсказуемость там, где состояний много и цена ошибки высока — платежи, многошаговые формы, сложные фильтры.
Выбор паттерна — вопрос соответствия сложности экрана. Простому экрану хватит MVVM, сложному выгоднее MVI, и в одном приложении они спокойно сосуществуют.
Clean Architecture: это правила, а не ещё один паттерн
Clean Architecture часто ставят в один ряд с MVVM и MVI, хотя это про другое. Паттерны представления отвечают за один слой, Clean Architecture задаёт правила для всего приложения.
Главное правило — направление зависимостей. Они смотрят внутрь, к бизнес-логике: доменный слой не знает ничего про интерфейс, конкретную базу или формат API. Практический выигрыш в том, что логику можно тестировать в изоляции, а внешние детали менять, не задевая ядро.
Цена — больше слоёв, интерфейсов и кода на старте. Поэтому подход оправдан на средних и крупных продуктах с долгой жизнью. Для приложения на три экрана он избыточен: правильно по учебнику означает здесь дорого и медленно без пользы.
Управление состоянием: где живёт правда о данных
Когда пользователь нажал «добавить в корзину», счётчик в шапке должен обновиться, экран корзины тоже, и всё это должно остаться согласованным. Чем сложнее приложение, тем важнее единый предсказуемый источник правды вместо десятка мест, где состояние живёт по-своему.
Инструменты зависят от технологии, но принцип общий: у каждого куска состояния есть одно место, которое им владеет, и один способ его изменить. Ошибки здесь дают плавающие баги, которые тяжело воспроизвести, — экран показывает старые данные, счётчик расходится с содержимым, повторное нажатие создаёт два заказа.
На нагруженных продуктах управление состоянием проектируют осознанно и заранее. Переделать его в работающем приложении — это переписать значительную часть клиента.
Практический признак проблем со стороны заказчика: баги, которые воспроизводятся «иногда». Товар пропадает из корзины через раз, уведомление приходит дважды, экран после возврата назад показывает устаревшие данные. Такие дефекты почти всегда растут из состояния, у которого нет единого владельца, и чинить их поштучно бесполезно — они возвращаются в других местах.
Офлайн: что приложение умеет без сети
Главное отличие мобильной архитектуры от серверной в том, что связь пропадает. В метро, в лифте, в подвале склада, в роуминге. Приложение, которое в этот момент показывает пустой экран, воспринимается как сломанное, хотя код исправен.
Решение начинается с вопроса, что именно должно работать без сети. Полный офлайн нужен редко: обычно достаточно, чтобы человек видел то, что уже загружал, и мог выполнить одно-два действия, которые уйдут на сервер позже.
| Уровень | Что работает | Кому нужно |
|---|---|---|
| Ничего | Только заглушка «нет сети» | Продукты, где офлайн не имеет смысла: платежи в реальном времени |
| Чтение из кеша | Видно всё, что уже загружалось | Каталоги, ленты, справочники — большинство продуктов |
| Отложенные действия | Можно создать запись, она уйдёт при связи | Полевые сотрудники, курьеры, торговые представители |
| Полный офлайн | Приложение работает автономно длительное время | Склады, производство, работа в зонах без покрытия |
Каждый следующий уровень заметно дороже предыдущего, и решение принимают на проектировании. Достроить полноценный офлайн к приложению, которое проектировали как онлайн-клиент, обычно означает переписать слой данных целиком.
Есть и промежуточная задача, о которой забывают: показать пользователю, в каком он состоянии. Человек должен понимать, что видит сохранённые данные, когда они обновлялись в последний раз и что его действие уйдёт на сервер позже. Без этого офлайн вызывает больше вопросов, чем честное сообщение об отсутствии связи.
Синхронизация и конфликты данных
Как только появляются отложенные действия, появляется и вопрос, что делать при расхождениях. Человек изменил запись в офлайне, в это же время её изменил коллега на сервере. Чья версия победит?
Вариантов ответа три, и выбирают их по сценарию использования.
Побеждает сервер. Локальные изменения отбрасываются. Просто в реализации, но пользователь теряет работу — годится там, где офлайн-правки редки и не критичны.
Побеждает последний. Сравниваются отметки времени. Работает для простых записей, но на встречных правках одного объекта тихо теряет часть данных.
Слияние по полям или ручной разбор. Изменения объединяются, а спорные случаи показываются человеку. Дороже всех и единственный честный вариант там, где данные важны: заказы, документы, показания приборов.
Отдельная тонкость — идентификаторы. Запись, созданная офлайн, ещё не имеет серверного номера, и приложение должно уметь связать временный идентификатор с постоянным после отправки. Без этого дубликаты появляются на второй неделе эксплуатации.
Как это выглядит в жизни. Торговый представитель оформил три заказа в магазине без связи, вышел на улицу, приложение отправило их пачкой. Один заказ не прошёл: товар кончился, пока представитель был офлайн. Правильное поведение — показать, что именно не прошло и почему, оставив два других. Неправильное — молча отклонить всю пачку или, хуже, отправить её повторно при следующем запуске.
Поэтому очередь отложенных операций проектируют вместе со сценарием разбора ошибок. Технически это несложно, но об этом почти никогда не пишут в требованиях, и появляется такая логика уже после первых жалоб с полей.
Работа с сетью: таймауты, повторы, обрывы
Мобильная сеть отличается от офисного интернета не только скоростью. Она нестабильна, и архитектура обязана это учитывать.
Что закладывают в слой работы с сетью:
- таймауты на каждый запрос, чтобы приложение не висело в ожидании вечно;
- повторы для операций, которые можно безопасно повторить, с нарастающей паузой между попытками;
- защиту от двойной отправки — если человек нажал «оплатить» дважды, сервер должен понять, что это одна операция;
- поведение при обрыве в середине: заказ отправлен, ответ не пришёл — приложение обязано выяснить итог операции перед повтором;
- очередь запросов с приоритетами, чтобы важное уходило первым при слабой связи.
Эти вещи не видны в интерфейсе и почти никогда не попадают в ТЗ, но именно они отличают приложение, которое работает в реальной жизни, от приложения, которое работает в офисе на вайфае. Как строят серверную часть под нестабильную нагрузку, разобрано в материале про высоконагруженные системы.
Что хранится на устройстве и где
Телефон — не защищённая среда. Его теряют, отдают в ремонт, на нём стоят другие приложения. Поэтому в архитектуре отдельно решают, что вообще можно держать на устройстве.
Токены доступа живут в системном защищённом хранилище, для которого у обеих платформ есть штатный механизм. У обеих платформ такое хранилище есть, и это не опция.
Кешированные данные шифруют, если среди них есть персональные. Список товаров шифровать незачем, историю обращений клиента — обязательно.
Ничего лишнего. Данные, которые не нужны офлайн, на устройстве не хранят вовсе. Это самый надёжный способ их не потерять.
Очистка при выходе. Когда пользователь выходит из аккаунта, локальные данные удаляются. Иначе следующий владелец телефона увидит чужую информацию.
Требования к персональным данным здесь не абстрактные: закон определяет и хранение, и журналы доступа, а штрафы за утечку исчисляются миллионами. Проектировать это после релиза дороже, чем сразу.
Как выбрать архитектуру под проект
Универсального ответа нет: подбирают под размер продукта и горизонт его жизни. Перебор так же вреден, как недобор — сложная архитектура для приложения на три экрана съедает бюджет без пользы.
| Продукт | Разумный выбор |
|---|---|
| Прототип, проверка гипотезы | Простая структура без лишних слоёв, MVVM, минимум абстракций |
| Приложение с одним-двумя сценариями | MVVM, разделение на слои, кеш для чтения |
| Продукт с интеграциями и ролями | Слои строго, MVI на сложных экранах, продуманная синхронизация |
| Большой продукт с командой и долгой жизнью | Clean Architecture, модульность, отдельные команды на модули |
Горизонт продукта важнее его текущего размера. Приложение, которое сейчас состоит из четырёх экранов, но через год обрастёт ролями, интеграциями и второй командой, лучше сразу собирать по слоям: докладывать функции в разделённый код дешевле, чем разделять код, который уже оброс связями.
До выбора архитектуры решают вопрос технологии — нативно или на общей кодовой базе. Разбор подходов есть в материале про нативную, гибридную и кроссплатформенную разработку, и на архитектуру он влияет: инструменты разные, принципы одни и те же. Мы ведём кроссплатформенную разработку как основной путь, и слои в ней раскладываются так же, как в нативном проекте.
Что архитектура делает с бюджетом второго года
Разработка — это первые месяцы, а живёт продукт годами, и архитектура определяет стоимость этих лет сильнее, чем что-либо ещё.
На хорошо разделённом коде новая функция стоит примерно одинаково и на первом году, и на третьем. Правка в одном слое не задевает остальные, тесты ловят поломки до релиза, новый разработчик разбирается за дни.
На запутанном коде каждая доработка дороже предыдущей. Разработчик тратит больше времени на понимание, чем на написание; изменения тянут за собой правки в неожиданных местах; регресс проверяют вручную, потому что автотесты невозможны. В какой-то момент команда предлагает переписать всё заново — и это честное предложение, потому что поддерживать становится дороже, чем построить.
Отсюда практический вывод для сметы. Экономия на архитектуре на старте измеряется неделями работы, а её последствия — месяцами на горизонте пары лет. Это тот случай, когда «сделаем побыстрее, потом разберёмся» имеет конкретную цену.
Как проверить архитектуру, не читая код
Заказчику не нужно разбираться в паттернах, чтобы понять состояние проекта. Достаточно задать вопросы и посмотреть на ответы.
- 1. Сколько времени занимает добавление типовой функции сейчас и сколько занимало полгода назад. Растущая цифра — главный симптом.
- 2. Есть ли автотесты и что они покрывают. Логика, которую нельзя протестировать отдельно от интерфейса, обычно означает, что слои перепутаны.
- 3. Сколько времени входит в проект новый разработчик. Больше двух недель на средний продукт — тревожно.
- 4. Что происходит при смене внешнего сервиса. Если замена платёжного провайдера задевает экраны, границы слоёв нарушены.
- 5. Есть ли описание архитектуры. Не толстый документ, а схема на страницу с ответом, где что лежит.
- 6. Как часто ломается то, чего не трогали. Это прямой признак связанности всего со всем.
Если ответы настораживают, следующий шаг — аудит кода и архитектуры: внешняя команда смотрит проект и говорит, что чинится точечно, а что придётся переделывать.
Смена команды: когда это возможно без переписывания
Момент, когда архитектура окупается заметнее всего, — расставание с подрядчиком или уход ключевого разработчика.
Проект с внятной структурой передаётся: новая команда читает схему слоёв, разбирается в границах, начинает работать через одну-две недели. Проект без структуры передать нельзя — можно только отдать исходники, и дальше новая команда полгода восстанавливает логику по коду или предлагает переписать.
Что делает передачу возможной: разделение на слои, отсутствие бизнес-логики внутри экранов, автотесты на ключевые сценарии, описание архитектуры и договорённостей, единый стиль в коде. Ничего экзотического, но всё это либо закладывается по ходу, либо не появляется вовсе.
Проверять это стоит не в момент расставания, а раз в полгода. Заодно выясняется реальное состояние продукта, которое не всегда совпадает с отчётами.
Ещё одна страховка — доступ к репозиторию с первого дня и регулярные сборки, которые вы можете поставить себе на телефон. Если код лежит у подрядчика и появляется у вас только по запросу, любая смена команды начинается с выяснения, всё ли вам отдали.
Частые ошибки
Бизнес-логика внутри экранов. Самая распространённая: правила расчётов живут в коде интерфейса, и переиспользовать их нельзя. Любое изменение правила приходится вносить в нескольких местах.
Архитектура не по размеру. Clean Architecture в прототипе или полное отсутствие слоёв в большом продукте — обе крайности стоят денег.
Офлайн, придуманный после релиза. Кеш и очередь отложенных операций встраиваются в слой данных на проектировании; поверх готового кода они не ложатся.
Единый источник правды отсутствует. Одни и те же данные живут в трёх местах и расходятся, а баги воспроизводятся через раз.
Смена архитектуры на середине. Половина продукта на одном подходе, половина на другом — худший из вариантов, потому что нужно держать в голове оба.
Архитектуру выбрали по статье в интернете. То, что подошло крупному банку, для приложения с тремя экранами избыточно ровно настолько, насколько дорого.
Нет описания принятых решений. Архитектура живёт в голове у одного разработчика, и с его уходом команда начинает трактовать правила по-своему. Схема на страницу и короткий документ с договорённостями стоят пары часов и снимают половину будущих споров.
С чего начать
- 1. Сформулируйте, что приложение должно уметь без сети, — это решение меняет архитектуру сильнее остальных.
- 2. Определите горизонт: продукт на год или на пять лет.
- 3. Решите вопрос технологии — нативно или общая кодовая база.
- 4. Договоритесь, где живёт бизнес-логика, и зафиксируйте это письменно.
- 5. Заложите автотесты на ключевые сценарии с первого спринта.
- 6. Попросите у команды схему архитектуры на одну страницу.
Когда именно всё это происходит в проекте, показано в материале про этапы разработки мобильного приложения, а как архитектура связана с остальными решениями — в гайде по созданию приложения.
Часто задаваемые вопросы
Это разделение ответственности внутри кода: где интерфейс, где бизнес-логика, где работа с данными. Пользователь её не видит, но от неё зависит, сколько будет стоить каждое изменение через год.
Для большинства продуктов — разделение на три слоя и MVVM в слое представления. Сложные экраны с множеством состояний выигрывают от MVI. Clean Architecture берут на крупных продуктах с долгой жизнью, для маленьких она избыточна.
MVVM — способ организовать один слой, отвечающий за интерфейс. Clean Architecture — правила связей между всеми слоями приложения. Они не конкурируют: внутри представления работает MVVM, а Clean задаёт границы между слоями.
Что именно должно работать без сети: только просмотр загруженного, создание записей с последующей отправкой или полноценная автономная работа. От ответа зависит слой данных, и достроить его потом дороже, чем спроектировать сразу.
Точечно — да, постепенно вынося логику из экранов и разделяя слои. Полная смена подхода на работающем продукте по трудозатратам близка к написанию заново, поэтому чаще выбирают поэтапный рефакторинг под конкретную задачу.
Разработка с Code Pilots
Мы проектируем мобильные продукты со сложной логикой, интеграциями и работой в нестабильной сети — от приложений для клиентов до инструментов полевых сотрудников, которые должны работать без связи.
Архитектуру выбираем под продукт и горизонт его жизни: маленькому не навязываем лишние слои, большому не оставляем логику в экранах. Команда собрана внутри — аналитика, дизайн, мобильная разработка, бэкенд и тестирование, — а цену и срок фиксируем до старта.
Опубликовано · обновлено