Как «Цифра» меняет инфраструктуру уже работающего приложения

Отказоустойчивость повышается за счет модульного подхода.

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

Эта история началась в конце 2021 года, когда наш «духовный наставник» Мурад Шихмагомедов поставил задачу разработать модульную архитектуру в мобильном приложении «Цифра банк» с целью улучшения отказоустойчивости работы приложения. На тот момент было принято решение сделать «псевдо-модульность», поскольку истинная модульная архитектура в рамках текущего стэка инструментов казалась невозможной. В результате, разработка приложения «Цифра банк» проводилась с использованием этого подхода.

Леонид Молчанов
Ведущий разработчик «Цифра банк»

Задача была не из простых, поскольку было две серьезные проблемы: «интеграция блока навигации модуля» и механизмы отладки и разработки. Мне хотелось создать что-то самостоятельное, некую сущность, которая будет функционировать отдельно. Но как это сделать, как собрать «Вольтрона» (ностальгия из детства)?! На обдумывание задачи у меня ушло около 3 месяцев, ответ, к счастью, был найден и, как это всегда бывает, он оказался достаточно простым.

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

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

Первый модуль уже внедрен в релиз, эта архитектура вовремя подошла к нашим внутренним банковским процессам по Agile-трансформированию. Теперь мы можем эффективно разделять разработку не только на продуктовые команды, но изолировать их на уровне модулей.

P.S. Более подробно об этом расскажет Ярослав

От идеи к реализации

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

Ярослав Белецкий
TechLead отдела мобильной разработки «Цифра банк»

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

Грубо говоря, есть один склад, на котором находятся все ресурсы - уже готовые шаблоны бизнес-процессов. И эти шаблоны можно использовать в любом другом мобильном приложении, которое написано с помощью framework React Native.

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

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

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

Возможность запускать процессы параллельно. То есть команды могут собрать каждый свое мобильное приложение со своими модулями для dev-тестирования.

Возможность совершить несколько сборок. Приложение собирается один раз, но сразу на три контура – dev-тестирование, test (на котором проводится регресс-тестирование) и production.

Было еще одно небольшое дополнительное требование – разграничение доступа к функционалу для разработчиков и тестировщиков.

Сборка

Так как у нас кроссплатформенная разработка, нам необходимо собирать сразу две платформы. Для Android мы можем собрать как на операционной системе Windows, так и на macOS, а iOS собирается только на macOS. Поэтому наш ресурс должен был лежать на сервере, который имеет операционную систему macOS. Мной был написан backend-сервер, который обрабатывает все запросы и умеет делать сборки, был написан web-интерфейс с различным функционалом.

Все модули лежат на GitLab Self-hosted. Я публикую бизнес-модули внутри каждого репозитория отдельно. Сборщик заходит в репозитарий на GitLab, смотрит на зависимости, которые имеет ядро, и собирает пакет.

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

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

Всем остальным управляет сам модуль. У него свой сетевой клиент, своя бизнес-логика, навигация. То есть это отдельная монолитная сущность, а ядро – это хранитель контекста, куда подключаются все модули. Первая версия модуля появилась в конце декабря. Первый раз, когда архитектура видела свет production, – середина января, это был модуль авторизации. Сейчас мы переписываем все мобильное приложение по модулям, работа сделана уже на 80%. Как только мы перенесем всю оставшуюся часть, то сможем использовать уже написанный нами сборщик.

Что это даст?

Какой будет результат:

✔ Не надо будет ждать выпуска релиза другого модуля.

✔ Независимая разработка. Нет никаких конфликтов между разработками.

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

✔ И, конечно же, повышение уровня отказоустойчивости мобильного приложения.

0
5 комментариев
Беженцы с TJ

О чем они вообще пишут? Ваше приложение самое глючное из всех банковских приложений что я видел, на андроид периодически зависает на входе по отпечатку, на ios глюк с тем что в брокерский счет для перевода не набираются буквы (на андроид набираются)
Пользуюсь только от безысходности тк нужны переводы в FFIN KZ
Если отзывы в сторе почитать можно вообще охренеть от обилия таких глюков

Ответить
Развернуть ветку
Ленни Лизовский

В React Native ничего из этого вообще нет? Мне казалось что мультимодульная структура, build types и build flavors это основы

Ответить
Развернуть ветку
Вы в федеральном розыске

"основы" чего?
У каждого проекта свои требования, назначение и цели.
И build types/flavours далеко не всем нужны.

Ответить
Развернуть ветку
Ленни Лизовский
"основы" чего

Современной системной архитектуры, где проповедуется полноценная инверсия зависимостей и абстракции максимально высокого уровня

build types/flavours далеко не всем нужны

Конкретно у них есть необходимость в этом, что они и обозначили

Ответить
Развернуть ветку
Alexander Volkov

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

Ответить
Развернуть ветку
2 комментария
Раскрывать всегда