Люди заслуживают больше видимости, контроля и свободы в управлении своими деньгами. С 2015 года Revolut стремится обеспечить именно это. Используя арсенал продвинутых продуктов, охватывающих платежи, сбережения, страхование путешествий, переводы, кредиты, криптовалютные операции, инвестиции, обмен валют и многое другое, мобильный суперапп помог более чем 25 миллионам клиентов отправлять, тратить, копить и инвестировать свои средства.

Заглянем за кулисы Android-разработки в Revolut и расскажем, как мы предоставляем приложения миллионам клиентов по всему миру.

Команда мечты

Наша Android-команда  —  в числе лидеров отрасли, и мы гордимся этим. Одна из движущих сил компании  —  более сотни первоклассных Android-инженеров. Они разрабатывают наши ключевые продукты:

  • Revolut для персональных клиентов;
  • упрощенная версия Revolut;
  • Revolut для бизнеса;
  • Revolut для подростков до 18 лет.

Мы верим, что ключ к успеху  —  в командах, состоящих из компетентных и целеустремленных специалистов, способных преодолевать трудности. Именно поэтому у нас около 70 сильных команд, образующих два основных подразделения: “Платформа” и “Продукт”.

В первое входят команды, специализирующиеся на разработке системы и мобильной платформы, а во второе  —  те, что создают различные продукты: онбординг, платежи, трейдинг, криптовалютные операции, кредитование и другие. Каждая команда состоит из руководителей продуктов, дизайнеров, iOS-, Android-, бэкенд- и фронтенд-инженеров, которые работают сообща для достижения лучших результатов.

Чтобы у вас сложилось более точное представление о наших командах и их вкладе в успех компании, рассмотрим подробнее их роли и обязанности.

Команда мобильной платформы

Наша команда мобильной платформы отвечает за широкий спектр работ, таких как создание инфраструктуры, управление релизами, поддержка архитектуры и руководство внутренними инструментами и библиотеками.

В обязанности команды входит:

  • обновление конфигурации проектов, в частности повышение производительности Gradle Builds;
  • поддержка архитектуры;
  • управление зависимостями и обеспечение устойчивости архитектуры;
  • модернизация конвейера CI/CD;
  • упрощение процесса выпуска мобильных релизов;
  • создание внутренних инструментов и фреймворков (инструмента автоматизированного отслеживания аналитики  —  Automated Analytics, фреймворков автоматизированного тестирования графического интерфейса, фреймворка для определения внедрения зависимостей и т.д.);
  • повышение производительности приложений;
  • совершенствование системы безопасности.

Команда дизайна системы

Команда дизайна системы  —  это сердце нашей культуры дизайна. Она отвечает за создание и поддержку центрального источника истины для разработчиков  —  UIKit (библиотеки многократно используемых компонентов и руководств).

Обязанности этой команды включают:

  • создание высококачественных компонентов пользовательского интерфейса;
  • обеспечение согласованности дизайна во всех приложениях;
  • настройку тем и стилей;
  • оптимизацию представлений, макетов и общей производительности рендеринга;
  • постоянное совершенствование пользовательского опыта;
  • разработку единых визуальных интерфейсов;
  • обеспечение плавного перехода между состояниями;
  • повышение удобства и эстетической привлекательности UI/UX.

Команды по работе над продуктом

Эти команды играют важнейшую роль на каждом шаге, этапе и выпуске продукта. Они похожи на небольшие стартапы: самостоятельные, обладающие свободой для инноваций и разработки новых продуктов и функций.

Такая команда сосредоточена на следующих процессах:

  • создании конкретного продукта;
  • систематизации технических требований;
  • внедрении новых функций;
  • сборе отзывов клиентов;
  • внесении улучшений.

Каждый из наших продуктов, включая кредиты, криптовалютные операции, отсрочки, вознаграждения и другие, составляет функционал единого приложения, нацеленный на все, что связано с финансами.

Процесс разработки

Процесс разработки требует участия всех членов команды для достижения поставленной цели. Планирование, наглядность и структура процесса разработки являются неотъемлемыми компонентами, которые позволяют команде избежать перегрузок и в срок реализовать качественную функциональность, удовлетворяющую бизнес-потребностям.

Процесс начинается с определения требований и критериев приемки, которые утверждаются и обсуждаются командой. Как только требования ясны, можно приступать к этапу определения объема работ. Он включает в себя анализ требований, определение потенциальных решений и создание планов разработки.

После достижения согласованности между всеми членами команды приступаем к разработке. Если в ее процессе появляются новые идеи или проблемы, возможен пересмотр уже определенного решения.

