Разработка
Основы Android-разработки в Revolut
Люди заслуживают получать больше от денег. Больше видимости, больше контроля, больше свободы. С 2015 года Revolut следует своей миссии по обеспечению именно этого. С арсеналом потрясающих продуктов, которые охватывают траты, сбережения, путешествия, переводы, кредиты, криптовалюты, инвестиции, обмен, страхование и многое другое, наше мобильное супер-приложение помогло 25+ миллионам клиентов отправлять, тратить, сберегать и инвестировать умнее.
Давайте заглянем за кулисы разработки Android в Revolut и узнаем, как мы предоставляем наши культовые приложения миллионам клиентов по всему миру.
Команда мечты
Наша Android-команда является одной из лучших в отрасли — и мы гордимся этим. Это одна из наших движущих сил: более сотни отличных Android-инженеров работают над нашими ключевыми продуктами, такими как Revolut для клиентов, Revolut Lite, Revolut Business и Revolut < 18 для подростков.
В Revolut мы верим, что создание разнообразных, бережливых команд, состоящих из блестящих целеустремленных специалистов, которые преодолевают барьеры — это ключ к победе. Именно поэтому у нас около 70 сильных команд, которые делят свою работу на два основных потока: Платформа и Продукт.
Поток Платформы состоит из команд по разработке системного дизайна (Design System) и мобильной платформы (Mobile Platform), а поток Продукта включает в себя различные продуктовые команды, такие как команда онбординга, команда платежей, команда торгов, команда криптовалют, команда кредитов и другие. Каждая команда состоит из владельцев продуктов, дизайнеров, инженеров iOS, Android, Backend и Frontend, которые работают вместе для достижения исключительных результатов.
Чтобы лучше понять наши команды и то, как они способствуют нашему успеху, давайте подробнее рассмотрим их роли и обязанности.
Команда мобильной платформы
Наша команда Mobile Platform занимается широким спектром задач, таких как создание инфраструктуры, управление релизами, поддержание архитектуры и владение внутренними инструментами и библиотеками.
Команда обновляет конфигурацию проектов, в частности, улучшает производительность сборок Gradle, поддерживает архитектуру, управляет зависимостями и поддерживает устойчивость архитектуры, модернизирует CI/CD конвейеры, облегчает процесс выпуска мобильных релизов, создает внутренние инструменты и фреймворки (Automated Analytics tracking tool, Automated UI tests framework, DI declaration framework и т.д.), улучшает производительность приложений и повышает безопасность.
Команда системного дизайна
Команда системного дизайна — это сердце нашей культуры дизайна. Она отвечает за создание и поддержку центрального источника истины для разработчиков — нашего UIKit (библиотеки многократно используемых компонентов и рекомендаций).
Они создают высококачественные компоненты пользовательского интерфейса, обеспечивают согласованность дизайна во всех приложениях, настраивают темы и стили, оптимизируют представления, макеты и общую производительность рендеринга. Они постоянно улучшают пользовательский опыт, создают единые визуальные интерфейсы, обеспечивают плавные переходы и делают наш UI/UX не только удобным, но и приятным.
Продуктовые команды
Продуктовые команды играют важнейшую роль на каждом шаге, стадии и выпуске продукта. Они похожи на небольшие стартапы: самостоятельные, обладающие свободой для инноваций и разработки новых продуктов и фич.
Такая команда сосредоточена на создании конкретного продукта, сборе требований, внедрении новых функций, сборе отзывов клиентов и внесении улучшений. Каждый из наших продуктов, включая кредиты, криптовалюты, путевки, вознаграждения и другие, создает наше единое приложение для всего, что связано с деньгами.
Процесс разработки
Процесс разработки требует участия всех членов команды для достижения поставленной цели. Планирование, видимость и структура процесса разработки являются неотъемлемыми компонентами, которые позволяют команде снять стресс и реализовать качественную функциональность, удовлетворяющую бизнес-потребности в срок.
Процесс начинается с определения требований и критериев приемки, которые проверяются и оспариваются командой. Как только требования ясны, можно приступать к фазе определения объема работ. Фаза определения объема включает в себя анализ требований, определение потенциальных решений и построение планов разработки.
После этапа определения потребностей все члены команды должны быть готовы к работе, поэтому можно приступать к разработке. При этом в процессе разработки могут появиться новые идеи или проблемы, и тогда может потребоваться повторное рассмотрение уже определенного решения.
Мы придерживаемся философии Agile, и у каждой команды есть выбор, какие системы управления проектами Agile использовать, обычно это Scrum и Kanban, или их сочетание.
Архитектура на уровне проекта
Все наши проекты являются многомодульными, то есть мы организуем наши функции в виде отдельных модулей. В то же время мы уделяем внимание уровню детализации и не разделяем функции, которые имеют высокую степень связности и связаны с одной и той же доменной областью, поскольку мы знаем, что слишком мелкозернистые модули становятся обузой.
Не все наши модули являются модулями функций. Существуют модули Core для общих нужд, таких как сеть, базы данных или компоненты Android, модули UIKit для элементов дизайна, модули Models для базовой структуры данных и модули Test. Мы повторно используем общие компоненты и базовую функциональность между функциональными модулями в одном проекте и распространяем их по всем нашим проектам. Это также применимо к функциям: у нас есть несколько функциональных модулей, которые мы совместно используем в разных продуктах.
В случае с модулями функций мы используем подход API/Implementation, поэтому каждая функция представлена в виде легкого API-модуля и тяжелого модуля Implementation.
Согласно этому подходу, Implementation фичи должен зависеть только от API модулей. Таким образом, тяжелые модули Implementation не зависят друг от друга и могут быть собраны параллельно, чтобы ускорить время сборки. Этот подход также делает реализацию фичи менее хрупкой, поскольку она зависит только от стабильных API модулей.
Архитектура на уровне приложений
Все наши продукты реализованы в соответствии с принципами чистой архитектуры. Revolut Business и Revolut < 18 основаны на нашем собственном фреймворке Kompot. Kompot реализует паттерн проектирования MVVM, обеспечивает однонаправленный поток данных, упрощает навигацию между различными частями приложения и поддерживает многомодульность.
Но это не единственное решение: Revolut для розничных клиентов, самое старое и самое большое приложение, использует другой подход, основанный на паттерне проектирования MVP. Тем не менее, новый подход с Kompot также может быть использован.
UI/UX
То, как мы работаем с пользовательским интерфейсом, заслуживает особого внимания, и в основном мы строим наши экраны на основе RecyclerView.
Наш UIKit определяет набор компонентов дизайна, которые могут быть использованы для построения пользовательского интерфейса. Большинство этих компонентов представлены в нашем коде как RecyclerViewDelegates, и мы строим каждый экран как список этих готовых компонентов (делегатов). Вы можете найти более подробную информацию о том, как мы создаем наши экраны здесь.
Мы также уделяем повышенное внимание UX. Согласно нашим рекомендациям, экраны должны поддерживать состояния Loading/Error/Empty. Кроме того, переход между состояниями должен быть плавным, без каких-либо визуальных шумов. Мы также стремимся отображать весь контент, который уже загружен, и использовать мерцающие скелеты только для тех частей, которые находятся в пути, вместо того чтобы загораживать весь экран старомодными индикаторами прогресса.
Что еще более важно, мы стремимся отображать контент на экране как можно быстрее, независимо от плохого интернет-соединения.
Здесь на сцену выходит реактивный поток данных. Согласно этому подходу, репозиторий предоставляет поток Данных, содержащий загружаемый контент, флаг, указывающий на то, что мы загружаем контент из сети, и необязательную ошибку.
Репозиторий также определяет логику получения и хранения содержимого. Обычно содержимое, которое мы получаем из удаленных источников, хранится в локальном кэше. Поэтому, когда мы подписываемся на поток Data, наш репозиторий сначала выдает кэшированное содержимое, чтобы мы могли избежать загрузки и сразу показать состояние на экране. Мы храним информацию в памяти и постоянном хранилище, поэтому наши клиенты имеют доступ к своим финансовым данным отовсюду, даже в автономном режиме.
Краткий обзор технического стека
Мы пишем на Kotlin и склонны использовать Kotlin coroutines и flows, но RxJava 2, вероятно, всегда будет в нашем сердце и в нашей кодовой базе.
Мы также используем:
- Фреймворки: RxJava 2, корутины (Flow), OkHttp, Retrofit, Dagger 2, тесты Facebook* Screenshot, тесты Kaspresso UI, Android SDK.
- База данных: SQLite (Room OM)
- Инфраструктура: Bitbucket, Jira, TeamCity
- Утилиты: Charles, Postman
QA и тестирование
В Revolut нет инженеров по контролю качества, но это не значит, что мы не проверяем правильность работы нашего приложения или не выявляем возможные проблемы, прежде чем выпустить его в свет. У нас есть несколько типов тестов, которые помогают нам последовательно проверять функциональное поведение. Юнит-тесты, интеграционные тесты, тесты скриншотов и автоматизированные тесты пользовательского интерфейса позволяют нам получить достаточно полное покрытие.
Кроме того, жизненный цикл нашего релиза состоит из нескольких этапов. На первой, Альфа-фазе, мы тестируем наши новые продукты и функции внутри компании, чтобы убедиться, что продукт соответствует бизнес-требованиям и работает правильно, без серьезных ошибок.
Следующая фаза — Бета, на которой мы публикуем бета-версии приложений, используемые внешними бета-пользователями, чтобы помочь нам выявить ошибки, а также проблемы производительности или удобства использования на ранней стадии, до публичного выпуска.
И последнее, но не менее важное: все основные изменения и обновления в коде мы покрываем переключателями фич, управляемыми нашей внутренней системой управления релизами Vader. Это позволяет нам постепенно и безопасно внедрять функции и отключать их в случае ошибки.
Обзор кода и поток Git
Разработчики создают новую ветку для любых функциональных изменений, а не фиксируют изменения непосредственно в локальной основной ветке разработки. После обновления, фиксации и переноса изменений в соответствующую ветку разработчики открывают Pull Request для обсуждения и проверки изменений до того, как они попадут в ветку разработки.
Когда открывается Pull Request или появляются какие-либо изменения в удаленной отслеживаемой ветке, мы выполняем CI/CD конвейеры, включая сборку проекта, запуск юнит и набора наиболее хрупких UI тестов, выполнение верификаций Danger.
Danger — это инструмент, который помогает автоматизировать общие процедуры проверки кода, позволяя нам создавать и распространять автоматические сообщения, напоминающие разработчикам о распространенных ошибках, чтобы они не сталкивались с ними снова и снова.
Пул-реквест готов к слиянию только в том случае, если проект успешно собран, Unit и UI тесты пройдены, нет ошибок со стороны Danger, и Pull Request имеет как минимум два одобрения. Весь наш код, модули, классы и тесты закреплены за конкретными владельцами кода (командами), что позволяет нам легко находить владельцев кода в процессе разработки, и, что более важно, назначать рецензентов, чей код был изменен в Pull Request.
Управление релизами
Наш релизный поезд отправляется каждый вторник в 9:00 утра. В этот момент происходит автоматическое замораживание кода, при котором все изменения из ветки разработки попадают в ветку релиза. К этому моменту все вовлеченные команды убеждаются, что все готово, и проверяют соответствующие переключатели функций. Начиная с этого момента, в ветку релиза можно добавлять только критические исправления.
Когда заморозка произошла, мы готовим Google Play bundle, запускаем юнит и все UI-тесты. Если все в порядке, мы публикуем первый бета-релиз (мы проходим эти шаги и загружаем бета-релизы каждый день, кроме воскресенья). На этом этапе мы собираем отзывы пользователей первой бета-версии, проверяем аналитику и отчеты о сбоях.
В следующий вторник, если все идет хорошо, мы объединяем ветку релиза с магазинной веткой и проводим поэтапное публичное развертывание релизной версии. К этому времени наш релиз-поезд превращается в ракету, доставляя новую версию приложения миллионам пользователей по всему миру, а на станцию приходит новый релиз-поезд.
Это последний этап цикла выпуска приложения, но не обязательно релиз функции. Даже когда ракета находится далеко в космосе, а наша новая версия приложения уже на устройствах пользователей, мы можем управлять развертыванием и доступностью конкретной функции удаленно, используя переключатели фич на базе Vader.
Аналитика
Мы анализируем данные клиентов для создания персонализированных моделей и повышения качества обслуживания. Например, мы распределяем события по категориям, чтобы персонализировать главный экран для каждого клиента, и предлагаем функции, основанные на его поведении.
Чтобы защитить конфиденциальность наших клиентов, мы не используем сторонние аналитические инструменты. Вместо этого мы создали собственное решение для анализа пользовательского интерфейса, которое включает в себя как фронтенд-фреймворк, так и серверную часть. Фреймворк на фронтенде отслеживает взаимодействие пользователя с приложением “из коробки”, не требуя от разработчиков написания специального кода. Сервер фиксирует пакетные события появления экранов и нажатия кнопок, что позволяет нам анализировать поведение пользователей для создания лучшего опыта для наших клиентов.
Присоединяйтесь к нам
Если вам интересны наши процессы, то, как мы работаем, и возможности, которые вы можете получить, работая с нами, почему бы вам не заглянуть на нашу страницу «Карьера» и не изучить вакансии в нашей Android-команде?
Благодарности Android-команде Revolut, особенно Антону Назаренко, Никите Баришок, Кириллу Ахметову и Артуру Харченко, Head of Engineering Ивану Важнову и нашему техническому директору Владу Яценко.