Согласно прогнозам Netguru, потребительские расходы на мобильные приложения должны достигнуть 288 миллиардов долларов к 2025 году и приблизиться к 626,4 миллиардам долларов к 2030 году. В столь конкурентной и динамичной среде, особенно в регулируемых отраслях, таких как финтех, здравоохранение и страхование, архитектурные ошибки обходятся дорого. Они приводят не только к появлению багов и технической задолженности, но и к нарушениям нормативных требований, угрозам безопасности и утрате доверия пользователей.
Мобильная архитектура перестала быть лишь инженерной задачей — это стратегическое решение продукта, напрямую влияющее на скорость разработки и долгосрочную устойчивость бизнеса.
Для того чтобы разобраться, почему архитектура является вопросом продукта, а не просто кодовым уровнем, и понять, как она помогает командам сохранять контроль и предсказуемость по мере их роста, мы поговорили с Романом Камышниковым, опытным старшим Android-инженером в компании Marshmallow. Он возглавлял архитектурные инициативы в быстроразвивающихся средах финтеха и иншуртеха, где стабильность, скорость и согласованность между командами имеют ключевое значение.
Помимо своей практической работы, Роман активно занимается наставничеством и выступает на Android Academy Global, а также является автором публикаций на ProAndroidDev и Droidcon, где его технические статьи собрали более 100,000 просмотров. Он помогает мобильным командам внедрять архитектурные практики, которые работают не только в теории, но и на практике — под давлением и в условиях регулирования при масштабировании.
ADM: Какую минимально жизнеспособную архитектуру (MVA) вы считаете необходимой для мобильного приложения на ранней стадии, чтобы она не мешала дальнейшему масштабированию? И как чистая архитектура помогает мобильным командам расти без ущерба для скорости или качества кода?
Роман: Хорошая минимально жизнеспособная архитектура (MVA) не стремится предсказать каждый будущий сценарий или решить все возможные проблемы — она просто обеспечивает достаточную структуру для безопасного роста. Это обычно означает разделение пользовательского интерфейса, логики домена и доступа к данным. Доменный слой может быть сначала тонким, но важны границы. Это соответствует рекомендациям Google по архитектуре в Android-приложениях, которые предполагают использование слоистой архитектуры для обеспечения разделения обязанностей.
Основываясь на своем опыте, могу сказать, что эффективным подходом является разделение каждой функции на API и модули реализации, даже на раннем этапе. Это сохраняет чистоту зависимостей и предотвращает излишнее увеличение времени сборки, особенно по мере роста команд.
Не обязательно внедрять полностью чистую архитектуру с первого дня, но наличие четкой структуры необходимо. Например, в одном проекте модульность была введена как реакция на растущую сложность между кажущимися несвязанными функциями. Это произошло слишком поздно и потребовало значительной переработки, которая заняла месяцы. Этого можно было бы избежать, если бы с самого начала существовали легкие границы.
Чистая архитектура помогает функциям определять четкие внутренние и внешние границы. Это делает межфункциональные зависимости явными и более управляемыми, позволяя разработчикам работать над различными частями приложения, не мешая друг другу. Это также относится к процессу адаптации — новый член команды может быстро начать работать над изолированной функцией, не изучая всю кодовую базу. Это также позволяет разделять работу по слоям: как только контракты между слоями определены, несколько инженеров могут работать над одной функцией или даже одним экраном параллельно.
ADM: В регулируемых отраслях, таких как иншуртех, масштабирование — это не только скорость, но и контроль. Как чистая архитектура помогает поддерживать и то, и другое, особенно в сочетании с Jetpack Compose?
Роман: В любой регулируемой отрасли важны соблюдение нормативных требований и безопасность, поэтому здесь речь идет не только о доставке функций, но и о том, чтобы точно знать, где в системе было произведено изменение и что оно может затронуть.
Jetpack Compose хорошо соответствует этим принципам. Поскольку он изначально поддерживает однонаправленный поток данных (UDF), он естественным образом способствует изоляции состояний, которую поощряет чистая архитектура. Например, в Marshmallow подход заключается в паре Compose с ViewModel для каждого экрана, а в некоторых случаях даже в ограничении ViewModel к повторно используемому компоненту.
Последний подход успешно используется такими компаниями, как Skyscanner, и поддерживается популярными библиотеками, такими как Decompose, Circuit от Slack и Molecule от CashApp. Он инкапсулирует бизнес-логику, улучшает повторное использование и облегчает отслеживание проблем, когда что-то идет не так — все это важно в регулируемой среде.
ADM: Какие архитектурные сигналы указывают на то, что проект больше не масштабируется хорошо? И как модульная структура позволяет развивать проект параллельно?
Роман: Обычно вы чувствуете это еще до того, как увидите в метриках — функции становятся дольше в доставке, pull-запросы затрагивают несвязанные части кодовой базы или изменяют слишком много файлов, и разработчики начинают избегать определенных областей, потому что "они рискованны для изменения". Это четкий сигнал о том, что архитектура не выдерживает нагрузку.
На уровне модуля легко отслеживаемым сигналом является количество зависимостей, которые имеет модуль, и сколько других модулей зависят от него. Дублированный код — это еще один симптом, который сложнее отслеживать автоматически, но который в конечном итоге становится заметным.
Исправления могут варьироваться от введения более четких границ модуля до переосмысления того, как распределяются обязанности. Иногда простой разбор одного чрезмерно сложного участка может значительно измениться на скорость доставки и уверенность разработчиков. Я считаю, что здесь применимо правило "бойскаута": всегда оставляйте код чище, чем нашли его.
Архитектура использует модульную структуру — модули функций и ядра, каждый из которых разделен на API и реализацию для обеспечения инверсии зависимостей. Функции обычно не зависят друг от друга напрямую, что позволяет безопасно развивать проект параллельно. Пока строгой принадлежности модулей нет, но по мере роста команды легко будет перейти на эту модель.
С Compose эта настройка работает хорошо. Используется UDF, и, как правило, один ViewModel на экран, но при необходимости логика изолируется в повторно используемых компонентах с собственным ViewModel. Например, есть экран ошибки, управляемый бэкендом, который может предложить пользователю различные действия в зависимости от ошибки — от повторной попытки действия до открытия живого чата или перехода в другую часть приложения через deeplink. У экрана есть собственный ViewModel, поэтому логика обработки действий является общей и не требует дублирования в каждой функции.
ADM: Какие инструменты вы используете для поддержки архитектурной масштабируемости по мере роста команды?
Роман: В настоящее время используются Detekt, Android Lint и пользовательские lint-правила для раннего обнаружения проблем. Konsist изучается как способ обеспечения архитектурных правил на уровне кода.
Скрипты Gradle и многофайловые шаблоны помогают создавать новые модули и шаблоны презентационного слоя. Внутренне разработанный плагин Android Studio автоматизирует настройку слоев данных и домена для новых конечных точек — генерируя доменные модели, преобразователи, репозитории и модульные тесты. Это сокращает количество шаблонного кода и обеспечивает согласованность между функциями. Даже простая README-файл, описывающий архитектуру проекта и структуру типичной функции, полезен.
Благодаря использованию упомянутых выше инструментов генерации кода, код, который ранее занимал часы написания, теперь можно сгенерировать за считанные секунды. Это еще более усиливается за счет экономии времени на ревью — автоматический код не требует столь тщательной проверки, поскольку он менее подвержен ошибкам.
ADM: Как вы видите будущее чистой архитектуры в мобильной разработке?
Роман: С появлением AI-сгенерированного кода, сильная архитектура и защитные механизмы становятся еще более важными, поскольку они придают структуру тому, что производит ИИ. То же самое касается UI, управляемого сервером: обертка моделей бэкенда в чистые доменные слои помогает управлять сложностью и поддерживать обратную совместимость.
В кроссплатформенных проектах, таких как Kotlin Multiplatform (KMP), слои могут немного измениться, но принципы остаются в силе. Из личного опыта могу сказать, что KMP почти по умолчанию обеспечивает чистую архитектуру — вам нужны общие абстракции, и придется активно работать против разделения обязанностей между платформой и общим кодом, чтобы его нарушить.
Чистая архитектура никуда не исчезнет — она будет эволюционировать. Ее основные идеи по-прежнему решают реальные проблемы. Но она должна применяться разумно и прагматично: если она кажется слишком тяжеловесной, команды будут избегать её; если она останется практичной, она будет актуальна.
Отличная статья! Полностью согласен с тем, что архитектурные решения должны основываться на практичности и долгосрочной перспективе. Особенно в финтехе, где безопасность и соответствие стандартам — ключевые аспекты.
Замечательно описано, как чистая архитектура может помочь в масштабировании команд. Интересно было бы узнать больше о реальных примерах внедрения этих принципов в крупных проектах.
Роман действительно объясняет сложные концепции простым языком. Особенно понравилась часть про модульность и её влияние на параллельное развитие. Это реально помогает избежать хаоса в кодовой базе!