Мы придерживаемся философии Agile (гибкой методологии разработки), и у каждой команды есть выбор, какие Agile-системы управления проектами использовать. Обычно это Scrum и Kanban, или их сочетание.

Архитектура на уровне проекта

Все наши проекты являются мультимодульными, то есть мы организуем наши функции как отдельные модули. В то же время мы обращаем внимание на уровень детализации и не разделяем функции, которые имеют высокую степень связности и относятся к одной и той же предметной области, поскольку чрезмерное дробление модулей создает лишнюю нагрузку.

Не все наши модули являются Feature-модулями (законченными фичами). Существуют Core-модули для общих потребностей, таких как сеть, базы данных и Android-компоненты, UIKit-модули для элементов дизайна, Models-модули для базовой структуры данных и Test-модули. Мы повторно используем общие компоненты и базовую функциональность между Feature-модулями в одном проекте, распространяя их по всем нашим проектам. Это также применимо к функциям: у нас есть несколько Feature-модулей, которые мы совместно используем в разных продуктах.

Граф зависимостей мультимодульного проекта Revolut

В случае с Feature-модулями мы используем подход API/Implementation (разработка интерфейса/реализация функции), поэтому каждая функция представлена в виде легкого API-модуля и тяжелого Implementation-модуля.

Согласно этому подходу, реализация функции должна определяться только API-модулями. Таким образом, тяжелые Implementation-модули не зависят друг от друга и могут быть собраны параллельно, что ускоряет время сборки. Этот подход также делает реализацию функции более надежной, поскольку она зависит только от стабильных API-модулей.

Архитектура на уровне приложений

Все наши продукты реализуются в соответствии с принципами чистой архитектуры. Revolut для бизнеса и Revolut для подростков до 18 лет основаны на нашем собственном фреймворке Kompot. Kompot реализует паттерн проектирования MVVM, обеспечивает однонаправленный поток данных, упрощает навигацию между различными частями приложения и поддерживает мультимодульность.

Но это не единственное решение: Revolut для розничных клиентов, самое старое и самое крупное приложение, использует другой подход, основанный на паттерне проектирования MVP. Тем не менее новый подход с Kompot также может быть применим.

UI/UX

То, как мы работаем с пользовательским интерфейсом, заслуживает особого внимания. В основном мы создаем экранные формы на базе RecyclerView.

UIKit определяет набор компонентов дизайна, которые можно использовать для разработки пользовательского интерфейса. Большинство этих компонентов представлены в коде как RecyclerViewDelegates, и мы создаем каждый экран как список этих готовых компонентов (делегатов).

Мы также уделяем повышенное внимание качеству пользовательского взаимодействия (UX). Согласно нашим принципам проектирования, экраны должны поддерживать состояния Loading/Error/Empty (загрузка данных/уведомление об ошибке/очищение экрана). Кроме того, переход между состояниями должен быть плавным, без каких-либо визуальных шумов. Мы также стремимся отображать весь контент, который уже загружен, и использовать мерцающие скелеты только для тех частей, которые находятся “в пути”, вместо того чтобы загораживать весь экран устарелыми индикаторами прогресса.

Что еще более важно, мы стремимся отображать контент на экране как можно быстрее, независимо от скорости интернет-соединения.

Здесь в дело вступает реактивный поток данных. Согласно этому подходу, репозиторий предоставляет поток данных, содержащий вводимый контент, флаг, указывающий на то, что мы загружаем контент из сети, и опциональную ошибку.

Репозиторий также определяет логику получения и хранения контента. Обычно контент, получаемый из удаленных источников, хранится в локальном кэше, поэтому при абонировании потока данных репозиторий сначала выдает кэшированный контент, чтобы избежать загрузки и сразу показать состояние экрана. Мы храним информацию в памяти и постоянном хранилище, поэтому наши клиенты имеют доступ к своим финансовым данным отовсюду, даже в автономном режиме.

Краткий обзор технического стека

Мы пишем на Kotlin и склонны использовать корутины и потоки Kotlin, но RxJava 2, вероятно, всегда будет в нашем сердце… и в кодовой базе тоже.

Мы также используем:

  • фреймворки (RxJava 2, Coroutines (Flow), OkHttp, Retrofit, Dagger 2, Facebook Screenshot tests, Kaspresso UI tests, Android SDK);
  • базу данных (SQLite  —  Room OM);
  • инфраструктуры (Bitbucket, Jira, TeamCity);
  • утилиты (Charles, Postman).

Контроль качества и тестирование

У нас в Revolut нет QA-инженеров (службы контроля качества), но это не значит, что мы не проверяем правильность работы нашего приложения или не выявляем возможные проблемы, прежде чем выпустить продукт. Мы используем несколько типов тестов, которые помогают последовательно проверять функциональное поведение. Модульные тесты, интеграционные тесты, тесты скриншотов и автоматизированные тесты пользовательского интерфейса предлагают достаточно полное тестовое покрытие.

Кроме того, жизненный цикл релиза состоит из нескольких этапов. На первом этапе, альфа-фазе, мы тестируем новые продукты и функции внутри компании, чтобы убедиться в их соответствии бизнес-требованиям и безошибочной работе.

Следующий этап  —  бета-фаза, на которой мы публикуем бета-версии приложений, используемые внешними бета-пользователями, чтобы выявить ошибки, а также проблемы производительности или удобства использования на ранней стадии, до публичного выпуска.

И последнее, но не менее важное: все основные изменения и обновления в коде мы обрабатываем с помощью переключателей функций, управляемых нашей внутренней системой управления релизами Vader. Это позволяет постепенно и безопасно внедрять функции и отключать их в случае ошибки.

Проверка кода и Git-поток

Разработчики создают новую ветку для всех функциональных изменений, а не коммитят их непосредственно на локальной основной ветке разработки. После обновления, коммита и переноса изменений в соответствующую ветку разработчики открывают пул-реквест (Pull Request) для обсуждения и проверки изменений до того, как эти изменения попадут в ветку разработки.

Когда открывается пул-реквест или появляются какие-либо изменения в удаленной отслеживаемой ветке, мы запускаем конвейеры CI/CD, включая сборку проекта, запуск модульных тестов и набора наиболее эффективных UI-тестов, выполнение Danger-верификаций.

Danger  —  это инструмент, который помогает автоматизировать общие процедуры проверки кода, позволяя создавать и распространять автоматические сообщения, напоминающие разработчикам об общих ошибках, чтобы они не сталкивались с ними снова и снова.

Пул-реквест (запрос на интеграцию изменений) принимается только при минимум двух одобрениях, а также при успешном выполнении сборки проекта, прохождении модульных и UI-тестов, отсутствии ошибок со стороны Danger. Весь код, модули, классы и тесты закреплены за конкретными владельцами кода (командами), что позволяет легко находить авторов кода в процессе разработки, и, что более важно, определять рецензентов, одобривших внесенные в код изменения в процессе пул-реквеста.

Управление релизами

Наш релиз-поезд отправляется каждый вторник в 9:00 утра. В этот момент происходит автоматическая заморозка кода, при которой все изменения из ветки разработки попадают в ветку релиза. К этому моменту все вовлеченные команды убеждаются в том, что все готово, и проверяют соответствующие переключатели функций. Начиная с этого момента, в ветку релиза можно добавлять только критические исправления.

По завершении периода заморозки кода мы готовим комплект Google Play, запускаем модульные и все UI-тесты. Если все в порядке, мы публикуем первый бета-релиз (вышеописанные шаги и загрузка бета-релизов у нас осуществляются каждый день, кроме воскресенья). На этом этапе мы собираем отзывы покупателей первой бета-версии, проверяем аналитику и отчеты о сбоях.

Если все идет хорошо, в следующий вторник мы объединяем релизную ветку с резервной и проводим поэтапное публичное развертывание релизной версии. К этому времени релиз-поезд превращается в ракету, доставляя новую версию приложения миллионам пользователей по всему миру, а на станцию приходит новый релиз-поезд.

Это последний этап цикла выпуска приложения, но он не всегда означает завершение релиза функции. Даже когда ракета находится далеко в космосе, а новая версия приложения уже на устройствах пользователей, мы можем управлять развертыванием и доступностью конкретной функции удаленно, используя переключатели функций на базе Vader.

Аналитика

Мы анализируем данные о клиентах для создания персонализированных моделей и совершенствования потребительского опыта. Например, мы распределяем события по категориям, чтобы персонализировать главный экран для каждого клиента, и предлагаем функции, основанные на его поведении.

Чтобы защитить конфиденциальность наших клиентов, мы не используем сторонние аналитические инструменты. Вместо этого мы создали собственное решение для анализа пользовательского интерфейса, которое включает в себя как фронтенд-фреймворк, так и сервер. Фронтенд-фреймворк отслеживает взаимодействие пользователя с приложением “из коробки”, не требуя от разработчиков написания специального кода. Сервер фиксирует пакетные события появления экранов и нажатия кнопок, что позволяет анализировать поведение пользователей и совершенствовать их взаимодействие с приложением.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Revolut: The Fundamentals of Android at Revolut

Предыдущая статьяОбрабатываем ошибки в React: полное руководство
Следующая статьяОбработка файлов на C